82
CQRS e Event Sourcing na prática com RavenDB Elemar Júnior @elemarjr [email protected] [email protected] elemarjr.com

TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Embed Size (px)

Citation preview

Page 1: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

CQRS e Event Sourcing na prática com RavenDB

Elemar Júnior@elemarjr

[email protected]@gmail.com

elemarjr.com

Page 2: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Olá, eu sou Elemar Jr

Page 3: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Eu gosto de RavenDB

Page 4: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

CQRS e Event Sourcing na prática com RavenDB

Mas, vamos falar de

Page 5: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Vamos pensar em um cenário...

Page 6: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Precisamos criar um um cadastro de funcionários...

Page 7: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Coisa simples...Nome, Endereço Residencial e Salário

Page 8: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Começamos com um protótipo

Page 9: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

[Route("api/[controller]")]public class EmployeesController : Controller{ [HttpGet] public IEnumerable<Employee> Get() { /* .. */ }

[HttpGet("{id}")] public Employee Get(string id) { /* .. */ }

[HttpPost] public void Post([FromBody]Employee value) { /* .. */ }

[HttpPut("{id}")] public void Put(string id, [FromBody]Employee value) { /* .. */ }

[HttpDelete("{id}")] public void Delete(string id) { /* .. */}}

Podemos modelar uma API...

Page 10: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

public class Employee{ public string Id { get; set; } public string Name { get; set; } public Address HomeAddress { get; set; } public decimal Salary { get; set; }}Definir o Modelo ...

[Route("api/[controller]")]public class EmployeesController : Controller{ [HttpGet] public IEnumerable<Employee> Get() { /* .. */ }

[HttpGet("{id}")] public Employee Get(string id) { /* .. */ }

[HttpPost] public void Post([FromBody]Employee value) { /* .. */ }

[HttpPut("{id}")] public void Put(string id, [FromBody]Employee value) { /* .. */ }

[HttpDelete("{id}")] public void Delete(string id) { /* .. */}}

Page 11: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

public class EmployeesRepository{ public Employee Load(string id) { using (var session = DocumentStoreHolder.Instance.OpenSession()) { return session.Load<Employee>(id); } }

public void Save(Employee newEmployee) { using (var session = DocumentStoreHolder.Instance.OpenSession()) { session.Store(newEmployee); session.SaveChanges(); } }}

Implementar persistência....

Page 12: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Pronto!

Page 13: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Será que ficou bom?

Page 14: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Vejamos...

Page 15: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

UI (HTML + Angular)

WebAPI

RavenDB

Presentation

Business

Persistence?

Page 16: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Esse modelo não está anêmico?

public class Employee{ public string Id { get; set; } public string Name { get; set; } public Address HomeAddress { get; set; } public decimal Salary { get; set; }}

Page 17: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Cadê a linguagem ubíqua?[Route("api/[controller]")]public class EmployeesController : Controller{ [HttpGet] public IEnumerable<Employee> Get() { /* .. */ }

[HttpGet("{id}")] public Employee Get(string id) { /* .. */ }

[HttpPost] public void Post([FromBody]Employee value) { /* .. */ }

[HttpPut("{id}")] public void Put(string id, [FromBody]Employee value) { /* .. */ }

[HttpDelete("{id}")] public void Delete(string id) { /* .. */}}

Page 18: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Será que escala?

Page 19: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Será que é fácil de alterar?

Page 20: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Vamos tentar resgatar conceitos...

Page 21: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB
Page 22: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB
Page 23: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB
Page 24: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

ViewModelInputModel

Page 25: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB
Page 26: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Modelo expressa o domíniopublic sealed class Employee { public Guid Id { get; private set; } public FullName Name { get; private set; } public decimal Salary { get; private set; } public Address HomeAddress { get; private set; }

public Employee(Guid id, FullName name, decimal initialSalary) { /* .. */ }

public void RaiseSalary(decimal amount) { /* .. */ }

public void ChangeHomeAddress(Address address) { /* .. */ }}

Page 27: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Modelo expressa o domíniopublic sealed class Employee { public Guid Id { get; private set; } public FullName Name { get; private set; } public decimal Salary { get; private set; } public Address HomeAddress { get; private set; }

public Employee(Guid id, FullName name, decimal initialSalary) { /* .. */ }

public void RaiseSalary(decimal amount) { /* .. */ }

public void ChangeHomeAddress(Address address) { /* .. */ }}

