본문 바로가기

Architecture

레거시 시스템을 DDD(도메인 주도 설계) 기반으로 재설계한 이야기 1편

https://liasn.tistory.com/10

 

솔루션 업체 개발자에서 교육 스타트업으로 이직 회고(with. F-Lab 멘토링)

▶ 들어가며 코로나 때문에 불안한 취업 시장에서 퇴사와 이직을 결심하고 그 끝에 만족스러운 결실을 맺은 올해 상반기는 인생에서 가장 보람찬 시간이었다고 생각 든다. 솔루션 업체에서 신

liasn.tistory.com

나는 F-Lab이라는 교육 스타트업으로 이직을 했고

 

최근에는 레거시 시스템을 개선한 새로운 프로덕트를 오픈하였다.

 

레거시 페이지
새로운 프로덕트 웹페이지

 

이번 글에서는 기존 레거시 시스템을 Domain Driven Design을 기반으로 새롭게 바꿔 나가면서 겪었던 경험을 이야기하고자 한다.

 

 

그전에 DDD는 뭘까?

DDD(Domain Driven Design)는 비즈니스에서 나타나는 도메인의 프로세스, 규칙을 충분히 반영 및 표현하는 도메인 모델을 중심으로 개발하는 방향성을 말한다.

 

소프트웨어의 본질은 소프트웨어를 사용하는 사용자의 도메인과 관련된 문제를 해결하는 것이다.

현재 회사를 예로 들자면, F-Lab의 비즈니스는 좋은 개발자 부족이라는 거시적 문제를 해결하는 것이다. 그리고 F-Lab의 사용자는 사용자 개개인이 가지고 있는 "좋은 개발자로서의 성장 방향성 부재"라는 문제를 해결하기를 원하고 있다.

결과적으로 F-Lab에서 만드는 소프트웨어의 본질은 사용자의 "개발 학습 방향성"이라는 도메인 문제를 해결하는 것이 되는 게 바람직하다.

 

DDD 관점으로 접근해보자.

F-Lab의 비즈니스(좋은 개발자가 부족한 현상을 해결하는 것)에서 개발자 부족 현상이 왜 발생했는지 고민해보면 개발자를 교육하는 과정에서 제대로 된 교육 방향성이 지금까지 부재했다고 볼 수 있다. 그럼 우리는 F-Lab의 비즈니스에서 "개발자 교육"이라는 문제 도메인을 추출할 수 있다.

그리고 추출된 문제 도메인(개발자 교육)을 해결하기 위해 어떤 문맥(context)으로 나누고 걔네를 어떻게 조합하는지 이해관계자들과 협의하여 전략적으로 디자인해야 한다.

예를 들어, 개발자 교육(문제 도메인)을 해결하는 핵심 방법은 여러 가지가 있을 수 있다. 개발자 전용 강의 제공, 실제 현업의 문제로 문제 해결 경험 제공, 현업 실무자들과 학습 방향성을 어떻게 가져가는 게 좋을지 얘기하는 커피챗 서비스 제공 또는 멘토와 팀을 이뤄 멘토링을 진행하는 방법 등등

이렇게 여러 방법 중에서 이해관계자들과 협의하여 핵심 방법을 추출한다. F-Lab은 멘토와 멘티가 팀을 이뤄 멘토링을 진행하는 방법을 핵심 방법으로 추출했다. 그런데 단순히 멘토링만으로는 문제 도메인을 효율적으로 해결하기 힘들다. 왜냐하면 개발은 분야가 너무 다양하기 때문에 코스별로 특화된 멘토링을 제공해야 한다. 이렇게 멘토링 코스라는 도메인을 식별했다.

또 멘토링을 받는 사용자는 매우 다양하고 모든 사용자가 멘토링이라는 방법이 효과적인 건 아니다. 그래서 사용자가 멘토링을 잘 받을 수 있는 상황, 배경인지 파악하기 위해 신청서를 받는다. 멘토링 신청서라는 도메인을 식별했다.

이어서 신청서를 받고 사용자가 멘토링을 받기 적절한 상황이면 결제할 수 있도록 해야 한다. 그럼 또 결제라는 도메인을 식별했다.

 


