[아티클] 지금 프로젝트에 BFF가 필요 없는 이유

Pattern
3/21/2024

Backend For Frontend ?

So the main task of the BFF is to get data from different services, gather them and format them in the easiest form so the frontend will do the least effort to render these data. For that we defined a new interface.
 

TL;DR

1) Web, Android,iOS 등 여러 플랫폼을 지원해야 하는 경우
2) ‘화면에 필요한 데이터만 받는’ partial response을 받을 수 없는 경우
프론트엔드가 자신에게 필요한 데이터만 사용할 수 있도록 중간 서버를 만들어주는 것이 BFF입니다.
만약 Progressive Wep App을 만든다면? 단일한 플랫폼만 지원하면 될 텐데 굳이 BFF 도입이 필요하지 않습니다. 백엔드와 성실히 논의를 해보는 게 가장 좋을 것 같습니다.
 

BFF가 필요해요?

여러 플랫폼을 지원하는 경우라면, 백엔드 개발자는 각 플랫폼마다 원하는 요구사항을 일일이 충족시킬 수 없습니다. 결국 모두 충족할 수 있는 ‘충분한’ 데이터를 보내주는데요.
그러다보니 필요 없는 데이터가 늘 같이 딸려오는 것이죠!
notion image

NextJS page router 에서 BFF 예시

페이지에서 API를 필요한 데이터를 전부 호출하는 대신, API Routes(app router의 경우, Route Handlers)를 활용하여 필요한 데이터만 골라 가공합니다.
User, Message, Notifications 등 개별 API route가 있어 필요한 데이터만 반환합니다. 이를 Profile (프론트엔드가 바로 활용할 수 있는, 최종 포맷된 형태의) 데이터로 모읍니다. 아래 코드를 참고해보세요!
// pages/api/bff.ts import type { NextApiRequest, NextApiResponse } from 'next' import axios from 'axios' interface User { first_name: string last_name: string birthdate: string address: string created_at: string } interface Message { uid: string text: string created_at: string read: boolean } export interface Profile { name: string birthdate: Date address: string joined: Date last_seen: Date new_notifications: number new_messages: number new_friend_requests: number } export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { host } = req.headers const api_url = `${process.env.NODE_ENV === 'development' ? 'http' : 'https'}://${host}` let user_data = {} as User let messages_data = [] let notifications_data = [] let friend_requests_data = [] let latest_message = {} as Message try { const response = await axios.get(`${api_url}/api/user`) const api_data: any = response.data user_data = api_data } catch (e) { console.error('error: ', e) } try { const response = await axios.get(`${api_url}/api/messages`, { params: { action: 'read', read: false } }) const api_data: any = response.data messages_data = api_data } catch (e) { console.error('error: ', e) } try { const response = await axios.get(`${api_url}/api/messages`, { params: { action: 'get_latest' } }) const api_data: any = response.data latest_message = api_data } catch (e) { console.error('error: ', e) } try { const response = await axios.get(`${api_url}/api/notifications`, { params: { action: 'seen', seen: false } }) const api_data: any = response.data notifications_data = api_data } catch (e) { console.error('error: ', e) } try { const response = await axios.get(`${api_url}/api/friend_requests`) const api_data: any = response.data friend_requests_data = api_data } catch (e) { console.error('error: ', e) } const profile: Profile = { name: `${user_data.first_name} ${user_data.last_name}`, birthdate: new Date(user_data.birthdate), address: user_data.address, joined: new Date(user_data.created_at), last_seen: new Date(parseInt(latest_message.created_at) * 1000), new_notifications: notifications_data.length, new_messages: messages_data.length, new_friend_requests: friend_requests_data.length, } res.status(200).json(profile) }

요랬는데 (adelhamad의 bff-demo)

그러면 실제
const handleMsgRes = () => { setLoadingResponse(true) axios .get(`${apiPath}/messages`, { params: { action: 'read', read: false } }) .then((res) => { setLoadingResponse(false) setResponse(res.data) }) .catch(console.error) } const handleUsrRes = () => { setLoadingResponse(true) axios .get(`${apiPath}/user`) .then((res) => { setLoadingResponse(false) setResponse(res.data) }) .catch(console.error) } const handleNotiRes = () => { setLoadingResponse(true) axios .get(`${apiPath}/notifications`, { params: { action: 'seen', seen: false } }) .then((res) => { setLoadingResponse(false) setResponse(res.data) }) .catch(console.error) } const handleFrndRes = () => { setLoadingResponse(true) axios .get(`${apiPath}/friend_requests`) .then((res) => { setLoadingResponse(false) setResponse(res.data) }) .catch(console.error) }
 

요래됐습니다 (adelhamad의 bff-demo)

const handleBffRes = () => { setLoading(true) axios .get(`${apiPath}/bff`) .then((res) => { setLoading(false) const resProfile: Profile = res.data as Profile setProfile(resProfile) }) .catch(console.error) }
 

장점으로 마무리

BFF의 가장 큰 장점은 실제 비즈니스 로직의 구현과 응답 데이터를 클라이언트에서 요구되는 데이터로 파싱하는 두가지 관점을 분리하여 복잡도를 낮추고, 필요한 작업에 집중하기 쉬워지는 것이라고 생각합니다. - kakaoent 박수빈 (cheese)
비즈니스 로직을 구현할 때, 필요한 데이터마다 n번 호출하고 제각각이라 계산만 n번 한다면, 정신이 없어질 수도 있겠습니다. 이때 BFF를 적용하여 클라이언트에게 ‘맞춤형’ 데이터를 만들어 둡시다. NextJS의 Route Handlers를 요긴하게 활용해봅시다. 나(=프론트엔드)를 위한 중간 서버를 만들어 두고, 플랫폼에 맞게 커스텀된 데이터를 만끽해봅시다.
 

Reference

bff-demo
adelhamadUpdated Jul 30, 2024
 
©JIYOUNG CHOI, All rights reserved