본문으로 건너뛰기

백엔드 개요

Lumie 백엔드는 app, libs/*, 그리고 lumie-backend/settings.gradle.kts에 포함된 Gradle 서브프로젝트들로 구성된 하나의 Spring Boot 배포 단위입니다. 엔트리포인트는 lumie-backend/app/src/main/java/com/lumie/app/LumieApplication.java이며, 이 클래스는 @SpringBootApplication(scanBasePackages = "com.lumie")@EnableAsync로 단일 JVM을 시작합니다.

이 페이지는 overview 문서입니다. 배포 경계, 주요 런타임 흐름, 그리고 모든 백엔드 모듈이 따르는 공통 규칙을 한눈에 보여줍니다.

소스 경로

Path역할
lumie-backend/settings.gradle.kts백엔드 런타임에 포함되는 서브프로젝트를 선언합니다
lumie-backend/app/build.gradle.ktsapp, libs/*, 포함된 modules/*로부터 배포 jar를 조립합니다
lumie-backend/app/src/main/java/com/lumie/app/LumieApplication.javaSpring Boot 엔트리포인트
lumie-backend/app/src/main/resources/application.yaml데이터 소스, Flyway, Modulith, RabbitMQ, Redis, MinIO, CORS, rate limit, worker URL에 대한 런타임 설정
lumie-backend/libs/common공통 tenant, auth, exception, idempotency, logging, base-entity 유틸리티
lumie-backend/libs/internal-api프로세스 내부 모듈 계약과 모듈 간 이벤트
lumie-backend/libs/messagingRabbitMQ queue, exchange, routing-key 상수
lumie-backend/modules/*동일한 JVM에 로드되는 제품 및 플랫폼 모듈

배포 단위 구조

인프로세스로 함께 배포되는 구성

app/build.gradle.kts는 다음 범주를 백엔드 jar에 연결합니다.

modules/activity-log는 리포지토리 트리에는 존재하지만 settings.gradle.kts에 포함되어 있지 않으므로 현재 런타임의 일부가 아닙니다.

주요 런타임 흐름

인증된 애플리케이션 요청

  1. modules/*/adapter/in/web 아래의 컨트롤러가 /v1/**를 받습니다.
  2. JwtAuthenticationFilterAuthorization: Bearer ... 또는 lumie_access_token 쿠키를 받아들입니다.
  3. tenant와 user context가 TenantContextHolderUserContextHolder에 기록됩니다.
  4. 해당 application service가 트랜잭션 안에서 실행됩니다.
  5. RlsTenantContextAspectapp.tenant_id를 바인딩하여 PostgreSQL RLS가 tenant 범위 row를 필터링할 수 있게 합니다.
  6. 모듈은 동기적으로 커밋하거나 후속 작업을 위해 post-commit event를 발행합니다.

내부 worker 또는 플랫폼 요청

  1. /internal/** 요청은 end-user JWT가 아니라 InternalHmacAuthFilter로 인증됩니다.
  2. 필터가 X-Tenant-Slug, X-Timestamp, X-Signature를 검증하고, tenant ID를 해석한 뒤, synthetic ROLE_INTERNAL을 부여합니다.
  3. 이후 코드는 일반 요청과 동일한 tenant context와 RLS 적용 하에서 실행됩니다.

커밋 후 비동기 후속 처리

  1. 모듈이 도메인 write와 같은 데이터베이스 트랜잭션 안에서 Spring Modulith event를 발행합니다.
  2. 이벤트는 public.event_publication에 저장됩니다.
  3. 커밋 후 @ApplicationModuleListener가 프로세스 내부에서 이벤트를 처리하거나 RabbitMQ로 전달합니다.
  4. listener 또는 broker 전송이 실패하면 publication은 미완료 상태로 남고, spring.modulith.events.republish-outstanding-events-on-restart=true 설정에 따라 재시작 시 다시 시도됩니다.

공통 경계 규칙

  • 백엔드는 분리 배포되는 Java 서비스 집합이 아니라 모듈러 모놀리스입니다. 모듈 이름은 한 프로세스 내부의 소유 경계를 나타냅니다.
  • 모듈 구조는 domain, application, adapter를 따릅니다. 자세한 내용은 Architecture를 참고하세요.
  • 동기식 모듈 간 호출은 libs/internal-api 인터페이스를 거치며, 대개 adapter/in/internal/ 아래에서 구현됩니다.
  • 내구성이 필요한 내부 이벤트는 직접 @TransactionalEventListener를 연결하지 않고 Spring Modulith의 JDBC outbox를 거칩니다.
  • 외부 Python worker는 integration입니다. 이들은 tenant data table을 소유하지 않으며, 트랜잭션 write와 tenant-safe read의 source of truth는 여전히 monolith입니다.

유의해야 할 실패 모드

  • context에 tenant ID가 없으면 slug가 있어도 RLS가 적용되는 table은 비어 있는 것처럼 보입니다.
  • 런타임 데이터베이스 역할에 SUPERUSER 또는 BYPASSRLS를 부여하면 tenant 격리가 조용히 무력화되므로 RuntimeDbRoleGuard가 startup을 차단합니다.
  • TenantContextHolderUserContextHolder를 잃어버린 async 코드는 task-decorator 또는 명시적 withinContext(...) 복원을 사용하지 않으면 실패하거나 잘못된 범위를 읽게 됩니다.
  • queue 기반 작업은 모듈이 outbox 경로를 통해 발행할 때만 durable합니다. worker로 직접 보내는 HTTP 호출에는 그 내구성이 없습니다.

검증 명령어

cd /Users/bluemayne/Projects/Lumie/lumie-backend
./gradlew test
./gradlew integrationTest
./gradlew :app:test
./gradlew :libs:common:test

이 개요와 관련해 유용한 integration test:

  • app/src/test/java/com/lumie/app/config/RoutingDataSourceIntegrationTest.java
  • app/src/test/java/com/lumie/app/migration/MigrationsRlsIntegrationTest.java
  • libs/common/src/test/java/com/lumie/common/tenant/RlsTenantContextAspectIntegrationTest.java

관련 페이지