конспект курса «Reactive Architecture Learning Path» by Lightbend Academy

1 Introduction to Reactive Systems

Зачем нужна реактивность?

Главная цель реактивной архитектуры - это обеспечение отзывчивости приложения при любых обстоятельствах.

Принципы реактивных систем

reactive-traits

Реактивные системы:

  • Отзывчивые (Responsive): стабильно возвращают своевременный ответ (основной принцип);
  • Отказоустойчивые (Resilient): остаются доступными даже в случае отказов используя дублирование, изоляцию (независимость), локализацию отказов и делегирование восстановления после сбоев;
  • Гибкие (Elastic): остаются отзывчивыми (responsive) при изменении нагрузки на систему используя автомасштабирование;
  • Основаны на обмене сообщениями (Message Driven): используют асинхронный, неблокирующий обмен сообщениями обеспечивая слабую связанность, изолированность и прозрачность местоположения (location transparency).

Reactive System

2 Domain Driven Design

Домен - это сфера знания. В контексте ПО домен относится к моделируемым бизнес-процессам или идее.

Модель - это не ПО. Модель представляет понимание домена, а ПО является реализацией модели.

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

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

Декомпозиция домена

Обычно в прикладной области выделяются поддомены включающие родственные идеи, процессы и правила. Поддомены являются хорошей основой для микросервисов.

Запутанные процессы могут быть причиной некорректного понимания домена.

Слишком большое количество зависимостей у поддомена говорит о переусложнении.

Поддержание чистоты

Для взаимодействия с внешним поддоменом, модель которого не применима к текущему, используется, так называемый, антикоррупционный слой (Anticorruption Layer - ACL), который выполняет требуемые преобразования и поддерживает поддомены изолированными друг от друга.

Доменные операции

  1. Команды - запросы на выполнение действий;
  2. События - представляют свершившиеся действия (часто в ответ на команды);
  3. Запросы - запрос информации о домене.

В реактивной системе данные операции являются сообщениями и представляют API домена (или микросервиса).

Доменные объекты

  1. Объекты-значения (Value Objects) - неизменяемые объекты, которые определяются своими аттрибутами (равны если аттрибуты имеют одинаковые значения)
  2. Сущности (Entities) - изменяемые объекты, определяемые идентификатором (равны если равны идентификаторы);
  3. Агрегаты (Aggregates) - набор доменных объектов связанных с корневой сущностью (определяет границы транзакции).

Доменные абстракции

  1. Сервисы (Services) - объекты без состояния инкапсулирующие бизнес-логику выходящую за рамки доменных объектов;
  2. Фабрики (Factories) - объекты абстрагирующие создание сложных доменных объектов;
  3. Репозитории (Repositories) - объекты абстрагирующие получение и изменение существующих объектов.

Гексагональная архитектура (Порты и адаптеры)

Гексагональная архитектура отделяет инфраструктуру (базы данных, API-шлюзы и т.п.) от доменной модели.

Порты - доменные интерфейсы (API домена), адаптеры - конкретные реализации портов.

Зависимости идут от домена к инфраструктуре.

3 Reactive Microservices

Монолит

Характеристики монолита:

  • развертывается (deployed) как единый модуль
  • общая база данных
  • взаимодействие происходит через синхронные вызовы методов
  • сильная связанность между компонентами (обычно через БД)
  • релизы включают много изменений
  • длительный релизный цикл
  • требуется синхронизация доработок в рамках релиза

Преимущества монолита:

  • простой межмодульный рефакторинг
  • простая поддержка согласованности
  • единый процесс развертывания
  • единственный объект мониторинга
  • простая модель масштабирования (разворачивание нескольких копий с общей БД)

Недостатки монолита:

  • ограничение максимальной мощностью одной физической машины
  • степень масштабирование зависит от БД
  • все компоненты масштабируются одновременно
  • (обычно) более длительный процесс разработки
  • (обычно) сильная связанность компонентов
  • критические ошибки в одном компоненте часто приводят к отказу всего приложения

SOA (Sevice Oriented Architecture)

Домены изолированы в автономные (имеют собственную БД) Сервисы. Сервисы могут как быть частью монолита, так и разворачиваться отдельно.

Взаимодействие между сервисами происходит через API.

Микросервисы

Характеристики микросервисной архитектуры:

  • является подмножеством SOA
  • логические компоненты изолированы друг от друга
  • микросервисы разделены физически и развёртываются как самостоятельные единицы
  • каждый микросервис имеет собственное хранилище данных
  • взаимодействие происходит синхронно или асинхронно (реактивно)
  • слабая связанность между компонентами
  • короткий цикл развертывания
  • часто используется методология DevOps

Преимущества микросервисов:

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

