JavaScript를 사용하여 마이크로서비스용 프론트엔드 최적화 BFF 구축
Olivia Novak
Dev Intern · Leapcell

소개
최신 웹 개발, 특히 마이크로서비스 아키텍처의 광범위한 채택으로 인해 프론트엔드 애플리케이션은 종종 수많은 과제에 직면하게 됩니다. 여러 백엔드 서비스를 직접 소비하면 클라이언트 측에서 복잡한 데이터 집계, 과도한 API 호출 및 일관성 없는 데이터 형식으로 이어질 수 있습니다. 이러한 복잡성은 프론트엔드 개발을 저해할 뿐만 아니라 애플리케이션 성능에도 부정적인 영향을 미칩니다. 특정 프론트엔드 요구에 맞게 이러한 백엔드 복잡성을 추상화하고 데이터 전달을 최적화할 수 있는 전용 중간 계층의 필요성이 매우 중요해졌습니다. 바로 여기서 백엔드 포 프론트엔드(BFF) 패턴이 빛을 발하며 이러한 문제에 대한 맞춤형 솔루션을 제공합니다. 이 글에서는 JavaScript를 사용하여 효율적이고 프론트엔드에 최적화된 BFF 레이어를 구축하는 방법을 자세히 살펴보고, 이를 통해 개발 프로세스를 간소화하고 사용자 경험을 향상시킬 것입니다.
핵심 개념 이해
구현 세부 사항을 자세히 살펴보기 전에 BFF 패턴을 이해하는 데 중요한 몇 가지 기본 용어를 명확히 하겠습니다.
- 마이크로서비스 아키텍처: 애플리케이션이 작고 독립적인 서비스 모음으로 구축되는 설계 접근 방식이며, 각 서비스는 자체 프로세스에서 실행되고 종종 HTTP API와 같은 경량 메커니즘을 통해 통신합니다. 이는 확장성, 복원력 및 독립적인 배포 가능성을 제공합니다.
- 백엔드 포 프론트엔드(BFF): 특정 프론트엔드 클라이언트(예: 웹 앱, 모바일 앱, 관리자 대시보드)에서 소비하기 위해 별도의 백엔드 서비스가 생성되는 설계 패턴입니다. 전통적인 모놀리식 API와 달리 BFF는 해당 프론트엔드에 필요한 특정 데이터 및 상호 작용 패턴에 최적화됩니다.
- API 게이트웨이: 모든 클라이언트에 대한 단일 진입점으로, 요청을 적절한 마이크로서비스로 라우팅하고 인증, 속도 제한 및 기타 공통 관심사를 관리합니다. BFF가 때때로 API 게이트웨이와 유사한 기능을 포함할 수 있지만, API 게이트웨이가 보다 일반적인 것과는 달리 BFF의 주요 초점은 프론트엔드별 최적화입니다.
- 데이터 집계: 여러 소스(이 경우 다른 마이크로서비스)에서 데이터를 수집하고 단일 포괄적인 응답으로 결합하는 프로세스입니다.
- 데이터 변환: 소비 애플리케이션의 요구에 더 잘 맞도록 데이터를 한 형식 또는 구조에서 다른 형식으로 변환하는 프로세스입니다.
BFF의 핵심 아이디어는 특정 프론트엔드에 대해 고도로 맞춤화된 API를 제공하여 백엔드 서비스와 프론트엔드 요구 사항 간의 "임피던스 불일치"를 줄이는 것입니다.
프론트엔드 최적화 BFF의 원칙
잘 설계된 BFF는 몇 가지 핵심 원칙을 준수합니다.
- 프론트엔드별 API: BFF는 특정 프론트엔드에 필요한 정확한 요구 사항에 맞춰진 API를 노출합니다. 이는 마이크로서비스의 원시 API를 노출하는 대신 프론트엔드에 필요한 데이터를 집계하고, 페이로드를 변환하고, 특정 비즈니스 로직을 구현하는 것을 의미합니다.
- 네트워크 오버헤드 감소: BFF는 여러 마이크로서비스에서 데이터를 단일 요청/응답 주기로 집계함으로써 프론트엔드가 수행해야 하는 HTTP 요청 수를 크게 줄여 로딩 시간과 성능을 향상시킵니다.
- 프론트엔드 개발 간소화: 프론트엔드 개발자는 여러 백엔드 호출을 조정하고 다양한 데이터 형식을 처리하는 복잡성에서 벗어나 더 간단하고 응집력 있는 API와 상호 작용할 수 있습니다.
- 디커플링: BFF는 프론트엔드를 기본 마이크로서비스 아키텍처에서 분리하는 데 도움이 됩니다. 마이크로서비스의 변경이 반드시 프론트엔드의 변경을 요구하는 것은 아니며, BFF의 변경만 요구합니다.
- 향상된 보안: BFF는 요청을 인증하고, 특정 데이터에 대한 액세스를 승인하고, 마이크로서비스로 전달하기 전에 입력을 정리하여 추가 보안 계층을 제공하는 게이트웨이 역할을 할 수 있습니다.
JavaScript로 BFF 구현하기
JavaScript는 클라이언트와 서버(Node.js) 모두에서 보편적으로 사용되므로 BFF 구축에 탁월한 선택입니다. Node.js의 비동기 이벤트 기반 특성은 여러 API 호출과 같은 I/O 집약적인 작업에 특히 적합합니다.
전자상거래 애플리케이션이 단일 제품 페이지에 제품 세부 정보, 고객 리뷰 및 추천 항목을 표시해야 하는 실제 예시를 고려해 보겠습니다. 마이크로서비스 아키텍처에서는 이러한 정보 조각이 각각 Product Service
, Review Service
, Recommendation Service
에서 나올 수 있습니다.
프로젝트 설정
BFF의 경우 웹 프레임워크로 Express.js
를 사용하고, 상위 서비스로 HTTP 요청을 보내기 위해 axios
를 사용합니다.
먼저 새 Node.js 프로젝트를 초기화합니다.
mkdir my-bff-service cd my-bff-service npm init -y npm install express axios dotenv
상위 서비스 URL을 저장하기 위해 .env
파일을 만듭니다.
PRODUCT_SERVICE_URL=http://localhost:3001/products
REVIEW_SERVICE_URL=http://localhost:3002/reviews
RECOMMENDATION_SERVICE_URL=http://localhost:3003/recommendations
기본 BFF 구조
server.js
가 BFF의 진입점 역할을 합니다.
require('dotenv').config(); // 환경 변수 로드 const express = require('express'); const axios = require('axios'); const app = express(); const PORT = process.env.PORT || 4000; app.use(express.json()); // JSON 본문 파싱 활성화 // 기본적인 오류 처리를 위한 미들웨어 app.use((err, req, res, next) => { console.error(err.stack); res.status(500).send('Something broke!'); }); // 특정 프론트엔드 요구 사항에 대한 라우트 정의 app.get('/api/product-details/:productId', async (req, res, next) => { try { const productId = req.params.productId; // 제품 정보 가져오기 const productResponse = await axios.get(`${process.env.PRODUCT_SERVICE_URL}/${productId}`); const productData = productResponse.data; // 제품에 대한 리뷰 가져오기 const reviewsResponse = await axios.get(`${process.env.REVIEW_SERVICE_URL}?productId=${productId}`); const reviewsData = reviewsResponse.data; // 제품에 대한 추천 항목 가져오기 (예: 제품 카테고리 기반) // 실제 시나리오에서는 더 복잡한 호출일 수 있습니다. const recommendationsResponse = await axios.get(`${process.env.RECOMMENDATION_SERVICE_URL}?category=${productData.category || 'general'}`); const recommendationsData = recommendationsResponse.data; // 프론트엔드를 위한 데이터 집계 및 변환 const consolidatedData = { id: productData.id, name: productData.name, description: productData.description, price: productData.price, imageUrl: productData.imageUrl, category: productData.category, reviews: reviewsData.map(review => ({ reviewer: review.user, rating: review.rating, comment: review.comment, date: new Date(review.timestamp).toLocaleDateString() })), // 예시 변환 recommendedProducts: recommendationsData.map(reco => ({ id: reco.id, name: reco.title, thumbnail: reco.image })) // 예시 변환 }; res.json(consolidatedData); } catch (error) { console.error('Error fetching product details:', error.message); // 오류를 오류 처리 미들웨어로 전달 next(error); } }); // BFF 서버 시작 app.listen(PORT, () => { console.log(`BFF Service running on port ${PORT}`); console.log(`Access product details at http://localhost:${PORT}/api/product-details/:productId`); });
설명 및 개선 사항
-
환경 변수:
dotenv
를 사용하여.env
파일에서 서비스 URL을 로드하여 민감한 정보를 코드베이스에서 제외하고 구성 관리를 더 쉽게 합니다. -
Express 라우트:
/api/product-details/:productId
엔드포인트는 프론트엔드의 제품 페이지에 대해 특별히 설계되었습니다.productId
매개변수를 예상합니다. -
병렬 API 호출: 성능 향상을 위해 한 서비스에서 가져온 데이터가 다른 서비스의 출력에 의존하지 않는 경우 병렬 API 호출을 고려하십시오. 예를 들어,
Promise.all
을 사용할 수 있습니다.// ... 라우트 핸들러 내부 const [productResponse, reviewsResponse, recommendationsResponse] = await Promise.all([ axios.get(`${process.env.PRODUCT_SERVICE_URL}/${productId}`), axios.get(`${process.env.REVIEW_SERVICE_URL}?productId=${productId}`), axios.get(`${process.env.RECOMMENDATION_SERVICE_URL}?category=${productCategory}`) // 카테고리는 사전 가져오기 또는 기본값 필요할 수 있음 ]); // ... 나머지 코드
참고: 예시에서
recommendationsResponse
는productData.category
에 의존하므로 세 가지에 대한 직접적인Promise.all
은 초기 제품 가져오기 또는 기본 카테고리가 없으면 실현 가능하지 않습니다. 보다 강력한 병렬 전략은 제품 데이터를 먼저 가져온 다음 제품 카테고리를 사용하여 리뷰 및 추천 항목을 병렬로 가져오는 것입니다. -
데이터 집계 및 변환:
- BFF는
Product Service
,Review Service
,Recommendation Service
에 개별 API 호출을 수행합니다. - 그런 다음 응답을 단일
consolidatedData
객체로 집계합니다. - 결정적으로 데이터를 변환합니다. 예를 들어,
reviewsData
에는 백엔드에서timestamp
필드가 포함될 수 있지만 프론트엔드는 형식화된date
를 선호할 수 있습니다. 마찬가지로recommendations
는title
및image
를 사용할 수 있지만 프론트엔드는name
및thumbnail
을 예상할 수 있습니다. 이렇게 하면 프론트엔드가 필요한 정확한 형태로 데이터를 수신하여 프론트엔드 측 데이터 조작을 줄입니다.
- BFF는
-
오류 처리: 기본적인 오류 처리가 포함되어 있으며, BFF가 상위 서비스의 실패를 우아하게 관리하고 프론트엔드에 의미 있는 피드백을 제공하는 방법을 보여줍니다.
-
인증/권한 부여: 명시적으로 표시되지는 않았지만 BFF는 프론트엔드별 인증 및 권한 부여 로직을 구현하기에 이상적인 장소입니다. 예를 들어, 요청을 전달하기 전에 사용자 토큰을 검증하거나 마이크로서비스에 대한 사용자별 헤더를 주입할 수 있습니다.
-
캐싱: Node.js BFF는 (예: 인메모리 또는 Redis) 캐싱 메커니즘을 구현하여 자주 요청되는 데이터를 저장하고, 마이크로서비스에 대한 로드를 더욱 줄이며, 응답 시간을 개선할 수 있습니다.
애플리케이션 시나리오
BFF는 다음과 같은 시나리오에서 특히 유용합니다.
- 복잡한 UI 보기: 단일 프론트엔드 화면이 여러 다른 마이크로서비스의 데이터가 필요한 경우.
- 모바일별 최적화: 대역폭 제약이 있고 더 작고 집계된 페이로드를 선호하는 모바일 장치에 대한 응답 맞춤화.
- 레거시 시스템 통합: 오래되고 덜 유연한 시스템과 통합할 때 BFF는 복잡성을 추상화하고 프론트엔드에 최신 API를 제공할 수 있습니다.
- 여러 프론트엔드 클라이언트: 웹 앱, 모바일 앱, 그리고 관리자 대시보드가 있고, 각기 약간 다른 데이터나 작업을 요구하는 경우, 각 클라이언트마다 전용 BFF를 가질 수 있습니다.
결론
백엔드 포 프론트엔드(BFF) 패턴, 특히 JavaScript와 Node.js를 사용하여 구현된 BFF는 마이크로서비스 세계에서 프론트엔드 개발의 과제에 대한 우아하고 강력한 솔루션을 제공합니다. 전용 중계자 역할을 함으로써 BFF는 데이터 집계를 간소화하고, 데이터 변환을 단순화하며, 궁극적으로 고도로 최적화된 데이터 페이로드를 전달하여 개발자 생산성과 최종 사용자 경험을 모두 향상시킵니다. BFF 계층을 채택하면 프론트엔드가 프레젠테이션에 집중할 수 있고, 백엔드 마이크로서비스는 독립적으로 핵심 도메인에 집중할 수 있습니다. JavaScript를 사용하여 BFF 계층을 구축하면 프론트엔드의 마이크로서비스 상호 작용이 간소화되어 최적의 성능과 개발자 경험을 제공합니다.