추천팀의 DDD 도입기

추천팀의 DDD 도입기

안녕하세요, 추천팀 제이입니다. 추천팀에서 새로운 머신러닝 플랫폼 개발 프로젝트에 Domain-driven design (DDD)를 적용했는데요, 이 글에서는 적용한 배경과 적용 과정, 그리고 진행하면서 느낀 점들을 공유하고자 합니다.

ML SaaS 개발 배경과 소프트웨어 복잡성

저는 추천팀에서 ML SaaS(Machine Learning Software as a Service) 플랫폼을 개발하고 있습니다. ML SaaS 플랫폼은 머신러닝을 잘 모르는 사용자도 추천팀이 가지고 있는 추천 기술을 포함한 여러 머신러닝 기술을 서비스 형태로 이용할 수 있는 플랫폼입니다. 즉, 이 플랫폼이 제공하는 것으로는 1) 머신러닝에 필요한 데이터 파이프라인, 서빙, 분석 지표 시스템을 손쉽게 구축하는 것, 2) 추천팀의 노하우가 담긴 추천 기술을 머신러닝 지식 없이도 자신의 서비스에 적용하는 것, 3) 서로 다른 알고리즘과 비즈니스 로직을 적용해 A/B 테스팅을 해볼 수 있는 것입니다. 또한 플랫폼의 관리자는 사용자가 자신의 비즈니스를 개선할 수 있도록 기존 알고리즘을 개선하거나 새롭게 만든 머신러닝 모델을 플랫폼에 적용할 수 있어야 합니다.

 

Figure 1. 플랫폼 요구 사항

 

이 플랫폼이 가진 문제 영역은 매우 복잡합니다. 데이터, 머신러닝 모델, 서빙, 통계 등 일반적으로 사용하는 머신러닝 파이프라인을 자동으로 구축하는 공장이 있으면서, 머신러닝 모델 최적화하고 A/B 테스팅, 분석할 수 있는 실험실도 마련해야 하기 때문입니다. 또한 추천 기술에 익숙한 사용자, 머신러닝 엔지니어, 개발자, 관리자 등등 플랫폼의 여러 이해관계가 얽혀 계속해서 기능은 많아지고 문제 영역의 복잡성은 커질 것입니다.

기술적 문제 또한 복잡성을 가지고 있습니다. “머신러닝 모델 학습을 위한 서버는 누가 만드는가?”,  “그것을 위한 사용자 입력은 누가 검사하고 데이터베이스에 어떻게 들고 있는가?”와 같은 질문에서 비롯되는 것입니다. 우리가 가진 복잡성은 아래의 그림처럼 도메인의 복잡성과 기술적 복잡성으로 이루어져 있습니다.

 

Figure 2. 소프트웨어 복잡성. 출처: [4]

 

우리는 이 복잡성을 설계에 알맞게 녹여낼 수 있도록 도와주고 아키텍처 의사 결정을 지원해 줄 설계 철학이 필요했습니다. DDD는 도메인의 깊은 이해를 바탕으로 만들어진 도메인 모델을 중심으로 소프트웨어를 개발, 설계하는 접근법입니다. 도메인 지식을 중앙화하고, 도메인을 구체화하며 도메인 모델 간의 경계를 분명히 할 수 있다는 것이 매력적이었습니다. 우리는 ML SaaS 프로젝트에 DDD를 적용하기로 했습니다.

 

유비쿼터스 언어와 바운디드 컨텍스트

에릭 에반스(Evans)의 책[1]에서는 프로젝트 구성원이 커뮤니케이션하면서 지속해서 배우는 것으로 설계를 시작합니다. 우리도 프로젝트 구성원 모두가 모여 서로가 단편적인 것으로 아는 것들을 나누고 생각하는 방향이나 추구하는 것들을 소통하는 자리를 만들었습니다. 하지만 책에 나온 예시처럼 무난하게 생각을 나누면서 회의를 진행하기는 어려웠습니다. 중요하게 생각하는 것들이 달라 다른 주제가 끊임없이 발생하고 그것들을 이해하고 정리하면서 다른 중요한 주제는 흘러갔습니다. 이런 비효율을 개선하기 위해 어떤 장치가 필요한 시점이었습니다.

