Backend for frontend bff что это
Перейти к содержимому

Backend for frontend bff что это

  • автор:

Backends for Frontends

Микросервисный пузырь как будто уже лопнул и все, кто поспешно применял этот взгляд на архитектуру бекенда либо канули в Лету, либо многократно пожалели о своем выборе этого тернистого пути. Пожалуй, это связано в первую очередь с бездумным разбиением кода на кучу маленьких тесно-связанных приложений, создания наносервисов и т.п. Всё это происходит из-за недостаточного количества хороших практик? Конечно нет, скорее из-за отсутствия информированности о простейших шаблонах проектирования таких систем. Здесь пойдет речь о шаблоке “бекенды для фронтендов”, но для начала давай разберемся, а в чем же проблема…

Проблема

Мы делаем софт у которого есть конечные пользователи. В случае разработки бекенда это может быть разношерстный пул клиентов. Это проблема. Клиенты должны с тобой взаимодействовать и для этого ты предоставляешь им API. Здесь довольно очевидные вещи и я не хотел бы останавливаться на них, разбирая термин API и говоря банальные вещи о том, что API должен быть документирован и удобен, не должен ломать совместимость от версии к версии (а лучше, должен версионироваться) и так далее, всё это ясно. А какие бывают клиенты? Вот тебе бытовая классификация потенциальных потребителей твоего апи:

  • классический веб фронтенд, написанный типично на js и работающий в браузере пользователя;
  • мобильные клиенты, обычные приложения, которые стоят на смартфонах пользователей;
  • GraphQL сервер между тобой и фронтендом;
  • интеграции с различными сервисами, когда ты определяешь api, которое будут дергать какие-то внутренние системы, которые разрабатываешь не ты;
  • внешние системы, например, какой-то другой бекенд, который написал кто-то для создания собственного сервиса, который использует тебя для части задач.

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

Какие есть варианты решения? Остановимся на нескольких клиентах, допустим у нас ожидается мобильное приложение, веб фронтенд, парочка внутренних интеграций.

Попытка решения 1

Давайте ничего не будем делать. Как гласят источники — иногда, ничего не делать — это лучшее решение. Поставим HTTP API, оформим для единообразия его в стиле REST (об этом была прошлая статья) и вуаля, всем подходит. Так ли это? На практике окажется, что в этом API сликом много данных для мобильных клиентов и вообще им приходится делать дюжину запросов, чтобы собрать полную картину в этом вашем REST. Внутренние интеграции будут недовольны необходимостью слишком много знать про твое API, да и вообще, не понятно как и где будут появляться специфичные для интеграции запросы. Зато веб фронтенду будет в принципе всё круто.

Попытка решения 2

Подумаем, можем ли мы упростить всё и поставить вместо HTTP API — GraphQL API, а уже за ним ничего не делать и снова оформить в REST стиле и попивать чай, наслаждаясь результатом? С первого взгляда создается ощущение, что да, однако, скорее всего окажется, что внутренние интеграции не захотят подобного рода взаимодействие и вообще им бы пару методов дернуть, только вот методы эти специфичные и нужны только им.

Попытка решения 3

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

Попытка решения 4

Дадим клиентам доступ напрямую к базе данных! Ладно, это шутка, никогда так не делайте, это как минимум свяжет вас по рукам и ногам.

Решение

О том, что этот шаблон в качестве решения проблемы используют на практике многие компании (например, SoundCloud) я узнал из книги Сема Ньюмана “Создание микросервисов”. Вот здесь я написал на неё обзор, ты можешь почитать. Позволь, я проиллюстрирую пример:

bff

Как становится очевидно, для каждого типа клиента заводится свой BFF (Backend For Frontend), что помогает спрятать в нём все специфики связанные с потребителем API. Если возникает потребность в дублировании части логики, то следует обратить на это внимание, возможно где-то раньше был пропущен кандидат на попадание в отдельный сервис, как, например, Service AB на рисунке выше. При всём этом, сколько BFF будет в итоге — частное решение, однако, если ты видишь, что логика работы двух мобильных клиентов с одним BFF всё больше отличается, то, вероятно, это знак к разделению Mobile BFF на Android BFF и iOS BFF.

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

На почитать:
Сем Ньюман “Создание микросервисов”
Backends for Frontends samnewman.io

