Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Let's write some history - DPC 2015

Let's write some history - DPC 2015

45 minute talk on Event Sourcing. Given on Dutch PHP Conference 2015

C347affc11bc313dc3da2398936d305a?s=128

Willem-Jan Zijderveld

June 26, 2015
Tweet

Transcript

  1. 1/92 Event Sourcing Let's write some history https://joind.in/14226

  2. Willem-Jan Zijderveld @willemjanz github.com/wjzijderveld  

  3. Rotterdam http://labs.qandidate.com

  4. Broadway Event Sourcing and CQRS library for PHP github.com/qandidate-labs/broadway

  5. Event Sourcing

  6. You are throwing away data!

  7. Only most recent state gets stored

  8. What was the previous state?

  9. Why did the state change?

  10. It's hard to keep track of all the data

  11. A company has one or more connected accounts

  12. A company has one or more enabled applications

  13. An account has access to one or more applications

  14. None
  15. Complicated solutions to store and retrieve the data

  16. But we manage Until...

  17. Boss: Which accounts received access to application X in December?

  18. "We'll add a timestamp and we will know" From that

    point in time
  19. With CRUD you lose information You only store the last

    known information
  20. Revoking access to an application for an account

  21. You could lose when the access was revoked

  22. You could lose why it got revoked

  23. You could lose for which application it was revoked

  24. You wouldn't know anything anymore

  25. Let's assume you have a soft-delete in place

  26. Boss: I want to know for which applications access got

    revoked more than once in the last year
  27. Keep track of revocations account_id app_id datetime 75623 8 2015-01-01T00:00:00+0000

    75623 8 2015-01-27T18:54:18+0000 Past revocations are gone
  28. How does Event Sourcing help me with that?

  29. Store your data in a different way

  30. Record what has changed The resulting state becomes a natural

    effect
  31. EventStream

  32. A serie of facts CompanyRegistered AppEnabled AccountConnected AccessGrantedToApp

  33. Been there, done that

  34. Single source of truth One source to rule all state

    The events cannot lie, it happened, deal with it
  35. Your version control is event sourced

  36. Sure thing, but how? There isn't one true answer

  37. Event Sourcing DDD CQRS All 3 are optional, but work

    very nice together
  38. Domain Driven Design The business should be reflected in your

    code
  39. Entity An model with an identity

  40. Aggregate root Responsible for keeping a group of entities consistent

  41. I want to update my model How do I do

    that?
  42. Record the change

  43. You normally might do it like this < ? p

    h p / / C o m p a n y . p h p p u b l i c f u n c t i o n _ _ c o n s t r u c t ( $ i d , $ n a m e ) { $ t h i s - > i d = $ i d ; $ t h i s - > n a m e = $ n a m e ; }
  44. But we want to record an event

  45. < ? p h p / / C o m

    p a n y . p h p p u b l i c s t a t i c f u n c t i o n r e g i s t e r ( $ i d , $ n a m e ) { $ c o m p a n y = n e w C o m p a n y ( ) ; $ c o m p a n y - > a p p l y ( n e w C o m p a n y R e g i s t e r e d E v e n t ( $ i d , $ n a m e ) ) ; r e t u r n $ c o m p a n y ; }
  46. Record the events < p h p / / A

    g g r e g a t e R o o t . p h p p u b l i c f u n c t i o n a p p l y ( $ e v e n t ) { $ t h i s - > h a n d l e ( $ e v e n t ) ; $ t h i s - > u n c o m m i t e d E v e n t s [ ] = $ e v e n t ; } p r i v a t e f u n c t i o n h a n d l e ( $ e v e n t ) { $ c l a s s P a r t s = e x p l o d e ( ' \ \ ' , $ e v e n t ) ; $ m e t h o d = ' a p p l y ' . e n d ( $ c l a s s P a r t s ) ; $ t h i s - > $ m e t h o d ( $ e v e n t ) ; } Just one possible implementation
  47. / / C o m p a n y .

    p h p p u b l i c f u n c t i o n a p p l y C o m p a n y R e g i s t e r e d E v e n t ( C o m p a n y R e g i s t e r e d E v e n t $ e v e n t ) { $ t h i s - > c o m p a n y I d = $ e v e n t - > g e t C o m p a n y I d ( ) ; $ t h i s - > c o m p a n y N a m e = $ e v e n t - > g e t C o m p a n y N a m e ( ) ; }
  48. Save your events in an eventstore

  49. Reloading your model

  50. < p h p / / C o m p

    a n y R e p o s i t o r y . p h p f u n c t i o n l o a d ( $ a g g r e g a t e I d ) { $ e v e n t s = $ t h i s - > e v e n t S t o r e - > l o a d ( $ a g g r e g a t e I d ) ; $ a g g r e g a t e = n e w $ t h i s - > a g g r e g a t e C l a s s ( ) ; $ a g g r e g a t e - > i n i t i a l i z e S t a t e ( $ e v e n t s ) ; r e t u r n $ a g g r e g a t e ; }
  51. < ? p h p / / C o m

    p a n y . p h p p u b l i c f u n c t i o n i n i t i a l i z e S t a t e ( a r r a y $ e v e n t s ) { f o r e a c h ( $ e v e n t s a s $ e v e n t ) { $ t h i s - > h a n d l e ( $ e v e n t ) ; } }
  52. Domain Message A message to tell your application what happened

  53. DomainMessage Identifier Sequencenumber Payload Timestamp Metadata

  54. Identifier + sequence number

  55. Payload The event itself, it tells you what happened

  56. Should contain everything It should only depend on previous events

    f i n a l c l a s s C o m p a n y R e g i s t e r e d E v e n t { p r i v a t e $ c o m p a n y I d ; p r i v a t e $ c o m p a n y N a m e ; / / c o n s t r u c t o r + g e t t e r s }
  57. Timestamp It tells you when it happened

  58. Metadata Descriptive, not structural

  59. Let's look at some other events CompanyRegistered AppEnabled AccountConnected AccessGrantedToApp

  60. CompanyRegistered / / C o m p a n y

    f u n c t i o n a p p l y C o m p a n y R e g i s t e r e d E v e n t ( C o m p a n y R e g i s t e r e d E v e n t $ e v e n t ) { $ t h i s - > c o m p a n y I d = $ e v e n t - > g e t C o m p a n y I d ( ) ; }
  61. AccountConnected / / C o m p a n y

    f u n c t i o n a p p l y A c c o u n t C o n n e c t e d E v e n t ( A c c o u n t C o n n e c t e d E v e n t $ e v e n t ) { $ i d = $ e v e n t - > g e t A c c o u n t I d ( ) ; $ t h i s - > a c c o u n t s [ $ i d ] = $ e v e n t - > g e t A c c o u n t I d ( ) ; }
  62. AppEnabled / / C o m p a n y

    f u n c t i o n a p p l y A p p E n a b l e d E v e n t ( A p p E n a b l e d E v e n t $ e v e n t ) { $ s u b s c r i p t i o n = n e w S u b s c r i p t i o n ( $ e v e n t - > g e t C o m p a n y I d ( ) , $ e v e n t - > g e t A p p I d ( ) ) ; $ t h i s - > s u b s c r i p t i o n s [ $ e v e n t - > g e t A p p I d ( ) ] = $ s u b s c r i p t i o n ; }
  63. AccessGrantedToApp / / C o m p a n y

    p r o t e c t e d f u n c t i o n g e t C h i l d E n t i t i e s ( ) { r e t u r n $ t h i s - > s u b s c r i p t i o n s ; } / / S u b s c r i p t i o n f u n c t i o n a p p l y A c c e s s G r a n t e d T o A p p E v e n t ( A c c e s s G r a n t e d T o A p p E v e n t $ e v e n t ) { i f ( $ t h i s - > a p p I d ! = = $ e v e n t - > g e t A p p I d ( ) ) { r e t u r n ; } $ a c c o u n t I d = $ e v e n t - > g e t A c c o u n t I d ( ) ; $ t h i s - > g r a n t e d A c c o u n t s [ $ a c c o u n t I d ] = $ e v e n t - > g e t A c c o u n t I d ( ) ; }
  64. We can retrieve all AccessGranted events for December

  65. None
  66. Won't this be slow?

  67. CQRS Command Query Responsibility Segregation

  68. Separate your writes and your reads

  69. Read Models Create them from events using projections Specific read

    models for specific views in your application
  70. CompanyRegistration c l a s s C o m p

    a n y R e g i s t r a t i o n i m p l e m e n t s R e a d M o d e l { p u b l i c f u n c t i o n _ _ c o n s t r u c t ( $ c o m p a n y I d , $ c o m p a n y N a m e , D a t e T i m e $ r e g i s t e r e d O n ) { / / . . } }
  71. Company Registration Projector c l a s s C o

    m p a n y R e g i s t r a t i o n P r o j e c t o r i m p l e m e n t s E v e n t L i s t e n e r { p u b l i c f u n c t i o n a p p l y C o m p a n y R e g i s t e r e d E v e n t ( C o m p a n y R e g i s t e r e d E v e n t $ e v e n t , D o m a i n M e s s a g e $ d o m a i n M e s s a g e ) { $ c o m p a n y = n e w C o m p a n y R e g i s t r a t i o n ( $ e v e n t - > g e t C o m p a n y I d ( ) , $ e v e n t - > g e t C o m p a n y N a m e ( ) , $ d o m a i n M e s s a g e - > g e t R e c o r d e d O n ( ) ) ; $ t h i s - > r e p o s i t o r y - > s a v e ( $ c o m p a n y ) ; } }
  72. Use the right tool for the right job

  73. Possibilities are endless

  74. The ability to create multiple read models List of company

    registrations Graph of all connections between companies and accounts Creating reports about the amount of revocations
  75. So.. how does this flow work in an application? We'll

    use Commands
  76. The write part of CQRS

  77. Command & CommandHandler A CommandHandler deals with Commands and communicates

    with the Aggregate root
  78. From Controller to CommandHandler c l a s s C

    o m p a n y C o n t r o l l e r { f u n c t i o n r e g i s t e r A c t i o n ( R e q u e s t $ r e q u e s t ) { $ t h i s - > c o m m a n d B u s - > d i s p a t c h ( n e w R e g i s t e r C o m p a n y C o m m a n d ( U u i d : : u u i d 4 ( ) , $ r e q u e s t - > r e q u e s t - > g e t ( ' c o m p a n y N a m e ' ) ) ) ; } }
  79. From CommandHandler to Aggregate c l a s s C

    o m p a n y C o m m a n d H a n d l e r { f u n c t i o n h a n d l e R e g i s t e r C o m p a n y C o m m a n d ( R e g i s t e r C o m p a n y C o m m a n d $ c o m m a n d ) { $ c o m p a n y = C o m p a n y : : r e g i s t e r ( $ c o m m a n d - > g e t C o m p a n y I d ( ) , $ c o m m a n d - > g e t C o m p a n y N a m e ( ) ) ; $ t h i s - > a g g r e g a t e R e p o s t i t o r y - > s a v e ( $ c o m p a n y ) ; } }
  80. From Aggregate to Event p u b l i c

    s t a t i c f u n c t i o n r e g i s t e r ( $ c o m p a n y I d , $ n a m e ) { $ c o m p a n y = n e w C o m p a n y ( ) ; $ c o m p a n y - > a p p l y ( n e w C o m p a n y R e g i s t e r e d E v e n t ( $ c o m p a n y I d , $ n a m e ) ) ; r e t u r n $ c o m p a n y ; } p u b l i c f u n c t i o n a p p l y C o m p a n y R e g i s t e r e d E v e n t ( C o m p a n y R e g i s t e r e d E v e n t $ e v e n t ) { $ t h i s - > c o m p a n y I d = $ e v e n t - > g e t C o m p a n y I d ( ) ; }
  81. From Aggregate Repository to EventStore / / C o m

    p a n y R e g i s t r y . p h p f u n c t i o n s a v e ( $ a g g r e g a t e ) { $ e v e n t s = $ a g g r e g a t e - > g e t U n c o m m i t t e d E v e n t s ( ) ; $ t h i s - > e v e n t S t o r e - > a p p e n d ( $ a g g r e g a t e - > g e t A g g r e g a t e I d ( ) , $ e v e n t s ) ; $ t h i s - > e v e n t B u s - > p u b l i s h ( $ e v e n t s ) ; }
  82. From event to read model c l a s s

    C o m p a n y R e g i s t r a t i o n P r o j e c t o r { p u b l i c f u n c t i o n a p p l y C o m p a n y R e g i s t e r e d E v e n t ( C o m p a n y R e g i s t e r e d E v e n t $ e v e n t , D o m a i n M e s s a g e $ d o m a i n M e s s a g e ) { $ c o m p a n y = n e w C o m p a n y R e g i s t r a t i o n ( $ e v e n t - > g e t C o m p a n y I d ( ) , $ e v e n t - > g e t C o m p a n y N a m e ( ) , $ d o m a i n M e s s a g e - > g e t R e c o r d e d O n ( ) ) ; $ t h i s - > r e p o s i t o r y - > s a v e ( $ c o m p a n y ) ; } }
  83. Controller -> CommandBus -> CommandHandler -> AggregateRoot -> Event ->

    EventStore -> EventBus -> Projectors -> Read Model
  84. Quite a bit to chew Try not to reinvent everything

    yourself
  85. Testing Scenario based testing Given - When - Then

  86. Test your Command Handler $ t h i s -

    > s c e n a r i o - > g i v e n ( [ n e w C o m p a n y R e g i s t e r e d E v e n t ( 1 2 3 ) ] ) - > w h e n ( n e w E n a b l e A p p F o r C o m p a n y C o m m a n d ( 4 2 , 1 2 3 ) ) - > t h e n ( [ n e w A p p E n a b l e d E v e n t ( 4 2 , 1 2 3 ) ] ) ;
  87. Or your aggregate $ t h i s - >

    s c e n a r i o - > g i v e n ( [ n e w C o m p a n y R e g i s t e r e d E v e n t ( 1 2 3 ) ] ) - > w h e n ( f u n c t i o n ( $ c o m p a n y ) { $ c o m p a n y - > e n a b l e A p p ( 4 2 ) ; } ) - > t h e n ( [ n e w A p p E n a b l e d E v e n t ( 4 2 , 1 2 3 ) ] ) ;
  88. Don't forget your read models $ t h i s

    - > s c e n a r i o - > g i v e n ( [ ] ) - > w h e n ( n e w C o m p a n y R e g i s t e r e d E v e n t ( 1 2 3 , ' A c m e I n c ' ) ) - > t h e n ( [ n e w C o m p a n y R e g i s t r a t i o n ( 1 2 3 , ' A c m e I n c ' ) ] ) ;
  89. Time travel is possible! Use events you recorded to create

    a new report multiple years after the fact
  90. You made a mistake in a projection? So what? Correct

    your projector and recreate your read model from your event stream
  91. Questions? joind.in/14226 @willemjanz Freenode: #qandidate

  92. More information http://codebetter.com/gregyoung/2010/02/20/why-use- event-sourcing/ http://codebetter.com/gregyoung/2010/02/13/cqrs-and- event-sourcing/ http://martinfowler.com/eaaDev/EventSourcing.html http://martinfowler.com/bliki/CQRS.html http://www.axonframework.org/docs/2.3/domain- modeling.html

    http://labs.qandidate.com/