문제의 근원 : Fat Service
애초에 왜 도메인 모델을 풍부하게 만들어야 할까요? 도메인 모델을 풍부하게 만든다가 절대선이라는 생각은 일단 내려놓고 생각하면, 프로그래밍 설계에서 대부분 무언가를 해야 한다면 2가지 이유가 있습니다.
- 더 이해하기 쉽게 만들기 위해
- 변경에 유연하게 하기 위해
그렇다면 도메인 모델을 풍부하게 만들어야 하는 이유는 왜 때문일까요? 🤔
이건 도메인 모델이 풍부하지 않은 경우 그 로직이 어디에 가는지 생각해 보면 됩니다. 요구사항을 만족시키는 로직은 결국 어딘가에는 있어야 합니다. 보통 도메인 모델에 로직이 없다면, 서비스 계층에 있겠죠? 사실 처음에는 서비스 계층이 통통해도 괜찮습니다. 괜히 이해하기 어려운 패턴이나 아키텍처를 도입하면 이해하기만 어렵고 이득보다 손해가 많습니다.
하지만 1,000줄이 넘어가기 시작하는 서비스를 보고 있으면... 한숨이 나옵니다. 이제 서비스는 통통한 정도가 아닙니다! 뚱뚱합니다! Fat Service의 등장입니다. 😱
꼭 헥사고날, DDD를 써야만 도메인 모델을 풍부하게 만들 수 있는건 아니다
도메인 모델을 풍부하게 만든다고 하면 바로 떠올르는 개념이 있습니다. 헥사고날과 DDD입니다. 헥사고날 아키텍처와 DDD는 애플리케이션이 점점 복잡해 지고 외부 의존성이 많아지면서 (API 호출, DB, Redis 같은 Cache 등) 순수 비즈니스 로직이 오염돼 유지보수하기 힘들고, 이해하기 힘든 상황이 된 것에 대한 반성으로 도메인을 이러한 외부 의존성으로부터 보호하자는 개념입니다.
이론상으로는 좋지만, 헥사고날과 DDD는 너무 과해 실용적이지 않다는 비판을 많이 받았습니다. 제 개인적으로도 얻을 수 있는 장점에 비해 단점이 더 많다고 생각해 좋아하지 않습니다.
여기서 얻어갈 건 1개입니다. 헥사고날/DDD는 도메인 모델을 풍부하게 하기 위한 방법론이지, 헥사고날/DDD를 써야만 도메인 모델을 풍부하게 할 수 있는건 아니라는 겁니다. 헥사고날은 계층별로 철저히 분리하고 제한된 곳을 통해서만 소통하게 해 각 계층의 역할을 명확하게 합니다. 그래서 헥사고날을 정확히 따른다면 모든 계층에 DTO도 따로 두고, 계층을 이동할 때 마다 DTO끼리 변환을 해야 합니다. 이건 도메인 모델을 풍부하게 하는 것과 관련은 없습니다. 계층별로 역할을 나누는것에 가깝습니다. 물론 계층별로 역할을 명확히 해야 도메인 모델을 지키고 풍부하게 만들기 쉬워지지만, 꼭 계층간의 역할을 명확히 해야 도메인 모델을 풍부하게 만들 수 있는건 아닙니다.
Fat Service를 UseCase 별로 나누자
보통 Domain이 있고, 그 도메인에 조작을 가하는 Service가 있습니다. 문제는 보통 Domian 입장에서는 똑같은 조작을 당하더라도 목적이 다르고, 그 목적에 따라 해야할 Business Logic 또한 다릅니다. 즉, Service 내에서 반복이 있게 됩니다.
여기서 Domain에 조작을 가하는 목적별로 Service를 나누고 (일반적으로 UseCase라 부릅니다.) 공통되는 로직 중에 도메인과 밀접한 관련이 있으면 Domain에 넣고, 관련이 없지만 공통되는 부분은 Service로 넣어야 합니다.
이렇게 말해도 너무 추상적이죠? 제가 실제로 만들었던 포트폴리오를 예로 들어 드리겠습니다. 😅
책잇아웃이라는 서비스인데, 책을 등록하고 독서활동을 측정할 수 있는 서비스입니다. 독서활동은 여러 경로로 추가/수정/삭제될 수 있고, 원래는 이 모든게 하나의 Fat Service에 위치에 있었습니다.
- 추가 : 타이머를 시작해서 서비스 내에서 추가
- 추가 : 서비스에서 측정하지 않고 나중에 직접 추가
- 삭제 : 측정이 시작된 독서활동을 저장하지 않고 삭제
- 삭제 : 추가된 독서활동을 직접 삭제
- 수정 : 추가된 독서활동을 직접 수정
- 수정 : 측정된 독서활동을 끝냄 (시간, 페이지 등 업데이트니 수정)
- 수정 : 독서활동 중간중간 시간 동기화
위 내용을 정확하게 이해할 필요는 없지만, 같은 추가/수정/삭제라고 하더라도 필요한 Business Logic이 다르지만 공통된 부분이 있습니다. 독서활동의 정책 (예를 들어 시작 페이지가 끝 페이보다 작을 수는 없다던가)은 공통됩니다. 이런 부분은 도메인에 넣어도 됩니다. 하지만 예를 들어 통계 수치를 업데이트 하기 위해 평균 독서 속도를 계산하는 로직의 경우, 공통된 코드긴 하지만 독서활동과 직접적인 연관이 있지는 않습니다. 이 경우 도메인에 넣지 않고, 서비스로 뺄 수 있습니다.
또, 독서활동이 변경될 수 있는 경로는 크게 3가지입니다.
- 직접 변경
- 앱 내에서 측정으로 변경
- 동기화
이 3가지 경우를 UseCase로 나누면 되겠죠.
정답은 없다
읽기 쉽고, 유지보수하기 쉬운 코드를 만든다
알고리즘 / 요구사항 구현에는 정답이 있지만, 소프트웨어 설계에는 정답이 없습니다. 그래서 재밌기도 하고, 그래서 짜증나기도 합니다. 하지만 결국 좋은 설계를 왜 하려 하는지 본질을 기억하면 일관된 방향으로 결정을 할 수 있습니다.
'👨💻 프로그래밍 > Architecture' 카테고리의 다른 글
☁️ AWS에서 무중단 배포 구현하기 (0) | 2024.11.22 |
---|---|
핵사고날? 클린 아키텍처? DDD? (0) | 2024.01.28 |
CQRS (Command Query Responsibility Segregation) 알아보기 (1) | 2023.12.08 |
Redis, RabbitMQ, Kafka를 각각 Message Queue로 사용할 때의 장단점 (1) | 2023.09.30 |
🔒 분산 Architecture에서 Redlock으로 Lock 걸기 (0) | 2023.09.28 |