Event Sourcing

Event Sourcing — це шаблон проєктування, який представляє стан об'єкта у вигляді множини подій.

Проблема

Потрібно мати доступ до історії змін сутності.

Переваги та недоліки

Переваги

  • доступ до історії змін сутності
  • можливість відновити стан на момент часу

Недоліки

  • важкий в реалізації
  • важко опрацювати зміну структури моделі
  • поганий коли часто потрібно знати стан сутності

Зауваження

  • Події не можна видаляти чи змінювати. Будь-який новий стан сутності потрібно змінювати шляхом додавання нової події.
  • Для того щоб покращити продуктивність і не застосовувати всі події вводять термін snapshot. Це збережений стан моделі із врахуванням подій на момент часу. Існують різноманітні стратегії додавання snapshot'ів, від кількості подій до часового проміжку між ними.

Опис мовою C#

Додамо деякі класи, які будуть симулювати реальні об'єкти.

public class User
	{

		public int Id { get; internal set; }
		public string Name { get; internal set; }

		public override string ToString()
		{
			return $"User = {Id} - {Name}";
		}
	}

Додамо базовий клас для подій

public abstract class EventBase
	{
		public int Id { get; }
		public EventBase(int entityId)
		{
			Id = entityId;
		}


		public abstract void Apply(User entity);
	}

Та декілька реалізацій цих подій:

public class UserCreatedEvent : EventBase
	{
		private readonly int id;
		private readonly string name;

		public UserCreatedEvent(int id, string name)
			: base(id)
		{
			this.id = id;
			this.name = name;
		}

		public override void Apply(User entity)
		{
			entity.Id = id;
			entity.Name = name;
		}
	}

	public class UserNameChangedEvent : EventBase
	{
		private readonly string name;

		public UserNameChangedEvent(int id, string name)
			: base(id)
		{
			this.name = name;
		}

		public override void Apply(User entity)
		{
			entity.Name = name;
		}
	}

Змінимо нашу сутність таким чином, щоб вона містила інформацію про події:

public class User
	{
...
		internal User() { }
		public User(int id, string name)
		{
			Id = id;
			Name = name;

			events.Add(new UserCreatedEvent(id, name));
		}

		public void SetName(string name)
		{
			Name = name;

			events.Add(new UserNameChangedEvent(this.Id, name));
		}


		private ICollection<EventBase> events = new List<EventBase>();
		public IEnumerable<EventBase> Events => events;
	}

Та сховище, яке вміє працювати із нашою сутністю та подіями:

public class Store
	{
		private readonly ICollection<EventBase> events = new List<EventBase>();

		public void Add(User user)
		{
			foreach (var domainEvent in user.Events)
			{
				events.Add(domainEvent);
			}
		}

		public User GetById(int id)
		{
			var domainEvents = events.Where(e => e.Id == id);

			var user = new User();
			foreach (var domainEvent in domainEvents)
			{
				domainEvent.Apply(user);
			}
			return user;
		}
	}

Використання цього шаблону не помітне для користувачів:

class Program
	{
		static void Main(string[] args)
		{
			var store = new Store();

			var user = new User(id: 1, name: "John");
			user.SetName("Jane");

			store.Add(user);

			var userFromStore = store.GetById(1);

			Console.WriteLine(userFromStore);
		}
	}


Реалізація

C#

Приклад реалізації на мові С#
using System;
using System.Linq;
using System.Collections.Generic;

namespace EventSourcing
{
	public abstract class EventBase
	{
		public int Id { get; }
		public EventBase(int entityId)
		{
			Id = entityId;
		}

		public abstract void Affect(User entity);
	}

	public class User
	{
		public int Id { get; private set; }
		public string Name { get; private set; }
		public override string ToString()
		{
			return $"User = {Id} - {Name}";
		}

		internal User() { }
		public User(int id, string name)
		{
			Apply(new UserCreatedEvent(id, name));
		}

		public void SetName(string name)
		{
			Apply(new UserNameChangedEvent(this.Id, name));
		}

