GH-285 - Migrate documentation to Antora.
This commit is contained in:
committed by
Oliver Drotbohm
parent
607d046621
commit
56036d151a
31
.github/workflows/deploy-docs.yml
vendored
Normal file
31
.github/workflows/deploy-docs.yml
vendored
Normal 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)
|
||||
36
.github/workflows/docs.yaml
vendored
36
.github/workflows/docs.yaml
vendored
@@ -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
6
.gitignore
vendored
@@ -14,3 +14,9 @@ changelog.txt
|
||||
#IntelliJ Stuff
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
# Antora
|
||||
node
|
||||
node_modules
|
||||
package.json
|
||||
package-lock.json
|
||||
83
pom.xml
83
pom.xml
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
33
src/docs/antora/.github/workflows/deploy-docs.yml
vendored
Normal file
33
src/docs/antora/.github/workflows/deploy-docs.yml
vendored
Normal 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)
|
||||
37
src/docs/antora/antora-playbook.yml
Normal file
37
src/docs/antora/antora-playbook.yml
Normal 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
|
||||
17
src/docs/antora/antora.yml
Normal file
17
src/docs/antora/antora.yml
Normal 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'
|
||||
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
10
src/docs/antora/modules/ROOT/nav.adoc
Normal file
10
src/docs/antora/modules/ROOT/nav.adoc
Normal 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[]
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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`:
|
||||
@@ -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].
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../spring-modulith-events/spring-modulith-events-jdbc/src
|
||||
7
src/docs/antora/resources/antora-resources/antora.yml
Normal file
7
src/docs/antora/resources/antora-resources/antora.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
version: ${antora-component.version}
|
||||
prerelease: ${antora-component.prerelease}
|
||||
asciidoc:
|
||||
attributes:
|
||||
attribute-missing: 'warn'
|
||||
version: ${project.version}
|
||||
projectversion: ${project.version}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user