새소식

인기 검색어

개발일기

쇼피파이 모듈식 모노리스 전환 사례

  • -

사내에서 책 마이크로서비스 아키텍처 구축 스터디를 하고 있습니다. 이 책에서 나온 쇼피파이의 사례를 조금 더 찾아보고 사내에서 공유했는데 이때 자료를 올려봅니다

https://m.yes24.com/Goods/Detail/119319406

개요

쇼피파이는 이커머스 기업중 하나인데, 우리가 참고하기 좋은 구조겠다 싶어서 간단하게 요약도 해보고 참고 자료들도 일부 가져와 봤습니다. 각잡고 번역한 것도 아니고 영어 실력이 조악해서 틀릴 수 있으니 양해 바랍니다…

쇼피파이는 당시 직원 수가 4천 명이고, 판매자 수가 80만 명에 달하는 규모의 회사인데, Ruby on Rails로 구성되어 있습니다.

  • (지금은 직원 수 9천명 조금 넘는 정도 되네요)
  • (이때 당시 기록을 보니까 개발자는 한 1천명 정도 되는 규모였던 것 같습니다)

기존 모놀리스 장점

  • 코드가 한 곳에 있음
  • 테스트 / 배포 파이프라인이 하나
  • 모든 곳에서 데이터를 사용할 수 있음
  • 인프라 세팅이 간편
  • 다른 컴포넌트 호출시 API를 통해 호환성 체크할 필요 없음

기존 모놀리스 단점

  • 새로운 코드가 어디에 영향을 끼칠지 모름
    • 세율 계산하는 방법에 대해 변경했더니 배송료가 변경되는 현상이 발생하는데, 이유를 명확하게 알 수 없습니다
  • 코드베이스 전체에 대한 이해가 필요
    • 단순 변경임에도 불구하고 더 많은 컨텍스트를 필요로 합니다.
    • 배송 팀에 있는 개발자가 배송 뿐만 아니라 청구 코드, 주문 생성 등을 이해해야만 관련 코드를 변경할 수 있습니다
  • 테스트가 느림
    • 테스트 속도가 너무 느려서 많은 사람들이 로컬에서 모든 테스트를 수행하지 않고, 여러 곳에 로직이 얽혀있어서 테스트 작성에도 생산성이 저하되는 문제가 있었음
      • (제 경험으로 빗대면 요런 경험 같습니다. 사용자 마이프로필에 있는 주문 내역 관련 기능 수정에 대한 테스트 작성인데, 이때 그 주문에 있는 표카, 전카, 전시 범위 / 타입, 마켓 정보, 배송 상태, 주문 상품 건수, goods_link 등에 대해서 엮여 있어서 테스트 코드 자체는 10줄 이내인데 테스트 조건 작성에만 100줄 가량 작성한 적이 있습니다. 그럼에도 테스트가 원하는대로 동작하지 않아 테스트 조건 찾아보느라 코드 베이스를 계속 검색하고 이런 경험이 있었습니다.)

이 모든 것이 서로 다른 기능 간의 경계가 명확하지 않아 발생한 경계라고 정의했고, 서로 다른 도메인 간 결합을 줄이는 것에 집중했다고 합니다.

MSA를 선택하지 않은 이유

  1. 각각의 서비스들에 대한 여러 다른 테스트들과 파이프라인을 설정하는데 인프라 오버헤드를 겪어야 함
    1. 그렇다고 해서 필요한 데이터에 항상 액세스할 수 있는 것도 아닙니다 (네트워크 문제라던지…)
  2. 이미 배포된 서비스들에 대해서 db를 공유하기 쉽지 않음
  3. 다른 서비스들 간 네트워크한다는 건 결국 레이턴시를 증가시키겠다는 의미
  4. 보안적 취약점이 발견되면 모든 서비스 전부 다 뒤져서 업데이트 해야함
  5. 배포를 조율해야 하는 상황이 되면 일이 복잡해짐

MSA가 틀렸다는 것은 아니고 단지 쇼퍼파이에 어울리지 않았을 뿐. 코드베이스를 전환하려는 목적은 비즈니스에 대해 점진적 기능 향상하고 싶어서였기도 하고, MSA로 전환하면 얽혀있는 구조를 싹다 갈아 엎어야 했기 때문에 적합하지 않다고 판단했다고 합니다.

 

대신 모든 코드를 같은 코드베이스에 유지하면서 엄격한 경계를 설정해 각 도메인의 결합도를 낮추는 모듈식 모노리스 방식을 채택하기로 했습니다.

 