Паттерны Gateway и Backend-for-Frontend

Gateway — единое окно для разных API. Backend-for-Frontend использует Gateway для обработки запросов и подготовки ответов, предназначенных для фронтенда.

Время чтения: 7 мин

Открыть/закрыть навигацию по статье

Обновлено 20 декабря 2021

Кратко

Скопировать ссылку «Кратко» Скопировано

Gateway и Backend-for-Frontend (BFF) — паттерны проектирования для разработки веб-приложений. Оба паттерна используются для обеспечения доступа к разным API с помощью одного контракта. Контракт — это всё то, что позволяет общаться двум сетевым узлам между собой: архитектура построения сетевого соединения, набор сетевых протоколов, набор адресов и портов для доступа к данным, правила формирования запросов со стороны клиентов, механизм подготовки и отправки ответов со стороны сервера.

Как понять

Скопировать ссылку «Как понять» Скопировано

Современное веб-приложение состоит из интерфейса, с которым работает пользователь, и бэкенда, который пользователь не видит. Бэкенд может быть устроен по-разному, всё зависит от задачи. Бэкенд крупных веб-приложений обычно довольно сложно устроен.

Как правило, бэкенд — это совокупность разных программ (микросервисы) или одна большая программа (монолит). В современном вебе всё чаще используется микросервисный подход, о котором вы можете прочитать в статье «Микросервисы». Проблема такого сложного бэкенда состоит в том, что каждый микросервис предоставляет уникальный программный интерфейс. Фронтенду приходится работать с каждым микросервисом отдельно и помнить API каждого. Это неудобно, создаёт жёсткую связь между фронтендом и бэкендом, требует много знаний о внутреннем устройстве бэкенда. Решение этой проблемы состоит в том, чтобы сделать один унифицированный интерфейс, который в свою очередь будет обращаться ко всем микросервисам.

Общение фронтенда с микросервисами напрямую

Одним из первых таких решений был Gateway, который является, как правило, прокси-сервером и предоставляет единое окно для доступа к данным по определённому программному интерфейсу (Application Programmable Interface — API), пересылая данные от фронтенда к нужному микросервису и обратно. Внешний вид приложения пользователя меняется, бизнес-логика меняется, клиентское приложение меняется, может меняться и контракт, но на остальные API это никак не влияет. Gateway пересылает запросы со стороны клиентов на другие API, учитывая разницу в контрактах, и этим позволяет снизить зацепление бэкенда и фронтенда в веб-приложении. Как правило, на стороне Gateway частые запросы могут кэшироваться, может быть реализована простая бизнес-логика, которая обеспечивает контроль над набором пересылаемых данных между клиентом и внешним API.

Использование Gateway

Работа Backend-for-Frontend, который является развитием идеи единого окна, похожа на Gateway в задачах обеспечения единого контракта и контроля над набором данных. BFF обязательно включает в себя Gateway, к которому предъявляются следующие требования:

  • Общие шаблоны на клиенте и сервере (например, JSX).
  • Высокая скорость ответа под нагрузкой.
  • Единый язык на клиенте и промежуточном сервере BFF.

Использование Backend-for-Frontend

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

Можно использовать BFF не только для доступа к различным API. Например, паттерн BFF прекрасно ложится на архитектуру построения веб-приложения, в которой на стороне сервера работают микросервисы. Вы можете прочитать о них в статье «Микросервисы».

Как начать

Скопировать ссылку «Как начать» Скопировано

Если в качестве языка разработки используется JavaScript, то очевидно, что необходимо обеспечить исполнение кода. Скорее всего, вы будете использовать Node.js, но есть и альтернативы: Deno или GraalVM. Node.js прекрасно подходит для большого количества операций ввода/вывода, но не в случае вычислений. Для BFF это отличный выбор.

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

Для BFF прекрасно подходит концепция слоёв. В этой концепции происходит разделение пользовательского интерфейса (в нашем случае API, к которому обращается клиент) от бизнес-логики и данных. Серверное приложение обычно построено на слоях и связях между ними. Есть несколько подходов для реализации слоёв.

Трёхуровневая (трёхзвенная) архитектура — исторически один из первых подходов, который построен на совместной работе трёх слоёв: слоя клиента, слоя бизнес-логики и слоя данных. Нижний слой — слой данных. Он взаимодействует со слоем бизнес-логики, который в свою очередь взаимодействует с клиентским слоем. В трёхуровневой архитектуре клиентский слой изолирован от слоя данных.

