NextJS 기반 DDD
먼저 프로젝트에 앞서, DDD를 도입하려고 한다
여기서 DDD란 도메인 주도 개발을 의미 하며 비즈니스 본질에 집중하여 구조화 할 수 있도록 도와주는 설계 방법이다
✅ DDD의 주요 장점
1. 🎯 비즈니스 중심의 설계
- 도메인 개념을 중심으로 시스템을 설계하기에 비즈니스 로직에 더 집중할 수 있다.
- 개발자가 단순히 코드만 짜는 게 아니라, 실제 비즈니스 문제를 이해하고 해결하는 구조로 전환됨
2. 🧱 모듈화로 인한 유지보수 용이
- 도메인별로 책임이 분리되어 있어 기능 확장 또는 수정 시 영향 범위가 작음.
- 예:
user
기능 수정이hair-style
도메인에는 영향을 주지 않음.
3. 👥 협업 효율성 증가
- 각 도메인이 독립적이기 때문에 여러 명이 동시에 각 도메인을 맡아 개발하기 좋음.
- 팀원 간 경계가 명확해서 코드 충돌이 줄어들고 역할 분담이 쉬워집니다.
4. 📦 비즈니스 로직의 응집력 강화
- UI, API 호출, 상태 관리 등을 섞지 않고 도메인 내부에 핵심 로직을 집중시킵니다.
- 해당 도메인의 모든 흐름을 한 폴더/파일 구조 안에서 파악 가능 → 가독성 ↑
5. 🧪 테스트 용이성
- 각 도메인이 독립되어 있고 로직이 모듈화되어 있어, 단위 테스트 및 통합 테스트가 수월합니다.
- 특히 서비스(UseCase) 단에서 로직 테스트 시 매우 효과적.
6. 🚀 확장성에 유리
- 도메인이 나눠져 있어서 새로운 기능을 추가하거나 도메인을 통째로 교체하기 쉬움.
- 마이크로 프론트엔드 또는 마이크로서비스 구조로 발전하기에도 적합한 설계 방식.
🆚 기존 구조와의 차이
항목 | 기능 중심 구조 | 도메인 주도 설계(DDD) |
---|---|---|
폴더 기준 | components , pages , hooks 등 기능별 | user , product , order 등 도메인별 |
의도 | 코드 재사용성과 단순 구성 | 비즈니스 모델링과 응집도 강화 |
협업 시 | 기능별 코드 분산 → 충돌 가능성 ↑ | 도메인 단위 병렬 개발 가능 |
확장 시 | 전체 구조 파악 필요 | 특정 도메인만 보면 됨 |
🧩 DDD 관점의 구성 방식
DDD의 핵심은 비즈니스 중심 설계임
따라서 각 도메인은 다음의 역할을 가져야 함
- Entitiy / Model : 도메인 객체 (예: User, HairStyle)
- Service (UseCase) : 도메인의 주요 행위 담당 : “유저로그인”, “헤어스타일 추천”
- Repository (선택) : 외부 API, DB 등과의 연결 추상화
- UI (Component) : 해당 도메인에서만 사용하는 UI
- Hook : 상태 및 비즈니스 로직 조합
🛠 DDD 관점의 구성 방식
markdown/domains/user
/components
- UserProfileCard.tsx
/hooks
- useUserProfile.ts
/services
- login.ts
- logout.ts
/models
- User.ts
- AuthStatus.ts
/api
- fetchUser.ts
/pages
- login.tsx
- signup.tsx
🧠 DDD 보너스 팁
✅ 개념 1: shared → domains
는 허용되지만, domains → 다른 도메인
접근은 지양
🔸 설명
- `shared/`는 **공통 유틸리티, 공통 컴포넌트, 전역 타입** 등을 담는 폴더입니다.
- `domains/`**는 각각의 업무 단위 책임(비즈니스 기능)을 담당합니다.**
🔄 **의존 방향 원칙**
```markdown
shared ← domain/user
← domain/hair-style
```
- 도메인은 shared에 의존할 수 있지만, 다른 도메인(
user → hair-style
)에는 직접 접근하지 않아야 한다. - 도메인 간 간접 의존이 필요하면
shared
또는 API를 통해 의존하도록 우회함
📦 예시 (지양해야 할 예)
typescript// ❌ hair-style 도메인에서 user 도메인의 서비스에 직접 접근
import { getUserProfile } from '@/domains/user/services/userService'
이런 방식은 도메인 간 강한 결합을 만들어 유지보수가 어려워지고, 나중에 도메인을 다른 모듈로 분리하고 싶을 때 방해 요소가 됨
✅ 개념 2: 서비스 레이어에서 외부 API를 직접 호출하지 않고, 추상화된 API 레이어를 사용
🔸 설명
서비스(UseCase) 레이어는 오직 비즈니스 로직만을 담당하고,
**API 통신(fetch, axios 등)**은 api/
디렉토리에 있는 함수에 위임하는 구조입니다.
📦 예시 구조
typescript/domains/user
/services
- login.ts ← 로그인 유스케이스
/api
- loginApi.ts ← axios 기반 HTTP 요청
✅ 예시 코드
/domains/user/api/loginApi.ts
typescriptimport axios from '@/shared/lib/axios'
export const loginApi = async (email: string, password: string) => {
const res = await axios.post('/login', { email, password })
return res.data
}
/domains/user/services/login.ts
typescriptimport { loginApi } from '../api/loginApi'
export const login = async (email: string, password: string) => {
const userData = await loginApi(email, password)
// 👉 여기서는 비즈니스 처리만
localStorage.setItem('token', userData.token)
return userData
}
이렇게 하면 서비스 로직을 외부 의존성(API, axios, fetch)등에서 격리할 수 있습니다. 추후 API 호출 방식이 바뀌더라도 비즈니스 로직에는 영향이 없습니다.
🔁 의존성 역전(Dependency Inversion Principle, DIP)
상위 모듈은 하위 모듈에 의존해서는 안되며, 둘 다 **추상화(인터페이스, 중간 계층)**에 의존해야 한다
즉,
- 서비스(비즈니스 로직)은 API 구현 디테일을 몰라야 한다
- 대신 API가 이런 역할을 한다는 명확한 역할만 의존하고, 구체적인 구현은 별도 레이어에서 담당
🎯 요약
원칙 | 설명 | 이점 |
---|---|---|
shared → domains 만 허용 | 도메인 간 결합도 낮춤 | 유지보수/테스트 편리 |
서비스 레이어는 직접 API 호출 ❌ | api/ 디렉토리에서 추상화된 함수 사용 | 외부 변경에도 유연하게 대응 가능 |
도메인 간 직접 import ❌ | 필요한 경우 shared 나 API 중간 레이어 사용 | 구조가 명확하고 확장에 유리 |
참고 사항 : https://lts0606.tistory.com/705