Page 28: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Modelo expressa o domíniopublic sealed class Employee { public Guid Id { get; private set; } public FullName Name { get; private set; } public decimal Salary { get; private set; } public Address HomeAddress { get; private set; }

public Employee(Guid id, FullName name, decimal initialSalary) { /* .. */ }

public void RaiseSalary(decimal amount) { /* .. */ }

public void ChangeHomeAddress(Address address) { /* .. */ }}

Page 29: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Consolidação da Linguagem Ubíqua

public sealed class Employee { public Guid Id { get; private set; } public FullName Name { get; private set; } public decimal Salary { get; private set; } public Address HomeAddress { get; private set; }

public Employee(Guid id, FullName name, decimal initialSalary) { /* .. */ }

public void RaiseSalary(decimal amount) { /* .. */ }

public void ChangeHomeAddress(Address address) { /* .. */ }}

Page 30: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Voltando ao protótipo...

public Employee(Guid id, FullName name, decimal initialSalary){ /* .. */ }

Page 31: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

public sealed class Employee { public Guid Id { get; private set; } public FullName Name { get; private set; } public decimal Salary { get; private set; } public Address HomeAddress { get; private set; }

public Employee(Guid id, FullName name, decimal initialSalary) { /* .. */ }

public void RaiseSalary(decimal amount) { /* .. */ }

public void ChangeHomeAddress(Address address) { /* .. */ }}

Consolidação da Linguagem Ubíqua

Page 32: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Cadê o protótipo?

public void RaiseSalary(decimal amount){ /* .. */ }

?

Page 33: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Command

InputModel

Page 34: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Anatomia de um Comandopublic sealed class UpdateEmployeeHomeAddressCommand : EmployeeCommand{ public Address HomeAddress { get; }

public UpdateEmployeeHomeAddressCommand( EmployeeId id, Address address) : base(id) { HomeAddress = address; }}

Page 35: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Command

InputModel

Page 36: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

public class EmployeeCommandsHandler : IMessageHandler<RegisterEmployeeCommand>, IMessageHandler<RaiseSalaryCommand>, IMessageHandler<ChangeHomeAddressCommand>{ private readonly IEmployeeRepository _repository;

public EmployeeCommandsHandler(IEmployeeRepository repository) { _repository = repository; }

public void Handle(RegisterEmployeeCommand command) { var newEmployee = new Employee(command.Id, command.Name, command.InitialSalary); _repository.Save(newEmployee); }

public void Handle(RaiseSalaryCommand command) { var employee = _repository.Load(command.EmployeeId); employee.RaiseSalary(command.Amount); _repository.Save(employee); }

public void Handle(ChangeHomeAddressCommand command) { var employee = _repository.Load(command.EmployeeId); employee.ChangeHomeAddress(command.NewAddress); _repository.Save(employee); }}

Page 37: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB
Page 38: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB
Page 39: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

InputModel

Command

Page 40: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Chegamos ao Manifesto Reativo

Page 41: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Resolvemos comandos! O que fazemos com consultas

Page 42: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Vamos lembrar...

Page 43: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

ViewModel

Page 44: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Logo, seria natural...

Page 45: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB
Page 46: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB
Page 47: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

E isso é CQRS

Page 48: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

... mas e Event Sourcing?

Page 49: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Employee

RaiseSalaryCommand

SalaryRaisedEvent

Page 50: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Command

InputModel

Page 51: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Anatomia de um Evento

public class EmployeeHomeAddressChangedEvent : VersionedEvent<Guid>{ public Address NewAddress { get; }

public EmployeeHomeAddressChangedEvent(Address newAddress) { NewAddress = newAddress; }}

public class VersionedEvent<TSourceId> : IVersionedEvent<TSourceId>{ public TSourceId SourceId { get; internal set; } public DateTime When { get; private set; } public int Version { get; internal set; }

public VersionedEvent() { When = DateTime.Now; }}

Page 52: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Anatomia de um Evento

public class EmployeeHomeAddressChangedEvent : VersionedEvent<Guid>{ public Address NewAddress { get; }

public EmployeeHomeAddressChangedEvent(Address newAddress) { NewAddress = newAddress; }}

public class VersionedEvent<TSourceId> : IVersionedEvent<TSourceId>{ public TSourceId SourceId { get; internal set; } public DateTime When { get; private set; } public int Version { get; internal set; }

public VersionedEvent() { When = DateTime.Now; }}

Page 53: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Anatomia de um Evento

public class EmployeeHomeAddressChangedEvent : VersionedEvent<Guid>{ public Address NewAddress { get; }

public EmployeeHomeAddressChangedEvent(Address newAddress) { NewAddress = newAddress; }}

