외부활동/우아한테크코스 [프리코스]

도메인의 성장과 Service 계층의 관계

softmoca__ 2025. 11. 2. 22:12
목차


도메인이 빈약했을 때는 Service가 필요했지만, 도메인이 풍부해지면서 Service는 자연스럽게 사라졌다 !

 

1,2주 차에는 미션과 요구사항, 도메인이 간단해서 늘 협력메시지 시나리오 흐름과 객체들의 책임과 역할을 모두 설계한 뒤 진행을 했지만 이번 3주차 로또는 요구사항이 많고 초기 설계하는 과정에서 너무 많은 역할과 로직들이 떠올라 유즈케이스와 기능구현목록 분석후 간단한 설계후 진행하였다. 하지만 이러한 원인 때문인지 미션 구현과 리팩토링 과정에서 많이 헤매었고, 특히 리팩토링을 하는 과정이 힘들었다.

이러한 과정 속에서 service 계층이 있었다가 최종적으로는 제거가 되었다.

 

 

우선 1,2주차에 비해 복잡하고 요구사항이 많아진 만큼 웹 애플리케이션 개발 경험을 떠올리며 Service 계층이 필요할것 같다는 생각을 했다.

도메인 객체들은 보이는데 협력 관계가 어려웠고, 도메인 객체를 조율하는 계층인 서비스를 바로 도입해 보았다.

 

초기: 빈약한 도메인과 Service계층

처음 만든 Lotto는 그저 숫자들을 담고 검증 정도만 하고, 실제로 "뭔가를 하는" 객체가 아니었다.

WinningNumbers도 마찬가지로 당첨 번호를 가지고는 있지만, 이 친구와 다른 객체들과 어떻게 협력할지 가늠 오지 않았다.

우선은 더욱 미션 전체 요구 사항이 많아지고 도메인이 커진 만큼 이런 객체들을 써서 조립할 서비스 계층이 필요해서 우선 만들어 보았다.

도메인 객체들이 스스로 할 수 없는 일들을 대신 해주는 보호자 같은 존재로서 우선 기능 구현을 마친 뒤 리팩토링을 할 계획이었다 !

처음 작성한 Lotto는 정말 단순했다. 이 로또는 자기가 뭘 할 수 있는지 몰랐다. 그저 번호를 담고 있을 뿐이었다.

당첨 확인? 비교? 매칭? 그런 건 우선 모르겠다 ! 그냥 번호를 가지고 있는 객체였다.

그리고 우선은 기능 구현을 위해 LottoMatcher과 LottoSHop을 서비스 계층에 구성했다 ! 이름도 상당히 마음에 들었다 실제 매칭 ! 그리고 가게 ! 하지만 이 선택 때문에 많은 리팩토링과 고민을 하는 시간을 가지게 되었다..!

 

 

리팩토링 시작 !

여러 도메인들을 풍부하게 만들고 하나씩 메시지를 통해 객체끼리 협력을 하게 하니 점차 의미가 명확해지고 협력관계가 점점 잘 보이기 시작했다 !

"이 로또와 저 로또의 매칭 개수를 세라"가 아니라 "이 로또야, 저 로또와 매칭 갯수 알려줘"로 구성을 하며 책임이 알맞게 이동하고 있음을 느꼈다. 이 과정에서 여러 책임들이 Service에서 각각의 도메인으로 이동하니 점차 Service 존재 자체가 의미가 없어지고 오히려 혼란만 더 가져왔다 !

그 순간 깨달았다 ! "도메인 객체가 똑똑해지면, Service가 할 일이 점점 없어진다." 라는 사실을 !!!

 

 

 

 

깨달음: Service는 도메인의 빈약함을 메우는 것

리팩토링을 하면 할수록 명확해졌다.

도메인이 데이터와 행동 모두 가지며 풍부해질수록 Service는 할일이 없어진다는것을 !

리팩토링 과정에서 거슬렸던 Service를 어느 순간 보니 그저 단순한 반복문과 메서드 호출로 데이터를 주고 받고만 있었다.

그러다  종종 아 이정도면 그냥 컨트롤러에 가져와도 되겠는데? 아 거슬리네 라는 생각을 했다 !

 

Service의 정체성 혼란

Service는 원래 아래와 같은 역할을 해야한다 !

  • 복잡한 비즈니스 흐름 조율
  • 여러 도메인 객체 조합
  • 트랜잭션 경계 설정
  • 외부 시스템 연동

하지만 당시 나의 Service에서는 그저 단순 반복문, 도메인 메서드 호출 그 뿐이었다 !

LottoMachine - 도메인 발견과 도입 

Service를 없애기로 결심했을 때, 가장 큰 고민은 "그럼 로또 발행은 누가 하지?"였다.

Lottos 일급객체를 도입해보기도 했지만 의미가 너무 와닿지 않았다.

로또 '발급'을 해야하는데 Lottos가 발행한다? 음.. 비즈니스 규칙이 캡슐화는 잘 되겠지만서도.. 저 로또들이 뭔지 이름으로 전혀 모르고 모호함이 상당하다고 생각했다 !

