Distinguish domain, integration and notification events by audience, payload and contract strength, raise domain events from aggregates, translate them into integration events via an outbox, design thin notifications with call-back, and choose the right type per consumer relationship
Not all events serve the same audience. Domain events express something meaningful that happened inside a bounded context, in its own language. Integration events are the curated, versioned facts a context publishes for other contexts. Notification events are thin signals — "something changed, here's its id" — that prompt consumers to fetch details if they need them.
Figure 1 — Event types (TOC)
TOC type
Definition
Domain
An event raised by the domain model (often by an aggregate) inside a bounded context, using its ubiquitous language and internal types, consumed by the same context's handlers; free to change with the model
Integration
An event published across bounded-context boundaries as part of the context's public contract — curated payload, stable schema, versioned, backward compatible, typically on a broker topic
Notification
A minimal event (type + identifier, perhaps a link) telling consumers that something changed without carrying the state; consumers call the source for current data if needed
Table 1 — The three TOC types
2Why It Exists
Publishing domain events directly couples every consumer to your internal model; publishing only notifications couples every consumer to your API at runtime. Separating types lets you keep internal freedom and external stability.
Each event type solves a different coupling problem, and using one type for everything recreates the problem the others avoid. Inside a context, fine-grained domain events let modules react to changes without direct calls, and they can change as freely as the model. Across contexts, consumers need stable facts with a schema, versioning and an owner, so the publishing team can still refactor internally. For external partners or sensitive data, a thin notification lets the source keep control of authorisation and avoids sending data to places it should not go. Recognising the audience of each event, private, internal-public or external, tells you how much contract, documentation and compatibility discipline it needs.
Order-svc serialised its JPA Order entity straight onto Kafka. Renaming an internal enum broke four consumer teams. Introducing OrderPlacedV1 integration events with an explicit mapper restored the team's freedom to refactor.
6.2 Partner webhooks
Marketplace sellers wanted order updates. ShopNow sends signed notification webhooks (order.updated + id); sellers call the partner API for details, which enforces authorisation per seller.
Fine-grained public events mirroring internal steps.
Most of these mistakes come from not deciding who an event is for. Serialising JPA entities or internal domain events onto Kafka is the most common, and it makes every internal refactoring a cross-team breaking change. Notification-only designs hit the opposite problem when many consumers call back at once and overload the producer. Publishing before commit creates phantom events for transactions that later roll back; listeners must run after commit, or events must go through an outbox. Unversioned schemas break consumers silently. Personal data such as addresses ends up on broadly readable topics because adding a field was easy. Finally, public events that mirror every internal step, such as DraftSaved and LineAdded, expose the workflow and force consumers to reassemble business facts themselves.
8Performance & Production Considerations
Aspect
Guideline
Domain events
In-process, after commit
Integration events
Outbox, registry, compatibility
Notifications
Signed, rate-aware callbacks
Payload size
Curated, not dumps
Docs
AsyncAPI catalog
Table 25 — Operational guidelines
9Architecture & Design Considerations
Keep domain events private.
Design integration events as published language.
Use notifications for partners, large or sensitive data.
Choose per consumer relationship.
10Interview Questions & Answers
Q1BasicConcept
What is a domain event?
A domain event is a record that something meaningful happened in the domain model of one bounded context, expressed in that context's ubiquitous language, such as OrderPlaced or LineItemRepriced. It is usually raised by an aggregate when a state change succeeds, for example through Spring Data's AbstractAggregateRoot.registerEvent, and published when the repository saves the aggregate. Its consumers are handlers in the same context or modular monolith, which react after commit using @TransactionalEventListener or Spring Modulith's @ApplicationModuleListener. Because it is private, it can use internal value objects and enums, be fine-grained and change whenever the model changes. That freedom is exactly why it should not be published directly to other teams.
Q2BasicConcept
What is a notification event?
A notification event is a thin signal that something changed, carrying only the event type, an identifier and perhaps a link, not the changed state. A CloudEvents message of type shopnow.product.changed with subject sku/SKU-4411 is typical. Consumers that care call back to the source's API to fetch the current data, so they always see the latest state and the producer's authorisation rules apply on every read. This is Fowler's event notification pattern. It suits external partners through signed webhooks, large or sensitive payloads such as customer addresses, and data that changes often. The cost is runtime coupling: consumers depend on the producer's API being available and fast.
Q3IntermediateTricky
Why not publish domain events directly to other services?
Because a domain event is part of your internal model, so publishing it turns your internal model into a public contract. Domain events use internal value objects, enums and names that change whenever the team refactors. Once other teams consume them, every rename or restructuring becomes a breaking change that needs coordination, so your context loses its freedom to evolve. Domain events are also fine-grained and may expose fields others should not see. The note's example: order-svc serialised its JPA entity to Kafka, and renaming one enum broke four consumer teams. The fix is to translate domain events at the boundary into curated, versioned integration events, such as OrderPlacedV1, using primitive and standard types.
Q4IntermediateConcept
What makes an integration event a contract?
An integration event is a contract because other contexts build on it, so the publisher promises stability. It is curated: only the fields consumers need, in agreed business vocabulary and portable types, for example totalPaise and currency instead of an internal Money object. It has an explicit schema in a schema registry such as Confluent or AWS Glue, with compatibility rules, usually backward compatibility, enforced on every change. It is versioned, with names like OrderPlacedV1 and topic orders.events.v1, and a breaking change creates a new version while the old one runs in parallel. It is documented in AsyncAPI with an owner, and reviewed like a public API before release.
Q5Senior EngineerTrade-off
When are notification events a poor choice?
Notification events are a poor choice when many consumers would call back for the same data, when the producer's availability is a concern, or when consumers need the history of changes. Each notification triggers a call per consumer, so twelve consumers and 3,500 events a minute produce 42,000 API calls, a call-back storm that can overload the producer at peak. Every consumer also depends at runtime on the producer's API, so its outage stops all of them. Because notifications are thin, a consumer that misses intermediate states cannot reconstruct what happened. In those cases event-carried state transfer, integration events with the needed state, is usually better, accepting larger payloads and eventual consistency.
Q6Senior EngineerInternals
How do you publish integration events reliably from domain events?
The key is to make publishing part of the same transaction as the state change, then deliver asynchronously. The aggregate raises a domain event, and a translator listens after commit and maps it to an integration event. With Spring Modulith, the event publication registry stores each publication in the database within the business transaction, and @Externalized routes the integration event to a Kafka topic with a key, so incomplete publications are retried after a restart. Without Modulith, the transactional outbox pattern does the same: write the event to an outbox table in the same transaction, then relay it with a poller or Debezium. Consumers must still be idempotent, because delivery is at least once.
Q7Technical ArchitectScenario
How do you serve analytics that need every field?
Give analytics a different contract instead of bloating integration events. Integration events are deliberately curated for operational consumers; adding every column to satisfy the data platform would expose internals, make the schema change constantly and couple every consumer to those changes. For analytics that need every field and full history, a change data capture stream from the database, using Debezium or AWS DMS into Kafka or a data lake, is usually better, ideally owned as a data product with documented semantics and access controls. Sensitive fields can be masked or tokenised in that pipeline. The operational integration events stay small and stable, and the analytics contract can evolve on its own terms.
Q8Solution ArchitectDesign
How would you govern event types across teams?
I would set a few clear rules and automate their checks. First, a naming convention such as shopnow.order.placed.v1 and one topic per context, keyed for ordering. Second, a rule that entities and domain events never go onto shared topics, enforced in review and with ArchUnit checks that published types live in an api package. Third, integration events are reviewed like public APIs, registered in a schema registry with backward-compatibility checks in CI, and documented in an AsyncAPI catalog with an owner. Fourth, notifications are the default for external partners and sensitive data, signed with HMAC. Finally, regular reviews look for notification storms and over-fine public events.
Newsletter
Get new tutorials every week
New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.
Your subscription could not be saved. Please try again.
Almost done! Check your inbox and click the confirmation link to complete your subscription.