You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/docs/asciidoc/10-fundamentals.adoc
+69-2Lines changed: 69 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -30,6 +30,7 @@ Thus, naturally, the module's API consists of all public types in the package.
30
30
31
31
Let us have a look at an example arrangement (icon:plus-circle[] denotes a public type, icon:minus-circle[] a package protected one).
32
32
33
+
.A single inventory application module
33
34
[source, subs="macros"]
34
35
----
35
36
icon:cubes[] Example
@@ -48,7 +49,8 @@ icon:cubes[] Example
48
49
49
50
If an application module package contains sub-packages, types in those might need to be made public so that it can be referred to from code of the very same module.
In this case code within the __inventory__ module was only allowed to refer to code in the __order__ module (and code not assigned to any module in the first place).
89
+
Find out about how to monitor that in <<verification>>.
90
+
73
91
[[fundamentals.modules.application-modules]]
74
92
=== The `ApplicationModules` Type
75
93
76
94
Spring Moduliths allows to inspect a codebase to derive an application module model based on the given arrangement and optional configuration.
77
95
The `spring-modulith-core` artifact contains `ApplicationModules` that can be pointed to a Spring Boot application class:
78
96
97
+
.Creating an application module model
79
98
[source, java]
80
99
----
81
100
var modules = ApplicationModules.of(Application.class);
82
101
----
83
102
84
103
To get an impression about what the analyzed arrangement looks like, we can just write the individual modules contained in the overall model to the console:
85
104
105
+
.Writing the application module arranagement to the console
86
106
[source, java]
87
107
----
88
108
modules.forEach(System.out::println);
89
109
----
90
110
111
+
.The console output of our application module arrangement
91
112
[source]
92
113
----
93
114
## example.inventory ##
@@ -109,3 +130,49 @@ Note, how each module is listed and the contained Spring components are identifi
109
130
110
131
[[fundamentals.modules.named-interface]]
111
132
=== Named Interfaces
133
+
134
+
By default and as described in <<fundamentals.modules.advanced>>, an application module's base package is considered the API package and thus is the only package to allow incoming dependencies from other modules.
135
+
In case you would like to expose additional packages to other modules, you need to use __named interfaces__.
136
+
You achieve that by annotating the `package-info.java` file of those package with `@NamedInterface`.
137
+
138
+
.A package arrangement to encapsulate an SPI named interface
The effect of that declaration is two fold: first, code in other application modules is allowed to refer to `SomeSpiInterface`.
163
+
Application modules are able to refer to the named interface in explicit dependency declarations.
164
+
Assume the __inventory__ module was making use of that, it could refer to the above declared named interface like this:
165
+
166
+
[source, java]
167
+
----
168
+
@org.springframework.modulith.ApplicationModule(
169
+
allowedDependencies = "order::spi"
170
+
)
171
+
package example.inventory;
172
+
----
173
+
174
+
Note how we concatenate the named interface's name `spi` via the double colon `::`.
175
+
In this setup, code in __inventory__ would be allowed to depend on `SomeSpiInterface` and other code residing in the `order.spi` interface, but not on `OrderManagement` for example.
176
+
For modules without explicitly described dependencies, both the application module root package *and* the SPI one are accessible.
* _No cycles on the application module level_ -- the dependencies between modules have to form directed, acyclic graph.
14
-
* _Efferent module access via API packages only_ -- All references to types that reside in application module internal packages are rejected. See <<fundamentals.modules.advanced>> for details.
15
-
* _Explicitly allowed application module dependencies only_ (optional) -- An application module can optionally define allowed dependencies via `@ApplicationModule(allowedDependencies = …)`. If those are configured, dependencies to other application modules are rejected.
14
+
* _Efferent module access via API packages only_ -- All references to types that reside in application module internal packages are rejected.
15
+
See <<fundamentals.modules.advanced>> for details.
16
+
* _Explicitly allowed application module dependencies only_ (optional) -- An application module can optionally define allowed dependencies via `@ApplicationModule(allowedDependencies = …)`.
17
+
If those are configured, dependencies to other application modules are rejected.
18
+
See <<fundamentals.modules.explicit-dependencies>> and <<fundamentals.modules.named-interfaces>> for details.
16
19
17
20
Spring Modulith optionally integrates with the jMolecules ArchUnit library and, if present, automatically triggers its verification rules described https://github.com/xmolecules/jmolecules-integrations/tree/main/jmolecules-archunit[here].
Copy file name to clipboardExpand all lines: src/docs/asciidoc/30-testing.adoc
+9-2Lines changed: 9 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -4,18 +4,23 @@
4
4
Spring Modulith allows to run integration tests bootstrapping individual application modules in isolation or combination with others.
5
5
To achieve this, place JUnit test class in an application module package or any sub-package of that and annotate it with `@ApplicationModuleTest`:
6
6
7
+
.A application module integration test class
7
8
[source, java]
8
9
----
10
+
package example.order;
11
+
9
12
@ApplicationModuleTest
10
13
class OrderIntegrationTests {
11
14
12
15
// Individual test cases go here
13
16
}
14
17
----
15
18
19
+
This will run you integration test similar to what `@SpringBootTest` would have achieved but with the bootstrap actually limited to the application module the test resides in.
16
20
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:
17
21
18
-
[source]
22
+
.The log output of a application module integration test bootstrap
23
+
[source, text, subs="macros"]
19
24
----
20
25
. ____ _ __ _ _
21
26
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
@@ -36,10 +41,11 @@ If you configure the log level for `org.springframework.modulith` to `DEBUG`, yo
36
41
… - + ….internal.OrderInternal
37
42
… - Starting OrderIntegrationTests using Java 17.0.3 …
38
43
… - No active profile set, falling back to 1 default profile: "default"
39
-
… - Re-configuring auto-configuration and entity scan packages to: example.order.
44
+
… - pass:quotes[**Re-configuring auto-configuration and entity scan packages to: example.order.**]
40
45
----
41
46
42
47
Note, how the output contains the detailed information about the module included in the test run.
48
+
It creates the application module module, finds the module to be run and limits the application of auto-configuration, component and entity scanning to the corresponding packages.
43
49
44
50
[[testing.bootstrap-modes]]
45
51
== Bootstrap Modes
@@ -57,6 +63,7 @@ When an application module is bootstrapped, the Spring beans it contains will be
57
63
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).
58
64
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.
59
65
66
+
.Mocking Spring bean dependencies in other application modules
Copy file name to clipboardExpand all lines: src/docs/asciidoc/40-events.adoc
+49-1Lines changed: 49 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -30,6 +30,9 @@ The `complete(…)` method creates functional gravity in the sense that it attra
30
30
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>>).
31
31
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.
32
32
33
+
We can change the application module interaction as follows:
34
+
35
+
.Publishing an application event via Spring's `ApplicationEventListener`
33
36
[source, java]
34
37
----
35
38
@Service
@@ -49,12 +52,14 @@ public class OrderManagement {
49
52
}
50
53
----
51
54
52
-
Note, how we use Spring's `ApplicationEventPublisher` to publish a domain event, once we have completed the state transitions on the primary aggregate.
55
+
Note, how, instead of depending on the other application module's Spring bean, we use Spring's `ApplicationEventPublisher` to publish a domain event, once we have completed the state transitions on the primary aggregate.
56
+
For a more aggregate-driven approach to even publication, see https://docs.spring.io/spring-data/data-commons/docs/current/reference/html/#core.domain-events[Spring Data's application event publication mechanism] for details.
53
57
As event publication happens synchronously by default, the transactional semantics of the overall arrangement stay the same as in the example above.
54
58
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.
55
59
56
60
A different way of approaching this is by moving the event consumption to asynchronous handling at transaction commit and treat secondary functionality exactly as that:
57
61
62
+
.An async, transactional event listener
58
63
[source, java]
59
64
----
60
65
@Service
@@ -117,3 +122,46 @@ The following starters are available:
117
122
* `spring-modulith-starter-jdbc` -- Using JDBC as persistence technology. Also works in JPA-based applications but bypasses your JPA provider for actual event persistence.
118
123
* `spring-modulith-starter-mongodb` -- Using MongoDB behind Spring Data MongoDB.
119
124
125
+
[[events.integration-testing]]
126
+
== Integration Testing Application Modules Workting with Events
127
+
128
+
Integration tests for application modules that interact with other modules' Spring beans usually have those mocked and the test cases verify the interaction by verifying that that mock bean was invoked in a particular way.
129
+
130
+
.Traditional integration testing of the application module interaction
In an event-based application interaction model, the dependency to the other application module's Spring bean is gone and we have nothing to verify.
150
+
Spring Modulith's `@ApplicationModuleTest` enables the ability to get a `PublishedEvents` instance injected into the test method to verify a particular set of events has been published during the course of the business operation under test.
151
+
152
+
.Event-based intergration testing of the application module arrangement
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
7
-
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
8
-
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
9
-
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
4
+
The application module model created via `ApplicationModules` can be used to create documentation snippets for inclusion into developer documentation written in Asciidoc.
5
+
Spring Modulith's `Documenter` abstraction can produce two different kinds of snippets:
6
+
7
+
* C4 and UML component diagrams describing the relationships between the individual application modules
8
+
* A so called __Application Module Canvas__, a tabular overview about the module and the most relevant elements in those (Spring beans, aggregate roots, events published and listened to as well as configuration properties).
* __The application module's name and base package.__
77
+
* __The Spring beans exposed by the application module, grouped by stereotype.__ -- In other words beans that are located in either the API package or any <<fundamentals.modules.named-interface, named interface package>>.
78
+
* __Exposed aggregate roots__
79
+
* __Application events published by the module__ -- Those event types need to be demarcated using jMolecules `@DomainEvent` or implement its `DomainEvent` interface.
80
+
* __Application events listened to by the module__ -- Derived from methods annotated with Spring's `@EventListener`, `@TransactionalEventListener` or beans implementing `ApplicationListener`.
81
+
* __Configuration properties__ -- Requires the usage of the `spring-boot-configuration-processor` artifact to extract the metadata attached to the properties.
0 commit comments