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# ред.


Див. також ред.

Джерела ред.