Series MapLesson 94 / 112
Focus mode active/Press Alt+Shift+R to toggle/Esc to exit
Final StretchOrdered learning track

Local Engineering Workflow

Developer Productivity and Local Engineering Workflow

Local development, Docker Compose, devcontainer, mock external services, test fixtures, seed data, local PostgreSQL/Kafka/Redis, and debugging JAX-RS services locally or in containers

5 min read989 words
PrevNext
Lesson 94112 lesson track93–112 Final Stretch
#local-development#docker-compose#devcontainer#debugging+5 more

Part 094 — Developer Productivity and Local Engineering Workflow

Fokus part ini: membangun workflow lokal yang memungkinkan engineer memahami, menjalankan, menguji, dan men-debug service enterprise JAX-RS dengan cepat tanpa mengorbankan production parity. Targetnya bukan “jalan di laptop saya”, tetapi local feedback loop yang realistis, repeatable, aman, dan cukup dekat dengan production behavior.

Catatan penting:

This part does not assume CSG uses Docker Compose, devcontainer, WireMock,
Testcontainers, local PostgreSQL, local Kafka, local Redis, remote shared dev
services, or any specific onboarding tool.

Treat all local workflow, dependency, seed data, and debugging setup as internal
verification items.

Local development is not a convenience layer only.

It is part of engineering throughput and defect prevention.

A weak local workflow creates slow feedback, untested assumptions, fragile onboarding, and production-only debugging.


1. What a Good Local Workflow Must Achieve

A good local workflow lets an engineer answer quickly:

Can I start the service?
Can I call a JAX-RS endpoint?
Can I connect to local or controlled dependencies?
Can I run database migrations?
Can I seed realistic data?
Can I debug a request end-to-end?
Can I reproduce a failing test?
Can I inspect logs, SQL, Kafka messages, and Redis keys?
Can I safely mock external services?
Can I switch between local and shared-dev config without leaking secrets?

The goal is not perfect production replication.

The goal is intentional parity.

Production parity where correctness depends on it.
Simplification where local cost would be too high.
Explicit mocks where external dependency is not safe or practical.

2. Local Development Mental Model

A JAX-RS backend service usually depends on many runtime surfaces.

flowchart LR Dev[Developer IDE] --> App[JAX-RS Service] Dev --> Tests[Unit / Integration Tests] App --> PG[(Local PostgreSQL)] App --> Kafka[(Local Kafka)] App --> Redis[(Local Redis)] App --> Mock[Mock External Services] App --> Config[Local Config] App --> Logs[Structured Logs] App --> Debugger[Debugger / JDWP]

Local workflow should make those surfaces visible.

Hidden local magic is dangerous.

If the service silently connects to a shared database, shared Kafka cluster, or real downstream service, local testing can mutate shared state.

Make environment targeting explicit.


3. Local Environment Options

Common models:

ModelGood ForRisk
Native app + local dependenciesfast debugginglaptop drift
Docker Composerepeatable dependenciescan diverge from Kubernetes
Devcontainerconsistent toolchainheavier setup
Remote dev namespaceproduction-like integrationslower feedback, shared-state risk
Testcontainers-onlytest isolationnot always interactive
Hybridrealistic and pragmaticneeds clear documentation

A mature team often uses more than one:

IDE/native for fast endpoint debugging
Docker Compose for dependencies
Testcontainers for integration tests
remote dev namespace for platform integration

Avoid one-size-fits-all local setup.

The best workflow depends on task type.


4. Minimal Local Golden Path

A good onboarding path should be boring.

Example target:

# 1. Clone repository
git clone <repo-url>
cd quote-service

# 2. Start dependencies
docker compose up -d postgres kafka redis mockserver

# 3. Run migrations
./mvnw -pl service-db-migration flyway:migrate

# 4. Seed data
./scripts/local/seed-data.sh

# 5. Start service
./mvnw -pl service-api exec:java -Dexec.args="server local"

# 6. Call health endpoint
curl -i http://localhost:8080/health

# 7. Call a sample JAX-RS endpoint
curl -i http://localhost:8080/api/v1/quotes/example

The internal version may differ.

The principle is the same:

one documented path
clear prerequisites
no hidden manual steps
safe local config
repeatable reset

5. Docker Compose for Local Dependencies

Docker Compose is useful when the service depends on infrastructure components.

Example local dependency skeleton:

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: quote_local
      POSTGRES_USER: quote
      POSTGRES_PASSWORD: quote
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7
    ports:
      - "6379:6379"

  kafka:
    image: apache/kafka:latest
    ports:
      - "9092:9092"

  mockserver:
    image: wiremock/wiremock:latest
    ports:
      - "8089:8080"

volumes:
  pgdata:

Do not copy this blindly into internal code.

Verify internal image standards, approved versions, and whether the organization uses Confluent images, Redpanda, LocalStack, Azurite, internal mocks, or shared platform services.

Docker Compose is a local convenience, not a production deployment model.


6. Devcontainer and Toolchain Consistency

A devcontainer can standardize:

JDK version
Maven version
Node/CLI tools
kubectl/helm/terraform tools
database client
Kafka client tools
pre-commit hooks
formatter/linter

Example shape:

{
  "name": "quote-service-dev",
  "image": "mcr.microsoft.com/devcontainers/java:17",
  "features": {
    "ghcr.io/devcontainers/features/docker-in-docker:2": {}
  },
  "customizations": {
    "vscode": {
      "extensions": [
        "vscjava.vscode-java-pack",
        "redhat.vscode-yaml"
      ]
    }
  }
}

Benefits:

less laptop drift
faster onboarding
consistent scripts
consistent toolchain

Risks:

slower startup
Docker-in-Docker complexity
hidden coupling to IDE
outdated dev image

Treat devcontainer as optional unless team standard says otherwise.


7. Local Configuration and Safe Secrets

Local configuration should be explicit and safe.

Example local properties:

app.environment=local
app.instanceId=local-${USER}

http.port=8080

postgres.url=jdbc:postgresql://localhost:5432/quote_local
postgres.username=quote
postgres.password=quote

kafka.bootstrapServers=localhost:9092
redis.url=redis://localhost:6379

external.pricing.baseUrl=http://localhost:8089/pricing
external.catalog.baseUrl=http://localhost:8089/catalog

security.auth.mode=local-dev

Rules:

Never require production secrets locally.
Never commit real credentials.
Never default local service to shared production/staging dependency.
Make target environment visible in startup logs.
Fail fast on missing required config.
Use fake/local credentials for local dependencies.

Startup log should say:

environment=local
postgres=localhost:5432/quote_local
kafka=localhost:9092
redis=localhost:6379
externalPricing=http://localhost:8089/pricing
authMode=local-dev

Without leaking passwords.


8. Local PostgreSQL Workflow

A local database should support:

run migrations
reset schema
seed baseline data
inspect tables
reproduce SQL bugs
run integration tests

Useful commands:

# connect
psql postgresql://quote:quote@localhost:5432/quote_local

# list tables
\dt

# inspect slow-ish query locally
EXPLAIN ANALYZE
SELECT * FROM quote WHERE status = 'ACTIVE' ORDER BY created_at DESC LIMIT 50;

A reset script is valuable:

#!/usr/bin/env bash
set -euo pipefail

psql "$LOCAL_DB_URL" -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public;'
./mvnw -pl db-migration flyway:migrate
./scripts/local/seed-data.sh

Good seed data is intentional.

It should include:

simple happy-path quote
expired quote
multi-line quote
tenant A and tenant B data
edge date/time case
currency/rounding case
invalid/downstream-failure scenario
large list/search dataset

Seed data should not be a random dump from production.


9. Local Kafka Workflow

Local Kafka should support:

create topics
produce test events
consume events
inspect headers
replay small scenario
verify outbox publisher
verify consumer idempotency

Example commands depend on the image/tooling, but the workflow is:

# list topics
kafka-topics --bootstrap-server localhost:9092 --list

# consume from beginning
kafka-console-consumer \
  --bootstrap-server localhost:9092 \
  --topic quote.events \
  --from-beginning

# produce a test event
kafka-console-producer \
  --bootstrap-server localhost:9092 \
  --topic quote.commands

For local testing, create predictable topic names:

