GH-285 - Migrate documentation to Antora.

This commit is contained in:
Rob Winch
2023-08-24 17:30:34 -07:00
committed by Oliver Drotbohm
parent 607d046621
commit 56036d151a
25 changed files with 247 additions and 209 deletions

31
.github/workflows/deploy-docs.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Deploy Docs
on:
push:
branches-ignore: [ gh-pages ]
tags: '**'
repository_dispatch:
types: request-build-reference # legacy
#schedule:
#- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: docs-build
fetch-depth: 1
- name: Dispatch (partial build)
if: github.ref_type == 'branch'
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
- name: Dispatch (full build)
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD)

View File

@@ -1,36 +0,0 @@
name: Publish Documentation
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
jobs:
build:
name: Publish documentation
runs-on: ubuntu-latest
steps:
- name: Check out sources
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
cache: 'maven'
- name: Setup Graphviz
uses: ts-graphviz/setup-graphviz@v1
- name: Deploy documentation
env:
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
run: ./mvnw -B clean deploy -Pdocumentation

6
.gitignore vendored
View File

@@ -14,3 +14,9 @@ changelog.txt
#IntelliJ Stuff
.idea
*.iml
# Antora
node
node_modules
package.json
package-lock.json

83
pom.xml
View File

@@ -41,7 +41,6 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring.version>6.0.11</spring.version> <!-- For Javadoc links only -->
<spring-asciidoctor-backends.version>0.0.7</spring-asciidoctor-backends.version>
<spring-boot.version>3.2.0-M2</spring-boot.version>
</properties>
@@ -277,76 +276,34 @@ limitations under the License.
<plugins>
<!--
Asciidoctor
Antora
-->
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>2.2.2</version>
<dependencies>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby</artifactId>
<version>9.3.7.0</version>
</dependency>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj</artifactId>
<version>2.5.7</version>
</dependency>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj-diagram</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>io.spring.asciidoctor.backends</groupId>
<artifactId>spring-asciidoctor-backends</artifactId>
<version>${spring-asciidoctor-backends.version}</version>
</dependency>
</dependencies>
<groupId>io.spring.maven.antora</groupId>
<artifactId>antora-component-version-maven-plugin</artifactId>
<version>0.0.4</version>
<executions>
<execution>
<id>html</id>
<phase>generate-resources</phase>
<goals>
<goal>process-asciidoc</goal>
<goal>antora-component-version</goal>
</goals>
<configuration>
<backend>spring-html</backend>
<sourceDirectory>../src/docs/asciidoc</sourceDirectory>
<sourceDocumentName>index.adoc</sourceDocumentName>
<outputDirectory>${generated-docs.directory}/html</outputDirectory>
<attributes>
<source-highlighter>highlight.js</source-highlighter>
<highlightjsdir>js/highlight</highlightjsdir>
<highlightjs-theme>github</highlightjs-theme>
</attributes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.spring.maven.antora</groupId>
<artifactId>antora-maven-plugin</artifactId>
<version>0.0.4</version>
<extensions>true</extensions>
<configuration>
<doctype>book</doctype>
<attributes>
<docinfo>shared</docinfo>
<icons>font</icons>
<sectids>false</sectids>
<imagesdir>images</imagesdir>
<projectName>${project.name}</projectName>
<projectVersion>${project.version}</projectVersion>
<springVersion>${spring.version}</springVersion>
<toclevels>4</toclevels>
<numbered>true</numbered>
</attributes>
<requires>
<require>asciidoctor-diagram</require>
</requires>
<playbook>../src/docs/antora/antora-playbook.yml</playbook>
<options>
<option>--to-dir=target/antora/site</option>
<option>--stacktrace</option>
<option>--fetch</option>
</options>
</configuration>
</plugin>
<!--
@@ -415,6 +372,12 @@ limitations under the License.
</plugins>
</pluginManagement>
<resources>
<resource>
<directory>src/docs/antora/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</profile>

View File

@@ -36,10 +36,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

View File

