У аббревиатуры BFF кроме Backend for Frontend есть и другая расшифровка — Best Friends Forever. И в контексте статьи это только отчасти шутка. Общение фронтенда и бэкенда не всегда происходит гладко (опустим тот факт, что существует множество мемов о противостоянии фронтендеров и бекендеров): клиент запрашивает данные, бэкенд отдаёт то, что запросили, но часто данных сильно больше, чем нужно, а это значит, что запрос будет возвращаться дольше, фронтенд будет отрисовываться тоже дольше и всё это отразится на опыте конечного пользователя.

А что если между фронтендом и бэкендом построить мостик, который распределит нагрузку и сделает всех дружелюбнее? Примерно в этом и состоит суть паттерна BBF, а в статье разберём подробнее: зачем его внедрять и какую роль он играет в масштабировании современных сервисов; как мы реализуем этот подход в рамках RUTUBE, какой профит он нам даёт; почему мы отказались от GraphQL; в чём отличия от API Gateway и как вообще проектировать такие сервисы.

Меня зовут Максим Ульянов, я руковожу отделом веб-разработки в RUTUBE, отвечаю за все браузерные интерфейсы: сайта rutube.ru, студии блогеров studio.rutube.ru, всех сателлитов и внутренних продуктов платформы. В свободное время я веду подкаст «Куда расти?» и Telegram-канал ULYANOV.LIFE. Этот материал написан по мотивам моего доклада на FrontendConf.

Предыстория, или Зачем нужен BFF 

Изначально архитектура веб-сервисов (тогда они еще были сайтами) была простой и строилась по принципу монолита: один бэкенд напрямую общается с единой клиентской частью с одной стороны, и с базой данных — с другой.

Схема из статьи https://philcalcado.com/2015/09/18/the_back_end_for_front_end_pattern_bff.html

В целом монолиты создаются и по сей день и такой подход может быть оправдан — но это не тема сегодняшнего обсуждения. Сегодня нас интересуют более сложные сценарии и сервисы с большим количеством разных клиентских приложений.

Backend for Frontend или BFF — архитектурный паттерн, который предполагает создание отдельного бэкенда под каждый фронтенд (клиентский интерфейс или приложение). Его задача — собирать и проксировать данные, необходимые для конкретного клиента. 

Термин Backend for Frontend появился в SoundCloud в 2013 году. Тогда их сайт выглядел примерно так:

Интерфейс SoundCloud в 2013 году
Интерфейс SoundCloud в 2013 году

На примере SoundCloud посмотрим, как идет развитие сервисов от монолита к микросервисам — в данном случае к архитектуре BFF. 

Основная проблема, которую решали в команде, заключалась в том, что существующая монолитная архитектура очень ограничивала скорость развития сервиса: мешала проводить A/B-тесты на разных платформах, реализовывать редизайн и быстро выкатывать обновления.  

Самая простая архитектура схематично изображена на следующей картинке слева: есть клиенты, например, веб, iOS, Android и партнёрские приложения, которые завязаны на один бэкенд. В таком случае бэкенд должен готовить данные для всех клиентов, учитывать особенности каждого из них и реализовывать все специфичные сценарии. 

Схемы из статьи https://www.thoughtworks.com/en-us/insights/blog/bff-soundcloud

Если же вы хотите быстрее развиваться и тестировать гипотезы на отдельных приложениях — на схеме справа это iOS и Android — а также проводить A/B-тесты и готовить разные наборы данных для разных клиентов, то для этого можно создать отдельный сервис, который будет называться Backend for Frontend. BFF с точки зрения данных будет завязан на всё тот же монолит, но при этом iOS- и Android-приложения будут получать данные через него, а не напрямую от бекенда. BFF будет передавать только те данные, которые нужны в данный конкретный момент времени и в той структуре, чтобы снизить количество операций при подготовке данных на клиенте и быстрее отрисовать интерфейс.

Можно пойти дальше и заменить в этой схеме монолитный бэкенд на Public API и постепенно реформировать то, что скрывается за фасадом: распиливать монолит, добавлять (микро)сервисы и т.д. Либо можно добавлять отдельные модули под конкретный BFF. 

