Stage 7 · Master
Event-Driven Communication
Decoupling Services with Domain Events
A design specification for Meridian's future Kafka layer: transactional services publish tenant-aware domain facts, and a planned reporting-service consumes them without joining the request path.
Why Synchronous Chains Become Brittle
A microservice architecture stops being modular the moment one service must synchronously notify every other interested service before its own request can succeed. That pattern looks workable when there are only two participants, but it degrades quickly: latency accumulates, retry rules become inconsistent, and one downstream outage turns a local write into a system-wide availability problem. The originating service becomes an orchestrator for responsibilities it does not own.
Events solve a narrower and more precise problem than many introductions imply. They are useful when one committed business fact has multiple independent downstream consequences owned by different services or read models. If identity-service creates a tenant, community-service should not need to be called inline merely because a future reporting model wants to count tenants. The source service should record the authoritative change and publish the fact that it happened; consumers should react on their own clocks.
| Cross-service pattern | Why teams reach for it | Why Meridian should choose or reject it |
|---|---|---|
| Inline HTTP fan-out | Simple to sketch with a small number of consumers | Rejected because write latency and success now depend on every downstream service |
| Shared database reads across services | No messaging infrastructure required | Rejected because it leaks table internals and destroys bounded-context ownership |
| Domain events over Kafka | Requires event contracts and consumer discipline | Chosen because independent consumers can be added without re-entering the source request path |
That is the conceptual difference from a command. A command asks another component to do work; a domain event records a fact that downstream components may react to independently.
What a Domain Event Actually Is
A domain event is a past-tense business fact with durable identity and clear scope. Good events are named after outcomes such as tenant.created, complaint.filed, or invoice.paid, not after storage implementation details like tenant_row_inserted. They carry enough information for a consumer to understand what changed without re-querying half the system just to interpret the message.
Meridian's Planned Event Map
The real meridian repository does not yet contain Kafka producers or consumers; the architecture document explicitly describes reporting as a future read-aggregator once identity, community, and billing hold real data. That makes this chapter forward-looking by design. The point is to define the event boundary before ad hoc cross-service calls appear, not to pretend the repository already ships a broker integration that does not exist.
- identity-service is the planned producer for facts such as
tenant.created,user.created, andmembership.assigned. - community-service is the planned producer for facts such as
resident.created,announcement.published, andcomplaint.filed. - billing-service is the planned producer for facts such as
invoice.issuedandinvoice.paid. - reporting-service is the planned future consumer that builds tenant-aware read models without coupling transactional services to reporting queries.
libs/platform/events/envelope.gocreateResponsibility: Define the common event envelope and any shared serialization helpers used by all producers and consumers.
Why now: Once multiple services publish and at least one service consumes, the envelope becomes shared platform vocabulary rather than service-local convenience code.
Connects to: Extends the shared-library role already played by libs/platform/config, logging, errors, response, and tenancy.
apps/identity-service/internal/events/publisher.gocreateResponsibility: Publish tenant and membership domain events after authoritative state changes in identity-service.
Why now: The service that owns the source of truth must be the service that states the fact happened.
Connects to: Will use the shared envelope from libs/platform/events and tenant vocabulary from libs/platform/tenancy.
apps/community-service/internal/events/publisher.gocreateResponsibility: Publish complaint, resident, and announcement events when community-owned state changes commit.
Why now: Community data should not be reinterpreted by another service reading its tables directly.
Connects to: Shares the same event contract as identity-service and billing-service.
apps/billing-service/internal/events/publisher.gocreateResponsibility: Publish invoice and payment events after billing-service commits financial state changes.
Why now: Payment state is especially sensitive to replay and reporting concerns, so it needs a durable integration boundary rather than informal HTTP callbacks.
Connects to: Feeds the future reporting-service and any later notification or analytics consumers.
Why Kafka Fits This Problem
Kafka is not being chosen because 'event-driven' is fashionable. It matches the specific mechanics Meridian needs once cross-service consumption begins: durable append-only logs, replay for rebuilding read models, independent consumer groups, and partitioning that can preserve order within a tenant and aggregate. Those are difficult and error-prone to reproduce with ad hoc callback registries or one-off retry loops built into each service.
Kafka also supports the most important long-term requirement: new consumers can be introduced without rewriting the transactional services that emit the events. Reporting is only the first planned consumer. Audit exports, notification digests, search indexing, or machine-learning feature extraction could arrive later, and each would subscribe independently rather than forcing identity-service, community-service, or billing-service to learn new synchronous dependencies.
Where Events Should Not Be Used
Events are not a universal replacement for request/response APIs. If a caller needs an immediate answer—authenticate a token, fetch the current complaint state, check whether an invoice belongs to a resident—an event log is the wrong tool. Events are also inappropriate for implementation noise that has no independent consumer, such as 'cache refreshed' or 'query executed.' The discipline is to publish business facts that other bounded contexts genuinely need, and to keep synchronous APIs for present-tense questions and commands.
The Decision
Meridian's planned event architecture uses Kafka to decouple future consumers from the request paths of identity-service, community-service, and billing-service. Events are defined as tenant-aware domain facts with stable identity, not as table-change noise or synchronous commands in disguise. The real repository has not implemented this layer yet; this chapter therefore serves as the design contract that later producers and the planned reporting-service should follow.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.