모든 프런트엔드 요구사항을 위한 단일 Node.js BFF
Olivia Novak
Dev Intern · Leapcell

소개
오늘날 빠르게 변화하는 디지털 환경에서 애플리케이션은 단일 플랫폼에 국한되는 경우가 드뭅니다. 우리는 웹 브라우저, 모바일 앱, 때로는 데스크톱 클라이언트를 통해 서비스와 상호 작용합니다. 이러한 멀티 플랫폼 현실은 백엔드 개발자에게 종종 상당한 문제를 제기합니다. 즉, 각 고유한 프런트엔드의 고유한 데이터 및 상호 작용 요구사항을 효율적으로 충족하는 단일 모놀리식 API를 만드는 방법입니다. "모든 경우에 맞는" API를 강제로 적용하려고 하면 일반적으로 데이터의 과다 요청 또는 부족 요청, 네트워크 페이로드 증가, 다양한 프런트엔드 코드베이스에 분산된 복잡한 데이터 변환 로직으로 이어집니다. 이는 프런트엔드 개발자에게 부담을 줄 뿐만 아니라 백엔드 진화도 복잡하게 만듭니다. 해결책은 프런트엔드의 API 요구사항을 핵심 백엔드 서비스와 전략적으로 분리하는 데 있으며, Node.js로 구축된 백엔드 포 프런트엔드(BFF) 계층이 빛나는 곳이 바로 이곳입니다. 이는 각 소비 클라이언트의 정확한 요구사항에 맞게 API 응답을 조정하는 지능형 중개자 역할을 하여 성능을 최적화하고 프런트엔드 개발을 단순화하며 독립적인 진화를 가능하게 합니다.
Node.js BFF 패러다임 이해
구현 세부 정보에 들어가기 전에 Node.js BFF 패턴을 뒷받침하는 몇 가지 핵심 개념을 명확히 해 보겠습니다.
마이크로서비스(Microservices)
마이크로서비스 아키텍처는 모놀리식 애플리케이션을 작고 독립적으로 배포 가능한 서비스 제품군으로 분해하며, 각 서비스는 특정 비즈니스 기능을 수행합니다. 확장성과 독립적인 개발의 이점을 제공하지만, 마이크로서비스는 종종 프런트엔드의 데이터 요구사항과 직접적으로 일치하지 않을 수 있는 세분화된 API를 노출합니다.
백엔드 포 프런트엔드(BFF)
**백엔드 포 프런트엔드(BFF)**는 API 게이트웨이 또는 서비스가 단일 유형의 프런트엔드 애플리케이션(예: 웹 BFF, 모바일 BFF)을 위해 특별히 구축되는 아키텍처 패턴입니다. 범용 API 게이트웨이와 달리 BFF는 여러 백엔드 서비스의 데이터를 지정된 프런트엔드에 완벽하게 적합한 형식으로 변환하고 집계하는 데 중점을 둡니다. 이는 프런트엔드를 기본 마이크로서비스의 복잡성에서 보호하는 추상화 계층 역할을 합니다.
Node.js
Node.js는 웹 브라우저 외부에서 JavaScript 코드를 실행하는 오픈 소스, 크로스 플랫폼 JavaScript 런타임 환경입니다. 비차단, 이벤트 기반 아키텍처 및 풍부한 모듈(npm) 생태계는 API 게이트웨이 및 BFF와 같은 고도로 확장 가능하고 성능이 뛰어난 네트워크 애플리케이션을 구축하는 데 탁월한 선택입니다.
맞춤형 API 원칙
BFF 패턴의 핵심 원칙은 맞춤형 API입니다. 일반 API를 노출하는 대신 BFF는 특정 클라이언트에 최적화된 API를 제공합니다. 예를 들어, 모바일 앱은 화면 공간, 네트워크 제약 조건 또는 사용 흐름의 차이로 인해 웹 애플리케이션과 다른 데이터 하위 집합 또는 다른 데이터 구조를 요구할 수 있습니다. BFF는 이러한 차이를 추상화하여 각 프런트엔드에 깔끔하고 최적화된 API를 제공합니다.
Node.js가 BFF를 지원하는 방법
Node.js는 몇 가지 주요 특성으로 인해 BFF를 구축하는 데 특히 적합합니다.
- JavaScript Everywhere: 프런트엔드와 BFF 모두에서 JavaScript를 사용하면 코드 공유, 개발자 컨텍스트 전환 감소, 통합된 개발 경험을 얻을 수 있습니다.
- 비동기 I/O: Node.js의 비차단 I/O 모델은 API 게이트웨이에서 흔히 발생하는 수많은 동시 요청을 처리하고 이벤트 루프를 차단하지 않고 여러 백엔드 서비스에서 효율적으로 데이터를 집계하는 데 이상적입니다.
- 풍부한 생태계: 광범위한 npm 생태계는 HTTP 통신(예:
axios
,node-fetch
), 라우팅(예: Express.js, Fastify), 데이터 변환(예: Lodash) 및 인증을 위한 준비된 라이브러리를 제공하여 개발을 크게 가속화합니다. - 성능: CPU 집약적인 강력한 성능은 아니지만, Node.js는 I/O 집약적인 작업에 탁월하여 API 호출을 조정하는 데 성능이 뛰어난 선택입니다.
Node.js BFF 빌딩 예제
실제 예를 통해 설명해 보겠습니다. Products
, Users
, Orders
에 대한 별도의 마이크로서비스가 있는 전자상거래 애플리케이션을 상상해 보겠습니다. 우리의 목표는 제품 목록 페이지에 대한 고유한 요구사항을 가진 웹 애플리케이션과 모바일 애플리케이션에 서비스를 제공하는 것입니다.
시나리오: 제품 목록 페이지
- 웹 애플리케이션:
productId
,productName
,description
,price
,imageUrl
,category
,averageRating
이 필요합니다. - 모바일 애플리케이션:
productId
,productName
,thumbnailUrl
,price
, 그리고 빠른 표시를 위한 간소화된shortDescription
이 필요합니다.
핵심 백엔드 API (가상)
GET /products/{id} # 상세 제품 정보 반환 GET /users/{id} # 사용자 세부 정보 반환 GET /orders/{id} # 주문 세부 정보 반환
Express.js를 사용한 Node.js BFF 구현
먼저 BFF를 위한 기본 Express.js 애플리케이션을 설정해 보겠습니다.
// app.js const express = require('express'); const axios = require('axios'); // 백엔드 서비스에 대한 HTTP 요청 생성용 const cors = require('cors'); // 다양한 프런트엔드에 대한 CORS 처리용 const app = express(); const PORT = 3000; // 미들웨어 app.use(express.json()); app.use(cors()); // 특정 출처에 맞게 CORS 구성 // --- 백엔드 서비스 URL (실제 URL로 대체) --- const PRODUCT_SERVICE_URL = 'http://localhost:3001/products'; const RATING_SERVICE_URL = 'http://localhost:3002/ratings'; // 별도의 등급 서비스라고 가정 // --- 제품 목록 페이지를 위한 웹 BFF 엔드포인트 --- app.get('/bff/web/products', async (req, res) => { try { // 모든 제품 가져오기 const { data: products } = await axios.get(PRODUCT_SERVICE_URL); // 각 제품에 대해 평균 등급 가져오기 (집계 예시) const productsWithRatings = await Promise.all(products.map(async (product) => { try { const { data: ratingInfo } = await axios.get(`${RATING_SERVICE_URL}/${product.id}/average`); return { productId: product.id, productName: product.name, description: product.longDescription, price: product.price, imageUrl: product.mainImage, category: product.category, averageRating: ratingInfo.averageRating || 0, // 등급이 없으면 기본값 0 }; } catch (error) { console.error(`제품 ${product.id}의 등급을 가져오는 데 실패했습니다:`, error.message); return { productId: product.id, productName: product.name, description: product.longDescription, price: product.price, imageUrl: product.mainImage, category: product.category, averageRating: 0, // 오류를 우아하게 처리 }; } })); res.json(productsWithRatings); } catch (error) { console.error('웹 제품 가져오기 오류:', error.message); res.status(500).json({ message: '웹 제품을 검색하지 못했습니다' }); } }); // --- 제품 목록 페이지를 위한 모바일 BFF 엔드포인트 --- app.get('/bff/mobile/products', async (req, res) => { try { // 모든 제품 가져오기 const { data: products } = await axios.get(PRODUCT_SERVICE_URL); // 모바일 특정 요구사항에 맞게 데이터 변환 const mobileProducts = products.map(product => ({ productId: product.id, productName: product.name, thumbnailUrl: product.thumbnailImage, // 모바일은 더 작은 썸네일을 선호할 수 있음 price: product.price, shortDescription: product.shortDescription || product.longDescription.substring(0, 100) + '...', // 설명 잘라내기 })); res.json(mobileProducts); } catch (error) { console.error('모바일 제품 가져오기 오류:', error.message); res.status(500).json({ message: '모바일 제품을 검색하지 못했습니다' }); } }); // BFF 서버 시작 app.listen(PORT, () => { console.log(`Node.js BFF가 ${PORT} 포트에서 수신 대기 중입니다.`); });
이 예시에서:
-
/bff/web/products
: 이 엔드포인트는 웹 애플리케이션을 특별히 지원합니다. 모든 제품을 가져온 다음 별도의RATING_SERVICE_URL
에서 각 제품의 평균 등급을 비동기적으로 가져오고 마지막으로 웹 프런트엔드가 기대하는 정확한 형식(예:averageRating
,imageUrl
)으로 데이터를 구성합니다. 여기에는 필요한 경우 추가 서비스(예: 사용자 리뷰, 재고 정보)에서 더 많은 데이터 집계가 포함될 수 있습니다. -
/bff/mobile/products
: 이 엔드포인트는 모바일 애플리케이션을 위해 설계되었습니다. 동일한 제품 데이터를 가져오지만 다른 변환을 적용합니다:imageUrl
대신thumbnailUrl
을 사용합니다.longDescription
을 잘라내거나 백엔드에서 제공된 전용 짧은 설명 필드를 사용하여shortDescription
을 생성합니다.- 페이로드 크기와 작은 화면에서의 시각적 복잡성을 줄이기 위해
description
,category
,averageRating
을 생략합니다.
이러한 BFF 내의 별도 라우팅 및 데이터 변환 로직은 프런트엔드 애플리케이션을 기본 마이크로서비스의 복잡성과 서로의 데이터 요구사항으로부터 효과적으로 분리합니다. 각 프런트엔드는 이제 과다 수집이나 클라이언트 측 데이터 조작 없이 완벽하게 맞춤화된 API를 소비할 수 있으며, 이는 성능 최적화 및 개발 단순화로 이어집니다.
애플리케이션 시나리오
Node.js BFF 패턴은 다양한 시나리오에서 매우 유익합니다.
- 다양한 클라이언트 유형: 웹, iOS, Android, 스마트 TV 등 상당한 차이가 있는 프런트엔드가 있고 서로 다른 데이터 구조나 세부 수준을 요구하는 경우.
- 마이크로서비스 아키텍처: 백엔드 서비스가 세분화되어 있고 이를 프런트엔드에 직접 노출하면 클라이언트 측에서 복잡한 오케스트레이션이 발생할 수 있습니다.
- 빠른 프런트엔드 반복: 프런트엔드 팀이 핵심 백엔드 변경에 영향을 주거나 기다리지 않고 특정 API 요구사항을 독립적으로 반복할 수 있습니다.
- 레거시 시스템 통합: 오래되고 덜 유연한 백엔드 시스템 위에 파사드 역할을 하여 새로운 프런트엔드에 최신 API를 제공할 수 있습니다.
- 인증 및 권한 부여: BFF는 인증 및 권한 부여 로직을 중앙 집중화하여 추가 보안 계층을 추가하고 클라이언트 측 보안 문제를 단순화할 수 있습니다.
- 성능 최적화: 서버 측에서 데이터를 집계하고 필터링하여 특정 클라이언트에 대한 데이터 페이로드 및 네트워크 요청을 줄입니다.
결론
Node.js 백엔드 포 프런트엔드 패턴은 복잡한 마이크로서비스 백엔드에서 다양한 프런트엔드 애플리케이션에 서비스를 제공하는 문제를 해결하는 강력한 아키텍처 접근 방식입니다. 맞춤형 중개자 역할을 함으로써 Node.js BFF는 데이터 흐름을 최적화하고 프런트엔드 개발을 단순화하며 클라이언트 애플리케이션과 핵심 백엔드 서비스의 독립적인 진화를 촉진합니다. 각 프런트엔드가 필요한 데이터를 정확한 형식으로 수신하도록 보장하여 성능을 향상시키고 전체 시스템 아키텍처를 더욱 유지 관리하기 쉽게 만듭니다. 애플리케이션이 여러 개의 서로 다른 클라이언트에 서비스를 제공하는 경우, 이 패턴은 세분화된 백엔드 서비스와 특수화된 프런트엔드 요구 사이의 간격을 지능적으로 연결하므로 고려해 볼 가치가 있습니다.