Master 2 Lernbuch - Moderne Enterprise Maven Modular Projekte
Architekturueberblick Master 2
Master 2 erweitert den ersten Maven-Master zu einer groesseren Enterprise-Referenz. Der Workspace ist auf fachliche Modulgrenzen, klare Abhaengigkeiten und austauschbare Infrastruktur ausgelegt.
Zielbild
Die Domain kennt keine REST-Controller, keine JPA-Entities, keine Kafka-Templates und keine Framework-Bootstrapping-Klassen. Application Services orchestrieren Use Cases ueber Ports. Adapter implementieren technische Details.
Modulgruppen
| Gruppe | Module | Zweck |
|---|---|---|
| Plattform | platform-bom, Parent POM |
Versionen, PluginManagement, Enforcer |
| Shared Kernel | shared-kernel |
Money, Result, DomainEvent, IdempotencyKey |
| Domain | order-domain, customer-domain, inventory-domain, pricing-domain, payment-domain |
Fachmodell und Regeln |
| Application | order-application |
Use Cases, Commands, Transactions-Port |
| Integration | integration-outbox |
Outbox, Relay, Saga |
| Adapter | REST, JPA, Kafka, Legacy ACL | Infrastruktur und Schnittstellen |
| Apps | Spring Boot, Quarkus | Laufzeitvarianten |
| Tests | architecture-tests |
ArchUnit-Regeln und Pattern-Pruefung |
Warum Maven modular?
Maven macht Abhaengigkeiten sichtbar. Wenn Domain-Module aus Versehen Spring oder JPA importieren, faellt das im Build und in Architekturtests auf.
Lernfokus
- Reactor verstehen: Parent, BOM, Module, pluginManagement.
- Ports and Adapters praktisch sehen.
- Fachliche Patterns direkt im Code finden.
- Outbox/Saga als Enterprise-Integrationsmuster verstehen.
- Spring Boot und Quarkus als alternative Runtimes ueber denselben Use Case vergleichen.
Maven Deep Dive
Parent POM
Der Parent POM definiert gemeinsame Java-Version, Plugin-Versionen und Module. Er soll nicht unkontrolliert Abhaengigkeiten in alle Module pumpen. Deshalb werden viele Abhaengigkeiten nur in den konkreten Modul-POMs gesetzt.
<modules>
<module>shared-kernel</module>
<module>order-domain</module>
<module>order-application</module>
<module>adapters-rest-spring</module>
<module>app-spring-boot</module>
</modules>
Dependency Management
Dependency Management legt Versionen zentral fest. Ein Modul entscheidet trotzdem selbst, ob es die Dependency braucht.
Plugin Management
Plugin Management vermeidet Versionsdrift. Der Compiler verwendet bewusst release, damit Source, Target und Bootclasspath konsistent sind.
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>${java.version}</release>
</configuration>
</plugin>
Reactor-Strategie
In grossen Projekten baust du selten alles. Beispiele:
mvn -pl order-domain test
mvn -pl app-spring-boot -am verify
mvn -pl architecture-tests -am test
-pl waehlt Module, -am baut benoetigte Abhaengigkeiten mit.
Typische Enterprise-Regel
Domain-Module duerfen keine Framework-Starter importieren. REST/JPA/Kafka gehoeren in Adaptermodule.
Entwurfsmuster im Maven Master 2
Aggregate Root
Zweck: Konsistenzgrenze fuer Bestellungen
Einsatzort: order-domain/Order.java
Begruendung: Zentralisiert Invarianten und Events
Value Object
Zweck: Unveraenderliche Fachwerte
Einsatzort: shared-kernel/Money.java, order-domain/OrderLine.java
Begruendung: Verhindert Primitive Obsession
Repository
Zweck: Persistenz-Port im Domain-Modell
Einsatzort: order-domain/OrderRepository.java
Begruendung: Domain bleibt DB-neutral
Factory Method
Zweck: Kontrollierte Aggregate-Erzeugung
Einsatzort: Order.start(), OrderId.newId()
Begruendung: Keine ungueltigen Startzustaende
Domain Event
Zweck: Fachliche Ereignisse explizit machen
Einsatzort: OrderPlaced.java
Begruendung: Entkopplung von Folgeprozessen
Specification
Zweck: Regeln kombinierbar ausdruecken
Einsatzort: OrderSpecification.java
Begruendung: Testbare Fachbedingungen
Strategy
Zweck: Preislogik austauschbar machen
Einsatzort: PricingStrategy.java
Begruendung: Varianten ohne if-Kaskaden
Policy
Zweck: Fachentscheidung kapseln
Einsatzort: CustomerCreditPolicy, InventoryReservationPolicy
Begruendung: Regeln bleiben eigenstaendig
Application Service
Zweck: Use Case orchestrieren
Einsatzort: PlaceOrderService.java
Begruendung: Keine Framework-Logik in Domain
Command
Zweck: Benutzerabsicht transportieren
Einsatzort: PlaceOrderCommand.java
Begruendung: Eingabe bleibt explizit und testbar
CQRS
Zweck: Lesen und Schreiben trennen
Einsatzort: OrderQueryService.java
Begruendung: Read Model darf optimiert sein
Adapter
Zweck: Technologie an Ports anschliessen
Einsatzort: REST, JPA, Kafka, Legacy
Begruendung: Austauschbarkeit
Facade
Zweck: Einheitliche REST-Fehler
Einsatzort: RestExceptionMapper.java
Begruendung: API bleibt konsistent
Data Mapper
Zweck: Entity von Domain trennen
Einsatzort: OrderEntity.java
Begruendung: JPA leakt nicht in Aggregate
Unit of Work
Zweck: Transaktionsgrenze kapseln
Einsatzort: TransactionBoundary.java
Begruendung: Application bleibt Framework-neutral
Transactional Outbox
Zweck: Dual-Writes vermeiden
Einsatzort: OutboxRecord.java
Begruendung: Event und Fachzustand in einer TX
Saga
Zweck: Verteilte Prozesse koordinieren
Einsatzort: PaymentSaga.java
Begruendung: Keine globale XA-Transaktion
Anti-Corruption Layer
Zweck: Legacy entkoppeln
Einsatzort: LegacyOrderGateway.java
Begruendung: Alte Modelle kontaminieren Domain nicht
Gateway
Zweck: Externes System kapseln
Einsatzort: LegacySoapClient.java
Begruendung: Technischer Zugriff bleibt schmal
Idempotent Consumer
Zweck: Retry-sicher verarbeiten
Einsatzort: IdempotentConsumerGuard.java
Begruendung: Doppelte Messages werden unschaedlich
Composition Root
Zweck: Verdrahtung zentralisieren
Einsatzort: EnterpriseSpringApplication, Quarkus Wiring
Begruendung: Domain baut keine Infrastruktur
Fitness Function
Zweck: Architektur pruefbar machen
Einsatzort: LayerRulesTest.java
Begruendung: Regeln werden Build-Bestandteil
Outbox und Saga Deep Dive
Problem
Ein Bestellservice soll eine Bestellung speichern und ein Event publizieren. Wenn erst die Datenbank schreibt und danach Kafka fehlschlaegt, entsteht ein inkonsistenter Zustand. Wenn erst Kafka publiziert und danach die Datenbank fehlschlaegt, entsteht ebenfalls Inkonsistenz.
Loesung: Transactional Outbox
Die Fachtransaktion schreibt Bestellung und Outbox-Datensatz gemeinsam. Ein Relay publiziert spaeter zuverlaessig.
// PATTERN: Transactional Outbox
orders.save(order);
order.domainEvents().forEach(outbox::append);
Saga
Eine Saga koordiniert laengere Prozesse wie Zahlung, Reservierung, Versand. Jeder Schritt hat Kompensationen oder klare Fehlerzustaende.
Wann sinnvoll?
- Payment und Fulfillment sind getrennte Systeme.
- Messaging ist asynchron.
- Kein globales XA ueber alle Systeme gewuenscht.
- Retries und Idempotenz sind Pflicht.
CQRS und Read Models
Idee
Das Schreibmodell schuetzt Invarianten. Das Lesemodell beantwortet Abfragen schnell und bequem. Dadurch muss das Aggregate nicht fuer Reporting, Suchlisten oder Dashboard-Zwecke verbogen werden.
Im Projekt
OrderQueryService ist bewusst von PlaceOrderUseCase getrennt. In einem echten Projekt koennte der Query-Service auf eine Projection-Tabelle, Elasticsearch, OpenSearch oder eine Materialized View zugreifen.
Merksatz
CQRS ist nicht automatisch Event Sourcing. CQRS bedeutet zuerst nur: Schreiben und Lesen werden getrennt modelliert.
Security und Observability
Security
Master 2 zeigt Security als Querschnitt, nicht als Domain-Bestandteil. Authentifizierung, Token-Validierung und technische Policies bleiben in App- und Adaptermodulen.
Observability
Spring Boot Actuator und Quarkus Health/Micrometer bilden Health, Metrics und Prometheus-Ausgabe ab. Fachliche Events sind nicht nur fuer Integration nuetzlich, sondern auch fuer Auditing und Diagnostik.
Build Gates
- Maven Enforcer fuer Java/Maven-Versionen.
- Architecture Tests fuer Layer-Regeln.
- Dependency Analyse und Lizenzbericht.
- Pattern-Marker-Test als Dokumentationsschutz.
Runbook
Lokaler Build
cd mvn-ws-sb3-quarkus-jpa-kafka
mvn -DskipTests verify
Nur Domain pruefen
mvn -pl order-domain,order-application test
Spring Boot App starten
mvn -pl app-spring-boot -am spring-boot:run
Quarkus App starten
mvn -pl app-quarkus -am quarkus:dev
Architekturtests
mvn -pl architecture-tests -am test
Was lokal mit Internet passiert
Maven laedt beim ersten Lauf externe Dependencies. Im ZIP sind keine externen JARs enthalten.
Glossar
Aggregate
Eine Konsistenzgrenze im Domain Model. Von aussen wird nur die Aggregate Root veraendert.
Adapter
Technische Umsetzung eines Ports. Beispiele: REST Controller, JPA Repository, Kafka Publisher, SOAP Client.
Anti-Corruption Layer
Schutzschicht gegen Legacy-Modelle. Alte Datenformen werden uebersetzt, bevor sie die neue Domain erreichen.
BOM
Bill of Materials. Zentralisiert Dependency-Versionen, ohne automatisch jede Dependency in jedes Modul zu ziehen.
Composition Root
Ort, an dem konkrete Implementierungen zusammengesetzt werden. In Spring ist das typischerweise Configuration/Bootstrapping.
Domain Event
Fachliches Ereignis wie OrderPlaced. Es beschreibt, was passiert ist, nicht was technisch als naechstes aufgerufen werden soll.
Outbox
Persistenter Zwischenspeicher fuer zu publizierende Events. Vermeidet Dual-Write-Probleme.
Port
Schnittstelle, die von der Application oder Domain benoetigt wird. Adapter implementieren Ports.
Reactor
Mavens Mechanismus, um Multi-Module-Projekte in korrekter Reihenfolge zu bauen.
Versionsannahmen und Quellenstand
Stand: 2026-07-07.
| Baustein | Im Projekt verwendet | Hinweis |
|---|---|---|
| Java | 21 | Enterprise-LTS-freundlich und kompatibel mit modernen Frameworks |
| Spring Boot | 4.1.0 | Parent nutzt Spring Boot BOM und Plugin |
| Quarkus | 3.37.1 | BOM und Maven Plugin im Parent |
| Jakarta EE | 11 | Dokumentations- und API-Zielbild |
| Maven Compiler Plugin | 3.15.0 | release-Konfiguration |
| Maven Enforcer Plugin | 3.6.3 | Build-Regeln |
| Maven Dependency Plugin | 3.11.0 | Analyse/Reports |
Die Versionen wurden im Master-POM zentralisiert, damit spaetere Deep-Dive-Versionen nur Properties aktualisieren muessen.
Master 2.1 Projektbeschreibungen und Ablauf
Dieses Dokument beschreibt kurz, was jedes Maven-Modul tut und wie der fachliche Ablauf durch die Module laeuft. Die gleichen Beschreibungen sind zusaetzlich direkt in den einzelnen Modulordnern als README.md und README.html integriert.
Fachlicher End-to-End-Ablauf
- HTTP Eingang:
adapters-rest-springoderapp-quarkusnimmt eine Bestellung entgegen. - Command: Der Eingang wird in
PlaceOrderCommanduebersetzt. - Use Case:
order-applicationorchestriert Kreditpruefung, Lagerpruefung, Preislogik, Bestellung und Transaktionsgrenze. - Domain:
order-domainerzwingt Invarianten und erzeugtOrderPlaced. - Persistenz:
adapters-persistence-jpaspeichert Bestellung und technische Transaktion. - Outbox:
integration-outboxspeichert Event-Auslieferungsabsicht und liefert spaeter aus. - Messaging:
adapters-messaging-kafkapubliziert Events und schuetzt Konsumenten mit Idempotenz. - Legacy:
adapters-legacy-aclkapselt altes SOAP-/Legacy-Modell. - Runtime:
app-spring-bootoderapp-quarkusstartet die gewuenschte Laufzeit. - Qualitaet:
architecture-testsprueft Modulgrenzen, Pattern-Marker und Architekturregeln.
Moduluebersicht
| Modul | Typ | Was es tut | Ablaufrolle |
|---|---|---|---|
platform-bom |
Maven-BOM / Build Governance | Zentralisiert Versionsverwaltung und gemeinsame Test-/Architektur-Bibliotheken, damit alle Module reproduzierbar dieselben Dependency-Versionen verwenden. | Wird zuerst im Reactor gelesen.; Andere Module importieren oder verwenden die verwalteten Versionen indirekt ueber den Parent. |
shared-kernel |
Domain Foundation | Enthaelt kleine, stabile Bausteine, die mehrere fachliche Bounded Contexts gemeinsam benutzen duerfen. | Domain-Module haengen von shared-kernel ab.; Aggregate registrieren Domain Events. |
order-domain |
Kern-Domain | Modelliert Bestellung, Bestellpositionen, Status, Repository-Port und fachliche Spezifikationen. | PlaceOrderService erstellt Order aus Command-Daten.; Order prueft Invarianten und registriert Domain Events. |
customer-domain |
Bounded Context | Kapselt Kundenregeln, besonders Kredit- und Freigabeentscheidungen fuer Bestellungen. | Order Application fragt Credit Policy vor dem Platzieren.; Policy entscheidet anhand Kundendaten und Risiko. |
inventory-domain |
Bounded Context | Beschreibt Reservierungsregeln fuer Lagerbestand, ohne direkt von Datenbank oder Messaging abzuhaengen. | Order Application prueft Reservierbarkeit.; Inventory Policy bewertet Positionen. |
pricing-domain |
Bounded Context | Berechnet Preise ueber austauschbare Strategien, damit Rabatt-, Staffel- oder Kampagnenlogik nicht im Order Aggregate landet. | Use Case uebergibt Warenkorb oder Positionen an Pricing Strategy.; Strategie berechnet Money-Werte. |
payment-domain |
Bounded Context / Port | Definiert die Zahlungsintegration als Port, damit echte Provider, Mocks oder Legacy-Zahlungssysteme austauschbar bleiben. | PaymentSaga oder Application ruft PaymentGatewayPort.; Ein Adapter fuehrt die echte Zahlung aus. |
fulfillment-domain |
Bounded Context | Plant Versand- oder Erfuellungsaktivitaeten nach erfolgreicher Bestellung und Zahlung. | Nach erfolgreicher Zahlung startet Fulfillment-Planung.; FulfillmentPlanner erzeugt naechste Schritte. |
notification-domain |
Bounded Context | Kapselt Benachrichtigungsvorlagen und die fachliche Entscheidung, welche Nachricht wann versendet werden soll. | Ein Event wie OrderPlaced loest Benachrichtigung aus.; Template erzeugt fachlichen Inhalt. |
order-application |
Use-Case-Schicht | Orchestriert den fachlichen Bestellablauf zwischen Domain-Modulen, Ports, Transaktionen und Ergebnisobjekten. | REST oder Quarkus Resource sendet PlaceOrderCommand.; PlaceOrderService prueft Regeln und erzeugt Order. |
integration-outbox |
Integration / Reliability | Macht Event-Publishing robust, indem fachliche Aenderung und Event-Auslieferungsabsicht getrennt aber nachvollziehbar behandelt werden. | Domain erzeugt Event.; Application speichert Aggregate und OutboxRecord in einer Transaktion. |
adapters-rest-spring |
Inbound Adapter | Stellt HTTP-Endpunkte bereit und uebersetzt Web-Requests in Use-Case-Commands. | Client sendet HTTP Request.; OrderRestController validiert und baut Command. |
adapters-persistence-jpa |
Outbound Adapter | Implementiert Persistenz fuer Orders und Outbox, ohne JPA in die Domain eindringen zu lassen. | Application ruft OrderRepository.; JpaOrderRepositoryAdapter mappt Domain auf Entity. |
adapters-messaging-kafka |
Outbound Messaging Adapter | Publiziert Integrationsereignisse nach Kafka und schuetzt Konsumenten gegen doppelte Verarbeitung. | OutboxRelay liefert Record an Publisher.; KafkaEventPublisher sendet Nachricht. |
adapters-legacy-acl |
Legacy Integration | Schuetzt moderne Domain vor altem SOAP-/Legacy-Modell durch eine Anti-Corruption-Layer. | Application braucht Legacy-Daten.; LegacyOrderGateway ruft SOAP Client. |
app-spring-boot |
Runtime Composition | Startet die Spring-Boot-Variante und verdrahtet REST, Persistenz, Messaging, Security und Observability. | Spring Boot startet Application Context.; UseCaseConfiguration verbindet Ports mit Adaptern. |
app-quarkus |
Alternative Runtime | Zeigt dieselben Use Cases in einer Quarkus/Jakarta-Runtime, um Framework-Austauschbarkeit der Architektur zu demonstrieren. | Quarkus startet Resource.; InMemoryQuarkusWiring liefert Use-Case-Instanzen. |
architecture-tests |
Qualitaets-Gate | Prueft Architekturregeln automatisiert, damit Modulgrenzen und Pattern-Markierungen nicht nur Dokumentation bleiben. | Maven test/verify fuehrt Architecture Tests aus.; LayerRulesTest prueft Abhaengigkeitsrichtung. |
Lese-Reihenfolge
shared-kernelfuer Grundtypen.order-domainfuer fachlichen Kern.order-applicationfuer Use-Case-Ablauf.adapters-rest-springundadapters-persistence-jpafuer technische Umsetzung.integration-outboxundadapters-messaging-kafkafuer robuste Events.app-spring-bootoderapp-quarkusfuer Laufzeit.architecture-testsfuer automatische Regeln.
Entwurfsmuster im Ablauf
- Aggregate Root:
Orderschuetzt Konsistenz. - Command Pattern:
PlaceOrderCommandtransportiert den Wunsch, eine Bestellung anzulegen. - Application Service:
PlaceOrderServiceorchestriert, aber enthaelt keine Infrastrukturdetails. - Ports and Adapters: Use Cases kennen nur Ports; Adapter implementieren Technik.
- Transactional Outbox: Domain-Aenderung und Event-Auslieferung werden robust gekoppelt.
- Saga: Mehrschrittiger Zahlungs-/Fulfillment-Prozess wird explizit koordiniert.
- Anti-Corruption Layer: Legacy-Modelle bleiben ausserhalb der modernen Domain.
- Architecture Fitness Function: ArchUnit schuetzt Regeln im Build.