본문으로 건너뛰기

아키텍처

모듈러 모놀리스

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.javaStudentService
구현체 (producer)modules/{module}/adapter/in/internal/XxxServiceAdapter.javaStudentServiceAdapter
소비자 (consumer)modules/{other}/adapter/out/internal/XxxInternalAdapter.javaStudentInternalAdapter

의존 관계


외부 서비스 (lumie-worker)

백엔드는 adapter/out/external/ 에 HTTP 클라이언트를 두고 lumie-worker의 Python 서비스를 호출합니다. 모든 호출에 X-Tenant-Slug 헤더를 전파합니다.

서비스기술용도호출 모듈
chatbot-svcPython/FastAPI + LangGraphAI 챗봇 (SSE 스트리밍, confirm)ai
grading-svcPython/FastAPIOMR 이미지 채점 (OpenCV)exam
report-svcPython/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 멀티테넌시를 우회합니다.


관련 문서