LY Corporation Tech Blog

LY Corporation과 LY Corporation Group(LINE Plus, LINE Taiwan and LINE Vietnam)의 기술과 개발 문화를 알립니다.

DDD를 Merchant 시스템 구축에 활용한 사례를 소개합니다

이제는 꽤나 대중적인 방법론으로 자리 잡은 DDD(domain driven design)는 도메인을 중심으로 소프트웨어를 모델링하는 데 중점을 둔 설계 접근 방식입니다.

저는 DDD가 이전의 방법론들과 크게 다르지 않다고 생각합니다. 그간의 설계 방식은 개발하고자 하는 서비스의 역할과 책임을 명확히 규정해 개발과 유지 보수 관점에서의 편의를 얻을 수 있게끔 발전해 왔는데요. DDD 또한 이 맥락에서 크게 다르지 않게 서비스의 역할과 책임을 어떻게 잘 정의할 수 있는지 구체적인 방안을 제시한 방법론이라고 생각합니다.  

ABC Studio의 개발 팀은 DDD를 활용해 소매 상품 판매를 위한 새로운 Merchant 시스템을 론칭했습니다. 흔히 DDD를 활용해 개발할 때 이를 어떻게 구현할지에 집중하는 모습을 볼 수 있는데요. 이와 다르게 저는 이 시스템을 론칭하는 과정에서 구현이 크게 중요한 부분이 아니라고 느꼈습니다.

저는 ABC Studio에서 백엔드를 담당하고 있는 김민섭이고, 이 글을 통해 DDD를 활용해 새로운 Merchant 시스템을 개발한 과정과 느낀 점을 공유하고자 합니다.

새로운 Merchant 시스템 소개

본격적으로 DDD를 활용한 과정을 다루기에 앞서 새로운 Merchant 시스템이 어떤 시스템인지 이해할 필요가 있습니다. 

기존 시스템은 주로 음식 배달 서비스를 위한 시스템으로, 저희는 이를 기반으로 소매 상품 판매 서비스도 전개하고 있었는데요. 그렇다 보니 소매 상품 판매에 적합한 형태로 서비스를 구성하지 못하고 소위 ‘휴지를 파는 음식점’의 형태로 판매가 이뤄져 왔습니다. 이와 같은 문제 때문에 서비스하면서 여러 한계에 봉착했고, 이를 개선하기 위해 기존 시스템과 독립적인 Merchant 시스템을 개발하기 시작했습니다.

새로운 Merchant 시스템의 특징은 소매 상품 판매에 특화된 플랫폼으로 기획해서 기존 시스템의 서비스에서뿐 아니라 다른 서비스의 판매 면에서도 물건을 판매할 수 있도록 설계했다는 것입니다.

구체적으로 말씀드리자면, Merchant 시스템에서는 점포와 상품, 재고 등 소매 상품 판매에 필요한 정보를 제공하고, 각 판매 면에서는 이 정보를 전달받아 사용자에게 판매를 진행하는 구조입니다. 판매 면에서 상품이 판매되면 해당 주문 정보는 Merchant 시스템으로 전달되고, Merchant 시스템은 이를 점포에 전달해서 상품을 준비해 사용자에게 배달할 수 있게끔 주문을 제어한 뒤 그 상태를 다시 해당 서비스의 판매 면으로 전달합니다. 

Merchant 시스템 내부에서는 판매 면을 '소비자 플랫폼(Consumer Platform)'이라고 정의했는데요. 아래와 같이 Merchant 시스템에서 소비자 플랫폼에게 점포나 상품과 같은 판매에 필요한 정보를 제공하면, 소비자 플랫폼에서는 이 정보를 바탕으로 사용자에게 상품을 전시하고 판매합니다. 

Merchant 시스템 판매 흐름

소비자 플랫폼에서 판매된 주문 정보는 Merchant 시스템으로 전달되고, Merchant 시스템은 이를 다시 점포와 배달원에게 전달해 사용자에게 상품이 배달되도록 합니다.

Merchant 시스템 배송 흐름

도메인 정의

앞서 설명한 바와 같이 Merchant 시스템은 상품 판매에 필요한 정보 생성 및 제공까지만 담당하고, 이후 실제로 소비자에게 판매하는 영역은 소비자 플랫폼으로 위임합니다. 그렇기 때문에 일반적인 전자 상거래에서 정의하는 도메인의 상당 부분을 덜어내고, 상품을 판매하기 위한 핵심/공통 정보만을 다루는 범위로 도메인을 정의하고자 했습니다.