Недостатки микросервисов:

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

Принципы изоляции

Реактивные микросервисы изолированы в разрезе:

  • Состояния (доступ к состоянию происходит исключительно через API)
  • Пространства (микросервисы должны иметь прозрачный доступ к другим сервисам)
  • Времени (асинхронные и неблокирующие запросы, согласованность в конечном счёте)
  • Отказов (отказ одного микросервиса не должен влиять на работу других сервисов)

Техники изоляции

  • Bulkheading (идея аналогична судовым отсекам в судостроении) - защита от лавинообразных отказов
  • Circuit Breaker (идея аналогична электрическим предохранителям) - изоляция отказавшего сервиса и пассивное ожидание его восстановления
  • Асинхронный неблокирующий обмен сообщениями - независимость и отсутствие ожидания ответа от других сервисов
  • Автономность - самодостаточность при работе, исправлении конфликтного состояния и восстановлении после сбоев
  • Шлюз (Gateway Service) - агрегирование сбора информации от нескольких микросервисов в одном API и обработка возможных отказов

4 Building Scalable Systems

Consistency (согласованность данных), Availability (доступность), Scalability (масштабируемость)

Система считается:

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

Производительность и масштабируемость родственные концепции:

  • производительность связана со временем отклика (время обработки одного запроса);
  • масштабируемость связана со способностью справляться с нагрузкой (количество запросов за единицу) и, в теории, неограниченна.

Распределенные системы, в силу физических ограничений, согласованы в конечном счёте. Согласованность в конечном счёте (eventual consistency) гарантирует, что при отсутствии изменений все обращения к конкретной части данных в итоге будут возвращать самое последнее значение.

Сильная согласованность (strong consistency) - обновление части данных, перед тем как стать видимым, требует согласия всех узлов. В распределенных системах сильная согласованность реализуется с использованием блокировок (locks).

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

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

Распределенные системы подчиняются Теореме CAP.

Устойчивость к разделению (partition tolerance) - система продолжает работать несмотря на отбрасывание (или задержку) сетью произвольного количества сообщений. Проблеме устойчивости к разделению подвержены все компьютерные сети.

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

  • AP - пожертвовать согласованностью;
  • CP - пожертвовать доступностью.

Шардирование (Sharding)

Для обеспечения сильной согласованности (CP) в распределенных системах часто используется шардирование (sharding) на уровне приложения.

  • Шардирование заключается в группировании сущностей в домене в соответствии с их идентификаторами (важно обеспечение равномерного распределения)
  • Группы сущностей называются шардами
  • Каждая сущность существует только в одном шарде
  • Каждый шард расположен только на одном узле (на каждый узел обычно должно приходиться не менее 10-ти шард)
  • Таким образом сущность определяет границы согласованности

CRDTs (Conflict-free Replicated Data Types)

Для обеспечения доступности в распределенных системах часто используют CRDTs.

Существует два типа CRDTs:

  • CvRDTs (Convergent Replicated Data Types) - копирование состояния между репликами (требует коммутативной, ассоциативной и идемпотентной операции слияния);
  • CmRDTs (Commutative Replicated Data Types) - копирование операций между репликами.

Лучше всего CRDTs подходит для небольших, требующих высокой доступности, наборов данных с редкими обновлениями (т.к. удаление элементов приводит к увеличению структуры данных, а не уменьшению).

5 Distributed Messaging Patterns

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

Преимущества асинхронных неблокирующих сообщений:

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

Саги (Sagas)

Saga - паттерн реализации распределенных транзакций (часто моделируется в виде конечного автомата).

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

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

Гарантия доставки

Задача двух генералов иллюстрирует невозможность синхронизации состояния двух систем по ненадёжному каналу связи, что также означает невозможность реализации однократной (exactly once) доставки сообщений.

Возможными видами доставки являются:

  • не более одного раза - at most once;
  • хотя бы один раз - at least once (требуется хранение сообщений на стороне отправителя для возможности повторной отправки).

Доставка exactly once может быть реализована с помощью at least once с идемпотентностью или дедупликацией, что требует хранения сообщений как на стороне отправителя, так и на стороне получателя.

Паттерны обмена сообщениями

  1. Прямое взаимодействие микросервисов в стиле точка-точка
  2. Использование брокера сообщений или шины в стиле издатель-подписчик

Точка-точка:

  • зависимости явно выражены
  • высокая связанность
  • явное проявление сложности

Издатель-подписчик:

  • гибкое управление зависимостями
  • низкая связанность
  • сложность системы скрыта

6 CQRS & Event Sourcing

Паттерн CQRS (Command Query Responsibility Segregation) дает возможность разъединить модель команд и модель запросов обеспечивая, тем самым, их независимость.

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