프로그래밍 PROGRAMMING/아키텍쳐

BFF 패턴으로 API 게이트웨이 설계하기

매운할라피뇽 2026. 3. 19. 09:30
반응형
BFF 패턴으로 API 게이트웨이 설계


BFF(Backend for Frontend) 패턴은 클라이언트 유형별로 전용 백엔드 레이어를 두어, 각 프론트엔드의 요구에 최적화된 API를 제공하는 아키텍처 패턴입니다. 단일 API 게이트웨이가 모든 클라이언트를 처리할 때 발생하는 오버페칭(over-fetching)·언더페칭(under-fetching) 문제를 구조적으로 해결할 수 있으며, 팀 경계를 명확히 분리하여 독립적인 배포와 유지보수를 가능하게 합니다.


BFF 패턴이 필요한 상황

전통적인 단일 API 게이트웨이는 모바일, 웹, 서드파티 클라이언트가 동일한 엔드포인트를 공유합니다. 이 구조에서는 다음과 같은 문제가 반복적으로 나타납니다.

  • 오버페칭: 모바일 앱은 상품명·가격만 필요한데, 웹용으로 설계된 응답에는 리뷰·재고·태그 등 수십 개 필드가 포함됩니다.
  • 강한 결합: 모바일 요구사항 변경이 웹 API에 영향을 주어 배포 주기가 충돌합니다.
  • 팀 자율성 저하: 프론트엔드 팀이 백엔드 일정에 종속되어 기능 출시가 지연됩니다.

BFF 패턴은 클라이언트 유형(모바일, 웹, TV 등)마다 독립된 BFF 서비스를 두고, 각 BFF가 다운스트림 마이크로서비스를 집계(aggregation)·변환(transformation)하는 책임을 집니다. 결과적으로 각 클라이언트 팀이 자신의 BFF를 직접 소유하고 배포할 수 있습니다.


Spring Boot로 BFF 레이어 구현하기

BFF는 별도 마이크로서비스로 구현하는 것이 일반적입니다. 아래 예제는 Web BFF가 상품 서비스와 리뷰 서비스를 병렬 호출해 집계하는 구조를 보여줍니다.

다음 코드는 WebClient를 이용한 비동기 병렬 집계 예시입니다.

// WebBffController.java
@RestController
@RequestMapping("/bff/web/products")
@RequiredArgsConstructor
public class WebBffController {

    private final ProductClient productClient;
    private final ReviewClient  reviewClient;

    @GetMapping("/{id}")
    public Mono<WebProductResponse> getProduct(@PathVariable String id) {

        // 두 마이크로서비스를 병렬 호출하여 응답 집계
        Mono<ProductDto> productMono = productClient.fetchById(id);
        Mono<List<ReviewDto>> reviewsMono = reviewClient.fetchByProductId(id);

        return Mono.zip(productMono, reviewsMono)
                .map(tuple -> WebProductResponse.builder()
                        .id(tuple.getT1().getId())
                        .name(tuple.getT1().getName())
                        .price(tuple.getT1().getPrice())
                        .description(tuple.getT1().getDescription()) // 웹 전용 필드
                        .reviews(tuple.getT2())                      // 웹 전용 필드
                        .build());
    }
}
// MobileBffController.java — 모바일 전용 BFF
@RestController
@RequestMapping("/bff/mobile/products")
@RequiredArgsConstructor
public class MobileBffController {

    private final ProductClient productClient;

    @GetMapping("/{id}")
    public Mono<MobileProductResponse> getProduct(@PathVariable String id) {

        // 모바일은 최소 필드만 반환 → 페이로드 경량화
        return productClient.fetchById(id)
                .map(p -> MobileProductResponse.builder()
                        .id(p.getId())
                        .name(p.getName())
                        .thumbnailUrl(p.getThumbnailUrl()) // 모바일 전용 필드
                        .price(p.getPrice())
                        .build());
    }
}

실행 결과 비교

단일 게이트웨이 (기존)24개~3.2 KB
Web BFF12개~1.8 KB
Mobile BFF4개~0.4 KB

