마이크로서비스의 외부 API 패턴 (2)

Greg Lee
10 min readNov 26, 2023

--

29CM 에서의 외부 API 패턴 적용 구상

하나의 건축물을 외부에서 바라본다면?

이 글은 마이크로서비스의 외부 API 패턴(1) 에서 소개한 Materialized View 와 Backend For Frontend 를 29CM 에서 어떻게 적용할 것인지를 소개하는 글입니다.

0. 지난 글의 요약

마이크로서비스는 비즈니스 도메인을 중심으로 모델링된 독립적으로 배포 가능한 서비스를 말합니다. 마이크로서비스는 도메인마다 독립적으로 서비스를 구현하기 때문에 전체 로직을 구성하는 과정에서 도메인 서비스간의 통신이 발생하게 되고, 이 때 필연적으로 네트워크 호출이 일어나게 됩니다. 이런 상황 때문에 기존의 모놀리식 서비스에서는 발생하지 않을 다양한 이슈가 마이크로서비스에서 발생하게 됩니다.

가령 과거에는 여러 도메인의 데이터를 SQL Join 으로 처리했다면, 마이크로서비스에서는 여러 도메인 API 의 호출과 조합으로 처리하게 됩니다. 이 과정에서 응답 간의 이슈가 발생하면 전체 응답이 실패할 수도 있고, 개발과 운영 과정에서도 많은 리소스가 필요하게 됩니다. 그리고 다양한 클라이언트마다 요구사항이 약간씩 다를 수 있는데 공통의 서비스에서 이를 모두 처리하다보면 그 과정에서 필연적으로 개발과 성능 상의 비효율이 발생할 수 밖에 없게 됩니다. 그리고 클라이언트가 내부 도메인 서비스를 직접 호출하는 구조도 유지보수 관점에서는 부담스러운 것도 사실입니다.

이와 같이 앞선 글에서는 마이크로서비스로 전환하는 과정에서 겪을 수 있는 외부 API 관점에서의 다양한 문제 상황을 Materialized View 와 Backend For Frontend, API Gateway 로 풀 수 있다는 것을 설명하였습니다.

이제 29CM 에서 마이크로서비스로 전환하는 과정에서 겪게 되는 문제 상황을 Materialized View (이하 MV)와 Backend For Frontend (이하 BFF)를 활용하여 풀어가는 과정을 소개합니다.

1. 29CM 에서의 문제 상황

29CM 와 같은 이커머스 플랫폼에서 상품 상세 페이지(이하 PDP), 상품 목록 페이지(이하 PLP), 검색 결과 페이지(이하 SRP) 는 고객 분들이 가장 많이 확인하시는 페이지이면서 상품, 가격, 재고, 할인, 리뷰와 같은 다양한 도메인 정보를 필요로 한다는 점에서 복잡도가 있는 페이지입니다.

그리고 마이크로서비스로 전환하는 과정에서 PDP 와 PLP, SRP 와 같은 페이지를 중심으로 크게 3가지의 문제 상황을 겪게 됩니다.

1) 상품 정보를 가져오는 구간이 크게 2개로 분리되어 있습니다.

29CM 의 PDP 와 PLP, SRP 등을 구성할 때 사용하는 서비스의 주체가 각각 다른 상황입니다.
1) PDP 는 마이크로서비스로 전환하기 전부터 모놀리틱 서비스의 API 에서 모든 상품 계열 정보를 제공하고, API 앞 단에는 별도의 cache 를 두어 대량의 호출을 처리하도록 구성했습니다.
2) PLP 와 SRP, 3) 추천 결과 응답 페이지는 ElasticSearch 를 활용하여 상품 정보를 가져오고 있습니다.

이런 식으로 상품 정보를 2개의 서비스로 분리하여 제공하게 되면, 2개의 서비스 간의 상품 정보 동기화 불일치가 발생할 때마다 페이지 별로 다른 응답 형태를 보이게 되고, 이는 고객 경험을 크게 해치는 상황으로 이어지게 됩니다. (ex. PDP 와 SRP 에서의 상품 판매가격이 다른 상황)

물론 현재는 지속적인 내부 개선을 통해 둘 간의 동기화 불일치 발생 가능성을 거의 없앴지만, 근본적인 해결은 하나의 단일화된 서비스에서 상품 정보를 제공하게끔 개선하는 것입니다.

2) ElasticSearch 가 본연의 역할 이상을 수행하고 있습니다.

