Entwicklung & Code
Deep Dive Spring Modulith Teil 1: Fachliche Module im Fokus
Mit Spring Modulith ist es möglich, Spring-Boot-Anwendungen in fachliche Module aufzuteilen, die über klare Schnittstellen verfügen, per Events kommunizieren und sich isoliert testen lassen. Die Architektur der Module lässt sich automatisiert überwachen und dokumentieren. Diese Artikelserie stellt Spring Modulith, dessen Version 2.0 im November 2025 erschien, in zwei Teilen vor. Der erste Teil zeigt die Grundlagen der Modul- und Event-basierten Architektur. Teil 2 wirft unter anderem einen Blick auf die Testmöglichkeiten der Module.
Weiterlesen nach der Anzeige
Nils Hartmann ist freiberuflicher Softwareentwickler und Coach mit den Schwerpunkten Java im Backend und React im Frontend, wozu er auch Workshops und Trainings gibt.
Von klassischen Architekturen zum modularen Monolithen
Spring Boot ist eine typische Wahl bei der Entwicklung von Java-basierten Anwendungen. Um den damit entwickelten Code auch langfristig beherrschbar zu halten, werden oft Architekturmuster wie Schichten-, Hexagonal- oder Onion-Architektur angewendet. Diese Muster teilen die Anwendung aber oft nur in eher technische Bestandteile auf, um beispielsweise eine Entkopplung von eingesetzten Datenbanken oder anderen externen Systemen zu ermöglichen. In der klassischen Layer-Architektur greifen beispielsweise Controller auf Services und Services auf Repositorys zu, nicht aber Repositorys auf Services oder Controller.
In der Hexagonal-Architektur werden einzelne Komponenten wie UI- und Persistenzschicht sowie der fachliche Anwendungskern über „Ports“ und „Adapter“ voneinander separiert. Das soll die Domain-Logik von technischen Details, wie der Anbindung an eine konkrete Datenbank, trennen. Außerdem sollen diese Architekturstile ermöglichen, dass einzelne Teile der Anwendung isoliert testbar und jederzeit austauschbar sind, etwa beim Wechsel der Datenbank. Spring Modulith geht einen anderen Weg.
Modulith ist ein Kofferwort aus „modularer Monolith“. Die Anwendung wird als Monolith entwickelt, ist aber intern nach streng fachlichen Modulen (auch Slices genannt) aufgeteilt. Der potenziell komplexe fachliche Kern einer Anwendung soll so in beherrschbare kleinere Einheiten zerteilt werden, die in sich abgeschlossen mit klaren Schnittstellen versehen sind und alles enthalten, was zur Umsetzung der jeweiligen Fachlichkeit nötig ist. Änderungen an fachlichen Anforderungen haben somit im besten Fall nur Auswirkungen auf genau ein Modul. Die Module einer Anwendung können nach den oben genannten Architekturstilen implementiert werden, müssen es aber nicht. Ähnlich wie bei Microservices kann auch hier pro Modul – jedenfalls in einem gewissen Rahmen – die jeweils passende Architektur gewählt werden.
Dieser Artikel stellt Spring Modulith anhand der Beispielanwendung „Plantify“ vor. Plantify bietet die Möglichkeit, dass Kunden ihre Pflanzen pflegen lassen. Dazu registrieren sie zunächst ihre Pflanzen. Plantify legt für jede Pflanze Pflegeaufgaben an, die dann ein Dienstleister abarbeitet. Für die durchgeführten Aufgaben schickt Plantify regelmäßig eine Rechnung an die Kunden. Die Kommunikation mit der Anwendung findet über eine HTTP-API statt. Der Sourcecode der Beispielanwendung ist auf GitHub zu finden.
(Bild: buraratn/123rf)
Bei der Online-Konferenz betterCode() Spring stehen am Vormittag sichere Anwendungen mit Spring Security und die Integration von KI mit Spring AI im Fokus. Der Nachmittag widmet sich Spring Boot und zeigt die Neuerungen von Version 4 im Zusammenspiel mit Java 25, Tipps zur Integration von Containern sowie in der Praxis bewährte Spring Boot Hacks.
Application Module mit Spring Modulith
Weiterlesen nach der Anzeige
Um Spring Modulith in der eigenen Anwendung zu verwenden, fügt man dessen Starter-Paket in der eigenen Maven- oder Gradle-Konfiguration hinzu. Das reicht schon aus, damit Spring Modulith die Packages in der Anwendung unterschiedlich klassifiziert und behandelt. Alle Packages, die sich direkt unterhalb des Root-Packages befinden (das ist das Package mit der Klasse SpringBootApplication), betrachtet Spring Modulith als „Application Module“. Technisch bleiben es zwar weiterhin normale Java-Packages, aber Spring Modulith legt der Verwendung dieser Application Modules einige Regeln auf. Zum Beispiel darf es zwischen den Application Modules nicht zu direkten oder indirekten zirkulären Abhängigkeiten kommen.
In der Plantify-Anwendung gibt es etwa direkt unterhalb des Root-Packages nh.plantify drei Packages plant, care und billing. Diese drei Packages interpretiert Spring Modulith als Application Modules. In der Anwendung greift das Application Module plant auf care zu (z. B. um die Pflegeaufgaben einer neuen Pflanze anzulegen). Außerdem greift care auf billing zu, um die Einrichtungsgebühr zu berechnen. Diese Abhängigkeiten sind erlaubt, denn sie zeigen nur in eine Richtung. Würde die Anwendung aber erweitert, sodass eine Klasse aus dem billing-Modul auf das plant-Modul zugreifen würde, ergäbe sich daraus eine zirkuläre Abhängigkeit. Diese erkennt und verbietet Spring Modulith, da diese Art der Abhängigkeit oft zu Problemen in der weiteren Entwicklung führt.
Das Einhalten der Regeln lässt sich mit Spring Modulith auf mehreren Wegen kontrollieren und sicherstellen. Zum einen kann Spring Modulith sie bei jedem Start der Anwendung prüfen und bei Verstößen Fehler ausgeben. Das kostet allerdings Zeit und findet im Entwicklungsprozess erst spät statt. Alternativ lässt sich ein JUnit-Test implementieren, der die Regel überprüft. Ein entsprechendes Beispiel findet sich in Listing 1.
class PlantifyModuleTest {
static ApplicationModules modules = ApplicationModules.of(PlantifyApplication.class);
@Test
void verifyModules() {
modules.verify();
}
@Test
void writeDocumentationSnippets() {
new Documenter(modules)
.writeModulesAsPlantUml();
}
}
Listing 1: Die Modulstruktur wird im JUnit-Test überprüft und visualisiert
Die Klasse ApplicationModules repräsentiert die erkannten Application Modules im übergebenen Paket. Die verify-Methode stellt innerhalb des Tests sicher, dass alle Regeln eingehalten werden. Bei Verstößen schlägt der Test mit einer ausführlichen Fehlermeldung fehl (siehe Abbildung 1).
Der Unit-Test deckt Regelverstöße (zirkuläre Abhängigkeiten) auf (Abb. 1).
Diese Tests werden mit allen anderen „normalen“ Tests einer Anwendung kontinuierlich ausgeführt, sodass Probleme schnell auffallen. Die ApplicationModules-Klasse kann außerdem die Modulstruktur als C4-Diagramm ausgeben, aus der auch die Art der Verwendungen eines Moduls hervorgeht (dazu später mehr). Die Visualisierung der Plantify-Module zu diesem Zeitpunkt ist in Abbildung 2 zu sehen.
Die initiale Modulstruktur, visualisiert von Spring Modulith (Abb. 2).
Interne Module
Neben dem Verbot zirkulärer Abhängigkeiten wendet Spring Modulith noch eine andere Regel von Haus aus an: Alle Unterpakete in einem Application Module gelten als „intern“. Klassen daraus dürfen zwar innerhalb des Application Module beliebig verwendet werden, allerdings ist der Zugriff darauf aus anderen Modulen verboten. Mit anderen Worten: Das Root-Package eines Application Module ist dessen öffentliche API. Nur die Public-Klassen stehen anderen Modulen zur Verfügung. Der oben gezeigte Test Case prüft auch die Einhaltung dieser Regel. Zusätzlich können IDEs wie IntelliJ, Eclipse oder VS Code mit den entsprechenden Plug-ins diese Regeln prüfen und direkt im Quellcode anzeigen. Abbildung 3 zeigt ein entsprechendes Beispiel in Eclipse mit den Spring Tools.
Die Verwendung interner Klassen zeigt Eclipse mit den Spring Tools als Fehler an (Abb. 3).
Hier wird aus dem CareTaskService, der sich im Modul care befindet, auf die Klasse InvoiceGenerator zugegriffen, die sich in dem Unterpaket invoice des billing-Moduls befindet. Ohne Spring Modulith wäre der Zugriff aus Java-Sicht regelkonform, da die Klasse InvoiceGenerator public ist, und somit von allen Packages aus verwendet werden darf.