O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

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

190 visualizações

Publicada em

CQRS e ES na prática com RavenDB

Publicada em: Educação
  • Seja o primeiro a comentar

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

  1. 1. CQRS e Event Sourcing na prática com RavenDB Elemar Júnior @elemarjr elemarjr@ravendb.net elemarjr@gmail.com elemarjr.com
  2. 2. Olá, eu sou Elemar Jr
  3. 3. Eu gosto de RavenDB
  4. 4. CQRS e Event Sourcing na prática com RavenDB Mas, vamos falar de
  5. 5. Vamos pensar em um cenário...
  6. 6. Precisamos criar um um cadastro de funcionários...
  7. 7. Coisa simples... Nome, Endereço Residencial e Salário
  8. 8. Começamos com um protótipo
  9. 9. [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...
  10. 10. 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) { /* .. */} }
  11. 11. 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....
  12. 12. Pronto!
  13. 13. Será que ficou bom?
  14. 14. Vejamos...
  15. 15. UI (HTML + Angular) WebAPI RavenDB Presentation Business Persistence
  16. 16. 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; } }
  17. 17. 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) { /* .. */} }
  18. 18. Será que escala?
  19. 19. Será que é fácil de alterar?
  20. 20. Vamos tentar resgatar conceitos...
  21. 21. ViewModel InputModel
  22. 22. Modelo expressa o domínio 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) { /* .. */ } }
  23. 23. Modelo expressa o domínio 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) { /* .. */ } }
  24. 24. Modelo expressa o domínio 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) { /* .. */ } }
  25. 25. 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) { /* .. */ } }
  26. 26. Voltando ao protótipo... public Employee(Guid id, FullName name, decimal initialSalary) { /* .. */ }
  27. 27. 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
  28. 28. Cadê o protótipo? public void RaiseSalary(decimal amount) { /* .. */ }
  29. 29. Command InputModel
  30. 30. Anatomia de um Comando public sealed class UpdateEmployeeHomeAddressCommand : EmployeeCommand { public Address HomeAddress { get; } public UpdateEmployeeHomeAddressCommand( EmployeeId id, Address address) : base(id) { HomeAddress = address; } }
  31. 31. Command InputModel
  32. 32. 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); } }
  33. 33. InputModel Command
  34. 34. Chegamos ao Manifesto Reativo
  35. 35. Resolvemos comandos! O que fazemos com consultas
  36. 36. Vamos lembrar...
  37. 37. ViewModel
  38. 38. Logo, seria natural...
  39. 39. E isso é CQRS
  40. 40. ... mas e Event Sourcing?
  41. 41. Employee RaiseSalaryCommand SalaryRaisedEvent
  42. 42. Command InputModel
  43. 43. 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; } }
  44. 44. 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; } }
  45. 45. 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; } }
  46. 46. Stream de Eventos EmployeeRegistered HomeAddressUpdated SalaryRaised SalaryRaised HomeAddressUpdated SalaryRaised
  47. 47. Stream de Eventos EmployeeRegistered HomeAddressUpdated SalaryRaised SalaryRaised HomeAddressUpdated SalaryRaised Por que não salvar?
  48. 48. Stream de Eventos No RavenDB é fácil!
  49. 49. Stream de Eventos No RavenDB é fácil!
  50. 50. ... mas antes é preciso Gerar os Eventos
  51. 51. 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)); }
  52. 52. Entidade/Agregado gera Eventos public void RaiseSalary(decimal amount) { Throw.IfArgumentIsNegative(amount, nameof(amount)); Update(new EmployeeSalaryRaisedEvent(amount)); }
  53. 53. Entidade/Agregado gera Eventos public void ChangeHomeAddress(Address address) { Throw.IfArgumentIsNull(address, nameof(address)); Update(new EmployeeHomeAddressChangedEvent(address)); }
  54. 54. 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); }
  55. 55. 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); }
  56. 56. 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; }
  57. 57. 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); }
  58. 58. Stream de Eventos EmployeeRegistered HomeAddressUpdated SalaryRaised SalaryRaised HomeAddressUpdated SalaryRaised Por que não salvar?
  59. 59. Application (InputModel) CommandHandler (Command) Entidade (Evento)
  60. 60. Application (InputModel) CommandHandler (Command) Entidade (Evento)
  61. 61. Application (InputModel) CommandHandler (Command) Entidade (Evento)
  62. 62. 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); }
  63. 63. 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; }
  64. 64. 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); }
  65. 65. 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(); } }
  66. 66. 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 }); }
  67. 67. 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 }); }
  68. 68. 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 }); }
  69. 69. 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); }
  70. 70. 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); }
  71. 71. 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; } }
  72. 72. elemarjr.com @elemarjr linkedin.com/in/elemarjr elemarjr@ravendb.net elemarjr@gmail.com Mantenha contato!
  73. 73. CQRS e Event Sourcing na prática com RavenDB Elemar Júnior @elemarjr elemarjr@ravendb.net elemarjr@gmail.com elemarjr.com

×