29CM 에서 사용하고 있는 ElasticSearch 는 고객의 검색 요청을 처리하는 것 뿐만 아니라 PLP 와 추천 응답에서 필요로 하는 상품 정보를 제공하는 역할도 함께 수행하고 있습니다. ElasticSearch 가 두 가지 이상의 역할을 수행하는 과정에서 안정적인 응답을 제공한다면 크게 문제가 안되겠지만, ElasticSearch 가 처리하기 어려운 수준의 트래픽이 몰릴 때에는 검색 요청과 상품 응답 요청 모두 장애가 발생할 수 있습니다. 그리고 책임과 역할의 분리를 명확히 할 때의 장점을 생각한다면 ElasticSearch 는 검색 본연의 역할에 집중하도록 개선하는 것이 장기적인 관점에서 맞는 방향이라고 봅니다.

3) 상품 정보의 각 항목마다 변경 주기가 다릅니다.

동일한 상품 정보군에서도 변경 주기가 긴 것과 짧은 것이 있는데, 이를 하나의 API 에서 처리하는 것은 효율이 좋지 않습니다. 예를 들어 상품명, 카테고리와 같은 정보는 한 번 등록되면 거의 바뀌지 않지만, 그에 비해 상품 판매가격은 프로모션이 진행되거나 신규 쿠폰이 발행되면 그 때마다 변경이 이루어지게 됩니다. 이 때 MV 나 ElasticSearch 를 통해서 판매가격이 포함된 상품 정보를 제공하려면, 가격 변경이 발생할 때마다 MV 또는 ElasticSearch 에 새로 계산된 가격을 계산하여 저장해야 합니다. 물론 한 번 저장해놓으면 이후에는 별도의 계산 없이 빠르게 정보를 제공할 수 있다는 장점이 있지만, 데이터의 변경 주기가 빠르면 빠를수록 별도의 계산과 저장 로직을 매번 실행해야 한다는 문제가 발생합니다.

29CM 는 2018년부터 23년 현재까지 70% 이상의 성장세를 지속하고 있기 때문에 그만큼 고객 경험과 서비스 안정성을 높은 수준으로 유지해야 하는 상황입니다. 당장은 지금과 같은 구조를 유지해도 향후 1~2년은 큰 문제 없이 서비스 운영이 가능하겠지만, 이후에는 위에서 설명한 문제 상황이 팀의 성장과 고객 만족을 크게 떨어뜨릴 수 있기 때문에 더 큰 성장을 위한 서비스 아키텍처와 운영에 대한 고민을 해야 합니다.

2. MV 와 BFF 를 활용한 서비스 아키텍처

위와 같은 문제 상황을 해결하기 위해 29CM 에서 구상한 아키텍처는 다음과 같습니다.

  1. 클라이언트 영역: App 과 Web 같은 다양한 클라이언트 플랫폼을 말한다. 필요로 하는 요구사항을 처리하기 위해 BFF 를 호출할 수도 있고, Service API 를 직접 호출할 수도 있다. 그리고 클라이언트는 29CM 과 같은 하나의 플랫폼을 넘어서 다른 플랫폼, 다른 외부 법인이 될 수도 있다.
  2. Backend For Frontend (이하 BFF) 영역: App 과 Web 같은 각각의 클라이언트가 필요로 하는 응답 프로퍼티를 제공하는 역할을 한다. 클라이언트가 필요로 하는 대부분의 프로퍼티는 MV 를 통해서 가져오지만 필요에 따라서는 별도의 Service API 를 추가로 호출하고 둘 간의 응답을 Aggregation 해야할 수도 있다. 물론 MV 의 존재 목적을 생각한다면 별도의 Service API 호출 횟수는 제한적으로 이루어져야 한다. BFF 는 클라이언트의 요구사항을 충족하면서도 개발 편의성과 성능, 안정성을 함께 고려하기 위해 Cache 와 Circuit Breaker 와 같은 구현도 검토해야 한다.
  3. Materialized View Service for Item (이하 MV) 영역: 상품 리스팅 도메인과 관련한 모든 프로퍼티를 제공하는 역할을 한다. 상품 리스팅 관련한 모든 프로퍼티 중에서 필요한 컬럼을 선별하여 클라이언트에게 제공하는 책임은 BFF 에게 있다. MV 는 API 를 제공하는 서버도 있지만 CDC 를 통해 발행된 메시지를 MV 의 저장 구조에 담는 Consumer Logic 도 포함한다.
  4. Change Data Capture (이하 CDC) 영역: 메인 트랜잭션 데이터베이스에서 데이터 변경이 발생했을 때 이를 감지하여 메시지 브로커에 변경 이벤트를 반영한다. 이를 통해 MV 를 위한 Consumer Logic 이 실시간으로 메시지를 consuming 하여 MV DB 를 구축할 것이다. CDC 는 데이터 변경 감지와 메시지 브로커에 변경 이벤트를 발행하는 것까지의 역할만 수행한다.
  5. Service API 영역: BFF 를 거치지 않으면서 클라이언트가 필요로 하는 도메인 요구사항을 처리하는 대부분의 도메인 서비스를 말한다 (ex. 유저 서비스, 주문 서비스) 또는 BFF 에서 기존 API 의 변경 없이 A/B Test 를 진행하거나 특정한 응답을 추가하여 내려주는 상황이 필요할 때에도 BFF 가 Service API 를 호출할 수도 있다.

