이 프로젝트는 어디까지나 Redis의 실전 활용 방식을 실험하기 위한 목적에서 출발했다.
분산 락을 적용해 중복 좌석 예약을 막고, 3단 캐시 구조로 응답 속도를 개선하며, 요청 제한(RateLimit)을 통해 비정상 트래픽을 제어하는 흐름을 만들고, 그 결과를 테스트해보는 것이 핵심 목표였다. 그런데 기능이 하나씩 늘어나고 도메인이 쌓이기 시작하자, 단일 프로젝트 구조로는 복잡도가 빠르게 증가했다. 자연스럽게 “이걸 MSA로 쪼개야 하나?”라는 고민이 들기 시작했다.
하지만 실제로 도메인 기반으로 완전히 나누려면, 생각보다 고려할 게 많았다. 모듈 간 API 통신, 각자 독립된 데이터베이스 구성, 인증 및 인가 흐름 정리, Kafka나 Feign 같은 메시징 혹은 HTTP 연동 설계까지 모든 요소가 한꺼번에 따라오게 된다. 당장 이 프로젝트에서 Redis가 흐름에 얼마나 유연하게 녹아드는지를 확인하려던 목적과는 점점 멀어지는 구조였다. 그래서 나는 서비스 분리보다는 계층 분리를 중심으로 한 멀티 모듈 구조를 선택했고, Redis 실험에 집중할 수 있도록 가볍고 명확하게 구조를 정리했다.
보통 멀티 모듈 구조는 두 가지 방향 중 하나를 선택하게 된다. 하나는 도메인별 모듈 분리 방식, 즉 service-movie
, service-reservation
, service-membership
처럼 도메인을 경계로 나누는 방식이다. 이 구조는 장기적으로 MSA로 확장하기에 유리하고, 팀 단위 분업에도 적합하다. 하지만 단점은 분명하다. 모듈 간 데이터베이스가 분리되어야 하고, 서로 API나 메시지로 통신해야 하며, 트랜잭션 경계나 인증 처리 등 추가적인 인프라 설계가 필요하다. 실험용 프로젝트에서 감당하기에는 과하고, Redis 실험이라는 본질에서 벗어나게 된다.
그래서 나는 도메인 경계를 쪼개는 대신, 레이어 단위로 모듈을 나누는 방식을 선택했다. 즉, 컨트롤러는 컨트롤러끼리, 서비스는 서비스끼리, 도메인은 도메인끼리 묶는 방식이다. 이런 계층 중심 구조는 Redis 캐시, 락, RateLimit 같은 기술 정책을 끼워넣고 실험하기에 훨씬 유리하다. 하나의 도메인 흐름을 유지한 채, 기술 요소만 독립적으로 붙였다 떼기 편하기 때문이다.
platform/
├── bootstrap/ # 실행 진입점 (@SpringBootApplication)
├── adapter/ # in/out 어댑터 모듈
│ ├── in/ # Controller, AOP, 요청 처리
│ └── out/ # DB, Redis, Kafka 등 외부 시스템 연동
├── application/ # 유스케이스, 서비스, Command, DTO
├── domain/ # 도메인 모델, Entity, Enum, 로직
├── infrastructure/ # Redis, Kafka, JPA 설정 등 환경 구성
├── common/ # 공통 DTO, 예외, 유틸
└── docs/ # 설계 문서, ERD, 테스트 시나리오
이 구조는 각 계층이 하나의 독립된 모듈로 분리되어 있어, 의존성과 책임이 명확하다.
예를 들어, adapter
모듈은 외부와의 경계를 담당하므로 Controller나 RedisAdapter, KafkaConsumer 같은 구현체가 들어가며,
application
은 순수 비즈니스 흐름만 담당하여 포트와 유스케이스만 포함된다.
이 구조 덕분에 Redis 락이나 캐시는 domain이나 application을 건드리지 않고 adapter 레이어만 수정해서 추가할 수 있다.
bootstrap
platform/bootstrap
@SpringBootApplication
이 정의된다. 모든 의존 모듈을 불러오는 역할을 하며, 테스트나 로컬 실행 시 이 모듈만 실행하면 된다.adapter
platform/adapter
adapter/in
, adapter/out
in
: 컨트롤러, AOP 기반 캐시, RateLimit, 분산락 어노테이션 처리 등 out
: Redis, DB, Kafka 연동 어댑터application
platform/application
UseCase
, Service
, Command
, DTO
, Port
가 모두 이곳에 있다.domain
platform/domain
Entity
, Value Object
, Enum
등이 포함된다.infrastructure
platform/infrastructure
JpaConfig
, RedisConfig
, KafkaConfig
, SwaggerConfig
등이 이 모듈에 포함된다.@Configuration
클래스들이 이곳에 존재하며, 설정 변경 시 다른 계층에 영향을 주지 않는다.common
platform/common
docs
platform/docs
멀티 모듈 프로젝트에서는 settings.gradle.kts
에 각 모듈을 등록하고, 각 build.gradle.kts
에서 필요한 의존성만 선언해야 한다.
각 모듈은 아래와 같은 의존 관계를 가진다 (→는 의존 방향).
bootstrap
└─→ application
└─→ domain
└─→ adapter (port 기준)
adapter
└─→ application
└─→ port (in/out)
adapter.out
└─→ infrastructure
└─→ common
application
└─→ domain
common (최하위)
의존성은 항상 한 방향으로만 흐르게 설계했으며, 순환 의존을 막기 위해 포트를 경유하도록 했다.
예를 들어 application
에서 DB나 Redis를 직접 참조하지 않고, MovieRepositoryPort
, LockPort
, RateLimitPort
같은 인터페이스만 사용하게 하고 실제 구현은 adapter.out
에 두었다.