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

  • производительность;
  • масштабируемость;
  • адаптивность;
  • гибкость;
  • простота;
  • надежность;
  • экономичность;
  • технические возможности команды…

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

Bounded Contexts 

Мой первый шаблон архитектуры на самом деле не является шаблоном. Однако Bounded Contexts (ограниченные контексты) — одна из самых мощных техник построения кода, который подлежит сопровождению. Bounded Contexts — концепция, взятая из стратегического дизайна DDD (Domain-Driven Design — предметно-ориентированное проектирование), но сейчас я вижу, что она применяется во многих проектах, которые не используют DDD.

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

Вот как концепции в приложении электронной коммерции могут быть разбиты на ограниченные контексты: Catalogue (Каталог), Ordering (Заказы), Payments (Платежи) и Identity (Идентификация).

Шаблон Bounded Contexts в приложении электронной коммерции

Bounded Contexts помогает бороться с тесной связанностью и сложностью, поскольку количество концепций в приложении со временем растет.

Я также рекомендовал бы организовывать код в проекте, созданном с Bounded Contexts. Это особенно полезно, если в будущем планируется разделить код на модули или микросервисы.

Вот как это может выглядеть для одного и того же приложения электронной коммерции, использующего Clean Architecture (чистая архитектура). Этот подход называется Domain-Centric Architecture (предметно-центристская архитектура — подход, который фокусируется на бизнес-домене и моделировании реальных рабочих процессов и поведения компании).

Предметно-центристская архитектура

Пример: Vertical Slice Architecture

Vertical Slice Architecture (архитектура вертикальных срезов) становится сейчас очень популярной. Она выводит эту концепцию на новый уровень, разбивая все приложение на вертикальные сегменты. Ключевую роль в этом играет понимание ограниченных контекстов.

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

Vertical Slice Architecture

Пример: Event Storming 

Отличным подходом для разработки схемы ограниченных контекстов является Event Storming (событийный штурм).

Я уже долго использую Event Storming в своих командах, и на данный момент он оказался самым эффективным инструментом для разработки и поиска решений. При применении предметно-ориентированного подхода или попытке смоделировать особенно сложную проблему Event Storming будет наиболее мощным инструментом!

Event Storming: вывод

Условия использования:

  • Средняя и высокая сложность домена.
  • Приложение, в котором количество функций или концепций будет расти с течением времени.
  • Приложение, которое может быть разделено на отдельные приложения/развертывания в будущем.

Sidecar 

Sidecar (шаблон расширения) — приложение, которое находится перед другим приложением, работающим на том же хосте. Хост может быть любым — от сервера или виртуальной машины до контейнера или pod’а в Kubernetes. Sidecar обычно используются для перехвата трафика, идущего к нижестоящему приложению и от него.

Шаблон Sidecar

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

Шаблон Sidecar также можно применять для добавления общей сквозной функциональности стандартным способом для всего портфеля приложений. Вот несколько примеров такой функциональности:

  • ведение журнала;
  • мониторинг производительности;
  • трассировка;
  • аутентификация и авторизация;
  • правила повторных попыток подключения и автоматические выключения;
  • прозрачное шифрование.

Пример: Service Mesh

Service Mesh (сервисная сетка) доводит концепцию Sidecar до крайности, объединяя каждое приложение в кластерной области данных (data plane) со своим собственным Sidecar Proxy. Все коммуникации между приложениями осуществляются через Sidecar. Это позволяет шифровать, отслеживать и защищать весь трафик стандартным способом с помощью области управления (control plane).

Архитектура Service Mesh 

Моя команда использует Service Mesh на всех наших кластерах Kubernetes, чтобы обеспечить их безопасность и мониторинг стандартным способом.

Случаи использования:

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

Publisher-Subscriber

Шаблон Publisher-Subscriber (издатель-подписчик) используется для поддержки асинхронной связи через брокер сообщений (Message Broker) между издателем (Publisher) и подписчиком (Subscriber). Брокеры сообщений содержат одну или несколько тем (Topic), которые можно опубликовать и на которые можно подписаться.

