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

Greg Lee
13 min readNov 19, 2023

--

부제: Materialized View, Backend For Frontend, API Gateway 에 대한 생각

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

해당 글에서는 마이크로서비스가 고도화되는 과정에서 API 호출을 중심으로 발생하는 다양한 문제 상황을 정의하고, 이를 해결하기 위한 검증된 아키텍처를 설명하고자 합니다. 이 글에서 설명할 다양한 문제는 현재 제가 일하고 있는 29CM 에서 겪고 있는 문제이기도 합니다.

1. Materialized View

문제상황

모놀리식 아키텍처에서는 전체 데이터가 하나의 데이터베이스에 있기 때문에 여러 도메인의 데이터를 SQL Join 등으로 손쉽게 가져올 수 있습니다. 하지만 마이크로서비스 구조에서는 하나의 클라이언트 뷰를 구성하는 과정에서 여러 개의 도메인 서비스 API 를 동시에 호출하고 이를 조합해야하는 과정이 필요할 수도 있습니다. 이커머스의 상품 상세 페이지를 예시로 든다면, 해당 페이지에서는 상품, 가격, 재고, 할인, 리뷰와 같은 다양한 도메인의 정보를 필요로 하고, 이 때 각 도메인 서버의 API 를 모두 호출하고 각각의 응답을 Aggregation 해야만 원하는 데이터를 만들 수 있게 됩니다.

여러 도메인 서비스로 분리되는 과정에서 발생하게 되는 문제 상황 예시

이러한 요구사항에서 발생할 수 있는 문제상황은 여러 개의 API 를 호출하는 과정에서 하나의 API 라도 응답이 느리거나 이슈가 발생한다면 클라이언트에서는 원하는 로직을 구현할 수 없게 되고, 개발과 운영 과정에서도 다양한 API Endpoint 를 개별적으로 관리해야 하는 어려움을 겪게 되는 것입니다.

이러한 문제상황을 해결하기 위해 Materialized View 구조가 등장하게 됩니다.

Materialized View 소개

Materialized View (이하 MV) 는 메인 트랜잭션 DB 와는 별개로 독립적으로 존재하는 미리 계산된 데이터 뷰를 말합니다. 각각의 도메인이 소유한 데이터베이스에서의 변경 상태를 MV 에 실시간으로 반영하고, 클라이언트는 관심 영역의 데이터를 MV 를 통해 빠르게 응답받을 수 있도록 합니다. 이 때 MV 에 실시간으로 변경 데이터를 반영하기 위해 kafka 기반으로 구현된 Change Data Capture (이하 CDC) 를 활용하기도 합니다.

MV 의 핵심은 클라이언트가 필요로 하는 다양한 도메인의 응답을 하나의 API 호출로 처리 가능하도록, 사전에 필요한 응답을 미리 계산하여 저장하는 구조를 만든다는 것입니다. 그리고 이런 구조를 만드는 과정에서 읽기 작업과 쓰기 작업을 분리하여 운영하는 CQRS 개념도 활용됩니다.

Materialized View 구현과 활용

또한 MV 는 백엔드와 프론트엔드 사이에 위치하여 프론트엔드가 필요로 하는 데이터만 맞춤형으로 제공하는 미들웨어 구조인 Backend For Frontend (이하 BFF) 의 부분집합으로 볼 수도 있습니다. BFF 의 핵심은 클라이언트가 필요로 하는 응답 프로퍼티를 제공하기 위해 뒷단의 서버 구조는 노출하지 않으면서 프론트엔드에 필요한 응답을 제공하는 구조를 만드는 것인데, 광의의 관점으로 본다면 MV 도 이러한 목적으로 만들어진 아키텍처 구조로 볼 수 있습니다.

2. Backend For Frontend

문제상황

서비스를 개발하다보면 클라이언트 플랫폼에 관계없이 동일한 서비스의 API 를 활용하여 클라이언트의 로직을 구현할 때가 많습니다. 예를 들자면 iOS, Android, Web 에서 메인 홈 화면을 로딩할 때, 동일한 서비스의 동일한 API 를 호출하는 상황과 같은 것을 말합니다. 하지만 서비스가 성장하는 과정에서는 클라이언트 별로 개별적인 요구사항을 구현해야 하는 상황이 발생할 수 있고, 서버에서 최적화가 덜 되어서 클라이언트에서는 필요로 하지 않는 API 의 응답이 과하게 내려갈 수도 있습니다. 이런 상황을 해결하려면 공통적으로 호출하는 특정 API 의 로직을 변경하는 과정이 필요한데, 클라이언트의 요구사항에 따라 매번 뒷단의 공통 API 를 변경하는 것은 아키텍처 구조와 운영 관점에서 또 다른 문제를 발생시킬 수 있게 됩니다.