위의 과정처럼 문제 도메인을 식별하고 이 문제를 해결하기 위해 여러 하위 도메인으로 나눈다. 이후에 개발자는 나누어진 하위 도메인을 해결책 공간으로 끌고 와 전략의 목표를 잘 달성할 수 있도록 여러 전술적 설계(Aggregate Pattern, Domain-Event 등등)를 활용해서 소프트웨어로 구현한다.

 

이 모든 과정이 Domain Driven Design이라고 할 수 있다.

굉장히 추상적일 수 있는데 맞다. 추상적인 개념이다. 그래서 이 내용에 대해서 내가 이해하고 있는 DDD를 조금씩 써 내려 가려고 한다.

 

우리 팀은 왜 DDD를 하려고 했을까? 

나와 CTO님이 F-Lab에 합류하기 전에는 개발자 출신 대표님이 혼자서 서비스를 개발하고 운영했었다. 혼자서 대표와 개발자로서 직무를 다하려고 하니 절대적인 리소스가 부족할 수밖에 없었다. 그리고 멘토링까지 직접 하셨으니 거의 쉬는 시간도 없었을 거다. 이러한 상황에서 서비스 개발에 깊이 고민할 시간은 더욱이 부족했고, 깊이 고민하지 못하는 상황에서는 시스템의 확장성이나 유지보수성보다는 지금 당장의 문제를 빠르고 신속하게 해결하는 것이 중요했다.

 

그렇게 서비스를 설계하다 보니 당연히 새로운 비즈니스 요구사항에 유연한 대처가 어려운 구조가 나타났고 이는 비즈니스가 성장함에 따라 발생하는 복잡한 요구사항을 시간이 갈수록 소프트웨어로 풀어내기 어려워짐을 의미했다.

 

비즈니스가 빠르게 성장하는 데 소프트웨어의 도움을 충분히 받지 못하면 300%만큼 성장할 수 있는 걸 200% 또는 그 이하만큼만 성장하는 게 되고 극단적으로 얘기하면 100%만큼 퇴보했다고 볼 수 있다.

성장판이 충분히 열려있는데 영양 공급이 부족해서 클 수 있는 만큼 못 큰다면 그만한 억울한 일이 더 있을까...?

 

이런 상황을 최대한 피하고 성장할 기회가 주어졌을 때 놓치지 않기 위해서는 비즈니스가 성장하면서 점점 복잡해지는 환경을 통제, 제어하는 게 좋다. 이때 DDD가 복잡성을 제어하는 좋은 열쇠가 될 수 있다.

 

마틴 파울러 선생님은 이런 얘기를 하셨다.

Domain-Driven Design is an approach to software development that centers the development on programming a domain model that has a rich understanding of the processes and rules of a domain.  .....  The approach is particularly suited to complex domains, where a lot of often-messy logic needs to be organized.

DDD는 도메인 프로세스와 규칙을 풍부하게 이해하는 도메인 모델을 프로그래밍하는 데 중심을 두는 소프트웨어 개발 접근 방식이다. .... 이 접근 방식은 복잡한 도메인에 특히 적합하다...

F-Lab 또한 비즈니스가 성장하면서 도메인이 점점 복잡해질 것이고 증가하는 복잡성을 조직적으로 잘 제어하기 위해서 DDD를 기반으로 재설계하는 것이 좋겠다는 결정을 했다.

 

비즈니스가 성장하면서 체감한 기존 시스템의 문제점

F-Lab은 초기에는 자바 백엔드 멘토링만 제공했었다. 그러다 사용자들이 프론트엔드나 안드로이드 같은 다른 분야의 멘토링은 오픈하지 않냐는 문의가 점점 늘어났고 우리는 비즈니스 확장을 위해 안드로이드, 프론트엔드 등등 다른 분야의 멘토링을 추가로 제공하기로 했다. 하지만 기존 시스템에서는 다른 분야의 확장성을 고려하지 않은 구조 때문에 새로운 코스에 대한 신청서를 구분할 수 없었다. 그리고 신청서 질문도 자바 백엔드에만 맞춰져 있어 사용자에게 각 코스에 맞는 질문을 제공할 수 없는 문제가 발생했다.

 

