Layering — 실전 예시와 템플릿
예시: Academic Life System의 3계층
리트머스 테스트 적용
"CLI를 추가한다고 상상하라. 뭘 중복 구현해야 하는가?"
| 기능 | 올바른 위치 | 잘못된 위치 | |------|-----------|-----------| | 추천 점수 계산 | Domain (RecommendationService) | Presentation (Controller에서 직접 계산) | | "추천 결과를 카드 UI로 표시" | Presentation (View) | Domain에서 HTML 생성 | | "LLM 응답을 파싱" | Domain (LLMCounselorService) | Presentation에서 JSON 파싱 | | "DB에서 학생 조회" | Data Source (StudentMapper) | Domain에서 직접 SQL |
CLI를 추가해도 Domain과 Data Source는 한 줄도 바꿀 필요 없어야 한다.
템플릿: 3계층 아키텍처 설계
1. 계층 구조 다이어그램
2. 계층 분리 체크리스트
## [프로젝트명] 계층 분리 체크리스트
### Presentation → Domain 의존성
- [ ] Controller는 도메인 객체의 메서드만 호출하는가?
- [ ] View에 비즈니스 로직(if/계산)이 없는가?
- [ ] API 응답 포맷 변환만 Presentation에 있는가?
### Domain 독립성
- [ ] Domain 클래스에 SQL/HTTP 코드가 없는가?
- [ ] Domain 클래스에 UI 관련 코드(HTML, JSON 포맷팅)가 없는가?
- [ ] Domain을 단독으로 유닛 테스트할 수 있는가?
### Data Source 캡슐화
- [ ] DB 접근이 Mapper/Gateway 뒤에 숨겨져 있는가?
- [ ] 외부 API 호출이 Gateway 뒤에 숨겨져 있는가?
- [ ] DB 스키마가 바뀌어도 Domain 코드는 그대로인가?
### 위반 감지
- [ ] CLI를 추가한다면 Domain/DataSource를 수정해야 하는가? → 위반!
- [ ] DB를 교체한다면 Domain/Presentation을 수정해야 하는가? → 위반!
3. 의존성 방향 검증 테이블
| From → To | 허용? | 우리 프로젝트에서의 예 |
|-----------|-------|---------------------|
| Presentation → Domain | ✅ | Controller가 Student.getCompletedCourses() 호출 |
| Presentation → Data Source | ❌ | Controller에서 직접 SQL 실행 |
| Domain → Presentation | ❌ | Student 클래스에서 HTML 생성 |
| Domain → Data Source | ⚠️ 패턴에 따라 | Active Record면 허용, Data Mapper면 Mapper가 Domain을 알지 Domain이 Mapper를 모름 |
| Data Source → Domain | ⚠️ 패턴에 따라 | Mapper가 Domain 객체를 생성하므로 Data Source → Domain 참조는 존재 |
| Data Source → Presentation | ❌ | Mapper에서 HTTP 응답 생성 |
예시: 계층별 클래스 배치 (Class Diagram with Namespace)
안티패턴 예시: 도메인 로직의 Presentation 누출
// ❌ BAD: Controller에 도메인 로직이 있음
class RecommendationController {
void handleRequest(req, res) {
auto courses = courseMapper.findAll();
auto completed = enrollmentMapper.findByStudent(studentId);
// 이 필터링 로직이 Controller에 있으면 안 된다!
vector<Course> recommended;
for (auto& c : courses) {
if (!isCompleted(c, completed) && meetsPrereqs(c, completed)) {
recommended.push_back(c);
}
}
res.send(recommended);
}
};
// ✅ GOOD: 도메인 로직은 Domain 계층에
class RecommendationService {
List<Course> recommend(Student student) {
auto completed = student.getCompletedCourses();
auto all = courseRepository.findAll();
return filterEligible(all, completed); // 도메인 로직
}
};
class RecommendationController {
void handleRequest(req, res) {
auto student = studentMapper.find(req.param("studentId"));
auto recommendations = recommendationService.recommend(student);
res.send(recommendations); // 위임만!
}
};
템플릿: 계층 간 데이터 흐름 시퀀스 다이어그램
템플릿: 파일 디렉토리 구조
src/
├── presentation/ # 또는 controllers/, views/, api/
│ ├── controllers/
│ └── views/
├── domain/ # 또는 models/, core/, business/
│ ├── entities/
│ └── services/
└── data_source/ # 또는 mappers/, repositories/, infrastructure/
├── mappers/
└── db/