FAQ¶
Questions a new engineer usually hits in the first week.
Why can't a service call the Kafka client API directly?¶
Because a direct write and a Kafka publish can't commit atomically - if the database write
succeeds and the Kafka publish fails (or vice versa), the system is left inconsistent with no
clean way to detect it. The transactional outbox (OutboxService.publish(...)) writes the
event to a table in the same database transaction as the domain change, so they succeed or fail
together; Debezium then relays that row to Kafka asynchronously. On the consuming side, the
inbox guarantees a redelivered message is only ever acted on once. See
Events & Messaging and
ADR-009.
Why can't a service depend on platform-core directly?¶
platform-core modules are framework-agnostic building blocks with no Spring dependency at all -
they are not meant to be wired up by hand in every service. The platform-starters/* modules
wrap them with Spring auto-configuration, so a service gets working, tested integration for free
by adding one starter dependency instead of hand-assembling platform-core pieces. The one
narrow exception is platform-event-contracts (schema-only, no runtime behavior). See
ADR-018.
How do I add a new event?¶
- Define the Avro schema under
platform/platform-event-contracts/src/main/avro/, following thedomain.event.v1naming convention. - Add the payload record and register the subject in that module's pom.
- Publish it from a command handler via
OutboxService.publish(aggregateType, aggregateId, eventType, payload)- never a direct Kafka call. - Add a Debezium outbox connector entry under
infra/docker/kafka-connect/connectors/if the producing service doesn't already have one. - Add a
*EventSchemaCompatTestin the producing service that checks the real payload class against the registered schema - this becomes a standing CI gate. - Add a row to the Event Catalog's registry table.
See ADR-019 for the full governance rule.
How do I add a new service?¶
Start from Development Workflow. In short: copy
service-template or reference-service, declare its architecture mode and infrastructure
profile, add only the starters it needs, register it in the Maven reactor and in
microservices/configs/, and add it to the Service Catalog.
Why does my service need to declare an "architecture mode"?¶
So a reader (human or agent) never has to guess how much structure to expect. A service that says "Simple Service Layer" is CRUD and should stay that way; one that says "CQRS + Mediator" or "Domain Orchestration" is expected to actually use the mediator pipeline and, for orchestration, coordinate other services through events rather than direct calls. Declaring the wrong mode - or mixing modes without approval - is treated as an architecture violation. See ADR-004.
The local stack won't start / runs out of memory. What do I do?¶
Bring up only the profile you actually need instead of the full stack - make dev (core + auth)
is usually enough for feature work on a single service. If you need more services running
simultaneously, use the curated make infra-sprint16-e2e subset rather than make full. See
Getting Started for the full profile table and
the memory-budget note.
A service won't start after make infra-destroy and re-up. Why?¶
infra-destroy deletes data volumes, so Postgres, Kafka topics, Keycloak's realm data, and MinIO
buckets are all gone. Debezium connectors also need to be re-registered after a fresh start
(make infra-connectors) since they depend on PostgreSQL publications that no longer exist post-
destroy. If only one service is unhealthy, check make infra-logs S=<container> first before
assuming a wider problem.
Where do I find the actual current delivery status, not just the roadmap?¶
docs/tasks/STATUS.md - it is the live dashboard, updated every time a
feature's state changes, and it is intentionally honest about what was live-verified versus only
authored/compiled. The Roadmap & Status page here is a snapshot that will
go stale; STATUS.md will not.
What does "gateway-behind-trust" mean?¶
The API Gateway is the only component that validates the incoming JWT. It strips any
client-supplied X-User-Id/X-User-Roles headers and re-injects them from the verified token
before forwarding the request. Downstream services trust those headers because only the gateway
can set them from a valid JWT - they don't re-validate the JWT themselves by default (though
starter-security can do so for defense-in-depth). See
Security & Identity.
Something here contradicts an ADR. Which one wins?¶
The ADR. CLAUDE.md's rule is explicit: ADRs win over prose, a tech-lead decision wins over
everything, and the docs/tasks backlog is authoritative for delivery status. This documentation
site follows the same rule - if a curated page here and an ADR ever disagree, treat that as a bug
in this site, not in the ADR.