local.quote.events
local.order.events
local.deadletter.quote

Avoid sharing topic names with real environments.

When debugging event-driven behavior, inspect:

topic
partition
offset
key
headers
payload
schema version
consumer group
commit position

10. Local Redis Workflow

Local Redis should support:

inspect cache keys
clear local cache
test TTL behavior
test idempotency keys
test rate limiter behavior
test lock expiry

Useful commands:

redis-cli -h localhost -p 6379 PING
redis-cli KEYS 'quote:*'
redis-cli TTL quote:123
redis-cli GET quote:123
redis-cli DEL quote:123

For local debugging, avoid KEYS against real/shared Redis.

Use SCAN in non-local environments.

Local key naming should make environment obvious:

local:quote:{quoteId}
local:idem:{requestId}
local:lock:{jobName}

11. Mock External Services

External services should usually be mocked locally unless there is a safe shared dev endpoint.

Mocking options:

static JSON fixture
WireMock-like HTTP mock
contract stub generated from OpenAPI
fake service implementation
local simulator such as LocalStack/Azurite if appropriate

A mock should not only return 200.

It should support failure scenarios:

200 success
400 validation failure
401/403 auth failure
404 not found
409 conflict
429 rate limit
500 technical failure
503 temporary unavailable
timeout
slow response
malformed response

Example WireMock-style stub concept:

{
  "request": {
    "method": "POST",
    "urlPath": "/pricing/calculate"
  },
  "response": {
    "status": 200,
    "headers": {
      "Content-Type": "application/json"
    },
    "jsonBody": {
      "currency": "USD",
      "total": "123.45"
    }
  }
}

A mock that never fails creates false confidence.


12. Debugging JAX-RS Endpoint Locally

Debugging path:

HTTP client/curl/Postman
  -> local port
  -> servlet/Jersey routing
  -> filters/interceptors
  -> resource method
  -> service layer
  -> repository/client/event publisher
  -> response/error mapper

Useful local request:

curl -i \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'X-Correlation-ID: local-debug-001' \
  -X POST http://localhost:8080/api/v1/quotes \
  --data '{"customerId":"C-001","currency":"USD"}'

Breakpoints to set:

request filter
auth/tenant resolver
resource method
validator/custom validator
mapper
service method
repository query
HTTP client wrapper
exception mapper
response filter

Always debug with correlation ID.

Local logs should include the same correlation ID from request start to response end.


13. Debugging Containerized Service

Sometimes the service only behaves correctly inside container packaging.

Enable remote debugging only for local/dev.

Example JVM option:

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005

Docker Compose snippet:

services:
  quote-service:
    image: quote-service:local
    ports:
      - "8080:8080"
      - "5005:5005"
    environment:
      JAVA_TOOL_OPTIONS: >-
        -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005

Never expose debug ports in production.

Debug ports are remote code execution risk.

Container debugging helps catch:

missing files
wrong working directory
wrong classpath
container memory behavior
non-root filesystem permission
timezone difference
DNS difference
network path difference

14. Local Auth and Identity Simulation

Enterprise APIs often require OAuth/OIDC/JWT claims.

Local development needs a safe identity model.

Possible approaches:

local-dev auth bypass with explicit warning
static signed local JWT
local identity provider
mock token introspection endpoint
test security context injection

Rules:

Do not disable auth silently.
Do not make local bypass available in non-local profile.
Log local auth mode clearly.
Test authorization separately from happy-path local bypass.
Include tenant/user/role claims in local fixtures.

Example local claims:

{
  "sub": "local-user-1",
  "tenant": "tenant-a",
  "roles": ["QUOTE_READ", "QUOTE_WRITE"],
  "aud": "quote-service-local",
  "iss": "local-dev-idp"
}

15. Test Fixtures and Seed Data

Fixtures should encode business edge cases.

Do not create only “John Doe happy path”.

Fixture categories:

minimal valid request
maximal valid request
missing required field
unknown enum
large payload
multi-tenant data
expired quote
future-effective catalog
pricing rounding edge
downstream timeout
duplicate idempotency key
stale event replay

Good fixtures are named by intent:

