깔끔한 아키텍처: Node.js API에서 관심사 분리 마스터하기
카테고리
프로그래밍/소프트웨어 개발
서브카테고리
웹 개발
대상자
- 대상자: Node.js API 개발자, 중간 이상의 소프트웨어 아키텍처 이해도를 가진 개발자
- 난이도: 중급 ~ 고급 (아키텍처 패턴, 의존성 주입, 테스트 전략 이해 필요)
핵심 요약
- 4단계 레이어 구조:
엔티티(Entity)
→사용 사례(Use Case)
→컨트롤러(Controller)
→인프라스트럭처(Infrastructure)
로 구성된 순환 의존성 방지 아키텍처 - 의존성 주입:
CompositionRoot
클래스를 통해 레이어 간 의존성을 관리 (예:MongoUserRepository
,CreateUserUseCase
) - 테스트 효율성: 각 레이어를 고립된 단위 테스트 가능 (예:
CreateUserUseCase.test.js
에서mockUserRepository
사용)
섹션별 세부 요약
1. 엔티티(Entity) 레이어
- 역할: 도메인 핵심 비즈니스 규칙과 데이터 모델 정의
- 예시:
```javascript
class User {
isValidEmail() { ... }
isPasswordExpired() { ... }
}
```
- 의존성: 외부 인프라스트럭처(예: DB)와 무관
2. 사용 사례(Use Case) 레이어
- 역할: 비즈니스 로직 조정 (엔티티와 인프라스트럭처 간 중개)
- 예시:
```javascript
class CreateUserUseCase {
async execute(userData) { ... }
}
```
- 의존성:
UserRepository
,EmailService
를 주입 받아 사용
3. 컨트롤러(Controller) 레이어
- 역할: HTTP 요청/응답 처리 및 사용 사례 호출
- 예시:
```javascript
class UserController {
async createUser(req, res) { ... }
}
```
- 의존성:
CreateUserUseCase
와GetUserUseCase
직접 사용
4. 인프라스트럭처(Infrastructure) 레이어
- 역할: DB, 웹 서버, 외부 API 구현
- 예시:
```javascript
class MongoUserRepository {
async save(user) { ... }
}
```
- 의존성:
MongoClient
와EmailService
를 직접 사용
5. 의존성 주입 및 구성 루트(CompositionRoot)
- 역할: 레이어 간 의존성 연결 (모든 레이어의 의존성을 주입)
- 예시:
```javascript
class CompositionRoot {
static async setup() {
const userController = new UserController(...);
}
}
```
6. 테스트 전략
- 테스트 대상: 각 레이어를 고립된 단위 테스트 가능
- 예시:
```javascript
test('should create a new user successfully', async () => {
mockUserRepository.findByEmail.mockResolvedValue(null);
const result = await useCase.execute({ ... });
});
```
7. 주의사항 및 트레이드오프
- 복잡성: 간단한 애플리케이션에 과도한 복잡성 유발 가능
- 성능: 레이어 간 추상화로 인한 성능 저하 가능성 (핫 경로 최적화 필요)
- 팀 적응: 점진적 도입 및 교육 필요
결론
- 실무 팁: 간단한 애플리케이션부터
컨트롤러-서비스-리포지토리
분리로 시작, 복잡도 증가 시Clean Architecture
로 확장 - 핵심 구현:
CompositionRoot
를 통해 의존성 주입,jest
로 단위 테스트 작성 - 결론: 유지보수성과 확장성을 확보하기 위한 아키텍처 패턴으로, 테스트 용이성과 팀 협업 효율성을 극대화 가능