Шаблон Publisher-Subscriber 

Сообщения ставятся в очередь в теме и рассылаются нижестоящим подписчикам по принципу «первым вошел — первым вышел» (FIFO). Этот шаблон используется для интеграции данных или событий между связанными приложениями. Он также может использоваться для асинхронного планирования долго выполняющихся задач в сервисе воркера (Service Worker).

Механизмы доставки

Существует 2 основных механизма доставки (Delivery Mechanisms) между издателем и подписчиком:

  • Конкурирующие потребители (Competing Consumers): каждое опубликованное сообщение потребляется только один раз любым подписчиком (этот шаблон можно использовать для масштабирования обработки длительных задач параллельно множеством сервисов воркеров подписчиков).
  • Фанаут (Fanout): каждое опубликованное сообщение потребляется всеми подписчиками (этот шаблон обычно используется для синхронизации событий или данных между множеством нижестоящих приложений).
Механизмы доставки Publisher-Subscriber 

Условия использования:

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

Application Gateway

Application Gateway (шлюз приложений) выполняет ту же работу, что и Load Balancer (балансировщик нагрузки), маршрутизируя сетевой трафик для нескольких приложений, расположенных ниже по потоку. В то время как Load Balancer работает на уровне 3/4, используя порт/IP-адрес, Application Gateway работает на уровне 7. Это позволяет Application Gateway выполнять глубокую проверку пакетов, что способствует осуществлению маршрутизации на основе информации о приложении, такой как hostname (имя хоста), path (путь) и headers (заголовки).

Маршрутизация Application Gateway 

Наиболее распространенный сценарий маршрутизации для Application Gateway — Path-Based (на основе пути). Если в организации имеется множество различных внутренних сервисов, которые необходимо разместить (например, сервис-ориентированные или микросервисные архитектуры), то использование Application Gateway может значительно упростить обслуживание, поскольку все сервисы обслуживаются по единому URL. Кроме того, Application Gateway часто предоставляет дополнительные функции безопасности, такие как завершение работы SSL или брандмауэр веб-приложений.

Пример: Infrastructure Platform

Application Gateway играет важнейшую роль в Infrastructure Platform (инфраструктурной платформе), которую моя команда создала для размещения всех наших продуктов.

Маршрутизация мультиарендной Infrastructure Platform

Application Gateway позволяет размещать множество различных клиентских продуктов на одном URL, что сводит к минимуму количество SSL-сертификатов и DNS-регистраций, которые необходимо настраивать и поддерживать.

Условия использования:

  • требуется разместить множество приложений по одному и тому же URL или набором URL;
  • маршрутизация к нижележащим службам с использованием префикса пути (path prefix) или имени хоста (hostname).

Microservice 

Шаблон Microservice (микросервис) используется для разбиения приложения на небольшие независимые сервисы. Каждый микросервис должен иметь свой API и базу данных. Микросервисы могут использовать различные технологии, лежащие в их основе, и должны быть независимо развертываемыми и масштабируемыми.

Микросервисная архитектура

Микросервисы часто используют единый монолитный Frontend (фронтенд) и Application Gateway для обслуживания всего трафика с одного URL.

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

Условия использования:

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

Microfrontend

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

Микрофронтенды

Каждый микрофронтенд должен иметь свои независимые экраны в веб-приложении Frontend. Обычно для отображения различных микрофронтендов используется некий пользовательский интерфейс в виде обертки портала.

Большинство веб-фреймворков имеют свои способы поддержки микрофронтендов. В последнее время фреймворки соответствуют стандартам веб-компонентов (Web Components). Веб-компоненты упаковываются в отдельные JavaScript-файлы, которые позволяют размещать UI-компоненты в веб-приложении с помощью пользовательского элемента HTML. Веб-компоненты полностью инкапсулируются — это означает, что каждый микрофреймворк может использовать разные JavaScript-фреймворки, если это необходимо.

Условия использования:

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

Архитектура на основе CQRS

