Clean Architecture 접근 방식으로 Domain-Driven Design (DDD) 원칙을 보여주는 Spring Boot 애플리케이션입니다. 이 프로젝트는 도메인 모델과 인프라스트럭처 레이어 간의 관심사 분리를 보여주며, DDD 모범 사례를 따릅니다.
- Domain-Driven Design: 도메인 모델(
User,UserPassword)과 JPA 엔티티(UserEntity) 간의 명확한 분리 - Clean Architecture: 컨트롤러, 서비스, 도메인, 인프라스트럭처 레이어 간 명확한 경계를 가진 계층형 아키텍처
- 비밀번호 정책: 구성 가능한 규칙으로 강제되는 비밀번호 검증
- RESTful API: 사용자 관리를 위한 완전한 CRUD 작업
- 예외 처리: 커스텀 예외를 사용한 중앙화된 예외 처리
- 포괄적인 테스트: HTTP 레이어를 위한 MockMvc 기반 유닛 테스트
- Java: 17
- Spring Boot: 3.5.9
- Spring Data JPA: 데이터베이스 접근용
- H2 Database: 개발용 인메모리 데이터베이스
- Lombok: 보일러플레이트 코드 감소용
- Spring Security Crypto: 비밀번호 인코딩용
- JUnit 5: 테스트용
- Mockito: 테스트에서 모킹용
src/main/java/io/github/ohmry/example/
├── controller/ # REST API 엔드포인트
│ ├── UserController.java
│ └── UserExceptionHandleController.java
├── data/ # DTOs (Data Transfer Objects)
│ ├── ApiResponse.java
│ ├── UserCreateRequest.java
│ └── UserUpdateRequest.java
├── domain/ # 도메인 모델 (비즈니스 로직)
│ ├── User.java
│ └── UserPassword.java
├── entity/ # JPA 엔티티 (인프라스트럭처)
│ └── UserEntity.java
├── exception/ # 커스텀 예외
│ ├── PasswordPolicyViolationException.java
│ └── UserNotFoundException.java
├── repository/ # 리포지토리 인터페이스
│ └── UserRepository.java
├── service/ # 애플리케이션 서비스
│ ├── UserService.java
│ └── UserServiceImpl.java
└── web/ # 웹 설정
└── SecurityConfiguration.java
이 프로젝트는 명확한 분리를 가진 계층형 아키텍처 패턴을 따릅니다:
┌─────────────────┐
│ Controller │ ← REST API 레이어
└────────┬────────┘
│
┌────────▼────────┐
│ Service │ ← 애플리케이션 서비스 레이어
└────────┬────────┘
│
┌────────▼────────┐
│ Domain │ ← 비즈니스 로직 레이어
└────────┬────────┘
│
┌────────▼────────┐
│ Repository │ ← 데이터 접근 인터페이스
└────────┬────────┘
│
┌────────▼────────┐
│ Entity (JPA) │ ← 인프라스트럭처 레이어
└─────────────────┘
-
도메인 모델 vs 엔티티 분리
User(도메인 모델): 비즈니스 로직과 규칙을 포함UserEntity(JPA 엔티티): 영속성을 위한 순수 데이터 표현- 변환 메서드:
User.toEntity()및User.from(UserEntity)
-
값 객체 (Value Objects)
UserPassword: 비밀번호 검증 및 인코딩 로직을 캡슐화- 도메인 레벨에서 비밀번호 정책 강제
-
애플리케이션 서비스
UserService: 도메인 작업을 조율- 트랜잭션 관리 및 레이어 간 조정
- 비즈니스 로직 없음 - 도메인 모델에 위임
POST /user
Content-Type: application/json
{
"email": "user@example.com",
"name": "홍길동",
"password": "Password123!"
}응답:
{
"code": "0000",
"message": "success",
"data": {
"id": "uuid",
"name": "홍길동",
"email": "user@example.com",
"password": {
"value": "encoded-password"
}
}
}GET /user/{id}PUT /user
Content-Type: application/json
{
"id": "user-id",
"newName": "김철수",
"newPassword": "NewPassword456!"
}DELETE /user
Content-Type: application/json
"user-id"애플리케이션은 다음 규칙으로 비밀번호 검증을 강제합니다:
-
옵션 1: 다음 중 3가지 이상을 포함하여 최소 8자:
- 대문자
- 소문자
- 숫자
- 특수 문자
-
옵션 2: 위 유형 중 2가지 이상을 포함하여 최소 10자
유효하지 않은 비밀번호는 오류 코드 1002와 함께 400 Bad Request를 반환합니다.
- Java 17 이상
- Maven 3.6+ (또는 포함된 Maven Wrapper 사용)
- 저장소 클론:
git clone <repository-url>
cd spring-ddd-user-example- 애플리케이션 실행:
./mvnw spring-boot:run또는 Maven 사용:
mvn spring-boot:run-
애플리케이션이
http://localhost:8081에서 시작됩니다 -
H2 콘솔 접근 (선택사항):
- URL:
http://localhost:8081/h2-console - JDBC URL:
jdbc:h2:mem:testdb - 사용자명:
sa - 비밀번호: (비어있음)
- URL:
./mvnw test또는 Maven 사용:
mvn test프로젝트는 MockMvc를 사용한 포괄적인 테스트를 포함합니다:
- 유닛 테스트:
UserControllerMockMvcTest- HTTP 상태 코드 테스트
- 요청/응답 구조 검증
- 예외 처리 검증
- 격리를 위한 서비스 레이어 모킹
특정 테스트 클래스 실행:
./mvnw test -Dtest=UserControllerMockMvcTest커스텀 예외는 중앙에서 처리됩니다:
-
UserNotFoundException (404): 사용자를 찾을 수 없을 때
- 오류 코드:
1001 - 메시지: "User is not exists."
- 오류 코드:
-
PasswordPolicyViolationException (400): 비밀번호가 정책을 충족하지 않을 때
- 오류 코드:
1002 - 메시지: "The password does not satisfy the required password rules."
- 오류 코드:
-
Domain-Driven Design
- 비즈니스 로직을 가진 풍부한 도메인 모델
- 캡슐화를 위한 값 객체
- 집계 루트
-
Clean Architecture
- 의존성 역전
- 레이어 분리
- 테스트 가능성
-
관심사 분리
- 인프라스트럭처로부터 독립적인 도메인 모델
- 순수 데이터 컨테이너로서의 JPA 엔티티
- 조율을 위한 서비스 레이어
참고: 이것은 Spring Boot로 DDD 원칙을 시연하는 예제 프로젝트입니다.