반 버논(Vernon)의 책[3]에서 이벤트 스토밍(Event Storming)이라는 워크숍 형태를 적용해 보기로 했습니다. 이벤트 스토밍은 여러 가지 색의 메모지에 자신이 생각하는 중요 이벤트, 사용자, 명령과 비즈니스 규칙을 적어 벽에 붙이면서 진행하는 워크숍입니다. 메모지를 벽에 붙이면서 곳곳에서 스몰톡이 일어나고, 그것을 토대로 구성원들이 도메인을 학습하는 것이 목적입니다.

하지만 우리는 코로나 시국 때문에 온라인으로 워크숍을 진행했습니다. 온라인 화이트보드 플랫폼인 miro를 활용해 이벤트 스토밍 형태를 갖출 수 있었습니다. 온라인 특성상 화자가 한 명일 수밖에 없어 다양한 스몰톡이 발생하지는 않았지만요. 개발, 시스템 구축 이야기는 배제하고 도메인에 집중해 큰 그림을 그렸습니다.

 

Figure 3. 여러 가지 색의 메모지 (이벤트 스토밍 범례)

 

Figure 4. 이벤트 스토밍 흔적

 

다양한 이벤트를 발견하고 생각을 공유하면서 우리만의 “유비쿼터스 언어”를 구축하기 시작했습니다. 데이터 파이프라인의 데이터 검증을 위한 스키마, 데이터 파이프라인의 데이터 단위인 데이터셋, 추천 API에 필요한 머신러닝 모델의 조합인 솔루션 등등 머신러닝 생태계에서 많이 사용하는 쉬운 단어이지만, 함께 그 개념을 모델링하고 그 도메인 모델 정의에 동의하며, 프로젝트 구성원이 공통적으로 이해하는 단어가 있다는 것이 중요했습니다.

유비쿼터스 언어를 구축하면서 뉘앙스, 혹은 이해가 조금씩 어긋나는 것들이 있었습니다. 예를 들면, 데이터셋은 사용자의 데이터를 받아 스키마를 검사하고 그 데이터를 저장하는 개념입니다. 역할이 데이터를 검증하고 저장하는 것이죠. 머신러닝 모델을 만드는 쪽에서는 조금 다르게 바라보고 있습니다. 여기에서는 데이터셋이 데이터의 어떤 부분을 어떻게 사용해야 하는지 적어놓은 명세에 가깝기 때문입니다. 구체적으로, 데이터셋에서 데이터를 저장할 때는 어떤 데이터를 저장하는지, 어떤 형식을 가졌는지가 필요하다면, 머신러닝 모델을 만드는 쪽은 어떤 데이터를 어떤 모델에 사용하는지, 데이터의 어떤 부분이 카테고리인지 아니면 아이템 ID인지가 필요합니다.

데이터셋이란 단어를 대하는 이 두 태도가 결국 같은 도메인 모델을 얘기하는 것으로 생각할 수도 있고, 미묘하게 목표나 목적이 다르다고 생각할 수 있습니다. 이 시점에서는 선택해야 합니다. 이대로 같은 정의를 사용할지, 미묘한 차이를 구분시켜 다른 컨텍스트를 구축할지 두 가지 선택이 있을 것 같습니다. 우리는 전자가 버논(Vernon)의 책[3]에서 말하는 “큰 진흙 덩어리(Big ball of mud, 진흙공)”의 초석이 될 것 같다고 예상했습니다. 나중에는 도메인 모델들이 엮이고 엮여서 유지보수하기 어렵고 이해하기 어려운 소프트웨어를 만들 수도 있다는 뜻입니다. 이런 사태는 피하고자 도메인 모델들을 나눠서 컨텍스트 경계를 분명히 하기 시작했습니다. DDD에서 이 패턴을 “바운디드 컨텍스트”라고 합니다.

두 영역을 나눠 하나는 같은 이름인 데이터셋 컨텍스트, 하나는 추천 컨텍스트로 이름 붙였습니다. 두 컨텍스트에는 모두 데이터셋이란 엔티티를 가지고 있고 같은 이름의 다른 두 엔티티는 위의 언급한 것처럼 다른 부분을 가지고 있습니다.

컨텍스트 경계를 짓는 작업을 더 진행해 컨텍스트를 정의했습니다. 결론적으로 데이터셋 컨텍스트는 데이터 검증과 데이터 저장을 담당하고, 추천 컨텍스트는 추천에 필요한 머신러닝 모델과 그 모델 간의 의존, 그리고 추천 서비스에 필요한 비즈니스 로직을 관리하고 있습니다. 서빙(Serving) 컨텍스트는 추천 컨텍스트에서 만든 추천 로직을 실제 API로 만들어 주고, A/B 테스팅을 지원합니다. 이 외에도 여러 컨텍스트가 있지만 이 세 컨텍스트는 플랫폼의 핵심입니다.

 