Трёхуровневая (трёхзвенная) архитектура

Проблемы такого подхода очевидны. Всё приложение построено в привязке к данным. Например, структура в базе данных будет полностью определять все верхние слои, что в корне неправильно. Это не позволяет обеспечивать должный уровень абстракции. Будут проблемы, если изменится какой-то внешний API или способ получения данных от микросервиса. Развивать и поддерживать такое приложение будет крайне сложно.

Domain-Driven Design (DDD) — более современный подход, когда приложение разбивается на четыре слоя: пользовательский интерфейс (клиент), бизнес-логика, домен, инфраструктура. В этом подходе нижним слоем является слой инфраструктуры, над которым располагается доменный слой (он определяет все сущности необходимые для работы слоя бизнес-логики в рамках доменной области).

Domain-Driven Design (DDD)

Такой подход неплох, но он слишком тяжёлый для BFF. Часто требования к продукту таковы, что определение домена и основной бизнес-логики должно лежать на бэкенде. Задача BFF — быть удобной и лёгкой прослойкой между фронтендом и бэкендом.

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

Чистая архитектура — ещё более современный подход, в котором используются те же четыре слоя, что и в DDD, но иначе. Слой инфраструктуры поднимается до уровня пользовательского интерфейса. Это позволяет избегать протечки абстракции. Домен становится центром приложения. В современных BFF-приложениях используется чистая архитектура.

Чистая архитектура

В качестве фреймворка часто выбирают Express в связке с Nest.js. Безусловно, проектирование конкретного приложения зависит от бизнес-задачи и доменной области веб-приложения. Вы можете почитать о нескольких успешных практиках использования BFF в следующих статьях:

  • Бэкенд для фронтенда, или Как в Яндекс.Маркете создают API без костылей.
  • Архитектура современных корпоративных Node.js-приложений.
  • Backend-for-Frontend: когда простого API не хватает.

Есть проекты с небольшими BFF, например, этот:

  • Код проекта к статье «JWT Architecture for Modern Apps»

Использование паттерна BFF для создания общих типов в бэкенде и фронтенде

image

Контракт между бэкендным сервисом и фронтендным потребителем (или клиентом) обычно является местом соединения двух миров. Такой контракт может принимать форму спецификации REST API, конечной точки GraphQL, или чего-то другого. Главное, чтобы он сообщал обеим сторонам, чего ожидать друг от друга.

Такова любовная история между бэкендом Node.js и фронтендом React. Живя в разных мирах, они нашли общий язык для общения, но этого было недостаточно — всё равно случались недопонимания: иногда один ждал, что другой скажет что-то такое, чего второй не может выразить. Такой была ситуация до недавнего времени, когда произошла генерализация TypeScript (и типов TypeScript), благодаря которой они начали говорить на одном языке.

Давайте узнаем, что такое шаблон BFF (нет, это не шаблон Best Friends Forever, как бы здорово это ни звучало), и разберёмся, как типы TS могут помочь нам создать надёжный контракт между бэком и фронтом.

Паттерн BFF

Как и другие забавные акронимы в нашей отрасли, шаблон BFF основан на концепции «Best Friends Forever», однако инженеры решили назвать его «Backend For Front-end».

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

Тем не менее, иногда случается, что клиентские приложения по каким-то причинам не всегда хотят/могут исполнять контракт вашего API. Из-за этого во фронтенд нужно добавлять новую логику для парсинга и преобразования ответа в то, что он может использовать.

И здесь в игру вступает BFF: вместо того, чтобы заставлять клиентские приложения общаться напрямую с микросервисом, мы приказываем им общаться с прокси-сервисом, который занимается отправкой запроса нужному сервису и преобразует ответ в вид, который может понять фронтенд.

