-
Notifications
You must be signed in to change notification settings - Fork 141
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GH-6 - Moar reference documentation.
- Loading branch information
Showing
2 changed files
with
143 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,73 @@ | ||
[[testing]] | ||
= Integration Testing Application Modules | ||
|
||
[[testing.subheadline]] | ||
== Subheadline | ||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. | ||
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. | ||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. | ||
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
Spring Modulith allows to run integration tests bootstrapping individual application modules in isolation or combination with others. | ||
To achieve this, place JUnit test class in an application module package or any sub-package of that and annotate it with `@ApplicationModuleTest`: | ||
|
||
[source, java] | ||
---- | ||
@ApplicationModuleTest | ||
class OrderIntegrationTests { | ||
// Individual test cases go here | ||
} | ||
---- | ||
|
||
If you configure the log level for `org.springframework.modulith` to `DEBUG`, you will see detailed information about how the test execution customizes the Spring Boot bootstrap: | ||
|
||
[source] | ||
---- | ||
. ____ _ __ _ _ | ||
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ | ||
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ | ||
\\/ ___)| |_)| | | | | || (_| | ) ) ) ) | ||
' |____| .__|_| |_|_| |_\__, | / / / / | ||
=========|_|==============|___/=/_/_/_/ | ||
:: Spring Boot :: (v3.0.0-SNAPSHOT) | ||
… - Bootstrapping @ApplicationModuleTest for example.order in mode STANDALONE (class example.Application)… | ||
… - ====================================================================================================== | ||
… - ## example.order ## | ||
… - > Logical name: order | ||
… - > Base package: example.order | ||
… - > Direct module dependencies: none | ||
… - > Spring beans: | ||
… - + ….OrderManagement | ||
… - + ….internal.OrderInternal | ||
… - Starting OrderIntegrationTests using Java 17.0.3 … | ||
… - No active profile set, falling back to 1 default profile: "default" | ||
… - Re-configuring auto-configuration and entity scan packages to: example.order. | ||
---- | ||
|
||
Note, how the output contains the detailed information about the module included in the test run. | ||
|
||
[[testing.bootstrap-modes]] | ||
== Bootstrap Modes | ||
|
||
The application module test can be bootstrapped in a variety of modes: | ||
|
||
* `STANDALONE` (default) -- Runs the current module only. | ||
* `DIRECT_DEPENDENCIES` -- Runs the current module as well as all modules the current one directly depends on. | ||
* `ALL_DEPENDENCIES` -- Runs the current module and the entire tree of modules depended on. | ||
|
||
[[testing.efferent-dependencies]] | ||
== Dealing with Efferent Dependencies | ||
|
||
When an application module is bootstrapped, the Spring beans it contains will be instantiated. | ||
If those contain bean references that cross module boundaries, the bootstrap will fail if those other modules are not included in the test run (see <<testing.bootstrap-modes>> for details). | ||
While a natural reaction might be to expand the scope of the application modules included, it is usually a better option to mock the target beans. | ||
|
||
[source, java] | ||
---- | ||
@ApplicationModuleTest | ||
class InventoryIntegrationTests { | ||
@MockBean SomeOtherComponent someOtherComponent; | ||
} | ||
---- | ||
|
||
Spring Boot will create bean definitions and instances for the types defined as `@MockBean` and add them to the `ApplicationContext` bootstrapped for the test run. | ||
|
||
If you find your application module depending on too many beans of other ones, that is usually a sign of high coupling between them. | ||
The dependencies should be reviewed for whether they are candidates for replacement by publishing a domain event (see <<events>>). | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,76 @@ | ||
[[events]] | ||
= Working with Application Events | ||
|
||
[[events.subheadline]] | ||
== Subheadline | ||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. | ||
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. | ||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. | ||
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | ||
To keep application modules as decoupled as possible from each other, their primary means of interaction should be event publication and consumption. | ||
This avoids the originating module to know about all potentially interested parties, which is a key aspect to enable application module integration testing (see <<testing>>). | ||
|
||
Often we will find application components defined like this: | ||
|
||
[source, java] | ||
---- | ||
@Service | ||
@RequiredArgsConstructor | ||
public class OrderManagement { | ||
private final InventoryManagement inventory; | ||
@Transactional | ||
public void complete(Order order) { | ||
// State transition on the order aggregate go here | ||
// Invoke related functionality | ||
inventory.updateStockFor(order); | ||
} | ||
} | ||
---- | ||
|
||
The `complete(…)` method creates functional gravity in the sense that it attracts related functionality and thus interaction with Spring beans defined in other application modules. | ||
This especially makes the component harder to test as we need to have instances available of those depended on beans just to create an instance of `OrderManagement` (see <<testing.efferent-dependencies>>). | ||
It also means that we will have to touch the class whenever we would like to integrate further functionality with the business event order completion. | ||
|
||
[source, java] | ||
---- | ||
@Service | ||
@RequiredArgsConstructor | ||
public class OrderManagement { | ||
private final ApplicationEventPublisher events; | ||
private final OrderInternal dependency; | ||
@Transactional | ||
public void complete(Order order) { | ||
// State transition on the order aggregate go here | ||
events.publishEvent(new OrderCompleted(order.getId())); | ||
} | ||
} | ||
---- | ||
|
||
Note, how we use Spring's `ApplicationEventPublisher` to publish a domain event, once we have completed the state transitions on the primary aggregate. | ||
As event publication happens synchronously by default, the transactional semantics of the overall arrangement stay the same as in the example above. | ||
Both for the good, as we get to a very simple consistency model (either both the status change of the order _and_ the inventory update succeed or none of them does), but also for the bad as more triggered related functionality will widen the transaction boundary and potentially cause the entire transaction to fail, even if the functionality that is causing the error is not crucial. | ||
|
||
A different way of approaching this is by moving the event consumption to asynchronous handling at transaction commit and treat secondary functionaly exactly as that: | ||
|
||
[source, java] | ||
---- | ||
@Service | ||
@RequiredArgsConstructor | ||
public class InventoryManagement { | ||
@Async | ||
@TransactionalEventListener | ||
void on(OrderCompleted event) { | ||
} | ||
} | ||
---- | ||
|
||
This now effectively decouples the original transaction from the execution of the listener. | ||
While this avoids the expansion of the original business transaction, it also creates a risk: if the listener fails for whatever reason, the event publication is lost, unless each listener actually implements its own safety net. | ||
Even worse, that doesn't fully work, as the system might fail before the method is even invoked. | ||
|
||
Spring Modulith ships with an event publication registry that hooks into the core event publication mechanism of Spring Framework. | ||
On event publication, it finds out about the transactional event listeners that will get the event delivered and writes entries for each of them into an event publication log as part of the original business transaction. |