Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Projections explained

10 488 vues

Publié le

Namasté ... come and meet BeauForma, the company you wished you worked for, and while you're at it, get an introduction in how to design, author and test projections for your next DDD/CQRS/ES project, hands-on. Bring a computer and pair up with a buddy.

Publié dans : Logiciels

Projections explained

  1. 1. PROJECTIONS EXPLAINED { YVES REYNHOUT }
  2. 2. AGENDA Meet the domain Projections Terminology Designing Authoring Testing
  3. 3. WELCOME @ BEAUFORMA (FR)
  4. 4. YOU ARE THE PRODUCT
  5. 5. ON THE OUTSIDE
  6. 6. ON THE INSIDE
  7. 7. THAT IS ... IF YOU CAN AFFORD US ;-)
  8. 8. CHALLENGES
  9. 9. CONTEXT MAP
  10. 10. REWARD
  11. 11. WHAT'S NOT TO LIKE ABOUT BEAUFORMA?
  12. 12. BUT, DUDE ... WE'RE HERE FOR PROJECTIONS, REMEMBER?
  13. 13. TERMINOLOGY Event: a fact, something that happened, a message, a datastructure Stream: a sequence of events, partitioned by something Event store: a collection of streams (simplified) Disclaimer: not authoritive, just my take
  14. 14. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: open System  type GuestStartedShopping = {     SubsidiaryId : Guid;    FeelGoodCartId : Guid;     GuestId : Guid;    StartDateAndTime : DateTime;  }  type GuestCheckedOutCart = {     FeelGoodPackageId : Guid;    FeelGoodCartId : Guid;     GuestId : Guid;    Items : Guid array;  }  type GuestAbandonedCart = {     FeelGoodCartId : Guid;     GuestId : Guid;    LastSeenDateAndTime : DateTime;  } 
  15. 15. 1: 2: 3: 4: 5: 6: 7: 8: type ItemWasAddedToCart = {     FeelGoodCartId : Guid;     ItemId : Guid;  }  type ItemWasRemovedFromCart = {     FeelGoodCartId : Guid;     ItemId : Guid;  } 
  16. 16. VANILLA CQRS+ES
  17. 17. TERMINOLOGY ProjectionHandler: a function that projects an event Projection: a collection of handlers that form a unit Projector: a function that dispatches an event or a batch of events to the matching handler(s) [Optional] ProjectionHandlerResolver: a function that returns the handlers that match an event Disclaimer: not authoritive, just my take
  18. 18. EXAMPLE 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: open StackExchange.Redis  let inline (!>) (x:^a) : ^b =     ((^a or ^b) : (static member op_Implicit : ^a ­> ^b) x)   let activeShoppersProjection (connection:IDatabase, message:Object) =    match message with    | :? GuestStartedShopping ­>       connection.StringIncrement(!> "ActiveShoppers", 1L) |> ignore    | :? GuestAbandonedCart ­>       connection.StringDecrement(!> "ActiveShoppers", 1L) |> ignore    | :? GuestCheckedOutCart ­>       connection.StringDecrement(!> "ActiveShoppers", 1L) |> ignore    | _ ­> () 
  19. 19. EXAMPLE 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: public class ActiveShoppersProjection : ConnectedProjection<IDatabase>  {    public ActiveShoppersProjection()    {      When<GuestStartedShopping>((connection, message) =>         connection.StringIncrementAsync("ActiveShoppers"));      When<GuestAbandonedCart>((connection, message) =>         connection.StringDecrementAsync("ActiveShoppers"));      When<GuestCheckedOutCart>((connection, message) =>         connection.StringDecrementAsync("ActiveShoppers"));    }  } 
  20. 20. EXAMPLE 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: public interface IHandler<TMessage>  {    void Handle(IDatabase connection, TMessage message);  }  public class ActiveShoppersProjection : IHandler<GuestStartedShopping>,    IHandler<GuestAbandonedCart>, IHandler<GuestCheckedOutCart>   {    public void Handle(IDatabase connection, GuestStartedShopping message)    {      connection.StringIncrement("ActiveShoppers");    }    public void Handle(IDatabase connection, GuestAbandonedCart message)    {      connection.StringDecrement("ActiveShoppers");    }    public void Handle(IDatabase connection, GuestCheckedOutCart message)    {      connection.StringDecrement("ActiveShoppers");    }  } 
  21. 21. VANILLA CQRS+ES
  22. 22. VANILLA DDD
  23. 23. DESIGNING PROJECTIONS consumer driven (by screen, api, model, ...) affected by the choice of store (e.g. required querying capabilities, non-functional requirements, ...)
  24. 24. DATASTRUCTURES
  25. 25. EXERCISE Define a datastructure for this widget
  26. 26. POSSIBLE SOLUTION 1: 2: 3: 4: 5: 6: 7: 8: type GuestsArrivingRecord = {     RecordId: string;    SubsidairyId: Guid;    Date: DateTime;    AppointmentId : Guid;    GuestName : string;     AppointmentDateAndTime : DateTime   } 
  27. 27. POSSIBLE SOLUTION 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: type GuestsArrivingDocument = {     DocumentId: string;    SubsidairyId: Guid;    Date: DateTime;    Appointments : Appointment array  }  type Appointment = {    AppointmentId : Guid;    GuestName : string;     AppointmentDateAndTime : DateTime   } 
  28. 28. EVENTS
  29. 29. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: type GuestBookedAppointment = {     AppointmentId : Guid;     GuestId : Guid;    AppointmentDateAndTime : DateTime;     FeelGoodPackageId : Guid;     SubsidiaryId : Guid   }  type GuestRescheduledAppointment = {     AppointmentId : Guid;     GuestId : Guid;     AppointmentDateAndTime : DateTime;     FeelGoodPackageId : Guid;     SubsidiaryId : Guid   }  type GuestSwappedAppointmentFeelGoodPackage = {     AppointmentId : Guid;     GuestId : Guid;     FeelGoodPackageId : Guid;     SubsidiaryId : Guid   } 
  30. 30. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: type GuestCancelledAppointment = {     AppointmentId : Guid;    GuestId : Guid;    Reason : string;     SubsidiaryId : Guid   }  type SubsidiaryCancelledAppointment = {     AppointmentId : Guid;    GuestId : Guid;    Reason : string;     SubsidiaryId : Guid   } 
  31. 31. EXERCISE Fill your datastructure using these events - booking.fshttps://goo.gl/BTBTfi
  32. 32. OBSERVATIONS Not all events are useful, Information might be missing from events, Not all data is for viewing Events challenge the datastructure What about your observations?
  33. 33. OBSERVATIONS Not all data comes from one projection, Not all data is owned by one model
  34. 34. ONE EXTRA EVENT TO TAKE INTO ACCOUNT 1: 2: 3: 4: 5: type GuestRegistered = {     GuestId : Guid;    FullName : string;    DateOfRegistration: DateTime;  } 
  35. 35. EXERCISE Extend your projection with the event - booking|guests.fshttps://goo.gl/BTBTfi
  36. 36. POSSIBLE SOLUTION 1: 2: 3: 4: 5: type GuestDocument = {     DocumentId: string;    GuestId: Guid;    FullName: string;  } 
  37. 37. AUTHORING PROJECTIONS
  38. 38. EXERCISE Express the projection in your language and store of choice - booking|guests.fshttps://goo.gl/BTBTfi
  39. 39. RECIPE? define message types in code (for statically typed languages) define and implement projection handlers for each message type define and implement a dispatcher to those projection handlers test drive in the program's main
  40. 40. NOT CHALLENGING ENOUGH?
  41. 41. EVENTSTORE IP Address: 178.62.229.196 Http Port: 2113 Tcp Port: 1113 Login: admin Password: changeit REDIS IP Address: 178.62.229.196 Port: 6379 ELASTICSEARCH IP Address: 178.62.229.196 Port: 9200
  42. 42. POSTGRES IP Address: 46.101.161.64 Port: 5432 Login: dddeu16 Password: dddeu16 Database: dddeu16 SslMode: required Server Certificate: see online environment file - online- environment.md https://goo.gl/BTBTfi
  43. 43. TESTING PROJECTIONS
  44. 44. EXAMPLE 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: public class ActiveShoppersProjectionScenarios  {    public Task when_multiple_active_shoppers()    {      return MemoryCacheProjection.For(new ActiveShoppersProjection())        .Given(          new GuestStartedShopping {             SubsidiaryId = BonifacioId,             FeelGoodCartId = RandomCartId(),            GuestId = OliverMartinezId,            StartDateAndTime = Today.At(6.PM())          },         new GuestStartedShopping {             SubsidiaryId = BonifacioId,             FeelGoodCartId = RandomCartId(),            GuestId = RodriguezId,            StartDateAndTime = Today.At(4.PM())          })       .Expect(new CacheItem("ActiveShoppersCount", 2));    }  } 

×