Постепенный уход от монолита через внедрение Public API
Постепенный уход от монолита через внедрение Public API

Такая архитектура позволяет постепенно эволюционировать и добавлять отдельные, независимые BFF под каждый клиент, постепенно отрезая куски от монолита, а за ними реализовывать контроллеры и shared-утилиты, которые могут переиспользоваться между разными BFF. 

Evolution of BFF
Эволюция BFF-сервисов в SoundCloud

Эта архитектура в первую очередь направлена на то, чтобы обеспечивать хороший time to market клиентских приложений.

Так развивалась архитектура SoundCloud и это отчасти похоже на путь RUTUBE. 

Контекст RUTUBE

Посмотрим на страницу с плеером RUTUBE. На первый взгляд она выглядит достаточно просто и кажется, что её легко заверстать и она может жить на монолите.

Страница видео на RUTUBE
Страница видео на RUTUBE

Но если углубиться в разные сценарии, то мы увидим, например, вариант с контентом, доступным только по подписке и закрытым от всех остальных пользователей. 

Платный контент на RUTUBE
Платный контент на RUTUBE

Также у нас есть серийный контент, на странице с которым показывается список сезонов и серий. 

Серийный контент на RUTUBE
Серийный контент на RUTUBE

А ещё есть трансляции блогеров и телеканалов c чатом и переключением между эфирами. 

Трансляции и эфиры на RUTUBE
Трансляции и эфиры на RUTUBE

Страницу отдельного видео можно поделить на смысловые блоки: само видео, которое мы запрашиваем чанками, чтобы в реалтайме подстраивать качество стриминга; информация о видео; настройки и пр. Чтобы отрисовать только блок с плеером, необходимо опросить разом девять API

Структура страницы видео на RUTUBE
Структура страницы видео на RUTUBE

Собирать и обрабатывать данные от девяти источников на клиенте — это риск для быстродействия и надежности. Каждый поход по сети — потенциальные задержки, плюс все данные еще нужно агрегировать, собрать из них готовую для отрисовки структуру, возможно, сделать дополнительные запросы и только потом отрисовать — кажется, пользователь устанет ждать и уйдет к тем, у кого работает быстрее.

Всё усложняется ещё тем, что у RUTUBE сейчас более 17 млн ежедневных пользователей и более 400 млн единиц контента — сервис высоконагруженный и должен быть рассчитан на масштабирование. 

По данным Mediascope за 2025 год с января по август
По данным Mediascope за 2025 год с января по август

Эволюция BFF в RUTUBE

RUTUBE — сервис с 19-летней историей, за это время веб-технологии сильно изменились, и архитектуру сервиса, естественно, требовалось перестраивать под актуальные задачи и подходы. Большая часть кодовой базы сейчас не старше 2022-го, но при этом, естественно, все изменения происходили постепенно: мы заменяли устаревшие решения одно за другим, уходя от монолита и переключаясь на более гибкие схемы. 

Ниже — примерная архитектурная схема RUTUBE. В ней более 120 микросервисов и оставшиеся ещё легаси-элементы, сложно связанные между собой. 

Архитектура платформы RUTUBE
Архитектура платформы RUTUBE

BFF как подход помогает нам перестраиваться, но 3,5 года назад ему тоже требовалось обновление. Когда я пришел в RUTUBE, BFF уже существовал, он был написан на Express.js, и там был in-memory cache, который кешировал данные на каждом конкретном инстансе приложения, запущенного в продакшене. 

Так в 2022-м выглядели зависимости нашего BFF
Так в 2022-м выглядели зависимости нашего BFF

Архитектура нашего BFF тогда выглядела стандартно: несколько ручек, описанных в контроллерах.

Архитектура BFF RUTUBE в 2022 году
Архитектура BFF RUTUBE в 2022 году

Данное решение было реализовано «на коленке» в качестве эксперимента и не было эталоном гибкости или надёжности. 

Например, мы собирались выкатить один A/B-тест и думали, что раз у нас есть BFF, сейчас всё будет быстро и здорово. Но в итоге та задача заняла порядка трех месяцев вместо пары недель! Тогда мы поняли, что надо что-то менять. 

