Pro Yearly is on sale from $80 to $50! »

Let's write some History - PHPBenelux 2016

Let's write some History - PHPBenelux 2016

In this talk I’m going to explain how Event Sourcing can help you solve this problem and what other benefits you can get from it.
We will walk over of the concepts and possible implementations of event sourcing.

C347affc11bc313dc3da2398936d305a?s=128

Willem-Jan Zijderveld

January 30, 2016
Tweet

Transcript

  1. 1/101 Event Sourcing Let's write some history https://joind.in/talk/6bf57

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

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

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

  5. Event Sourcing

  6. You are throwing away data!

  7. Only state gets stored

  8. Only the most recent state gets stored

  9. What was the previous state?

  10. Why did the state change?

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

  12. An example

  13. A company has one or more connected accounts

  14. A company has one or more enabled applications

  15. An account has access to one or more applications

  16. None
  17. Complicated solutions to store and retrieve the data

  18. But we manage Until...

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

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

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

    known information
  22. Another example

  23. Revoking access to an application for an account Just remove

    the row in our join table
  24. You could lose when the access was revoked

  25. You could lose why it got revoked

  26. You could lose for which application it was revoked

  27. You wouldn't know anything anymore

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

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

    revoked more than once in the last year
  30. 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 Are you going to do this for all actions on accounts?
  31. How does Event Sourcing help me with that?

  32. Store your data in a different way

  33. Record what has happened The resulting state becomes a natural

    effect
  34. EventStream

  35. A serie of facts CompanyRegistered AppEnabled AccountConnected AccessGrantedToApp

  36. Been there, done that

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

    The events cannot lie, it happened, deal with it
  38. You have been working with this system

  39. Your version control is event sourced

  40. Sure thing, but how? "It depends"

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

    very nice together
  42. Domain Driven Design Let the events tell the story The

    business should be reflected in your code
  43. Entity An model with an identity

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

  45. I want to create/update my model How do I do

    that?
  46. Record what happened

  47. 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 ; }
  48. But we want to record an event

  49. < ? 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 ; }
  50. 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 ) ; }
  51. / / 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 ( ) ; }
  52. Save your events in an eventstore

  53. Current situation

  54. Reloading your model

  55. < 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 ; }
  56. < ? 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 ) ; } }
  57. Domain Message A message to tell your application what happened

  58. DomainMessage Identifier Sequencenumber Payload Timestamp Metadata

  59. Identifier + sequence number

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

  61. 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 }
  62. Timestamp It tells you when it happened

  63. Metadata Descriptive, not structural

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

  65. 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 ( ) ; }
  66. 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 ] = $ i d ; }
  67. 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 ; }
  68. 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 ] = $ a c c o u n t I d ; }
  69. We can retrieve all AccessGranted events for December

  70. Won't this be slow?

  71. CQRS Command Query Responsibility Segregation

  72. Separate your writes and your reads

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

    models for specific views in your application
  74. EventBus All events go onto the event bus after being

    saved
  75. 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 ) { / / . . } }
  76. 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 ) ; } }
  77. Use the right tool for the right job

  78. Possibilities are endless

  79. 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
  80. So.. how does this flow work in an application?

  81. None
  82. The C in CQRS

  83. The write part of CQRS

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

    with the Aggregate root
  85. From Controller to CommandBus 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 ' ) ) ) ; } }
  86. 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 ) ; } }
  87. 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 ( ) ; }
  88. From 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 ) ; }
  89. 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 ) ; } }
  90. Quite a bit to chew

  91. None
  92. But we are not done yet!

  93. Testing

  94. Scenario based testing Given - When - Then

  95. 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 ) ] ) ;
  96. 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 ) ] ) ;
  97. 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 ' ) ] ) ;
  98. Time travel is possible! Use events you recorded to create

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

    your projector and recreate your read model from your event stream
  100. Questions? https://joind.in/talk/6bf57 @willemjanz Freenode: #qandidate

  101. 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/