		public void Apply(UserCreatedEvent userCreatedEvent)
		{
			this.Id = userCreatedEvent.Id;
			this.Name = userCreatedEvent.Name;

			events.Add(userCreatedEvent);
		}
		public void Apply(UserNameChangedEvent userNameChangedEvent)
		{
			this.Name = userNameChangedEvent.Name;

			events.Add(userNameChangedEvent);
		}


		private ICollection<EventBase> events = new List<EventBase>();
		public IEnumerable<EventBase> Events => events;
		public void ClearEvents()
		{
			this.events.Clear();
		}
	}

	public class UserCreatedEvent : EventBase
	{
		public int Id { get; }
		public string Name { get; }

		public UserCreatedEvent(int id, string name)
			: base(id)
		{
			this.Id = id;
			this.Name = name;
		}

		public override void Affect(User entity)
		{
			entity.Apply(this);
		}
	}
	public class UserNameChangedEvent : EventBase
	{
		public string Name { get; }

		public UserNameChangedEvent(int id, string name)
			: base(id)
		{
			this.Name = name;
		}

		public override void Affect(User entity)
		{
			entity.Apply(this);
		}
	}

	public class Store
	{
		private readonly ICollection<EventBase> events = new List<EventBase>();

		public void Add(User user)
		{
			foreach (var domainEvent in user.Events)
			{
				events.Add(domainEvent);
			}
		}

		public User GetById(int id)
		{
			var domainEvents = events.Where(e => e.Id == id);

			var user = new User();
			foreach (var domainEvent in domainEvents)
			{
				domainEvent.Affect(user);
			}
			user.ClearEvents();
			return user;
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			var store = new Store();

			var user = new User(id: 1, name: "John");
			user.SetName("Jane");

			store.Add(user);

			var userFromStore = store.GetById(1);

			Console.WriteLine(userFromStore);
		}
	}
}


Див. також

Джерела

  • A simple snapshots example [Архівовано 9 березня 2020 у Wayback Machine.]
  • Event sourcing: handle event schema changing [Архівовано 24 липня 2021 у Wayback Machine.]
  • п
  • о
  • р