public class VersionedEvent<TSourceId> : IVersionedEvent<TSourceId>{ public TSourceId SourceId { get; internal set; } public DateTime When { get; private set; } public int Version { get; internal set; }

public VersionedEvent() { When = DateTime.Now; }}

Page 54: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Stream de EventosEmployeeRegistered

HomeAddressUpdated

SalaryRaised

SalaryRaised

HomeAddressUpdated

SalaryRaised

Page 55: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Stream de EventosEmployeeRegistered

HomeAddressUpdated

SalaryRaised

SalaryRaised

HomeAddressUpdated

SalaryRaised

Por que não salvar?

Page 56: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Stream de Eventos

No RavenDB é fácil!

Page 57: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Stream de Eventos

No RavenDB é fácil!

Page 58: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

... mas antes é preciso Gerar os Eventos

Page 59: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Entidade/Agregado gera Eventos

public Employee(Guid id, FullName name, decimal initialSalary) : this(id){ Throw.IfArgumentIsNull(name, nameof(name)); Throw.IfArgumentIsNegative(initialSalary, nameof(initialSalary));

Update(new EmployeeRegisteredEvent(name, initialSalary));}

Page 60: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Entidade/Agregado gera Eventos

public void RaiseSalary(decimal amount){ Throw.IfArgumentIsNegative(amount, nameof(amount)); Update(new EmployeeSalaryRaisedEvent(amount));}

Page 61: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Entidade/Agregado gera Eventos

public void ChangeHomeAddress(Address address){ Throw.IfArgumentIsNull(address, nameof(address)); Update(new EmployeeHomeAddressChangedEvent(address));}

Page 62: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Atualiza estado a partir dos Eventos

protected void Update(VersionedEvent<TId> e){ e.SourceId = Id; e.Version = Version + 1;

_handlers[e.GetType()].Invoke(e);

Version = e.Version; _pendingEvents.Add(e);}

Page 63: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Atualiza estado a partir dos Eventos

protected void Update(VersionedEvent<TId> e){ e.SourceId = Id; e.Version = Version + 1;

_handlers[e.GetType()].Invoke(e);

Version = e.Version; _pendingEvents.Add(e);}

private Employee(Guid id) : base(id){ Handles<EmployeeRegisteredEvent>(OnEmployeeRegistered); Handles<EmployeeSalaryRaisedEvent>(OnEmployeeSalaryRaised); Handles<EmployeeHomeAddressChangedEvent>(OnEmployeeHomeAddressChanged); }

Page 64: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Atualiza estado a partir dos Eventos

protected void Update(VersionedEvent<TId> e){ e.SourceId = Id; e.Version = Version + 1;

_handlers[e.GetType()].Invoke(e);

Version = e.Version; _pendingEvents.Add(e);}

private void OnEmployeeRegistered(EmployeeRegisteredEvent @event){ Name = @event.Name; Salary = @event.InitialSalary;}

private void OnEmployeeSalaryRaised(EmployeeSalaryRaisedEvent @event){ Salary += @event.Amount;}

Page 65: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Atualiza estado a partir dos Eventos

protected void Update(VersionedEvent<TId> e){ e.SourceId = Id; e.Version = Version + 1;

_handlers[e.GetType()].Invoke(e);

Version = e.Version; _pendingEvents.Add(e);}

Page 66: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Stream de EventosEmployeeRegistered

HomeAddressUpdated

SalaryRaised

SalaryRaised

HomeAddressUpdated

SalaryRaised

Por que não salvar?

Page 67: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Application (InputModel)

CommandHandler (Command)

Entidade (Evento)

Page 68: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Application (InputModel)

CommandHandler (Command)

Entidade (Evento)

Page 69: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Application (InputModel)

CommandHandler (Command)

Entidade (Evento)

Page 70: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Salvar um documento com eventospublic void Save(Employee employee)

{ var head = GetHead(employee.Id); var storedVersion = GetStoredVersionOf(head);

if (storedVersion != (employee.Version - employee.PendingEvents.Count())) throw new InvalidOperationException("Invalid object state.");

if (head == null) { SaveNewEmployee(employee); } else { SaveEmployeeEvents(employee); }

foreach (var evt in employee.PendingEvents) Bus.RaiseEvent(evt);}

Page 71: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Salvar um documento com eventos

public JsonDocumentMetadata GetHead(Guid id){ string localId = $"employees/{id}"; return _store.DatabaseCommands.Head(localId);}

public int GetStoredVersionOf(JsonDocumentMetadata head){ return head ?.Metadata[EmployeeEntityVersion] .Value<int>() ?? 0;}

