Spring Modulith Docs - Production-ready Features
Spring Modulith는 런타임에서도 모듈 구조를 활용해요. 모듈 의존 순서대로 초기화하고, 모듈별 Flyway 마이그레이션을 돌리고, 모듈 구조를 Actuator로 노출하고, 모듈 간 호출을 트레이싱해줘요. 이 노트는 그 런타임/프로덕션 기능들을 정리해요.
Actuator·트레이스에 찍히는 이벤트 흐름은 F. Producing Documentation for Application Modules에서 다룬 “의존성 방향 vs 이벤트 흐름”이 런타임에서 그대로 나타난 결과예요.
1. Runtime Support — 모듈 의존 순서를 런타임에 적용해요
dependencies {
runtimeOnly 'org.springframework.modulith:spring-modulith-runtime'
}
이 JAR를 추가하면 다음이 자동으로 등록돼요.
ApplicationModulesRuntime— 런타임에서ApplicationModules에 접근SpringBootApplicationRuntime— 메인 애플리케이션 클래스 감지RuntimeApplicationModuleVerifier— 시작 시 모듈 구조 검증 (옵트인)ApplicationModuleInitializer실행기 — 모듈 의존 순서대로 초기화 코드 실행
2. ApplicationModuleInitializer — 위상 정렬로 초기화 순서를 보장해요
모듈 A에 의존하는 모듈 B가 있으면, A의 초기화 코드가 먼저 실행돼야 해요. @Order를 직접 지정할 필요 없이 모듈 의존 그래프를 따라 **위상 정렬(topological sort)**로 실행돼요.
// order 모듈 — 의존 없음 → 먼저 실행
@Component
class OrderModuleInitializer implements ApplicationModuleInitializer {
@Override
public void initialize() {
// 주문 테이블 기본 데이터 세팅
log.info("Order module initialized");
}
}
// inventory 모듈 — order에 의존 → order 이후 실행
@Component
class InventoryModuleInitializer implements ApplicationModuleInitializer {
@Override
public void initialize() {
// 재고 초기 데이터 로딩
// order 초기화가 완료된 상태가 보장됨
log.info("Inventory module initialized");
}
}
// notification 모듈 — inventory에 의존 → inventory 이후 실행
@Component
class NotificationModuleInitializer implements ApplicationModuleInitializer {
@Override
public void initialize() {
// 알림 채널 설정
log.info("Notification module initialized");
}
}
실행 순서는 이래요.
order → inventory → notification
시작 시 모듈 구조 검증을 켜려면 다음 속성을 써요.
spring.modulith.runtime.verification-enabled=true
순환 의존 등 구조 위반이 있으면 애플리케이션 시작이 중단돼요.
NOTE · 빌드타임 검증과의 관계 여기서의 런타임 검증은 B. Verifying Application Module Structure에서 다룬 테스트 기반 검증을 시작 시점에도 한 번 더 걸어주는 거예요. 테스트를 건너뛴 배포에서도 구조 위반을 잡을 수 있어요.
3. 모듈별 Flyway 마이그레이션
각 모듈이 자기 테이블만 관리하는 마이그레이션 구조예요. 실행 순서도 모듈 의존 그래프를 따라요.
spring.modulith.runtime.flyway-enabled=true
디렉토리 구조는 이래요.
db/migration/
├── __root/ # 공통 마이그레이션 (기본 tracking table 사용)
│ └── V1__create_schema.sql
├── order/ # order 모듈 전용 (flyway_schema_history_order)
│ ├── V1__create_orders.sql
│ └── V2__add_status.sql
└── inventory/ # inventory 모듈 전용 (flyway_schema_history_inventory)
└── V1__create_inventory.sql
핵심 동작을 정리하면 이래요.
db/migration/__root— 기존db/migration역할. 기본flyway_schema_history테이블 사용db/migration/{moduleIdentifier}— 모듈별 별도 tracking table (flyway_schema_history_{moduleIdentifier})- 버전 번호가 모듈 스코프 — 각 모듈이 독립적으로
V1,V2를 쓸 수 있음 - 통합 테스트 시 테스트에 포함된 모듈의 마이그레이션만 실행
MigrationFilter빈을 등록하면 마이그레이션 실행 대상을 추가 필터링 가능
4. Actuator — 모듈 구조를 JSON으로 노출해요
dependencies {
runtimeOnly 'org.springframework.modulith:spring-modulith-actuator'
runtimeOnly 'org.springframework.boot:spring-boot-starter-actuator'
}
엔드포인트는 이래요.
GET /actuator/modulith
응답 예시:
{
"order": {
"basePackage": "com.acme.commerce.order",
"displayName": "Order",
"dependencies": []
},
"inventory": {
"basePackage": "com.acme.commerce.inventory",
"displayName": "Inventory",
"dependencies": [
{
"target": "order",
"types": ["EVENT_LISTENER", "USES_COMPONENT"]
}
]
}
}
의존 유형(types)은 이래요.
| 타입 | 의미 | 코드 예시 |
|---|---|---|
DEFAULT |
단순 타입 참조 | import com.acme.order.OrderId; |
USES_COMPONENT |
Spring 빈 주입 | @Autowired OrderService orderService; |
EVENT_LISTENER |
이벤트 구독 | @EventListener public void on(OrderCompleted e) |
4-1. 이벤트 흐름 vs 의존성 방향 (복습)
Actuator 응답의 inventory → order (EVENT_LISTENER)를 코드로 보면 이래요.
// inventory 모듈에 있는 코드
package com.acme.commerce.inventory;
import com.acme.commerce.order.OrderCompleted; // ← order 타입을 import = 의존
@Component
public class InventoryOrderEventListener {
@EventListener
public void on(OrderCompleted event) { ... }
}
의존성 방향 (JSON): inventory ──────────────→ order (inventory가 order를 안다)
이벤트 흐름 (런타임): order ──(OrderCompleted)──→ inventory (이벤트는 반대로 흐른다)
듣는 쪽이 보내는 쪽을 알아요. 보내는 쪽은 듣는 쪽을 몰라요. (F. Producing Documentation for Application Modules에서 다룬 원칙이 런타임 JSON에도 그대로 나타나요.)
5. Observability — 모듈 간 호출을 트레이싱해요
dependencies {
runtimeOnly 'org.springframework.modulith:spring-modulith-observability'
}
Actuator + Observability를 한번에 넣으려면 스타터를 써요.
dependencies {
runtimeOnly 'org.springframework.modulith:spring-modulith-starter-insight'
}
5-1. 동작 원리
모듈의 API에 해당하는 Spring 빈을 AOP로 감싸서, 호출이 일어날 때마다 Micrometer span을 자동 생성해요. Zipkin/Wavefront 같은 도구에서 모듈 간 호출 트레이스를 시각적으로 볼 수 있어요.
트레이스 예시:
payment.complete()
└─ order.changeState(COMPLETED)
└─ [async] engine.process()
└─ order.changeState(PROCESSED)
시간 경과 이벤트 패턴과 결합하면 이렇게 잡혀요.
moments.DayHasPassed
└─ invoiceDebtCollection.on(DayHasPassed)
└─ [event] InvoiceBecameOverdue
├─ accountSuspension.on(InvoiceBecameOverdue)
└─ revenueAdjustment.on(InvoiceBecameOverdue)
프로덕션에서 이벤트 체이닝의 전체 흐름(E. Working with Passage of Time Events)이 트레이스로 잡혀요.
5-2. 추가 설정
Zipkin, Wavefront 등 모니터링 도구와 연결하려면 별도 의존성이 필요해요. Spring Boot의 Micrometer Tracing 문서를 참고해요.
// Zipkin + Brave 예시
dependencies {
implementation 'io.micrometer:micrometer-tracing-bridge-brave'
implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
}
# application.properties
management.tracing.sampling.probability=1.0
management.zipkin.tracing.endpoint=http://localhost:9411/api/v2/spans
최종 정리
spring-modulith-runtime은 모듈 의존 그래프를 런타임에도 활용해 초기화 순서(위상 정렬)와 모듈별 Flyway 마이그레이션을 보장하고, 시작 시 구조 검증을 옵트인으로 걸 수 있어요.spring-modulith-actuator는 모듈 구조를/actuator/modulithJSON으로 노출하고(의존성 방향은 이벤트 흐름과 반대),spring-modulith-observability는 모듈 간 호출과 이벤트 체이닝을 Micrometer span으로 트레이싱해요. 이 JAR들은 전부runtimeOnly라, 프로덕션 코드가 라이브러리에 컴파일 타임 의존을 갖지 않아요.
전체 의존성 정리
| JAR | 용도 | 스코프 |
|---|---|---|
spring-modulith-runtime |
모듈 초기화 순서, Flyway 연동 | runtimeOnly |
spring-modulith-actuator |
/actuator/modulith 엔드포인트 |
runtimeOnly |
spring-modulith-observability |
모듈 간 호출 트레이싱 | runtimeOnly |
spring-modulith-starter-insight |
actuator + observability 통합 | runtimeOnly |
관련 노트
- F. Producing Documentation for Application Modules — Canvas·C4, 의존성 방향 vs 이벤트 흐름
- E. Working with Passage of Time Events —
DayHasPassed, 이벤트 체이닝 트레이스 - B. Verifying Application Module Structure — 모듈 구조 검증 (빌드타임)
- @EventListener — 이벤트 구독의 기본
- 이전: F. Producing Documentation for Application Modules
이 글에 대해 이야기해요
질문이나 다른 관점을 남겨 주세요.