Skip to content
Gennady Lebedev edited this page Mar 28, 2020 · 9 revisions

В дизайне рудиментов Memory - это поток Command -> Message, где конкретный тип означает, что обработка команды:

  • Command -> Event закончилась с результатом (return)
  • Command -> Error закончилась с ошибкой (throw)
  • Command -> Effect привела к промежуточному событию (log)
  • Command -> Command требует выполнения другой команды (call - вызов сервиса, углубление в бизнес-стек)

Память - это не шина данных, а журнал. БОльшая (а лучше - вся) логика передачи команд и сообщений между компонентами организуется роутерами, а механизмы памяти используется для принятия решений (вычислительно-просто!) в ситуациях, таких как:

  • управление lock'ами
  • откат транзакций
  • обеспечение распределенной устойчивости
  • проверка консистентности
  • кэширование (!)

Контракты

Для синергии Memory требуется выполнение соглашений:

  1. компоненты детерминированы потоком Command -> Message
    • значимое состояние потребителей команд не должно браться ниоткуда
    • источниками событий являются порты приложения
    • повторное выполнение событий из памяти должно приводить компонент в то же состояние (могут быть другие не-значащие изменения, например timestamp и порожденные id)
  2. память это не шина данных, значит организация подписки на тяжелые вычисления поверх стрима памяти может привести к узким местам производительности и сложно-уловимым ошибкам
  3. стрела времени для команд
    • события в памяти не меняется задним числом
    • агрегат контекста иммутабелен на время выполнения (ACID)
    • прошлое можно забывать (физическая память не бесконечна)
    • забывать лучше связанными частями, например обработка команды целиком, единый контекст

Projection

Сама Memory требует бойлерплейта для практического использования. Типовые кейсы использования выделены в проекции - вычислимые по потоку куски. Если Memory является полным журналом и в этом его ценность, то Projection это утилитарный сервис с конкретным предназначением.

Stream projection

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

Saga

Проекция с командами в адаптеры (за IO отвечают только они), позволяет организовать правильный откат во внешние системы при ошибках на любой стадии выполнения

Event Sourcing

Оставляет только значимые Event, позволяет восстановить состояние в другом месте или в другое время

Stats

Агрегация для сбора статистики - любые накопительные операции над потоком. Счетчик команд и событий, например.

Анонимизация

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

State Projection

Проекция-состояние, содержит слепок для удобной работы на месте, не дает гарантий последовательности.

Stack

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

Cache

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

Композиция памяти

Если память будет одна на все приложение, она будет хрупким и, при этом, очень избыточным местом. Всем компонентам нужны все сообщения и никто не знает что ему потребуется по цепочке преобразований - так была дискредитирована SOA на примере ESB. Чтобы избежать этой проблемы, разумно считать память не глобальной инвариантой приложения, а компонентом. При этом память приложения тоже может существовать как набор проекций из памяти модулей. Например:

  • в модуле реализована память и ее проекции, которые используются только внутри модуля. В память попадают все записи, включая вызов по стеку и эффекты
  • в приложении реализована проекция из памяти модуля, которая оставляет только исходную команду и ее результаты из всех модулей (эквивалент кэширования call-return)

Memory & Type System

Если не нарушены контракты, память может в моменте содержать всю информацию для текущих и недавних команд. Ее будет много, что может вести к сложностям - memory будет занимать много физической памяти, проекции будут работать медленней из-за необходимости фильтровать большое число воспоминаний. Для оптимизации можно использовать информацию из Type System:

  1. вычислить, какие воспоминания никогда не используются или используются только для разработки (например, Effect чаще всего это только замена логу и на него никто не подписывается)
  2. на основе 1. построить пре-фильтр, чтобы в память попадали только актуальные воспоминания
  3. сгруппировать обработчики по принципу общих воспоминаний и раутинга, это должно уменьшить расходы на итерацию по обработчикам

Clone this wiki locally