Page 72: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Salvar um documento com eventospublic void Save(Employee employee)

{ var head = GetHead(employee.Id); var storedVersion = GetStoredVersionOf(head);

if (storedVersion != (employee.Version - employee.PendingEvents.Count())) throw new InvalidOperationException("Invalid object state.");

if (head == null) { SaveNewEmployee(employee); } else { SaveEmployeeEvents(employee); }

foreach (var evt in employee.PendingEvents) Bus.RaiseEvent(evt);}

Page 73: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Salvar um documento com eventos

private void SaveNewEmployee(Employee employee){ using (var session = _store.OpenSession()) { var document = new EmployeeEvents( employee.Id, employee.PendingEvents ); session.Store(document);

session.Advanced.GetMetadataFor(document) .Add(EmployeeEntityVersion, employee.Version);

session.SaveChanges(); }}

Page 74: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

private void SaveEmployeeEvents( Employee employee ){ var patches = new List<PatchRequest>(); foreach (var evt in employee.PendingEvents) { patches.Add(new PatchRequest { Type = PatchCommandType.Add, Name = "Events", Value = RavenJObject.FromObject(evt, _serializer) }); } var localId = $"employees/{employee.Id}";

var addEmployeeEvents = new PatchCommandData() { Key = localId, Patches = patches.ToArray() };

var updateMetadata = new ScriptedPatchCommandData() { Key = localId, Patch = new ScriptedPatchRequest { Script = $"this['@metadata']['{EmployeeEntityVersion}'] = {employee.Version}; " } }; _store.DatabaseCommands.Batch(new ICommandData[] { addEmployeeEvents, updateMetadata });}

Page 75: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

private void SaveEmployeeEvents( Employee employee ){ var patches = new List<PatchRequest>(); foreach (var evt in employee.PendingEvents) { patches.Add(new PatchRequest { Type = PatchCommandType.Add, Name = "Events", Value = RavenJObject.FromObject(evt, _serializer) }); } var localId = $"employees/{employee.Id}";

var addEmployeeEvents = new PatchCommandData() { Key = localId, Patches = patches.ToArray() };

var updateMetadata = new ScriptedPatchCommandData() { Key = localId, Patch = new ScriptedPatchRequest { Script = $"this['@metadata']['{EmployeeEntityVersion}'] = {employee.Version}; " } }; _store.DatabaseCommands.Batch(new ICommandData[] { addEmployeeEvents, updateMetadata });}

Page 76: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

private void SaveEmployeeEvents( Employee employee ){ var patches = new List<PatchRequest>(); foreach (var evt in employee.PendingEvents) { patches.Add(new PatchRequest { Type = PatchCommandType.Add, Name = "Events", Value = RavenJObject.FromObject(evt, _serializer) }); } var localId = $"employees/{employee.Id}";

var addEmployeeEvents = new PatchCommandData() { Key = localId, Patches = patches.ToArray() };

var updateMetadata = new ScriptedPatchCommandData() { Key = localId, Patch = new ScriptedPatchRequest { Script = $"this['@metadata']['{EmployeeEntityVersion}'] = {employee.Version}; " } }; _store.DatabaseCommands.Batch(new ICommandData[] { addEmployeeEvents, updateMetadata });}

Page 77: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Salvar um documento com eventospublic void Save(Employee employee)

{ var head = GetHead(employee.Id); var storedVersion = GetStoredVersionOf(head);

if (storedVersion != (employee.Version - employee.PendingEvents.Count())) throw new InvalidOperationException("Invalid object state.");

if (head == null) { SaveNewEmployee(employee); } else { SaveEmployeeEvents(employee); }

foreach (var evt in employee.PendingEvents) Bus.RaiseEvent(evt);}

Page 78: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Carregar um documento com eventos

public Employee Load(Guid id){ EmployeeEvents data; using (var session = _store.OpenSession()) { data = session.Load<EmployeeEvents>(id); } return new Employee(id, data.Events);}public Employee(Guid id, IEnumerable<IVersionedEvent<Guid>> history) : this(id){ LoadFrom(history);}

Page 79: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

Carregar um documento com eventos

protected void LoadFrom(IEnumerable<IVersionedEvent<TId>> pastEvents){ foreach (var e in pastEvents) { _handlers[e.GetType()].Invoke(e); Version = e.Version; }}

Page 80: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB
Page 82: TDC2016POA | Trilha .NET - CQRS e ES na prática com RavenDB

CQRS e Event Sourcing na prática com RavenDB

Elemar Júnior@elemarjr

[email protected]@gmail.com

elemarjr.com