โมดูลแอปพลิเคชันและการตรวจสอบขอบเขต
กำหนดและบังคับใช้ขอบเขตโมดูลด้วยการตรวจสอบโครงสร้างและเอกสารของ Spring Modulith
โมดูลแอปพลิเคชันและการตรวจสอบขอบเขต เป็นบทเรียน Spring Boot 4 Complete Guide ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Spring Boot 4 Complete Guide และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Spring Boot 4 Complete Guide มีบทเรียนทั้งหมด 4 บทเรียน
บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ
Why Module Boundaries Matter
A Spring Boot monolith tends to rot: any class can @Autowired any other, and over time everything depends on everything. Spring Modulith brings discipline by treating top-level packages under your main application package as application modules.
- Each direct sub-package of the main package is one module.
- Code inside a module's root package and a special
apisub-package is public. - All other nested packages are internal and may not be referenced from other modules.
This lets you keep a single deployable while enforcing the boundaries you would get from microservices.
A Modular Package Layout
Consider an e-commerce app with the main class in com.shop. Each business concern becomes a direct sub-package. Spring Modulith infers the modules order, inventory, and notification from this structure alone — no XML, no annotations required.
Classes directly in com.shop.order are the module's public API; classes in com.shop.order.internal are hidden from other modules.
com.shop
├── ShopApplication.java
├── order
│ ├── OrderService.java // public API
│ └── internal
│ └── OrderRepository.java // internal
├── inventory
│ ├── InventoryService.java
│ └── internal
│ └── StockLevel.java
└── notification
└── NotificationService.javaAdding the Modulith Dependency
Spring Modulith ships as a BOM plus a set of starters. For boundary verification and documentation you need spring-modulith-starter-core on the test classpath (and usually the test starter).
- The BOM aligns all Modulith artifact versions with your Spring Boot version.
spring-modulith-starter-testbrings theApplicationModulesverification API into the test scope.
<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>Bootstrapping the Module Model
The entry point for everything is ApplicationModules.of(...). You pass it your main application class; Modulith scans the package structure and builds an in-memory model of every module and its allowed dependencies.
Calling verify() on that model fails fast if any module reaches into another module's internals or forms an illegal cyclic dependency.
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();
}
}What verify() Actually Checks
A single call to verify() enforces several structural rules at once:
- No internal access: a module may only depend on another module's public types (root package or
apipackage), never its internal sub-packages. - No cycles: module dependencies must form a directed acyclic graph; A→B→A fails the build.
- Declared dependencies only: if a module restricts its allowed dependencies with
@ApplicationModule(allowedDependencies = ...), any undeclared dependency is rejected.
Run it as a normal JUnit test so violations break CI before they ever ship.
Restricting Allowed Dependencies
By default a module may depend on any other module's public API. To tighten that, place a package-info.java in the module's root package and annotate it with @ApplicationModule.
Here the order module is allowed to use only inventory. If someone later wires NotificationService into the order module, verify() fails immediately.
@org.springframework.modulith.ApplicationModule(
allowedDependencies = { "inventory" }
)
package com.shop.order;
import org.springframework.modulith.ApplicationModule;Named Interfaces for Selective Exposure
Sometimes a module needs to expose a second public surface beyond its root package. A named interface marks an extra package as public and lets other modules target it explicitly.
Annotate the package with @NamedInterface("spi"); then a consumer can declare a dependency on order :: spi rather than the whole module.
// 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;Decoupling with Application Events
The cleanest way to keep modules independent is to avoid direct service calls altogether. Instead of injecting InventoryService into the order module, publish a Spring ApplicationEventPublisher event and let inventory listen.
This inverts the dependency: order no longer needs to know inventory exists, which keeps the verified dependency graph small and acyclic.
@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) {}Listening Across Modules
The inventory module consumes the event without any compile-time link back to order — it only depends on the published event type. Spring Modulith encourages @ApplicationModuleListener, which combines @Async, @Transactional(propagation = REQUIRES_NEW), and @TransactionalEventListener so the listener runs in its own transaction after the publisher commits.
@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) { /* ... */ }
}Generating Living Documentation
The same module model can render documentation. Documenter produces C4-style component diagrams (PlantUML) and an Asciidoctor module canvas describing each module's dependencies, exposed types, and events.
Because the diagrams are derived from real code during the test run, they never drift out of date — regenerating is just re-running the test.
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
}A Standalone Cycle Check
The boundary idea — reject dependency cycles — is simple enough to model in plain Java. This runnable program builds a tiny module graph and reports whether a cycle exists, mirroring what verify() does for real modules.
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);
}
}Quick Check
You annotate the order module with @ApplicationModule(allowedDependencies = { "inventory" }). A new commit injects NotificationService (from the notification module) into a bean inside order. What happens?
Recap
You learned how Spring Modulith turns a Spring Boot monolith into a set of verified modules:
- Modules are the direct sub-packages of your main application package; root and
api/named-interface packages are public, the rest internal. ApplicationModules.of(App.class).verify()enforces no internal access, no cycles, and declared-only dependencies as a JUnit test.@ApplicationModule(allowedDependencies = ...)tightens the graph;@NamedInterfaceexposes extra public surfaces selectively.- Prefer application events with
@ApplicationModuleListenerto decouple modules instead of direct service calls. Documentergenerates always-current PlantUML diagrams and module canvases from the same model.
เรียนรู้ Java ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 21
- บทเรียน
- 84
คำถามที่พบบ่อย
บทเรียน “โมดูลแอปพลิเคชันและการตรวจสอบขอบเขต” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “โมดูลแอปพลิเคชันและการตรวจสอบขอบเขต” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Spring Boot 4 Complete Guide ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Spring Boot 4 Complete Guide มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “โมดูลแอปพลิเคชันและการตรวจสอบขอบเขต”
กำหนดและบังคับใช้ขอบเขตโมดูลด้วยการตรวจสอบโครงสร้างและเอกสารของ Spring Modulith คุณปฏิบัติ Spring Boot 4 Complete Guide ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Spring Boot 4 Complete Guide หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Spring Boot 4 Complete Guide บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “โมดูลแอปพลิเคชันและการตรวจสอบขอบเขต” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Spring Boot 4 Complete Guide นี้ได้ไหม
ได้ บทเรียน Spring Boot 4 Complete Guide ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- โมดูลแอปพลิเคชันและการตรวจสอบขอบเขต
- เหตุการณ์ภายในแอปพลิเคชันและตัวรับฟัง
- การเผยแพร่เหตุการณ์เชิงธุรกรรมและเอาต์บ็อกซ์
- การทดสอบการผสานรวมโมดูลและสถานการณ์