프로젝트 초기에는 쿠폰이나 할인 등을 다루는 프로모션 도메인도 이 시스템에 포함되기도 했는데요. 어떤 부분을 위임하고 어떤 부분을 담당하는 것이 효율적일지 많은 논의를 거친 끝에 다음 다섯 가지 도메인으로 Merchant 시스템의 영역이 정의됐습니다. 

  • 점포(shop)
  • 상품(item)
  • 카테고리(category)
  • 재고(inventory)
  • 주문(order)

DDD를 활용한 서비스 구성

아래 그림은 DDD를 활용해 서비스의 각 시스템을 구성하는 시스템 아키텍처부터 전체 서비스 아키텍처를 구성한 과정을 간략하게 나타낸 그림입니다. 

전체 서비스 아키텍처

각 부분이 어떤 과정을 거치며 위와 같이 발전해 나갔는지 살펴보겠습니다.

시스템 아키텍처 구성

시스템을 구현하는 개발 아키텍처는 클린 아키텍처를 따르도록 구현했습니다. 

Merchant 시스템 아키텍처

'왜 클린 아키텍처를 따랐냐'고 물어보신다면 따라 할 때 참고할 수 있는 책이 얇았기 때문입니다. 🙂 물론 반 농담 삼아서 이야기한 것인데요. 나머지 반의 관점에서는 실제로 이 점이 중요했습니다. 우리 모두가 DDD의 전문가는 아니며, 앞으로 새로 합류할 인원 또한 그렇지 않을 가능성이 높습니다. 따라서 시스템을 구현하는 방법은 모두가 쉽게 이해하고 따를 수 있는 방법이어야 하는데요. 얇은 책 한 권을 읽고 따라 할 수 있다는 것은 클린 아키텍처를 선택하게 만든 주요 장점 중 하나였습니다.

클린 아키텍처의 중심에는 도메인 엔티티가 있고, 도메인 엔티티에 접근할 수 있는 유스케이스를 통해 외부 인터페이스 어댑터를 구현합니다. 여기서 중요한 점은, 클린 아키텍처에서 계층 간 의존성은 내부 방향으로만 허용하기 때문에 도메인 엔티티에서는 외부 환경에 영향을 주지 않고 도메인 코드를 모델링할 수 있다는 것입니다. 따라서 클린 아키텍처는 DDD를 적용하기에 적합한 아키텍처로 평가받고 있습니다. 

클린 아키텍처

시스템 모듈 구성

각 도메인 서비스는 각자의 역할에 맞춰 여러 개의 모듈로 구성해 서비스합니다. 여기서 모듈은 크게 도메인의 비즈니스 로직을 처리하는 '엔진'과 외부 요청을 받는 'API'로 구분됩니다. 

Merchant 시스템 모듈

이와 같이 구성한 이유는 시스템의 가용성과 확장성을 확보하고, 외부 시스템의 요청을 받는 부분과 도메인 로직을 처리하는 부분을 레이어로 분명히 구분해서 모듈 개발과 유지 보수 관점에서 편의를 얻기 위해서입니다.

API는 프로토콜의 변화를 명확히 공유할 수 있도록 gRPC로 제공했고, API와 엔진 간 요청 전달에는 Apache Kafka를 이용했습니다. 또한 엔진 측에서는 Kafka의 작업 큐 라이브러리인 Decaton을 적극 사용해서 파티션을 적게 사용하면서 처리량을 높였습니다.

흐름을 살펴보면, API로 들어온 요청은 내부 토픽을 통해 엔진으로 전달되며, 엔진은 전달받은 요청을 처리해 도메인 이벤트를 이벤트 토픽으로 발행해서 다시 이를 필요로 하는 외부 도메인으로 전달합니다.

Merchant 시스템 모듈 흐름

각 도메인은 모두 위와 같은 구조를 동일하게 구현해 아래와 같은 구조로 외부에 서비스를 제공합니다.

Merchant 시스템 모듈 전체 흐름

위 구조는 모든 구간이 비동기로 설계돼 있습니다. 따라서 전체 시스템이 빠른 API 응답 능력과 요청한 작업이 언젠가는 처리될 것이라는 최종 일관성(eventual consistency)을 기대하는 경우에는 적합하지만, 요청을 즉각 처리해서 그 결과를 바로 응답해야 하는 시스템에는 적합하지 않습니다. Merchant 시스템은 플랫폼으로 설계하고 있었기 때문에 많은 요청을 받아 빠르게 응답하는 능력이 우선 필요했으며, 대부분의 상황에서 도메인 작업을 즉각 처리하는 것은 필요하지 않았기에 위 구조를 사용할 수 있었습니다.

