diff --git a/pom.xml b/pom.xml
index c20998c2..9792668f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -96,6 +96,7 @@ limitations under the License.
spring-modulith-integration-test
+ spring-modulith-example
diff --git a/spring-modulith-example/pom.xml b/spring-modulith-example/pom.xml
new file mode 100644
index 00000000..27526186
--- /dev/null
+++ b/spring-modulith-example/pom.xml
@@ -0,0 +1,142 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.0.0-SNAPSHOT
+
+
+
+ spring-modulith-example
+ Spring Modulith - Example
+ 0.1.0-SNAPSHOT
+
+
+ 17
+
+
+
+
+
+ org.springframework.experimental
+ spring-modulith-bom
+ 0.1.0-SNAPSHOT
+ pom
+ import
+
+
+ org.jmolecules
+ jmolecules-bom
+ 2022.2.0-SNAPSHOT
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-json
+
+
+
+ org.springframework.experimental
+ spring-modulith-starter-jpa
+
+
+
+
+
+ org.jmolecules
+ jmolecules-events
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.experimental
+ spring-modulith-starter-test
+ test
+
+
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+
+
+ spring-snapshot
+ https://repo.spring.io/snapshot
+
+ false
+
+
+
+ spring-milestone
+ https://repo.spring.io/milestone
+
+ false
+
+
+
+
+
+
+ spring-snapshot
+ https://repo.spring.io/snapshot
+
+ false
+
+
+
+ spring-milestone
+ https://repo.spring.io/milestone
+
+ false
+
+
+
+
+
diff --git a/spring-modulith-example/src/main/java/example/Application.java b/spring-modulith-example/src/main/java/example/Application.java
new file mode 100644
index 00000000..826492cc
--- /dev/null
+++ b/spring-modulith-example/src/main/java/example/Application.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example;
+
+import example.order.Order;
+import example.order.OrderManagement;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+/**
+ * Spring Modulith example application
+ *
+ * @author Oliver Drotbohm
+ */
+@EnableAsync
+@SpringBootApplication
+@ConfigurationPropertiesScan
+public class Application {
+
+ public static void main(String... args) throws Exception {
+
+ SpringApplication.run(Application.class, args)
+ .getBean(OrderManagement.class)
+ .complete(new Order());
+ }
+}
diff --git a/spring-modulith-example/src/main/java/example/inventory/InventoryInternal.java b/spring-modulith-example/src/main/java/example/inventory/InventoryInternal.java
new file mode 100644
index 00000000..6190b9c3
--- /dev/null
+++ b/spring-modulith-example/src/main/java/example/inventory/InventoryInternal.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.inventory;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Oliver Drotbohm
+ */
+@Component
+class InventoryInternal {}
diff --git a/spring-modulith-example/src/main/java/example/inventory/InventoryManagement.java b/spring-modulith-example/src/main/java/example/inventory/InventoryManagement.java
new file mode 100644
index 00000000..7c0e6973
--- /dev/null
+++ b/spring-modulith-example/src/main/java/example/inventory/InventoryManagement.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.inventory;
+
+import example.order.OrderCompleted;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.event.TransactionalEventListener;
+
+/**
+ * @author Oliver Drotbohm
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class InventoryManagement {
+
+ private final InventoryInternal dependency;
+
+ @Async
+ @TransactionalEventListener
+ void on(OrderCompleted event) throws InterruptedException {
+
+ var orderId = event.getOrderId();
+
+ LOG.info("Received order completion for {}.", orderId);
+
+ // Simulate busy work
+ Thread.sleep(1000);
+
+ LOG.info("Finished order completion for {}.", orderId);
+ }
+}
diff --git a/spring-modulith-example/src/main/java/example/inventory/InventorySettings.java b/spring-modulith-example/src/main/java/example/inventory/InventorySettings.java
new file mode 100644
index 00000000..2bab8d95
--- /dev/null
+++ b/spring-modulith-example/src/main/java/example/inventory/InventorySettings.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.inventory;
+
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import lombok.Value;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.ConstructorBinding;
+
+/**
+ * Some Spring Boot configuration properties exposed by the inventory.
+ *
+ * @author Oliver Drotbohm
+ */
+@Value
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE, onConstructor = @__(@ConstructorBinding))
+@ConfigurationProperties("example.inventory")
+class InventorySettings {
+
+ /**
+ * Some Javadoc.
+ */
+ int stockThreshold;
+}
diff --git a/spring-modulith-example/src/main/java/example/order/Order.java b/spring-modulith-example/src/main/java/example/order/Order.java
new file mode 100644
index 00000000..2e11429b
--- /dev/null
+++ b/spring-modulith-example/src/main/java/example/order/Order.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.order;
+
+import lombok.Value;
+
+import java.util.UUID;
+
+/**
+ * @author Oliver Drotbohm
+ */
+@Value
+public class Order {
+
+ private OrderIdentifier id = new OrderIdentifier(UUID.randomUUID());
+
+ @Value
+ public static class OrderIdentifier {
+ UUID id;
+ }
+}
diff --git a/spring-modulith-example/src/main/java/example/order/OrderCompleted.java b/spring-modulith-example/src/main/java/example/order/OrderCompleted.java
new file mode 100644
index 00000000..661d2e7d
--- /dev/null
+++ b/spring-modulith-example/src/main/java/example/order/OrderCompleted.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.order;
+
+import example.order.Order.OrderIdentifier;
+import lombok.Value;
+
+import org.jmolecules.event.types.DomainEvent;
+
+/**
+ * @author Oliver Drotbohm
+ */
+@Value
+public class OrderCompleted implements DomainEvent {
+ OrderIdentifier orderId;
+}
diff --git a/spring-modulith-example/src/main/java/example/order/OrderManagement.java b/spring-modulith-example/src/main/java/example/order/OrderManagement.java
new file mode 100644
index 00000000..9200dc1a
--- /dev/null
+++ b/spring-modulith-example/src/main/java/example/order/OrderManagement.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.order;
+
+import example.order.internal.OrderInternal;
+import lombok.RequiredArgsConstructor;
+
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author Oliver Drotbohm
+ */
+@Service
+@RequiredArgsConstructor
+public class OrderManagement {
+
+ private final ApplicationEventPublisher events;
+ private final OrderInternal dependency;
+
+ @Transactional
+ public void complete(Order order) {
+ events.publishEvent(new OrderCompleted(order.getId()));
+ }
+}
diff --git a/spring-modulith-example/src/main/java/example/order/internal/OrderInternal.java b/spring-modulith-example/src/main/java/example/order/internal/OrderInternal.java
new file mode 100644
index 00000000..72640fab
--- /dev/null
+++ b/spring-modulith-example/src/main/java/example/order/internal/OrderInternal.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.order.internal;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Oliver Drotbohm
+ */
+@Component
+public class OrderInternal {}
diff --git a/spring-modulith-example/src/main/resources/application.properties b/spring-modulith-example/src/main/resources/application.properties
new file mode 100644
index 00000000..f3767c76
--- /dev/null
+++ b/spring-modulith-example/src/main/resources/application.properties
@@ -0,0 +1,5 @@
+spring.jackson.constructor-detector=use-properties-based
+
+spring.jpa.show-sql=true
+
+logging.level.org.springframework.modulith=info
\ No newline at end of file
diff --git a/spring-modulith-example/src/main/resources/logback.xml b/spring-modulith-example/src/main/resources/logback.xml
new file mode 100644
index 00000000..472b60e7
--- /dev/null
+++ b/spring-modulith-example/src/main/resources/logback.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ %d %5p %40.40c:%4L - %m%n
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-modulith-example/src/test/java/example/ApplicationIntegrationTests.java b/spring-modulith-example/src/test/java/example/ApplicationIntegrationTests.java
new file mode 100644
index 00000000..8ca81beb
--- /dev/null
+++ b/spring-modulith-example/src/test/java/example/ApplicationIntegrationTests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example;
+
+import example.order.Order;
+import example.order.OrderManagement;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * Integration test for the overall application.
+ *
+ * @author Oliver Drotbohm
+ */
+@SpringBootTest
+class ApplicationIntegrationTests {
+
+ @Autowired OrderManagement orders;
+
+ @Test
+ void completesOrder() throws Exception {
+
+ orders.complete(new Order());
+
+ Thread.sleep(2000);
+ }
+}
diff --git a/spring-modulith-example/src/test/java/example/ModularityTests.java b/spring-modulith-example/src/test/java/example/ModularityTests.java
new file mode 100644
index 00000000..0bb40cbc
--- /dev/null
+++ b/spring-modulith-example/src/test/java/example/ModularityTests.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.modulith.docs.Documenter;
+import org.springframework.modulith.docs.Documenter.CanvasOptions;
+import org.springframework.modulith.docs.Documenter.Options;
+import org.springframework.modulith.model.ApplicationModules;
+
+/**
+ * Tests to verify the modular structure and generate documentation for the modules.
+ *
+ * @author Oliver Drotbohm
+ */
+class ModularityTests {
+
+ ApplicationModules modules = ApplicationModules.of(Application.class);
+
+ @Test
+ void verifiesModularStructure() {
+ modules.verify();
+ }
+
+ @Test
+ void createModuleDocumentation() {
+
+ var canvasOptions = CanvasOptions.defaults();
+
+ new Documenter(modules) //
+ .writeDocumentation(Options.defaults(), canvasOptions);
+ }
+}
diff --git a/spring-modulith-example/src/test/java/example/order/EventPublicationRegistryTests.java b/spring-modulith-example/src/test/java/example/order/EventPublicationRegistryTests.java
new file mode 100644
index 00000000..c93b8895
--- /dev/null
+++ b/spring-modulith-example/src/test/java/example/order/EventPublicationRegistryTests.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.order;
+
+import static org.assertj.core.api.Assertions.*;
+
+import example.order.EventPublicationRegistryTests.FailingAsyncTransactionalEventListener;
+import lombok.RequiredArgsConstructor;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+import org.springframework.modulith.events.EventPublicationRegistry;
+import org.springframework.modulith.test.ApplicationModuleTest;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.transaction.event.TransactionalEventListener;
+
+/**
+ * A show case for how the Spring Modulith application event publication registry keeps track of incomplete publications
+ * for failing transactional event listeners
+ *
+ * @author Oliver Drotbohm
+ */
+@ApplicationModuleTest
+@RequiredArgsConstructor
+@Import(FailingAsyncTransactionalEventListener.class)
+@DirtiesContext
+class EventPublicationRegistryTests {
+
+ private final OrderManagement orders;
+ private final EventPublicationRegistry registry;
+
+ @Test
+ void leavesPublicationIncompleteForFailingListener() throws Exception {
+
+ var order = new Order();
+
+ orders.complete(order);
+
+ Thread.sleep(40);
+
+ assertThat(registry.findIncompletePublications()).hasSize(1);
+ }
+
+ static class FailingAsyncTransactionalEventListener {
+
+ @Async
+ @TransactionalEventListener
+ void foo(OrderCompleted event) {
+ throw new IllegalStateException();
+ }
+ }
+}
diff --git a/spring-modulith-example/src/test/java/example/order/OrderIntegrationTests.java b/spring-modulith-example/src/test/java/example/order/OrderIntegrationTests.java
new file mode 100644
index 00000000..8d520151
--- /dev/null
+++ b/spring-modulith-example/src/test/java/example/order/OrderIntegrationTests.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.order;
+
+import static org.assertj.core.api.Assertions.*;
+
+import lombok.RequiredArgsConstructor;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.modulith.test.ApplicationModuleTest;
+import org.springframework.modulith.test.PublishedEvents;
+
+/**
+ * @author Oliver Drotbohm
+ */
+@ApplicationModuleTest
+@RequiredArgsConstructor
+class OrderIntegrationTests {
+
+ private final OrderManagement orders;
+
+ @Test
+ void publishesOrderCompletion(PublishedEvents events) {
+
+ var reference = new Order();
+
+ orders.complete(reference);
+
+ assertThat(events.ofType(OrderCompleted.class))
+ .hasSize(1)
+ .element(0)
+ .extracting(OrderCompleted::getOrderId).isEqualTo(reference.getId());
+ }
+}