197 lines
6.8 KiB
Markdown
197 lines
6.8 KiB
Markdown
# Architecture
|
|
|
|
## Context & Scope
|
|
|
|
이 문서는 `Project-Auth-Server`의 레이어 구조와 조립 방식을 설명합니다.
|
|
핵심 질문은 "왜 이 구조를 택했고, 실제로 의존 방향을 어떻게 통제하는가"입니다.
|
|
|
|
문서 작성 기준은 [docs/documentation-guide.md](../documentation-guide.md)를 따릅니다.
|
|
|
|
### Scope
|
|
|
|
- 포함: 레이어 책임, 의존 방향, 웹 앱과 migration 앱의 조립 방식
|
|
- 제외: 개별 인증 플로우, 운영 환경별 GitOps 선언 상세
|
|
|
|
## Why
|
|
|
|
이 프로젝트는 인증/인가 서버이기 때문에 비즈니스 규칙, HTTP 계약, 외부 기술 구현이 빠르게 섞이기 쉽습니다.
|
|
특히 보안, DB migration, 외부 시스템 연동이 함께 존재하면 "편해서 한 군데에 몰아넣은 코드"가 빠르게 늘어납니다.
|
|
|
|
이 구조가 해결하려는 문제는 다음과 같습니다.
|
|
|
|
- 도메인 규칙이 Spring MVC, JPA, 보안 프레임워크에 오염되는 문제
|
|
- 응답 계약과 비즈니스 규칙이 뒤섞이는 문제
|
|
- migration 같은 운영성 실행을 웹 애플리케이션과 강하게 결합하는 문제
|
|
- 기술 교체 영향 범위를 좁히기 어려운 문제
|
|
|
|
## What
|
|
|
|
### 한눈에 보는 의존 흐름
|
|
|
|
```mermaid
|
|
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 앱의 조립
|
|
|
|
```mermaid
|
|
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에 의존하지 않는다
|
|
- `application`은 `presentation`/`infrastructure`에 의존하지 않는다
|
|
- `presentation`은 `domain`/`infrastructure`에 직접 의존하지 않는다
|
|
- `bootstrap`만 `config` 패키지를 조립 지점으로 사용한다
|
|
|
|
즉 이 문서는 "설계 설명"이고, ArchUnit은 "설계 위반 방지 장치"입니다.
|
|
|
|
## Alternatives Considered
|
|
|
|
### 대안 1: 패키지 수준 구분만 두고 단일 모듈에 집중
|
|
|
|
- 장점: 초기 개발 속도가 빠르고 진입 장벽이 낮습니다.
|
|
- 단점: 시간이 지나면 HTTP, DB, 도메인 규칙이 같은 계층에서 섞이기 쉽습니다.
|
|
- 결론: 지금 프로젝트처럼 인증, 보안, 외부 시스템 연동이 많은 경우 경계가 빨리 무너질 가능성이 높아 채택하지 않았습니다.
|
|
|
|
### 대안 2: 웹 앱과 migration을 같은 진입점에서 처리
|
|
|
|
- 장점: 실행 경로가 단순합니다.
|
|
- 단점: migration만 수행해도 웹 관련 빈과 설정이 함께 로딩될 수 있습니다.
|
|
- 결론: 운영 절차를 분리하고 실패 반경을 줄이기 위해 별도 `MigrationApplication`을 유지합니다.
|
|
|
|
## Result / Trade-offs
|
|
|
|
이 구조로 얻는 이점은 다음과 같습니다.
|
|
|
|
- 도메인/유스케이스가 웹 프레임워크에 오염되지 않습니다.
|
|
- 기술 교체 영향 범위를 주로 `infrastructure`로 제한할 수 있습니다.
|
|
- 응답 계약과 비즈니스 규칙의 경계를 분명히 할 수 있습니다.
|
|
- migration 같은 운영성 실행을 별도 진입점으로 분리할 수 있습니다.
|
|
|
|
동시에 감수하는 비용도 있습니다.
|
|
|
|
- 모듈과 패키지 경계를 지키기 위한 학습 비용이 있습니다.
|
|
- 단기적으로는 파일 수와 설정 포인트가 늘어납니다.
|
|
- 구조를 설명하는 문서와 ArchUnit 테스트를 함께 관리해야 합니다.
|