Figure 5. 요약한 바운디드 컨텍스트

 

여담으로 컨텍스트의 경계는 마이크로서비스 아키텍처(Microservice Architecture)로 가는 지름길을 내주었습니다. 각각의 컨텍스트가 독립적인 서비스를 이루는 형태로 구성만 하면 마이크로서비스 아키텍처라고 할 수 있었기 때문입니다. 물론 컨텍스트의 크기와 마이크로서비스의 크기를 맞출 필요는 없지만, 마이크로서비스 아키텍처를 위해서 바운디드 컨텍스트를 도입하는 것 또한 좋은 접근일 것 같습니다. 다음에 기회가 된다면 ML SaaS 프로젝트에서 마이크로서비스 아키텍처 적용으로 얻은 장단점을 비교해 보겠습니다.

플랫폼은 이 컨텍스트 간의 협력으로 작동합니다. 이런 협력을 DDD에서는 “컨텍스트 매핑”이라고 부릅니다. 컨텍스트 매핑은 컨텍스트 간의 관계로 구분하는데, 이 관계는 여러 가지 형태가 있습니다. 공통된 도메인 모델을 공유하는 관계, 한 컨텍스트가 다른 컨텍스트의 요구사항을 반영해주는 관계, 한 컨텍스트가 다른 컨텍스트의 도메인 모델을 그대로 따르는 관계 등이 있습니다.

데이터셋과 지표 컨텍스트 관계를 살펴보겠습니다. 데이터셋 컨텍스트는 데이터를 검증하고 저장하는데 필요한 도메인 모델을 가지고 있습니다. 지표 컨텍스트는 피드백을 포함한 사용자의 데이터를 서비스에 중요한 지표로 변환하는 도메인 모델을 가지고 있습니다. 지표를 만드는 데 필요한 데이터는 데이터셋이 저장하는 데이터를 훔쳐보고 있습니다. 지표 컨텍스트의 도메인 모델이 변경되었을 때 데이터셋 컨텍스트의 변경이 필요할까요? 극단적으로 지표 컨텍스트가 제공하는 서비스가 모두 작동하지 않아도 데이터셋에 영향은 없어야 합니다. 하지만 반대로 데이터셋 컨텍스트의 도메인 모델이 변경되었다면, 예를 들어 데이터셋에서 특정 알고리즘이 사용하기 쉽도록 데이터를 항상 변환한다면 어떨까요? 지표 컨텍스트에서 서비스에 중요한 값을 해석하는 과정에 변화가 있을 것입니다. 이 경우는 데이터셋의 도메인 모델을 지표 컨텍스트가 그대로 따르는 관계가 적절할 것으로 보이고, 이 관계를 “준수자(Confirmist)”라고 부릅니다.

위의 사례에서 컨텍스트 간의 의존이 한쪽으로 치우쳤다는 것을 토대로 준수자가 적절하다고 판단했습니다. 하지만 컨텍스트 작업자와 팀, 코드 유사를 바탕으로 다른 관계로 정의할 수도 있습니다. 매트릭이 중요한 도메인이고 데이터셋에게 요구사항을 전달하고 데이터셋이 이를 충족시켜 주는 관계라면 “고객-공급자” 관계라고 할 수 있습니다.

사실 이 둘의 컨텍스트가 어떤 관계라고 정의하는 것은 크게 중요하지 않은 것 같습니다. 컨텍스트끼리 모델, 정보를 공유하는 방법으로 컨텍스트 매핑 종류를 참고해서 경계 유지와 통합에 노력해 큰 진흙 덩어리를 만들지 않는 것이 중요할 것입니다.

 

도메인 이벤트와 애그리게이트

DDD는 프로젝트 경계를 이해하는 전략 설계와 컨텍스트 내부의 도메인을 모델링하는 전술 설계로 나뉘는데, 지금까지 전략 설계의 큰 기둥인 유비쿼터스 언어과 바운디드 컨텍스트를 경험했습니다. 이제 그림을 확대해서 전술적인 설계가 필요한 때입니다.

