투어&티켓 전체 리뉴얼 프로젝트

‘투어&티켓 전체 리뉴얼 프로젝트’

그림1

작년 하반기부터 올해 상반기까지, 기존 루비 기반의 시스템을 코틀린으로 전환하는 T&A 3.0 프로젝트를 진행했습니다.

이번 회고에서는 그 과정에서 느꼈던 경험과 배운 점을 기록하고자 합니다.

프로젝트 개요

저는 투어&티켓 개발팀에서 일하고 있으며, 해당 팀은 회사 내 가장 큰 영업 이익을 창출하는 중요한 부서입니다.

시스템은 10년 전 루비로 개발되어 운영되었으나, 시간이 지나면서 다양한 요구사항을 수용하지 못하고, 레거시 코드로 인해 불안정한 부분이 많아졌습니다.

결국 코틀린을 활용해 시스템을 재구축하는 T&A 3.0 프로젝트를 시작했습니다.

프로젝트는 2023년 10월에 시작해, 2024년 5월 정식 오픈에 성공했습니다.

현재는 안정화를 위해 지속적으로 운영 중입니다.

저는 초기 설계부터 참여해 루비 개발자들과 함께 아키텍처 설계를 담당했고, 이후 재고와 상품 등의 다양한 도메인 개발을 맡았습니다.

설계 과정에서 경험한 다양한 트레이드 오프들을 글로 기록합니다.

도메인 설계

도메인은 크게 캘린더, 상품, 옵션, 재고, 예약으로 구성되어 있으며, 파트너, 매니저, 여행자라는 바운디드 컨텍스트에 따라 설계되었습니다.

초반 설계시에 바운디드 컨택스트에 따라 파트너, 매니저, 여행자로 레포지토리를 분리하는 방식을 제안했습니다.

하지만 팀 내에 중복 코드에 대한 우려가 있었고, 이에 따라 단일 레포에서 코드를 구성하게됐습니다.

그러나 여행자와 파트너, 매니저의 서버가 달라야한다는 기술적 요구사항은 존재했습니다.

파트너, 매니저 서버의 장애가 여행자 서버까지 장애가 전파되면 안되기 때문입니다.

이에 따라, 단일 레포로 관리하면서 호스팅 서버를 분리했습니다.

멀티 모듈 아키텍처를 채택했고, api, application, domain, infra 모듈로 나누어 관리했습니다.

모듈 및 패키지 구조

각 모듈은 기능에 따라 분리했으며, 가장 바깥 패키지는 도메인에 따라 분리했습니다.

도메인간의 로직이 뒤섞이는것을 방지하고, 나중에 도메인별로 코드를 쉽게 분리하기 위해서는, 도메인별로 모듈을 분리하는것이 올바른 의사결정일 수 있습니다.

하지만 팀 내에서는 레이어를 기준으로 모듈을 나누는것이 익숙한 상태였기 때문에 레이어를 기준으로 나누게 되었습니다.

  • api 모듈은 실행 포인트를 담당하며, 여행자 서버와 파트너/매니저 서버를 분리하여 각각 독립적으로 운영되도록 했습니다.
  • application 모듈은 응용 계층으로, 기술적 설정과 스레드풀 설정 등도 서버별로 분리하여 구현했습니다.
  • domain 모듈은 핵심 비즈니스 로직을 담당하며, 여행자와 매니저/파트너가 공유할 수 있도록 공통 모듈로 관리했습니다.
  • infra 모듈은 외부 서비스와의 직접적인 연결을 담당하는 구현체들이 포함된 모듈입니다.

프로젝트를 진행하면서 얻은 교훈들

프로젝트를 거치면서 얻은 여러가지 경험들을 정리해봤습니다.

소통의 방법

개인적으로 프로젝트를 진행하면서 소통하는 방법을 제일 크게 배웠습니다.

일단 기술적인 미팅에서는 대부분 정답이 없습니다.

왜냐하면 미팅을 진행할 정도의 아젠다라면 트레이드 오프 관계가 있기 때문이죠.

즉 정답은 없고, 각각의 아젠다에 장단점이 존재하는것인데요.

이러한 경우, 위키를 활용해서 글로 적는것이 매우 도움이 된다는 사실을 알았습니다.

이야기로 주고 받을 경우, 각 아젠다의 장점에만 집중하거나 단점에만 집중하는 경우가 자주 있습니다.

이렇게되면 우리가 비지니스 임팩트를 위해 현재 무엇을 해야하는지 까먹기 쉽더군요.

그래서 글로 각 아젠다의 트레이드 오프로 얻는것과 잃는것 그리고 임팩트를 내기 위한 나의 의견을 정리하는 습관을 길렀습니다.

또한 사업부와의 소통에서는 문제의 근본에 접근하는 것이 중요합니다.