클라이언트의 요구사항에 따라 하나의 API 가 아니라 여러 개의 API 를 호출하고, 이를 Aggregation 하여 로직을 처리해야 하는 상황이 발생할 수도 있습니다. 빠르게 가설을 검증하기 위해서 기존 로직에서 특정한 API 를 호출하여 Aggregation 해야할 수도 있고, 하나의 도메인 서비스가 여러 개의 마이크로서비스로 분리되는 과정에서 기존 응답과 동일한 로직을 만들기 위해 분리된 서비스 API 를 클라이언트가 각각 호출하여 Aggregation 해야할 수도 있습니다. 클라이언트 입장에서는 서비스가 고도화될수록 호출해야 하는 API 가 많아지고 이 때문에 신규 기능의 구현과 유지보수 관점에서 어려움이 발생하게 됩니다.

이런 상황이 발생하게 된 근본적인 원인은 클라이언트와 도메인 서비스 사이에서 분리된 레이어 없이 직접적으로 연결되었기 때문인데, 이러한 문제상황을 해결하기 위해 Backend For Frontend 구조가 등장하게 됩니다.

Backend For Frontend 소개

Backend For Frontend (이하 BFF) 는 데이터를 제공하는 백엔드와 이를 활용하는 프론트엔드 사이에 위치하여, 프론트엔드가 필요로 하는 데이터만 맞춤형으로 제공하는 일종의 미들웨어 서비스 구조를 말합니다. BFF 를 활용하게 되면 하나의 서비스마다 다양한 클라이언트 플랫폼이 있을 때 각 플랫폼별로 별도의 응답을 제공할 수 있고, 또는 공통의 서비스를 활용하는 플랫폼마다 별도의 응답을 제공할 수도 있습니다. 예를 들어 BFF 를 활용하면 iOS, Android, Web 마다 별도의 응답을 제공하는 구조를 만들면서도 실제 데이터를 가져올 때에는 뒷단의 공통 서비스를 호출하게 만들 수 있습니다. 또는 그룹사 공통의 Core Service 가 존재한다고 가정할 때, 각 플랫폼별 니즈에 맞는 응답을 제공하도록 Core Service 앞 단에 BFF 를 두고 맞춤형 API 를 제공할 수도 있습니다.

BFF 의 핵심은 프론트엔드와 백엔드 사이에 추상화된 레이어를 두고, 다양하게 변화하는 양쪽의 요구사항을 BFF 에서 확장성있게 처리한다는 것입니다. 프론트엔드가 백엔드를 호출하고, 백엔드가 응답을 내려주는 과정에서 BFF 를 거치기 때문에 클라이언트의 니즈에 맞는 적절한 변환 로직을 수행할 수 있습니다. 또한 필요에 따라서는 여러 도메인의 API 를 호출하고 각각의 응답을 Aggregation 할 수도 있습니다.

BFF 가 제대로 운영되려면 특정 요구사항에 따른 변경 관리의 책임을 하나의 팀에서 가져져가야 한다는 것과, 클라이언트의 필요에 맞는 응답을 전달할 수 있어야 한다는 것입니다. 하나의 팀이 관리하기 때문에 팀에 맞는 요구사항을 독립적이면서 빠르게 구현할 수 있고, 클라이언트에 필요한 맞춤형 응답을 제공할 수 있게 됩니다. 클라이언트에 제공할 응답을 만들어내는 과정에서는 MV 를 통해 필요한 응답을 가져올 수도 있고, 특정한 서버 API 를 여러 개 호출하면서 전체 응답을 Aggregation 하여 클라이언트가 필요로 하는 응답을 제공할 수도 있습니다.

3. API Gateway

문제상황