예를 들어, 자바 백엔드 멘토링 신청자에게는 자바에 대한 경험이나 객체 지향 프로그래밍, 스프링 프레임워크 경험에 관해 물어봐야 하고 프론트엔드 신청자에게는 javascript나 함수형 프로그래밍, React 경험에 관해 물어봐야 하는데 신청서 질문 자체가 자바 백엔드를 기준으로만 설계되어 있어서 각 코스에 맞는 질문으로 커스텀해서 제공하는 게 어려웠다.

 

멘토링 상품을 등록하는 부분에서도 비효율성이 존재했다. F-Lab의 멘토링 상품은 사전 구매하면 할인을 해주는 얼리버드 정책이 있다. 근데 이 얼리버드는 매월 말까지 적용되고 월이 지나면 상품 가격을 바꿔야 하는데 기존 시스템 구조로는 상품을 자동으로 변경하게 할 수 없었고 사람이 직접 상품 가격을 변경하여 말일이 넘어가는 자정에 기다리고 있다가 배포해야 변경된 가격으로 제공할 수 있는 구조였다.

 

초기 규모에는 손으로 직접 변경해서 배포하는 게 크게 어렵지 않다고 하더라도 사용자가 많아지고 더구나 구성원이 가격 설정에 대한 지식이 없으면 손해가 발생할 수 있는 상황이 연출된다.

 

상품을 관리하는 측면에서도 문제가 많았다. 이벤트를 진행하면 사용자별로 커스텀한 가격으로 상품을 제공해야 하는데 레거시 시스템에서는 커스텀 상품을 사용자별로 하나하나 만들어서 등록해줘야 했다. 이런 구조에서는 어떤 사람이 몇 월 상품을 결제했고 어떤 이벤트로 얼마만큼 할인받았고 실제 결제 금액은 얼마인지 추적하기가 어렵다. 

그리고 상품 환불 관련해서도 운영 매니저님이 직접 pg사 웹사이트에 접속하여 직접 환불 처리하고 환불 금액을 따로 문서로 정리해야 하는 구조였다.

 

다른 팀의 늘어나는 요구사항을 만족시키는 것에도 제약이 많았다.

예를 들어, 마케팅팀에서

  • "신청서를 작성한 사용자에게 F-Lab의 콘텐츠를 담은 이메일을 보내주고 싶어요."
  • "결제를 완료한 사람들에게 이러한 카톡 알림을 보내고 싶어요."
  • "12월 멘토링 구매 가능 횟수가 세 개 이하일 때 신청서를 작성하고 있는 사람이나 아직 결제하지 않은 사람들에게 12월 멘토링이 곧 마감된다고 문자나 알림을 보내고 싶어요."

이러한 요구사항이 들어오면 "지금 구조에서는 제공하기 어려워요.." 라는 말밖에 할 수 없었다.

 

이외에도 많은 문제점이 있었고 이런 일들을 체감할 때마다 비즈니스 도메인을 잘 정의하고 문제점을 식별하여 풀어내는 것이 매우 중요하구나를 깨달았다.

 

전략적 설계를 위한 이벤트 스토밍(Event Storming)

DDD에서 특히 중요한 부분을 전략적 설계이다. 즉, 복잡하고 큰 비즈니스 도메인의 문제를 어떤 문맥(Bounded-Context)으로 나누고 구성(organize)하여 걔네를 어떻게 조합하여 풀어나갈 건지를 전략적으로 설계해야 한다는 것이다. 큰 문제를 작은 문제로 나눠서 풀어나가는 분할 정복과 비슷한 맥락이라고 볼 수 있다.

 

그래서 우리 개발팀은 비즈니스 도메인의 문제점을 식별하고 전략적 설계를 위해 먼저 이벤트 스토밍을 진행하기로 했다.

 

이벤트 스토밍이란?

이벤트와 브레인 스토밍의 합성어로 비즈니스 이해관계자들이 모여 소프트웨어 영역에서 발생하는 도메인 이벤트를 식별, 도출해내는 기법이다.

 

F-Lab의 비즈니스에서 발생할 수 있는 이벤트, 예를 들어 멘토링을 받기 위해 사용자는 신청서를 작성해야 한다. 그러면 "멘토링 신청서 작성됨"과 같은 이벤트가 있을 수 있다. 또 신청서를 작성하기 이전에 사용자는 회원 가입이 필요하다고 하면 회원 가입을 진행할 거고 "사용자 회원 가입됨"이라는 이벤트도 도출할 수 있다. 이렇게 비즈니스 내에서 발생하는 이벤트를 쭉 그려 나가면서 이벤트를 식별하고 이것들을 바탕으로 맥락(Context)의 경계를 정하는 것까지 할 수 있다.

