Architectural Patterns

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

Welcome to the Architectural Patterns reference. This section applies domain-driven design (DDD) to microservice architectures: it shows how to structure a single service’s code around its domain model, and, above all, why the data-consistency toolbox a monolith takes for granted — one ACID transaction spanning everything it touches — stops working the moment every service owns its own database. From there it builds up, pattern by pattern, the toolbox that replaces it: sagas coordinating local transactions with compensations, domain events as the currency those sagas exchange, the transactional outbox and listen-to-yourself patterns for publishing those events reliably, CQRS for the queries a decomposed data model can no longer answer directly, and event sourcing as an alternative persistence model built entirely out of events. The tactical DDD building blocks — entities, value objects, aggregates, repositories, factories and domain services — are what make "one transaction, one aggregate" possible in the first place, so this section covers them in depth too. Deciding whether to split a system into services in the first place, and the strangler-style migration that gets you there, was previously left out of scope here; that material now lives in the Architecture Decisions and Migration Paths reference, the other half of the Architecture reference.

Every code example in this section shares one running domain — an e-commerce Order / Inventory / Payment / Shipping set of bounded contexts — and is written and verified against Java 21, Spring Boot 4.1.x (Spring Framework 7.0.x), Spring Data JPA, Spring Modulith 2.x and Spring for Apache Kafka 4.1.x. The domain model itself stays plain Java — records, sealed interfaces, no framework — and Spring only appears in the application and infrastructure layers.

If you are new to this section, read Strategic Design for Microservices first, then Structuring a DDD Microservice’s Code, then the tactical building blocks — Entities, Value Objects, Aggregates and Repositories, Factories, and Domain Services — before moving on to Why Distributed Transactions Don’t Work Across Microservices, Sagas, Domain Events and Event Storming, then The Transactional Outbox Pattern and Listen to Yourself, CQRS and Event Sourcing, and finally Putting It All Together: The Place Order Flow, which assembles every prior page’s code into one coherent flow.

This section complements, rather than duplicates, four existing references. The SpringBoot Reference’s Architectural Patterns page gives a shorter, introductory tour of the same vocabulary — hexagonal architecture, the outbox, listen-to-yourself, a saga diagram — and now points here for the deeper treatment; its SpringBoot Reference landing page is the place for the Spring Data repository and JPA-mapping mechanics this section assumes rather than re-explains. The Messaging Reference’s Messaging Patterns in Practice documents the same outbox, saga and event-sourcing/CQRS patterns from the broker side — Kafka topics, RabbitMQ exchanges, log compaction — and its Kafka Connect page is the reference for the Debezium-based transaction-log-tailing option this section’s outbox page links to rather than repeats. The Quarkus Reference’s Datasources and Transactions page covers Narayana LRA as the Quarkus-native realization of saga compensation. And Choosing the Right Database covers CQRS from the storage-selection angle — polyglot persistence and CDC pipelines — which this section’s CQRS page complements with the command/query model split, view design and query services.

What’s covered

Foundations

  • Strategic Design for Microservices — domains and subdomains, the ubiquitous language, the bounded context as the unit that owns both a model and a database, the context map’s relationship patterns, and the three integration styles between contexts.

  • Structuring a DDD Microservice’s Code — transaction script vs. domain model, application services as the transaction boundary, inbound/outbound adapters, and the Maven module and Java package layout the running example follows throughout this section.

Tactical building blocks

  • Entities — identity over attributes, rich behavior instead of getters/setters, a state-machine view of OrderStatus, and JPA mapping of a typed identifier.

  • Value Objects — value objects defined by the absence of identity, equality by attributes, immutability, self-validation, when to promote a primitive to a value object, and their JPA persistence as @Embeddable, @ElementCollection, or JSON columns.

  • Aggregates — the tactical DDD building block that ties entities and value objects to a transactional consistency boundary — fuzzy boundaries, Richardson’s and Vernon’s rules, granularity trade-offs, optimistic locking, and eventual consistency between aggregates.

  • Repositories, Factories, and Domain Services — the collection-like Repository port and its Spring Data JPA adapter, static-factory vs. dedicated-factory construction, domain services vs. application-service orchestration, and modules as the coarsest-grained building block.

Consistency across services

  • Why Distributed Transactions Don’t Work Across Microservices — why the ACID transaction a monolith takes for granted stops at the service boundary — two-phase commit’s mechanics and real failure modes, the availability-product and CAP-theorem arguments against it, and the three strategies that replace it in practice.

  • Sagas — message-driven sequences of local transactions coordinating the Place Order Saga across bounded contexts — compensating transactions, choreography vs. orchestration, the ACD lack of isolation and its countermeasures.

  • Domain Events — past-tense facts about an aggregate — naming, the envelope and its metadata, minimal vs. enriched payloads, how the aggregate hands events off, in-process and out-of-process consumption, and event versioning.

  • Event Storming — the collaborative workshop that discovers domain events, commands, and aggregates before any code is written, and how the running domain’s own board maps onto the pages this section already covers.

  • The Transactional Outbox Pattern — how a service publishes a domain event reliably in the same local transaction as the state change it describes — the dual-write problem, the outbox table, polling publishers vs. transaction-log tailing, Spring Modulith’s event publication registry, and idempotent consumers on the receiving end.

  • Listen to Yourself — publishing a domain event before applying it to the publisher’s own database, and consuming that same event as the one code path that updates local state, contrasted with the transactional outbox and event sourcing.

  • CQRS — why cross-service queries need a different answer than the write model, the command side vs. query side split, designing a view module, idempotent projections, replication lag, and the canonical OrderHistoryView read model.

  • Event Sourcing — the trouble with state-based persistence, the append-only event store, reconstituting state by replay, snapshots and evolving events, and the canonical EventSourcedOrder decide/apply split.

Putting it all together

  • Putting It All Together: The Place Order Flow — assembles every pattern in this section into one worked example — the happy path and the payment-failure path through the Place Order Saga, end to end from an HTTP request to the CQRS read model, plus a checklist of what to test at each layer.

Reference

Bibliography

The books above are consulted references only, never the primary source for any code example: on any discrepancy, the pattern authors' current online catalogues — microservices.io, the Fowler bliki, the Domain Language DDD Reference — and the Spring / Java framework documentation are authoritative, and every code example in this section is verified against them at implementation time.