서비스 아키텍처 및 팀 구성

소비자 플랫폼은 일본 내 서비스이기 때문에 일본 팀과 협업해야 했습니다. 협업은 앞서 발행한 글로벌 협업의 4가지 패턴에서 언급된 바와 같이 핵심 도메인 개발은 한국의 팀에서, 현지 시스템 연계는 일본의 팀에서 담당하는 구조로 진행됐는데요. 한국 팀에서 생각하는 핵심 도메인의 역할과 책임을 일본 팀에게 공유하면서 핵심 도메인은 외부 환경에 영향을 받지 않아야 한다는 공감대를 형성하는 과정을 거쳤습니다. 이 과정을 통해 서로 확인한 역할에 따라 전체 서비스의 아키텍처를 대략 아래 그림과 같이 구성했습니다.

Merchant 시스템 서비스 아키텍처

서로 공감대를 형성하는 과정에서 각 도메인의 역할이 조정되는 등의 결정 사항들이 도출되는데요. 저희는 이를 ADR(Architectural Decision Record)을 사용해 남겨뒀습니다. 

ADR은 구축하려는 소프트웨어 아키텍처의 중요한 측면에서 내려진 결정을 기술하는 문서입니다. 이 문서를 작성한 이유는, 팀 내에서 내린 결정은 별도 문서로 남기지 않아도 잘 전달되는 편이지만 조직 간에 논의해 결정한 사항들은 제대로 전달되지 않아 회의 후 시간이 지나면 ‘그때 회의에서 이렇게 하는 것으로 했던 것 같은데?’라고 생각하게 되는 경우가 많기 때문입니다. ADR에는 딱히 정해진 규격이 없기 때문에 '어떠한 논의를 했고, 이 논의가 왜 필요했으며, 논의 끝에 어떤 결정을 했는지'를 간략하게 작성해 문서로 남겨두는 것으로 충분합니다.

서비스 아키텍처를 결정한 뒤에는 이에 따라 팀을 구성했습니다. 한국의 팀은 핵심 도메인 개발을 담당하는 'Core 팀'으로 구성하고, 일본은 소비자 플랫폼과의 연계를 담당하는 'Link 팀', 앱과 관리자 기능을 위해 백엔드 서비스를 제공하는 'BFF 팀', POS 연계를 위한 'Merchant Link 팀'으로 구성해 각자의 도메인 역할에만 충실할 수 있도록 구성했습니다.

이와 같이 각자의 역할을 명확히 정의한 후 시스템을 구성하고, 이에 맞게 아래와 같이 팀을 조직해 다른 도메인의 영역을 침범하는 실수를 줄였습니다.

Merchant 시스템 팀 구성

마치며

새로운 Merchant 시스템은 위와 같은 과정을 거쳐 예정된 시기에 잘 론칭됐고, 현재까지 별다른 문제 없이 잘 발전해 나가고 있습니다. 저는 이 과정을 되짚어 보면서 시스템 아키텍처와 모듈 구성, 전체 서비스 아키텍처 및 팀의 모습이 콘웨이의 법칙과 닮아 있다는 사실을 깨달았습니다. 전체의 중심에 핵심 도메인이 자리하고, 핵심 도메인의 바깥에는 이를 추상화하고 외부와 연계하는 계층이 존재해 각자의 역할에만 충실할 수 있게끔 구성돼 있다는 것입니다. 이와 같은 구성 덕분에 처음에 저희가 의도한 바와 같이 여러 다른 소비자 플랫폼이 등장해도 핵심 도메인의 변화는 최소화되고 연계 계층에만 영향이 미칩니다.

콘웨이의 법칙

시스템을 설계하는 모든 조직은 그 조직의 소통 구조를 닮은 시스템을 설계할 것이다.

흔히들 DDD에 대한 경험을 이야기해 보라고 하면 ‘헥사고날이 …’로 시작해 기술 관점의 이야기만 공유되는 경우가 많습니다. 하지만 저희는 이번 사례를 겪으면서 시스템 아키텍처와 같은 기술 관점의 경험도 중요하지만, 도메인의 역할과 책임을 논의해 결정한 뒤 팀 간에 공감대를 형성해 이를 바탕으로 조직을 구성하는 것도 꼭 필요한 의미 있는 경험이라고 느꼈습니다. 저희는 이 경험을 활용할 또 다른 도전을 기대하고 있는데요. 저희의 경험이 이 글을 읽고 계신 독자분들에게도 도움이 되기를 바라며 이만 마치겠습니다.