Мы стали исследовать варианты и выяснили, что лучшим решением для организации Node.js-приложений является фреймворк Nest. У Nest сильное комьюнити и хорошая документация.

Актуальные зависимости BFF RUTUBE в 2025 году
Актуальные зависимости BFF RUTUBE в 2025 году

Мы используем многие классные инструменты, которые есть в Nest и, кроме того: 

  • Redis для распределенного кэширования;

  • RxJS для асинхронных операций;

  • Fastify в качестве роутера вместо Express. 

Fastify мы взяли, потому что это самый быстрый на сегодняшний день Node.js роутер по количеству обрабатываемых запросов за секунду. 

Результаты бенчмарка производительности https://fastify.dev/benchmarks/
Результаты бенчмарка производительности https://fastify.dev/benchmarks/

В обновлённом BFF мы смогли добиться такой стабильности, что его захотели использовать и другие клиенты: мобильные приложения, Студия RUTUBE (приложение для авторов), Smart TV.  Поэтому сейчас наш BFF обслуживает 5 сервисов и выглядит следующим образом.

Структура проекта BFF RUTUBE в 2025-м
Структура проекта BFF RUTUBE в 2025-м

Здесь есть важный момент — насколько правильно с точки зрения архитектурного паттерна иметь один BFF для пяти клиентов? Чтобы BFF соответствовал своей изначальной идее — обеспечивать быстрый доступ к нужным данных для разных независимых клиентов — в Nest есть Workspaces. В результате сейчас у нас каждый сервис может разрабатываться и катиться полностью независимо, SLA всех клиентов, которые к нам обращаются, полностью соблюдается.

CI/CD-схема BFF для веб-клиента
CI/CD-схема BFF для веб-клиента

Таким образом BFF является центральным сервисом для агрегации и проксирования данных для всех наших клиентов. За ним стоят бэкенды, к которым мы обращаемся.

С точки зрения структуры модули нашего BFF организованы в концепции Nest: мы не стали «изобретать велосипед», действовали в соответствии с документацией фреймворка.

Модули веба в нашем BFF, проект называется Pangolin
Модули веба в нашем BFF, проект называется Pangolin

В модуле есть, разумеется, тесты, DTO-объекты, из которых впоследствии генерируются типы данных и которые мы можем переиспользовать, а также мапперы, которые нужны для обработки определённых запросов: 

Структура отдельного модуля BFF Pangolin
Структура отдельного модуля BFF Pangolin

Мы используем Swagger-документацию, которая автогенерируется из DTO-объектов. Её очень удобно передавать бэкендерам, тестировщикам и всем, с кем надо согласовать контракт.

Swagger-документация веб-модуля BFF Pangolin
Swagger-документация веб-модуля BFF Pangolin

Рассмотрим, как теперь выглядят запросы за получением данных для уже знакомой нам страницы плеера и ее блока с видео.  

Логика обработки запроса данных для проигрывания видео на RUTUBE
Логика обработки запроса данных для проигрывания видео на RUTUBE

Запрос за видео состоит из нескольких параллельных запросов и раскладывается в такую схему: 

  • клиент отправляет запрос по одному конкретному URL;

  • URL через балансер попадает на наш BFF-сервис;

  • если ответ на этот запрос закеширован, то он автоматически отдаётся из кеша на клиент, не создавая дополнительной нагрузки на API;

  • если в кеше ответа нет, то идём в API;

  • полученный по API ответ кешируем в Redis с указанием временного ключа кеширования (ключи можно посмотреть на иллюстрации выше);

  • далее прогоняем некоторые мапперы — это позволяет убрать часть бизнес-логики с клиента на сторону BFF и при этом предоставлять клиенту конститентные данные;

  • отдаём данные через сервис на клиент;

  • пользователь смотрит видео :)

Итого, суммируем, когда BFF нужен, а когда — это погоня за хайпом.

