Привет, Хабр! Я — Константин Вартаньянц, системный аналитик-эксперт в «Синимекс». Мне приходится проектировать системы на микросервисах, и я нередко сталкиваюсь с одной и той же историей: как построить бэкэнд для сложных приложений со множеством фронтендов.
В поисках решения я столкнулся с подходом Backend for Frontend, использовал его для проектирования и реализации нескольких систем и теперь хочу поделиться своим опытом.
Проблема: почему нельзя просто взять и написать один бэкенд для всех
Начну с главного. Какая главная задача у бэкенда, где крутится вся бизнес-логика? Правильно, обслуживать бизнес-процессы. Для него пользователь — это просто управляющий элемент: прилетели данные и команды, он их обработал, вернул результат и процесс пошел дальше. Ему глубоко всё равно, какой кнопкой, формой или голосом эти данные ввели. А вот у фронтенда работа другая — сделать так, чтобы пользователю было удобно, понятно и приятно. Это мир UX-гипотез, A/B-тестов и постоянной борьбы за лучший интерфейс. И для всех этих «рюшечек» и экспериментов ему нужны данные, которые в строгой бизнес-логике обычно не предусмотрены.
Когда мы пытаемся в одном бэкенде совместить и строгую бизнес-логику, и все эти «рюшечки» для фронта — получается монстр Франкенштейна. В коде смешивается логика предметной области (например, доменная модель из DDD, то есть модель, описывающая ключевые бизнес-сущности и правила их взаимодействия) и логика подготовки данных для конкретной формы в интерфейсе.
В итоге получается, что стабильная по своей природе бизнес-логика начинает дёргаться от каждой «косметической» правки в интерфейсе. Решили на фронте добавить пару колонок в таблице — и вот уже летит задача на доработку бэка. Это не только усложняет код, но и кратно повышает риск регресса (ситуации, когда после обновления ломается то, что раньше работало).
Добавьте к этому, что у нас часто не один, а несколько фронтендов: веб-сайт для клиентов, админка для сотрудников, мобильное приложение. У каждого свои пользователи, свои сценарии и свои «хотелки». Чтобы угодить всем, бэкенд-разработчикам приходится плодить десятки почти одинаковых методов, которые раздувают кодовую базу и усложняют тестирование.
И далее, самая больная тема — безопасность. Если бэкенд отдаёт «сырые» данные, а фронтенд сам решает, что показать пользователю, а что нет, — это огромная дыра в безопасности. Любой студент, открывший инструменты разработчика в браузере, увидит всё, что от него пытались скрыть.
Очевидно, что вся фильтрация конфиденциальных данных должна происходить на сервере. Но это ещё не всё. Чтобы правильно нарисовать интерфейс — показать нужные кнопки, сделать поля доступными или неактивными — фронтенду часто нужны данные о правах пользователя. Отдавать всю модель прав на клиента — либо небезопасно, либо технически невозможно. Значит, бэкенд должен сам решать, что можно, а что нельзя. И мы снова возвращаемся к проблеме: либо мы плодим методы под каждую роль, либо строим один гигантский универсальный метод, поддерживать который — боль.
В итоге мы стоим перед выбором: либо раздутый и сложный в поддержке бэкенд, либо небезопасный и перегруженный логикой фронтенд. Выделение промежуточного слоя, который возьмёт на себя всю грязную работу по подготовке данных для интерфейса, выглядит единственным здравым решением.
Что такое BFF и как он работает

