Contents
- 시작하며
- Level 1. 이벤트 순서대로 나열하기
- 갑자기 Bonus? 중개와 중계 차이점
- Level 2. 역할 관점으로 분리하기
- Level 3. 결과 반환시점을 기준으로 기준으로 분리
- Level 4. 커뮤니케이션 역할에 따른 서비스 분리
- Level 5. 동기-비동기로 분리
- Level 6. 초기세부구현 : 시작점
- Level 7. 추후 고민이 필요한 부분들
- 1. 주문상태확인 기능
- 2. 중개 기능 (요청 전달)
- 3. 라이더의 위치정보
- 오늘은 여기까지
시작하며
지난번 블로그에서 프로젝트 주제선정 과정과 기초 유저 플로우(위 사진)를 소개해드렸습니다.
이후 저는 상세 구현 목록과 서비스 아키텍처를 도출하기 위해 로버트 마틴 "클린 아키텍처"를 옆에 펼쳐놓고 서비스를 디테일하게 분석하기 시작했습니다.
저는 어떻게 접근해야될지 감이 안 올 때 서비스 관점에서 분석을 시작하는 편입니다.
서비스를 더 깊게 분석하면 할 수록 특정 패턴이 보이고 각 부분들의 공통점이 보기 시작합니다.
그렇게 쭉 진행하면서 현재는 이벤트 기반 아키텍처를 설계 중에 있으며 지금까지의 과정을 소개하려고 합니다.
내용이 좀 길어질 수 있으니 최대한 읽기 좋게 소개해보겠습니다.
Level 1. 이벤트 순서대로 나열하기
우선 중계 서비스에서 발생하는 이벤트들을 발생 순서대로 나열했습니다.
보시면 좌측에 주문생성부터 일렬로 & 아래로 6단계에 걸쳐 진행이 됩니다.
우측에 부분적으로 주문 접수 요청과 배차 요청을 외부로 전송하고 외부 주체는 언젠가 (now or later) 액션을 취합니다.
우측 하단의 주문/배달 현황은 모든 외부 주체(external)가 요청 가능한 정보입니다.
이렇게 서비스를 정리하고 나니 위치상으로만 봐도 서비스에 어떤 구분이 발생하는 것이 보이기 시작합니다.
갑자기 Bonus? 중개와 중계 차이점
중개와 중계라는 단어 모두 중간에서 이어준다는 공통점이 있지만 미세하면서 치명적인 차이점이 있습니다.
중개는 두 당사자 사이에 직접 참여해서 일을 주선하는 것 (ex. 공인중개사)을 의미하며 중계는 두 당사자 사이에서 간접적으로 실황을 전달해주는 것 (ex. 생중계)을 의미합니다.
이 차이점을 설명하는 이유는 다음 Level 2. 에서 알게 됩니다.
Level 2. 역할 관점으로 분리하기
"관리 - 중개 - 중계"
현실세계에서 발생하는 이벤트의 흐름을 지속하기 위해 중계 서비스는 내부적으로 세 가지 역할을 수행합니다.
주문의 상태를 관리하는 [ 관리인 ]의 역할, 주문에 상태에 따라 외부로 액션을 요청하는 [ 중개인 ] 역할, 그리고 모두의 관심사인 주문/배달 현황을 공유하는 [ 중계인 ] 역할로 나눌 수 있습니다.
이제 이 역할들이 정확히 어떻게 외부 주체들과 대화를 나누는가 를 알아보겠습니다.
Level 3. 결과 반환시점을 기준으로 기준으로 분리
중계 서비스의 이벤트들은 순차적으로 발생해야 하며 순차적으로 처리되야합니다.
예를 들어 사장님이 주문 접수를 완료하기 전에 배차를 할 수 없고 주문이 들어오기 도전에 라이더님이 음식을 픽업하러 갈 수 없습니다.
그렇지만 모든 이벤트들이 도미노처럼 연쇄적으로 진행되지는 않습니다.
사실상 각 이벤트 간에는 공백이 존재합니다.
(실제로 제가 가게를 운영하고 배민 서비스를 활용한 경험이 있어 알 수 있었습니다.)
예를 들어 주문 접수 요청이 들어오면 사장님은 어느 배달 대행사를 지정할지, 조리시간은 얼마나 걸릴지를 고민하는 시간이 필요하며 배달 대행사에서는 적합한 라이더를 선정할 시간이 필요합니다.
이런 생각들을 하다 보니 서비스의 어느 정확한 부분에서 즉각적인 피드백이 필요하고 어디서는 필요하지 않은지가 보이기 시작했습니다.
예를 들어 주문 접수 요청에서 "주문 접수 요청" 수신에 대한 결과는 즉시 알아야 하지만 "사장님의 주문 접수 완료" 이벤트는 연쇄적으로 발생하지 않습니다.
이렇게 서비스의 각 기능들을 마치 동기와 비동기를 비교하듯이 결과 반환시점에 대한 관심사별로 분류를 할 수 있었습니다.
사실상 이 모든 것을 순서대로 동기화 처리하려면 주문 한 번에 커넥션 10개 이상을 유지해야 하며 장애 발생 가능성은 최대치로 올라가겠지요.
이렇게 관심사별로 분리를 한 이후 저는 "사장님과 배달 대행사"가 접수 요청을 받아 접수를 하는 이중적인 역할을 분리하기 시작했습니다.
Level 4. 커뮤니케이션 역할에 따른 서비스 분리
Sender-Receiver와 Publisher-Subscriber의 관계
가장 먼저 [ 중개인 ]의 이중적인 역할을 분리했습니다.
요청 전달에 대한 결과는 중계 서비스에서 즉시 필요하지만 이후의 액션은 사장님과 배달 대행사가 진행합니다.
여기서 서비스가 요청을 하고, 요청을 받고, 이벤트를 발행하고, 이벤트를 구독하는 역할들을 각 Sender, Receiver, Publisher, Subscriber로 구분을 했습니다.
보면 관리/중개/중계 부분들이 두 가지 역할들을 수행하고 있으며 저는 이를 해소해야 하는지 고민했습니다.
하지만 중계 서비스의 특성상 중간다리 역할은 필연이다라는 논리를 가지고 문제가 없다는 판단을 내리게 되었습니다.
이후 구체적으로 서비스를 그리기 시작했습니다.
Level 5. 동기-비동기로 분리
위 Level 3과 Level 4에서 다룬 역할, 그리고 결과 반환 시점에 대한 부분을 동시성의 관점에서 바라봤습니다.
클라이언트-서버(동기) 부분에서는 주문 상태를 갱신하고 수신확인에 대한 OK200를 리턴하며 Publisher의 입장으로 이벤트/메시지를 발행합니다.
(이미지의 단순화를 위해 요청하는 방향만 화살표로 나타냈습니다.)
비동기 부분에서는 subcriber들이 각자 필요로 하는 이벤트들을 구독하며 발행된 이벤트에 따라 사장님에게 주문 접수 요청을 하고 배달 대행사에 배차 요청을 하게 됩니다.
주문/배달 현황은 사장님과 고객님, 그리고 잠재적으로 다른 곳에서도 확인을 하게 되는 정보이며 주문의 수 대비 훨씬 많은 read 요청을 받기 때문에 우선 별도로 분리해두었습니다.
Level 6. 초기 세부 구현 : 시작점
Speculation(추측)과 Specification(명세서)는 어원이 같다는 것을 알고 계시나요?
지금까지 추측을 통해 위와 같은 최소한의 서비스 명세를 도출했고 이제 개발을 시작할 단계입니다.
시작으로 서비스 전체를 동기(Sync)로 세부 구현하는 것을 계획했습니다.
각 주문 관련 요청들의 HTTP 메서드와 URI, 그리고 실행 순서 과정을 정리했습니다.
메시지 발행 부분은 Abstraction을 통해 이후 별도 모듈로 분리를 계획 중입니다.
Level 7. 추후 고민이 필요한 부분들
지금까지 서비스를 계획하며 추후 고민이 필요한 부분들을 정리했습니다.
당장의 문제는 아니지만 이후 Integration 테스트, 성능 테스트 과정에서 필요한 내용들입니다.
아래 다이어그램에서는 노란 점선으로 표시된 부분들입니다.
1. 주문 상태 확인 기능
주문상태확인 부분은 read-heavy operation입니다.
주문 상태, 생성 , 배차 완료와 같은 [ 관리 ] 부분은 요청수가 고정적으로 주문대비 1:7 정도로 발생이 예상됩니다.
하지만 주문 상태 확인 부분은 주문의 상태를 알아야 하는 배민 서비스의 서버, 사장님 앱을 통해 배달 현황을 체크하는 사장님, 주문 상태가 궁금한 고객님까지 상대적으로 매우 많은 요청이 발생하게 됩니다.
이 부분을 해결하기 위해 예상해볼 수 있는 방안은 별도의 Cache 서버를 구성하거나 데이터베이스의 Async Replcation을 통해 여러 read-only slave 노드를 활용할 수도 있으며 주문 상태 확인 요청을 위한 별도 서버도 구성할 수 있습니다.
이런 부분들은 기억해두고 추후 협의를 통해 반영할 예정입니다.
2. 중개 기능 (요청 전달)
중개가 필요한 새로운 주문을 배민 서비스로부터 전달받으면 사장님은 주문 접수 요청을 받아야 합니다.
사장님이 주문 접수를 하면 지정된 배달 대행사는 배차 요청을 받아야 합니다.
여기서 생각해볼 수 있는 부분은 사장님에게 push를 할 것인가, 사장님이 pull 을 할 것인가입니다.
push 를 할 경우 push 서버를 구현할 것인지, 또는 구글 firebase과 같은 push 서비스를 적용할 것인지,
pull를 할 경우 사장님과 배달 대행사는 polling 인터벌을 얼마나 길게, 또는 짧게 가져갈 것인가 등을 고민할 필요가 있습니다.
3. 라이더의 위치정보
하루 발생하는 라이더 위치정보 데이터 수를 예상해보겠습니다.
# 일평균 주문 수 100만건 (2020년 배달의민족 기준)
# 예상 평균 배달시간 10분(==600초)
# 예상 라이더 위치정보 5초단위 전송
100만 x (600초 / 5초) = 12000만 = 1.2 조
라이더 위치 데이터는 주문 수와 정비례하지만 주문 대비 정말 많이 발생하는 것을 예상해볼 수 있습니다.
비즈니스 룰은 협의가 필요하겠지만 데이터가 자산이 되는 요즘 시대에 라이더 위치정보를 배달 완료 후 폐기하는 것은 비현실적으로 보입니다.
또한 이후 "배달이 늦어서 음식이 식었습니다."와 같은 클레임 발생을 대비하기 위해서도 필요한 데이터들입니다.
여기서 예상해볼 수 있는 해결 방안은 배달이 완료된 이후 라이더 위치정보들을 별도의 DB에 저장하고, 라이더 위치 조회는 캐시를 활용하는 방법들입니다.
오늘은 여기까지
지금까지 배달의민족 중계 서비스 아키텍처를 구상하는 과정을 공유했습니다.
물론 지금까지의 아웃풋이 결말은 아니며 지속적인 협의를 통해 진화하는 살아있는 아키텍처를 만들고자 합니다.
추가적인 부분들은 별도 포스팅으로 다뤄볼 예정입니다.
감사합니다.
'in-bob-we-trust' 카테고리의 다른 글
[Reactive한 라이더위치 기능구현 ] 요구사항 분석부터 위치정보 저장 기능 구현까지 (0) | 2022.01.13 |
---|---|
Github 프로젝트 & Intellij 전반에 걸쳐 Google Java Style Guide 를 강제하기 (0) | 2022.01.09 |
중계서비스 Swagger 도큐먼트 툴 적용 + 단점 보완 (0) | 2021.12.13 |
프로젝트 주제를 정했습니다. (0) | 2021.11.30 |
프로젝트를 시작합니다. (0) | 2021.11.28 |