2. 개발 가이드라인 | 시스템 아키텍처 구조 - DDD-Community/DDD-12-MOYORAK-API GitHub Wiki
우리 프로젝트는 전형적인 레이어드 아키텍처를 기반으로 다음과 같은 계층 구조를 따릅니다.
Controller → Service → Repository → Database
각 계층은 다음과 같은 역할을 담당합니다.
계층 | 역할 설명 |
---|---|
Controller | 외부 요청을 받아, 어느 서비스를 호출할 것인지를 판단합니다. |
Service | 핵심 비즈니스 로직을 담당합니다. |
Repository | DB 접근과 영속성 관련 작업을 담당합니다. |
Database | 실제 데이터를 저장하는 저장소입니다. |
이러한 구조는 관심사의 분리(SoC, Separation of Concerns)를 기반으로 하며, 각 계층은 자신의 역할에만 집중할 수 있도록 설계되어 있습니다.
우리는 레이어드 아키텍처의 단방향 의존성을 원칙으로 삼습니다. 즉, 상위 계층은 하위 계층을 참조할 수 있지만, 하위 계층이 상위 계층을 참조하는 역방향 참조는 금지합니다.
- ❌ 허용되지 않는 예
- Controller → 다른 Controller 참조 ❌
- Service → Controller 참조 ❌
- Repository → Service 또는 Controller 참조 ❌
Controller는 단순히 외부 요청을 받아 처리 흐름을 시작하는 역할만 하므로,
다른 Controller를 직접 참조하거나 호출하는 것은 금지합니다.
따라서 컨트롤러 클래스는 public으로 선언하지 않고, default(package-private)로 선언하여
외부 참조를 명시적으로 차단합니다.
@RestController
class AuthController {
// public ❌, class-level default 접근제한자 사용
}
기본적으로 Service 클래스는 서로를 직접 참조하지 않도록 설계합니다. 서비스 간 의존이 많아질 경우 다음과 같은 문제가 발생할 수 있습니다.
- 서비스 간 강한 결합도 (tight coupling)
- 순환 참조 (circular dependency)
- 단일 책임 원칙 위반
- 테스트 및 유지보수의 어려움
그래서 도입하는 파사드(Facade) 계층을 도입하고자 합니다.
여러 서비스가 협력해야 하는 복잡한 비즈니스 흐름이 있을 경우, 컨트롤러와 서비스 사이에 파사드 계층을 두어 서비스들을 조율하는 역할을 맡깁니다.
Controller
↓
Facade (서비스 간 조율)
↓
Service1 Service2 ...
↓ ↓
Repository Repository
|항목|설명|
|역할|여러 서비스를 조합하여 하나의 흐름을 형성|
|위치|컨트롤러와 서비스 사이. @Service
로 명시|
|장점|각 서비스 마다 독립성 유지 + 복잡한 흐름 처리 가능|
|트랜잭션 관리|파사드 계층을 통해 전체 트랜잭션 범위 제어|
|예외 처리|하나의 기능 단위로 예외 처리 용이|
@Service
말고 명확한 구분이 필요하다면 커스텀 어노테이션@Facade
생성을 해도 좋을 것 같습니다.
@Service
@RequiredArgsConstructor
public class AuthFacade {
private final UserService userService;
private final CouponService couponService;
@Transactional
public void signUp(final SignUpRequest request) {
userService.register(request));
couponService.issue(CouponType.ISSUE, request);
}
}
만약 서비스간의 참조가 가능했더라면, UserService.register
내부에서 회원가입 기념 쿠폰 발급을 위해, CouponService.signupIssue
를 참조해야 할 것입니다.
파사드 계층을 이용하게 되면 컨트롤러는 AuthFacade.signUp()
만 호출하면 되며, 내부적으로 필요한 서비스들이 조합되어 처리됩니다.
각 서비스는 다른 서비스의 의존 없이 단일적으로 처리되게 됩니다.