На самом ли деле шаблон BFF лучше, чем отсутствие шаблонов? Преимущества:

  1. Клиентское приложение остаётся «глупым», и это предпочтительно с точки зрения безопасности и простоты использования. Чем глупее должны быть клиентские приложения, тем быстрее другие команды смогут создавать клиентские приложения. Кроме того, основная бизнес-логика и необходимая обработка данных остаются сокрытыми на стороне бэкенда.
  2. Увеличение задержки из-за дополнительного подключения в бэкенде теоретически должно быть меньше, чем влияние потребления дополнительных ресурсов во фронтенде.
  1. Из-за нового элемента (т.е. ещё одного сервиса) архитектура системы усложняется. В нашем примере рассматривается только один BFF, но потенциально вы можете создавать по одному BFF для каждого типа клиентских приложений.
  2. Из-за BFF-сервиса существует тесная косвенная связь между бэкендом и фронтендом. Да, смысл BFF на самом деле в том, чтобы контракт между бэком и фронтом был именно таким, какой нужен клиенту, что до минимума снижает вероятность внесения изменений в будущем. Однако эта вероятность никогда не равна нулю, и модификации в бэкенде или фронтенде подразумевают непосредственное изменение на другой стороне. То есть они связаны.

Что насчёт общих типов?

Общие типы (Shared types) — это специализация шаблона BFF, рассчитанная на TypeScript; она позволяет использовать общее определение типов в коде и фронтенда, и бэкенда.

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

Чтобы это сработало, нужно найти способ превратить определение типов в отдельный модуль, который смогут импортировать обе команды. Это можно сделать множеством способов, например:

  • Если у вас есть монорепозиторий, то можно просто импортировать определение из generic path в оба проекта. Однако такой подход привязывает жизненный цикл ваших типов к обоим проектам одновременно, потому что внося изменение в файл (например, в определение типа), вы влияете на весь монорепозиторий. Да, хорошо владея git, вы можете обойти эти проблемы, но это немного трудоёмко.
  • Можно превратить определение типов в модуль NPM, после чего установить в качестве зависимости в код фронтенда и бэкенда. Затем всё будет работать отлично, но на этапе начальной разработки вам необходимо будет опубликовать модуль, иначе у вас будут локальные пути импорта, которые придётся рефакторить в абсолютные. И даже после завершения начального этапа разработки любые изменения в локальной версии модуля типов нужно будет публиковать, иначе их не увидят другие проекты. Это может оказаться довольно хлопотным.

Что такое Bit?

Если вы о нём ещё не слышали, Bit — это open-source-инструмент (имеющий нативную интеграцию с платформой удалённого хостинга Bit.dev), помогающий создавать и делать общими независимые компоненты. Это компоненты (или модули), которые независимо разрабатываются, имеют собственные версии и над которыми можно совместно независимо работать.

Можно или создавать новые независимые компоненты с нуля, или постепенно извлекать компоненты из уже имеющейся кодовой базы.

Хотя кажется, что это ужасно похоже на NPM, есть и важные отличия:

  • Не нужно физически извлекать код, чтобы создавать независимые новые версии, делать его общим и совместно над ним работать. Можно «экспортировать» компонент прямо из своего репозитория. Bit позволяет задать часть кода в качестве компонента и с этого момента работать с ним независимо. В свою очередь, это позволяет упростить процесс совместного использования, потому что нет необходимости в настройке отдельного репозитория и переработке процесса импорта этих файлов в свой проект.
  • Люди, «импортирующие» ваши компоненты (а не просто устанавливающие их), также могут участвовать в совместной работе над ними, изменять их и экспортировать обратно в их «remote scope» (удалённый хостинг компонентов). Это невероятно мощный подход, если вы работаете группой команд в одной организации, потому что вы можете совместно работать над одним и тем же инструментом без необходимости работы над отдельным проектом. При импорте компонента Bit код скачивается и копируется в вашу рабочую папку. Также при этом генерируется соответствующий пакет в папке node_modules . При изменении исходного кода (в рабочей папке) пакет генерируется заново. Благодаря этому вы можете использовать его, указывая абсолютный путь, подходящий для всех контекстов (это сработает, даже если вы решите установить пакет компонента без его импорта).

Создание общего модуля типов

Чтобы показать красоту практического применения этого паттерна общих типов, я создам три компонента:

  • Разумеется, определение типов. Его будут использовать на своей стороне два других компонента. Также этот компонент будет экспортировать мок с фейковыми данными, используемый компонентом фронтенда, когда у него нет связи с бэкендом. Последняя часть нужна только в качестве примера, вероятно, вы найдёте более удобные способы решения проблемы на своей стороне.
  • Бэкенд-сервис, экспортирующий функцию, готовую к созданию HTTP-веб-сервера и передаче фиксированного ответа на каждый запрос. Так как это пример, сервер будет очень простым, однако его будет достаточно, чтобы показать полезность подхода. В своём коде он будет использовать общее определение типов для проверки того, что ответ имеет нужный формат.
  • React-компонент, который будет запрашивать данные у сервиса (или использовать данные мока) и рендерить их на странице. Разумеется, в его коде тоже будет использоваться общее определение типов.