그리고 다시금 반복해서 미션 가이드를 일던 도중 왜 이제야 해당 단어가 머리에 박혔는지 '발매기'자체가 확 머리에 들어왔다.

지금까지 컨트롤러 그자체가 '발매기'로서의 역할을 한다고만 생각을 했던 것이 문제였다 !

 

LottoMachine 도메인 추가 

public class LottoMachine {
    private final LottoNumberGenerator generator;
    private List<Lotto> purchasedLottos;
    
    // 발매기가 로또를 발행한다
    public void purchase(PurchaseAmount amount) {
        int quantity = amount.getLottoQuantity();
        this.purchasedLottos = generateLottos(quantity);
    }
    
    // 발매기가 당첨을 확인한다
    public WinningStatistics check(WinningNumbers winning) {
        WinningStatistics statistics = new WinningStatistics();
        for (Lotto lotto : purchasedLottos) {
            Rank rank = winning.match(lotto);
            statistics.addResult(rank);
        }
        return statistics;
    }
}

이 순간, 모든 것이 맞아떨어졌다 !

LottoMachine은 Service가 아니라 진짜 도메인 객체였다!

실제 로또 발매기처럼:

  1. 돈을 넣으면 로또를 발행하고
  2. 당첨 번호를 입력하면 확인해주는

그런 실제 존재하는 것이었다.

이후  도메인은 크게 보면 아래처럼 아주 컴팩트하게 모두가 스스로 행동을 취하며 소통할수 있게 되었다 !

서비스 계층을 완전히 필요가 없다 !

Lotto: 스스로 매칭할 수 있음
WinningNumbers: 스스로 등수를 판정할 수 있음
LottoMachine: 스스로 발행하고 확인할 수 있음

 

핵심

1. Service는 도메인의 빈약함을 채우는 것

처음에는 이해하지 못했다. "왜 Service를 만들었는데 라팩토링할 사항이 더 보이지 ??" "늘 웹 애플리케이션개발 할떄 Service를 잘 써서 개발해왔는데 ??" 지금은 안다.

Service가 필요한주요 이유는 아래와 같다는것을 !

  • 도메인이 할 수 없는 일을 대신 할 때
  • 여러 도메인을 조율해야 할 때
  • 트랜잭션 경계를 설정할 때

Service는 도메인이 스스로 할 수 있을 때, 단순 위임만 할 때 때는 필요가 없단느 것을 !

2. 도메인이 성장하면 Service는 자연스럽게 사라진다

이게 가장 큰 깨달음이었다! Service를 "제거"한 게 아니라, 도메인이 성장하면서 Service가 "불필요"해진 것이다 !

마치 어릴 때는 부모님 도움이 필요하지만, 자라면서 스스로 할 수 있게 되는 것처럼 ! 내가 만들고 잘 키운 도메인들이 이미 무럭무럭 커서 서비스같은 보호자가 필요하지 않게 된것이다 !

즉, 풍부한 도메인은 그 스스로 자체만드로 충분하다 !

3. "좋은 설계"의 착각

처음에 나는  "Controller - Service - Domain" 이 3계층 구조가 "좋은 설계"라고, 많은 튜토리얼에서 봤던 구조고, 늘 잘 이용해 왔다고 생각했다.  하지만 깨달았다 !

좋은 설계는 계층이 많은 게 아니라, 각 계층이 존재 이유가 명확한 것이다 ! 늘 관습처럼 사용하던 그 service 자체도 하나씩 파고 들어가보니 다 이유가 있었다 ! 불필요한 계층은 오히려 복잡도만 높인다 !

 

 

마치며

Service를 만들었다가 지운 것이 실패일까? 아니다 !

비록 많은 생각 없이 도입을 해서 많이 돌아왔지만, 오히려 이 과정이 없었다면, 나는 영원히 빈약한 도메인과 Service 관계에 대해 생각해 보지 못했을 거라 생각한다 !

Service를 지웠을 때는 "이게 맞는 건가?" 하는 불안함과 찜찜함이 있었지만 점차 리팩토링을 통해 도메인이 풍부해지는 코드를 보며 "아, 이게 맞구나" 하는 강한 확신과 새로 배운 내용에 대한 행복을 느꼈다 !

 

계층을 추가하는 것은 쉽다. 하지만 그 계층이 정말 필요한가? 복잡도를 증가시키는 만큼의 가치를 제공하는가? 이 질문에 "Yes"라고 답할 수 있을 때만 계층을 추가해야 한다 !

또한 좋은 코드는 좋은 코드는 더 많은 것을 추가함으로써 얻는것 뿐만 아니라 불필요한 것을 제거하는 것 또한 너무 중요하다는걸 몸소 느꼈다 ! Service를 만들었다가 지운 것은 실패가 아니라 성장이었다 !

늘 팀프로젝트를 하며 API를 뽑아내던 내가 프리코스가 아니라면 이런 경험을 했을까?

난 아마 못했고 좁은 시야를 가진 개발자로서 살아갔을것 같다 !

 

아.. 3주차에서도 너무 많은 시야를 넓혀갔다 !