이벤트 스토밍 초기 과정

이벤트 스토밍의 또 다른 가치는 이해관계자들이 모여 진행하면서 서로 달랐던 비즈니스 관점들에 대해 논의하고 얘기하고 그 격차를 줄여나갈 수 있다는 것이다. 또 그 과정에서 DDD에서 중요한 유비쿼터스 언어(Ubiquitous Language)가 점점 확립된다는 것이다.

 

DDD가 도메인의 풍부한 의미를 소프트웨어에 충분히 담겠다는 거고, 그러기 위해서 개발자는 도메인 지식이 깊은 도메인 전문가들과 끊임없이 커뮤니케이션해야 하고 소통 과정에서 숨어있는 도메인 규칙, 도메인 로직을 찾아내야 한다. 이때 도메인 전문가와 개발자 간의 용어가 달라 버리면 문제가 발생할 수 있다. 

도메인 전문가는 소프트웨어 개발에 사용되는 기술적인 전문 용어를 이해하는 데 한계가 있고 개발자 또한 도메인 전문 용어를 이해하는 데 한계가 있으므로 이 둘 간의 간극을 메우면서 도메인의 풍부한 의미를 표현할 수 있는 유비쿼터스 언어를 구성해야 한다.

 

즉, 유비쿼터스 언어가 없는 상황에서는 다양한 이해관계자들이 자신들의 언어를 의사소통을 위해 서로 번역하는 문제가 발생하고 이는 비효율적인 커뮤니케이션 비용을 초래할 수 있다는 것이다.

 

여담이지만 나는 처음에 CTO님이 이벤트 스토밍을 하자고 했을 때 이걸 왜 하지? 라는 생각이 들었었다. 그때는 DDD의 가치와 중요성을 피상적으로 느끼고 있었던 시기라 그런지 이벤트를 식별함으로써 얻는 가치에 크게 공감하지 못했다. 시간이 지나서 지금의 나는 "내가 그때 왜 그런 생각을 했을까..?"라고 느껴질 정도로 이벤트 스토밍의 가치를 체감하고 있다.

 

아무튼 F-Lab 개발팀은 이벤트 스토밍으로 이해관계자들과 의사소통을 거쳐 이벤트를 식별하고, DDD의 전략적 설계에 중요한 "문맥을 어떻게 나눌지"를 파악했다.

예를 들어,

  • 사용자 회원 가입됨
  • 이메일 인증 요청됨
  • 암호 초기화 요청됨
  • 이메일 인증됨
  • 사용자 정보 변경됨
  • 멘토링 신청서 제출됨
  • 멘토링 주문됨
  • 멘토링 주문 결제됨
  • 결제됨
  • 결제 환불됨
  • 팀 매칭됨
  • 멘토링 시작됨
  • 멘토링 종료됨
  • 이메일 전송됨
  • SMS 전송됨
  • 알림 톡 전송됨

이러한 이벤트를 식별했다고 가정하면 이벤트를 트리거 하는 커맨드(Command), 시스템과 상호작용하는 주체인 액터(Actor), 애그리거트(Aggregate)도 도출할 수 있을 거고 문맥(Context)의 경계(Boundary)도 구분지을 수 있을 거다.

 

  사용자 컨텍스트 멘토링 컨텍스트 결제 컨텍스트 알림 컨텍스트
식별한 이벤트
  • 사용자 회원 가입됨
  • 이메일 인증 요청됨
  • 암호 초기화 요청됨
  • 이메일 인증됨
  • 사용자 정보 변경됨
  • 멘토링 신청서 제출됨
  • 멘토링 시작됨
  • 멘토링 종료됨
  • 멘토링 주문됨
  • 멘토링 주문 결제됨
  • 팀 매칭됨
  • 결제됨
  • 환불됨
  • 이메일 전송됨
  • SMS 전송됨
  • 알림 톡 전송됨