Микросервисы позволяют каждому сервису использовать различные технологии баз данных. Но что, если каждый микросервис также должен использовать различные технологии баз данных? CQRS (command query responsibility segregation — разделение обязанностей команд и запросов) позволяет использовать различные технологии для записи данных (Write Side — сторона записи) и чтения данных (Read Side — сторона чтения). Это может означать разные представления одних и тех же данных, а может и использование совершенно разных технологий баз данных. Идея заключается в том, что каждую сторону можно оптимизировать с учетом выполняемых ею обязанностей и ожидаемых моделей использования.

Архитектура на основе CQRS

Возможно, вы захотите использовать что-то простое и дешевое, например S3 Buckets, для записи и что-то с лучшей поддержкой запросов на стороне чтения, например Elastic Search. Реляционная база данных SQL, вероятно, лучше подходит для одной стороны, а база данных NoSQL — для другой. В зависимости от шаблонов доступа к данным, можно масштабировать каждую сторону полностью независимо.

Если используются разные базы данных, то для интеграции изменений данных между стороной записи и стороной чтения с помощью Eventual Consistency часто используется Message Broker (брокер сообщений).

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

Разновидности архитектуры на основе CQRS

Существует множество CQRS-архитектур, различающихся по степени сложности. При выборе варианта необходимо исходить из конкретных потребностей проекта.

Виды архитектуры на основе CQRS

Пример: Event Sourcing

Event Sourcing (порождение событий) — один из предпочитаемых мной видов CQRS-архитектуры. Вместо сохранения текущего состояния модели, используются хранилища событий, предназначенные только для записи полной последовательности действий, выполненных моделью. Когда возникает новая команда, текущее состояние модели/сущности «регидратируется» путем воспроизведения всех событий, которые когда-либо происходили с этим экземпляром.

Пример потока событий банковского счета

Часто приводимая аналогия, помогающая понять Event Sourcing, — банковский счет. Все транзакции хранятся в виде событий, а баланс вычисляется путем воспроизведения всех транзакций.

Каждый экземпляр модели на стороне записи хранится как собственный независимый Event Stream (поток событий). Этот поток событий может быть воспроизведен в любое время, чтобы материализовать различные представления данных. Если сторона чтения рассинхронизируется, можно запросить все события со стороны записи и пересобрать модели.

Event Sourcing как разновидность архитектуры на основе CQRS 

Условия использования:

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

Собираем все вместе: Polyglot Architecture

Все перечисленные шаблоны могут быть объединены в архитектуре вашего решения и согласованы с конкретными требованиями каждого Bounded Context (ограниченного контекста).

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

Polyglot Architecture: приложения электронной коммерции

В этой архитектуре собраны лучшие технологии и шаблоны, соответствующие требованиям каждого Bounded Context; она известна как Polyglot Architecture (полиглотная архитектура).

У каждого Bounded Context (ограниченного контекста) есть свой Microservice (микросервис) и Microfrontend (микрофронтенд). Все микросервисы обслуживаются по общему URL через Application Gateway (шлюз приложений).

Каталог использует Redis для оптимизации производительности запросов. При оформлении заказа используется PostgreSQL для обеспечения надежной реляционной согласованности. В Payments применяются CQRS-архитектура и Event Sourcing для моделирования сложных потоков платежей во времени.

У большинства микросервисов есть свой Message Broker Topic (темы брокеров сообщений) для публикации событий. Также существует множество Orchestration Services (сервисов оркестрации), потребляющих события для интеграции изменений данных из других Bounded Contexts (ограниченных контекстов) и асинхронной обработки длительных задач.

Identity Microservice (микросервис идентификации) использует KeyCloak (систему идентификации и управления доступом) для аутентификации. Существует также общая реализация Identity Sidecar для обеспечения аутентификации и авторизации стандартным образом для всех остальных микросервисов.

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

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Matt Bentley: My Favourite Software Architecture Patterns

Предыдущая статьяКак создать атомарный загрузчик в Jetpack Compose
Следующая статьяСоздатель NPM запустил менеджер пакетов JavaScript