3. Intent Topics per Aggregate, Schemas Owned by the Service

Who defines intent schemas, and why intents are published on one topic per aggregate to keep their order.
  • Status: accepted
  • Date: 2025-07-17 (ordering rule 2025-04-24)
  • Deciders: Jannik Alexander, Stefan Bente, Daniel Köllgen (governance board)

Context

Players send their commands as intent events (ADR-0002). This leaves two questions: who owns the intent schema — the player who sends it, or the service that acts on it? And how are intents spread over topics?

Intents for the same robot must be processed in the order they were sent (e.g. “move north”, then “hit”). The expected load is high: 20 players with 500 robots each, sending 10 intents per second per robot, is 100,000 intents per second.

Decision

  • The core service that acts on an intent MUST define its schema and publish it in its AsyncAPI specification. Players implement against it.
  • Intents MUST be published to one topic per aggregate — currently robot.intents.v1 (Robot service) and voucher.intents.v1 (Trading service).
  • All intents addressed to one aggregate MUST use that aggregate’s single topic, whatever the intent type. Otherwise intents sent in sequence could be processed in a different order.
  • Intents SHOULD be keyed by the id of the entity whose order matters (e.g. robot id), so they land on the same partition. Voucher intents are keyed by player id, as the voucher does not exist yet.
  • An intent carries the headers event_id (UUID, for duplicate detection), event_type and event_type_version. It MUST NOT rely on sequence_id, since a value set by players cannot be trusted.
  • Core services do not provide an outbox for players. Players MAY implement one.

Alternatives

  • Player-owned schemas — rejected because services would have to implement whatever players define.
  • One topic for all intents — rejected because every core service would have to filter 100,000 intents per second for the few it owns.
  • One topic per service — rejected because it bakes service names into the topology, against ADR-0005.
  • One topic per intent type — rejected because it loses all ordering between different intents for the same robot.

Consequences

  • Good: ordering per aggregate instance comes from Kafka partitioning, not from custom sequencing logic.
  • Good: each service consumes only the intents it owns, and versions their schemas itself.
  • Bad: intents on different aggregates’ topics are not ordered relative to each other (e.g. a voucher purchase and a robot move).
  • Bad: intents that concern two aggregates need a decision which topic they belong on.

Current State

  • Intent topics are named <aggregate>.intents.v1, without a message-type prefix.
Last modified October 22, 2026: Update documentation (f481966)