Этот посредник и есть Backend for Frontend (BFF). Суть паттерна проста: мы создаём отдельный, специализированный бэкенд для каждого типа клиентов (например, один для веб-приложения, другой — для мобильного). BFF не содержит собственной бизнес-логики. Его задача — быть переводчиком и адаптером между миром фронтенда и миром бэкенда. Он забирает на себя всю логику подготовки данных, освобождая от неё как основные сервисы, так и клиентские приложения. В идеальном мире у каждого фронта свой BFF, но в реальности похожие клиенты могут делить один общий BFF на всех.
Как это работает? Основные бизнес-сервисы очищаются от всего, что связано с «красотой»: в них остаются только чистые методы, реализующие бизнес-логику. Всю грязную работу берёт на себя BFF: он стучится в разные микросервисы, собирает от них данные, форматирует, фильтрует и отдаёт фронтенду в идеально готовом для употребления виде. В результате команда фронтенда может менять интерфейс как угодно — пока это не затрагивает суть бизнес-процесса, бэкенд-разработчики могут спать спокойно.
Вопрос безопасности, о котором я говорил, тоже элегантно решается. BFF, находясь в защищённом контуре, получает от внутренних сервисов все данные, включая конфиденциальные. Затем, зная права текущего пользователя, он, как строгий охранник, отсекает всё лишнее и отправляет на фронт только то, что разрешено видеть. Никакие секреты больше не утекают в браузер. Более того, BFF может управлять не только данными, но и логикой интерфейса, сообщая фронту, какие кнопки и поля показывать, а какие — прятать.
Плюсы и минусы подхода BFF
Перечислим плюсы, которые дает внедрение паттерна Backend for Frontend.
Упрощение и оптимизация клиент-серверного взаимодействия. Вместо того чтобы фронтенд делал десять запросов к разным микросервисам, он делает один запрос к своему BFF. А тот уже сам, по внутренним, быстрым и надёжным каналам, собирает всё необходимое. Это резко сокращает трафик, упрощает код на клиенте и повышает общую стабильность, особенно для мобильных приложений с их нестабильным интернетом.
Максимальное «облегчение» фронтенда. Вся сложная логика — агрегация данных из разных источников, фильтрация, подготовка — переезжает на BFF. Фронтенд превращается в тонкого клиента, чья задача — просто красиво отрисовать то, что ему прислали, и реагировать на действия пользователя. BFF может даже присылать готовые флаги-подсказки: «эту кнопку показывай, а эту — скрой».
Упрощение клиент-серверного взаимодействия. Вместо серии вызовов с клиента к разным сервисам выполняется один запрос к BFF, который сам обходит нужные системы. Поскольку взаимодействие между сервисами бэкенда быстрее и надежнее, чем обращения из внешней сети (особенно, когда взаимодействие фронта и бэка выполняется через интернет), общая производительность и стабильность возрастают.
Разные темпы разработки. У бизнес-логики и логики взаимодействия с пользователем совершенно разные жизненные циклы. Бизнес-процессы меняются редко и основательно. Интерфейс — это динамичная среда, где постоянно проверяются гипотезы и меняется дизайн. Выделение BFF-сервиса позволяет разорвать эту зависимость. Команда фронтенда получает свой собственный «бэкенд», который может развивать в нужном ей темпе, не отвлекая команду, ответственную за ядро системы. Конфликты между командами из-за разной скорости работы сходят на нет.
Лёгкое подключение новых клиентов. Появилось у нас мобильное приложение или, скажем, терминал для самообслуживания? Не проблема. Мы не трогаем основные сервисы, а просто пишем для нового клиента свой BFF. Это также упрощает поддержку старых версий приложений: можно менять API для новой версии, сохраняя совместимость для старой на уровне BFF.
Но, как известно, за всё хорошее надо платить. Внедрение дополнительного слоя в архитектуру — это не бесплатно.
Дополнительные сетевые задержки. Каждый запрос теперь делает лишний «прыжок» через BFF, что добавляет миллисекунды к общему времени ответа. Плюс, если раньше сложную фильтрацию могла делать сама база данных, то теперь BFF часто вынужден забирать избыточные данные по сети, чтобы отфильтровать их уже у себя в коде.
Усложнение инфраструктуры и эксплуатации. BFF — это не просто строчка на схеме, а полноценный сервис. Ему нужны свои серверы, процессы развёртывания, мониторинг и логи. Всё это — дополнительная головная боль для DevOps-инженеров и дополнительные расходы.
Рост сложности разработки и дублирование кода. Логика теперь «размазана» по нескольким сервисам, что усложняет разработку и тестирование. А если у нас веб-сайт и мобильное приложение с похожими экранами, то, скорее всего, придётся написать два почти одинаковых BFF, а значит, и поддерживать дублирующийся код.
Поэтому, прежде чем бросаться внедрять BFF, стоит сесть и честно посчитать, окупятся ли все возросшие сложность и расходы появившимися плюсами.
Когда без BFF не обойтись: критерии и стратегия внедрения
Разберёмся, какие флажки должны зажечься на дашборде проекта, чтобы всерьёз задуматься о внедрении BFF.
У вас много разных клиентов. Если у вас один сайт и, может быть, в планах мобильное приложение, то, пожалуй, овчинка выделки не стоит. Но когда у вас уже есть веб для клиентов, админка для сотрудников и мобильное приложение для партнёров — это явный сигнал, что пора задуматься о BFF.
Жёсткие требования ИБ. Если вам нужно жёстко контролировать, какие данные уходят на клиент, а впихивать эту логику в основные сервисы — значит превращать их в клубок спагетти, BFF — ваш выбор. Особенно это спасает при работе с унаследованными системами (legacy), которые трогать страшно и дорого. Проще поставить рядом с таким «чёрным ящиком» BFF-конвертер, который будет забирать из него всё, а наружу отдавать только разрешённое.
Данные разбросаны по разным сервисам. Когда для отрисовки одного экрана фронтенду нужно сходить в пять-десять разных микросервисов, это явный перебор. Перекладывать эту агрегацию на плечи клиента — плохая идея: приложение станет медленным и прожорливым. Создавать отдельный сервис-агрегатор? Так это и есть одна из ключевых функций BFF. Если без агрегации никак, значит, вам нужен BFF.
Разные «скорости» разработки. Как я уже говорил, это организационный фактор. Если команда фронтенда постоянно торопится и экспериментирует, а команда бэкенда работает медленно и основательно, их совместная работа в одном репозитории превращается в вечный конфликт. BFF позволяет им разойтись по разным комнатам и не мешать друг другу.
Хорошо, допустим, мы взвесили все «за» и «против» и решили, что BFF нам необходим.
Как его внедрять?
Если проект только начинается, всё просто — слой BFF можно сразу заложить в архитектуру.
Гораздо интереснее, когда речь идет о внедрении в уже работающий проект. Стратегия «большого взрыва», когда мы пытаемся одним махом пропустить все запросы через новый слой, скорее всего, приведёт к катастрофе. Правильный путь — постепенная, эволюционная миграция.
Начать можно с так называемого смешанного режима. Простые запросы пусть продолжают ходить напрямую в основные сервисы (через API Gateway, то есть единую точку входа для всех запросов). А вот для новых, сложных задач мы сразу начинаем использовать BFF.
Путь первый: новые запросы — в новый сервис. Появляется в бэклоге задача, требующая агрегации данных из нескольких источников или хитрой логики разграничения прав? Отлично, мы не трогаем основной бэк, а сразу реализуем для неё отдельный метод в новом BFF-сервисе.
Путь второй: постепенный рефакторинг. В какой-то момент нам всё равно придётся модифицировать один из старых сервисов. И вот тогда, в рамках плановых работ, мы выносим из него методы, отвечающие исключительно за подготовку данных для клиентов, в соответствующий BFF-сервис.
Такой подход позволяет растянуть переходный период, внедрять изменения небольшими, контролируемыми порциями и не взрывать инфраструктуру и процессы разработки одним махом.
Проектирование BFF: от данных к экранам
Раз уж мы переносим логику с фронта на бэк, стоит упомянуть подход Screen as a Service. Суть его в том, чтобы проектировать API не вокруг абстрактных сущностей («пользователи», «заказы»), а вокруг конкретных экранов в интерфейсе. Мы думаем не «какие данные у нас есть», а «какие данные нужны пользователю на этом конкретном экране».
API для BFF идеально ложится на эту концепцию. Мы проектируем его «от экрана». Фронтенд говорит: «Дай мне всё для экрана „Карточка товара“», — и BFF возвращает один-единственный JSON, в котором уже всё собрано, отформатировано и готово к отрисовке. Внутри себя BFF сам сходит во все нужные сервисы, соберёт данные и применит все правила.
Чем это может помочь?
Во-первых, радикально облегчается спецификация API. Вместо описания десятков мелких методов для получения разрозненных данных, я описываю ресурсы-экраны. Техническое задание строится вокруг пользовательских сценариев: «Для экрана „Список заказов“ нужны такие-то поля, сгруппированные по статусу». И в результате появляется только один метод API, готовящий данные для этого экрана.
Во-вторых, появляется возможность централизованно управлять логикой интерфейса. Все правила отображения определены в одном месте на бэке, а не размазаны по коду различных фронтальных приложений. К тому же BFF, находясь в доверенном контуре системы, может получить гораздо больше данных для принятия решений по отрисовке, чем это можно безопасно делать на фронте.
В-третьих, упрощается тестирование бэка для фронта. Можно проверить один-единственный эндпоинт, возвращающий экран целиком, вместо проверки на клиенте сложной цепочки вызовов.
Истории из жизни: как мы внедряли BFF
В дополнение к сказанному хотел привести примеры решения пары практических задач, которые решались с использованием данного паттерна.
История первая: собираем «зоопарк» в одном месте. У нас была система, где исторически каждый тип заявок (кредитные, сервисные и т.д.) жил в своём микросервисе. У каждого был свой формат данных и свой интерфейс. Пока пользователи работали с ними в разных разделах портала, всё было терпимо. Но со временем этот «зоопарк» разросся до неприличных размеров.
Появились новые требования:
Необходимо было создать единое рабочее место, где все заявки видны в одном списке, со сквозной фильтрацией и сортировкой, как будто данные и не были никогда разделены. И фильтровать они хотели по единому словарю, а не по разрозненным справочникам каждого отдельного сервиса. Вишенкой на торте стало требование, чтобы для разных типов заявок были доступны разные действия, причём их доступность определялась не только правами, но и содержимым самой заявки.
Так как в списке будут все типы заявок, то необходимо при отображении или к обращении к заявке в списке учитывать привилегии пользователя для типа заявки.
Дополнительно, при отображении данных заявок в интерфейсах существующих сервисов необходимо было, в зависимости от привилегий, показывать не все данные (например, для обычного сотрудника скрывать финансовую информацию).
Первой мыслью было консолидировать данные в новом, универсальном сервисе и реализовать там же методы для получения данных. Идея разбилась о суровую реальность:
такое решение требовало серьёзной доработки имеющихся сервисов для организации репликации данных, а на это ресурсов не было,
не хотелось бы опять в рамках одного сервиса смешивать бизнес-логику и логику работы с фронтом (а в перспективе было, что для каждого нового типа заявки нужно было бы дорабатывать API).
Вариант с агрегацией данных на фронтенде отмёлся сразу из-за высокой нагрузки на клиентское устройство.
В итоге мы реализовали специализированные методы в BFF-сервисе, который и взвалил на себя всю сложность общения с разнородными бэкендами и подготовки данных для существующих фронтов, но с учетом новых требований безопасности.
В случае получения общего списка заявок (с общим фильтром и сортировкой) фронтенд отправлял один-единственный запрос с фильтрами и параметрами страницы, а дальше начиналась обработка на стороне BFF.
Первым делом BFF-метод анализировал переданные с фронта фильтры. Если по типу заявок было видно, что в каком-то из сервисов-источников данных заведомо не будет, запрос туда просто не отправлялся. Для остальных же выполнялось преобразование: значения полей из общего фильтра переводились на язык, понятный каждому конкретному сервису, с использованием внутренних таблиц псевдонимов и правил трансформации значений.
Далее, поскольку наш BFF был «приклеен» к конкретному экземпляру фронтенд-приложения (через sticky session на API Gateway, механизм, который направляет все запросы одного пользователя на один и тот же сервер), он мог позволить себе хранить состояние между запросами. При переходе на следующую страницу списка сервис уже знал, что мы запрашивали раньше, и подгружал только недостающие данные, используя кэш.
После получения ответов от нескольких сервисов BFF выполнял их постобработку (как минимум, приведение к общему виду), агрегацию и сквозную сортировку, и готовый список представлял на фронт, для отображения.
И наконец, на последнем шаге, на основе привилегий текущего пользователя и данных каждой заявки, BFF вычислял доступные для неё действия: «редактировать», «просмотреть», «согласовать» и так далее. Эти флаги добавлялись прямо в объект с данными заявки. Фронтенду оставалось лишь отрисовать полученный список и показать нужные кнопки в каждой строке — никакой логики разграничения доступа на клиенте не осталось.
Что касается реализации новых требований безопасности для существующих сервисов, то в этом случае на стороне BFF использовались методы API, которые реализовывали существующую спецификацию (т. е. фронт достаточно было перенаправить по новым путям). А уже эти методы, получив данные от связанных бизнес-сервисов и информацию о привилегиях пользователя, дорабатывали ответ бизнес-сервиса, удаляя из него чувствительную информацию (или заменяя на “заглушки”).
Таким образом, фронтальное приложение стало получать готовый, обогащённый набор данных через один метод. Вся сложность маршрутизации, трансформации, агрегации, кэширования и применения прав доступа надёжно спряталась на стороне BFF, а на стороне бизнес-сервисов осталась только логика, нужная для выполнения бизнес-процесса. Это позволило нам решить задачу без рискованной переделки имеющихся систем и без риска превратить клиентское приложение в неповоротливого монстра.
История вторая: выносим всю логику авторизации из фронтенда. Раз уж у нас появился такой мощный инструмент, как BFF, мы решили пойти дальше и максимально «облегчить» фронтенд, вынеся из него всю логику, связанную с правами доступа.
Сложность была в том, что в компании жило несколько систем аутентификации и авторизации. Вдобавок к этому, у нас была собственная, довольно развесистая модель прав, которая использовала не только RBAC-подход (на основе ролей), но и ABAC (на основе атрибутов самих данных). Разбирать весь этот зоопарк на стороне фронтенда было бы трудоёмко и небезопасно.
Поэтому всю функцию определения видимости элементов интерфейса и, по сути, полной настройки GUI по ролям мы вынесли в слой BFF.
Для этого мы реализовали специальные методы в BFF-сервисе. При открытии формы она, функция, первым делом делала предварительный запрос к своему BFF, смиренно передавая ему тот токен авторизации, который ей достался от родительского приложения. BFF-сервис, как заправский полиглот, определял тип токена и его источник. Затем он обращался к нужным системам авторизации, получал базовые привилегии пользователя и обогащал их, применяя нашу собственную атрибутную модель доступа.
На основе полного и всеобъемлющего набора прав BFF вычислял, какие именно элементы интерфейса — кнопки, поля, целые блоки — должны быть показаны этому конкретному пользователю в этой конкретной ситуации. Результатом этого анализа была готовая управляющая разметка, которая и возвращалась на фронт.
Аналогично мы поступали и с данными. BFF запрашивал всю информацию из основных сервисов, а затем применял к ней те же правила видимости, преобразуя и фильтруя так, чтобы на фронт уезжали только разрешённые к показу сведения.
В итоге фронтальная форма превратилась в идеального исполнителя: она просто отрисовывала то, что ей было приказано в ответе от BFF, и не содержала в себе ни строчки логики, связанной с разграничением доступа. Такой подход дал нам невероятную гибкость в управлении доступом к новому функционалу: захотели показать новую кнопку только участникам пилотного запуска — поменяли правило в BFF, и только они её и увидят. Фронтенд-разработку при этом даже не пришлось беспокоить.
Вот, собственно, и всё. Как видите, BFF — это не просто паттерн, это философия. Философия разделения ответственности, которая позволяет бизнес-логике оставаться чистой и стабильной, а интерфейсам — развиваться и экспериментировать. Это способ подружить два мира, живущих в разном ритме.
Конечно, это не пуля из серебра, и за удобство приходится платить сложностью. Но в больших системах с кучей клиентов и жёсткими требованиями к безопасности игра, на мой взгляд, определённо стоит свеч. А как вы решаете подобные задачи? Встречали «монстров Франкенштейна» в своих проектах? Делитесь опытом в комментариях, будет интересно обсудить!





















