시작하며
안녕하세요. 카카오페이 오프라인결제서비스유닛에서 백엔드를 개발하고 있는 Happy라고 합니다.
카카오페이 오프라인 결제 서비스에서 새롭게 추가된 ‘내 주변 매장 찾기' 서비스를 개발하기 위해 BFF 서버 구조에서 WebFlux, 코루틴으로 비동기 API 서버 개발 경험에 대해 이야기하려고 합니다.
서비스 소개: 내 주변 매장 찾기
서비스에 도입한 기술을 이해하기에 앞서, 내 주변 매장 찾기 서비스에 대해 소개드리려고 합니다. 내 주변 매장 찾기 서비스는 사용자가 사용자 주변 카카오페이 매장들을 쉽게 찾고, 매장의 혜택과 멤버십 정보들을 쉽게 확인할 수 있는 서비스입니다. 9월 20일부터 카카오페이 결제 바코드 화면에서 이용하실 수 있습니다.
간단해 보이는 서비스이지만 한 화면에서 위치 정보, 매장 정보, 혜택, 멤버십, 결제 정보 등 다양한 정보를 보여주고 있는데요. 서비스별로 정보가 산재되어 있는 MSA 환경에서 최적의 방법으로 정보를 가져오기 위해 비동기 방식으로 API를 개발하고, 이 데이터를 효율적으로 프론트에 전달하기 위해 BFF(Backend For Frontend) 패턴을 적용했습니다.
그렇다면 BFF란 무엇일까요?
BFF(Backend For Frontend)란
BFF란 Backend For Frontend의 줄임말로, 프론트엔드에 표현될 데이터를 위한 백엔드 즉, 프론트엔드 데이터에 대한 책임을 백엔드가 가진다는 것을 의미합니다. 백엔드는 당연히 프론트엔드에 표현되는 데이터를 제공하니, 기존 방식과 BFF가 무엇이 다른지 아직은 감이 안 오실 텐데요. BFF는 단순히 데이터를 제공하는 것에서 나아가 프론트엔드 친화적으로 데이터를 제공합니다.
프론트엔드 친화적인 API 서버
프론트엔드 개발자와 백엔드 개발자가 모여 API 스펙을 정의할 때, 어디서 처리해야 할지 애매한 포인트들이 있습니다. 아래처럼 즐겨찾기 한 매장이 보이는 화면이 있고, 여기에는 결제 수단 정보들이 보이게 됩니다. 결제 수단은 매장에서 카카오페이로 오프라인 결제를 할 때 사용할 수 있는 수단에 대한 정보이고, 결제와 직결되기 때문에 오프라인 결제 서버가 책임을 지고 관리하고 있습니다. BFF 서버에서는 결제 수단 정보를 가지고 프론트엔드에 보내줘야 하는데요. 오프라인 결제 서버에서는 이 정보를 아래와 같이 제공합니다.
{
...
"payment_method": {
"money_enabled": true,
"point_enabled": false,
"card_enabled": true
}
...
}
이때 boolean 값들로 '페이머니', '페이포인트', '신용카드'라는 결제 수단 정보를 문자열로 표기하는 작업은 프론트엔드에서 해야 할까요? 백엔드에서 해야 할까요? 둘 다 할 수 있지만, BFF는 프론트엔드 친화적인 API 서버이기에 백엔드가 그 역할을 하게 됩니다. 화면에 보이는 데이터를 가공하는 책임은 서버가 지고, 프론트엔드는 UI를 그리는데 집중하게 됩니다. 따라서 아래와 같이 보이게 됩니다.
오프라인 결제 서비스는 결제 수단에 대한 데이터를 제공할 뿐, 프론트엔드에서 보이는 형태로 데이터를 제공할 책임이 없습니다. 따라서 BFF가 프론트엔드의 책임을 대신하여 데이터를 제공하게 됩니다. 프론트엔드에서 데이터를 가공하면, 사용자 모바일/데스크톱 컴퓨터 자원을 쓸 수 있다는 점은 좋지만 모바일의 경우 배터리 소모 문제를 고려해야 할 수도 있습니다. 이 예시의 경우엔 간단하기 때문에 많은 자원을 사용하지 않지만요. 하지만 복잡도 관점에서 프론트엔드가 UI 로직에 집중할 수 있도록 하기 때문에 유지보수와 캡슐화 관점에서 이익이 큽니다.
다음 예시로는 여러 조건에 따라 멤버십, 결제 버튼 영역이 각기 다르게 프론트엔드에 표현되어야 하는 경우에 대해 보여드리겠습니다.
- 매장의 카카오페이 멤버십 가맹 여부
- 유저의 카카오페이 멤버십 가입 여부
- 유저가 해당 매장의 카카오페이 멤버십 연동 여부
- 카카오페이 가입 여부(비회원 페이지인지)
예시 9가지만 가져왔지만 실제 멤버십의 할인 타입과 결제 시 멤버십이 자동으로 적립되는지 등에 따라 경우가 더욱 다양합니다. 이에 서버에서는 프론트엔드에 아래와 같은 방식으로 데이터를 전달합니다.
데이터의 형태가 거의 화면에서 필요한 컴포넌트의 형태와 유사하지요? 이처럼 여러 조건에 대한 처리는 서버가 처리하고 프론트엔드에 보일 데이터만 제공하는 것이 BFF의 역할입니다.
API 조합기 역할
만약 프론트엔드에서 바라보는 서버가 1개라면 BFF는 필요 없습니다. BFF는 MSA 환경에서 다양한 서비스들의 데이터를 조합해 프론트엔드에 내려주는 ‘API 조합기’ 역할을 해야 합니다.
앞서 프론트엔드가 데이터를 처리할 때, 서버가 데이터를 처리할 때를 비교해보았는데요. 이번에는 프론트엔드가 API 조합기 역할을 할 때, 서버가 API 조합기 역할을 할 때를 비교해보도록 하겠습니다.
프론트엔드가 API 조합기 역할을 하게 되면, 유저의 모바일 폰에서 각 서비스로 호출이 발생합니다. 보통 모바일 인터넷은 대역폭이 좁고 불안정한데요. 모바일 인터넷의 지연시간은 LAN보다 100배는 더 깁니다. 이때 서비스 A, B, C로의 통신이 모두 성공해야 하며 각 서비스로 호출이 발생할 때마다 그 앞의 API Gateway를 지나게 됩니다. 카카오페이의 경우 프론트엔드에서 인증 토큰을 넣어 서비스에 요청하게 되면 그 앞의 API Gateway에서 토큰을 유저 계정 정보로 변환하여 서비스에 넘겨주게 됩니다. 고로 서비스 A, B, C 각각의 호출마다 인증 서버를 거치게 되겠지요. 위 예시처럼 프론트엔드가 API 조합기 역할을 한다면, 느린 네트워크를 통해 왕복 3번의 통신이 필요하고 서비스별로 필요한 인증을 3번 거치게 된다는 단점이 발생합니다.
그렇다면 프론트엔드가 BFF만 바라보는 경우는 어떨까요? 프론트엔드가 BFF로 요청을 보내면, API Gateway를 통해 인증을 1회 조회하게 됩니다. BFF에서 각 서비스에 계정 정보를 활용하여 필요한 요청을 보내게 되겠죠. BFF에서 다른 마이크로 서비스와 통신할 때는 방화벽 내부에 있고, 고속 LAN 환경에서 통신하기 때문에 더 빠르게 데이터를 모아올 수 있습니다. 그리고 BFF를 적용하면 추후 캐시를 적용할 때도 쉽게 반영할 수 있다는 장점도 있습니다.
다양한 환경에서 구동된다면 GraphQL 고려하기
BFF는 마이크로 서비스로 나눠진 서버들 사이의 데이터를 프론트엔드 친화적으로 내려주게 되는데요. 이때 함께 사용되는 기술이 GraphQL입니다. 만약, 모바일과 데스크톱으로 서비스 화면이 여러 개로 나눠져 있다면 BFF에 GraphQL을 검토해보는 것이 좋습니다. GraphQL은 프론트엔드에서 질의를 통해, 자신에게 필요한 데이터를 백엔드로부터 질의해오는 기술인데요. 모바일과 데스크톱의 경우 UI 구성이 다르기 때문에 GraphQL을 제공한다면 서버에서는 API를 사용자 환경별로 즉, 모바일과 데스크톱에 따라 다르게 제공하지 않아도 됩니다. 유지보수성이 좋아지는 것은 물론이겠죠.
그러나 저희는 GraphQL을 도입하진 않았습니다. 내 주변 매장 찾기를 비롯한 카카오페이의 많은 서비스들에는 모바일에서 보이는 화면만 존재하기 때문에 이번 프로젝트에서 GraphQL을 도입할 이유가 없었기 때문입니다. 만약 다양한 환경에서 구동되는 서비스를 개발하고 계신다면 검토해보시면 좋을 것 같습니다.