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
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.
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:
| Model | Good For | Risk |
|---|---|---|
| Native app + local dependencies | fast debugging | laptop drift |
| Docker Compose | repeatable dependencies | can diverge from Kubernetes |
| Devcontainer | consistent toolchain | heavier setup |
| Remote dev namespace | production-like integration | slower feedback, shared-state risk |
| Testcontainers-only | test isolation | not always interactive |
| Hybrid | realistic and pragmatic | needs 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
| Symptom | Likely Cause | First Checks |
|---|---|---|
| service fails at startup | config missing, port conflict, dependency unavailable | startup logs, env vars, port usage |
| 404 on endpoint | wrong base path, servlet mapping, resource not registered | Application/ResourceConfig, route logs |
| 415 unsupported media type | missing Content-Type, provider mismatch | request headers, @Consumes, JSON provider |
| 406 not acceptable | Accept mismatch | response media types, @Produces |
| DB connection refused | Postgres not running/wrong port | compose status, JDBC URL |
| migration fails | schema drift, checksum mismatch | migration table, reset script |
| Kafka consumer idle | wrong topic/group/bootstrap | topic list, consumer group, logs |
| Redis miss always | wrong key prefix/TTL/serialization | key naming, TTL, serializer |
| auth failure | local token/audience/issuer mismatch | decoded JWT, config |
| correlation ID missing | filter/MDC propagation issue | request filter, executor wrapper |
| breakpoint not hit | running different process/container | port, 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?
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.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.