2025. 12. 23. 21:07ㆍㄱㅐㅂㅏㄹ/Spring
안녕하세요!
크리스마스가 이틀 앞으로 다가왔네요. 2025년 한 해를 정리하며 개발 트렌드도 한번 되짚어보는 시간을 가져볼까 해요.
혹시 여러분의 팀은 "우리는 MSA(Microservices Architecture)를 한다"고 하지만, 실제로는 거대한 '분산된 모놀리스(Distributed Monolith)' 때문에 고통받고 있지 않으신가요? 서비스는 쪼개놨는데 배포는 같이 해야 하고, 트랜잭션 관리는 복잡해지고...
오늘은 그런 고민을 가진 분들에게 아주 매력적인 대안, Spring Modulith에 대해 이야기해보려 합니다.
(저도 요즘 살짝 해보고 있습니다.)

🧩 Spring Modulith가 뭔가요?
간단히 말해, Spring Modulith는 '모놀리스(Monolith)를 모듈(Module) 단위로 잘 구조화해서 개발하도록 도와주는 스프링 프레임워크의 확장 프로젝트'입니다.
과거의 모놀리스는 시간이 지나면 코드 간의 의존성이 스파게티처럼 꼬여서 유지보수가 지옥 같았죠. 그렇다고 바로 MSA로 가기엔 인프라 복잡도라는 비용이 너무 큽니다.
Spring Modulith는 그 중간 지점을 제시해요. "일단 하나의 배포 단위(Monolith)로 만들되, 내부는 MSA처럼 명확한 경계를 가진 모듈로 나누자"는 것이죠. 이것을 'Modular Monolith'라고 부릅니다.
✨ 주요 특징과 실제 코드
그렇다면 Spring Modulith는 어떻게 우리의 코드를 우아하게 만들어줄까요? 핵심 기능과 실제 Java 코드를 살펴보죠.
1. 구조 검증 (Structure Verification) 가장 강력한 기능이에요. 코드를 작성하고 테스트를 돌릴 때, 모듈 간의 참조 규칙을 위반했는지 자동으로 검사해 줍니다. 예를 들어, order 패키지(모듈)가 inventory 패키지의 내부 구현 클래스를 직접 가져다 쓰면 테스트가 실패하게 만들 수 있어요.
package com.example.ecommerce;
import org.junit.jupiter.api.Test;
import org.springframework.modulith.core.ApplicationModules;
import org.springframework.modulith.docs.Documenter;
class ApplicationTests {
@Test
void verifyModularity() {
// 전체 애플리케이션의 모듈 구조를 분석합니다.
ApplicationModules modules = ApplicationModules.of(Application.class);
// 정의된 모듈 간 경계 규칙을 위반했는지 검증합니다.
// 위반 시 예외가 발생하며 테스트가 실패합니다.
modules.verify();
}
@Test
void createDocumentation() {
// (덤) 자동으로 아키텍처 문서(C4 모델 등)도 만들어줍니다!
new Documenter(ApplicationModules.of(Application.class))
.writeDocumentation();
}
}
이렇게 테스트 코드 하나만 추가해도 스파게티 코드가 되는 걸 막을 수 있다니, 정말 든든하지 않나요?
2. 이벤트 기반 연동 지원 모듈 간의 결합도를 낮추는 가장 좋은 방법은 이벤트를 사용하는 것입니다. Spring Modulith는 스프링의 애플리케이션 이벤트를 활용해서 이를 아주 쉽게 구현하도록 도와줍니다.
[주문 모듈 - 이벤트 발행] 주문이 완료되면 다른 모듈에게 알리기 위해 이벤트를 발행합니다.
package com.example.ecommerce.order;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orders;
private final ApplicationEventPublisher events; // 스프링 기본 이벤트 퍼블리셔
@Transactional
public void completeOrder(Long orderId) {
Order order = orders.findById(orderId).orElseThrow();
order.complete();
orders.save(order);
// 주문 완료 이벤트 발행!
events.publishEvent(new OrderCompletedEvent(orderId));
}
}
[재고 모듈 - 이벤트 구독] 재고 모듈은 이벤트를 받아서 재고를 차감합니다. @ApplicationModuleListener를 사용하면 비동기로 처리되어 주문 트랜잭션과 분리할 수 있습니다.
package com.example.ecommerce.inventory;
import com.example.ecommerce.order.OrderCompletedEvent;
import lombok.RequiredArgsConstructor;
import org.springframework.modulith.events.ApplicationModuleListener;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
class InventoryService {
private final InventoryRepository inventories;
// @EventListener + @Async 와 유사하지만, 트랜잭션 아웃박스 패턴 등을 지원해 더 강력합니다.
@ApplicationModuleListener
void on(OrderCompletedEvent event) {
// 주문 완료 이벤트를 받아 재고 차감 로직 수행
System.out.println("재고 차감 처리: 주문 ID = " + event.orderId());
// inventories.decreaseStock(event.orderId()); ...
}
}
심지어 트랜잭션 아웃박스 패턴 같은 복잡한 구현도 어노테이션 몇 개로 지원해주니, 개발자는 비즈니스 로직에만 집중할 수 있습니다.
🤔 그래서, 누구에게 필요한가요?
MSA를 도입하기엔 팀 규모나 인프라 리소스가 부족한 스타트업
레거시 모놀리스를 리팩토링하고 싶지만, 어디서부터 손대야 할지 막막한 팀
비즈니스 도메인이 아직 명확하지 않아 섣불리 서비스를 분리하기 어려운 초기 프로젝트
2025년의 끝자락, 무작정 유행을 쫓기보다 우리 팀의 상황에 맞는 아키텍처가 무엇인지 고민해보는 건 어떨까요? Spring Modulith와 위 코드들이 그 고민의 훌륭한 시작점이 될 수 있을 거예요.
모두 따뜻한 연말 보내시길 바라요!
'ㄱㅐㅂㅏㄹ > Spring' 카테고리의 다른 글
| spring boot 와 Opentelemetry (0) | 2026.01.12 |
|---|---|
| [Java/Spring] 인터페이스/추상클래스 JSON 변환이 안될 때? Jackson 다형성(Polymorphism) 완벽 해결법 (0) | 2026.01.04 |
| @DataJpaTest 할 때의 주의 사항 (0) | 2021.02.02 |
| Spring REST 에서의 Global Exception (0) | 2021.01.11 |
| Spring Cloud OpenFeign, 그리고 SSL (2) | 2020.12.29 |