안녕하세요. 오늘의집 커머스 서비스의 Tech Lead를 맡고 있는 지노입니다.
빠른 속도로 발전해 온 오늘의집은 보다 안정적이고 빠른 확장을 위해 모놀리식 아키텍쳐에서 MSA로 전환을 시작 했는데요. 이번 편에서는 마이크로서비스로 나아가기 위해 백엔드를 분리하는 과정에서 어떠한 일을 했는지 간략하게 소개해 드리고자 합니다.
짧은 글에 저희가 고민했던 수많은 이야기들을 모두 담을 수는 없겠지만, MSA 전환을 준비하고 있고 어디에서부터 어떻게 시작해야 할지 고민 중인 분들께 조금이나마 도움이 될 수 있었으면 좋겠습니다.
마이크로서비스를 위한 준비
타사 사례 분석
저희가 가장 먼저 시작한 것은 타사의 전환 성공 사례를 찾아보는 것이었습니다. 구글링을 통해 타사의 전환 사례를 쉽게 접할 수 있었지만 "그래서, 어떻게...?"라는 질문에 답을 찾기는 어려웠습니다.
"API Gateway는 어떤 제품을 써야 할까?"
"우리에게 Internal API Gateway가 필요한가?"
"서비스별로 VPC를 나누어야 하나?"
"Service Mesh도 도입하고 싶은데..."
"상품 가격은 상품 서비스인가? 가격 서비스인가?"
많은 분들이 정성스럽게 써주신 전환 사례로 우리가 고민하고 있는 모든 것 들을 해결할 수는 없었습니다. 결론적으로 타사 사례에서 얻을 수 있었던 가장 큰 성과는 우리가 가야 하는 목적지에 대한 대략적인 구조(Architecture)를 그린 것이었습니다.
브레인스토밍
- 적은 수의 팀원이 다수의 서비스를 담당하게 되는 문제를 만들지 않아야 합니다. 서비스가 커지면 그때 다시 나눕니다
- Bounded Context를 명확히 한다면 서비스를 나누는 것은 어렵지 않을 것입니다.
- 유사하지만 다른 도메인이 하나의 서비스에 존재하는 경우에는 패키지 구조를 분리하여 경계를 명확하게 합니다.
도메인을 분석하고 경계를 명확하게 하기 위해 팀원들 모두 의식의 흐름에 따라 브레인스토밍을 시작했습니다. 코시국에 오프라인 미팅을 자주 갖기는 어려웠고, 코드장인 윌리 님의 추천으로 Miro를 이용 오프라인보다 더 편리하게 도메인을 정의하고 나눌 수 있었습니다.
기술 스터디
마음은 이미 마이크로서비스를 개발하고 있었지만 손이 따라주지 않았습니다. 오랜 기간 Ruby on rails를 기반으로 성장해온 오늘의집 시스템에 각기 다른 회사에서 서로 다른 경험을 가지고 모인 사람들이 하나의 전략을 정하고 빠르게 움직이는 것은 쉬운 일이 아니었습니다.
팀원들 간의 경험과 지식 수준 차이도 문제였습니다. 모두가 비슷한 수준의 지식을 갖기 위해 스터디를 시작했습니다.
비즈니스 업무도 수행하면서 매주 4시간 정도의 근무시간을 투자하여 같이 보기 시작한 책만 해도 5~6권이 넘었던 것 같습니다. [마이크로서비스 도입 이렇게 한다], [Microservice Pattern], [Enterprise Integration Pattern], [코틀린 마이크로 서비스 개발] 등의 책을 읽고 이해를 공유하며 자연스럽게 논의를 이어 나갔습니다. (스터디 장인 콰지 교수님 감사합니다.)
물론, 우리가 찾는 정답이 꼭 책에 있다고 생각하지는 않습니다. 다만, 책을 통한 간접경험으로 MSA 경험이 없는 팀원들도 자유롭게 토론에 참여할 수 있게 되었고, MSA Phase 1. 에 필요한 전략과 구조는 스터디를 진행하 완성할 수 있었습니다.
현재 MSA 준비를 위한 스터디는 마무리되었지만 ‘클린 아키텍쳐’, ‘스프링 5를 활용한 리액티브 프로그래밍’ 등을 공부하고 있으며, 새로 만들어진 마이크로서비스의 아키텍쳐를 잘 만들고 유지하기 위해 여전히 스터디 중입니다.
실행 가능한 목표
비즈니스 업무를 멈추고 MSA 전환에 집중할 수 있도록 우리에게 주어진 시간은 3개월이었습니다. 하고 싶은 일이 너무 많았지만 이 기간동안 가장 임팩트 있는 일을 찾아 진행 하는 것이 중요했습니다.
먼저 가장 임팩트가 크다고 생각되는 목표 3가지를 정했습니다.
- 커머스, 콘텐츠, O2O팀의 배포 독립성을 확보합니다.
- 서비스 간 의존성을 제거합니다. 타 팀에서 필요로 하는 API는 1순위로 개발합니다.
- API Gateway 내부 통신은 gRPC로 통일합니다.
빠른 결정과 실행을 위한 몇 가지 원칙도 세웠습니다.
- 진행하다 멈추면 안 한 것만 못합니다. 도전의식은 잠시 내려놓고 현실에 집중합니다.
- 분리 과정에서 발견한 코드 개선이나 버그 수정은 진행하지 않습니다.
- 애매하면 (슬랙)허들을 키고 이야기합니다.
MSA 집중 기간 동안 컨트롤 타워 역할을 하는 리더는 없었습니다. (아... 내가 했어야 하는 거였나?😅) 새로운 문제를 발견하면 팀원 모두가 주도적으로 이야기하고, 원칙을 정하고, 공유하며 다시 진행했습니다.
마일스톤
각 팀의 상황에 맞춰 동시에 개발을 진행해야 하지만, 3개월이라는 일정이 정해져 있는 만큼 일정 관리를 위한 최소한의 지점만을 정하는 것으로 마일스톤을 정했습니다.
언제까지 무엇이 완료되어야 다음 진행이 가능한지 마일스톤을 정하고, 마일스톤을 기준으로 각 팀에서 세부적인 플래닝을 진행했습니다. 2주 단위의 스프린트 플래닝, 매일매일 진행하는 데일리 스크럼을 통해 팀 내에서 이슈를 해결하고, 해결이 어려운 부분은 전체 팀에 공유하여 다시 해결책을 찾아냈습니다. 물론 이 과정에서 일부 기능 개발을 제외하거나 마일스톤의 Due Date를 변경하기도 했습니다.
Tech CoP
우리팀 1년 차 개발자 멀린 님이 특정 기능을 모듈화해서 모두의 개발 시간을 단축할 만한 부분을 찾았습니다. 빠르게 프로토 타이핑하고 분리된 라이브러리로 개발을 시작했습니다. 2주에 한번 씩 정기적으로 진행하는 Tech CoP를 통해 오늘의집 개발자 모두에게 공유하기에는 너무 늦을 것 같다는 의견이 있어 임시 Tech CoP를 소집하였습니다.
▶오늘의집 개발문화 Tech CoP 자세히 보기 (클릭)
기능에 대한 설명, 구조, 사용법 등을 공유하였고 결과적으로 모든 팀에서 해당 모듈을 사용하고 있습니다. 모듈에 대한 자세한 내용은 추후 게재될 멀린 님의 포스팅으로 확인하실 수 있을 것입니다.
gRPC + Mortar + Armeria
마이크로서비스의 통신 프로토콜은 gRPC를 사용하기로 했습니다. Protobuf를 사용하기 때문에 성능에 대한 이점도 있지만 무엇보다도 Polygrot 프로그래밍 환경에서의 장점이 많기 때문입니다. (오늘의집 개발팀은 루비, 코틀린, 고, 파이썬 등의 언어를 사용하고 있습니다.)
오늘의집 개발팀 에이스 로버 님이 개발한 Mortar와 라인에서 개발한 Armeria를 같이 사용하여 서비스 구축, Stub 생성, 배포, 문서화 그리고 테스트까지 간편하게 해결할 수 있었습니다.
마이크로서비스 열차 탑승
긴 준비 과정을 마치고 드디어 개발을 시작하였습니다. 지금부터는 마이크로서비스로의 스타트를 끊기 위해 저희가 사용한 전략을 소개합니다.
Branch By Abstraction
기존 레거시 시스템과 구버전 앱을 유지하면서 새로운 시스템으로 교체하기 위한 방법으로 추상화된 브랜치 전략을 사용하기로 하였습니다. (*참고 Branch By Abstraction Martin Fowler)
우선 레거시 시스템에 대한 추상화를 진행하고 마이크로서비스를 개발합니다.
Aggregator는 기존과 동일한 Response를 유지하면서 타 서비스와의 의존성을 제거하는 역할도 수행합니다.
집들이와 같은 콘텐츠 서비스에서 커머스 서비스의 상품 정보를 조회하거나, 상품 상세페이지에서 유저들의 스타일링샷을 제공하는 기능이 좋은 예제입니다. (여기까지가 MSA Phase 1. 백엔드 분리작업입니다.)
새로운 마이크로서비스를 개발할수록 레거시 시스템의 영역은 점점 줄어듭니다.
새로운 버전의 앱/웹에는 페이지 별로 BFF(Backend For Frontend) 패턴도 적용합니다. BFF는 Gateway 내부의 Aggregator를 사용하지 않고 마이크로서비스와 직접 통신합니다.
모놀리식 아키텍쳐에서 마이크로서비스 아키텍쳐로 전환이 완료됩니다.
Legacy Service
- 서비스별 의존성 분리 작업을 진행합니다.
- 사용하지 않게 된 코드를 모두 삭제합니다.
- 코드 개선이나 버그 수정은 진행하지 않습니다.
Aggregator
- 레거시 API 인터페이스를 유지하기 위한 Abstraction Layer 역할을 수행합니다.
- 개발하면서 @Deprecated를 추가하는 코드들입니다. 비즈니스로직과 코드를 최소화합니다.
개발 편의를 위해 Spring Cloud Gateway와 CustomFilter를 활용하였고, 앞서 소개해 드린 멀린 님의 공통 모듈을 적용하였습니다. 공통 모듈은 기본적으로 Reactive Stream과 Coroutine을 지원하기 때문에, 다른 마이크로서비스에서 필요한 정보를 병렬로 요청하고 Aggregation해서 응답속도도 개선되었습니다.
Microservice
- 브레인스토밍 과정에서 명확하게 분리가 가능한 서비스를 대상으로 시작합니다.
- 초기에는 타서비스와의 연동 시 레거시 서비스를 은닉하기 위한 Abstraction Layer로도 동작합니다.
- 데이터베이스의 분리 등을 진행하면서 Abstraction Layer에서 서비스로 진화합니다.
Backend For Frontend
마이크서비스로 개선했다고 하더라도 하나의 Aggregator로 API 응답을 의존하는 방식을 사용한다면, 플랫폼별 새로운 기능을 추가할 때마다 걸림돌이 될 수 있습니다. 실제 오늘의집 구버전 API를 분석해 보니 코드 안에 플랫폼에 대한 분기문이 생각보다 많이 존재하고 있었고 관리가 쉽지는 않았습니다.
개발 시 리소스가 조금 더 들어갈 수 있지만, 유저의 관점에서 가장 최적의 서비스를 제공할 수 있을 것이라 판단하여 BFF 패턴도 도입하기로 하였습니다.
- BFF를 도입하면 CX(Customer Experience) 관점에서 플랫폼별 최적의 기능을 제공할 수 있고, 하위 버전에 대한 코드나 긴급 대응을 위한 분기 처리도 별도 관리가 가능합니다.
- 각 플랫폼이 잘하는, 잘 아는 방법을 제약 없이 사용할 수 있습니다. 안드로이드는 gRPC, 웹은 graphQL, iOS는 RestAPI를 사용할 수도 있습니다.
- (이론상) 사용하지 않는 데이터는 조회하지 않아 리소스의 효율을 높일 수 있습니다.
Traffic Shadowing
가장 최신 버전을 기준으로 QA까지 마무리되었지만, 유저들이 사용하는 모든 앱에 대한 검증이 완료되었다고 할 수는 없었습니다. 마이크로서비스로 전환된 시스템을 서비스에 투입하기 전에 트래픽 쉐도잉을 통한 GET 메소드 요청에 대한 검증을 진행하기로 했습니다.
- Y-Pipe-Gateway(Shadow Gateway)에서 레거시 시스템에 전달한 요청을 마이크로서비스 Aggregator에 Async로 요청합니다.
- 레거시 시스템과 Aggregator의 응답을 Kafka로 전송합니다.
- Diff Cheker에서 두 개의 응답을 비교한 후 다를 경우 Slack으로 알려줍니다.
- 코드상 문제가 발견된 경우 마이크로서비스 코드를 수정하고 재배포한 후 모니터링합니다.
- Known Issue인 경우 알림이 오지 않도록 예외 처리합니다.
MSA Phase 2. 를 향해
비즈니스 업무를 멈추고 3개월이라는 시간을 사용하였지만, 이제 막 열차에 탑승하고 바퀴를 움직이기 시작한 정도입니다. 언제 끝날지 기약조차 하기 어려울 정도로 앞으로 해야 할 일이 많이 남았습니다. 그래서 MSA Phase 1. 이겠죠?
진행하다 멈추면 안 한 것만 못합니다. 열차는 출발하였고 이제는 우리의 도전 의식이 필요한 시기라고 생각합니다.
새 기능은 마이크로서비스로 개발
- 새로 추가되는 기능, 기존 기능의 개편 등은 모두 마이크로서비스에서 개발합니다.
- 비즈니스 측면에서는 조금 느려질 수 있지만, 먼 미래 그려보면 오히려 더 빠를 것이라고 생각됩니다.
마치며
- 모놀리스 시스템의 마이크로서비스 전환 계획은 멈추지 않고 진행합니다.
- 구글신도 우리에게 꼭 맞는 정답을 알려 주지는 못했습니다.
👨💻 오늘의집 개발팀을 더 자세히 알고 싶다면? (클릭)