본문 바로가기

카테고리 없음

BFF - Design Pattern with Next.js

 

BFF - Backend for Frontend Design Pattern with Next.js

Intro These days microservice architecture becoming more and more popular, and if you...

dev.to

위 글을 번역했습니다.

Intro

요즘 마이크로서비스 아키텍처가 점점 더 대중화되고 있으며, 이 아키텍처를 채택한 프로젝트에서 일했다면 프론트엔드 개발자로서 다음 시나리오 중 하나에 직면했을 것입니다.

 

- 여러 플랫폼(웹, 모바일 앱, 스마트워치)을 지원하며 각 플랫폼마다 특정 데이터 요구 사항이 존재합니다.

- 여러 서비스에서 API를 호출하여 하나의 사용자 인터페이스를 구축합니다. 

- 여러 API 호출의 응답을 조작, 혼합 및 일치시켜 원하는 형태의 데이터에 도달합니다.

- 전혀 필요하지 않은 API로부터 불필요한 데이터를 수신합니다. 

- 서로 다른 데이터 유형으로 서로 다른 서비스에서 동일한 정보를 수신하는 경우, 예를 들어 한 서비스는 날짜를 epoch로 전송하고 다른 서비스는 Linux 타임스탬프로 전송할 수 있습니다.

- 복잡한 계산이나 비즈니스 로직을 프론트엔드에서 직접 작성해야 하는 경우도 있습니다. 

 

코드 베이스가 커지고 복잡해지면 정돈된 상태를 유지하기 어려워지고, 어느 순간 코드베이스가 통제 불능 상태가 되어 버그가 숨어 있는 복잡성을 발견할 수도 있습니다. 

 

일반적으로 프론트엔드 코드는 매우 간단하고 직관적이며 읽기 쉬워야 하며, 특히 렌더링 중에 UI 레이어에서 복잡한 연산을 수행하는 것을 피해야 합니다. 그렇지 않으면 브라우저 리소스를 훨씬 더 많이 사용하게 되어 성능이 저하될 수 있습니다.

General Purpose API

일반 API에는 소비자 애플리케이션에 쓸모없는 불필요한 데이터가 포함되어 있습니다. 이는 특히 스마트워치와 같은 일부 프론트엔드에 가능한 한 작은 응답을 제공해야 할 때 매우 중요할 수 있습니다. 이러한 각 프론트엔드는 백엔드에서 전달되는 데이터에 대한 특정 요구사항이 있을 수 있습니다. 그리고 이들 모두 동일한 API를 호출하기 때문에 백엔드 개발자는 모든 프론트엔드 요구 사항을 충족하기 위해 가능한 모든 데이터를 뱉어내려고 노력할 것입니다.

What is BFF Design Pattern

이 패턴은 Sam Newman이 처음 사용했습니다.

BFF를 구현함으로써 우리는 프론트엔드를 백엔드와 분리된 상태로 유지하려고 합니다. BFF는 애초에 프론트엔드 요구 사항을 충족하기 위해 존재하며 이상적으로는 프론트엔드 개발자가 구축해야 하기 때문에 프론트엔드와 긴밀하게 결합되어야 합니다. 대부분의 경우 각 프론트엔드마다 하나의 BFF가 있어야 하며, 그런 다음 해당 프론트엔드의 요구 사항에 따라 BFF를 사용자 정의하고 미세 조정할 수 있습니다. 요구 사항이 매우 유사한 경우 하나의 BFF를 여러 프론트엔드와 공유할 수 있습니다. 예를 들어 iOS와 Android용 BFF가 SoundCloud에서 채택한 방식과 같이, 이렇게 하면 BFF간에 코드가 중복되는 것을 방지할 수 있습니다. 

 

One BFF per frontend

 

Sharing BFF for some frontends

Not an API gateway: BFF가 API 게이트웨이와 매우 유사하다고 생각할 수 있지만, API 게이트웨이의 주된 이뉴는 소비자와 다른 모든 마이크로서비스 사이의 역방향 프록시 역할을 하는 것이지 특정 프론트엔드 요구에 따라 응답을 사용자 정의하는 것이 아니기 때문에 그렇지 않습니다. 또한 API 게이트웨이는 BFF가 특정 프론트엔드에 국한되어 있든 상관없이 모든 백엔드 서비스에 도달해야하는 모든 사용자에게 단일 진입점입니다. 

 

BFF는 프론트엔드에서 많은 복잡성을 숨겨서 앱이 새로운 변화에 더 탄력적으로 대응할 수 있도록 합니다. 또한 다른 서비스에서 REST 또는 SOAP를 사용하더라도 GraphQL과 같이 가장 익숙한 프로토콜을 자유롭게 사용할 수 있습니다. BFF를 사용하면 프론트엔드 관련 단위 테스트도 추상화할 수 있습니다. 단, 하나의 프론트엔드만 지원하는 경우에는 BFF 패턴이 유용하지 않다는 점에 유의하세요.

