Applikationsmoduler og verifikation af grænser
Definér og håndhæv modulgrænser med Spring Moduliths strukturverifikation og dokumentation.
Applikationsmoduler og verifikation af grænser er en gratis Komplet guide til Spring Boot 4-lektion på CoddyKit. Dette er lektion 1 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Komplet guide til Spring Boot 4, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Komplet guide til Spring Boot 4-kurset indeholder 4 lektioner i alt.
Hvorfor modulgrænser er vigtige
En Spring Boot-monolit har en tendens til at forfalde: enhver klasse kan @Autowired enhver anden, og med tiden afhænger alt af alt. Spring Modulith skaber disciplin ved at behandle pakker på øverste niveau under din hovedapplikationspakke som applikationsmoduler.
- Hver direkte underpakke til hovedpakken er ét modul.
- Kode i et moduls rodpakke og i en særlig
api-underpakke er offentlig. - Alle andre indlejrede pakker er interne og må ikke refereres fra andre moduler.
Det lader dig beholde én implementerbar applikation og samtidig håndhæve de grænser, du ellers ville få med mikrotjenester.
En modulær pakkestruktur
Forestil dig en e-handelsapp med hovedklassen i com.shop. Hvert forretningsområde bliver en direkte underpakke. Spring Modulith udleder modulerne order, inventory og notification alene ud fra denne struktur — ingen XML eller annoteringer er nødvendige.
Klasser direkte i com.shop.order udgør modulets offentlige API, mens klasser i com.shop.order.internal er skjult for andre moduler.
com.shop
├── ShopApplication.java
├── order
│ ├── OrderService.java // public API
│ └── internal
│ └── OrderRepository.java // internal
├── inventory
│ ├── InventoryService.java
│ └── internal
│ └── StockLevel.java
└── notification
└── NotificationService.javaTilføjelse af Modulith-afhængigheden
Spring Modulith leveres som en BOM plus en række starters. For at kontrollere og dokumentere grænser skal du have spring-modulith-starter-core på testklassestien (og normalt også test-starteren).
- BOM'en afstemmer versionerne af alle Modulith-artefakter med din Spring Boot-version.
spring-modulith-starter-testtilføjer API'et til kontrol afApplicationModulesi testomfanget.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-bom</artifactId>
<version>1.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-starter-test</artifactId>
<scope>test</scope>
</dependency>Initialisering af modulmodellen
Indgangspunktet for det hele er ApplicationModules.of(...). Du giver den din hovedapplikationsklasse, hvorefter Modulith scanner pakkestrukturen og opbygger en model i hukommelsen over hvert modul og dets tilladte afhængigheder.
Når du kalder verify() på modellen, fejler den straks, hvis et modul trænger ind i et andet moduls interne dele eller danner en ulovlig cyklisk afhængighed.
import org.springframework.modulith.core.ApplicationModules;
class ModularityTests {
static final ApplicationModules modules =
ApplicationModules.of(ShopApplication.class);
@org.junit.jupiter.api.Test
void verifiesModularStructure() {
modules.verify();
}
}Hvad verify() faktisk kontrollerer
Ét kald til verify() håndhæver flere strukturelle regler på én gang:
- Ingen adgang til interne dele: Et modul må kun afhænge af et andet moduls offentlige typer (rodpakken eller
api-pakken), aldrig dets interne underpakker. - Ingen cyklusser: Modulafhængigheder skal danne en rettet acyklisk graf; A→B→A får bygget til at fejle.
- Kun erklærede afhængigheder: Hvis et modul begrænser sine tilladte afhængigheder med
@ApplicationModule(allowedDependencies = ...), afvises enhver afhængighed, der ikke er erklæret.
Kør det som en almindelig JUnit-test, så overtrædelser stopper CI, før de nogensinde leveres.
Begrænsning af tilladte afhængigheder
Som standard må et modul afhænge af ethvert andet moduls offentlige API. Hvis du vil stramme dette, skal du placere en package-info.java i modulets rodpakke og annotere den med @ApplicationModule.
Her må order-modulet kun bruge inventory. Hvis nogen senere kobler NotificationService ind i order-modulet, fejler verify() med det samme.
@org.springframework.modulith.ApplicationModule(
allowedDependencies = { "inventory" }
)
package com.shop.order;
import org.springframework.modulith.ApplicationModule;Navngivne grænseflader til selektiv eksponering
Nogle gange skal et modul eksponere en ekstra offentlig overflade ud over sin rodpakke. En navngiven grænseflade markerer en ekstra pakke som offentlig og lader andre moduler målrette den eksplicit.
Annotér pakken med @NamedInterface("spi"); derefter kan en forbruger erklære en afhængighed af order :: spi i stedet for af hele modulet.
// com/shop/order/spi/package-info.java
@org.springframework.modulith.NamedInterface("spi")
package com.shop.order.spi;
import org.springframework.modulith.NamedInterface;
// Consumer module restricts itself to that named interface
@org.springframework.modulith.ApplicationModule(
allowedDependencies = { "order :: spi" }
)
package com.shop.billing;Afkobling med applikationshændelser
Den reneste måde at holde moduler uafhængige på er helt at undgå direkte kald til tjenester. I stedet for at injicere InventoryService i order-modulet skal du publicere en Spring-ApplicationEventPublisher-hændelse og lade inventory lytte.
Det vender afhængigheden om: order behøver ikke længere vide, at inventory findes, hvilket holder den kontrollerede afhængighedsgraf lille og acyklisk.
@org.springframework.stereotype.Service
class OrderService {
private final org.springframework.context.ApplicationEventPublisher events;
OrderService(org.springframework.context.ApplicationEventPublisher events) {
this.events = events;
}
void placeOrder(String sku, int qty) {
// ... persist order ...
events.publishEvent(new OrderPlaced(sku, qty));
}
}
record OrderPlaced(String sku, int qty) {}Lytning på tværs af moduler
Inventory-modulet bruger hændelsen uden nogen kompileringsafhængighed tilbage til order — det afhænger kun af den publicerede hændelsestype. Spring Modulith anbefaler @ApplicationModuleListener, som kombinerer @Async, @Transactional(propagation = REQUIRES_NEW) og @TransactionalEventListener, så lytteren kører i sin egen transaktion efter udgiverens commit.
@org.springframework.stereotype.Component
class InventoryEventHandler {
@org.springframework.modulith.events.ApplicationModuleListener
void on(OrderPlaced event) {
// runs async, in a fresh transaction, after commit
decrementStock(event.sku(), event.qty());
}
private void decrementStock(String sku, int qty) { /* ... */ }
}Generering af levende dokumentation
Den samme modulmodel kan gengive dokumentation. Documenter producerer komponentdiagrammer i C4-stil (PlantUML) og et Asciidoctor-modullærred, der beskriver hvert moduls afhængigheder, eksponerede typer og hændelser.
Fordi diagrammerne udledes fra den faktiske kode under testkørslen, bliver de aldrig forældede — at generere dem igen kræver blot, at du kører testen igen.
import org.springframework.modulith.docs.Documenter;
@org.junit.jupiter.api.Test
void writeDocumentation() {
var modules = ApplicationModules.of(ShopApplication.class);
new Documenter(modules)
.writeModulesAsPlantUml() // overview diagram
.writeIndividualModulesAsPlantUml()
.writeModuleCanvases(); // target/modulith-docs
}En selvstændig cykluskontrol
Idéen bag grænserne — at afvise afhængighedscyklusser — er enkel nok til at modellere i almindelig Java. Dette kørbare program opbygger en lille modulgraf og rapporterer, om der findes en cyklus, som en efterligning af det, verify() gør for virkelige moduler.
import java.util.*;
public class CycleCheck {
static Map<String, List<String>> graph = new HashMap<>();
static void dependsOn(String a, String b) {
graph.computeIfAbsent(a, k -> new ArrayList<>()).add(b);
}
static boolean hasCycle(String node, Set<String> stack, Set<String> seen) {
if (stack.contains(node)) return true;
if (seen.contains(node)) return false;
seen.add(node);
stack.add(node);
for (String next : graph.getOrDefault(node, List.of()))
if (hasCycle(next, stack, seen)) return true;
stack.remove(node);
return false;
}
public static void main(String[] args) {
dependsOn("order", "inventory");
dependsOn("inventory", "order"); // illegal cycle
boolean cyclic = false;
for (String m : graph.keySet())
cyclic |= hasCycle(m, new HashSet<>(), new HashSet<>());
System.out.println("Cycle detected: " + cyclic);
}
}Hurtigt tjek
Du annoterer order-modulet med @ApplicationModule(allowedDependencies = { "inventory" }). En ny commit injicerer NotificationService (fra notification-modulet) i en bean i order. Hvad sker der?
Opsummering
Du har lært, hvordan Spring Modulith omdanner en Spring Boot-monolit til en række kontrollerede moduler:
- Moduler er de direkte underpakker til din hovedapplikationspakke; rodpakker og
api/navngivne grænsefladepakker er offentlige, mens resten er interne. ApplicationModules.of(App.class).verify()håndhæver ingen adgang til interne dele, ingen cyklusser og kun erklærede afhængigheder som en JUnit-test.@ApplicationModule(allowedDependencies = ...)strammer grafen;@NamedInterfaceeksponerer ekstra offentlige overflader selektivt.- Foretræk applikationshændelser med
@ApplicationModuleListenerfor at afkoble moduler i stedet for direkte kald til tjenester. Documentergenererer PlantUML-diagrammer og modullærreder, der altid er aktuelle, ud fra den samme model.
Lær Java med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 21
- Lektioner
- 84
Ofte stillede spørgsmål
Er lektionen “Applikationsmoduler og verifikation af grænser” gratis?
Ja — alle 3 lektioner i læringssporet Komplet guide til Spring Boot 4, inklusive “Applikationsmoduler og verifikation af grænser”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Komplet guide til Spring Boot 4-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Applikationsmoduler og verifikation af grænser”?
Definér og håndhæv modulgrænser med Spring Moduliths strukturverifikation og dokumentation. Du øver dig i Komplet guide til Spring Boot 4 med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Komplet guide til Spring Boot 4?
Der kræves ingen tidligere erfaring. Komplet guide til Spring Boot 4 på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 1 af 4.
Hvor lang tid tager lektionen “Applikationsmoduler og verifikation af grænser”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Komplet guide til Spring Boot 4-lektion?
Ja. Alle Komplet guide til Spring Boot 4-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Applikationsmoduler og verifikation af grænser
- Interne applikationshændelser og listeners
- Transaktionel publicering af hændelser og outbox
- Integrationstest af moduler og scenarier