Files

Architecture

Context & Scope

이 문서는 Project-Auth-Server의 레이어 구조와 조립 방식을 설명합니다.
핵심 질문은 "왜 이 구조를 택했고, 실제로 의존 방향을 어떻게 통제하는가"입니다.

문서 작성 기준은 docs/documentation-guide.md를 따릅니다.

Scope

  • 포함: 레이어 책임, 의존 방향, 웹 앱과 migration 앱의 조립 방식
  • 제외: 개별 인증 플로우, 운영 환경별 GitOps 선언 상세

Why

이 프로젝트는 인증/인가 서버이기 때문에 비즈니스 규칙, HTTP 계약, 외부 기술 구현이 빠르게 섞이기 쉽습니다.
특히 보안, DB migration, 외부 시스템 연동이 함께 존재하면 "편해서 한 군데에 몰아넣은 코드"가 빠르게 늘어납니다.

이 구조가 해결하려는 문제는 다음과 같습니다.

  • 도메인 규칙이 Spring MVC, JPA, 보안 프레임워크에 오염되는 문제
  • 응답 계약과 비즈니스 규칙이 뒤섞이는 문제
  • migration 같은 운영성 실행을 웹 애플리케이션과 강하게 결합하는 문제
  • 기술 교체 영향 범위를 좁히기 어려운 문제

What

한눈에 보는 의존 흐름

flowchart LR
  Bootstrap[bootstrap]
  Presentation[presentation]
  Infrastructure[infrastructure]
  Application[application]
  Domain[domain]

  Bootstrap --> Presentation
  Bootstrap --> Infrastructure
  Bootstrap --> Application
  Bootstrap -. compileOnly .-> Domain
  Presentation --> Application
  Infrastructure --> Application
  Infrastructure --> Domain
  Application --> Domain

핵심은 의존 방향이 항상 안쪽으로만 흐른다는 점입니다.

  • domain은 가장 안쪽에 있고 아무 것도 모릅니다.
  • application은 유스케이스와 포트를 통해 도메인 규칙을 조합합니다.
  • presentation, infrastructure는 바깥쪽 어댑터로서 application에 의존합니다.
  • bootstrap은 실제 Spring Boot 실행 모듈로, 바깥 레이어들을 조립합니다.

레이어별 책임

domain

도메인 모델과 순수 규칙만 둡니다.

  • 예: User, UserEmail, UserName, UserPasswordPolicy
  • 금지: Spring annotation, JPA annotation, HTTP/Servlet 타입

application

“무슨 일을 한다”를 담당합니다.

  • 유스케이스
  • 커맨드/결과 DTO
  • 포트(port/in, port/out)
  • 비즈니스 에러 코드/예외

여기서는 더 이상 HTTP status를 다루지 않습니다.
에러는 code, message만 가지고 있고, HTTP status 매핑은 바깥쪽 presentation에서 담당합니다.

presentation

HTTP 입출력과 API 응답 계약만 담당합니다.

  • Controller
  • Request/Response DTO
  • ApiResult
  • ApiSuccessCode
  • ValidationExceptionHandler
  • RequestExceptionHandler
  • ApplicationExceptionHandler
  • ApiErrorHttpStatusMapper

즉 API 응답 모양과 HTTP status는 이 레이어의 책임입니다.

infrastructure

기술 구현체만 담당합니다.

  • JPA repository adapter
  • persistence mapper/entity
  • password encoder adapter
  • JWT/Vault integration

application이 정의한 포트를 실제 기술로 연결합니다.

bootstrap

전체를 조립합니다.

  • Spring Boot entrypoint
  • configuration
  • security wiring
  • presentation이 직접 의존할 수 없는 기술 예외를 HTTP 경계에서 번역하는 bootstrap 전용 web adapter
  • migration 전용 app entrypoint

이 프로젝트에서는 일반 웹 애플리케이션 진입점과 별도로 MigrationApplication을 둬서 DB migration을 전용 실행 단위로 분리했습니다.

InfrastructureExceptionHandler가 bootstrap에 있는 이유도 여기 있습니다. 이 핸들러는 HTTP 응답을 만들지만, 이를 presentation으로 옮기면 presentation -> infrastructure 의존이 생겨 현재 ArchUnit 규칙을 깨게 됩니다. 그래서 security filter chain 예외나 InfrastructureException처럼 presentation이 직접 알 수 없는 타입을 HTTP로 번역하는 adapter는 bootstrap이 맡습니다.

How

웹 앱과 migration 앱의 조립

flowchart TD
  Web[AuthApplication]
  Migration[MigrationApplication]
  App[application]
  Infra[infrastructure]
  Pres[presentation]
  Domain[domain]
  DB[(PostgreSQL)]

  Web --> Pres
  Web --> App
  Web --> Infra
  Pres --> App
  Infra --> App
  Infra --> Domain
  App --> Domain
  Infra --> DB

  Migration --> App
  Migration --> Infra
  Migration --> DB

이 구조의 의도는 이렇습니다.

  • 웹 앱은 presentation + application + infrastructure를 조립
  • migration 앱은 웹 어댑터 없이 DB migration만 수행

즉 migration을 위해 auth-server 전체 웹 컨텍스트를 억지로 띄우지 않도록 분리했습니다.

레이어 규칙을 어떻게 유지하는가

구조는 설명만으로 유지되지 않기 때문에, ArchUnit 테스트로 핵심 규칙을 검증합니다.

  • 위치: bootstrap/src/test/java/com/project/auth/architecture/LayerDependencyArchitectureTest.java

현재 강제하는 규칙:

  • domain은 Spring/JPA/Servlet에 의존하지 않는다
  • applicationpresentation/infrastructure에 의존하지 않는다
  • presentationdomain/infrastructure에 직접 의존하지 않는다
  • bootstrapconfig 패키지를 조립 지점으로 사용한다

즉 이 문서는 "설계 설명"이고, ArchUnit은 "설계 위반 방지 장치"입니다.

Alternatives Considered

대안 1: 패키지 수준 구분만 두고 단일 모듈에 집중

  • 장점: 초기 개발 속도가 빠르고 진입 장벽이 낮습니다.
  • 단점: 시간이 지나면 HTTP, DB, 도메인 규칙이 같은 계층에서 섞이기 쉽습니다.
  • 결론: 지금 프로젝트처럼 인증, 보안, 외부 시스템 연동이 많은 경우 경계가 빨리 무너질 가능성이 높아 채택하지 않았습니다.

대안 2: 웹 앱과 migration을 같은 진입점에서 처리

  • 장점: 실행 경로가 단순합니다.
  • 단점: migration만 수행해도 웹 관련 빈과 설정이 함께 로딩될 수 있습니다.
  • 결론: 운영 절차를 분리하고 실패 반경을 줄이기 위해 별도 MigrationApplication을 유지합니다.

Result / Trade-offs

이 구조로 얻는 이점은 다음과 같습니다.

  • 도메인/유스케이스가 웹 프레임워크에 오염되지 않습니다.
  • 기술 교체 영향 범위를 주로 infrastructure로 제한할 수 있습니다.
  • 응답 계약과 비즈니스 규칙의 경계를 분명히 할 수 있습니다.
  • migration 같은 운영성 실행을 별도 진입점으로 분리할 수 있습니다.

동시에 감수하는 비용도 있습니다.

  • 모듈과 패키지 경계를 지키기 위한 학습 비용이 있습니다.
  • 단기적으로는 파일 수와 설정 포인트가 늘어납니다.
  • 구조를 설명하는 문서와 ArchUnit 테스트를 함께 관리해야 합니다.