Паттерны проектирования распределенных систем на реальных примерах

Часть 1

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

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

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

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

Паттерны управления данными в микросервисной архитектуре:

Паттерны коммуникации микросервисов:

  • Шаблон «API-шлюз» (API Gateway)
  • Шаблон «Бэкенды для фронтендов» (Backends for Frontends, BFF)

Паттерны повышения отказоустойчивости:

  • Шаблон «Автоматический выключатель» (Circuit Breaker)
  • Шаблон «Переборка» (Bulkhead)

Паттерны организации периферийных задач (мониторинг, безопасность, отказоустойчивость, внешние конфигурации):

  • Шаблон «Посредник» (Ambassador)
  • Шаблон «Прицеп» (Sidecar)
  • Шаблон «Тестирование контрактов, ориентированных на потребителя» (Consumer-Driven Contract Testing)
  • Шаблон «Внешняя конфигурация» (External Configuration)

Безусловно, существует и множество других паттернов, например, «Анти-коррупционный слой» (Anti-Corruption Layer), который помогает интегрировать старые системы с новыми, или Шаблон «Экземпляр сервиса на хост» (Service Instance Per Host), который касается тонкостей развертывания. Не хочется на них акцентировать внимание в этой статье, так как они больше про конкретную реализацию, чем про планирование архитектуры.

Паттерны управления данными в микросервисной архитектуре

  • Шаблон «База данных на сервис» (Database Per Service)

Паттерн "База данных на сервис" (Database Per Service) является одним из основных архитектурных принципов в микросервисной архитектуре. Он предполагает, что каждый микросервис имеет свою собственную базу данных, что обеспечивает высокую изоляцию данных, улучшает масштабируемость и облегчает управление системой. При создании системы с нуля стоит опираться на этот паттерн, так как он позволяет каждому микросервису развиваться и развёртываться независимо, что приводит к гибкости и устойчивости всей системы.

Разные сервисы могут иметь различные требования к хранению данных. Для одних оптимальны реляционные базы данных, для других — NoSQL решения, такие как MongoDB или Neo4J.

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

Представьте, что у вас есть стартап по доставке еды - ваша команда разделена на несколько групп: одна работает над сервисом управления заказами, другая — над сервисом рекомендаций, а третья — над сервисом аналитики. Сервис управления заказами использует реляционную базу данных PostgreSQL для хранения детальной информации о заказах. Сервис рекомендаций использует NoSQL базу данных MongoDB для быстрой работы с неструктурированными данными о предпочтениях пользователей. Сервис аналитики использует ElasticSearch для эффективного поиска и анализа большого объема данных. Благодаря этому каждый сервис может развиваться независимо и использовать наиболее подходящую технологию для своих задач, что ускоряет разработку и повышает производительность всей системы.

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

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

Часто паттерн Database Per Service противопоставляют другому шаблону — Shared Database («Разделяемая база данных»). Последний представляет собой антипаттерн и подразумевает использование одного хранилища данных несколькими микросервисами. Это может привести к тесной связанности сервисов, что усложняет их независимое масштабирование, обновление и отладку. Кроме того, Shared Database увеличивает риск конфликтов и проблем с производительностью, что делает его менее привлекательным выбором для современных микросервисных архитектур. Но, даже с учетов всех этих недостатков, использовать Shared Database на НЕБОЛЬШОМ ПРОЕКТЕ – приемлемо, и помогает избежать излишней сложности.

  • Шаблон «API-композиция» (API Composition)

Представьте крупный онлайн-магазин, в котором несколько команд разработчиков работают над различными микросервисами: одна команда отвечает за каталог товаров, другая за управление заказами, третья за обработку платежей, а четвертая за управление отзывами и рейтингами. Чтобы обеспечить единый пользовательский интерфейс, команда интеграции разрабатывает слой API Composition.

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

GraphQL идеально подходит для реализации паттерна API Composition благодаря своей способности объединять данные из различных источников в один запрос. С помощью GraphQL клиентское приложение может запрашивать именно те данные, которые ему нужны, без перегрузки избыточной информацией. Это достигается путем создания GraphQL-схемы, которая описывает, какие данные доступны и как их можно запросить. В результате, GraphQL сервер может агрегировать данные из нескольких микросервисов или баз данных и возвращать их в одном, оптимизированном ответе.

Кроме того, инструменты, такие как Apollo Server и Apollo Federation, значительно упрощают создание распределенных GraphQL-схем. Apollo Federation, например, позволяет создавать модульные и масштабируемые GraphQL-сервисы, которые могут агрегировать данные из множества микросервисов.

Часть 2