우리가 하나의 컨텍스트안에서 가장 중요하게 생각한 것은 도메인 이벤트였습니다. 도메인 이벤트는 도메인에서 발생한 사건으로, 이벤트 스토밍에서 주인공 행세를 한 메모지입니다.

데이터셋 컨텍스트에는 데이터 검증을 위한 스키마 생성, 데이터 저장을 위한 자원 획득, 데이터 파이프라인 구축, 데이터 파이프라인 삭제와 같은 도메인 이벤트가 있습니다. 설명을 위해서 이벤트를 단순화했습니다.

 

Figure 6. 데이터셋 컨텍스트의 이벤트

 

추상화를 통해 데이터 검증에 명세가 되는 스키마와 데이터 파이프라인의 실체인 데이터셋 모델을 만들었습니다. 스키마는 스키마 생성 이벤트와 연관이 있고, 데이터셋은 나머지 이벤트와 연관이 있는 엔티티입니다. 결국 데이터셋 컨텍스트에 스키마와 데이터셋 도메인 모델이 있습니다.

이제 애그리게이트가 나와야 합니다. 애그리게이트는 도메인 이벤트를 발생시키는 주체로 비즈니스 규칙을 포함하고 도메인 모델을 내포하고 있습니다. 컨텍스트를 회사에 비유한다면 애그리게이트는 회사 내 팀장에 비유할 수 있습니다. 팀원들은 도메인 모델이겠네요. 여기는 캡슐화가 되어 있어 서로 다른 팀끼리 팀장을 통해서만 소통할 수 있습니다.

우리는 스키마와 데이터셋을 각각 애그리게이트로 내세웠습니다. 애그리게이트에서 도메인 이벤트를 발생시키는 행동 하나가 시스템에서 하나의 트랙잭션일 건데요, 스키마와 데이터셋이 같은 트랙잰션 내에서 이루어져야 하는 행동이 없었습니다. 게다가 애그리게이트가 SRP(Single-responsibility Principle)를 지키도록 작아야 유지보수가 쉬워질 것이기 때문입니다.

결국 스키마 애그리게이트는 스키마 생성이라는 도메인 이벤트를 발생시킬 수 있습니다. 도메인 이벤트를 발생시키는 행동에서는 비즈니스 규칙을 검사합니다. 스키마는 다른 도메인 이벤트가 없기 때문에 상태를 가질 필요가 없습니다. 엔티티의 상태 변화는 도메인 이벤트에서만 기인해야 하기 때문입니다.

반면에 데이터셋 애그리게이트는 상태를 가지고 있고 다양한 도메인 이벤트로 인해 상태가 변할 수 있습니다.

 

Figure 7. 애그리게이트

 

애그리케이트가 나오면서 구현을 시작했습니다. 도메인 이벤트를 프로그래밍 언어로 그대로 옮기는 작업이 먼저였고, 애그리케이트를 클래스로 작성했고, 이벤를 발생시키는 행동을 메소드로 정의했고, 그 메소드에는 비즈니스 규칙이 담겼습니다. 메소드의 결과는 애그리게이트 상태 변화와 함께 도메인 이벤트가 발생했습니다.

 

Figure 8. 파이썬 코드 예시

 

참고로 코드에서 도메인 이벤트를 발생시키는 방식은 여러 가지가 있습니다. 애그리게이트에서 단순히 반환(return)할 수도 있고 (링크) 도메인 이벤트에 헬퍼(helper)를 구축할 수도 있고 (링크, 애그리게이트에 도메인 이벤트 리스트를 가지고 있도록 구현(링크))할 수도 있습니다. 예제에서는 단순히 반환(return)하는 방식을 사용했습니다.

도메인 이벤트는 그 도메인에서 단일 진실 공급원(Single source of truth)이 될 수 있습니다. 그렇다면 자연스럽게 도메인 이벤트가 주도하여 애플리케이션 로직을 이끌어 갑니다. 이 상황은 이벤트 주도 아키텍처(Event-driven architecture)와 결이 같습니다. 또한, 애플리케이션을 도메인 이벤트가 주도하는 명령 부분과 조회 부분을 분리하여 CQRS(Command Query Responsibility Segregation) 패턴을 활용할 수도 있고, 더 나아가 도메인 모델의 상태를 변경 불가한 이벤트의 시퀀스로 대체하는 이벤트 소싱(Event sourcing) 패턴을 같이 적용할 수도 있습니다. 다음에 기회가 된다면 ML SaaS 플랫폼에 CQRS와 이벤트 소싱 패턴을 적용한 경험담을 공유하도록 하겠습니다.