quote_active_single_line.json
quote_expired_pending_reconciliation.json
pricing_rounding_half_even_usd.json
order_submitted_event_v2.json
catalog_effective_future_version.json

Bad fixtures:

test1.json
data.json
sample.json

Fixtures become a shared language for debugging.


16. Local Troubleshooting Matrix

SymptomLikely CauseFirst Checks
service fails at startupconfig missing, port conflict, dependency unavailablestartup logs, env vars, port usage
404 on endpointwrong base path, servlet mapping, resource not registeredApplication/ResourceConfig, route logs
415 unsupported media typemissing Content-Type, provider mismatchrequest headers, @Consumes, JSON provider
406 not acceptableAccept mismatchresponse media types, @Produces
DB connection refusedPostgres not running/wrong portcompose status, JDBC URL
migration failsschema drift, checksum mismatchmigration table, reset script
Kafka consumer idlewrong topic/group/bootstraptopic list, consumer group, logs
Redis miss alwayswrong key prefix/TTL/serializationkey naming, TTL, serializer
auth failurelocal token/audience/issuer mismatchdecoded JWT, config
correlation ID missingfilter/MDC propagation issuerequest filter, executor wrapper
breakpoint not hitrunning different process/containerport, command, IDE config

17. Local Safety Rules

Local workflow must not accidentally mutate real systems.

Rules:

local profile must be explicit
real production secrets must not be accepted locally
startup logs must show target environment
shared-dev dependencies must be opt-in
dangerous operations need local-only safeguards
local topics/keys/schemas should be namespaced
local tenant IDs should be fake
production-like data must be anonymized if ever used

A local config that defaults to a shared environment is dangerous.

Prefer fail-fast:

No explicit environment? fail startup.
No local DB URL? fail startup.
No mock downstream URL? fail startup.

18. Internal Onboarding Checklist for Local Setup

Verify these in the internal CSG onboarding material/codebase:

[ ] Required JDK version
[ ] Required Maven version
[ ] Required Docker/Docker Compose version
[ ] Whether devcontainer is supported or mandatory
[ ] How to start local PostgreSQL
[ ] How to run migrations locally
[ ] How to seed data locally
[ ] How to start local Kafka or connect to shared dev Kafka
[ ] How to create local topics
[ ] How to start local Redis
[ ] How external services are mocked
[ ] How auth/JWT is simulated locally
[ ] How tenant/user/role data is configured locally
[ ] How to run unit tests
[ ] How to run integration tests
[ ] How to run a single test class
[ ] How to debug a JAX-RS endpoint from IDE
[ ] How to debug the containerized service
[ ] How to reset local state safely
[ ] How to inspect logs/metrics/traces locally if supported
[ ] How to avoid connecting to production/staging accidentally

19. PR Review Checklist

When reviewing local workflow changes, ask:

[ ] Does this reduce or increase onboarding complexity?
[ ] Is the local path documented?
[ ] Are dependencies pinned to safe versions/images?
[ ] Does the local setup avoid real secrets?
[ ] Does startup clearly show target environment?
[ ] Are seed data and fixtures meaningful?
[ ] Can a developer reset local state easily?
[ ] Are mocks realistic enough to test failure modes?
[ ] Does local setup support multi-tenant scenarios?
[ ] Are debug ports restricted to local/dev?
[ ] Does Docker Compose conflict with Kubernetes assumptions?
[ ] Are local scripts portable enough for the team?
[ ] Are there CI equivalents for critical local checks?
[ ] Does this add hidden dependency on a shared resource?

20. Senior Engineer Mental Model

Developer productivity is architecture in disguise.

A poor local workflow produces:

slow feedback
shallow testing
unsafe manual setup
tribal knowledge
production-only debugging
fragile onboarding

A good local workflow produces:

fast feedback
repeatable setup
safe dependency targeting
realistic fixtures
clear debugging path
higher-quality PRs
lower onboarding cost

For a senior engineer, the question is not only:

Can I run it locally?

The better question is:

Can a new engineer reproduce a realistic production scenario locally,
understand every dependency boundary, debug a failure, reset safely,
and avoid touching real systems by accident?
Lesson Recap

You just completed lesson 94 in final stretch. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.