4.0 KiB
order-fulfillment
Companion code for Spring Modulith 2.1: Enforcing Module Boundaries Inside a Spring Boot Monolith on ankurm.com.
A 4-module Spring Boot monolith — order, inventory, shipping, notification — verified
with Spring Modulith's ApplicationModules.verify(). Everything in the post was produced by
actually running this code; see output/ for every captured transcript, in order.
Versions
| Artifact | Version |
|---|---|
| Spring Boot | 4.1.1 |
| Spring Modulith | 2.1.1 (current GA; 2.2 exists only as milestones as of this writing — verified against Maven Central's maven-metadata.xml, not the project's own <release> tag, which has pointed at a milestone before) |
| JDK | 25 (LTS) |
| H2 | 2.4.240 (in-memory, for the demo only) |
Quickstart
mvn -pl order-fulfillment -am test
Runs the module-boundary verification, the documentation generator, and the full
order → inventory → shipping → notification event-cascade integration test.
To run the app itself:
mvn -pl order-fulfillment -am spring-boot:run
curl -X POST "http://localhost:8080/orders?sku=WIDGET-1&quantity=3"
curl "http://localhost:8080/orders/1"
Modules
| Module | Public API | Internal | Listens for |
|---|---|---|---|
order |
OrderManagement, OrderController |
internal.Order, internal.OrderRepository |
— |
inventory |
InventoryManagement |
internal.Stock, internal.StockRepository, internal.StockSeeder |
OrderPlaced |
shipping |
ShippingManagement |
internal.Shipment, internal.ShipmentRepository |
StockReserved |
notification |
NotificationManagement |
internal.Notification, internal.NotificationRepository |
OrderPlaced, ShipmentScheduled |
order has zero module dependencies — on purpose. See the Javadoc on OrderManagement
for why, and output/03-cycle-via-public-api-still-failed.txt for what happens the first
time that seems like a good idea to "fix" by adding one back.
Endpoints
| Method | Path | Description |
|---|---|---|
POST |
/orders?sku={sku}&quantity={n} |
Place an order |
GET |
/orders/{id} |
Fetch an order |
Captured output (output/)
Every number and every stack trace in the post traces to one of these, byte for byte:
| File | What it captures |
|---|---|
00-table-name-collision.txt |
H2 rejecting CREATE TABLE order — ORDER is a reserved word |
01-verify-violation-failed.txt |
ApplicationModules.verify() failing on a direct reference to inventory.internal.StockRepository — both a cycle and a non-exposed-type violation |
02-verify-passed.txt |
The same test, green, after removing the dependency on inventory entirely |
03-cycle-via-public-api-still-failed.txt |
Why routing the same call through InventoryManagement (the real public API) still fails — a cycle isn't a visibility problem |
04-event-flow-missing-dependency.txt |
@ApplicationModuleTest(mode = ALL_DEPENDENCIES) bootstrapping zero other modules for order, because order has no outgoing dependencies, and the resulting event-cascade test timing out |
05-redundant-transactional-failure.txt |
Stacking a plain @Transactional on top of @ApplicationModuleListener failing bean registration outright |
06-event-flow-passed.txt |
The full order → inventory → shipping → notification cascade, fixed, with both notification hops firing |
07-generated-docs.txt |
The real output of Documenter.writeModulesAsPlantUml().writeModuleCanvases()... — the PlantUML component diagram and per-module canvases this repo's own structure produces |
08-javap-listener-meta-annotations.txt |
javap -v on the compiled @ApplicationModuleListener class, showing its real meta-annotations (@Async + @Transactional(REQUIRES_NEW) + @TransactionalEventListener) |
No docs/ chapter directory in this repository — the intermediate and reference-depth
material that would normally live there is in accordion sections inside the WordPress
post itself.