Commands, events & retry routing
Digital Twin speaks two kinds of Kafka message, and they fail differently. The one rule to remember: a failed command returns an error to its sender; a failed event is re-routed to a retry topic and retried later. DTW never silently retries a command, and never returns an event failure to a publisher.
EntryRequestCommand). The sender expects a reply and gets a paired …CommandSuccessResult / …CommandFailureResult. On failure the error goes back to the caller — no retry topic.AccountStatusChangeEvent) that keeps DTW’s local copies in sync. If a consumer can’t apply it yet, it is re-routed to a retry topic and retried when the blocking condition clears.What is an “aggregate”?
An Aggregate is a cluster of related objects treated as one consistency boundary, with a single Aggregate Root as the only external entry point — a core building block of Domain-Driven Design (Eric Evans, Domain-Driven Design, 2003; Vaughn Vernon, Implementing Domain-Driven Design, 2013). Account is an aggregate root; its features and limits are members.
DTW cares because change-capture is aggregate-scoped: the aggregate type is the label a failed event carries, and it is exactly the key the retry router routes on. The type is a discriminator string on the entity mapping (@AnyDiscriminatorValue, stored in the AGGREGATE_ROOT_TYPE column).
| Service | Aggregate-root types (routing keys) |
|---|---|
| registry | ACCOUNT, ACCOUNT_TYPE, LIMIT_TYPE, HISTORY_CODE, PARTY, SYSTEM, GENERIC_DOMAIN |
| transaction | ACCOUNT, ACCOUNT_TYPE, HISTORY_CODE, SYSTEM_LIMIT, HOLD_REASON, UNSUPPORTED_GENERIC_DOMAIN, ACCOUNT_ENTRY |
| balance / statements | ACCOUNT, ACCOUNT_TYPE, HISTORY, HOLD_REASON, ACCOUNT_REG |
| entrypoints | SYSTEM_STATE |
Same entity, two discriminators (ACCOUNT_ENTRY, ACCOUNT_REG): a service consuming the same Account from two sources gives it a second discriminator so the two failure streams can route to different retry topics.
The retry flow
…_EVENT_ROUTING, …_EVENT_ROUTING_DATA) and keeps reading — the partition never stalls.retry profile, listens on the retry topic and re-attempts. Every mirroring consumer runs as two deployments: regular + retry.Where you configure it
The router maps each aggregate type to a retry topic via application.routing-topics (deploy-time config — ConfigMap or SPRING_APPLICATION_JSON). Registry and transaction are already separate because the router runs once per sub-system; within a service you split further per aggregate.
# router — aggregate type -> retry topic (Kubernetes ConfigMap)
application.routing-topics.ACCOUNT: <prefix>.dtw.transaction.retry.event.account
application.routing-topics.ACCOUNT_TYPE: <prefix>.dtw.transaction.retry.event.config.account-type
# consumer — the 'retry' profile rebinds the topic and flips the bridge
spring.config.activate.on-profile: retry
dtw-common.consumer-bridge.mode: retry
Example: a “create account” event arriving before its account-type is mirrored is re-routed to …retry.event.account and retried once the account-type lands — the main flow keeps moving.