With Multiple Backend services

소셜 플랫폼의 사용자 프로필 페이지를 구축해야 하는데 이 플랫폼이 마이크로 서비스 아키텍처로 구축되어 있다고 가정하면 다음과 같은 모양이 됩니다.

여기에서 볼 수 있듯이 웹 UI는 프로필 페이지를 구축하기 위해 여러 서비스의 API를 호출한다. 먼저 사용자에 대한 데이터를 가져와야 하며, 가져온 사용자 이름 또는 사용자 ID를 기반으로 나머지 결과를 가져오기 위해 두 번 이상 호출을 수행해야 한다. 응답에는 이 UI를 구축하는데 필요하지 않은 많은 데이터가 포함될 수 있으며 후자의 호출을 병렬로 호출하여 더 짧은 시간에 실행한 다음 응답을 병합하고 이 사용자 프로필 페이지를 구축하는데 필요한 데이터만 수집해야 합니다. 힘들어보이시죠? 훨씬 더 복잡한 UI와 훨씬 더 많은 서비스에서 데이터를 소비하는 유사한 시나리오가 있다고 상상해보세요.

 

대신 하나의 API만 호출하여 이 페이지를 구축하는데 필요한 데이터만 가져오는 것이 더 효율적이며, 이것이 바로 BFF 계층에서 이루어져야 하는 일입니다. 이러한 방식으로 프론트엔드에서 이 모든 복잡성을 추상화했으며, 여기서 프론트엔드의 역할은 반환도니 데이터를 표시하는 것뿐입니다. 이 글의 뒷부분에서 동일한 문제에 대한 예제를 살펴보겠습니다. 

API Versioning and A/B testing

동일한 서비스에 대해 서로 다른 버전의 API를 지원하는 경우가 있는데, 이 경우 프론트엔드에서 이를 추상화하여 BFF 내부에서 이를 처리하는 것이 훨씬 쉽습니다. 이렇게 하ㄴ면 프론트엔드에서 버전을 인식하지 않고 어떤 버전이든 UI를 렌더링할 수 있습니다. 예를 들어 사용자 객체로 특정 사용자에게 필요한 버전을 반환한 다음 BFF가 다른 API 버전을 처리하도록 하는 등 A/B 테스트 캠페인을 실행하려는 경우에도 유용할 수 있습니다.

Nice Additions, Taking it further

이제 BFF 레이어를 추가한 후에는 해당 프론트엔드에 특별히 할 수 있는 멋진 작업이 많이 있습니다.

- 보안: 프론트엔드에 필요한 것만 전송하기 때문에 공격자가 악용할 수 있는 불필요하거나 민감한 데이터를 많이 숨길 수 있습니다. 

- 캐싱: 예를 들어 redis에 직접 연결하여 API응답을 캐싱한 다음 마이크로서비스를 호출하는 대신 가능한 경우 캐싱에서 결과를 제공할 수 있습니다. 

- 오류 처리: 여러 서비스가 서로 다른 방식으로 오류를 처리할 수 있으며, BFF에서는 오류 발생시 프론트엔드에 일관된 응답을 제공하는 통합된 방법을 정의할 수 있습니다. 

- 접근 제어

- 로깅

- Web Sockets

- etc

 

가능한 한 단순하게 유지하고 일반적인 문제를 해결하는 것이 아니라 특정 프론트엔드의 문제를 해결하는 이 BFF를 구축한 주된 이유에 충실하는 것이 좋다고 생각합니다. 코드 베이스가 커짐에 따라 BFF 내부에 범용적인 작은 서비스를 구현하게 될 수도 있으므로(사운드 클라우드가 이 문제에 직면했습니다) 처음부터 정의한 대로 BFF의 범위를 유지하려고 노력하세요.

With Next.js

Next.js를 사용하면 다음과 같은 몇 가지 이점을 즉시 누릴 수 있습니다.

- 배포 횟수 감소: 기본적으로 Next.js와 통합되므로 BFF를 별도로 배포할 필요가 없습니다.

- Next.js의 백엔드 레이어를 사용하면 BFF가 프론트엔드에 긴밀하게 결합되며, 이것이 바로 우리에게 꼭 필요한 기능입니다. 

- type 정의 및 유틸리티 함수와 같은 코드를 BFF와 프론트엔드 간에 매우 쉽게 공유할 수 있다.

 

BFF가 작동하는 방식을 보여드리기 위해 Next.js를 사용하여 마이크로서비스 동작을 시뮬레이션할 것이므로 다음 각각에 대해 하나의 파일이 필요합니다:

- 메시징 서비스에는 다음이 포함됩니다.

  - '읽음' 필터를 기반으로 모든 메시지를 가져오는 엔드포인트 하나, 두 가지 값(참 거짓)을 사용할 수 있습니다.

  - 가장 최근에 받은 메시지를 가져오는 엔드포인트 하나(마지막으로 본 메시지 가져오기).

- 알림 서비스에는 "본" 필터를 기반으로 모든 메시지를 가져오는 엔드포인트가 하나 포함되며 두 개의 값(1, 0)을 사용할 수 있습니다.

- 친구 서비스에는 보류 중인 모든 친구 요청을 가져오는 하나의 엔드포인트가 포함됩니다. 

- BFF 자체는 이러한 모든 서비스의 API를 소비합니다. 

 

먼저 각 서비스에서 데이터가 어떻게 표시되는지 살펴봅시다.

Message object

    {
        "uid": "263f4178-39c6-4b41-ad5b-962a94682ceb",
        "text": "Morbi odio odio, elementum eu, interdum eu, tincidunt in, leo. Maecenas pulvinar lobortis est. Phasellus sit amet erat. Nulla tempus.",
        "created_at": "1634320826",
        "read": false
    }

 

Notification object

    {
        "uid": "ee7cd9df-2409-46af-9016-83a1b951f2fa",
        "text": "Vestibulum quam sapien, varius ut, blandit non, interdum in, ante.",
        "created_at": "1617738727000",
        "seen": 0
    }

Person object

    {
        "id": 1,
        "first_name": "Marillin",
        "last_name": "Pollicott",
        "birthdate": "4/20/2021",
        "email": "mpollicott0@wikispaces.com",
        "gender": "Male",
        "ip_address": "105.134.26.93",
        "address": "2132 Leroy Park",
        "created_at": "9/13/2021"
    }

Desired profile object

{
    "name": "John Doe",
    "birthdate": "2020-11-17T00:00:00.000Z",
    "address": "242 Vermont Parkway",
    "joined": "2021-08-27T00:00:00.000Z",
    "last_seen": "2021-10-15T18:00:26.000Z",
    "new_notifications": 61,
    "new_messages": 56,
    "new_friend_requests": 15
}

 

메시지 객체에서는 초 단위의 Linux 타임스탬프이고 알림 서비스에서는 밀리초 단위의 Linux 타임스탬프인 반면 친구 서비스에는 단순한 날짜 문자열인 것처럼 각 서비스마다 데이터 유형이 다르다는 것을 알 수 있으며, 실제로 우리가 원하는 것은 시간대가 0 UTC 오프셋으로 설정도니 단순화된 확장 ISO 형식이므로 프론트엔드에서 원하는 대로 서식을 지정할 수 있습니다. 또한 메시지 서비스에는 참 거짓으로 표현되고 알림 서비스에서는 (1, 0)으로 표시되는 것을 볼 수 있으며, 자세히 살펴보면 다른 차이점도 존재합니다.

 

따라서 사람 객체에는 이름과 성이 다른 속성으로 있지만 프론트엔드에서는 두 속성 조합을 표시하는 것을 알 수 있습니다. 따라서 BFF의 주요 임무는 다양한 서비스에서 데이터를 가져와서 수집하고 가장 쉬운 형식으로 포맷하여 프론트엔드에서 이러한 데이터를 렌더링하는데 최소한의 노력을 기울일 수 있도록 하는 것입니다. 이를 위해 새로운 인터페이스(프로필)을 정의했습니다.

 

interface Profile {
   name: string
   birthdate: Date
   address: string
   joined: Date
   last_seen: Date
   new_notifications: number
   new_messages: number
   new_friend_requests: number
}

 

이 인터페이스에서는 원하는 데이터와 프론트엔드에 반환되는 응답이 항상 올바르도록 보장하기 위해 어떤 유형인지 설명했습니다. 

 

You can check the code on this link
The demo on this link

 

BFF Demo

Birthdate: Address: Joined: 2021-10-22 Last Seen (Last Recieved Message): 2021-10-22 New Notifications: New Messages: New Friend Requests: View BFF ResponseView User ResponseView Messages Service ResponseView Notifications Service ResponseView Friends Serv

backend-for-frontend.vercel.app

 

TL;DR

- BFF는 프론트엔드마다 해당 프론트엔드의 요구사항만을 충족하는 새로운 백엔드를 만드는데 중점을 둡니다.

- BFF는 여러 서비스에서 API를 호출하여 필요한 최소한의 응답을 생성합니다.

- 프론트엔드는 UI를 렌더링하는 데 필요한 것만 가져옵니다.