Когда BFF полезен:

  • У продукта несколько клиентов. У нас это web, iOS, Android, Smart TV и ещё партнёры — нам необходимо поддерживать для них различную структуру данных.

  • Частые изменения UI: A/B-тесты, быстрые MVP, редизайны.

  • Нужно собрать много данных в один ответ. Как в примере выше, когда чтобы отрисовать страницу, нужно опросить 9-10 API ручек, то через BFF это будет сильно дешевле с точки зрения сетевых задержек.

  • Требуется изоляция контрактов — то самое распиливание монолита и постепенная замена его на микросервисы. При этом есть возможность поддерживать на уровне BFF старые версии ручек, что очень важно для клиентов типа Smart TV и мобильных приложений, которые медленно обновляются и имеют очень длинный хвост поддержки. 

Когда BFF — хайп: 

  • Если у вас всего один клиент, например, одно, пусть даже большое, SPA-приложение, то BFF не нужен. 

  • Если все клиенты могут работать с универсальным API, то проще иметь public API вокруг монолита или микросервисов — неважно. 

  • Если дополнительный слой не ускоряет работу и не упрощает контракты — этот слой, т.е. BFF не нужен.

  • Если нет ресурсов на поддержку ещё одного сервиса, то он вместо пользы может превратиться в узкое горлышко. 

Best practices при проектировании BFF-сервиса

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

Один сервис — один ответственный

Это правило справедливо для большинства инфраструктурных сервисов и их поддержки. В нашем случае оно означает, что если при большом количестве разных клиентов, каждый из которых имеет свой бэклог, свои сроки и хочет работать стабильно, сделать один большой монолитный BFF, то это не даст никаких преимуществ. Разным клиентам нужны отдельные BFF — то есть свои ответственные.

У нас это устроено следующим образом: у нас в веб-отделе есть платформенная команда, это фронтендеры, которые умеют писать на Node.js, и именно они поддерживают BFF. А чтобы каждый конкретный сервис мог катиться отдельно, как уже говорилось выше, у нас есть Workspaces и конкретные договоренности по SLA со всеми клиентскими командами. 

Прозрачное логирование

BFF — так или иначе бэкенд-сервис и ему необходимо прозрачное логирование, чтобы мы понимали, что происходит.

У нас стандартный стэк: Kibana (интерфейс к данным в Elasticsearch) + Senry + Prometheus + Grafana. 

Prometheus-метрики веб-клиента BFF RUTUBE в одном из ЦОД-ов
Prometheus-метрики веб-клиента BFF RUTUBE в одном из ЦОД-ов

Выше скрин наших дашбордов, туда можно вывести все метрики жизнеспособности сервиса и быстро реагировать, если что-то идёт не так. 

Заголовки trace-id

Как мы помним, на одну страницу у нас опрашивается несколько ручек — если не подписать изначальный запрос, то будет трудно проследить дальнейшую цепочку.  

С trace-id через тот же самый интерфейс Kibana можно проследить весь поток запросов и то, какой сервис отдавал или принимал данные, какие были заголовки и т.д. Это позволит воспроизвести всю цепочку запросов для конкретной ручки конкретного клиента и понять, на каком из звеньев и в каком сервисе произошла ошибка.

Пример того, как мы в RUTUBE отслеживаем цепочки запросов по trace-id в Kibana
Пример того, как мы в RUTUBE отслеживаем цепочки запросов по trace-id в Kibana

Передать сервис в службу мониторинга

Очевидная штука: это бэкенд-сервис, если за ним не следить, то можно слишком поздно узнать о проблемах. Для продукта с миллионами пользователей это недопустимо.

Распределённый кеш

Выше я уже говорил, что в первой, экспериментальной версии нашего BFF был in-memory cache Проблема заключалась в том, что у RUTUBE несколько ЦОДов и много инстансов сервисов, чтобы выдерживать нагрузку и горизонтально масштабироваться. Кеширование данных в памяти на конкретном инстансе приводит к тому, что они становятся неконсистентными. Чтобы обеспечить валидные данные для наших пользователей при перезаходах и обновлениях страниц, а так же, чтобы не плодить лишнюю нагрузку на бэкенды, необходим распределенный кеш. В данном случае мы выбрали Redis.

Договориться об SLA с клиентскими командами

Это поможет всем точнее планировать ресурсы разработки, определить формат поддержки и время реагирования на критичные инциденты. 

BFF и GraphQL

