문제상황
- 모바일, 데스크톱, 웹 등 클라이언트의 환경별로 데이터의 요구사항이 다르므로 클라이언트 단에서 코드 변경 로직이 필요하다.
- 클라이언트의 복잡한 코드로직으로 인해 유지보수가 어렵고 성능도 떨어진다.
BFF - Backend for Frontend Pattern
Backends for Frontends 패턴은 특정한 프론트엔트 어플리케이션 환경에 따른 각각의 API Gateway를 제공하는 것을 말한다. 각각의 API Gateway에서 UI가 서로 다른 개별적인 클라이언트가 필요로 하는 데이터만 받아볼수 있도록 노출한다. 여러개의 프론트엔트 인터페이스가 각각의 BFF 서버를 호출하기 때문에 부하도 줄일 수 있고 프론트엔드에서 데이터 가공을 위한 따로 로직을 작성할 필요도 없다.
AWS에서 Event-driven BFF
위의 다이어그램은 pub-sub기반의 event driven architecture를 보여준다.
오른쪽의 도메인 publisher는 각각 고유의 도메인별 집계 데이터베이스를 보유하고 있고, 왼쪽의 BFF subscriber는 각각 고유한 사용자별 데이터베이스를 가지고 있다. 중간의 event bus는 도메인의 상태변경 사항을 전파하고 publisher와 subscriber를 분리하게 한다.
event-driven BFF에서는 다음과 같은 서비스도 사용될 수 있다.
- AWS DynamoDB : NoSQL기반으로 데이터를 저장하고 데이터의 변화를 감지할 수 있다(Change Data Capture - CDC).
- AWS Lambda : 요청을 처리하고 변화가 감지된 데이터(CDC Stream)를 API와 통합하기 위해 사용할 수있다.
- AWS Cognito : Authentication, Authorization, API 보호를 위해서 사용한다. 회원가입, 로그인, 접근 제어 등에 사용할 수있다.
Building an event-driven REST BFF using API Gateway
위의 아키텍처 다이어그램은 DDD(Domain-Driven-Design)개념을 사용하여 API Gateway Websocket API를 활용하여 user에게 이벤트 기반 UI를 만드는 방법을 보여준다.
- BFF Event Consumer를 통해 이벤트를 포착하게 한다. AWS DynamoDB에서 비정규화된 데이터의 업데이트를 예측한다.
- UI로드를 위해 클라이언트는 AWS Cognito를 통해 인증을 거치고, API Gateway로 만들어진 BFF API를 통해 데이터를 받아온다. 데이터는 직접 API Gateway를 통해 가져오거나 Lambda로 구축된 BFF 쿼리 핸들러를 통해 DynamoDB에서 가져온다.
- 클라이언트에서는 API Gateway에서 제공하는 BFF WebSocket 엔드포인트에 연결함으로써 이후의 모든 데이터 변경을 항상 구독하도록 한다.
- BFF event consumer를 통해 어플리케이션의 모든 관련 이벤트를 소비하고 처리한다. 이러한 Consumers는 실시간으로 BFF DB(DynamoDB)를 업데이트 한다.
- AWS DynamoDB Stream을 통해 BFF DB의 데이터 변경으로 인한 모든 이벤트를 구독한다. 그리고 새로운 스트림 레코드가 감지될 때 비동기적으로 BFF 스트림 핸들러 람다를 호출할 수 있도록 AWS Lambda를 트리거에 등록한다.
- BFF stream handler(Lambda)가 API Gateway의 WebSocket에 연결된 클라이언트에게 알림을 푸시한다.
- API Gateway의 알림을 클라이언트가 수신하면, UI를 업데이트한다.
Building an event-driven GraphQL BFF using AppSync
GraphQL을 사용하면 프론트엔드에서 단일 GraphQL엔드포인트에서 여러 데이터베이스, 마이크로서비스, API를 쿼리할수 있기 때문에 BFF 패턴에서 GraphQL을 많이 사용하고 있다.
AWS AppSync를 통해 DynamoDB, Lambda등과 같은 데이터 소스들과 안전하게 연결하고 GraphQL API를 쉽게 개발할 수 있다. AWS AppSync는 자동으로 GraphQL API의 실행엔진을 API요청 양에 따라 확장 축소할 수 있다. 또한 캐시를 통한 성능 향상, 오프라인 클라이언트를 동기화상태로 유지하는 클라이언트 사이드 데이터 저장소를 지원한다. 그리고 웹소켓을 통한 구독을 통해 실시간 업데이트를 지원한다.
- BFF Event Consumer를 통해 어플리케이션의 이벤트를 포착한다. 프론트엔트에서 소비할수 있도록 DynamoDB에서 비정규화된 데이터의 보기를 보관하는 담당을 한다.
- 프론트엔드에서 AWS Congino를 통해 인증을 거치고 AWS AppSync로 구축된 BFF API를 호출하여 GraphQL로 데이터 쿼리를 하여 UI를 로드한다. AppSync를 통해 직접 또는 Lambda를 통해 만들어진 BFF 쿼리 핸들러를 통해 DynamoDB에서 데이터를 가져온다.
- 프론트엔드에서는 웹소켓을 통해 AWS AppSync를 구독하여 이후의 모든 데이터 변경을 구독한다.
- BFF Event Consumer를 통해 어플리케이션의 모든 이벤트를 소비하고 처리한다. Consumer는 실시간으로 BFF DB(DynamoDB)를 업데이트 한다.
- DynamoDB 스트림을 사용하여 BFF DB(DynamoDB)의 데이터 변경으로 인한 모든 이벤트를 구독한 다음, 새로운 스트림 레코드가 감지되면 비동기적으로 BFF 스트림 핸들러 람다 함수를 호출하도록 AWS Lambda에 트리거를 등록한다.
- BFF Stream Hanlder (Lambda)는 구독이 강제적으로 트리거 되도록 생성된 AppSync의 GraphQL 스키마의 빈 mutation을 호출하고 클라이언트에게 알림을 보낸다.
- 클라이언트가 AppSync의 변경 알림을 받으면 UI를 업데이트 할 수 있다. AppSync는 실시간 데이터, 연결, 확장성, 팬아웃, 브로드캐스팅을 간편한 웹소켓 환경으로 제공한다. AppSync를 통해 개발자는 복잡한 웹소켓 연결을 관리할 필요 없이 어플리케이션 요구사항에 집중할 수 있다.
AWS AppSync
https://docs.aws.amazon.com/appsync/latest/devguide/what-is-appsync.html
참고한 글, 함께 읽어보면 좋은 글
https://samnewman.io/patterns/architectural/bff/
https://medium.com/design-microservices-architecture-with-patterns/backends-for-frontends-pattern-bff-7ccd9182c6a1
https://aws.amazon.com/blogs/mobile/backends-for-frontends-pattern/
https://medium.com/@saabeilin/the-backend-for-frontend-pattern-in-distributed-event-driven-systems-803ba2ecb58e