DDD 전술 설계에는 다른 다양한 패턴들이 있습니다. 레포지토리, 값 객체, 도메인 서비스, 팩토리 같은 것들입니다. 이것들은 특별히 DDD가 아니더라도 접할 기회가 많고 애그리게이트와 도메인 이벤트에 비해 고민할 점이 크게 없어서 따로 언급하지는 않겠습니다.

 

소감

DDD는 원리, 원칙이고 패턴이면서 관행의 모음입니다. 사실 예전에는 DDD를 도메인의 복잡함을 해결해 설계에 반영해주는 “은 탄환(Silver Bullet)”처럼 생각했었습니다. DDD를 배우고 적용하면 설계가 쉽게 끝나고 구현을 시작하자는 생각도 잠깐 했던 것처럼요. DDD가 우리의 플랫폼 설계에 어떤 것도 정해주지 않고 어려운 개념들을 계속해서 쏟아내는 걸 보며 잠깐 실망했던 적도 있는 것 같습니다. 하지만 점차 DDD에서 요구하는 것들을 따르고 설계하면서 DDD는 툴이 아니고 설계 철학 같다란 생각을 먼저 하는 것 같습니다. 도메인의 복잡함은 해결할 것이 아니라 정복하는 것이란 생각도 들고요. 이런 방식 없이 설계했다면 아키텍처 부분 부분마다 일관되지 않은 베스트 프랙티스(Best Practice)를 계속해서 만들고 있었을 것 같습니다.

DDD 적용하면서 느낀 단점 또한 있었습니다. 초반 설계에 비용이 많이 든다는 점입니다. 물론 구성원이 DDD 패턴과 도메인에 익숙하지 않은 문제일 것 같습니다. “Domain First”보다 “System First”를 고수해 시스템 아키텍처를 먼저 구성하거나 데이터베이스 설계를 먼저 했다면 개발부터 시작했을 수도 있을 것 같네요. 물론 이 경우 도메인이 얼마나 복잡하게 스파게티를 이룰지 예상하기 힘들지만요. DDD에 익숙하지 않은 점이 개발 주기를 민첩하게 하는 데 방해되었고 따라서 DDD를 익히고 사용하는데 초반에 많은 시간을 소모하는 변명 같아 보이는 단점이 있다고 말하고 싶습니다.

 

마치며

지금까지 ML SaaS 프로젝트에 DDD를 적용하는 과정을 살펴보았습니다.

설계 결과가 좋다고 말하기는 어렵지만, DDD를 제대로 경험했다고 말할 수 있을 것 같습니다.

설계에 DDD를 도입한다면 글에 나온 과정이 도움이 되길 바랍니다.

 


참고 문헌

  • [1] Evans, Eric, and Martin Fowler. Domain-Driven Design Tackling Complexity in the Heart of Software. Upper Saddle River, NJ: Addison-Wesley, 2019.
  • [2] Vernon, Vaughn, and Eric Evans. Implementing Domain-Driven Design. Upper Saddle River etc.: Addison-Wesley, 2015.
  • [3] Vernon, Vaughn. Domain-Driven Design Distilled. Boston: Addison-Wesley, 2016.
  • [4] Millett, Scott, and Nick Tune. Patterns, Principles, and Practices of Domain-Driven Design. Indianapolis: John Wiley & Sons, 2015.
  • [5] Khononov, Vladik, and Julie Lerman. Learning Domain-Driven Design: Aligning Software Architecture and Business Strategy. Sebastopol, CA: O’Reilly Media, Inc, 2022.
카카오톡 공유 보내기 버튼

Latest Posts

제5회 Kakao Tech Meet에 초대합니다!

Kakao Tech Meet #5 트렌드와 경험 및 노하우를 자주, 지속적으로 공유하며 개발자 여러분과 함께 성장을 도모하고 긴밀한 네트워크를 형성하고자 합니다.  다섯 번째

테크밋 다시 달릴 준비!

(TMI: 이 글의 썸네일 이미지는 ChatGPT와 DALL・E로 제작했습니다. 🙂) 안녕하세요, Kakao Tech Meet(이하 테크밋)을 함께 만들어가는 슈크림입니다. 작년 5월에 테크밋을 처음 시작하고,