모듈식 모노리스는 모든 코드가 단일 애플리케이션을 구동하고 서로 다른 도메인 간 엄격하게 경계가 설정된 시스템을 말합니다.

 

이를 선택한 건 모놀리스와 MSA의 장점을 모두 얻고자 함이였고, 배포 단위를 늘리지 않으면서 모듈을 늘릴 수 있는 것을 목표로 했습니다.

 

약 2년간의 모듈식 모노리스 적용 과정

 

1. 코드 재조직

  • sw 개념이 아닌, 현실 세계 개념을 반영할 수 있도록 변경했습니다
  • 변경하기 이전에는 모든 루비 클래스가 약 6천개에 달했고, 이들을 모두 스프레드 시트들에 나열해서 어떤 컴포넌트에 속하는지 직접 라벨을 붙여가며 정했습니다.
  • 이 작업은 위험할 수 있었기 때문에(단순 파일 위치 변경이더라도 import, 객체 정의 등의 이유로 런타임 에러가 발생할 수 있음) 로컬 / 스테이징 / CI에서 테스트를 많이 작성해서 누락된 부분이 없나 확인했습니다.
  • 이때 업무중인 다른 개발자들을 방해하지 않기 위해 PR 하나로만 작성해 (빅뱅 PR) rebase하기 간편하게 만들었습니다
  • 이때 각 분야의 관계자들이 모여 조사 / 의견을 내어 조정했습니다.

2. 의존성 격리

  • 각 컴포넌트는 공용 API를 통해 도메인 경계를 전용 인터페이스를 사용했고, 관련 데이터에 대해 독점적으로 사용할 수 있도록 만들어 의존성을 격리시켰습니다.
    • (즉, 각 컴포넌트들은 다른 코드베이스에 대한 "전역적" 권한이 없다고 할 수 있습니다)
  • 따라서 모든 개발자들은 자기 조직의 컴포넌트에 대한 책임만을 가지고 있는 구조로 전환했습니다

  • 이때 도메인 경계를 넘나드는 걸 체크하기 위해 wedge라는 툴을 직접 만들어 사용했습니다
  • 도메인 경계를 가로지르는 코드를 사용하면 웨지는 점수를 게산해서 컴포넌트별 위반 사항을 나열한다고 합니다. 이걸 CI에 적용하기도 하고, 이 점수를 특정 날짜까지 몇%까지 낮춰오세요 이런 식으로 점진적으로 추적했다고 합니다
    • ex) public API 외에 다른 방법으로 다른 컴포넌트에 접근하는 경우
    • 컴포넌트들 간 호출되는 부분들을 모아서 Wedge로 전달하면 이러한 cross-component(호출, 연결(active record), 상속)중 어떤 것이 정상이고 위반인지 판단했다고 합니다. 일반적으로 다음과 같았다고 합니다
      • 교차 컴포넌트 연결은 항상 컴포넌트화를 위반합니다
      • 호출은 명시적인 public (API)일 때만 괜찮습니다
      • 상속도 비슷하지만 아직 덜 구현된 상태라고 합니다. (무슨 말인지 정확히 모르겠네요…? 아마 wedge가 아직 상속에 대해서는 잘 체크하지 못한다 이런 의미로 받아들였습니다.)

3. 경계 강제

  • 격리가 100% 보장되어야 할 수 있는 단계
  • 다른 컴포넌트에 접근하면 런타임 에러 / 테스트 실패 나도록 했다고 합니다. (빡세네요…)
  • 종속성을 명시적으로 작성하니 의존성 그래프를 작성하고 시각화할 수 있었다고 합니다. 순환 종속성을 제거하는 데에도 도움이 되었다고 합니다

2.의존성 격리, 3.경계 강제 step을 지나고 나니 필요시 코드베이스에서 격리하는 게 쉬워졌고, 컴포넌트 유연한 변경이 가능해졌다고 합니다.

 

좋은 설계는 진화하는 시스템이라고 생각하기 때문에 MSA로 전환하고 싶다면 모듈식 모놀리스로 먼저 전환하고나서 전환하는 편을 추천한다고 합니다.

  • 리팩토링과 재설계 하면서 비즈니스 도메인에 대해 지속적으로 더 많이 배우고, 도메인 전문가를 갖추게된 이후에야 MSA 설계하는 것이 적합하다고 하네요

출처: https://martinfowler.com/bliki/DesignStaminaHypothesis.html

(Wedge 매우 흥미로워서 어떻게 만들었는지 살펴보려고 했는데 공유는 안 되어있는 것 같네요 😢)

 

관심이 있으면 다음 자료들을 추천

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.