В старые добрые времена приложения были простыми. Браузер отправлял запрос на конечную точку веб-приложения, последнее получало данные из базы данных и возвращало ответ.
Появление мобильных клиентов и интеграций с другими приложениями нарушило эту простоту. В этой статье я хочу обсудить одно из решений, позволяющих справиться со сложностью.
Усложнение архитектуры системы
Давайте сначала смоделируем описанную выше простую архитектуру.
Мобильные клиенты изменили этот подход. Площадь дисплея мобильных клиентов меньше: чуть меньше для планшетов и намного меньше для телефонов.
Возможным решением было бы вернуть все данные и позволить каждому клиенту отфильтровать ненужные. К сожалению, телефонные клиенты также страдают от более низкой пропускной способности. Не каждый телефон обладает возможностями 5G. Даже если бы это было так, это бесполезно, если он расположен в глуши, а точка подключения обеспечивает только H+.
Следовательно, избыточная выборка — не вариант. Каждому клиенту требуется свое подмножество данных. При использовании монолитов можно предложить несколько конечных точек в зависимости от каждого клиента.
Можно разработать веб-приложение с определенным слоем на переднем плане. Такой слой определяет клиента, от которого поступил запрос, и отфильтровывает неактуальные данные в ответе. Избыточная выборка в веб-приложении не является проблемой.
В настоящее время микросервисы — это модная тенденция. Все и каждый хотят реализовать микросервисную архитектуру.
За микросервисами стоит идея команд, состоящих из двух пицц. Каждая команда автономна и отвечает за один микросервис — или одно внешнее приложение. Для того чтобы избежать связи между усилиями по разработке, каждая команда микросервиса публикует свой API-контракт и очень тщательно обрабатывает изменения.
Каждый микросервис должен обслуживать данные, строго необходимые для каждого типа клиента, чтобы избежать вышеупомянутой проблемы избыточной выборки. При небольшом количестве микросервисов громоздко заставлять каждый из них отфильтровывать данные в зависимости от клиента; при большом количестве это просто невозможно. Таким образом, коэффициент деления между количеством микросервисов и количеством различных клиентов делает выделенные конечные точки данных на каждом микросервисе экспоненциально дорогими.
Решение: Бэкенд для фронтенда
Идея, лежащая в основе BFF заключается в том, чтобы перенести логику из каждого микросервиса в выделенную развертываемую конечную точку. Последняя отвечает за:
- Получение данных от каждого требуемого микросервиса
- извлечение соответствующих частей
- агрегирование их
- и, наконец, возвращает их в формате, соответствующем конкретному клиенту.
Одна и та же команда разрабатывает клиента и связанный с ним BFF. BFF предлагает тот же компромисс, что и микросервисы: повышение скорости разработки за счет увеличения сложности системы.
Отдельная единица развертывания по сравнению с API-шлюзами
Литература о BFF подразумевает выделенные единицы развертывания, как на диаграмме выше. Некоторые посты, например, этот, противопоставляют BFF шлюзам API. Но концептуальная диаграмма не обязательно должна совпадать один в один с диаграммой развертывания.
Как и во многих других областях, люди должны больше внимания уделять организационной стороне вещей и меньше — технической. В данном случае наиболее важным моментом является то, что команда, отвечающая за front-end, также несет ответственность за BFF. Будет ли это отдельная единица развертывания или часть конфигурации шлюза API — это уже детали реализации.
Например, в Apache APISIX каждая команда может развернуть свой код BFF независимо в виде отдельного плагина.
Соображения по производительности
Для монолита ситуация следующая:
- Запрос от клиента к монолиту занимает определенное время T. Он проходит через Интернет, и T, вероятно, длинный.
- Различные внутренние обращения к базе данных (базам данных) пренебрежимо малы по сравнению с T.
При переходе на микросервисы клиенту нужно обращаться к каждому из них по очереди. Следовательно, время становится Σ(T1, T2, Ti, Tn) для последовательных вызовов. Поскольку это неприемлемо, клиенты обычно используют параллельные вызовы. Время становится max(T1, T2, Ti, Tn). Обратите внимание, что даже в этом случае клиенту необходимо выполнить n запросов.
В случае BFF мы возвращаемся к одному запросу за время T, независимо от реализации. По сравнению с монолитом, есть дополнительные запросы t1
, t2
, ti
, tn
от BFF к микросервисам, но они, вероятно, расположены вместе. Следовательно, общее время будет больше, чем для монолита, но поскольку каждый t
намного короче, чем T
, это не сильно повлияет на пользовательский опыт.
Заключение
Возможно, вам не стоит внедрять микросервисы. Если вы это сделаете, микросервисы не должны возвращать данные целиком и заставлять клиентов отвечать за их очистку. Таким образом, микросервис должен возвращать именно те данные, которые необходимы, в зависимости от клиента. Это вводит сильную связь между микросервисом и его клиентами.
Вы хотите устранить эту связь. Чтобы достичь этого, подход Backend For Front-end извлекает логику очистки из каждого сервиса в специальный компонент, которому также поручено агрегировать данные. Каждая клиентская команда также отвечает за свой выделенный BFF: когда клиент меняет свои требования к данным, команда может развернуть новую версию BFF, адаптированную к новым потребностям.
BFF — это концептуальное решение. Ничто не обязывает размещать логику сбора/очистки/агрегирования данных в определенном месте. Это может быть специальный модуль развертывания или плагин в API-шлюзе.
В одном из следующих постов я продемонстрирую различные шаги, описанные в этом посте.
Чтобы продолжить:
- Pattern: Backends For Frontends
- Паттерн API-шлюза в сравнении с прямым взаимодействием клиента с микросервисом
- API-шлюз против бэкенда для фронтенда
Первоначально опубликовано на A Java Geek 23 июляrd, 2022