Перейти к содержанию

Backend-for-Frontend (BFF)

Backend-for-Frontend - отдельный server-side слой под один конкретный фронтенд. Он собирает данные downstream-сервисов и подгоняет их под нужды именно этого UI.

Note

BFF путают с API Gateway: оба стоят на входе перед сервисами. Gateway - один общий вход на всех клиентов, BFF - по одному на каждый тип клиента (см. BFF и API Gateway).

Паттерн родом из SoundCloud. Практику описал Phil Calçado в 2015 году (philcalcado.com), а сам термин BFF, по его словам, придумал Nick Fisher, tech lead веба в SoundCloud. Как общий паттерн BFF описал Sam Newman (ThoughtWorks, ноябрь 2015) и разнёс по индустрии в статьях и книгах про микросервисы.

Зачем BFF

Один backend на всех клиентов - вечный компромисс:

  • Мобайл хочет мало: узкий канал, дорогой трафик, маленький экран. Ему нужно три поля, а общий endpoint отдаёт двадцать.
  • Web хочет много: больше данных за раз, другие экраны, другая навигация.
  • Under-fetching и over-fetching одновременно: один клиент недополучает, другой тащит лишнее.
  • Клиенты развиваются с разной скоростью, и общий API становится местом, где сталкиваются все команды.

Что из этого выходит: каждая UI-команда правит общий контракт под себя, и API превращается в кашу из query-параметров и условий, где никому не удобно. Любое изменение под мобайл рискует задеть web и наоборот. Координация дорожает с числом клиентов.

Решение: дать каждому клиенту свой backend. Мобильная команда оптимизирует свой endpoint под мобайл, web-команда - под web, и никто никому не мешает.

Когда подходит:

  • несколько клиентов с реально разными нуждами: мобайл, web, партнёрский API
  • у каждого клиента своя команда, и их релизы хочется развязать
  • общий API уже стал узким местом, где дерутся за поля

Когда BFF не нужен

  • Один клиент, один web. Тогда BFF - это просто твой backend, отдельное имя ему не нужно.
  • Клиенты почти одинаковые. Дублирование съест выгоду, проще жить с общим API.

Как устроено

        iOS app            Android app            Web SPA
           |                    |                    |
      +----------+         +----------+         +----------+
      | iOS BFF  |         | Android  |         | Web BFF  |
      | (thin)   |         |   BFF    |         | (thin)   |
      +----------+         +----------+         +----------+
           |     \            /    \            /     |
           |      \          /      \          /      |
           v       v        v        v        v       v
      +---------+  +-----------+  +-----------+  +-----------+
      | Orders  |  |  Catalog  |  |  Users    |  |  Pricing  |   downstream services
      +---------+  +-----------+  +-----------+  +-----------+
  • Один UI - один BFF: iOS-BFF, Android-BFF, web-BFF. Клиент ходит только в свой BFF.
  • BFF делает fan-out по нужным сервисам, склеивает ответы и отдаёт ровно ту форму, что нужна экрану. По сути это API Composition под один UI.
  • Downstream-сервисы общие. BFF их не дублирует, а композирует.
  • BFF тонкий: собирает ответы, переводит протоколы, режет лишние поля. Domain logic в нём не живёт.

Граница владения тут организационная, а не техническая - это закон Конвея. BFF принадлежит той же команде, что пишет клиент. Контракт между клиентом и BFF меняется внутри одной команды, без межкомандной координации.

Плюсы и минусы

Плюсы:

  • UI-команда владеет своим backend целиком. Меняешь экран - меняешь свой BFF, никого не спрашивая и не дерясь за общий gateway.
  • Каждый клиент получает ровно свою форму данных, без over-fetching и under-fetching.
  • BFF деплоится независимо от других BFF. Релизный цикл клиента не завязан на чужие.
  • Downstream-сервисы остаются чистыми и generic: вся клиент-специфика уезжает в BFF.

Минусы:

  • Дублирование. Похожая агрегация и трансляция переписываются в каждом BFF: web и Android часто хотят почти одно и то же и пишут это дважды.
  • N клиентов - N backend-ов, которые надо писать, деплоить, мониторить и дежурить.
  • Общий код между BFF. Вынесешь в shared-библиотеку - снова свяжешь команды, не вынесешь - копипаста. Ещё вариант - опустить общую логику в нижележащий сервис. Золотой середины нет, выбираешь по контексту.

Грабли:

  • BFF - не место для бизнес-логики. Это тонкий слой сборки и перевода. Как только в нём появляются решения «можно ли», а не «как показать», это уже не BFF, а скрытый сервис. Бизнес-правила остаются в сервисах, иначе BFF расползается в god gateway, только теперь их несколько.
  • Один общий BFF на всех - это снова API Gateway под другим именем, и вся затея теряет смысл.

BFF и API Gateway

Оба сидят на входе, оба стоят перед сервисами. Разница в количестве и в назначении.

Свойство API Gateway BFF
Сколько один общий вход на всех по одному на каждый UX
Кому служит всем клиентам сразу конкретному клиенту
Что делает routing, auth, rate limit, единый вход агрегация и подгонка под один UI
Кто владеет платформенная или infra-команда UI-команда этого клиента
Форма ответа более-менее generic заточена под экраны клиента
  • Одно другому не мешает. Часто BFF стоят за общим gateway: gateway держит auth и rate limit на периметре, дальше трафик расходится по BFF. Gateway - горизонталь, BFF - вертикали под ним.
  • Richardson подаёт BFF как вариант gateway: отдельный gateway на каждый тип клиента. Акцент другой: BFF не стесняется логики «под экран», gateway держит общие сквозные задачи.

Итого: gateway - общая дверь для всех, BFF - по двери на клиента, и каждая знает свой UI в лицо.