모바일 BFF는 불필요한 필드를 제거하여 네트워크 비용을 약 87% 절감할 수 있습니다.


API 게이트웨이와 BFF의 역할 분리

BFF 패턴에서 API 게이트웨이BFF는 서로 다른 책임을 집니다.

API 게이트웨이의 역할

API 게이트웨이(예: AWS API Gateway, Kong, Spring Cloud Gateway)는 인프라 횡단 관심사를 처리합니다.

  • 인증(JWT 검증) / 인가(OAuth 2.0 스코프 확인)
  • TLS 종료, 속도 제한(rate limiting), IP 차단
  • 라우팅: /bff/web/* → Web BFF, /bff/mobile/* → Mobile BFF

BFF의 역할

BFF는 클라이언트 전용 비즈니스 로직을 처리합니다.

  • 다운스트림 서비스 데이터 집계·변환
  • 클라이언트별 필드 선택 및 포맷 변환
  • 클라이언트 특화 에러 메시지 가공

아래는 Spring Cloud Gateway에서 라우팅을 설정하는 예시입니다.

# application.yml — Spring Cloud Gateway 라우팅 설정
spring:
  cloud:
    gateway:
      routes:
        - id: web-bff
          uri: http://web-bff-service:8081
          predicates:
            - Path=/bff/web/**
          filters:
            - name: RequestRateLimiter   # 속도 제한 (인프라 관심사)
              args:
                redis-rate-limiter.replenishRate: 100
                redis-rate-limiter.burstCapacity: 200

        - id: mobile-bff
          uri: http://mobile-bff-service:8082
          predicates:
            - Path=/bff/mobile/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 300  # 모바일은 더 높은 처리량
                redis-rate-limiter.burstCapacity: 500

이 구조에서 게이트웨이는 라우팅·보안만 담당하고, 데이터 집계 로직은 BFF에 온전히 위임됩니다.


BFF 패턴 적용 시 고려 사항

BFF 패턴을 도입하면 서비스 수가 증가하므로, 다음 사항을 사전에 검토하는 것이 중요합니다.
클라이언트 경계 정의: BFF를 지나치게 세분화하면 관리 부담이 증가합니다. 일반적으로 Web, Mobile(iOS/Android 공용), Third-party 세 가지 정도로 분리하는 것이 균형 잡힌 출발점입니다.
공통 로직 중복 방지: 인증 처리, 공통 헤더 변환 등 여러 BFF에 반복되는 로직은 공유 라이브러리(internal library) 또는 API 게이트웨이 필터로 위임합니다. BFF 내부에 중복 구현하면 유지보수 비용이 증가합니다.
관찰 가능성(Observability) 확보: 집계 레이어가 추가되었으므로, 분산 추적(Distributed Tracing)을 반드시 적용해야 합니다. Spring Boot 환경에서는 Micrometer Tracing과 Zipkin 또는 Tempo 조합을 권장합니다.

캐싱 전략: BFF는 다운스트림 서비스 부하를 줄이기 위해 응답 캐싱을 적용하기 좋은 위치입니다. @Cacheable이나 Redis 캐시를 BFF 레이어에 적용하면 집계 비용을 효과적으로 줄일 수 있습니다.


맺음말

BFF 패턴은 클라이언트 다양성이 증가하는 현대 서비스 환경에서 API 설계의 복잡성을 구조적으로 관리하는 검증된 접근법입니다. 단일 게이트웨이의 범용성 대신 클라이언트 전용 레이어를 두어, 프론트엔드 팀의 자율성과 배포 속도를 높이는 데 유의미한 효과를 기대할 수 있습니다.
특히 모바일·웹·서드파티 채널이 동시에 존재하거나, 클라이언트별 응답 포맷이 크게 다를 때 도입 효과가 두드러집니다. 다만 팀 규모가 작거나 클라이언트 종류가 하나뿐이라면, BFF보다 GraphQL의 필드 선택 기능이 더 적합한 대안이 될 수 있습니다.
관련하여 마이크로서비스 패턴의 집계 전략을 함께 살펴보면 BFF 패턴을 더 넓은 아키텍처 맥락에서 이해하는 데 도움이 됩니다.

반응형