Add order-fulfillment module: Spring Modulith 2.1 boundary enforcement, events, generated docs
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
$ mvn -pl order-fulfillment -am test
|
||||
(captured before adding @Table(name = "orders") to com.ankurm.modulithdemo.order.internal.Order)
|
||||
|
||||
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "create table [*]order (id bigint generated by default as identity, quantity integer not null, sku varchar(255), status varchar(255), primary key (id))"; expected "identifier"; SQL statement:
|
||||
create table order (id bigint generated by default as identity, quantity integer not null, sku varchar(255), status varchar(255), primary key (id)) [42001-240]
|
||||
at org.h2.message.DbException.getJdbcSQLException(DbException.java:514) ~[h2-2.4.240.jar:2.4.240]
|
||||
at org.h2.message.DbException.getJdbcSQLException(DbException.java:489) ~[h2-2.4.240.jar:2.4.240]
|
||||
at org.h2.message.DbException.getSyntaxError(DbException.java:261) ~[h2-2.4.240.jar:2.4.240]
|
||||
at org.h2.command.Parser.readIdentifier(Parser.java:5568) ~[h2-2.4.240.jar:2.4.240]
|
||||
at org.h2.command.Parser.parseCreateTable(Parser.java:8908) ~[h2-2.4.240.jar:2.4.240]
|
||||
at org.h2.command.Parser.parseCreate(Parser.java:6483) ~[h2-2.4.240.jar:2.4.240]
|
||||
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.createTable(AbstractSchemaMigrator.java:305) ~[hibernate-core-7.4.5.Final.jar:7.4.5.Final]
|
||||
at org.hibernate.tool.schema.internal.GroupedSchemaMigratorImpl.performTablesMigration(GroupedSchemaMigratorImpl.java:80) ~[hibernate-core-7.4.5.Final.jar:7.4.5.Final]
|
||||
|
||||
Root cause: JPA defaults an entity's table name to its simple name. `Order` the Java class
|
||||
becomes `order` the table name, and ORDER is a reserved word in H2 (and in the SQL
|
||||
standard, and in Postgres, and in MySQL) because of the ORDER BY clause. Hibernate's
|
||||
schema-update path logs this as an error and carries on, which is worse than failing fast
|
||||
-- the table partially/never exists and every INSERT against it fails later with its own,
|
||||
less obvious error. Fixed with @Table(name = "orders") on the entity.
|
||||
@@ -0,0 +1,49 @@
|
||||
$ mvn -pl order-fulfillment -am test -Dtest=ModularityTests#verifiesModuleStructure
|
||||
(captured with OrderManagement holding a direct field of type
|
||||
com.ankurm.modulithdemo.inventory.internal.StockRepository)
|
||||
|
||||
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 1.768 s <<< FAILURE! -- in com.ankurm.modulithdemo.ModularityTests
|
||||
[ERROR] com.ankurm.modulithdemo.ModularityTests.verifiesModuleStructure -- Time elapsed: 0.120 s <<< ERROR!
|
||||
org.springframework.modulith.core.Violations:
|
||||
- Cycle detected: Slice inventory ->
|
||||
Slice order ->
|
||||
Slice inventory
|
||||
1. Dependencies of Slice inventory
|
||||
- Method <com.ankurm.modulithdemo.inventory.InventoryManagement.on(com.ankurm.modulithdemo.order.OrderPlaced)> has parameter of type <com.ankurm.modulithdemo.order.OrderPlaced> in (InventoryManagement.java:0)
|
||||
- Method <com.ankurm.modulithdemo.inventory.InventoryManagement.on(com.ankurm.modulithdemo.order.OrderPlaced)> calls method <com.ankurm.modulithdemo.order.OrderPlaced.sku()> in (InventoryManagement.java:44)
|
||||
- Method <com.ankurm.modulithdemo.inventory.InventoryManagement.on(com.ankurm.modulithdemo.order.OrderPlaced)> calls method <com.ankurm.modulithdemo.order.OrderPlaced.quantity()> in (InventoryManagement.java:45)
|
||||
- Method <com.ankurm.modulithdemo.inventory.InventoryManagement.on(com.ankurm.modulithdemo.order.OrderPlaced)> calls method <com.ankurm.modulithdemo.order.OrderPlaced.orderId()> in (InventoryManagement.java:48)
|
||||
2. Dependencies of Slice order
|
||||
- Constructor <com.ankurm.modulithdemo.order.OrderManagement.<init>(..., StockRepository)> has parameter of type <com.ankurm.modulithdemo.inventory.internal.StockRepository> in (OrderManagement.java:0)
|
||||
- Field <com.ankurm.modulithdemo.order.OrderManagement.stockShortcut> has type <com.ankurm.modulithdemo.inventory.internal.StockRepository> in (OrderManagement.java:0)
|
||||
- Method <com.ankurm.modulithdemo.order.OrderManagement.placeOrder(java.lang.String, int)> calls method <com.ankurm.modulithdemo.inventory.internal.StockRepository.findBySku(java.lang.String)> in (OrderManagement.java:39)
|
||||
- Module 'order' depends on non-exposed type com.ankurm.modulithdemo.inventory.internal.StockRepository within module 'inventory'!
|
||||
OrderManagement declares constructor OrderManagement(OrderRepository, ApplicationEventPublisher, StockRepository) in (OrderManagement.java:0)
|
||||
- Module 'order' depends on non-exposed type com.ankurm.modulithdemo.inventory.internal.StockRepository within module 'inventory'!
|
||||
Method <com.ankurm.modulithdemo.order.OrderManagement.placeOrder(java.lang.String, int)> calls method <com.ankurm.modulithdemo.inventory.internal.StockRepository.findBySku(java.lang.String)> in (OrderManagement.java:39)
|
||||
- Module 'order' depends on non-exposed type com.ankurm.modulithdemo.inventory.internal.StockRepository within module 'inventory'!
|
||||
Field <com.ankurm.modulithdemo.order.OrderManagement.stockShortcut> has type <com.ankurm.modulithdemo.inventory.internal.StockRepository> in (OrderManagement.java:0)
|
||||
at org.springframework.modulith.core.Violations.and(Violations.java:141)
|
||||
at org.springframework.modulith.core.ApplicationModules.detectViolations(ApplicationModules.java:497)
|
||||
at org.springframework.modulith.core.ApplicationModules.verify(ApplicationModules.java:451)
|
||||
at org.springframework.modulith.core.ApplicationModules.verify(ApplicationModules.java:435)
|
||||
at com.ankurm.modulithdemo.ModularityTests.verifiesModuleStructure(ModularityTests.java:28)
|
||||
|
||||
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0
|
||||
|
||||
One field in OrderManagement produced TWO distinct violation types in the same run:
|
||||
|
||||
1. "Cycle detected" - inventory already depends on order (it listens for OrderPlaced,
|
||||
so it necessarily knows about order's event type). Order reaching back into
|
||||
inventory's internals closes a cycle: order -> inventory -> order. Spring Modulith
|
||||
treats module dependencies as a DAG by default; a cycle is a structural violation
|
||||
even before asking whether the specific type reached for was internal.
|
||||
|
||||
2. "depends on non-exposed type" - independent of the cycle, StockRepository lives
|
||||
under inventory.internal, which nothing outside the inventory package is allowed
|
||||
to reference, exposed or not.
|
||||
|
||||
Either violation alone would have failed the build. This one shortcut produced both at
|
||||
once, which is the realistic case: an internal type reached across a boundary usually
|
||||
also reverses or closes a dependency cycle, because the "proper" event-based direction
|
||||
was already established the other way.
|
||||
@@ -0,0 +1,17 @@
|
||||
$ mvn -pl order-fulfillment -am test
|
||||
(captured after removing the dependency on inventory entirely from OrderManagement --
|
||||
see the Javadoc on OrderManagement for why routing it through InventoryManagement's
|
||||
public API still was not enough, in output/03-cycle-via-public-api-still-failed.txt)
|
||||
|
||||
[INFO] Running com.ankurm.modulithdemo.order.OrderFulfillmentIntegrationTests
|
||||
2026-10-03T23:53:21.630+05:30 INFO 2118 --- [order-fulfillment] [ task-2] c.a.m.n.NotificationManagement : Order 1 received for 3x WIDGET-1
|
||||
2026-10-03T23:53:21.685+05:30 INFO 2118 --- [order-fulfillment] [ task-4] c.a.m.n.NotificationManagement : Order 1 is on its way (WIDGET-1)
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.748 s -- in com.ankurm.modulithdemo.order.OrderFulfillmentIntegrationTests
|
||||
[INFO] Running com.ankurm.modulithdemo.ModularityTests
|
||||
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.471 s -- in com.ankurm.modulithdemo.ModularityTests
|
||||
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
|
||||
[INFO] BUILD SUCCESS
|
||||
|
||||
Same ApplicationModules.verify() call, same four modules, zero violations. The only
|
||||
change from output/01-verify-violation-failed.txt is that OrderManagement no longer
|
||||
depends on anything in inventory, in either direction.
|
||||
@@ -0,0 +1,26 @@
|
||||
$ mvn -pl order-fulfillment -am test -Dtest=ModularityTests#verifiesModuleStructure
|
||||
(captured with OrderManagement depending on InventoryManagement -- inventory's actual
|
||||
public API, not an internal type -- expecting this to fix the earlier violation)
|
||||
|
||||
org.springframework.modulith.core.Violations: - Cycle detected: Slice inventory ->
|
||||
Slice order ->
|
||||
Slice inventory
|
||||
1. Dependencies of Slice inventory
|
||||
- Method <com.ankurm.modulithdemo.inventory.InventoryManagement.on(com.ankurm.modulithdemo.order.OrderPlaced)> has parameter of type <com.ankurm.modulithdemo.order.OrderPlaced> in (InventoryManagement.java:0)
|
||||
- Method <com.ankurm.modulithdemo.inventory.InventoryManagement.on(com.ankurm.modulithdemo.order.OrderPlaced)> calls method <com.ankurm.modulithdemo.order.OrderPlaced.sku()> in (InventoryManagement.java:44)
|
||||
- Method <com.ankurm.modulithdemo.inventory.InventoryManagement.on(com.ankurm.modulithdemo.order.OrderPlaced)> calls method <com.ankurm.modulithdemo.order.OrderPlaced.quantity()> in (InventoryManagement.java:45)
|
||||
- Method <com.ankurm.modulithdemo.inventory.InventoryManagement.on(com.ankurm.modulithdemo.order.OrderPlaced)> calls method <com.ankurm.modulithdemo.order.OrderPlaced.orderId()> in (InventoryManagement.java:48)
|
||||
- Method <com.ankurm.modulithdemo.inventory.InventoryManagement.on(com.ankurm.modulithdemo.order.OrderPlaced)> calls method <com.ankurm.modulithdemo.order.OrderPlaced.sku()> in (InventoryManagement.java:48)
|
||||
2. Dependencies of Slice order
|
||||
- Constructor <com.ankurm.modulithdemo.order.OrderManagement.<init>(com.ankurm.modulithdemo.order.internal.OrderRepository, org.springframework.context.ApplicationEventPublisher, com.ankurm.modulithdemo.inventory.InventoryManagement)> has parameter of type <com.ankurm.modulithdemo.inventory.InventoryManagement> in (OrderManagement.java:0)
|
||||
- Field <com.ankurm.modulithdemo.order.OrderManagement.inventory> has type <com.ankurm.modulithdemo.inventory.InventoryManagement> in (OrderManagement.java:0)
|
||||
- Method <com.ankurm.modulithdemo.order.OrderManagement.placeOrder(java.lang.String, int)> calls method <com.ankurm.modulithdemo.inventory.InventoryManagement.isInStock(java.lang.String, int)> in (OrderManagement.java:40)
|
||||
|
||||
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0 -- BUILD FAILURE
|
||||
|
||||
Notice: no "non-exposed type" complaint this time -- InventoryManagement is inventory's
|
||||
real public API, exactly as advertised. Only "Cycle detected" remains, which proves the
|
||||
point: going through the public API fixes visibility violations, not direction
|
||||
violations. inventory already depends on order (its listener takes OrderPlaced as a
|
||||
parameter), so order depending on inventory in return -- through any type, public or
|
||||
not -- closes a cycle. The fix is to remove the dependency, not to launder it.
|
||||
@@ -0,0 +1,45 @@
|
||||
2026-10-03T23:48:20.007+05:30 INFO 1451 --- [order-fulfillment] [ main] ustomizerFactory$ModuleContextCustomizer : Bootstrapping @org.springframework.modulith.test.ApplicationModuleTest for Order in mode ALL_DEPENDENCIES (class com.ankurm.modulithdemo.Application)?
|
||||
2026-10-03T23:48:20.016+05:30 INFO 1451 --- [order-fulfillment] [ main] ustomizerFactory$ModuleContextCustomizer :
|
||||
2026-10-03T23:48:20.024+05:30 INFO 1451 --- [order-fulfillment] [ main] ustomizerFactory$ModuleContextCustomizer : # Order
|
||||
2026-10-03T23:48:20.024+05:30 INFO 1451 --- [order-fulfillment] [ main] ustomizerFactory$ModuleContextCustomizer : > Logical name: order
|
||||
2026-10-03T23:48:20.024+05:30 INFO 1451 --- [order-fulfillment] [ main] ustomizerFactory$ModuleContextCustomizer : > Base package: com.ankurm.modulithdemo.order
|
||||
2026-10-03T23:48:20.024+05:30 INFO 1451 --- [order-fulfillment] [ main] ustomizerFactory$ModuleContextCustomizer : > Excluded packages: none
|
||||
2026-10-03T23:48:20.025+05:30 INFO 1451 --- [order-fulfillment] [ main] ustomizerFactory$ModuleContextCustomizer : > Direct module dependencies: none
|
||||
2026-10-03T23:48:20.025+05:30 INFO 1451 --- [order-fulfillment] [ main] ustomizerFactory$ModuleContextCustomizer : > Spring beans:
|
||||
2026-10-03T23:48:20.025+05:30 INFO 1451 --- [order-fulfillment] [ main] ustomizerFactory$ModuleContextCustomizer : + ?.OrderController
|
||||
2026-10-03T23:48:20.025+05:30 INFO 1451 --- [order-fulfillment] [ main] ustomizerFactory$ModuleContextCustomizer : + ?.OrderManagement
|
||||
2026-10-03T23:48:20.025+05:30 INFO 1451 --- [order-fulfillment] [ main] ustomizerFactory$ModuleContextCustomizer : o ?.internal.OrderRepository
|
||||
2026-10-03T23:48:20.032+05:30 INFO 1451 --- [order-fulfillment] [ main] ustomizerFactory$ModuleContextCustomizer :
|
||||
2026-10-03T23:48:20.042+05:30 INFO 1451 --- [order-fulfillment] [ main] c.a.m.o.OrderFulfillmentIntegrationTests : Starting OrderFulfillmentIntegrationTests using Java 25.0.4.1 with PID 1451 (started by root in /home/claude/spring-modulith-demo/order-fulfillment)
|
||||
2026-10-03T23:48:23.449+05:30 DEBUG 1451 --- [order-fulfillment] [ main] .s.m.e.c.DefaultEventPublicationRegistry : Looking up incomplete event publications ?
|
||||
2026-10-03T23:48:23.793+05:30 DEBUG 1451 --- [order-fulfillment] [ main] .s.m.e.c.DefaultEventPublicationRegistry : No publication found.
|
||||
2026-10-03T23:48:23.807+05:30 INFO 1451 --- [order-fulfillment] [ main] o.s.s.config.TaskSchedulerRouter : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
|
||||
2026-10-03T23:48:23.811+05:30 INFO 1451 --- [order-fulfillment] [ main] c.a.m.o.OrderFulfillmentIntegrationTests : Started OrderFulfillmentIntegrationTests in 4.061 seconds (process running for 6.7)
|
||||
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3
|
||||
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
|
||||
WARNING: A Java agent has been loaded dynamically (/root/.m2/repository/net/bytebuddy/byte-buddy-agent/1.18.11/byte-buddy-agent-1.18.11.jar)
|
||||
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
|
||||
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
|
||||
WARNING: Dynamic loading of agents will be disallowed by default in a future release
|
||||
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 16.48 s <<< FAILURE! -- in com.ankurm.modulithdemo.order.OrderFulfillmentIntegrationTests
|
||||
[ERROR] com.ankurm.modulithdemo.order.OrderFulfillmentIntegrationTests.placingAnOrderCascadesThroughInventoryAndShipping(Scenario) -- Time elapsed: 10.50 s <<< ERROR!
|
||||
org.awaitility.core.ConditionTimeoutException: Lambda expression in org.springframework.modulith.test.Scenario$When$EventResult expected the predicate to return <true> but it returned <false> for input of <[]> within 10 seconds.
|
||||
at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
|
||||
at org.awaitility.core.AbstractHamcrestCondition.await(AbstractHamcrestCondition.java:86)
|
||||
at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160)
|
||||
at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:712)
|
||||
at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:729)
|
||||
at org.springframework.modulith.test.Scenario$When.awaitInternal(Scenario.java:420)
|
||||
at org.springframework.modulith.test.Scenario$When$EventResult.toArriveAndVerifyInternal(Scenario.java:657)
|
||||
at org.springframework.modulith.test.Scenario$When$EventResult.toArriveAndVerify(Scenario.java:583)
|
||||
at com.ankurm.modulithdemo.order.OrderFulfillmentIntegrationTests.placingAnOrderCascadesThroughInventoryAndShipping(OrderFulfillmentIntegrationTests.java:29)
|
||||
|
||||
Note the line: "> Direct module dependencies: none"
|
||||
That line is the whole bug. ALL_DEPENDENCIES bootstraps the modules `order` depends ON
|
||||
(what order imports), not the modules that depend on order (what listens to order's
|
||||
events). Since nothing in `order` imports from inventory/shipping/notification, none of
|
||||
their beans - including InventoryManagement, the @ApplicationModuleListener that reacts
|
||||
to OrderPlaced - exist in this test's ApplicationContext. The event is published,
|
||||
there is no listener to receive it, and awaitility times out waiting for a
|
||||
ShipmentScheduled that was never going to arrive. Fixed by adding
|
||||
extraIncludes = {"inventory", "shipping", "notification"} to @ApplicationModuleTest.
|
||||
@@ -0,0 +1,20 @@
|
||||
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385) ~[surefire-booter-3.2.5.jar:3.2.5]
|
||||
at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162) ~[surefire-booter-3.2.5.jar:3.2.5]
|
||||
at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507) ~[surefire-booter-3.2.5.jar:3.2.5]
|
||||
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495) ~[surefire-booter-3.2.5.jar:3.2.5]
|
||||
Caused by: java.lang.IllegalStateException: @TransactionalEventListener method must not be annotated with @Transactional unless when declared as REQUIRES_NEW or NOT_SUPPORTED: public void com.ankurm.modulithdemo.inventory.InventoryManagement.on(com.ankurm.modulithdemo.order.OrderPlaced)
|
||||
at org.springframework.transaction.annotation.RestrictedTransactionalEventListenerFactory.createApplicationListener(RestrictedTransactionalEventListenerFactory.java:52) ~[spring-tx-7.0.9.jar:7.0.9]
|
||||
at org.springframework.context.event.EventListenerMethodProcessor.processBean(EventListenerMethodProcessor.java:187) ~[spring-context-7.0.9.jar:7.0.9]
|
||||
at org.springframework.context.event.EventListenerMethodProcessor.afterSingletonsInstantiated(EventListenerMethodProcessor.java:141) ~[spring-context-7.0.9.jar:7.0.9]
|
||||
... 92 common frames omitted
|
||||
|
||||
2026-10-03T23:49:39.346+05:30 WARN 1623 --- [order-fulfillment] [ main] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener] to prepare test instance [com.ankurm.modulithdemo.order.OrderFulfillmentIntegrationTests@646af766]
|
||||
|
||||
Root cause: @ApplicationModuleListener already carries @Transactional(propagation =
|
||||
REQUIRES_NEW) as a meta-annotation (confirmed by javap -v on the annotation class itself -
|
||||
see the post's "how it really works underneath" section). Redeclaring a plain
|
||||
@Transactional (default propagation REQUIRED) on the same method is not harmless
|
||||
layering - Spring's RestrictedTransactionalEventListenerFactory refuses to even register
|
||||
the listener, and the whole ApplicationContext fails to start. Fixed by deleting the
|
||||
redundant @Transactional from InventoryManagement.on(OrderPlaced) and
|
||||
ShippingManagement.on(StockReserved).
|
||||
@@ -0,0 +1,19 @@
|
||||
$ mvn -pl order-fulfillment -am test
|
||||
|
||||
2026-10-03T23:50:25.993+05:30 INFO 1737 --- [order-fulfillment] [ task-2] c.a.m.n.NotificationManagement : Order 1 received for 3x WIDGET-1
|
||||
2026-10-03T23:50:26.062+05:30 INFO 1737 --- [order-fulfillment] [ task-4] c.a.m.n.NotificationManagement : Order 1 is on its way (WIDGET-1)
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.888 s -- in com.ankurm.modulithdemo.order.OrderFulfillmentIntegrationTests
|
||||
[INFO] Running com.ankurm.modulithdemo.ModularityTests
|
||||
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.437 s -- in com.ankurm.modulithdemo.ModularityTests
|
||||
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
|
||||
[INFO] BUILD SUCCESS
|
||||
|
||||
All three modules fired in sequence from a single published OrderPlaced, on two
|
||||
different async listener threads (task-2, task-4 - each @ApplicationModuleListener
|
||||
runs on its own thread from the pool, which is why ordering between independent
|
||||
listeners on the SAME event is not guaranteed, only causal ordering between an event
|
||||
and what it triggers):
|
||||
|
||||
order -> inventory (reserve stock, publish StockReserved)
|
||||
-> shipping (schedule shipment, publish ShipmentScheduled)
|
||||
-> notification (fires twice: once reacting to OrderPlaced, once to ShipmentScheduled)
|
||||
@@ -0,0 +1,90 @@
|
||||
=== target/spring-modulith-docs/components.puml ===
|
||||
@startuml
|
||||
title <size:24>Order Fulfillment</size>
|
||||
|
||||
set separator none
|
||||
top to bottom direction
|
||||
|
||||
<style>
|
||||
root {
|
||||
BackgroundColor: #ffffff
|
||||
FontColor: #444444
|
||||
}
|
||||
</style>
|
||||
|
||||
!include <C4/C4>
|
||||
!include <C4/C4_Context>
|
||||
!include <C4/C4_Component>
|
||||
|
||||
System_Boundary("OrderFulfillment_boundary", "Order Fulfillment", $tags="") {
|
||||
Container_Boundary("OrderFulfillment.OrderFulfillment_boundary", "Order Fulfillment", $tags="") {
|
||||
Component(OrderFulfillment.OrderFulfillment.Order, "Order", $techn="Module", $descr="", $tags="", $link="")
|
||||
Component(OrderFulfillment.OrderFulfillment.Inventory, "Inventory", $techn="Module", $descr="", $tags="", $link="")
|
||||
Component(OrderFulfillment.OrderFulfillment.Shipping, "Shipping", $techn="Module", $descr="", $tags="", $link="")
|
||||
Component(OrderFulfillment.OrderFulfillment.Notification, "Notification", $techn="Module", $descr="", $tags="", $link="")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rel(OrderFulfillment.OrderFulfillment.Notification, OrderFulfillment.OrderFulfillment.Order, "listens to", $techn="", $tags="", $link="")
|
||||
Rel(OrderFulfillment.OrderFulfillment.Shipping, OrderFulfillment.OrderFulfillment.Inventory, "listens to", $techn="", $tags="", $link="")
|
||||
Rel(OrderFulfillment.OrderFulfillment.Inventory, OrderFulfillment.OrderFulfillment.Order, "listens to", $techn="", $tags="", $link="")
|
||||
Rel(OrderFulfillment.OrderFulfillment.Notification, OrderFulfillment.OrderFulfillment.Shipping, "listens to", $techn="", $tags="", $link="")
|
||||
|
||||
SHOW_LEGEND(true)
|
||||
hide stereotypes
|
||||
@enduml
|
||||
=== target/spring-modulith-docs/module-order.adoc ===
|
||||
[%autowidth.stretch, cols="h,a"]
|
||||
|===
|
||||
|Base package
|
||||
|`com.ankurm.modulithdemo.order`
|
||||
|Spring components
|
||||
|_Controllers_
|
||||
|
||||
* `c.a.m.o.OrderController`
|
||||
|
||||
_Services_
|
||||
|
||||
* `c.a.m.o.OrderManagement`
|
||||
|===
|
||||
|
||||
=== target/spring-modulith-docs/module-inventory.adoc ===
|
||||
[%autowidth.stretch, cols="h,a"]
|
||||
|===
|
||||
|Base package
|
||||
|`com.ankurm.modulithdemo.inventory`
|
||||
|Spring components
|
||||
|_Services_
|
||||
|
||||
* `c.a.m.i.InventoryManagement`
|
||||
|Events listened to
|
||||
|* `c.a.m.o.OrderPlaced` (async)
|
||||
|===
|
||||
|
||||
=== target/spring-modulith-docs/module-shipping.adoc ===
|
||||
[%autowidth.stretch, cols="h,a"]
|
||||
|===
|
||||
|Base package
|
||||
|`com.ankurm.modulithdemo.shipping`
|
||||
|Spring components
|
||||
|_Services_
|
||||
|
||||
* `c.a.m.s.ShippingManagement`
|
||||
|Events listened to
|
||||
|* `c.a.m.i.StockReserved` (async)
|
||||
|===
|
||||
|
||||
=== target/spring-modulith-docs/module-notification.adoc ===
|
||||
[%autowidth.stretch, cols="h,a"]
|
||||
|===
|
||||
|Base package
|
||||
|`com.ankurm.modulithdemo.notification`
|
||||
|Spring components
|
||||
|_Services_
|
||||
|
||||
* `c.a.m.n.NotificationManagement`
|
||||
|Events listened to
|
||||
|* `c.a.m.s.ShipmentScheduled` (async)
|
||||
* `c.a.m.o.OrderPlaced` (async)
|
||||
|===
|
||||
@@ -0,0 +1,30 @@
|
||||
$ CP=$(find ~/.m2 -iname "spring-modulith-events-api-2.1.1.jar")
|
||||
$ unzip -o -q "$CP" "org/springframework/modulith/events/ApplicationModuleListener.class" -d /tmp/inspect
|
||||
$ javap -v /tmp/inspect/org/springframework/modulith/events/ApplicationModuleListener.class
|
||||
|
||||
RuntimeVisibleAnnotations:
|
||||
0: #28()
|
||||
org.springframework.scheduling.annotation.Async
|
||||
1: #14(#22=e#24.#25)
|
||||
org.springframework.transaction.annotation.Transactional(
|
||||
propagation=Lorg/springframework/transaction/annotation/Propagation;.REQUIRES_NEW
|
||||
)
|
||||
2: #29()
|
||||
org.springframework.transaction.event.TransactionalEventListener
|
||||
3: #30()
|
||||
java.lang.annotation.Documented
|
||||
4: #31(#32=[e#33.#34,e#33.#35])
|
||||
java.lang.annotation.Target(
|
||||
value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.ANNOTATION_TYPE]
|
||||
)
|
||||
5: #36(#32=e#37.#38)
|
||||
java.lang.annotation.Retention(
|
||||
value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME
|
||||
)
|
||||
|
||||
This is the compiled annotation class itself, not documentation describing it --
|
||||
@ApplicationModuleListener is literally @Async + @Transactional(propagation =
|
||||
REQUIRES_NEW) + @TransactionalEventListener (default phase AFTER_COMMIT), stacked as
|
||||
meta-annotations. Confirmed this way rather than taken from prose because the reference
|
||||
documentation does not spell out the propagation level, and getting it wrong is exactly
|
||||
what produces the failure in output/05-redundant-transactional-failure.txt.
|
||||
Reference in New Issue
Block a user