research

Layering — 실전 예시와 템플릿

Layering — 실전 예시와 템플릿

이론: 01-three-principal-layers

예시: 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/