-
Notifications
You must be signed in to change notification settings - Fork 0
Memory
В дизайне рудиментов Memory - это поток Command -> Message, где конкретный тип означает, что обработка команды:
-
Command -> Eventзакончилась с результатом (return) -
Command -> Errorзакончилась с ошибкой (throw) -
Command -> Effectпривела к промежуточному событию (log) -
Command -> Commandтребует выполнения другой команды (call - вызов сервиса, углубление в бизнес-стек)
Память - это не шина данных, а журнал. БОльшая (а лучше - вся) логика передачи команд и сообщений между компонентами организуется роутерами, а механизмы памяти используется для принятия решений (вычислительно-просто!) в ситуациях, таких как:
- управление lock'ами
- откат транзакций
- обеспечение распределенной устойчивости
- проверка консистентности
- кэширование (!)
Для синергии Memory требуется выполнение соглашений:
- компоненты детерминированы потоком
Command->Message- значимое состояние потребителей команд не должно браться ниоткуда
- источниками событий являются порты приложения
- повторное выполнение событий из памяти должно приводить компонент в то же состояние (могут быть другие не-значащие изменения, например timestamp и порожденные id)
- память это не шина данных, значит организация подписки на тяжелые вычисления поверх стрима памяти может привести к узким местам производительности и сложно-уловимым ошибкам
- стрела времени для команд
- события в памяти не меняется задним числом
- агрегат контекста иммутабелен на время выполнения (ACID)
- прошлое можно забывать (физическая память не бесконечна)
- забывать лучше связанными частями, например обработка команды целиком, единый контекст
Сама Memory требует бойлерплейта для практического использования. Типовые кейсы использования выделены в проекции - вычислимые по потоку куски. Если Memory является полным журналом и в этом его ценность, то Projection это утилитарный сервис с конкретным предназначением.
Проекция-поток, на нее можно добавить операцию над стримами. Обычно это фильтры, преобразования и агрегатные операции:
Проекция с командами в адаптеры (за IO отвечают только они), позволяет организовать правильный откат во внешние системы при ошибках на любой стадии выполнения
Оставляет только значимые Event, позволяет восстановить состояние в другом месте или в другое время
Агрегация для сбора статистики - любые накопительные операции над потоком. Счетчик команд и событий, например.
Над потоком производится необратимое преобразование, удаляются или заменяются персональные и защищенные данные. Сам поток при этом может быть дополнительно перегруппирован, чтобы исключить деанонимизацию
Проекция-состояние, содержит слепок для удобной работы на месте, не дает гарантий последовательности.
Дерево команд, вызвавших друг друга. Полезно для мониторинга, обработки ошибок, прерываний (когда надо узнать всех, кто сейчас чем-то занят).
Ограниченная по физической памяти проекция с результатами выполнения предыдущих команд. В отличие от полноценной коллекции агрегатов умеет только перезаписывать часть состояния, но не накапливать его. Полезно ставить перед адаптерами для ускорения чтения.
Если память будет одна на все приложение, она будет хрупким и, при этом, очень избыточным местом. Всем компонентам нужны все сообщения и никто не знает что ему потребуется по цепочке преобразований - так была дискредитирована SOA на примере ESB. Чтобы избежать этой проблемы, разумно считать память не глобальной инвариантой приложения, а компонентом. При этом память приложения тоже может существовать как набор проекций из памяти модулей. Например:
- в модуле реализована память и ее проекции, которые используются только внутри модуля. В память попадают все записи, включая вызов по стеку и эффекты
- в приложении реализована проекция из памяти модуля, которая оставляет только исходную команду и ее результаты из всех модулей (эквивалент кэширования call-return)
Если не нарушены контракты, память может в моменте содержать всю информацию для текущих и недавних команд. Ее будет много, что может вести к сложностям - memory будет занимать много физической памяти, проекции будут работать медленней из-за необходимости фильтровать большое число воспоминаний. Для оптимизации можно использовать информацию из Type System:
- вычислить, какие воспоминания никогда не используются или используются только для разработки (например, Effect чаще всего это только замена логу и на него никто не подписывается)
- на основе 1. построить пре-фильтр, чтобы в память попадали только актуальные воспоминания
- сгруппировать обработчики по принципу общих воспоминаний и раутинга, это должно уменьшить расходы на итерацию по обработчикам