@@ -0,0 +1,33 @@
name: Deploy Docs
on:
push:
branches-ignore: [ gh-pages ]
tags: '**'
repository_dispatch:
types: request-build-reference # legacy
#schedule:
#- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions:
actions: write
jobs:
build:
runs-on: ubuntu-latest
# FIXME: enable when pushed to spring-projects
# if: github.repository_owner == 'spring-projects'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: docs-build
fetch-depth: 1
- name: Dispatch (partial build)
if: github.ref_type == 'branch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
- name: Dispatch (full build)
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD)

View File

@@ -0,0 +1,37 @@
antora:
extensions:
- '@springio/antora-extensions/partial-build-extension'
- require: '@springio/antora-extensions/latest-version-extension'
- require: '@springio/antora-extensions/inject-collector-cache-config-extension'
- '@antora/collector-extension'
- '@antora/atlas-extension'
- require: '@springio/antora-extensions/root-component-extension'
root_component_name: 'modulith'
site:
title: Spring Modulith
url: https://docs.spring.io/spring-modulith/reference/
content:
sources:
- url: ./../../..
branches: HEAD
start_path: src/docs/antora
worktrees: true
asciidoc:
attributes:
page-pagination: ''
hide-uri-scheme: '@'
tabs-sync-option: '@'
chomp: 'all'
extensions:
- '@asciidoctor/tabs'
- '@springio/asciidoctor-extensions'
sourcemap: true
urls:
latest_version_segment: ''
runtime:
log:
failure_level: warn
format: pretty
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.3.5/ui-bundle.zip

View File

@@ -0,0 +1,17 @@
name: modulith
version: true
title: Spring Modulith
nav:
- modules/ROOT/nav.adoc
ext:
collector:
run:
command: mvnw -N antora-component-version:antora-component-version resources:resources -am -Pdocumentation
local: true
scan:
dir: target/classes/antora-resources/
asciidoc:
attributes:
attribute-missing: 'warn'
chomp: 'all'

View File

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

View File

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -0,0 +1,10 @@
* xref:index.adoc[Overview]
* xref:fundamentals.adoc[]
* xref:verification.adoc[]
* xref:events.adoc[]
* xref:testing.adoc[]
* xref:moments.adoc[]
* xref:documentation.adoc[]
* xref:runtime.adoc[]
* xref:production-ready.adoc[]
* xref:appendix.adoc[]

View File

@@ -1,9 +1,9 @@
[[appendix]]
= Appendix
:jdbc-schema-base: ../../../spring-modulith-events/spring-modulith-events-jdbc/src/main/resources
:jdbc-schema-base: partial$spring-modulith-events-jdbc-src/main/resources
[appendix]
[[appendix.configuration-properties]]
[[configuration-properties]]
== Spring Modulith Configuration Properties
[cols="2,1,3", options="header, unbreakable"]
|===
@@ -31,7 +31,7 @@
|`spring.modulith.moments.enableTimeMachine`
|`false`
|Whether to enable the <<moments, `TimeMachine`>>.
|Whether to enable the xref:moments.adoc[`TimeMachine`].
|`spring.modulith.moments.granularity`
|`HOURS`
@@ -51,7 +51,7 @@
|===
[appendix]
[[appendix.artifacts]]
[[artifacts]]
== Spring Modulith modules
.Spring Modulith starter POMs
@@ -119,19 +119,19 @@ a|* `spring-modulith-docs`
|`spring-modulith-events-jpa`|`runtime`|A JPA-based implementation of the `EventPublicationRegistry`.
|`spring-modulith-events-kafka`|`runtime`|Event externalization support for Kafka.
|`spring-modulith-events-mongodb`|`runtime`|A MongoDB-based implementation of the `EventPublicationRegistry`.
|`spring-modulith-moments`|`compile`|The Passage of Time events implementation described <<moments, here>>.
|`spring-modulith-moments`|`compile`|The Passage of Time events implementation described xref:moments.adoc[here].
|`spring-modulith-runtime`|`runtime`|Support to bootstrap an `ApplicationModules` instance at runtime. Usually not directly depended on but transitively used by `spring-modulith-actuator` and `spring-modulith-observability`.
|`spring-modulith-observability`|`runtime`|Observability infrastructure described <<observability, here>>.
|===
[appendix]
[[appendix.schemas]]
[[schemas]]
== Event publication registry schemas
The JDBC-based event publication registry support expects the following database schemas to be present in the database.
If you would like Spring Modulith to create the schema for you, set the application property `spring.modulith.events.jdbc-schema-initialization.enabled` to `true`.
[[appendix.schemas.h2]]
[[schemas.h2]]
=== H2
[source, sql]
@@ -139,7 +139,7 @@ If you would like Spring Modulith to create the schema for you, set the applicat
include::{jdbc-schema-base}/schema-h2.sql[]
----
[[appendix.schemas.hsqldb]]
[[schemas.hsqldb]]
=== HSQLDB
[source, sql]
@@ -147,7 +147,7 @@ include::{jdbc-schema-base}/schema-h2.sql[]
include::{jdbc-schema-base}/schema-hsqldb.sql[]
----
[[appendix.schemas.mysql]]
[[schemas.mysql]]
=== MySQL
[source, sql]
@@ -155,7 +155,7 @@ include::{jdbc-schema-base}/schema-hsqldb.sql[]
include::{jdbc-schema-base}/schema-mysql.sql[]
----
[[appendix.schemas.postgresql]]
[[schemas.postgresql]]
=== PostgreSQL
[source, sql]
@@ -164,7 +164,7 @@ include::{jdbc-schema-base}/schema-postgresql.sql[]
----
[appendix]
[[appendix.migrating-from-moduliths]]
[[migrating-from-moduliths]]
== Migrating from Moduliths