여러 개의 마이크로서비스를 구현하는 과정에서는 각 서비스마다 공통적으로 구현하고 관리해야하는 기능들이 발견됩니다. 인증 및 인가, API 요청 제한 처리, API 공통 로깅 등이 이와 같은 것들인데 이런 기능들은 보통 횡단 관심사(Cross-Cutting Concerns) 라고 부릅니다. 이러한 횡단 관심사 기능을 모든 마이크로서비스에서 개별적으로 구현하고 관리하는 것은 시스템 아키텍처와 팀 운영 관점에서 좋지 않은 접근입니다.

그리고 클라이언트에서 내부 마이크로서비스의 엔드포인트를 직접 호출하게 되면 클라이언트와 내부 마이크로서비스의 변경에 대해 상호간에 직접적인 영향을 받기 때문에 중간에 추상화된 레이어를 두는 것은 확장성과 유지보수 측면에서 유리한 점이 많습니다.

이러한 문제상황을 해결하기 위해 API Gateway 구조가 등장하게 됩니다.

API Gateway 소개

API Gateway 는 외부 서비스에서 내부 마이크로서비스를 호출할 때의 엑세스를 관리하는 서비스 구조를 말합니다. 이런 관점으로 본다면 API Gateway 는 Reverse Proxy 와 비슷한 역할을 한다고 볼 수도 있는데, API Gateway 는 Reverse Proxy 에서 수행하는 것보다 더 많은 역할을 수행합니다.

API Gateway 구조 (.NET 마이크로 서비스 — 아키텍처 eBook 참고)

API Gateway 와 Reverse Proxy 모두 외부 호출에 대한 단일 접점에 위치하기 때문에, 외부에서 내부 서비스를 호출하는 과정에서 필요로 하는 기능을 수행할 수 있습니다. 우선 뒷단에 위치한 내부 서비스의 IP 등을 노출할 필요가 없기 때문에 예상치 못한 외부 공격에서 내부 서비스를 보호할 수 있습니다. 모니터링 과정에서 외부 공격으로 의심되는 트래픽이 있다면 해당 구조를 이용하여 해당 트래픽을 차단할 수도 있습니다. 성능 측면에서도 동일한 응답을 가지는 반복되는 요청에 대해서는 해당 응답을 캐싱하여 응답을 처리할 수 있고, SSL Offloading 과 같은 처리를 통해서 내부 서비스에서는 SSL 처리에 대한 기능 구현은 하지 않고도 보안성이 높은 서비스 구조를 가져갈 수 있습니다. 다만 API Gateway 는 Reverse Proxy 에서는 수행하기 어려운 인증과 인가를 처리하거나 외부 요청의 호출량을 제한하는 것 등의 애플리케이션 레벨에서의 역할을 추가적으로 수행할 수 있다는 점에서 Reverse Proxy 보다 더 많은 역할을 한다고 볼 수 있습니다.

API Gateway 는 장점과 단점이 명확한 패턴이기 때문에 팀 내에서 적용할 때에는 다양한 관점에서 트레이드오프를 검토해야 합니다. 위에서 언급한 것처럼 API Gateway 를 적용하면 외부의 트래픽을 단일화된 구조 내에서 관리할 수 있다는 명확한 장점이 있지만, 그렇기 때문에 API Gateway 에서 서비스 장애가 발생한다면 API Gateway 가 관리하는 API 는 모두 서비스 장애 상황에 빠지게 된다는 단점도 있습니다. 그리고 팀 내에서 API Gateway 를 집중적으로 관리하고 구현하는 별도의 팀이 존재해야 한다는 것도 API Gateway 도입 시에 검토해야 할 사항입니다. 개발 팀의 규모가 크지 않다면 API Gateway 도입은 득보다 실이 더 많을 수 있기 때문에 신중한 검토와 적용이 필요합니다.

