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/