아키텍처
모듈러 모놀리스
Lumie 백엔드는 모듈러 모놀리스 아키텍처를 채택합니다. 하나의 Spring Boot 애플리케이션(app 모듈)이 모든 도메인 모듈을 포함하며, 단일 JAR(lumie-backend.jar)로 배포됩니다. 마이크로서비스가 아닙니다 — 모듈은 논리적 경계이며 각각 독립 배포되지 않습니다.
왜 모듈러 모놀리스인가
- 단일 DB: 모든 모듈이 같은 PostgreSQL을 공유 (RLS 기반 멀티테넌시)
- 밀결합: 거의 모든 요청이 tenant/auth를 참조하는 구조
- 소규모 팀: 독립 배포의 이점보다 운영 단순화가 더 중요
- 리소스 절감: 다수의 JVM pod 대신 3개 replica로 메모리 절감
헥사고날 아키텍처
각 모듈은 동일한 헥사고날 아키텍처 패키지 구조를 따릅니다.
com.lumie.{module}/
├── adapter/
│ ├── in/
│ │ ├── web/ # REST 컨트롤러
│ │ ├── messaging/ # @RabbitListener
│ │ ├── internal/ # libs/internal-api 구현체 (producer)
│ │ └── security/ # Spring Security 필터 (auth 모듈만)
│ └── out/
│ ├── persistence/ # JPA 리포지토리 어댑터
│ ├── external/ # 외부 HTTP 클라이언트 (worker 호출 등)
│ └── config/ # 모듈 전용 @Configuration / @Bean
├── application/
│ ├── service/ # 유스케이스 구현 (컨트롤러가 직접 주입)
│ ├── port/out/ # 아웃바운드 포트 인터페이스
│ └── dto/ # 요청/응답 DTO (record 우선)
└── domain/
├── entity/ # JPA 엔티티
├── vo/ # 값 객체
├── repository/ # 리포지토리 인터페이스
└── exception/ # 모듈별 ErrorCode + BusinessException 서브클래스
규칙 (BACKEND_RULES.md 발췌)
application/port/in/인터페이스 없음 — 컨트롤러는 서비스 클래스를 직접 주입infrastructure/패키지 없음 — 설정은adapter/out/config/, 보안 필터는adapter/in/security/- 도메인 레이어에 Spring 애노테이션 없음
- 다른 모듈의
*.domain.entity직접 임포트 금지 →libs/internal-api인터페이스 경유
모듈 간 통신
모듈 간 동기 호출은 libs/internal-api 에 정의된 인터페이스를 통한 직접 빈 주입 으로 이루어집니다. HTTP/gRPC 없음, 네트워크 오버헤드 없음.
명명 규칙
| 역할 | 위치 | 예시 |
|---|---|---|
| 인터페이스 | libs/internal-api/com/lumie/{domain}/api/XxxService.java | StudentService |
| 구현체 (producer) | modules/{module}/adapter/in/internal/XxxServiceAdapter.java | StudentServiceAdapter |
| 소비자 (consumer) | modules/{other}/adapter/out/internal/XxxInternalAdapter.java | StudentInternalAdapter |
의존 관계
외부 서비스 (lumie-worker)
백엔드는 adapter/out/external/ 에 HTTP 클라이언트를 두고 lumie-worker의 Python 서비스를 호출합니다. 모든 호출에 X-Tenant-Slug 헤더를 전파합니다.
| 서비스 | 기술 | 용도 | 호출 모듈 |
|---|---|---|---|
| chatbot-svc | Python/FastAPI + LangGraph | AI 챗봇 (SSE 스트리밍, confirm) | ai |
| grading-svc | Python/FastAPI | OMR 이미지 채점 (OpenCV) | exam |
| report-svc | Python/FastAPI | 성적표 생성 | exam |
중요: 백엔드는 Spring AI를 사용하지 않습니다. Spring AI는 2026-05-26 Phase 5에서 완전 제거되었습니다. 챗봇 LLM 오케스트레이션은 전적으로 chatbot-svc(LangGraph)가 담당하며, 백엔드의 ai 모듈은 프록시(SSE 중계, RLS 범위 내 대화 영속성)만 수행합니다.
OpenAPI / orval 계약
BE springdoc FE orval codegen
─────────────────────────────────────────────────
/v3/api-docs (JSON) ──────► openapi.json ──► 생성된 훅/클라이언트
▲ ▲
OpenApiSnapshotTest nextjs-ci 드리프트 게이트
(스냅샷 불일치 = CI 실패) (생성 파일 커밋 변경 = CI 실패)
app/src/main/java/com/lumie/app/config/OpenApiConfig.java— Bearer JWT 스키마, LocalTime 매핑app/src/main/java/com/lumie/app/config/OpenApiResponseRequiredCustomizer.java,OpenApiPageableFlattenCustomizer.java— 응답 필드 필수화 / Pageable 플래튼app/src/test/java/com/lumie/app/OpenApiSnapshotTest.java— 스냅샷 드리프트 게이트- 스냅샷 재생성:
./gradlew :app:test --tests '*OpenApiSnapshotTest' -DupdateOpenApiSnapshot=true
Spring Modulith 이벤트 아웃박스
교차 모듈 도메인 이벤트(예: StudentRegisteredEvent, TenantCreatedEvent)는 Spring Modulith JDBC 아웃박스로 발행됩니다. 이벤트 발행자는 libs/internal-api 에 이벤트 record를 선언하고, 구독자는 @ApplicationEventListener 로 수신합니다. JDBC 스토어는 event_publication 테이블을 public 스키마에 고정하여 Hibernate 멀티테넌시를 우회합니다.
관련 문서
- 모듈 레퍼런스 — 각 모듈 상세 설명
- 멀티테넌시 — RLS 격리 메커니즘
- 인증 & 멀티테넌시 — JWT 인증 플로우
- 인프라 — 운영 배포 환경