예를 들어서 특정 문제를 해결해는것을 요구했을때, 시스템 상에서 이를 받아줄 수 없는 경우가 자주 존재했습니다.

이 경우, 무조건 안된다라고 말하는것을 지양하며 문제의 근본에 접근해야합니다.

만약 정말로 어떻게해도 시스템에서 받아줄 수 없는 상황이라면, 문제의 근본에서부터 접근해서 다른 방법이 어떤것이 있을 수 있는지 제시해야합니다.

꼭 코드를 수정해서 해결하는 방법이 아니어도, 다른 해결 방법이 있는 경우가 실제로 매우 많았습니다.

레이어별로 잘게 쪼갠 모듈 구조의 단점

사실 초기 설계시에는, 사내에서 대부분 사용하는 구조라는 이유로 큰 생각없이 멀티모듈 구조를 선택했었습니다.

하지만 개발을 진행할수록, 초반에 레이어별로 잘게 나눈 모듈들이 원망스러웠습니다.

모듈별로 테스트 코드의 의존성 관리도 복잡해지고, 레이어를 옮겨갈수록 dto가 중복되어서 생성되므로 개발 생산성이 떨어진다는것을 알았기 때문이죠.

사실상 domain 레이어에서 클라이언트에게 필요한 dto를 만들어서 내리는 경우가 대부분이었는데, application, api 레이어를 옮겨가면서 dto를 두벌씩 만들어줬습니다.

여기에서 생성되는 버그도 많고, 개발 생산성도 매우 떨어졌습니다.

또한 어느정도 사이즈가 있는 프로젝트에서는 의존성의 방향을 강제할 수 있기 때문에 장점이 있습니다만, MSA에서의 작은 단위의 프로젝트에서는 큰 의미 없다고 생각이 들었습니다.

ACL

상품, 캘린더, 옵션 등 여러 도메인들이 존재하는데, 각각의 로직이 뒤섞이는게 바람직하지 않다고 생각했습니다.

그래서 “domain 모듈에서는 각자의 도메인 로직만 가져가고, 타 도메인의 데이터가 필요하면 application layer를 통해서 제공받자” 라고 팀 내에서 규칙을 정했는데요.

즉, application layer를 ACL(anti corruption layer)로 가져려고 했던것입니다.

장점으로는 도메인에 대한 코드 응집성이 높아지다보니 재활요할 수 있는 서비스 코드가 늘어났습니다.

단점은 아래와 같습니다.

application 레이어에서 다른 도메인의 데이터를 가져오도록 규칙을 정했기 때문에, 상품의 domain 로직 중간에서 옵션이 필요하다면 해결책은 두가지입니다.

  1. 애초에 옵션 도메인의 데이터를 미리 받아온 상태에서 상품 도메인 로직을 시작한다. -> 이렇게되면 코드 레벨 구현의 비지니스 흐름이, 실제와 달라지면서 복잡한 코드가 되는 경우가 존재합니다.
  2. 도메인 레이어에서 상품 도메인 로직을 수행하다가, 어플리케이션 레이어로 나가서 옵션의 데이터를 받아오고, 다시 도메인 레이어로 상품 도메인 로직을 수행한다. -> 간단한 코드가 이로 인해서 매우 복잡해집니다.

비대한 상품 도메인 모델

이커머스에서 가장 핵심인 도메인은 상품입니다.

그러다보니깐 상품에 너무 많은 필드들이 달라붙게됩니다.

그러다보니 상품 테이블의 필드가 30개가 넘어가고 매우 비대해지는 현상이 발생합니다.

당연히 상품 수정의 원인이 여러가지가되며 단일 책임 원칙을 위배합니다.

또한 kotlin에서는 JPA에서 one to one 관계에서 lazy loading을 지원하지않는 이슈가 있는데요. (이에 대한 포스팅은 여기에서 확인이 가능합니다.)

상품 단일 객체에 붙어있는 지연 로딩의 일대일 관계만 10개가 넘다보니, 자칫하면 10번의 쿼리가 추가 발생할 수 있었습니다.

지금에서는 생각해보면 책임을 잘 분리해서 상품 테이블을 여러개로 쪼개는 방법이 떠오르네요.

결론

상반기 평가

모든 이해관계자가 매일 11시까지 근무하면서 성공적으로 서비스를 오픈할 수 있었습니다.

팀원 단 한명이라도 없었다면 불가능했을 정도로 일정이 타이트한 프로젝트였고 그 만큼 배운것도 많고 뿌듯함도 큰 프로젝트였습니다.

제가 개발자로써 길을 걸으면서 진행했던, 반년 이상의 기간을 가지는 첫 프로젝트이었는데요.

미래에 제가 해당 포스팅을 보면서 추억에 빠질 수 있었으면 좋겠네요 .


© 2021. All rights reserved.

Powered by Hydejack v9.2.1