Исходная структура

Будем считать, что вы уже установили Bit и залогинились в платформе. Теперь потратьте две минуты на создание коллекции (также называемой «scope») и назовите её «bff» (можете выбрать любое другое имя).

Затем инициализируем рабочую среду:

$ bit init --harmony

После этого отредактируем файл workspace.jsonc и зададим defaultScope значение [username].bff , в моём случае это выглядит как «deleteman.bff» .

Теперь можно перейти в терминал и создать первый компонент:

$ bit create react-component ui/client-app

Создастся новый компонент на основе React-шаблона, в котором будет присутствовать бойлерплейт. Также будет создана структура папок, необходимая для новых компонентов (должна создаться папка bff и папка ui/client-app внутри неё).

Следующие два компонента нужно создать вручную, потому что у Bit пока нет для них шаблонов:

image

Просто скопируйте показанную выше структуру, поместив общие типы в папку common/bff-types , а сервис в папку backend/service .

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

Определение типов в нашем примере будет очень простым:

//index.ts file import from './types' const ServiceResponseMocks: ServiceResponseType = [ < name: "Fernando Doglio", age: 37, address: "Madrid, Spain" >] export //types.d.ts file type PersonType = < name: string, age: number, address: string >type ServiceResponseType = PersonType[] export

Обратите внимание, что в показанный выше фрагмент кода я включил оба файла.

Код сервиса не сложнее:

//service.ts file import * as http from 'http'; import from '@deleteman/bff.bff-types'; let data: ServiceResponseType = [ < name: "Fernando Doglio", age: 23, address: "Madrid, Spain" >, < name: "Second Person", age: 99, address: "Somewhere else" >] export function newServer() < http.createServer(function (req, res) < res.setHeader('Content-Type', 'application/json'); res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET'); res.setHeader('Access-Control-Max-Age', 2592000); // 30 days res.end(JSON.stringify(data)); >).listen(8000); > //index.ts file inside backend/service export * as service from './service'

Обратите внимание, что я всегда возвращаю JSON с двумя элементами внутри и использую определение типов, чтобы создаваемая структура возвращалась без потери атрибутов. Экспортированная функция newServer создаёт простой HTTP-сервер (никакого Express или чего-то подобного) и настраивает необходимые CORS-заголовки, чтобы вы могли протестировать пример сервера со своего локального хоста.

React-компонент выглядит так:

import React, < ReactNode, useState >from 'react'; import from 'react'; import from '@deleteman/bff.bff-types'; export function ClientApp(props: ) < let [items, setItems] = useState([]) let [error, setError] = useState(null) useEffect(() => < if(props.mock) < return setItems(ServiceResponseMocks) >fetch("http://localhost:8000") .then(res => res.json()) .then( (result: ServiceResponseType) => < setItems(result as ServiceResponseType); >, (error) => < setError(error); >) >, []) if(error) < return (

Error found: ) > return (

); > function renderPeople(ppl: ServiceResponseType): ReactNode[] < return ppl.map( (person: PersonType) =>< return
>

Name:

Age:

Address:

>) >

Много кода, но на самом деле здесь не происходит ничего особо сложного.

Вы уже наверно заметили две строки import и в компоненте сервера, и в React-компоненте:

import from '@deleteman/bff.bff-types';

Обычно это должно обозначать, что я уже опубликовал модуль общих типов и теперь импортирую его. Однако благодаря Bit это не требуется. Мне достаточно было превратить модуль в компонент, после чего Bit создаёт внутри папки node_modules символическую ссылку, указывающую на мою локальную копию. Это, в свою очередь, позволяет мне работать над другими компонентами, как будто всё уже было опубликовано.

Превращаем код в компоненты

Помните, что пока мы создали только один «официальный» компонент — React-компонент. Другие два пока представляют собой только папки с кодом. Нам нужно, чтобы Bit узнал о них, и это можно сделать так:

$ bit add bff/backend/service $ bit add bff/common/bff-types

image

image

Итак, мы сообщили Bit, что эти две папки тоже содержат компоненты и что их тоже нужно отслеживать. Также это должно изменить содержимое файла .bitmap (трогать его не нужно, просто убедитесь, что он существует для каждого из модулей).

Последнее, что нужно сделать, прежде чем всё будет готово — вернуться в файл workspace.jsonc и изменить ключ variants , чтобы для каждого компонента использовалась нужная среда. Помните, что мы работаем с React-компонентом на одной стороне, с Node.js-компонентом с другой и с обобщённым компонентом, поэтому Bit должен знать об этом. Просто откройте файл и убедитесь, что раздел teambit.workspace/variants выглядит следующим образом:

"teambit.workspace/variants": < "bff/ui/client-app": < "teambit.react/react": <>>, "bff/common/*": < "teambit.react/react": <>>, "bff/backend/service": < "teambit.harmony/node": <>> >

Теперь можно выполнить bit start , запустив таким образом локальный сервер разработки. Этот сервер разработки предоставит вам всю необходимую информацию о компонентах и покажет, как будут выглядеть их документы после публикации.

Интересной для нас информацией здесь является URL импорта. Можно получить его, нажав на компонент после запуска сервера, первым делом вы увидите автоматически сгенерированную строку.

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

Если вы хотите поделиться этой работой с другими, это тоже можно сделать через Bit:

$ bit tag --all #1 $ bit export #2

Шаг 1 помечает все компоненты, то есть мы создаём для них версию, под которой они будут экспортироваться. Шаг 2 экспортирует компоненты в глобальный репозиторий Bit.dev. На случай, если вам захочется взглянуть, я поделился своим здесь.

image

Что же мы сделали?

Благодаря использованию Bit и шаблона общих компонентов мы смогли соединить бэкенд-сервис с фронтенд-приложением, обеспечив для них общее определение типов.

Но что ещё здесь произошло?

  1. Во-первых, мы разработали и опубликовали три модуля, при этом нам даже не пришлось думать о компиляторе TS, WebPack, NPM или любых других инструментах, за исключением Bit. Именно поэтому я выбрал Bit и поэтому считаю его таким полезным инструментом. Он абстрагировал все шаги, необходимые для создания трёх компонентов, даже когда они работают в трёх разных средах.
  2. Мы работали с локальной зависимостью, которая должна быть внешней зависимостью, и даже не заметили разницы. Каждое обновление, вносимое в локальную версию компонента с типами BFF, подхватывалось другими двумя компонентами, и нам не приходилось заново выполнять весь процесс экспорта.

Эта связь образуется между сервером и одним клиентом — помните, BFF связывает одно клиентское приложение (или тип приложений) со специализированной версией сервера. Он не отменяет обобщённый контракт с другими клиентами, которые могут использоваться с исходной версией сервиса.

Дата-центр ITSOFT – размещение и аренда серверов и стоек в двух ЦОДах в Москве; colocation GPU-ферм и ASIC-майнеров, аренда GPU-серверов. Лицензии связи, SSL-сертификаты. Администрирование серверов и поддержка сайтов. UPTIME за последние годы составляет 100%.

  • Блог компании ITSOFT
  • Проектирование и рефакторинг
  • Node.JS
  • ReactJS
  • TypeScript

Backend for frontend (BFF) pattern— why do you need to know it?

O ur typical issue starts when we need to integrate some API to our mobile app. Let’s imagine the case when you need to create a mobile app for an existing system. The system was one monolithic solution that exposed an API serving only the web client.

The idea from the client is not only limited to new mobile apps, but he also considers voice assistants and 3rd party services which will use our API. So the issue is that one API needs to support all these types of clients, and we need to take care of their requirements and maintenance.

The solution which can help in this case is the Backend For Frontend pattern.

Backend for Frontend (BFF) design pattern

You need to think of the user-facing application as being two components — a client-side application living outside your perimeter and a server-side component (BFF) inside your perimeter. BFF is a variant of the API Gateway pattern, but it also provides an additional layer between microservices and each client type separately. Instead of a single point of entry, it introduces multiple gateways. Because of that, you can have a tailored API that targets the needs of each client (mobile, web, desktop, voice assistant, etc.), and remove a lot of the bloat caused by keeping it all in one place. The below image describes how it works.

