This repository contains the sample code for my talk / workshop "Spring for architecturally curious developers".
It does not use a stable commit history but rather a commit per step in the demo that can be referred to via a tag.
This means that all tags starting with steps/…
are not stable either.
If you’re looking for the state of the project for a particular event, skim through the tags starting with events/…
.
They keep a reference to the latest commit of the codebase for that particular event.
After this step you should…
-
… understand how Spring Modulith guides developers in structuring their Spring Boot applications
-
… be familiar with the concept of architectural observability: being able to get a high-level understanding of the logical, functional parts of application and how they interact with each other.
-
$ git checkout step/0
-
Run
./etc/prepare-talk.sh
(Openssrc/main/asciidoc/index.adoc
in browser with Asciidoctor plugin installed).
-
Three logical application modules
customer
,order
,inventory
representing the functional blocks of our application. -
The order module refers to a customer identifier (aggregate structure reference only)
-
ModularityTests
— creates documentation based on the module conventions and verifies the general structure.
-
Run documentation tests of
ModularityTest
.-
Show how the public Spring components appear in the documentation: component diagrams and Application Module Canvases (AMC).
-
Reference
Order
→CustomIdentifier
is reflected in the component relationships with a depends-on-relationship.
-
-
Introduce
OrderManagement
→Inventory
dependency to invoke it fromOrderManagement.complete(…)
.-
Show how the relationship is reflected in the component diagram as uses-relationship.
-
-
Introduce public
InventoryRepository
as inventory-internal component.-
Show how it appears in the AMC.
-
Introduce invalid dependency from
OrderManagement
.-
Option A — Make it package private. Component disappears from AMC and cannot be referred to, as enforced by the compiler.
-
Option B — Move type into sub-package. Component disappears from AMC but can still be referred to.
-
Run
ModularityTests.verifyModularity()
and see the reference causing the test to fail as an internal component is referenced.
-
-
-
-
Additional discussion
-
Further details of verification
-
No cyclic dependencies
-
No field injection
-
jMolecules verifications (DDD building blocks)
-
-
-
Run
InventoryIntegrationTests
— Show log output. Component scanning and auto-configuration restricted to only theinventory
package. -
Run
OrderIntegrationTests
— The tests fails as the bean reference toInventory
cannot be satisfied-
Option A — Use
@MockBean
to include a mock in the bootstrap. -
Option B — Use
@ApplicationModuleTest(bootstrapMode = …)
to include the inventory module in the bootstrap.
-
-
Additional discussion
-
Domain model relationships important. Cross-references only into aggregate roots via identifiers.
-
After this step you should understand…
-
… the downsides of application module integration via bean references.
-
Application modules encapsulate internals using package private components.
-
Inventory exposes configuration properties that are documented in the AMC.
-
OrderManagement
actively triggersInventory
. Order completion is a point of functional gravity that will also attract other tangential functionality: calculating rewards points for completed orders, sending confirmation emails etc. Integration is synchronous, causes the transaction to expand (violates DDD principle of aggregate being the scope of strong consistency) and increases the risk of primary business functionality to break because of failure in attached functionality. -
The actually required
Order
parameter forInventory.updateStock()
creates a module cycle. 😣 -
Tests require mocks for all cross-application-module bean references.
After this step, you should understand…
-
… how to replace a Spring bean invocation with an event publication to increase the cohesion of an application module.
-
… how that affects testing in a way that they stay focused on the module to test as the side effect ends in that very module.
-
We register a domain event from
Order.complete()
and implicitly publish it through the….save(…)
call on the repository. -
We removed the dependency from
OrderManagement
toInventory
. -
We changed our test case to remove the mock for
Inventory
and rather test for the event publication only. The side effect of the business operations ends within the module. -
The event publication and listening is reflected in the generated documentation.
-
Show
Inventory.updateStock(OrderCompletedEvent)
and discuss how the behavior changed. -
Show
OrderIntegrationTests.completionCausesEventPublished(Scenario)
and discussScenario
API.
-
Eventually-consistent application module integration model
-
Support for integration testing such an integration
After this step, you should understand…
-
… how handling events in an asynchronous, transactional event listener might be subject to data loss unless handled properly
-
… how to use Spring Modulith’s event publication registry implementation to prevent this
After this step you should understand… * … how the functional architecture implemented in the codebase can be exposed to the runtime platform.
-
Additional dependencies added in the POM.
-
modulith
actuator was registered for web exposure inapplication.properties
.
-
Run the application with the actuator profile enabled (
./mvnw spring-boot:run -Pactuator
). -
Point the browser to http://localhost:8080/actuator. Show how the
modulith
resource is exposed. Follow the link and show how the JSON exposes the dependency structure in machine readable format. -
Show in the logs how the actuators were bootstrapped and the Spring Modulith application module model was bootstrapped asynchronously.
A couple of useful scripts to be found in etc
:
-
prepare-talk.sh
— opens a browser pointing to the documentation source file. Make sure you have an Asciidoctor plugin installed to let the rendered view show up. -
publish.sh
— pushes current state, retags commits and pushes those as well. -
remove-remote-tags.sh
— Removes all step tags from the remote repository. -
retag.sh
— execute each time you change something about an individual commit to update thesteps/…
tags to be used in demos. -
test-all-commits.sh
— runs the Maven build for all commits of the main branch. -
test-all-tags.sh
— runs the Maven build for allstep/…
tags.