29CM 에서 BFF 와 MV 를 조합한 아키텍처는 주로 상품 상세 요청과 검색 요청을 처리할 때 활용할 예정입니다.

지금까지의 설명은 29CM 에서의 BFF 와 MV 의 구조를 한 눈에 볼 수 있도록 넓은 관점에서 조망하면서 설명한 것이라면, 이제는 각각의 상황에서 BFF 와 MV 를 어떻게 활용하여 요구사항을 처리할 것인지를 설명하겠습니다.

1) 상품 상세 응답 처리

상품 상세 페이지의 응답을 처리하는 아키텍처와 서버 Flow 는 다음과 같습니다.

  1. 클라이언트에서 상품 상세 페이지를 위한 서버 응답을 BFF 에게 요청한다.
  2. BFF 에서는 클라이언트에게 전달 받은 상품 ID 를 가지고 1) MV 와 2) Promotion 서버를 동시에 호출한다. 이 때 MV 에서는 상품명, 카테고리, 리뷰와 같이 데이터 변경 주기가 길면서 상품 정보의 대부분을 차지하는 프로퍼티를 제공하고, Promotion 에서는 판매가격과 같이 변경 주기가 짧으면서 실시간성이 중요한 프로퍼티를 제공한다.
  3. BFF 는 MV 와 Promotion 양쪽의 API 응답을 Aggregation 하여 클라이언트가 필요로 하는 상품 정보 전체의 응답을 구성하여 제공한다.

2) 검색 요청 응답 처리

검색 요청에 대한 응답을 처리하는 아키텍처와 서버 Flow 는 다음과 같습니다.

  1. 클라이언트에서 고객이 입력한 검색어를 BFF 에게 전달하여 검색 결과에 대한 응답을 요청한다.
  2. BFF 에서는 전달 받은 검색어를 Search API 에 전달하고, Search API 는 ElasticSearch 를 통해 검색어에 맞는 상품 ID List 를 BFF 에 전달한다.
  3. BFF 는 Search API 에게 전달 받은 상품 ID List 를 가지고 1) MV 와 2) Promotion 서버를 동시에 호출한다.
  4. 이 때 MV 에서는 상품명, 카테고리, 리뷰와 같이 데이터 변경 주기가 길면서 상품 정보의 대부분을 차지하는 프로퍼티를 제공하고, Promotion 에서는 판매가격과 같이 변경 주기가 짧으면서 실시간성이 중요한 프로퍼티를 제공한다.

위와 같은 아키텍처를 구성하면 상품 정보를 제공하는 원천 서비스를 일원화하여 불일치 구간을 제거하면서도, 상품 판매가격과 같은 실시간 데이터는 그에 맞게 빠른 반영이 가능하게 됩니다. 각각의 요구사항에 대한 책임과 역할 분리도 기존보다 상세화되는 장점도 있습니다.

3. 아키텍처는 트레이드오프

모든 아키텍처 의사결정에는 트레이드오프가 따릅니다. 장점을 취하면 그에 맞는 단점을 감수해야 한다는 것이죠. 그리고 마이크로서비스로 전환하는 과정에서도 이와 같은 관점을 기반으로 팀 상황과 목표에 맞는 아키텍처 선택을 하는 것이 중요합니다. 장점이 극대화되면서도 단점을 보완하거나 감수할 수 있는 선택지를 찾아야 합니다.

마이크로서비스로 전환하는 과정을 외부 API 호출 관점에서 바라본다면 위에서 언급한 것과 같은 문제 상황이 발생할 수 있습니다. 이 때에는 MV 와 BFF 를 서비스 앞 단의 구조로 적용하는 것을 검토해보는 것이 좋습니다.

--

--