# Digital Twin — Messaging Model

Messaging model

# 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.

Command — “do this”

An imperative request (e.g. EntryRequestCommand). The sender expects a reply and gets a paired …CommandSuccessResult / …CommandFailureResult. On failure the error goes back to the caller — no retry topic.

Event — “this happened”

A fire-and-forget fact (e.g. 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.

These are industry-standard patterns, not Matera coinages: Command Message vs Event Message (Hohpe & Woolf, Enterprise Integration Patterns, 2003); Command–Query Separation (Meyer, 1988); CQRS (Young / Dahan). The retry topic is EIP’s Dead Letter Channel refined into a non-blocking retry topic.

## 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).

ServiceAggregate-root types (routing keys)

registryACCOUNT, ACCOUNT_TYPE, LIMIT_TYPE, HISTORY_CODE, PARTY, SYSTEM, GENERIC_DOMAIN
transactionACCOUNT, ACCOUNT_TYPE, HISTORY_CODE, SYSTEM_LIMIT, HOLD_REASON, UNSUPPORTED_GENERIC_DOMAIN, ACCOUNT_ENTRY
balance / statementsACCOUNT, ACCOUNT_TYPE, HISTORY, HOLD_REASON, ACCOUNT_REG
entrypointsSYSTEM_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

1 · Consume & reroute
On a blocking condition the consumer persists the event to the reroute tables (…_EVENT_ROUTING, …_EVENT_ROUTING_DATA) and keeps reading — the partition never stalls.

2 · Route by aggregate
A per-sub-system router polls those tables and publishes each entry to the retry topic mapped to its aggregate type. An unmapped type is not re-routed.

3 · Retry-profile consumer
The same consumer, deployed under a 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.

Full technical reference (command payloads, per-service topic tables, config examples, reroute/idempotency table names, delivery & ordering guarantees) is in the machine-readable docs for AI coding agents: messaging-model.md, indexed from llms.txt.

← AVRO / Kafka messages
Mainframe shadowing →
