Add order-fulfillment module: Spring Modulith 2.1 boundary enforcement, events, generated docs

This commit is contained in:
2026-10-03 18:45:37 +00:00
commit 866eceed0a
34 changed files with 1149 additions and 0 deletions
@@ -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.