Возможно, вам сейчас кажется, что описанная в статье схема работы с данными похожа на ту, что есть в GraphQL. Действительно, GraphQL и BFF — пересекающиеся подходы. Но есть существенные различия, из-за которых мы отказались от GraphQL.

Почему мы отказались от GraphQL:

  • Ценность GraphQL реализуется с помощью BFF. Конечно, это разные вещи, но с помощью BFF на REST можно сделать то же, что и с GraphQL. 

  • В GraphQL есть сложности с кешированием данных (на GraphQL запросы идут, по факту, через метод POST и ответы просто так не закешировать), а с BFF мы избегаем этих проблем. 

  • В GraphQL запросы произвольные, а в REST легко строить метрики/алерты по явным маршрутам (/video/:id), что нам необходимо для качественной аналитики и мониторинга.  

  • В GraphQL нужны защиты от N+1 сложности запросов, а в REST вы контролируете форму и «вес» ответа на уровне проектирования контаракта.

  • Time-to-market в нашем случае с GraphQL просел бы: нам понадобилось бы перестраивать архитектуру внутри бэкенда, ребятам пришлось бы учиться работать с новой технологией и писать все эти запросы и guards. 

BFF vs API Gateway

Также может показаться, что подход BFF похож на API Gateway, но это не так. 

API Gateway — центральная точка входа в систему, которая принимает запросы клиентов и передаёт ответы обратно клиенту. Обычно управляется платформенной или DevOps-командой и меняется сравнительно редко.

BFF — как мы ранее говорили, это архитектурный паттерн, который подразумевает создание отдельного бэкенда под каждый фронтенд, чтобы в моменте отрисовать тот или иной UI. 

Архитектурная схема взаимодействия API Gateway и микросервисов выглядит примерно так:

BFF vs API Gateway
BFF vs API Gateway


API Gateway отвечает за то, чтобы балансировать вообще все запросы и перенаправлять их к сервисам. А также за безопасность, логирование и мониторинг. Кроме того, бывает, что на стороне API Gateway присутствует такой аспект, как трансформации, но я считаю, что все трансформации должны происходить на уровне микросервисов. Можно сказать, что BFF как раз и является тем самым микросервисом, который за это должен отвечать.

Итого: BFF не серебряная пуля

  • BFF полезен далеко не в каждой архитектуре. Если после прочтения статьи у вас остались вопросы, зачем вам BFF, то скорее всего он вам и не нужен. 

  • Относитесь к BFF, как к полноценному бэкенд-сервису.

  • Избегайте харкода и размазывания бизнес-логики. Если занести в BFF большую часть обработки данных, место которой на уровне клиента или бэкенда, то весь смысл BFF пропадет и потеряется гибкость решения. 

  • Не превращайте BFF в API Gateway на стероидах. Пускать все запросы через BFF не нужно, это имеет смысл только для запросов, которые требуют сложной агрегации или предварительных подготовки данных для конкретного клиента. 

  • Не переусложняйте! Когда вы внедряете новый сервис, ваша система сразу становится сложнее. Соответственно это решение должно решать какую-то вашу задачу и хорошо бы убедиться, что нет более простого способа это сделать. 

Подписывайтесь на этот блог и канал Смотри за IT, если хотите знать больше о создании медиасервисов. Там рассказываем об инженерных тонкостях, продуктовых находках и полезных мероприятиях, делимся видео выступлений и кадрами из жизни команд Цифровых активов «Газпром-Медиа Холдинга» таких, как RUTUBE, PREMIER, Yappy.

А если вы хотите знать больше о трендах в IT-отрасли, следите за обновлениями и анонсами новых конференций на сайте «Онтико». Например, в Москве 3 декабря состоится бесплатная конференция про разработку системного ПО, ядра Linux и open source OS DevConf 25 powered by GigaChat.

Комментарии (1)


  1. SolidSnack
    27.11.2025 14:50

    Изначально архитектура веб-сервисов (тогда они еще были сайтами) была простой и строилась по принципу монолита: один бэкенд напрямую общается с единой клиентской частью с одной стороны, и с базой данных — с другой.

    Оправдывать нагораживание микросервисов историческим переходом от монолита к некст гену так сказать, это сильно