Overview

6 The running hexagon: From pieces to system

The chapter explains how the hexagonal parts of WriteFlow move from isolated, well-defined components to a fully running application. It begins by showing that architectural boundaries need build-time enforcement, which is why the project is split into Maven modules for the shared kernel, application core, adapters, and a separate assembly module. This structure makes the dependency direction explicit, prevents inner code from depending on outer frameworks, and keeps the core portable and testable.

From there, the focus shifts to assembly, where Spring Boot is used only as the wiring mechanism. The assembly module discovers application services, adapters, and domain services through custom stereotype scanning, registers command and query buses, and provides external infrastructure beans such as clients and data sources. It also resolves the chapter’s most important runtime concern: transaction management. A technology-agnostic transactional annotation from the Architecture Kernel is bridged to Spring so that handlers can control transactional boundaries even when they return functional error values instead of throwing exceptions, preserving both rollback behavior and the separation from Spring-specific APIs.

The final part traces how events behave once the system is running. Domain events are delivered in-process to listeners, which can either participate in the same transaction or run after commit depending on the desired coupling. When a reaction must cross process or bounded-context boundaries, the chapter introduces integration events and the outbox pattern, which store the event alongside the business change in the same transaction and relay it later for reliable delivery. By combining modular structure, dependency injection, transactional control, local domain events, and durable integration events, the application becomes a coherent system rather than a collection of disconnected pieces.

Module dependency graph. Inner modules never reference outer ones. The assembly module is the only component that sees everything—its job is to wire, not to contain logic.
Assembly and runtime flow. At startup, Spring discovers beans through stereotype annotations, and the bus assembler creates buses backed by the application context. At runtime, the bus resolves handlers by command type—adapters never reference handlers directly.
The assembled WriteFlow hexagon. The assembly module wires the application at startup, while the runtime structure still respects the hexagonal boundary: input adapters enter through input ports, the bus dispatches to handlers, handlers orchestrate domain behaviour and output ports, and output adapters provide the infrastructure implementations.
Synchronous listeners execute inside the transaction; after-commit listeners execute after. The after-commit listener enters the application as a new use case through the command bus.
From handler to external delivery. The handler calls an output port whose adapter writes the integration event to the outbox within the same transaction. A relay process delivers it to external consumers. Consistency is guaranteed because the outbox write shares the domain state's transaction.

Summary

  • Maven modules enforce hexagonal boundaries at compile time. If a dependency is not in the POM, the compiler rejects the import—no runtime discipline required.
  • The spring-boot-assembly module is the only component that sees all others. It discovers hexagonal stereotypes (@ApplicationService, @Adapter, @DomainService) through annotation-based component scanning and wires the command and query buses backed by Spring's ApplicationContext.
  • The Architecture Kernel's @Transactional annotation declares transactional intent without importing framework concerns. A custom transaction interceptor bridges the gap at runtime—and solves the Either problem: rollback by value, not by exception.
  • Transaction boundaries always live at the handler level. The handler is the atomic unit of work—everything it orchestrates succeeds or fails together.
  • Domain events decouple secondary effects from the primary use case. After-commit listeners execute only when the primary transaction succeeds, preventing secondary failures from rolling back the primary operation.
  • Event listeners are input adapters. They receive a domain event and enter the application core through the command bus—same pattern as a REST controller, different trigger.
  • Integration events escape the process boundary. An output port writes them to an outbox table within the same transaction as the domain state change. A relay process delivers them externally. Consumers must be idempotent.
  • The assembly module draws a clear line: the application core compiles, runs its logic, and returns results without knowing which framework assembled it.

FAQ

Why do we use Maven modules in a hexagonal architecture?Maven modules make architectural boundaries enforceable at compile time. If a module does not declare a dependency, code inside it cannot import classes from that technology or layer, helping prevent accidental boundary violations.
What is the role of the assembly module in the running hexagon?The assembly module is the only part that sees and wires everything together. It contains startup and configuration code, but no business logic, and its job is to compose the application through dependency injection.
Why is Spring used only in the assembly layer?Spring is treated as an infrastructure concern, not part of the application core. Keeping it in the assembly layer preserves the core’s independence, improves testability, and allows the system to be assembled with a different runtime if needed.
How are handlers connected to their repository implementations at runtime?Handlers depend on repository interfaces, while persistence adapters implement those interfaces. Spring component scanning finds the beans, and dependency injection resolves the interface to the correct concrete adapter during startup.
Why does the Architecture Kernel provide its own @Transactional annotation?The Architecture Kernel’s @Transactional is technology-agnostic, so handlers can declare transactional intent without importing Spring. A starter library bridges that annotation to Spring’s transaction machinery at runtime.
How does rollback work when handlers return Either instead of throwing exceptions?A custom transaction interceptor inspects the returned Either. If the result is a Left value matching the rollback rules, it marks the transaction rollback-only, so failures can trigger rollback without exceptions.
Where should transaction boundaries be placed in hexagonal architecture?Transaction boundaries belong at the handler level, because handlers orchestrate the full use case and define the atomic unit of work. Repositories, adapters, and domain services should not own their own transaction boundaries.
Why should query handlers use @Transactional(readOnly = true)?Read-only transactions reinforce Command Query Separation. They signal that the handler should not modify state, and the database can enforce that discipline by rejecting writes inside a read-only transaction.
What is the difference between domain events and integration events?Domain events are internal events used within the same application or bounded context, often carrying domain types. Integration events are external contracts meant for other systems, so they should use stable, versionable data such as primitive IDs and timestamps.
Why is the outbox pattern needed for integration events?The outbox pattern avoids the dual-write problem by storing the integration event in the same transaction as the business change. A separate relay then publishes the event reliably, usually with at-least-once delivery.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Clean Applications with Hexagonal Architecture ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Clean Applications with Hexagonal Architecture ebook for free