사용자 컨텍스트는 사용자 계정 관리에 대한 문제를 해결하는 공간이고 멘토링은 F-Lab의 핵심 비즈니스인 멘토링에 대한 문제를 해결하는 공간이다. 결제는 외부 PG사를 통해서 결제하는 문제를 해결하는 공간이고 알림은 수신자에게 보낼 알림(이메일, sms, push, kakaotalk 등등) 문제를 해결하는 공간이다. 우리는 나눠진 컨텍스트를 조합하여 더 크고 복잡한 문제를 풀어나가겠다는 거다.

 

이렇게 문맥(Context)을 나눠서 바라보면 레거시 시스템에서 만족시키기 어려웠던 요구사항도 어렵지 않게 구현할 수 있다는 느낌이 든다.

 

"결제를 완료한 사람들에게 이러한 카톡 알림을 보내고 싶어요."

 

이 요구사항은 멘토링 컨텍스트에서 "멘토링 주문 결제됨" 이벤트가 발생하면 알림 컨텍스트의 "알림톡 전송됨" 이벤트를 트리거 하는 "알림톡 전송 커맨드"와 통합시켜 구현 가능하다고 예측할 수 있다.

 

또 다른 이점은 개발자뿐만 아니라 개발과 전혀 관련 없는 이해관계자들도 시스템의 도움을 받을 수 있는지 없는지 스스로 판단하여 더 능동적인 업무 수행이 가능해진다는 점이다.

 

"신청서를 작성한 사용자에게 F-Lab의 콘텐츠를 담은 이메일을 보내고 싶어요."

 

위 요구사항이 마케팅팀에서 필요하다고 가정하면 마케팅팀은 "멘토링 신청서 제출됨" 이벤트가 발생하는 걸 확인하고 "이메일 전송 커맨드"와 통합시켜 F-Lab의 콘텐츠를 담은 이메일 전송이 가능하다는 걸 능동적으로 인지할 수 있다.

 

이렇게 이해관계자들은 이벤트 스토밍의 결과만으로 현재 도메인 내에서 발생되는 이벤트가 무엇인지 확인하고 특정 이벤트가 발생했을 때 어떤 작업을 수행할 수 있는지 스스로 판단하여 좀 더 능동적인 업무를 할 수 있게 된다는 의미다.


 

지금까지 우리 조직이 DDD를 하게 된 배경과 과정에 대해서 간단하게 살펴봤다.

 

요즘 느끼는 거는 개발자들 사이에서 DDD에 대한 관심이 날이 갈수록 높아지는 것 같은데 DDD가 만병통치약은 절대 아니라는 말을 하고 싶다. 단순히 핫한 키워드라서 채용 공고 우대사항에 DDD가 있어서 DDD를 시작하게 되면 정작 DDD의 본질에 대해서는 이해하지 못하고 코드로 구현하는 전술적 패턴에만 매몰되어 DDD의 진정한 가치를 추구하지 못할 수 있다.

 

계속 강조하고 싶지만, 코드로 구현하는 것도 중요하긴 한데 더욱더 중요하고 가치 있는 것은 비즈니스의 도메인 문제를 어떤 문맥(Context)으로 나누고 조합하여 풀어나가는지 고민하는 "전략적 설계"이다. 이 전략적 설계는 이해관계자들과 커뮤니케이션하고 지식 탐구 과정을 통해서 탄생하기 때문에 코드로 잘 구현하는 능력보다는 이해관계자와 충분한 의사소통으로 문제를 이끌어내는 능력이 더 중요하다는 것이다. (그렇다고 코드 구현 능력이 중요하지 않다는 뜻이 "절대" 아니다.)

 

이 글을 읽는 독자들도 내가 DDD의 진정한 가치를 이해하고 있는지? 다른 부분에 너무 매몰되어 있지는 않은지? 스스로 질문해보면 좋을 것 같다. 나도 맨날 질문하지만 대답하기 너무 어렵다...

 

 

다음 글에서는 DDD를 구현하는 전술적 패턴이 있는데 우리 조직에서 어떤 패턴을 사용했는지 코드의 아키텍처는 어떻게 구성했는지 조금 상세하게 살펴보겠다. 

 

 

 

 

 

 

 

 

 

'Architecture' 카테고리의 다른 글

서버의 확장? Scale up과 Scale out이란?  (0) 2021.02.09