From 8edf42494ca85021948a532a10f6f547bbd831fc Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Wed, 23 Nov 2022 18:49:53 +0100 Subject: [PATCH] Replaced Event Bus with Event Store in projection tests to make them more explicit and self-explanatory --- .../ProjectionsTests.cs | 80 +++++--------- .../Tools/EventStore.cs | 24 +++-- .../ProjectionsTests.cs | 99 ++++++----------- .../Tools/EventStore.cs | 29 ++++- .../ProjectionsTests.cs | 100 +++++++----------- .../Tools/EventStore.cs | 29 ++++- .../ProjectionsTests.cs | 99 ++++++----------- .../Tools/EventStore.cs | 23 ++-- .../ProjectionsTests.cs | 99 ++++++----------- .../Tools/EventStore.cs | 29 ++++- .../ProjectionsTests.cs | 99 ++++++----------- .../Tools/EventStore.cs | 29 ++++- 12 files changed, 327 insertions(+), 412 deletions(-) diff --git a/Workshops/IntroductionToEventSourcing/12-Projections.SingleStream/ProjectionsTests.cs b/Workshops/IntroductionToEventSourcing/12-Projections.SingleStream/ProjectionsTests.cs index 553ddca78..93dd7de6b 100644 --- a/Workshops/IntroductionToEventSourcing/12-Projections.SingleStream/ProjectionsTests.cs +++ b/Workshops/IntroductionToEventSourcing/12-Projections.SingleStream/ProjectionsTests.cs @@ -93,65 +93,37 @@ public void GettingState_ForSequenceOfEvents_ShouldSucceed() var otherPendingShoppingCartId = Guid.NewGuid(); var otherClientId = Guid.NewGuid(); - var logPosition = 0ul; - - var events = new EventEnvelope[] - { - // first confirmed - new EventEnvelope(new ShoppingCartOpened(shoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(shoppingCartId, twoPairsOfShoes), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(shoppingCartId, tShirt), EventMetadata.For(3, ++logPosition)), - new EventEnvelope( - new ProductItemRemovedFromShoppingCart(shoppingCartId, pairOfShoes), - EventMetadata.For(4, ++logPosition)), - new EventEnvelope(new ShoppingCartConfirmed(shoppingCartId, DateTime.UtcNow), - EventMetadata.For(5, ++logPosition)), - - // cancelled - new EventEnvelope(new ShoppingCartOpened(cancelledShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(cancelledShoppingCartId, dress), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope(new ShoppingCartCanceled(cancelledShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // confirmed but other client - new EventEnvelope(new ShoppingCartOpened(otherClientShoppingCartId, otherClientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(otherClientShoppingCartId, dress), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ShoppingCartConfirmed(otherClientShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // second confirmed - new EventEnvelope(new ShoppingCartOpened(otherConfirmedShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(otherConfirmedShoppingCartId, trousers), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ShoppingCartConfirmed(otherConfirmedShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // first pending - new EventEnvelope(new ShoppingCartOpened(otherPendingShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)) - }; - - var eventBus = new EventStore(); + var eventStore = new EventStore(); var database = new Database(); // TODO: // 1. Register here your event handlers using `eventBus.Register`. // 2. Store results in database. - eventBus.Append(events); + + // first confirmed + eventStore.Append(shoppingCartId, new ShoppingCartOpened(shoppingCartId, clientId)); + eventStore.Append(shoppingCartId, new ProductItemAddedToShoppingCart(shoppingCartId, twoPairsOfShoes)); + eventStore.Append(shoppingCartId, new ProductItemAddedToShoppingCart(shoppingCartId, tShirt)); + eventStore.Append(shoppingCartId, new ProductItemRemovedFromShoppingCart(shoppingCartId, pairOfShoes)); + eventStore.Append(shoppingCartId, new ShoppingCartConfirmed(shoppingCartId, DateTime.UtcNow)); + + // cancelled + eventStore.Append(cancelledShoppingCartId, new ShoppingCartOpened(cancelledShoppingCartId, clientId)); + eventStore.Append(cancelledShoppingCartId, new ProductItemAddedToShoppingCart(cancelledShoppingCartId, dress)); + eventStore.Append(cancelledShoppingCartId, new ShoppingCartCanceled(cancelledShoppingCartId, DateTime.UtcNow)); + + // confirmed but other client + eventStore.Append(otherClientShoppingCartId, new ShoppingCartOpened(otherClientShoppingCartId, otherClientId)); + eventStore.Append(otherClientShoppingCartId, new ProductItemAddedToShoppingCart(otherClientShoppingCartId, dress)); + eventStore.Append(otherClientShoppingCartId, new ShoppingCartConfirmed(otherClientShoppingCartId, DateTime.UtcNow)); + + // second confirmed + eventStore.Append(otherConfirmedShoppingCartId, new ShoppingCartOpened(otherConfirmedShoppingCartId, clientId)); + eventStore.Append(otherConfirmedShoppingCartId, new ProductItemAddedToShoppingCart(otherConfirmedShoppingCartId, trousers)); + eventStore.Append(otherConfirmedShoppingCartId, new ShoppingCartConfirmed(otherConfirmedShoppingCartId, DateTime.UtcNow)); + + // first pending + eventStore.Append(otherPendingShoppingCartId, new ShoppingCartOpened(otherPendingShoppingCartId, clientId)); // first confirmed var shoppingCart = database.Get(shoppingCartId)!; diff --git a/Workshops/IntroductionToEventSourcing/12-Projections.SingleStream/Tools/EventStore.cs b/Workshops/IntroductionToEventSourcing/12-Projections.SingleStream/Tools/EventStore.cs index f0c90a646..49c1230ea 100644 --- a/Workshops/IntroductionToEventSourcing/12-Projections.SingleStream/Tools/EventStore.cs +++ b/Workshops/IntroductionToEventSourcing/12-Projections.SingleStream/Tools/EventStore.cs @@ -3,6 +3,7 @@ namespace IntroductionToEventSourcing.GettingStateFromEvents.Tools; public class EventStore { private readonly Dictionary>> handlers = new(); + private readonly Dictionary> events = new(); public void Register(Action> handler) where TEvent : notnull { @@ -16,8 +17,20 @@ public class EventStore handlers.Add(eventType, new List> { WrappedHandler }); } - public void Append(EventEnvelope eventEnvelope) + public void Append(Guid streamId, TEvent @event) where TEvent : notnull { + if (!events.ContainsKey(streamId)) + events[streamId] = new List(); + + var eventEnvelope = new EventEnvelope(@event, + EventMetadata.For( + (ulong)events[streamId].Count + 1, + (ulong)events.Values.Sum(s => s.Count) + ) + ); + + events[streamId].Add(eventEnvelope); + if (!handlers.TryGetValue(eventEnvelope.Data.GetType(), out var eventHandlers)) return; foreach (var handle in eventHandlers) @@ -25,12 +38,5 @@ public void Append(EventEnvelope eventEnvelope) handle(eventEnvelope); } } - - public void Append(params EventEnvelope[] eventEnvelopes) - { - foreach (var @event in eventEnvelopes) - { - Append(@event); - } - } } + diff --git a/Workshops/IntroductionToEventSourcing/13-Projections.SingleStream.Idempotency/ProjectionsTests.cs b/Workshops/IntroductionToEventSourcing/13-Projections.SingleStream.Idempotency/ProjectionsTests.cs index 4d5ab1a29..3b3633c47 100644 --- a/Workshops/IntroductionToEventSourcing/13-Projections.SingleStream.Idempotency/ProjectionsTests.cs +++ b/Workshops/IntroductionToEventSourcing/13-Projections.SingleStream.Idempotency/ProjectionsTests.cs @@ -246,59 +246,7 @@ public void GettingState_ForSequenceOfEvents_ShouldSucceed() var otherPendingShoppingCartId = Guid.NewGuid(); var otherClientId = Guid.NewGuid(); - var logPosition = 0ul; - - var events = new EventEnvelope[] - { - // first confirmed - new EventEnvelope(new ShoppingCartOpened(shoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(shoppingCartId, twoPairsOfShoes), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(shoppingCartId, tShirt), EventMetadata.For(3, ++logPosition)), - new EventEnvelope( - new ProductItemRemovedFromShoppingCart(shoppingCartId, pairOfShoes), - EventMetadata.For(4, ++logPosition)), - new EventEnvelope(new ShoppingCartConfirmed(shoppingCartId, DateTime.UtcNow), - EventMetadata.For(5, ++logPosition)), - - // cancelled - new EventEnvelope(new ShoppingCartOpened(cancelledShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(cancelledShoppingCartId, dress), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope(new ShoppingCartCanceled(cancelledShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // confirmed but other client - new EventEnvelope(new ShoppingCartOpened(otherClientShoppingCartId, otherClientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(otherClientShoppingCartId, dress), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ShoppingCartConfirmed(otherClientShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // second confirmed - new EventEnvelope(new ShoppingCartOpened(otherConfirmedShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(otherConfirmedShoppingCartId, trousers), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ShoppingCartConfirmed(otherConfirmedShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // first pending - new EventEnvelope(new ShoppingCartOpened(otherPendingShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)) - }; - - var eventBus = new EventStore(); + var eventStore = new EventStore(); var database = new Database(); // TODO: @@ -306,21 +254,44 @@ public void GettingState_ForSequenceOfEvents_ShouldSucceed() // 2. Store results in database. var shoppingCartDetailsProjection = new ShoppingCartDetailsProjection(database); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); var shoppingCartShortInfoProjection = new ShoppingCartShortInfoProjection(database); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + + // first confirmed + eventStore.Append(shoppingCartId, new ShoppingCartOpened(shoppingCartId, clientId)); + eventStore.Append(shoppingCartId, new ProductItemAddedToShoppingCart(shoppingCartId, twoPairsOfShoes)); + eventStore.Append(shoppingCartId, new ProductItemAddedToShoppingCart(shoppingCartId, tShirt)); + eventStore.Append(shoppingCartId, new ProductItemRemovedFromShoppingCart(shoppingCartId, pairOfShoes)); + eventStore.Append(shoppingCartId, new ShoppingCartConfirmed(shoppingCartId, DateTime.UtcNow)); + + // cancelled + eventStore.Append(cancelledShoppingCartId, new ShoppingCartOpened(cancelledShoppingCartId, clientId)); + eventStore.Append(cancelledShoppingCartId, new ProductItemAddedToShoppingCart(cancelledShoppingCartId, dress)); + eventStore.Append(cancelledShoppingCartId, new ShoppingCartCanceled(cancelledShoppingCartId, DateTime.UtcNow)); - eventBus.Append(events); + // confirmed but other client + eventStore.Append(otherClientShoppingCartId, new ShoppingCartOpened(otherClientShoppingCartId, otherClientId)); + eventStore.Append(otherClientShoppingCartId, new ProductItemAddedToShoppingCart(otherClientShoppingCartId, dress)); + eventStore.Append(otherClientShoppingCartId, new ShoppingCartConfirmed(otherClientShoppingCartId, DateTime.UtcNow)); + + // second confirmed + eventStore.Append(otherConfirmedShoppingCartId, new ShoppingCartOpened(otherConfirmedShoppingCartId, clientId)); + eventStore.Append(otherConfirmedShoppingCartId, new ProductItemAddedToShoppingCart(otherConfirmedShoppingCartId, trousers)); + eventStore.Append(otherConfirmedShoppingCartId, new ShoppingCartConfirmed(otherConfirmedShoppingCartId, DateTime.UtcNow)); + + // first pending + eventStore.Append(otherPendingShoppingCartId, new ShoppingCartOpened(otherPendingShoppingCartId, clientId)); // first confirmed var shoppingCart = database.Get(shoppingCartId)!; diff --git a/Workshops/IntroductionToEventSourcing/13-Projections.SingleStream.Idempotency/Tools/EventStore.cs b/Workshops/IntroductionToEventSourcing/13-Projections.SingleStream.Idempotency/Tools/EventStore.cs index 1493bf6e0..1b66bbea8 100644 --- a/Workshops/IntroductionToEventSourcing/13-Projections.SingleStream.Idempotency/Tools/EventStore.cs +++ b/Workshops/IntroductionToEventSourcing/13-Projections.SingleStream.Idempotency/Tools/EventStore.cs @@ -2,10 +2,11 @@ namespace IntroductionToEventSourcing.GettingStateFromEvents.Tools; public class EventStore { - private readonly Dictionary>> handlers = new(); - private readonly Random random = new(); + private readonly Dictionary>> handlers = new(); + private readonly Dictionary> events = new(); + public void Register(Action> handler) where TEvent : notnull { var eventType = typeof(TEvent); @@ -33,11 +34,29 @@ public void Append(EventEnvelope eventEnvelope) } } - public void Append(params EventEnvelope[] eventEnvelopes) + public void Append(Guid streamId, TEvent @event) where TEvent : notnull { - foreach (var @event in eventEnvelopes) + if (!events.ContainsKey(streamId)) + events[streamId] = new List(); + + var eventEnvelope = new EventEnvelope(@event, + EventMetadata.For( + (ulong)events[streamId].Count + 1, + (ulong)events.Values.Sum(s => s.Count) + ) + ); + + events[streamId].Add(eventEnvelope); + + if (!handlers.TryGetValue(eventEnvelope.Data.GetType(), out var eventHandlers)) return; + + foreach (var handle in eventHandlers) { - Append(@event); + var numberOfRepeatedPublish = random.Next(1, 5); + do + { + handle(eventEnvelope); + } while (--numberOfRepeatedPublish > 0); } } } diff --git a/Workshops/IntroductionToEventSourcing/14-Projections.SingleStream.EventualConsistency/ProjectionsTests.cs b/Workshops/IntroductionToEventSourcing/14-Projections.SingleStream.EventualConsistency/ProjectionsTests.cs index 4d5ab1a29..8be9af72b 100644 --- a/Workshops/IntroductionToEventSourcing/14-Projections.SingleStream.EventualConsistency/ProjectionsTests.cs +++ b/Workshops/IntroductionToEventSourcing/14-Projections.SingleStream.EventualConsistency/ProjectionsTests.cs @@ -246,59 +246,7 @@ public void GettingState_ForSequenceOfEvents_ShouldSucceed() var otherPendingShoppingCartId = Guid.NewGuid(); var otherClientId = Guid.NewGuid(); - var logPosition = 0ul; - - var events = new EventEnvelope[] - { - // first confirmed - new EventEnvelope(new ShoppingCartOpened(shoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(shoppingCartId, twoPairsOfShoes), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(shoppingCartId, tShirt), EventMetadata.For(3, ++logPosition)), - new EventEnvelope( - new ProductItemRemovedFromShoppingCart(shoppingCartId, pairOfShoes), - EventMetadata.For(4, ++logPosition)), - new EventEnvelope(new ShoppingCartConfirmed(shoppingCartId, DateTime.UtcNow), - EventMetadata.For(5, ++logPosition)), - - // cancelled - new EventEnvelope(new ShoppingCartOpened(cancelledShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(cancelledShoppingCartId, dress), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope(new ShoppingCartCanceled(cancelledShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // confirmed but other client - new EventEnvelope(new ShoppingCartOpened(otherClientShoppingCartId, otherClientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(otherClientShoppingCartId, dress), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ShoppingCartConfirmed(otherClientShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // second confirmed - new EventEnvelope(new ShoppingCartOpened(otherConfirmedShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(otherConfirmedShoppingCartId, trousers), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ShoppingCartConfirmed(otherConfirmedShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // first pending - new EventEnvelope(new ShoppingCartOpened(otherPendingShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)) - }; - - var eventBus = new EventStore(); + var eventStore = new EventStore(); var database = new Database(); // TODO: @@ -306,21 +254,45 @@ public void GettingState_ForSequenceOfEvents_ShouldSucceed() // 2. Store results in database. var shoppingCartDetailsProjection = new ShoppingCartDetailsProjection(database); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); var shoppingCartShortInfoProjection = new ShoppingCartShortInfoProjection(database); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + + // first confirmed + eventStore.Append(shoppingCartId, new ShoppingCartOpened(shoppingCartId, clientId)); + eventStore.Append(shoppingCartId, new ProductItemAddedToShoppingCart(shoppingCartId, twoPairsOfShoes)); + eventStore.Append(shoppingCartId, new ProductItemAddedToShoppingCart(shoppingCartId, tShirt)); + eventStore.Append(shoppingCartId, new ProductItemRemovedFromShoppingCart(shoppingCartId, pairOfShoes)); + eventStore.Append(shoppingCartId, new ShoppingCartConfirmed(shoppingCartId, DateTime.UtcNow)); + + // cancelled + eventStore.Append(cancelledShoppingCartId, new ShoppingCartOpened(cancelledShoppingCartId, clientId)); + eventStore.Append(cancelledShoppingCartId, new ProductItemAddedToShoppingCart(cancelledShoppingCartId, dress)); + eventStore.Append(cancelledShoppingCartId, new ShoppingCartCanceled(cancelledShoppingCartId, DateTime.UtcNow)); + + // confirmed but other client + eventStore.Append(otherClientShoppingCartId, new ShoppingCartOpened(otherClientShoppingCartId, otherClientId)); + eventStore.Append(otherClientShoppingCartId, new ProductItemAddedToShoppingCart(otherClientShoppingCartId, dress)); + eventStore.Append(otherClientShoppingCartId, new ShoppingCartConfirmed(otherClientShoppingCartId, DateTime.UtcNow)); + + // second confirmed + eventStore.Append(otherConfirmedShoppingCartId, new ShoppingCartOpened(otherConfirmedShoppingCartId, clientId)); + eventStore.Append(otherConfirmedShoppingCartId, new ProductItemAddedToShoppingCart(otherConfirmedShoppingCartId, trousers)); + eventStore.Append(otherConfirmedShoppingCartId, new ShoppingCartConfirmed(otherConfirmedShoppingCartId, DateTime.UtcNow)); + + // first pending + eventStore.Append(otherPendingShoppingCartId, new ShoppingCartOpened(otherPendingShoppingCartId, clientId)); - eventBus.Append(events); // first confirmed var shoppingCart = database.Get(shoppingCartId)!; diff --git a/Workshops/IntroductionToEventSourcing/14-Projections.SingleStream.EventualConsistency/Tools/EventStore.cs b/Workshops/IntroductionToEventSourcing/14-Projections.SingleStream.EventualConsistency/Tools/EventStore.cs index 1493bf6e0..1b66bbea8 100644 --- a/Workshops/IntroductionToEventSourcing/14-Projections.SingleStream.EventualConsistency/Tools/EventStore.cs +++ b/Workshops/IntroductionToEventSourcing/14-Projections.SingleStream.EventualConsistency/Tools/EventStore.cs @@ -2,10 +2,11 @@ namespace IntroductionToEventSourcing.GettingStateFromEvents.Tools; public class EventStore { - private readonly Dictionary>> handlers = new(); - private readonly Random random = new(); + private readonly Dictionary>> handlers = new(); + private readonly Dictionary> events = new(); + public void Register(Action> handler) where TEvent : notnull { var eventType = typeof(TEvent); @@ -33,11 +34,29 @@ public void Append(EventEnvelope eventEnvelope) } } - public void Append(params EventEnvelope[] eventEnvelopes) + public void Append(Guid streamId, TEvent @event) where TEvent : notnull { - foreach (var @event in eventEnvelopes) + if (!events.ContainsKey(streamId)) + events[streamId] = new List(); + + var eventEnvelope = new EventEnvelope(@event, + EventMetadata.For( + (ulong)events[streamId].Count + 1, + (ulong)events.Values.Sum(s => s.Count) + ) + ); + + events[streamId].Add(eventEnvelope); + + if (!handlers.TryGetValue(eventEnvelope.Data.GetType(), out var eventHandlers)) return; + + foreach (var handle in eventHandlers) { - Append(@event); + var numberOfRepeatedPublish = random.Next(1, 5); + do + { + handle(eventEnvelope); + } while (--numberOfRepeatedPublish > 0); } } } diff --git a/Workshops/IntroductionToEventSourcing/Solved/12-Projections.SingleStream/ProjectionsTests.cs b/Workshops/IntroductionToEventSourcing/Solved/12-Projections.SingleStream/ProjectionsTests.cs index e28f2d5dd..1de18967d 100644 --- a/Workshops/IntroductionToEventSourcing/Solved/12-Projections.SingleStream/ProjectionsTests.cs +++ b/Workshops/IntroductionToEventSourcing/Solved/12-Projections.SingleStream/ProjectionsTests.cs @@ -245,59 +245,7 @@ public void GettingState_ForSequenceOfEvents_ShouldSucceed() var otherPendingShoppingCartId = Guid.NewGuid(); var otherClientId = Guid.NewGuid(); - var logPosition = 0ul; - - var events = new EventEnvelope[] - { - // first confirmed - new EventEnvelope(new ShoppingCartOpened(shoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(shoppingCartId, twoPairsOfShoes), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(shoppingCartId, tShirt), EventMetadata.For(3, ++logPosition)), - new EventEnvelope( - new ProductItemRemovedFromShoppingCart(shoppingCartId, pairOfShoes), - EventMetadata.For(4, ++logPosition)), - new EventEnvelope(new ShoppingCartConfirmed(shoppingCartId, DateTime.UtcNow), - EventMetadata.For(5, ++logPosition)), - - // cancelled - new EventEnvelope(new ShoppingCartOpened(cancelledShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(cancelledShoppingCartId, dress), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope(new ShoppingCartCanceled(cancelledShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // confirmed but other client - new EventEnvelope(new ShoppingCartOpened(otherClientShoppingCartId, otherClientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(otherClientShoppingCartId, dress), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ShoppingCartConfirmed(otherClientShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // second confirmed - new EventEnvelope(new ShoppingCartOpened(otherConfirmedShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(otherConfirmedShoppingCartId, trousers), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ShoppingCartConfirmed(otherConfirmedShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // first pending - new EventEnvelope(new ShoppingCartOpened(otherPendingShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)) - }; - - var eventBus = new EventStore(); + var eventStore = new EventStore(); var database = new Database(); // TODO: @@ -305,21 +253,44 @@ public void GettingState_ForSequenceOfEvents_ShouldSucceed() // 2. Store results in database. var shoppingCartDetailsProjection = new ShoppingCartDetailsProjection(database); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); var shoppingCartShortInfoProjection = new ShoppingCartShortInfoProjection(database); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + + // first confirmed + eventStore.Append(shoppingCartId, new ShoppingCartOpened(shoppingCartId, clientId)); + eventStore.Append(shoppingCartId, new ProductItemAddedToShoppingCart(shoppingCartId, twoPairsOfShoes)); + eventStore.Append(shoppingCartId, new ProductItemAddedToShoppingCart(shoppingCartId, tShirt)); + eventStore.Append(shoppingCartId, new ProductItemRemovedFromShoppingCart(shoppingCartId, pairOfShoes)); + eventStore.Append(shoppingCartId, new ShoppingCartConfirmed(shoppingCartId, DateTime.UtcNow)); + + // cancelled + eventStore.Append(cancelledShoppingCartId, new ShoppingCartOpened(cancelledShoppingCartId, clientId)); + eventStore.Append(cancelledShoppingCartId, new ProductItemAddedToShoppingCart(cancelledShoppingCartId, dress)); + eventStore.Append(cancelledShoppingCartId, new ShoppingCartCanceled(cancelledShoppingCartId, DateTime.UtcNow)); - eventBus.Append(events); + // confirmed but other client + eventStore.Append(otherClientShoppingCartId, new ShoppingCartOpened(otherClientShoppingCartId, otherClientId)); + eventStore.Append(otherClientShoppingCartId, new ProductItemAddedToShoppingCart(otherClientShoppingCartId, dress)); + eventStore.Append(otherClientShoppingCartId, new ShoppingCartConfirmed(otherClientShoppingCartId, DateTime.UtcNow)); + + // second confirmed + eventStore.Append(otherConfirmedShoppingCartId, new ShoppingCartOpened(otherConfirmedShoppingCartId, clientId)); + eventStore.Append(otherConfirmedShoppingCartId, new ProductItemAddedToShoppingCart(otherConfirmedShoppingCartId, trousers)); + eventStore.Append(otherConfirmedShoppingCartId, new ShoppingCartConfirmed(otherConfirmedShoppingCartId, DateTime.UtcNow)); + + // first pending + eventStore.Append(otherPendingShoppingCartId, new ShoppingCartOpened(otherPendingShoppingCartId, clientId)); // first confirmed var shoppingCart = database.Get(shoppingCartId)!; diff --git a/Workshops/IntroductionToEventSourcing/Solved/12-Projections.SingleStream/Tools/EventStore.cs b/Workshops/IntroductionToEventSourcing/Solved/12-Projections.SingleStream/Tools/EventStore.cs index f0c90a646..7f4bfb973 100644 --- a/Workshops/IntroductionToEventSourcing/Solved/12-Projections.SingleStream/Tools/EventStore.cs +++ b/Workshops/IntroductionToEventSourcing/Solved/12-Projections.SingleStream/Tools/EventStore.cs @@ -3,6 +3,7 @@ namespace IntroductionToEventSourcing.GettingStateFromEvents.Tools; public class EventStore { private readonly Dictionary>> handlers = new(); + private readonly Dictionary> events = new(); public void Register(Action> handler) where TEvent : notnull { @@ -16,8 +17,20 @@ public class EventStore handlers.Add(eventType, new List> { WrappedHandler }); } - public void Append(EventEnvelope eventEnvelope) + public void Append(Guid streamId, TEvent @event) where TEvent : notnull { + if (!events.ContainsKey(streamId)) + events[streamId] = new List(); + + var eventEnvelope = new EventEnvelope(@event, + EventMetadata.For( + (ulong)events[streamId].Count + 1, + (ulong)events.Values.Sum(s => s.Count) + ) + ); + + events[streamId].Add(eventEnvelope); + if (!handlers.TryGetValue(eventEnvelope.Data.GetType(), out var eventHandlers)) return; foreach (var handle in eventHandlers) @@ -25,12 +38,4 @@ public void Append(EventEnvelope eventEnvelope) handle(eventEnvelope); } } - - public void Append(params EventEnvelope[] eventEnvelopes) - { - foreach (var @event in eventEnvelopes) - { - Append(@event); - } - } } diff --git a/Workshops/IntroductionToEventSourcing/Solved/13-Projections.SingleStream.Idempotency/ProjectionsTests.cs b/Workshops/IntroductionToEventSourcing/Solved/13-Projections.SingleStream.Idempotency/ProjectionsTests.cs index f49da06f7..ecdf64647 100644 --- a/Workshops/IntroductionToEventSourcing/Solved/13-Projections.SingleStream.Idempotency/ProjectionsTests.cs +++ b/Workshops/IntroductionToEventSourcing/Solved/13-Projections.SingleStream.Idempotency/ProjectionsTests.cs @@ -281,59 +281,7 @@ public void GettingState_ForSequenceOfEvents_ShouldSucceed() var otherPendingShoppingCartId = Guid.NewGuid(); var otherClientId = Guid.NewGuid(); - var logPosition = 0ul; - - var events = new EventEnvelope[] - { - // first confirmed - new EventEnvelope(new ShoppingCartOpened(shoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(shoppingCartId, twoPairsOfShoes), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(shoppingCartId, tShirt), EventMetadata.For(3, ++logPosition)), - new EventEnvelope( - new ProductItemRemovedFromShoppingCart(shoppingCartId, pairOfShoes), - EventMetadata.For(4, ++logPosition)), - new EventEnvelope(new ShoppingCartConfirmed(shoppingCartId, DateTime.UtcNow), - EventMetadata.For(5, ++logPosition)), - - // cancelled - new EventEnvelope(new ShoppingCartOpened(cancelledShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(cancelledShoppingCartId, dress), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope(new ShoppingCartCanceled(cancelledShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // confirmed but other client - new EventEnvelope(new ShoppingCartOpened(otherClientShoppingCartId, otherClientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(otherClientShoppingCartId, dress), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ShoppingCartConfirmed(otherClientShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // second confirmed - new EventEnvelope(new ShoppingCartOpened(otherConfirmedShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(otherConfirmedShoppingCartId, trousers), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ShoppingCartConfirmed(otherConfirmedShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // first pending - new EventEnvelope(new ShoppingCartOpened(otherPendingShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)) - }; - - var eventBus = new EventStore(); + var eventStore = new EventStore(); var database = new Database(); // TODO: @@ -341,21 +289,44 @@ public void GettingState_ForSequenceOfEvents_ShouldSucceed() // 2. Store results in database. var shoppingCartDetailsProjection = new ShoppingCartDetailsProjection(database); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); var shoppingCartShortInfoProjection = new ShoppingCartShortInfoProjection(database); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Append(events); + // first confirmed + eventStore.Append(shoppingCartId, new ShoppingCartOpened(shoppingCartId, clientId)); + eventStore.Append(shoppingCartId, new ProductItemAddedToShoppingCart(shoppingCartId, twoPairsOfShoes)); + eventStore.Append(shoppingCartId, new ProductItemAddedToShoppingCart(shoppingCartId, tShirt)); + eventStore.Append(shoppingCartId, new ProductItemRemovedFromShoppingCart(shoppingCartId, pairOfShoes)); + eventStore.Append(shoppingCartId, new ShoppingCartConfirmed(shoppingCartId, DateTime.UtcNow)); + + // cancelled + eventStore.Append(cancelledShoppingCartId, new ShoppingCartOpened(cancelledShoppingCartId, clientId)); + eventStore.Append(cancelledShoppingCartId, new ProductItemAddedToShoppingCart(cancelledShoppingCartId, dress)); + eventStore.Append(cancelledShoppingCartId, new ShoppingCartCanceled(cancelledShoppingCartId, DateTime.UtcNow)); + + // confirmed but other client + eventStore.Append(otherClientShoppingCartId, new ShoppingCartOpened(otherClientShoppingCartId, otherClientId)); + eventStore.Append(otherClientShoppingCartId, new ProductItemAddedToShoppingCart(otherClientShoppingCartId, dress)); + eventStore.Append(otherClientShoppingCartId, new ShoppingCartConfirmed(otherClientShoppingCartId, DateTime.UtcNow)); + + // second confirmed + eventStore.Append(otherConfirmedShoppingCartId, new ShoppingCartOpened(otherConfirmedShoppingCartId, clientId)); + eventStore.Append(otherConfirmedShoppingCartId, new ProductItemAddedToShoppingCart(otherConfirmedShoppingCartId, trousers)); + eventStore.Append(otherConfirmedShoppingCartId, new ShoppingCartConfirmed(otherConfirmedShoppingCartId, DateTime.UtcNow)); + + // first pending + eventStore.Append(otherPendingShoppingCartId, new ShoppingCartOpened(otherPendingShoppingCartId, clientId)); // first confirmed var shoppingCart = database.Get(shoppingCartId)!; diff --git a/Workshops/IntroductionToEventSourcing/Solved/13-Projections.SingleStream.Idempotency/Tools/EventStore.cs b/Workshops/IntroductionToEventSourcing/Solved/13-Projections.SingleStream.Idempotency/Tools/EventStore.cs index 1493bf6e0..1b66bbea8 100644 --- a/Workshops/IntroductionToEventSourcing/Solved/13-Projections.SingleStream.Idempotency/Tools/EventStore.cs +++ b/Workshops/IntroductionToEventSourcing/Solved/13-Projections.SingleStream.Idempotency/Tools/EventStore.cs @@ -2,10 +2,11 @@ namespace IntroductionToEventSourcing.GettingStateFromEvents.Tools; public class EventStore { - private readonly Dictionary>> handlers = new(); - private readonly Random random = new(); + private readonly Dictionary>> handlers = new(); + private readonly Dictionary> events = new(); + public void Register(Action> handler) where TEvent : notnull { var eventType = typeof(TEvent); @@ -33,11 +34,29 @@ public void Append(EventEnvelope eventEnvelope) } } - public void Append(params EventEnvelope[] eventEnvelopes) + public void Append(Guid streamId, TEvent @event) where TEvent : notnull { - foreach (var @event in eventEnvelopes) + if (!events.ContainsKey(streamId)) + events[streamId] = new List(); + + var eventEnvelope = new EventEnvelope(@event, + EventMetadata.For( + (ulong)events[streamId].Count + 1, + (ulong)events.Values.Sum(s => s.Count) + ) + ); + + events[streamId].Add(eventEnvelope); + + if (!handlers.TryGetValue(eventEnvelope.Data.GetType(), out var eventHandlers)) return; + + foreach (var handle in eventHandlers) { - Append(@event); + var numberOfRepeatedPublish = random.Next(1, 5); + do + { + handle(eventEnvelope); + } while (--numberOfRepeatedPublish > 0); } } } diff --git a/Workshops/IntroductionToEventSourcing/Solved/14-Projections.SingleStream.EventualConsistency/ProjectionsTests.cs b/Workshops/IntroductionToEventSourcing/Solved/14-Projections.SingleStream.EventualConsistency/ProjectionsTests.cs index f1b1b364c..68c13dc5a 100644 --- a/Workshops/IntroductionToEventSourcing/Solved/14-Projections.SingleStream.EventualConsistency/ProjectionsTests.cs +++ b/Workshops/IntroductionToEventSourcing/Solved/14-Projections.SingleStream.EventualConsistency/ProjectionsTests.cs @@ -308,59 +308,7 @@ public void GettingState_ForSequenceOfEvents_ShouldSucceed() var otherPendingShoppingCartId = Guid.NewGuid(); var otherClientId = Guid.NewGuid(); - var logPosition = 0ul; - - var events = new EventEnvelope[] - { - // first confirmed - new EventEnvelope(new ShoppingCartOpened(shoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(shoppingCartId, twoPairsOfShoes), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(shoppingCartId, tShirt), EventMetadata.For(3, ++logPosition)), - new EventEnvelope( - new ProductItemRemovedFromShoppingCart(shoppingCartId, pairOfShoes), - EventMetadata.For(4, ++logPosition)), - new EventEnvelope(new ShoppingCartConfirmed(shoppingCartId, DateTime.UtcNow), - EventMetadata.For(5, ++logPosition)), - - // cancelled - new EventEnvelope(new ShoppingCartOpened(cancelledShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(cancelledShoppingCartId, dress), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope(new ShoppingCartCanceled(cancelledShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // confirmed but other client - new EventEnvelope(new ShoppingCartOpened(otherClientShoppingCartId, otherClientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(otherClientShoppingCartId, dress), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ShoppingCartConfirmed(otherClientShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // second confirmed - new EventEnvelope(new ShoppingCartOpened(otherConfirmedShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)), - new EventEnvelope( - new ProductItemAddedToShoppingCart(otherConfirmedShoppingCartId, trousers), - EventMetadata.For(2, ++logPosition)), - new EventEnvelope( - new ShoppingCartConfirmed(otherConfirmedShoppingCartId, DateTime.UtcNow), - EventMetadata.For(3, ++logPosition)), - - // first pending - new EventEnvelope(new ShoppingCartOpened(otherPendingShoppingCartId, clientId), - EventMetadata.For(1, ++logPosition)) - }; - - var eventBus = new EventStore(); + var eventStore = new EventStore(); var database = new Database(); // TODO: @@ -368,21 +316,44 @@ public void GettingState_ForSequenceOfEvents_ShouldSucceed() // 2. Store results in database. var shoppingCartDetailsProjection = new ShoppingCartDetailsProjection(database); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); - eventBus.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); + eventStore.Register(shoppingCartDetailsProjection.Handle); var shoppingCartShortInfoProjection = new ShoppingCartShortInfoProjection(database); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); - eventBus.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + eventStore.Register(shoppingCartShortInfoProjection.Handle); + + // first confirmed + eventStore.Append(shoppingCartId, new ShoppingCartOpened(shoppingCartId, clientId)); + eventStore.Append(shoppingCartId, new ProductItemAddedToShoppingCart(shoppingCartId, twoPairsOfShoes)); + eventStore.Append(shoppingCartId, new ProductItemAddedToShoppingCart(shoppingCartId, tShirt)); + eventStore.Append(shoppingCartId, new ProductItemRemovedFromShoppingCart(shoppingCartId, pairOfShoes)); + eventStore.Append(shoppingCartId, new ShoppingCartConfirmed(shoppingCartId, DateTime.UtcNow)); + + // cancelled + eventStore.Append(cancelledShoppingCartId, new ShoppingCartOpened(cancelledShoppingCartId, clientId)); + eventStore.Append(cancelledShoppingCartId, new ProductItemAddedToShoppingCart(cancelledShoppingCartId, dress)); + eventStore.Append(cancelledShoppingCartId, new ShoppingCartCanceled(cancelledShoppingCartId, DateTime.UtcNow)); - eventBus.Append(events); + // confirmed but other client + eventStore.Append(otherClientShoppingCartId, new ShoppingCartOpened(otherClientShoppingCartId, otherClientId)); + eventStore.Append(otherClientShoppingCartId, new ProductItemAddedToShoppingCart(otherClientShoppingCartId, dress)); + eventStore.Append(otherClientShoppingCartId, new ShoppingCartConfirmed(otherClientShoppingCartId, DateTime.UtcNow)); + + // second confirmed + eventStore.Append(otherConfirmedShoppingCartId, new ShoppingCartOpened(otherConfirmedShoppingCartId, clientId)); + eventStore.Append(otherConfirmedShoppingCartId, new ProductItemAddedToShoppingCart(otherConfirmedShoppingCartId, trousers)); + eventStore.Append(otherConfirmedShoppingCartId, new ShoppingCartConfirmed(otherConfirmedShoppingCartId, DateTime.UtcNow)); + + // first pending + eventStore.Append(otherPendingShoppingCartId, new ShoppingCartOpened(otherPendingShoppingCartId, clientId)); // first confirmed var shoppingCart = database.GetExpectingGreaterOrEqualVersionWithRetries(shoppingCartId, 5)!; diff --git a/Workshops/IntroductionToEventSourcing/Solved/14-Projections.SingleStream.EventualConsistency/Tools/EventStore.cs b/Workshops/IntroductionToEventSourcing/Solved/14-Projections.SingleStream.EventualConsistency/Tools/EventStore.cs index 1493bf6e0..1b66bbea8 100644 --- a/Workshops/IntroductionToEventSourcing/Solved/14-Projections.SingleStream.EventualConsistency/Tools/EventStore.cs +++ b/Workshops/IntroductionToEventSourcing/Solved/14-Projections.SingleStream.EventualConsistency/Tools/EventStore.cs @@ -2,10 +2,11 @@ namespace IntroductionToEventSourcing.GettingStateFromEvents.Tools; public class EventStore { - private readonly Dictionary>> handlers = new(); - private readonly Random random = new(); + private readonly Dictionary>> handlers = new(); + private readonly Dictionary> events = new(); + public void Register(Action> handler) where TEvent : notnull { var eventType = typeof(TEvent); @@ -33,11 +34,29 @@ public void Append(EventEnvelope eventEnvelope) } } - public void Append(params EventEnvelope[] eventEnvelopes) + public void Append(Guid streamId, TEvent @event) where TEvent : notnull { - foreach (var @event in eventEnvelopes) + if (!events.ContainsKey(streamId)) + events[streamId] = new List(); + + var eventEnvelope = new EventEnvelope(@event, + EventMetadata.For( + (ulong)events[streamId].Count + 1, + (ulong)events.Values.Sum(s => s.Count) + ) + ); + + events[streamId].Add(eventEnvelope); + + if (!handlers.TryGetValue(eventEnvelope.Data.GetType(), out var eventHandlers)) return; + + foreach (var handle in eventHandlers) { - Append(@event); + var numberOfRepeatedPublish = random.Next(1, 5); + do + { + handle(eventEnvelope); + } while (--numberOfRepeatedPublish > 0); } } }