아래 3가지는 제가 생각할 때 API Gateway 를 적용할 때 중점적으로 고려해야 할 사항입니다.

  1. API Gateway 는 장단점이 명확하기 때문에 도입 시에 명확한 장점이 있는 영역에서만 제한적으로 도입하는 것이 좋습니다. 서비스 성격와 팀 규모에 따라 다르겠지만 외부 연동 서비스, 또는 내부 어드민 서비스와 같이 트래픽이 한정적이면서 호출 구간에서의 인증 처리, API 요청 제한 처리, API 공통 로깅, 데이터 집계와 같은 기능이 필요한 서비스에서 API Gateway 를 먼저 적용해보는 것이 좋습니다.
  2. API Gateway 내에는 도메인 로직, 핵심 비즈니스 로직이 포함되어서는 안 됩니다. API Gateway 는 외부 요청을 내부 서비스에 전달하는 과정에서의 비기능 요소를 처리하는 서비스 구조입니다. 만약 API Gateway 에 특정한 도메인 로직이 추가되었다고 가정한다면, 해당 도메인 로직이 변경될 때마다 API Gateway 코드를 매번 배포해야 하는데, 이는 특정 도메인 로직의 변경으로 인해 다른 도메인 서비스에 영향을 줄 수 있는 구조가 발생하는 것이고, 이는 마이크로서비스가 추구하는 배포 독립성에 위반되는 사항입니다
  3. API Gateway 는 외부 서비스가 내부 서비스를 호출할 때의 엑세스를 관리하는 것이 핵심입니다. 그렇기 때문에 내부 마이크로서비스간의 호출 구간에서는 API Gateway 가 호출의 중개자로 사용되어서는 안 됩니다. 이는 불필요한 네트워크 호출을 증가시키고 API Gateway 의 도입 목적에도 맞지 않습니다. 마이크로서비스의 내부간 호출은 istio 와 같은 service mesh 를 통해 관리하는 것이 맞습니다.

4. MV, BFF, API Gateway 간의 차이점

마이크로서비스의 외부 API 패턴으로 소개되는 Backend For Frontend, API Gateway 와 CQRS 모델을 기반으로 만들어지는 Materialized View 는 풀고자 하는 문제 상황과 이를 풀어내는 방식에 있어서 유사함을 발견할 수 있습니다.

MV 와 BFF 를 살펴본다면 둘 다 여러 도메인의 API 를 호출해야만 하는 문제 상황을 해결할 때 생각해볼 수 있는 구조입니다. 이 때 MV 는 CQRS 모델을 기반으로 클라이언트가 필요로 하는 응답을 사전에 미리 계산하여 저장 관리하는 방식으로 이를 해결한다면, BFF 는 다양한 도메인 서비스의 API 를 호출하여 Aggregation 하는 방식으로 해결합니다. 물론 BFF 와 MV 를 같이 사용하여 BFF 가 MV 를 포함한 다양한 도메인 서비스의 API 를 호출하고, 전달받은 응답을 가공하여 클라이언트에 전달할 수도 있습니다.

API Gateway 는 외부에서 내부 마이크로서비스를 호출할 때의 단일 엑세스를 관리하는 서비스 구조인데, 이를 세분화하여 여러 클라이언트마다 각각의 API Gateway 를 둘 수도 있습니다. 클라이언트마다 필요로 하는 응답의 형태나 요구사항이 다를 수 있고, 이를 하나의 공통 API Gateway 에서 처리하기에는 역할 범위가 커질 수 있기 때문입니다. 이러한 상황에서 BFF 가 등장하게 되는 것이고, 크게 보면 BFF 는 API Gateway 의 세분화된 버전이라고 볼 수도 있습니다.

실무 레벨에서 생각한다면 API Gateway 는 서비스 전체의 공통된 횡단 관심사 기능을 단일 레이어에서 처리하는 역할을 수행하고, BFF 는 필요에 따라 여러 API 를 호출하고 응답을 Aggregation 하여 클라이언트에게 맞춤형 응답을 제공할 수 있습니다. 다만 여러 API 를 동시에 호출하고 이를 Aggregation 하는 과정에서는 성능과 운영 측면에서 어려움을 겪을 수 있으니, 주요한 응답은 Materialized View 를 미리 구축하여 처리할 수도 있습니다. 즉, API Gateway 와 BFF, Materialized View 는 각각의 구조가 풀고자 하는 주요한 문제를 생각하면서 팀 상황에 맞게 적절하게 선택하여 사용할 수 있고, 세 가지 구조를 함께 사용한다면 가장 앞 단에 API Gateway 가 위치하면서 그 뒤로 BFF 와 Materialized View 가 위치할 수 있다고 생각합니다.

다음 아티클에서는 마이크로서비스의 외부 API 패턴을 29CM 에서 어떻게 적용할 것인지를 간략히 설명하도록 하겠습니다.

마이크로서비스의 외부 API 패턴 (2), (29CM 에서의 외부 API 패턴 적용 구상)

--

--