How to create the best API for your BFF

Let’s back to our client and his Web API, which can be a legacy XML-based solution in which only a few parts of shared data are important from a mobile perspective. Using BFF, we can extract important data and convert them to a better format (for example, JSON). It is also an excellent time to create endpoints dedicated to specific app screens or features and data required by them. For example, the below image shows us that green and yellow parts can be extracted separately from legacy API because different mobile app screens need them.

Here you can find some recommendations from me for that step:

  • Focus on UI/UX and data needed by them.
  • Don’t try to make everything generic from the beginning; this may cause that component to be used across organizations and many people will want to contribute.
  • Use particular feature first over generic usage strategy. This is the best approach to keep clean API dedicated to one client.
  • Use the gold rule of three.

Why BFF?

Decoupling of Backend and Frontend​ for sure gives us faster time to market as frontend teams can have dedicated backend teams serving their unique needs. The release of new features of one frontend does not affect the other.

We can much easier maintain and modify APIs​ and even provide API versioning dedicated for specific frontend, which is a big plus from a mobile app perspective as many users do not update the app immediately.

Simplify the system from Frontend and Backend perspectives​ as we don’t need any compromise.

The BFF can benefit from hiding unnecessary or sensitive data before transferring it to the frontend application interface, so keys and tokens for 3rd party services can be stored and used from BFF.

Allows to send formatted data to frontend and because of that can minimalize logic on it.

Additionally, give us possibilities for performance improvements and good optimization for mobile.

How to introduce BFF into the project

In our example, we have started by creating BFF only for the mobile clients, and in the first version, the web client used the same legacy API. This step is usually tiny as it covers only one BFF without refactoring of the existing system. After its release, you can show the customer and other teams (backend, other frontends teams) the benefits of it, mostly from a maintenance and development perspective. In the presented case, the whole part related to legacy API preprocessing and conversion was extracted to separate microservice from a performance perspective. The future refactoring needs of the legacy monolith system and API were another reason for this extraction.

The next step was the whole BFF structure for existing frontends. Please check the figure below which shows different types of microservices and their usage:
The blue ones are the layers on top of the monolith system which is under refactoring to microservices.
The green one is the service that is used by both BFFs at the same time.
Yellow ones, on the other hand, are duplicated from a deployment perspective and dedicated for each BFF — better performance.
The red one is dedicated only to mobile BFF and provides some mobile-specific solutions, for example, notifications, message queues, etc.

Fault tolerance in BFF

Fault tolerance is the most significant power of BFF allows us to handle most of the problems on the BFF layer instead of the client (frontend). In case of any microservice change, it can be controlled by all BFFs without emergency deployment of frontend clients. We all know that update of the mobile application on both stores is not an easy operation. It requires some additional time like review, which is not easy to estimate, and sometimes even its output can be a surprise — rejection. With BFF solution, we can cover versioning and backward compatibility separately per each client (frontend). Whole fault tolerance and strategies for that can be covered and managed in the BFF layer. For example, in our system, we can introduce separate BFF for each mobile client and avoid problems when one of them has some issues that affect the whole system, such as doing some self DDoS. In that case, we can disconnect this BFF from the system and investigate the problem inside of it without affecting the rest of the system. This is for sure a good strategy for BFFs dedicated for 3rd party services.

When use BFF?

Like many other patterns, using the BFF in your application depends on the context and the architecture you plan to follow.

For an application that only provides one front-end, I suspect Backend For Frontend will only make sense if and when you have a significant amount of aggregation required on the server-side. Still, even then, the concept of API gateway can be used. BFF style can be considered when you plan to extend the number of frontend types.

If your application needs to develop an optimized backend for a specific frontend interface or your clients need to consume data that require aggregation on the backend, BFF is for sure a suitable option. Of course, you might reconsider if the cost of deploying additional BFFs’ services is high, but even then the separation of concerns that a BFF can bring make it a fairly compelling proposition in most cases.

Conclusion

Backend For Frontend is a design pattern created with the developer and, more importantly, the user and their experience in mind. It is the answer to the ever-growing adoption of applications to meet, interact, and serve customers, ensuring consistency while still meeting their diverse and evolving needs.

Video about BFF from Droidcon Berlin

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *