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

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

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

Я уверен, что вы читали Известную запись в блоге Сэма Ньюмана и кучу других ресурсов о лучших друзьях, и хотя они дают вам отличное представление о том, что такое шаблон и почему он полезен, не сразу становится очевидным, как лучшепостроить лучшую подругу. Или ошибки, которые вы, вероятно, сделаете на этом пути.

Итак, без лишних слов, вот несколько подсказок, советов и приемов, а также общие советы разработчиков о Backends-for-Frontends, взятые из моего личного опыта их создания для приложений с большим объемом данных. То, что я хотел бы знать, когда только начинал.

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

1. Поймите, что вы *не* создаете шлюз API.

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

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

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

Больше трения, меньше веселья.

Backends-for-Frontends отличаются от шлюзов API тем, что они специально созданы для одного клиента/пользователя, с одним BFF для каждого клиента/пользователя.

Ваш продукт состоит из настольного приложения React, приложения для Android/iOS и приложения для Xbox/PlayStation? С шаблоном BFF у вас не будет трех клиентов, обращающихся к одному шлюзу API, который берет на себя несколько обязанностей, а вместо этого:

  • Один «бэкэнд», специально созданный для каждого из них, принадлежащий каждой клиентской команде.
  • Каждый из них меньше и менее сложен, чем шлюз API, и их легче поддерживать, поскольку существует неотъемлемое разделение задач, которое продвигает этот шаблон.
  • Каждый делает именно то, что нужно пользовательскому интерфейсу для его конкретного клиента — и ничего больше.

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

2. Рассмотрите возможность использования структуры BFF.

Создание готовых к производству BFF требует, чтобы вы заново изобрели колесо для множества частей — маршрутизация и диспетчеризация запросов, агрегация и оркестровка API, преобразование/форматирование данных, промежуточное ПО, кэширование, ведение журнала и обработка ошибок, безопасность… и это даже не учитывая фактическую Дизайн BFF API.

В сообществе нет установленной спецификации или даже консенсуса относительно того, как вы на самом деле создаете и объединяете все эти слои.

По этой причине я использую WunderGraph — бесплатную среду Backend-for-Frontend с открытым исходным кодом (лицензия Apache 2.0), которую можно развернуть с помощью Docker, что избавляет меня от необходимости писать и склеивать шаблоны.



Если вы похожи на меня:

  1. Любите TypeScript (и понимаете, почему безопасность типов необходима),
  2. Приходится создавать приложения с большим объемом данных,
  3. Необходимо объединить десятки микросервисов, баз данных и API-интерфейсов аутентификации/платежей от поставщиков SaaS.

Тогда WunderGraph отлично подходит.

Это позволяет мне объединить все эти зависимости — неважно, построены ли они с использованием разных технологий, с использованием разных рабочих процессов аутентификации/авторизации или возвращают данные в разных форматах — в один унифицированный, безопасный, расширяемый API. Затем я могу написать операции GraphQL или TypeScript для агрегирования, обработки, проверки или иного получения нужных мне данных в виде JSON через RPC.

Мне не нужно вручную составлять и организовывать все эти зависимости. Я просто определяю их декларативно как «конфигурация как код», позволяю WunderGraph SDK сгенерировать для меня унифицированный API, а затем получаю безопасный доступ как к интерфейсу, так и к серверу (с поддержкой всех основных интерфейсных фреймворков, таких как React, NextJS, Remix, Astro, Svelte, Expo, Vue и др.).

Вам никогда не придется работать ни с чем, кроме кода TypeScript, и у вас будут встроенные функции кэширования, тестирования/моделирования, безопасности, аналитики, мониторинга и трассировки.

Чтобы начать работу с WunderGraph для создания BFF, ознакомьтесь с их документацией здесь.

3. Кэширование, аутентификация и ведение журнала отлично работают на уровне BFF.

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

Опять же, это возвращается к тому, что BFF отлично знает своего клиента. Поскольку мы точно знаем данные, методы аутентификации и требования/стратегии кэширования, которые нам потребуются для данного клиента (и его формат), мы можем передать эти операции BFF.

Кэширование

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

WunderGraph, например, автоматически хеширует все операции BFF, превращая их в постоянные запросы, которые отвечают только на клиентские запросы действительных хэшей. Сервер WunderGraph BFF генерирует Entity Tag (ETag) для каждого ответа — сравнивая его с ETag на сервере для каждого последующего запроса — и если они совпадают, это указывает на то, что ничего не изменилось, и кешированная версия клиента все еще в силе. Это открывает очень быстрые стратегии устаревания во время повторной проверки на клиенте без необходимости вручную устанавливать время истечения срока действия и тщательно рассчитывать его, чтобы он был равен самому свежему фрагменту контента, необходимому для данной агрегации.

Авторизация

Аутентификация часто включает интеграцию с внешними поставщиками удостоверений, каталогами пользователей или системами единого входа (SSO). Это функциональность, которая идеально подходит для уровня BFF по целому ряду причин, которые выходят за рамки простого упрощения кодовых баз внешнего и внутреннего интерфейса:

  1. У каждого клиента (точнее, взаимодействия с пользователем) могут быть уникальные требования к аутентификации. Внедрив аутентификацию в BFF, вы можете настроить логику аутентификации в соответствии с конкретными потребностями/стандартами каждого клиента. Это позволяет выполнять точную контекстно-зависимую аутентификацию.
  2. Гораздо разумнее реализовать аутентификацию в BFF, чем на еще одном сервере Nginx выше по течению, который вам придется тестировать, развертывать и поддерживать независимо.
  3. Кроме того, наличие аутентификации в BFF — это просто еще один уровень безопасности, поскольку BFF по своей сути скрывает всю внутреннюю архитектуру / реализацию от клиента.
  4. Если вашему приложению необходимо поддерживать аутентификацию с использованием нескольких учетных данных — классического имени пользователя/пароля, внешних поставщиков OAuth, таких как Google/GitHub, 2FA/MFA и т. д., — BFF может интегрировать несколько поставщиков удостоверений, сопоставляя их с унифицированным интерфейсом для вашего клиента. (А с помощью WunderGraph вы можете добавлять провайдеров аутентификации, как если бы вы добавляли пакеты с помощью NPM — подробнее см. здесь.)
  5. BFF также может реализовывать детальный контроль доступа на основе ролей пользователей, широко известный как контроль доступа на основе ролей (RBAC). RBAC, реализованный в BFF, упрощает обслуживание и обновление правил управления доступом, поскольку логика авторизации находится в централизованном месте.

Ведение журнала

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

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

Но это больше, чем просто это. Реальная ценность, добавляемая при регистрации на уровне BFF, — это контекст. Лучшие друзья обладают ценной контекстной информацией о каждом запросе. Они могут извлекать важные данные, такие как личность пользователя, тип используемого внешнего приложения, конечные точки API, к которым осуществляется доступ, и отправленные параметры. На самом деле вы могли бы обогатить журналы этим важным контекстом, упростив отладку на несколько порядков.

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

4. Нормализуйте свои ошибки в BFF.

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

Но эти подчиненные службы могут выходить из строя совершенно по-разному, и они также могут возвращать ошибки совершенно по-разному. Некоторые могут выдавать общий HTTP 500 (и вам это может не понравиться), некоторые выдавать HTTP 200 OK, но включать данные об ошибках в тело, а некоторые даже вообще не возвращают JSON, а XML/HTML.

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

Вот пример. В обычном REST API запрос, не прошедший проверку, вернет вам код состояния HTTP 4xx или 5xx, но что, если одна из ваших доменных служб является API GraphQL?

Допустим, этот запрос на мутацию терпит неудачу (очевидно, потому что во входных данных отсутствует имя).

mutation {
  updateUserProfile(input: { name: "" }) {
    id
    name
    email
  }
}

Но это всегда будет приводить к коду состояния 200, а полезные данные ответа содержат конкретную информацию об ошибке. Если вы переложите эту ответственность на клиента, необходимая логика обработки ошибок (с надлежащей обратной связью UI/UX) сделает ее раздутой и трудной в обслуживании.

{
  "data": {
    "updateUserProfile": null
    },
    "errors": [
        {
        "message": "Field 'updateUserProfile' is missing required arguments: input",
        "locations": [
        {
        "line": 2,
        "column": 3
        }
      ],
      "extensions": {
        "code": "BAD_USER_INPUT"
      }
    }
  ]
}

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

{
  "status": "error",
  "code": "BAD_USER_INPUT",
  "message": "Field 'updateUserProfile' is missing required arguments: input"
}

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

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

5. Интеграционное тестирование дает вам максимальную отдачу от вложенных средств в BFF.

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

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

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

Узнайте, как тестовые и имитационные серверы WunderGraph упрощают тестирование типов. 👉

Фронтенд- и бэкенд-команды должны только вместе согласовать контракт API, и, если доменные службы/бизнес-логика еще не готовы, клиентские команды могут просто смоделировать данные на своем собственном уровне BFF. Монолитная серверная команда, обслуживающая нужды конкурирующих клиентских команд, никогда не станет узким местом.

6. Не беспокойтесь о DRY.

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

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

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

Если бы вы объединили это дублирование обратно в абстракцию, это было бы уже не так. Несколько команд/приложений теперь будут зависеть от общей службы, и вы не сможете двигаться быстро, потому что независимо от того, кто теперь несет ответственность за общую службу/библиотеку, им часто придется работать с другими командами и придумывать стратегии. для критических изменений, требований к задержке и т. д. Вы бы просто создали еще одно узкое место.

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

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

7. Документация станет постоянной рутиной.

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

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

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

Заключение

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

Когда вы должны строить BFF? Идеальным сценарием было бы, когда вам нужно поддерживать несколько клиентских платформ, каждая из которых имеет уникальные потребности и ограничения. Принятие шаблона BFF также может решить организационные проблемы с коммуникацией, как это может сделать GraphQL, за исключением того, что BFF имеют преимущество, когда передача ответственности за данные клиенту невозможна (проблемы с размером пакета, потребителям API необходимо изучить новую парадигму, безопасность вопросы и др.)

Попутно лучшее, что вы можете сделать, чтобы обеспечить хороший опыт разработчика, — это принять фреймворк BFF, такой как WunderGraph. Писать выделенный серверный компонент в той же кодовой базе, что и ваш клиент, совместно использовать типы и анализировать/преобразовывать входящие данные на уровне сервера (с намного более быстрым соединением) без заполнения клиентского пакета? Фантастический DX. Кроме того, WunderGraph Cloud упрощает развертывание BFF вместе с интерфейсом.