View File

@@ -7,7 +7,7 @@ Spring Modulith's `Documenter` abstraction can produce two different kinds of sn
* C4 and UML component diagrams describing the relationships between the individual application modules
* 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).
[[documentation.component-diagrams]]
[[component-diagrams]]
== Generating Application Module Component diagrams
The documentation snippets can be generated by handing the `ApplicationModules` instance into a `Documenter`.
@@ -86,7 +86,7 @@ Rel_D(Modulith.Application.order, Modulith.Application.catalog, "depends on", $t
SHOW_LEGEND()
....
[[documentation.component-diagrams.uml]]
[[component-diagrams.uml]]
=== Using Traditional UML Component Diagrams
If you prefer the traditional UML style component diagrams, tweak the `DiagramOptions` to rather use that style as follows:
@@ -178,7 +178,7 @@ package "Application" <<Container>> {
6 .[#707070].> 7 : uses
....
[[documentation.application-module-canvas]]
[[application-module-canvas]]
== Generating Application Module Canvases
The Application Module Canvases can be generated by calling `Documenter.writeModuleCanvases()`:
@@ -246,7 +246,7 @@ _Others_
It consists of the following sections:
* __The application module's base package.__
* __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-interfaces, named interface package>>.
* __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 xref:fundamentals.adoc#modules.named-interfaces[named interface package].
This will detect component stereotypes defined by https://github.com/xmolecules/jmolecules/tree/main/jmolecules-architecture[jMolecules architecture abstractions], but also standard Spring stereotype annotations.
* __Exposed aggregate roots__ -- Any entities that we find repositories for or explicitly declared as aggregate via jMolecules.
* __Application events published by the module__ -- Those event types need to be demarcated using jMolecules `@DomainEvent` or implement its `DomainEvent` interface.

View File

@@ -1,9 +1,10 @@
[[events]]
:imagesdir: images
[[working-with-application-events]]
= Working with Application Events
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>>).
This avoids the originating module to know about all potentially interested parties, which is a key aspect to enable application module integration testing (see xref:testing.adoc[Integration Testing Application Modules]).
Often we will find application components defined like this:
@@ -27,7 +28,7 @@ public class OrderManagement {
----
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>>).
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 xref:testing.adoc#efferent-dependencies[Dealing with 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.
We can change the application module interaction as follows:
@@ -75,7 +76,7 @@ This now effectively decouples the original transaction from the execution of th
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 even fully work, as the system might fail before the method is even invoked.
[[events.aml]]
[[aml]]
== Application Module Listener
To run a transactional event listener in a transaction itself, it would need to be annotated with `@Transactional` in turn.
@@ -106,7 +107,7 @@ class InventoryManagement {
}
----
[[events.publication-registry]]
[[publication-registry]]
== The Event Publication Registry
Spring Modulith ships with an event publication registry that hooks into the core event publication mechanism of Spring Framework.
@@ -117,14 +118,12 @@ image::event-publication-registry-start.png[]
Each transactional event listener is wrapped into an aspect that marks that log entry as completed if the execution of the listener succeeds.
In case the listener fails, the log entry stays untouched so that retry mechanisms can be deployed depending on the application's needs.
Automatic republication on application restart can be enabled by setting the `spring.modulith.republish-outstanding-events-on-restart` property to `true`.
Note, that this is only recommended for single-instance application deployments.
For a more flexible arrangement, `EventPublicationRegistry` exposes a method `….findIncompletePublications()` that can be called from user code.
By default, all incomplete event publications are resubmitted at application startup.
.The transactional event listener arrangement after execution
image::event-publication-registry-end.png[]
[[events.publication-registry.managing-publications]]
[[publication-registry.managing-publications]]
=== Managing Event Publications
Event publications may need to be managed in a variety of ways during the runtime of an application.
@@ -148,24 +147,24 @@ This artifact contains two primary abstractions, that are available to applicati
* `CompletedEventPublications` -- This interface allows accessing all completed event publications, and provides API to immediately purge all of them from the database or the completed publications older that a given duration (for example, 1 minute).
* `IncompleteEventPublications`-- This interface allows accessing all incomplete event publications to resubmit either the ones matching a given predicate or older than a given `Duration` relative to the original publishing date.
[[events.publication-registry.publication-repositories]]
[[publication-registry.publication-repositories]]
=== Event Publication Repositories
To actually write the event publication log, Spring Modulith exposes an `EventPublicationRepository` SPI and implementations for popular persistence technologies that support transactions, like JPA, JDBC and MongoDB.
You select the persistence technology to be used by adding the corresponding JAR to your Spring Modulith application.
We have prepared dedicated <<events.starters, starters>> to ease that task.
We have prepared dedicated xref:events.adoc#starters[starters] to ease that task.
The JDBC-based implementation can create a dedicated table for the event publication log when the respective configuration property (`spring.modulith.events.jdbc-schema-initialization.enabled`) is set to `true`.
For details, please consult the <<appendix.schemas, schema overview>> in the appendix.
For details, please consult the xref:appendix.adoc#schemas[schema overview] in the appendix.
[[events.publication-registry.serialization]]
[[publication-registry.serialization]]
=== Event Serializer
Each log entry contains the original event in serialized form.
The `EventSerializer` abstraction contained in `spring-modulith-events-core` allows plugging different strategies for how to turn the event instances into a format suitable for the datastore.
Spring Modulith provides a Jackson-based JSON implementation through the `spring-modulith-events-jackson` artifact, which registers a `JacksonEventSerializer` consuming an `ObjectMapper` through standard Spring Boot auto-configuration by default.
[[events.publication-registry.customize-publication-date]]
[[publication-registry.customize-publication-date]]
=== Customizing the Event Publication Date
By default, the Event Publication Registry will use the date returned by the `Clock.systemUTC()` as event publication date.
If you want to customize this, register a bean of type clock with the application context:
@@ -182,7 +181,7 @@ class MyConfiguration {
}
----
[[events.externalization]]
[[externalization]]
== Externalizing Events
Some of the events exchanged between application modules might be interesting to external systems.
@@ -195,7 +194,7 @@ To use that support you need to take the following steps:
To find out how to use other ways of selecting events for externalization, or customize their routing within the broker, check out <<events.externalization.fundamentals>>.
[[events.externalization.infrastructure]]
[[externalization.infrastructure]]
=== Supported Infrastructure
[%header,cols="1,3,6"]
@@ -219,7 +218,7 @@ The logical routing key will be used as AMQP routing key.
Does not support routing keys.
|===
[[events.externalization.fundamentals]]
[[externalization.fundamentals]]
=== Fundamentals of Event Externalization
The event externalization performs three steps on each application event published.
@@ -237,7 +236,7 @@ In other words, in a Spring Boot application with a base package of `com.acme.ap
Some brokers also allow to define a rather dynamic routing key, that is used for different purposes within the actual target.
By default, no routing key is used.
[[events.externalization.annotations]]
[[externalization.annotations]]
=== Annotation-based Event Externalization Configuration
To define a custom routing key via the `@Externalized` annotations, a pattern of `$target::$key` can be used for the target/value attribute available in each of the particular annotations.
@@ -266,7 +265,7 @@ If the key calculation becomes more involved, it is advisable to rather delegate
@Externalized("…::#{@beanName.someMethod(#this)}")
----
[[events.externalization.api]]
[[externalization.api]]
=== Programmatic Event Externalization Configuration
@@ -281,7 +280,7 @@ class ExternalizationConfiguration {
@Bean
EventExternalizationConfiguration eventExternalizationConfiguration() {
return EventExternalizationConfiguration.externalizing() // <1>
return EventExternalizationConfiguration.externalizing() // <1>
.select(EventExternalizationConfiguration.annotatedAsExternalized()) // <2>
.mapping(SomeEvent.class, it -> …) // <3>
.routeKey(WithKeyProperty.class, WithKeyProperty::getKey) // <4>
@@ -299,7 +298,7 @@ Note, that the routing will still be determined by the original event instance,
<4> We finally determine a routing key by defining a method handle to extract a value of the event instance.
Alternatively, a full `RoutingKey` can be produced for individual events by using the general `route(…)` method on the `Router` instance returned from the previous call.
[[events.testing]]
[[testing]]
== Testing published events
NOTE: The following section describes a testing approach solely focused on tracking Spring application events.
@@ -348,11 +347,11 @@ class OrderIntegrationTests {
Note, how the type returned by the `assertThat(…)` expression allows to define constraints on the published events directly.
[[events.starters]]
[[starters]]
== Spring Boot Event Registry Starters
Using the transactional event publication log requires a combination of artifacts added to your application.
To ease that task, Spring Modulith provides starter POMs that are centered around the <<events.publication-repositories, persistence technology>> to be used and default to the Jackson-based `EventSerializer` implementation.
To ease that task, Spring Modulith provides starter POMs that are centered around the xref:events.adoc#publication-repositories[persistence technology] to be used and default to the Jackson-based `EventSerializer` implementation.
The following starters are available:
* `spring-modulith-starter-jpa` -- Using JPA as persistence technology.
@@ -361,4 +360,3 @@ Also works in JPA-based applications but bypasses your JPA provider for actual e
* `spring-modulith-starter-mongodb` -- Using MongoDB behind Spring Data MongoDB.
Also enables MongoDB transactions and requires a replica set setup of the server to interact with.
The transaction auto-configuration can be disabled by setting the `spring.modulith.events.mongobd.transaction-management.enabled` property to `false`.

View File

@@ -5,7 +5,7 @@ Spring Modulith supports developers implementing logical modules in Spring Boot
It allows them to apply structural validation, document the module arrangement, run integration tests for individual modules, observe the modules interaction at runtime and generally implement module interaction in a loosely-coupled way.
This section will discuss the fundamental concepts that developers need to understand before diving into the technical support.
[[fundamentals.modules]]
[[modules]]
== Application modules
In a Spring Boot application, an application module is a unit of functionality that consists of the following parts:
@@ -17,7 +17,7 @@ In a Spring Boot application, an application module is a unit of functionality t
Spring Moduliths provides different ways of expressing modules within Spring Boot applications, primarily differing in the level of complexity involved in the overall arrangement.
This allows developers to start simple and naturally move to more sophisticated means as and if needed.
[[fundamentals.modules.simple]]
[[modules.simple]]
=== Simple Application Modules
The application's _main package_ is the one that the main application class resides in.
@@ -44,7 +44,7 @@ icon:cubes[] Example
<1> The application's main package `example`.
<2> An application module package `inventory`.
[[fundamentals.modules.advanced]]
[[modules.advanced]]
=== Advanced Application Modules
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.
@@ -73,7 +73,7 @@ Note, how `SomethingOrderInternal` is a public type, likely because `OrderManage
This unfortunately means, that it can also be referred to from other packages such as the `inventory` one.
In this case, the Java compiler is not of much use to prevent these illegal references.
[[fundamentals.modules.explicit-dependencies]]
[[modules.explicit-dependencies]]
=== Explicit Application Module Dependencies
A module can opt into declaring its allowed dependencies by using the `@ApplicationModule` annotation on the `package-info.java` type.
@@ -87,9 +87,9 @@ package example.inventory;
----
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).
Find out about how to monitor that in <<verification>>.
Find out about how to monitor that in xref:verification.adoc[Verifying Application Module Structure].
[[fundamentals.modules.application-modules]]
[[modules.application-modules]]
=== The `ApplicationModules` Type
Spring Moduliths allows to inspect a codebase to derive an application module model based on the given arrangement and optional configuration.
@@ -129,10 +129,10 @@ modules.forEach(System.out::println);
Note, how each module is listed and the contained Spring components are identified and the respective visibility is rendered, too.
[[fundamentals.modules.named-interfaces]]
[[modules.named-interfaces]]
=== Named Interfaces
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.
By default and as described in xref:fundamentals.adoc#modules.advanced[Advanced Application Modules], an application module's base package is considered the API package and thus is the only package to allow incoming dependencies from other modules.
In case you would like to expose additional packages to other modules, you need to use __named interfaces__.
You achieve that by annotating the `package-info.java` file of those package with `@NamedInterface`.
@@ -176,7 +176,7 @@ Note how we concatenate the named interface's name `spi` via the double colon `:
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.
For modules without explicitly described dependencies, both the application module root package *and* the SPI one are accessible.
[[fundamentals.customizing-modules]]
[[customizing-modules]]
=== Customizing Module Detection
If the default application module model does not work for your application, the detection of the modules can be customized by providing an implementation of `ApplicationModuleDetectionStrategy`.

View File

@@ -1,6 +1,14 @@
[preface]
[[preface]]
= Preface
[[spring-modulith-reference-documentation]]
= Spring Modulith
Oliver Drotbohm
:revnumber: {version}
:revdate: {localdate}
:icons: font
© 2022-2023 The original authors.
NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
[[preface.project-metadata]]
== Project Metadata
@@ -12,6 +20,7 @@
* Snapshot repository: https://repo.spring.io/snapshot
* Javadoc: https://docs.spring.io/spring-modulith/docs/{projectVersion}/api
[[using-spring-modulith]]
== Using Spring Modulith
Spring Modulith consists of a set of libraries that can be used individually and depending on which features of it you would like to use.
@@ -34,8 +43,10 @@ To ease the declaration of the individual modules, we recommend to declare the f
----
The individual sections describing Spring Modulith features will refer to the individual artifacts that are needed to make use of the feature.
For an overview about all modules available, have a look at <<appendix.artifacts>>.
For an overview about all modules available, have a look at xref:appendix.adoc#artifacts[Spring Modulith modules].
[[examples]]
== Examples
If you would like to play with the features of the project and see them live in action, check out the examples https://github.com/spring-projects/spring-modulith/tree/{projectVersion}/spring-modulith-examples[here]

View File

@@ -19,7 +19,7 @@ This will include the actuator and observability support as well as Spring Boot'
Note, that you will still have to add further dependencies to connect your application to your monitoring tools such as https://zipkin.io/[Zipkin], https://docs.wavefront.com/[Wavefront] etc. usually via https://opentelemetry.io/[OpenTelemetry] or https://github.com/openzipkin/brave[Brave].
Find more information on that in https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.micrometer-tracing[the corresponding section] of Spring Boot's reference documentation.
[[production-ready.actuator]]
[[actuator]]
== [[observability.actuator]]Application Module Actuator
The application module structure can be exposed as Spring Boot actuator.
@@ -81,12 +81,12 @@ The `modulith` resource adheres to the following structure:
|===
|JSONPath|Description
|`$.{moduleName}`|The technical name of an application module. Target for module references in `dependencies.target`.
|`$.{moduleName}.displayName`|The human-readable name of the application module.
|`$.{moduleName}.basePackage`|The application module's base package.
|`$.{moduleName}.dependencies[]`|All outgoing dependencies of the application module
|`$.{moduleName}.dependencies[].target`|The name of the application module depended on. A reference to a `{moduleName}`.
|`$.{moduleName}.dependencies[].types[]`|The types of dependencies towards the target module. Can either be `DEFAULT` (simple type dependency), `USES_COMPONENT` (Spring bean dependency) or `EVENT_LISTENER`.
|`+$.{moduleName}+`|The technical name of an application module. Target for module references in `dependencies.target`.
|`+$.{moduleName}.displayName+`|The human-readable name of the application module.
|`+$.{moduleName}.basePackage+`|The application module's base package.
|`+$.{moduleName}.dependencies[]+`|All outgoing dependencies of the application module
|`+$.{moduleName}.dependencies[].target+`|The name of the application module depended on. A reference to a `+{moduleName}+`.
|`+$.{moduleName}.dependencies[].types[]+`|The types of dependencies towards the target module. Can either be `DEFAULT` (simple type dependency), `USES_COMPONENT` (Spring bean dependency) or `EVENT_LISTENER`.
|===
An example module arrangement would look like this:
@@ -111,7 +111,7 @@ An example module arrangement would look like this:
}
----
[[production-ready.observability]]
[[observability]]
== [[observability]]Observing Application Modules
:imagesdir: images

View File

@@ -4,7 +4,7 @@
The functionality described in previous chapters have all used the application module arrangement in either testing scenarios for verification and documentation purposes or were general support functionality that help to loosely couple modules but did not work with the application module structure directly.
In this section we are going to describe Spring Modulith's support for
[[runtime.setup]]
[[setup]]
== Setting up Runtime Support for Application Modules
To enable the runtime support for Spring Modulith, make sure you include the `spring-modulith-runtime` JAR in your project.
@@ -24,9 +24,9 @@ Adding this JAR will cause Spring Boot auto-configuration to run that registers
* An `ApplicationModulesRuntime` that allows to access the `ApplicationModules`.
* A `SpringBootApplicationRuntime` to back the former bean to detect the main application class.
* An event listener for https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.spring-application.application-events-and-listeners[`ApplicationStartedEvent`]s that will invoke <<runtime.application-module-initializer, `ApplicationModuleInitializer`>> beans defined in the application context.
* An event listener for https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.spring-application.application-events-and-listeners[`ApplicationStartedEvent`]s that will invoke xref:runtime.adoc#application-module-initializer[`ApplicationModuleInitializer`] beans defined in the application context.
[[runtime.application-module-initializer]]
[[application-module-initializer]]
== Application Module Initializers
When working with application modules, it is pretty common to need to execute some code specific to an individual module on application startup.
@@ -73,4 +73,4 @@ class MyInitializer implements ApplicationModuleInitializer {
}
----
Note, that the `ApplicationModuleInitializer` beans will only be invoked if the `spring-modulith-runtime` JAR is on the classpath (see <<runtime.setup>>) as that pulls in the dependencies that are needed to topologically sort the initializers according to the application module structure.
Note, that the `ApplicationModuleInitializer` beans will only be invoked if the `spring-modulith-runtime` JAR is on the classpath (see xref:runtime.adoc#setup[Setting up Runtime Support for Application Modules]) as that pulls in the dependencies that are needed to topologically sort the initializers according to the application module structure.

View File

@@ -47,7 +47,7 @@ If you configure the log level for `org.springframework.modulith` to `DEBUG`, yo
Note, how the output contains the detailed information about the module included in the test run.
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.
[[testing.bootstrap-modes]]
[[bootstrap-modes]]
== Bootstrap Modes
The application module test can be bootstrapped in a variety of modes:
@@ -56,11 +56,11 @@ The application module test can be bootstrapped in a variety of modes:
* `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]]
[[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).
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 xref:testing.adoc#bootstrap-modes[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.
.Mocking Spring bean dependencies in other application modules
@@ -76,13 +76,13 @@ class InventoryIntegrationTests {
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>>).
The dependencies should be reviewed for whether they are candidates for replacement by publishing a domain event (see xref:events.adoc#events[null]).
[[testing.scenarios]]
[[scenarios]]
== Defining Integration Test Scenarios
Integration testing application modules can become a quite elaborate effort.
Especially if the integration of those is based on <<events.aml, asynchronous, transactional event handling>>, dealing with the concurrent execution can be subject to subtle errors.
Especially if the integration of those is based on xref:events.adoc#aml[asynchronous, transactional event handling], dealing with the concurrent execution can be subject to subtle errors.
Also, it requires dealing with quite a few infrastructure components: `TransactionOperations` and `ApplicationEventProcessor` to make sure events are published and delivered to transactional listeners, Awaitility to handle concurrency and AssertJ assertions to formulate expectations on the test execution's outcome.
To ease the definition of application module integration tests, Spring Modulith provides the `Scenario` abstraction that can be used by declaring it as test method parameter in tests declared as `@ApplicationModuleTest`.
@@ -178,7 +178,7 @@ The `result` handed into the `….andVerify(…)` method will be the value retur
By default, non-`null` values and non-empty ``Optional``s will be considered a conclusive state change.
This can be tweaked by using the `….andWaitForStateChange(…, Predicate)` overload.
[[testing.scenarios.customize]]
[[scenarios.customize]]
=== Customizing Scenario Execution
To customize the execution of an individual scenario, call the `….customize(…)` method in the setup chain of the `Scenario`:

View File

@@ -1,5 +1,6 @@
[[verification]]
= Verifying Application Module Structure
:page-section-summary-toc: 1
We can verify whether our code arrangement adheres to the intended constraints by calling the `….verify()` method on our `ApplicationModules` instance:
@@ -12,9 +13,9 @@ The verification includes the following rules:
* _No cycles on the application module level_ -- the dependencies between modules have to form directed, acyclic graph.
* _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.
See xref:fundamentals.adoc#modules.advanced[Advanced Application Modules] for details.
* _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.
See <<fundamentals.modules.explicit-dependencies>> and <<fundamentals.modules.named-interfaces>> for details.
See xref:fundamentals.adoc#modules.explicit-dependencies[Explicit Application Module Dependencies] and xref:fundamentals.adoc#modules.named-interfaces[Named Interfaces] for details.
Spring Modulith optionally integrates with the jMolecules ArchUnit library and, if present, automatically triggers its Domain-Driven Design verification rules described https://github.com/xmolecules/jmolecules-integrations/tree/main/jmolecules-archunit[here].

View File

@@ -0,0 +1 @@
../../../../../../spring-modulith-events/spring-modulith-events-jdbc/src

View File

@@ -0,0 +1,7 @@
version: ${antora-component.version}
prerelease: ${antora-component.prerelease}
asciidoc:
attributes:
attribute-missing: 'warn'
version: ${project.version}
projectversion: ${project.version}

View File

@@ -1,37 +0,0 @@
= Spring Modulith -- Reference documentation
Oliver Drotbohm
:revnumber: {projectVersion}
:revdate: {localdate}
:toc: left
:iconfont-fontawesome:
© 2022 The original authors.
NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
:leveloffset: +1
:!numbered:
include::00-preface.adoc[]
:numbered:
include::10-fundamentals.adoc[]
include::20-verification.adoc[]
include::30-events.adoc[]
include::40-testing.adoc[]
include::50-moments.adoc[]
include::60-documentation.adoc[]
include::70-runtime.adoc[]
include::80-observability.adoc[]
include::90-appendix.adoc[]
:leveloffset: -1