Основні шаблони
Абстрагування (програмування) • Делегування (Delegation) • Інтерфейс (Interface) • Інтерфейс-маркер (Marker Interface) • Незмінний інтерфейс (Immutable Interface) • Незмінний об'єкт (Immutable Object) • Функціональний дизайн (Functional Design) • Контейнер властивостей (Property Container) • Канал подій (Event Channel)
Твірні шаблони
Абстрактна фабрика (Abstract Factory) • Будівник (Builder) • Одинак (Singleton) • Прототип (Prototype) • Фабричний метод (Factory Method) • Пул об'єктів • Fluent builder • Мультитон • Лінива ініціалізація Отримання ресурсу, як ініціалізація (Resource Acquisition Is Initialization)
Структурні шаблони
Адаптер (Adapter) • Декоратор (Decorator) • Замісник (Proxy) • Компонувальник (Composite) • Міст (Bridge) • Легковаговик (Flyweight) • Фасад (Facade) • Модуль • Виділення приватного класу даних • Близнюки
Шаблони поведінки
Відвідувач (Visitor) • Інтерпретатор (Interpreter) • Ітератор (Iterator) • Команда (Command) • Ланцюжок відповідальностей (Chain of Responsibility) • Посередник (Mediator) • Спостерігач (Observer) • Стан (State) • Стратегія (Strategy) • Знімок (Memento) • Шаблонний метод (Template Method) • Одноразовий відвідувач • Null object • Специфікація • Feature toggle • Мультиметод • Перехоплювач (Interceptor) • Накопичувач (Collecting Parameter) • Слуга (Servant)
Функційні
Функтор • Генератор • Замикання • Монади • Каррінг • Функція зворотного виклику • Функція вищого порядкуВкладена функція • Результат (Result)
Патерни
конкурентного
програмування
Блокування • Модель акторів • Бар'єр • Монітор • Семафор • М'ютексПланувальник операційної системиЛокальна пам'ять ниток • Оптимістичне блокування (Optimistic Offline Lock) • Песимістичне блокування (Pessimistic Offline Lock) • Активний об'єкт (Active Object)
Кешування
Архітектурні
Базові шаблони
Клієнт-серверна архітектураFront end та back endТриярусна архітектура • Гексагональна архітектура (Архітектура портів та адаптерів) • Відокремлений інтерфейс (Separated Interface) • Сервісно-орієнтована архітектураМікросервісиPush/Pull модель
Шаблони об'єктного структурування
Шаблони представлення
MVCPureMVCHMVCMVPMVVMPost/Redirect/Get
Шаблони предметно-орієнтованого проєктування
Rich/Anemic модельDDD • Інваріант • EntityValue ObjectAggregate RootDTORepositoryПатерн сервісного рівня (Service Layer) • Фабричний метод (Factory Method) • Специфікація
Шаблони сервісно-орієнтованої архітектури
Архітектура
корпоративних
програмних
додатків
Базові шаблони
Об'єкт-значення (Value Object) • Гроші (Money) • Особливий випадок (Special Case) • Супертип рівня (Layer Supertype) • Відокремлений інтерфейс (Separated Interface) • Шлюз (Gateway) • Розподільник (Mapper) • Реєстр (Registry) • Плагін (Plugin) • Набір записів (Record Set) • Заглушка сервісу (Service Stub)
Шаблони логіки домену
Сценарій транзакції (Transaction script) • Модель предметної області (Domain model) • Обробник таблиці (Table Module) • Патерн сервісного рівня (Service Layer)
Шаблони сховища даних
Активний запис (Active Record) • Шлюз до даних таблиці (Table Data Gateway) • Шлюз до даних запису (Row Data Gateway) • Відображення даних (Data Mapper)
Шаблони об'єктно-реляційної поведінки
Одиниця роботи (Unit Of Work) • Мапа відповідності (Identity Map) • Ліниве завантажування (Lazy Load)
Шаблони об'єктно-реляційного структурування
Поле первинного ключа (Identity Field) • Розмітка зовнішніх ключів (Foreign Key Mapping) • Розмітка зв'язків таблиць (Association Table Mapping) • Відображення залежних об'єктів (Dependent Mapping) • Об'єднане значення (Embedded Value) • Серіалізований великий об'єкт (Serialized LOB) • Наслідування з однією таблицею (Single Table Inheritance) • Наслідування з таблицею для кожного класу (Class Table Inheritance) • Наслідування з таблицею для кожного конкретного класу (Concrete Table Inheritance) • Відображення із наслідуванням (Inheritance Mappers) • База даних звітності
Шаблони обробки об'єктно-реляційних метаданих
Відображення на основі метаданих (Metadata Mapping) • Об'єкт-запит (Query Object) • Сховище (Repository)
Шаблони вебпредставлення
Модель-вид-контролер (Model View Controller) • Контролер сторінки (Page Controller) • Єдина точка входу (Front controller) • Контролер аплікації (Application Controller) • Шаблонізатор (Template View) • Перетворювач (Transform View) • Двокрокова шаблонізація (Two Step View)
Шаблони розподіленої обробки даних
Шаблони локального конкурентного програмування
Оптимістичне блокування (Optimistic Offline Lock) • Песимістичне блокування (Pessimistic Offline Lock) • Блокування із низьким рівнем деталізації (Coarse Grained Lock) • Неявне блокування (Implicit Lock)
Шаблони збереження стану сеансу
Збереження стану сеансу на стороні клієнта (Client Session State) • Збереження стану сеансу на стороні сервера (Server Session State) • Збереження стану сеансу в базі даних (Database Session State)
Тестування
PageObjectМакет об'єкта (Mock Object) • Заглушка сервісу (Service Stub) • Скромний об'єкт (Humble Object)
Інші
Впровадження залежностейIoC контейнер • Локатор служб (Service Locator) • М'яке видалення (Soft Delete) • Auditable Entity • Entity Component System (ECS)Extract, Transform, Load (ETL)
Див. також
Design Patterns (книга) • Бізнес-логіка • Інваріант • Зв'язність (Coupling) • Пов'язаність (Cohesion) • Закон ДеметриKISSDRYYAGNITell Don't Ask • SOLID • CQRSGRASPІдемпотентністьМартін ФаулерАнтипатерн