From 1b6afc6a91d3a58d2001452c11866793277f7e15 Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Mon, 19 Feb 2018 17:59:52 +0100 Subject: [PATCH 1/4] Expose a flag that indicates if an event was handled or not Projectors that use the EventMap to handle events now receive an boolean to indicate if any handlers were registered for that event. #110 --- Src/LiquidProjections/EventMap.cs | 10 ++++++---- Src/LiquidProjections/IEventMap.cs | 5 ++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Src/LiquidProjections/EventMap.cs b/Src/LiquidProjections/EventMap.cs index a387248..eacaa28 100644 --- a/Src/LiquidProjections/EventMap.cs +++ b/Src/LiquidProjections/EventMap.cs @@ -26,7 +26,7 @@ internal void Add(Func action) /// /// Handles asynchronously using context . /// - public async Task Handle(object anEvent, TContext context) + public async Task Handle(object anEvent, TContext context) { if (anEvent == null) { @@ -40,15 +40,17 @@ public async Task Handle(object anEvent, TContext context) Type key = anEvent.GetType(); - List handlers; - - if (mappings.TryGetValue(key, out handlers)) + if (mappings.TryGetValue(key, out var handlers)) { foreach (Handler handler in handlers) { await handler(anEvent, context); } + + return true; } + + return false; } private delegate Task Handler(object @event, TContext context); diff --git a/Src/LiquidProjections/IEventMap.cs b/Src/LiquidProjections/IEventMap.cs index ca57131..43ef1f3 100644 --- a/Src/LiquidProjections/IEventMap.cs +++ b/Src/LiquidProjections/IEventMap.cs @@ -13,6 +13,9 @@ public interface IEventMap /// /// Handles asynchronously. /// - Task Handle(object anEvent, TContext context); + /// + /// Returns a value indicating whether the event was handled by any event. + /// + Task Handle(object anEvent, TContext context); } } \ No newline at end of file From 5dd9dc34c0547a0f4afdc8422138f849542487fa Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Wed, 21 Feb 2018 15:48:28 +0100 Subject: [PATCH 2/4] Changed the mapping API to be able to support more fluent options --- .vs/LiquidProjections/v15/sqlite3/storage.ide | Bin 1249280 -> 1441792 bytes Samples/ExampleHost/ExampleProjector.cs | 34 +- Src/LiquidProjections/EventMap.cs | 5 - Src/LiquidProjections/EventMapBuilder.cs | 1207 +++-------------- .../EventMapBuilderExtensions.cs | 747 ++++++++++ Src/LiquidProjections/IEventMap.cs | 3 +- Src/LiquidProjections/IEventMapBuilder.cs | 205 +-- .../{IEventMappingBuilder.cs => IAction.cs} | 37 +- .../MapBuilding/ICreateAction.cs | 40 + .../MapBuilding/ICreateEventActionBuilder.cs | 22 - .../MapBuilding/IDeleteAction.cs | 22 + .../MapBuilding/IUpdateAction.cs | 43 + .../MapBuilding/IUpdateEventActionBuilder.cs | 22 - Src/LiquidProjections/Projector.cs | 11 +- .../LiquidProjections.Specs/EventMapSpecs.cs | 708 +++++----- 15 files changed, 1487 insertions(+), 1619 deletions(-) create mode 100644 Src/LiquidProjections/EventMapBuilderExtensions.cs rename Src/LiquidProjections/MapBuilding/{IEventMappingBuilder.cs => IAction.cs} (74%) create mode 100644 Src/LiquidProjections/MapBuilding/ICreateAction.cs delete mode 100644 Src/LiquidProjections/MapBuilding/ICreateEventActionBuilder.cs create mode 100644 Src/LiquidProjections/MapBuilding/IDeleteAction.cs create mode 100644 Src/LiquidProjections/MapBuilding/IUpdateAction.cs delete mode 100644 Src/LiquidProjections/MapBuilding/IUpdateEventActionBuilder.cs diff --git a/.vs/LiquidProjections/v15/sqlite3/storage.ide b/.vs/LiquidProjections/v15/sqlite3/storage.ide index 8a135bbb8fc634c813c7364408905aeb42cce379..4cd3f7bc4a05d5c66315607d6be3d0a6edfa9de2 100644 GIT binary patch delta 191111 zcmc$H31AJ^_wdZTnR)ZpdD$hFB=(R!LF|bTf)HZgvOa`tvRKQ@TVf}8xT>Xg+Nxcj zE|gkIOG|$(U8to>H$`iymi9Ym-WG(S`u}cnX6`w6J?GvzbMLwL&MRNhy}W!t!wZY^GfRVFC>_NxRD4hnMbUKtGVDMx^BcFI>uk+fBi2%i2PvQq68{u$idV%S z#Baqj;wkZj_>p*UdysCe*aSlRo14M4uem;4)68CQO*J!ctz#MkiM>paFLlk7xczFN zPS^Ax!)8(ZeR+G6$YjO<@s!4i zthtP7)3-!l+%Y$EL_uLmX>N8&bi4GF-0b4QlEUGo;faMg`he;EX%{7wgvjK(&Eg#(4f?^ti0UpZu$v*3P z4UdUy);u#Org^MBrdfEJK0mVn$~MqDL6n1yBUy8=rd6Ipxdr-;`s}>S;>=QL z1qa6u(W~Y6L3R!I^{D@V-qGP>;r-dv$8Dez?9ksW>xV zKd!KNbe{=D`e)|c&bZ;3o@0r>v8*}&ANhNTy@Z?``-7UBm7AAaYM78xn3Fp^7Zi%4 zA3qSm9sVylt)5ZC6KgD28!v$#-RIFgx@zk_#65J}J|l|@$8{QC1d0@@WN<#d4}}!` zC;UFhuHn%d+ODs1f!p48UG>ze3V4zpi=?uG2cBLR8QwpI|2Ldg�lwyNI3-y!JDu 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 private IEventMap BuildMapFrom(IEventMapBuilder mapBuilder) { - mapBuilder.HandleProjectionModificationsAs((key, context, projector, options) => + return mapBuilder.Build(new ProjectorMap { - TProjection projection = store.GetRepository().Find(key); - if (projection == null) + Create = async (key, context, projector, shouldOverride) => { - projection = new TProjection() + var projection = new TProjection() { Id = key }; - store.Add(projection); - } + await projector(projection); - return projector(projection); - }); + store.Add(projection); + }, + Update = async (key, context, projector, createIfMissing) => + { + TProjection projection = store.GetRepository().Find(key); + await projector(projection); - mapBuilder.HandleProjectionDeletionsAs((key, context, options) => - { - store.GetRepository().RemoveByKey(key); + store.Add(projection); + }, + Delete = (key, context) => + { + store.GetRepository().RemoveByKey(key); - return Task.FromResult(0); + return Task.FromResult(true); + }, + Custom = (context, projector) => projector() }); - - mapBuilder.HandleCustomActionsAs((context, projector) => projector()); - - return mapBuilder.Build(); } public async Task Handle(IReadOnlyList transactions) diff --git a/Src/LiquidProjections/EventMap.cs b/Src/LiquidProjections/EventMap.cs index eacaa28..83397e5 100644 --- a/Src/LiquidProjections/EventMap.cs +++ b/Src/LiquidProjections/EventMap.cs @@ -11,8 +11,6 @@ public class EventMap : IEventMap { private readonly Dictionary> mappings = new Dictionary>(); - internal CustomHandler Do { get; set; } - internal void Add(Func action) { if (!mappings.ContainsKey(typeof(TEvent))) @@ -23,9 +21,6 @@ internal void Add(Func action) mappings[typeof(TEvent)].Add((@event, context) => action((TEvent)@event, context)); } - /// - /// Handles asynchronously using context . - /// public async Task Handle(object anEvent, TContext context) { if (anEvent == null) diff --git a/Src/LiquidProjections/EventMapBuilder.cs b/Src/LiquidProjections/EventMapBuilder.cs index 8890bae..63cc5fd 100644 --- a/Src/LiquidProjections/EventMapBuilder.cs +++ b/Src/LiquidProjections/EventMapBuilder.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; - using LiquidProjections.MapBuilding; namespace LiquidProjections @@ -12,98 +11,74 @@ namespace LiquidProjections public sealed class EventMapBuilder : IEventMapBuilder { private readonly EventMap eventMap = new EventMap(); - private bool isBuilt; - private CustomHandler customHandler; + private ProjectorMap projector; /// /// Starts configuring a new handler for events of type . /// /// - /// that allows to continue configuring the handler. + /// that allows to continue configuring the handler. /// - public IEventMappingBuilder Map() + public IAction Map() { AssertNotBuilt(); - return new EventMappingBuilder(this); + return new Action(this, () => projector); } /// - /// Builds the resulting event map. Can only be called once. - /// No changes can be made after the event map has been built. + /// Builds the resulting event map. /// - public IEventMap Build() + /// + /// Can only be called once. + /// No changes can be made after the event map has been built. + /// + /// + /// Contains the handler that a projector needs to support to handle events from this map. + /// + public IEventMap Build(ProjectorMap projector) { AssertNotBuilt(); - AssertComplete(); - - isBuilt = true; - return eventMap; - } - /// - /// Configures the event map to handle custom actions via the provided delegate . - /// - public void HandleCustomActionsAs(CustomHandler handler) - { - if (handler == null) - { - throw new ArgumentNullException(nameof(handler)); - } - - if (customHandler != null) + if (projector == null) { - throw new InvalidOperationException( - $"{nameof(IEventMapBuilder.HandleCustomActionsAs)} was already called."); + throw new ArgumentNullException(nameof(projector)); } - AssertNotBuilt(); - - customHandler = handler; - } - - internal void Add(Func action) - { - if (action == null) + if (projector.Custom == null) { - throw new ArgumentNullException(nameof(action)); + throw new ArgumentException( + $"Expected the Custom property to point to a valid instance of {nameof(CustomHandler)}", nameof(projector)); } - AssertNotBuilt(); + this.projector = projector; - eventMap.Add(action); + return eventMap; } - internal void AssertNotBuilt() + private void AssertNotBuilt() { - if (isBuilt) + if (projector != null) { throw new InvalidOperationException("The event map has already been built."); } } - private void AssertComplete() - { - if (customHandler == null) - { - throw new InvalidOperationException( - $"{nameof(IEventMapBuilder.HandleCustomActionsAs)} was not called."); - } - } - - internal sealed class EventMappingBuilder : IEventMappingBuilder + private sealed class Action : IAction { - private readonly EventMapBuilder eventMapBuilder; + private readonly EventMapBuilder parent; + private readonly Func> getProjector; private readonly List>> predicates = new List>>(); - public EventMappingBuilder(EventMapBuilder eventMapBuilder) + public Action(EventMapBuilder parent, Func> getProjector) { - this.eventMapBuilder = eventMapBuilder; + this.parent = parent; + this.getProjector = getProjector; } - public IEventMappingBuilder When(Func> predicate) + public IAction When(Func> predicate) { if (predicate == null) { @@ -121,12 +96,12 @@ public void As(Func action) throw new ArgumentNullException(nameof(action)); } - Add((anEvent, context) => eventMapBuilder.customHandler(context, async () => await action(anEvent, context))); + Add((anEvent, context) => getProjector().Custom(context, async () => await action(anEvent, context))); } - internal void Add(Func action) + private void Add(Func action) { - eventMapBuilder.Add(async (anEvent, context) => + parent.eventMap.Add(async (anEvent, context) => { foreach (Func> predicate in predicates) { @@ -150,204 +125,112 @@ internal void Add(Func action) public sealed class EventMapBuilder : IEventMapBuilder { private readonly EventMapBuilder innerBuilder = new EventMapBuilder(); - private ProjectionModificationHandler projectionModificationHandler; - private ProjectionDeletionHandler projectionDeletionHandler; + private ProjectorMap projector; /// /// Starts configuring a new handler for events of type . /// /// - /// that allows to continue configuring the handler. + /// that allows to continue configuring the handler. /// - public IEventMappingBuilder Map() - { - innerBuilder.AssertNotBuilt(); - - return new ProjectionEventMappingBuilder(this); - } - - /// - /// Builds the resulting event map. Can only be called once. - /// No changes can be made after the event map has been built. - /// - public IEventMap Build() - { - innerBuilder.AssertNotBuilt(); - AssertComplete(); - - return innerBuilder.Build(); - } - - /// - /// Configures the event map to handle custom actions via the provided delegate . - /// - public void HandleCustomActionsAs(CustomHandler handler) - { - innerBuilder.HandleCustomActionsAs(handler); - } - - /// - /// Configures the event map to handle projection creation and updating - /// via the provided delegate . - /// - public void HandleProjectionModificationsAs(ProjectionModificationHandler handler) + public ICrudAction Map() { - if (handler == null) - { - throw new ArgumentNullException(nameof(handler)); - } - - if (projectionModificationHandler != null) - { - throw new InvalidOperationException( - $"{nameof(IEventMapBuilder.HandleProjectionModificationsAs)} " + - "was already called."); - } - - innerBuilder.AssertNotBuilt(); - - projectionModificationHandler = handler; + return new CrudAction(this); } /// - /// Configures the event map to handle projection deletion - /// via the provided delegate . + /// Builds the resulting event map. /// - public void HandleProjectionDeletionsAs(ProjectionDeletionHandler handler) - { - if (handler == null) - { - throw new ArgumentNullException(nameof(handler)); - } - - if (projectionDeletionHandler != null) - { - throw new InvalidOperationException( - $"{nameof(IEventMapBuilder.HandleProjectionDeletionsAs)} was already called."); - } - - innerBuilder.AssertNotBuilt(); - - projectionDeletionHandler = handler; - } - - private void AssertComplete() + /// + /// Can only be called once. + /// No changes can be made after the event map has been built. + /// + /// + /// Contains the create, update, delete and custom handlers that a projector needs to support to handle events from this map. + /// + public IEventMap Build(ProjectorMap projector) { - if (projectionModificationHandler == null) - { - throw new InvalidOperationException( - $"{nameof(IEventMapBuilder.HandleProjectionModificationsAs)} was not called."); - } - - if (projectionDeletionHandler == null) + this.projector = projector; + return innerBuilder.Build(new ProjectorMap { - throw new InvalidOperationException( - $"{nameof(IEventMapBuilder.HandleProjectionDeletionsAs)} was not called."); - } + Custom = (context, projectEvent) => projectEvent() + }); } - private sealed class ProjectionEventMappingBuilder : IEventMappingBuilder + private sealed class CrudAction : ICrudAction { - private static readonly ProjectionDeletionOptions optionsForDelete = - new ProjectionDeletionOptions(MissingProjectionDeletionBehavior.Throw); - - private static readonly ProjectionDeletionOptions optionsForDeleteIfExists = - new ProjectionDeletionOptions(MissingProjectionDeletionBehavior.Ignore); + private readonly IAction actionBuilder; + private readonly Func > getProjector; - private readonly EventMapBuilder.EventMappingBuilder innerBuilder; - private readonly EventMapBuilder eventMapBuilder; - - public ProjectionEventMappingBuilder(EventMapBuilder eventMapBuilder) + public CrudAction(EventMapBuilder parent) { - innerBuilder = new EventMapBuilder.EventMappingBuilder(eventMapBuilder.innerBuilder); - this.eventMapBuilder = eventMapBuilder; + actionBuilder = parent.innerBuilder.Map(); + getProjector = () => parent.projector; } - public ICreateEventActionBuilder AsCreateOf(Func getKey) + public ICreateAction AsCreateOf(Func getKey) { if (getKey == null) { throw new ArgumentNullException(nameof(getKey)); } - return new CreateEventActionBuilder(this, getKey); + return new CreateAction(actionBuilder, getProjector, getKey); } - public ICreateIfDoesNotExistEventActionBuilder AsCreateIfDoesNotExistOf( + public ICreateAction AsCreateIfDoesNotExistOf( Func getKey) { - if (getKey == null) - { - throw new ArgumentNullException(nameof(getKey)); - } - - return new CreateIfDoesNotExistEventActionBuilder(this, getKey); + return AsCreateOf(getKey).IgnoringDuplicates(); } - public ICreateOrUpdateEventActionBuilder AsCreateOrUpdateOf(Func getKey) + public ICreateAction AsCreateOrUpdateOf(Func getKey) { - if (getKey == null) - { - throw new ArgumentNullException(nameof(getKey)); - } - - return new CreateOrUpdateEventActionBuilder(this, getKey); + return AsCreateOf(getKey).OverwritingDuplicates(); } - public void AsDeleteOf(Func getKey) + public IDeleteAction AsDeleteOf(Func getKey) { if (getKey == null) { throw new ArgumentNullException(nameof(getKey)); } - innerBuilder.Add((anEvent, context) => - eventMapBuilder.projectionDeletionHandler(getKey(anEvent), context, optionsForDelete)); + return new DeleteAction(actionBuilder, getProjector, getKey); } - public void AsDeleteIfExistsOf(Func getKey) + public IDeleteAction AsDeleteIfExistsOf(Func getKey) { - if (getKey == null) - { - throw new ArgumentNullException(nameof(getKey)); - } - - innerBuilder.Add((anEvent, context) => - eventMapBuilder.projectionDeletionHandler(getKey(anEvent), context, optionsForDeleteIfExists)); + return AsDeleteOf(getKey).IgnoringMisses(); } - public IUpdateEventActionBuilder AsUpdateOf(Func getKey) + public IUpdateAction AsUpdateOf(Func getKey) { if (getKey == null) { throw new ArgumentNullException(nameof(getKey)); } - return new UpdateEventActionBuilder(this, getKey); + return new UpdateAction(actionBuilder, getProjector, getKey); } - public IUpdateIfExistsEventActionBuilder AsUpdateIfExistsOf(Func getKey) + public IUpdateAction AsUpdateIfExistsOf(Func getKey) { - if (getKey == null) - { - throw new ArgumentNullException(nameof(getKey)); - } - - return new UpdateIfExistsEventActionBuilder(this, getKey); + return AsUpdateOf(getKey).IgnoringMisses(); } public void As(Func action) { - innerBuilder.As(action); + actionBuilder.As((anEvent, context) => getProjector().Custom(context, () => action(anEvent, context))); } - IEventMappingBuilder IEventMappingBuilder.When( + IAction IAction.When( Func> predicate) { return When(predicate); } - public IEventMappingBuilder When( + public ICrudAction When( Func> predicate) { if (predicate == null) @@ -355,920 +238,170 @@ public IEventMappingBuilder When( throw new ArgumentNullException(nameof(predicate)); } - innerBuilder.When(predicate); + actionBuilder.When(predicate); return this; } - private sealed class CreateEventActionBuilder : - ICreateEventActionBuilder + private sealed class CreateAction : ICreateAction { - private static readonly ProjectionModificationOptions options = new ProjectionModificationOptions( - MissingProjectionModificationBehavior.Create, - ExistingProjectionModificationBehavior.Throw); + private Func shouldOverwrite; - private readonly ProjectionEventMappingBuilder eventMappingBuilder; + private readonly IAction actionBuilder; + private readonly Func> projector; private readonly Func getKey; - public CreateEventActionBuilder( - ProjectionEventMappingBuilder eventMappingBuilder, - Func getKey) + public CreateAction(IAction actionBuilder, + Func> projector, Func getKey) { - this.eventMappingBuilder = eventMappingBuilder; + this.actionBuilder = actionBuilder; + this.projector = projector; this.getKey = getKey; + + shouldOverwrite = (existingProjection, @event, context) => + throw new ProjectionException( + $"Projection {typeof(TProjection)} with key {getKey(@event)}already exists."); } - public void Using(Func projector) + public ICreateAction Using(Func projector) { if (projector == null) { throw new ArgumentNullException(nameof(projector)); } - eventMappingBuilder.innerBuilder.Add((anEvent, context) => - eventMappingBuilder.eventMapBuilder.projectionModificationHandler( + actionBuilder.As((anEvent, context) => this.projector().Create( getKey(anEvent), context, projection => projector(projection, anEvent, context), - options)); - } - } + existingProjection => shouldOverwrite(existingProjection, anEvent, context))); - private sealed class CreateIfDoesNotExistEventActionBuilder : - ICreateIfDoesNotExistEventActionBuilder - { - private static readonly ProjectionModificationOptions options = new ProjectionModificationOptions( - MissingProjectionModificationBehavior.Create, - ExistingProjectionModificationBehavior.Ignore); - - private readonly ProjectionEventMappingBuilder eventMappingBuilder; - private readonly Func getKey; + return this; + } - public CreateIfDoesNotExistEventActionBuilder( - ProjectionEventMappingBuilder eventMappingBuilder, - Func getKey) + public ICreateAction IgnoringDuplicates() { - this.eventMappingBuilder = eventMappingBuilder; - this.getKey = getKey; + shouldOverwrite = (duplicate, @event,context) => false; + return this; } - public void Using(Func projector) + public ICreateAction OverwritingDuplicates() { - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } + shouldOverwrite = (duplicate, @event,context) => true; + return this; + } - eventMappingBuilder.innerBuilder.Add((anEvent, context) => - eventMappingBuilder.eventMapBuilder.projectionModificationHandler( - getKey(anEvent), - context, - projection => projector(projection, anEvent, context), - options)); + public ICreateAction HandlingDuplicatesUsing(Func shouldOverwrite) + { + this.shouldOverwrite = shouldOverwrite; + return this; } } - private sealed class UpdateEventActionBuilder : - IUpdateEventActionBuilder + private sealed class UpdateAction : IUpdateAction { - private static readonly ProjectionModificationOptions options = new ProjectionModificationOptions( - MissingProjectionModificationBehavior.Throw, - ExistingProjectionModificationBehavior.Update); - - private readonly ProjectionEventMappingBuilder eventMappingBuilder; + private readonly IAction actionBuilder; + private readonly Func> projector; private readonly Func getKey; + private Func handleMissesUsing; - public UpdateEventActionBuilder( - ProjectionEventMappingBuilder eventMappingBuilder, - Func getKey) + public UpdateAction(IAction actionBuilder, + Func> projector, Func getKey) { - this.eventMappingBuilder = eventMappingBuilder; + this.projector = projector; + this.actionBuilder = actionBuilder; this.getKey = getKey; + + ThrowingIfMissing(); } - public void Using(Func projector) + public IUpdateAction Using(Func updateAction) { - if (projector == null) + if (updateAction == null) { - throw new ArgumentNullException(nameof(projector)); + throw new ArgumentNullException(nameof(updateAction)); } - eventMappingBuilder.innerBuilder.Add((anEvent, context) => - eventMappingBuilder.eventMapBuilder.projectionModificationHandler( - getKey(anEvent), - context, - projection => projector(projection, anEvent, context), - options)); + actionBuilder.As((anEvent, context) => OnUpdate(updateAction, anEvent, context)); + + return this; } - } - private sealed class UpdateIfExistsEventActionBuilder : - IUpdateIfExistsEventActionBuilder - { - private static readonly ProjectionModificationOptions options = new ProjectionModificationOptions( - MissingProjectionModificationBehavior.Ignore, - ExistingProjectionModificationBehavior.Update); + private async Task OnUpdate(Func projector, TEvent anEvent, TContext context) + { + var key = getKey(anEvent); + + await this.projector().Update( + key, + context, + projection => projector(projection, anEvent, context), + () => handleMissesUsing(key, context)); + } - private readonly ProjectionEventMappingBuilder eventMappingBuilder; - private readonly Func getKey; + public IUpdateAction ThrowingIfMissing() + { + handleMissesUsing = (key, ctx) => throw new ProjectionException($"Failed to find {typeof(TProjection).Name} with key {key}"); + return this; + } - public UpdateIfExistsEventActionBuilder( - ProjectionEventMappingBuilder eventMappingBuilder, - Func getKey) + public IUpdateAction IgnoringMisses() { - this.eventMappingBuilder = eventMappingBuilder; - this.getKey = getKey; + handleMissesUsing = (_, __) => false; + return this; } - public void Using(Func projector) + public IUpdateAction CreatingIfMissing() { - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } + handleMissesUsing = (_, __) => true; + return this; + } - eventMappingBuilder.innerBuilder.Add((anEvent, context) => - eventMappingBuilder.eventMapBuilder.projectionModificationHandler( - getKey(anEvent), - context, - projection => projector(projection, anEvent, context), - options)); + public IUpdateAction HandlingMissesUsing(Func action) + { + handleMissesUsing = action; + return this; } } - private sealed class CreateOrUpdateEventActionBuilder : - ICreateOrUpdateEventActionBuilder + private class DeleteAction : IDeleteAction { - private static readonly ProjectionModificationOptions options = new ProjectionModificationOptions( - MissingProjectionModificationBehavior.Create, - ExistingProjectionModificationBehavior.Update); - - private readonly ProjectionEventMappingBuilder eventMappingBuilder; - private readonly Func getKey; + private Action handleMissing; - public CreateOrUpdateEventActionBuilder( - ProjectionEventMappingBuilder eventMappingBuilder, - Func getKey) + public DeleteAction(IAction actionBuilder, + Func> projector, Func getKey) { - this.eventMappingBuilder = eventMappingBuilder; - this.getKey = getKey; + actionBuilder.As((anEvent, context) => OnDelete(projector(), getKey, anEvent, context)); + + ThrowingIfMissing(); } - public void Using(Func projector) + private async Task OnDelete(ProjectorMap projector, Func getKey, TEvent anEvent, TContext context) { - if (projector == null) + TKey key = getKey(anEvent); + bool deleted = await projector.Delete(key, context); + if (!deleted) { - throw new ArgumentNullException(nameof(projector)); + handleMissing(key, context); } - - eventMappingBuilder.innerBuilder.Add((anEvent, context) => - eventMappingBuilder.eventMapBuilder.projectionModificationHandler( - getKey(anEvent), - context, - projection => projector(projection, anEvent, context), - options)); } - } - } - } - - /// - /// Contains extension methods to map events to handlers in a fluent fashion. - /// - public static class EventMapBuilderExtensions - { - /// - /// Finishes configuring a custom handler for events of type - /// using context of type . - /// - /// The . - /// - /// The synchronous delegate that handles the event. - /// Takes the event and the context as the parameters. - /// - public static void As( - this IEventMappingBuilder eventMappingBuilder, - Action action) - { - if (eventMappingBuilder == null) - { - throw new ArgumentNullException(nameof(eventMappingBuilder)); - } - - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - eventMappingBuilder.As((anEvent, context) => - { - action(anEvent, context); - return SpecializedTasks.ZeroTask; - }); - } - - /// - /// Finishes configuring a custom handler for events of type - /// using context of type . - /// - /// The . - /// - /// The synchronous delegate that handles the event. - /// Takes the event as the parameter. - /// - public static void As( - this IEventMappingBuilder eventMappingBuilder, - Action action) - { - if (eventMappingBuilder == null) - { - throw new ArgumentNullException(nameof(eventMappingBuilder)); - } - - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - eventMappingBuilder.As((anEvent, context) => - { - action(anEvent); - return SpecializedTasks.ZeroTask; - }); - } - - /// - /// Finishes configuring a custom handler for events of type - /// using context of type . - /// - /// The . - /// - /// The asynchronous delegate that handles the event. - /// Takes the event as the parameter. - /// - public static void As( - this IEventMappingBuilder eventMappingBuilder, - Func action) - { - if (eventMappingBuilder == null) - { - throw new ArgumentNullException(nameof(eventMappingBuilder)); - } - - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - eventMappingBuilder.As((anEvent, context) => action(anEvent)); - } - - /// - /// Continues configuring a handler for events of type - /// using context of type . - /// Provides an additional condition that needs to be satisfied in order for the event to be handled by the handler. - /// - /// The . - /// - /// The synchronous delegate that filters the events and - /// should return true for events that will be handled by the handler. - /// Takes the event and the context as the parameters. - /// - /// - /// that allows to continue configuring the handler. - /// - public static IEventMappingBuilder When( - this IEventMappingBuilder eventMappingBuilder, - Func predicate) - { - if (eventMappingBuilder == null) - { - throw new ArgumentNullException(nameof(eventMappingBuilder)); - } - - if (predicate == null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - return eventMappingBuilder.When((anEvent, context) => Task.FromResult(predicate(anEvent, context))); - } - - /// - /// Continues configuring a handler for events of type - /// using context of type . - /// Provides an additional condition that needs to be satisfied in order for the event to be handled by the handler. - /// - /// The . - /// - /// The synchronous delegate that filters the events and - /// should return true for events that will be handled by the handler. - /// Takes the event as the parameter. - /// - /// - /// that allows to continue configuring the handler. - /// - public static IEventMappingBuilder When( - this IEventMappingBuilder eventMappingBuilder, - Func predicate) - { - if (eventMappingBuilder == null) - { - throw new ArgumentNullException(nameof(eventMappingBuilder)); - } - - if (predicate == null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - return eventMappingBuilder.When((anEvent, context) => Task.FromResult(predicate(anEvent))); - } - - /// - /// Continues configuring a handler for events of type - /// using context of type . - /// Provides an additional condition that needs to be satisfied in order for the event to be handled by the handler. - /// - /// The . - /// - /// The asynchronous delegate that filters the events and - /// should return true for events that will be handled by the handler. - /// Takes the event as the parameter. - /// - /// - /// that allows to continue configuring the handler. - /// - public static IEventMappingBuilder When( - this IEventMappingBuilder eventMappingBuilder, - Func> predicate) - { - if (eventMappingBuilder == null) - { - throw new ArgumentNullException(nameof(eventMappingBuilder)); - } - if (predicate == null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - return eventMappingBuilder.When((anEvent, context) => predicate(anEvent)); - } + public IDeleteAction ThrowingIfMissing() + { + handleMissing = (key, ctx) => throw new ProjectionException($"Could not delete {typeof(TProjection).Name} with key {key} because it does not exist");; + return this; + } - /// - /// Continues configuring a handler for events of type - /// for projections of type with key of type - /// using context of type . - /// Provides an additional condition that needs to be satisfied in order for the event to be handled by the handler. - /// - /// The . - /// - /// The synchronous delegate that filters the events and - /// should return true for events that will be handled by the handler. - /// Takes the event and the context as the parameters. - /// - /// - /// that allows to continue configuring the handler. - /// - public static IEventMappingBuilder When( - this IEventMappingBuilder eventMappingBuilder, - Func predicate) - { - if (eventMappingBuilder == null) - { - throw new ArgumentNullException(nameof(eventMappingBuilder)); - } + public IDeleteAction IgnoringMisses() + { + handleMissing = (_, __) => {}; + return this; + } - if (predicate == null) - { - throw new ArgumentNullException(nameof(predicate)); + public IDeleteAction HandlingMissesUsing(Action action) + { + handleMissing = action; + return this; + } } - - return eventMappingBuilder.When((anEvent, context) => Task.FromResult(predicate(anEvent, context))); - } - - /// - /// Continues configuring a handler for events of type - /// for projections of type with key of type - /// using context of type . - /// Provides an additional condition that needs to be satisfied in order for the event to be handled by the handler. - /// - /// The . - /// - /// The synchronous delegate that filters the events and - /// should return true for events that will be handled by the handler. - /// Takes the event as the parameter. - /// - /// - /// that allows to continue configuring the handler. - /// - public static IEventMappingBuilder When( - this IEventMappingBuilder eventMappingBuilder, - Func predicate) - { - if (eventMappingBuilder == null) - { - throw new ArgumentNullException(nameof(eventMappingBuilder)); - } - - if (predicate == null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - return eventMappingBuilder.When((anEvent, context) => Task.FromResult(predicate(anEvent))); - } - - /// - /// Continues configuring a handler for events of type - /// for projections of type with key of type - /// using context of type . - /// Provides an additional condition that needs to be satisfied in order for the event to be handled by the handler. - /// - /// The . - /// - /// The asynchronous delegate that filters the events and - /// should return true for events that will be handled by the handler. - /// Takes the event as the parameter. - /// - /// - /// that allows to continue configuring the handler. - /// - public static IEventMappingBuilder When( - this IEventMappingBuilder eventMappingBuilder, - Func> predicate) - { - if (eventMappingBuilder == null) - { - throw new ArgumentNullException(nameof(eventMappingBuilder)); - } - - if (predicate == null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - return eventMappingBuilder.When((anEvent, context) => predicate(anEvent)); - } - - /// - /// Finishes configuring a projection creation handler for events of type - /// for projections of type using context of type . - /// - /// The . - /// - /// The synchronous delegate that initializes the created projection. - /// Takes the projection, the event and the context as the parameters. - /// - public static void Using( - this ICreateEventActionBuilder eventActionBuilder, - Action projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => - { - projector(projection, anEvent, context); - return SpecializedTasks.FalseTask; - }); - } - - /// - /// Finishes configuring a projection creation handler for events of type - /// for projections of type using context of type . - /// - /// The . - /// - /// The synchronous delegate that initializes the created projection. - /// Takes the projection and the event as the parameters. - /// - public static void Using( - this ICreateEventActionBuilder eventActionBuilder, - Action projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => - { - projector(projection, anEvent); - return SpecializedTasks.FalseTask; - }); - } - - /// - /// Finishes configuring a projection creation handler for events of type - /// for projections of type using context of type . - /// - /// The . - /// - /// The asynchronous delegate that initializes the created projection. - /// Takes the projection and the event as the parameters. - /// - public static void Using( - this ICreateEventActionBuilder eventActionBuilder, - Func projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => projector(projection, anEvent)); - } - - /// - /// Finishes configuring a projection creation handler for projections which do not exist yet - /// for events of type - /// for projections of type using context of type . - /// - /// - /// The . - /// - /// - /// The synchronous delegate that initializes the created projection. - /// Takes the projection, the event and the context as the parameters. - /// - public static void Using( - this ICreateIfDoesNotExistEventActionBuilder eventActionBuilder, - Action projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => - { - projector(projection, anEvent, context); - return SpecializedTasks.FalseTask; - }); - } - - /// - /// Finishes configuring a projection creation handler for projections which do not exist yet - /// for events of type - /// for projections of type using context of type . - /// - /// - /// The . - /// - /// - /// The synchronous delegate that initializes the created projection. - /// Takes the projection and the event as the parameters. - /// - public static void Using( - this ICreateIfDoesNotExistEventActionBuilder eventActionBuilder, - Action projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => - { - projector(projection, anEvent); - return SpecializedTasks.FalseTask; - }); - } - - /// - /// Finishes configuring a projection creation handler for projections which do not exist yet - /// for events of type - /// for projections of type using context of type . - /// - /// - /// The . - /// - /// - /// The asynchronous delegate that initializes the created projection. - /// Takes the projection and the event as the parameters. - /// - public static void Using( - this ICreateIfDoesNotExistEventActionBuilder eventActionBuilder, - Func projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => projector(projection, anEvent)); - } - - /// - /// Finishes configuring a projection updating handler for events of type - /// for projections of type using context of type . - /// - /// - /// The . - /// - /// - /// The synchronous delegate that updates the projection. - /// Takes the projection, the event and the context as the parameters. - /// - public static void Using( - this IUpdateEventActionBuilder eventActionBuilder, - Action projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => - { - projector(projection, anEvent, context); - return SpecializedTasks.FalseTask; - }); - } - - /// - /// Finishes configuring a projection updating handler for events of type - /// for projections of type using context of type . - /// - /// - /// The . - /// - /// - /// The synchronous delegate that updates the projection. - /// Takes the projection and the event as the parameters. - /// - public static void Using( - this IUpdateEventActionBuilder eventActionBuilder, - Action projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => - { - projector(projection, anEvent); - return SpecializedTasks.FalseTask; - }); - } - - /// - /// Finishes configuring a projection updating handler for events of type - /// for projections of type using context of type . - /// - /// - /// The . - /// - /// - /// The asynchronous delegate that updates the projection. - /// Takes the projection and the event as the parameters. - /// - public static void Using( - this IUpdateEventActionBuilder eventActionBuilder, - Func projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => projector(projection, anEvent)); - } - - /// - /// Finishes configuring a projection updating handler for projections which do already exist - /// for events of type - /// for projections of type using context of type . - /// - /// - /// The . - /// - /// - /// The synchronous delegate that updates the projection. - /// Takes the projection, the event and the context as the parameters. - /// - public static void Using( - this IUpdateIfExistsEventActionBuilder eventActionBuilder, - Action projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => - { - projector(projection, anEvent, context); - return SpecializedTasks.FalseTask; - }); - } - - /// - /// Finishes configuring a projection updating handler for projections which do already exist - /// for events of type - /// for projections of type using context of type . - /// - /// - /// The . - /// - /// - /// The synchronous delegate that updates the projection. - /// Takes the projection and the event as the parameters. - /// - public static void Using( - this IUpdateIfExistsEventActionBuilder eventActionBuilder, - Action projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => - { - projector(projection, anEvent); - return SpecializedTasks.FalseTask; - }); - } - - /// - /// Finishes configuring a projection updating handler for projections which do already exist - /// for events of type - /// for projections of type using context of type . - /// - /// - /// The . - /// - /// - /// The asynchronous delegate that updates the projection. - /// Takes the projection and the event as the parameters. - /// - public static void Using( - this IUpdateIfExistsEventActionBuilder eventActionBuilder, - Func projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => projector(projection, anEvent)); - } - - /// - /// Finishes configuring a projection creation or updating handler for events of type - /// for projections of type using context of type . - /// - /// - /// The . - /// - /// - /// The synchronous delegate that initializes the created projection or updates the existing projection. - /// Takes the projection, the event and the context as the parameters. - /// - public static void Using( - this ICreateOrUpdateEventActionBuilder eventActionBuilder, - Action projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => - { - projector(projection, anEvent, context); - return SpecializedTasks.FalseTask; - }); - } - - /// - /// Finishes configuring a projection creation or updating handler for events of type - /// for projections of type using context of type . - /// - /// - /// The . - /// - /// - /// The synchronous delegate that initializes the created projection or updates the existing projection. - /// Takes the projection and the event as the parameters. - /// - public static void Using( - this ICreateOrUpdateEventActionBuilder eventActionBuilder, - Action projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => - { - projector(projection, anEvent); - return SpecializedTasks.FalseTask; - }); - } - - /// - /// Finishes configuring a projection creation or updating handler for events of type - /// for projections of type using context of type . - /// - /// - /// The . - /// - /// - /// The asynchronous delegate that initializes the created projection or updates the existing projection. - /// Takes the projection and the event as the parameters. - /// - public static void Using( - this ICreateOrUpdateEventActionBuilder eventActionBuilder, - Func projector) - { - if (eventActionBuilder == null) - { - throw new ArgumentNullException(nameof(eventActionBuilder)); - } - - if (projector == null) - { - throw new ArgumentNullException(nameof(projector)); - } - - eventActionBuilder.Using((projection, anEvent, context) => projector(projection, anEvent)); } } } \ No newline at end of file diff --git a/Src/LiquidProjections/EventMapBuilderExtensions.cs b/Src/LiquidProjections/EventMapBuilderExtensions.cs new file mode 100644 index 0000000..0858934 --- /dev/null +++ b/Src/LiquidProjections/EventMapBuilderExtensions.cs @@ -0,0 +1,747 @@ +using System; +using System.Threading.Tasks; +using LiquidProjections.MapBuilding; + +namespace LiquidProjections +{ + /// + /// Contains extension methods to map events to handlers in a fluent fashion. + /// + public static class EventMapBuilderExtensions + { + /// + /// Finishes configuring a custom handler for events of type + /// using context of type . + /// + /// The . + /// + /// The synchronous delegate that handles the event. + /// Takes the event and the context as the parameters. + /// + public static void As( + this IAction actionBuilder, + Action action) + { + if (actionBuilder == null) + { + throw new ArgumentNullException(nameof(actionBuilder)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + actionBuilder.As((anEvent, context) => + { + action(anEvent, context); + return SpecializedTasks.ZeroTask; + }); + } + + /// + /// Finishes configuring a custom handler for events of type + /// using context of type . + /// + /// The . + /// + /// The synchronous delegate that handles the event. + /// Takes the event as the parameter. + /// + public static void As( + this IAction actionBuilder, + Action action) + { + if (actionBuilder == null) + { + throw new ArgumentNullException(nameof(actionBuilder)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + actionBuilder.As((anEvent, context) => + { + action(anEvent); + return SpecializedTasks.ZeroTask; + }); + } + + /// + /// Finishes configuring a custom handler for events of type + /// using context of type . + /// + /// The . + /// + /// The asynchronous delegate that handles the event. + /// Takes the event as the parameter. + /// + public static void As( + this IAction actionBuilder, + Func action) + { + if (actionBuilder == null) + { + throw new ArgumentNullException(nameof(actionBuilder)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + actionBuilder.As((anEvent, context) => action(anEvent)); + } + + /// + /// Continues configuring a handler for events of type + /// using context of type . + /// Provides an additional condition that needs to be satisfied in order for the event to be handled by the handler. + /// + /// The . + /// + /// The synchronous delegate that filters the events and + /// should return true for events that will be handled by the handler. + /// Takes the event and the context as the parameters. + /// + /// + /// that allows to continue configuring the handler. + /// + public static IAction When( + this IAction actionBuilder, + Func predicate) + { + if (actionBuilder == null) + { + throw new ArgumentNullException(nameof(actionBuilder)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return actionBuilder.When((anEvent, context) => Task.FromResult(predicate(anEvent, context))); + } + + /// + /// Continues configuring a handler for events of type + /// using context of type . + /// Provides an additional condition that needs to be satisfied in order for the event to be handled by the handler. + /// + /// The . + /// + /// The synchronous delegate that filters the events and + /// should return true for events that will be handled by the handler. + /// Takes the event as the parameter. + /// + /// + /// that allows to continue configuring the handler. + /// + public static IAction When( + this IAction actionBuilder, + Func predicate) + { + if (actionBuilder == null) + { + throw new ArgumentNullException(nameof(actionBuilder)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return actionBuilder.When((anEvent, context) => Task.FromResult(predicate(anEvent))); + } + + /// + /// Continues configuring a handler for events of type + /// using context of type . + /// Provides an additional condition that needs to be satisfied in order for the event to be handled by the handler. + /// + /// The . + /// + /// The asynchronous delegate that filters the events and + /// should return true for events that will be handled by the handler. + /// Takes the event as the parameter. + /// + /// + /// that allows to continue configuring the handler. + /// + public static IAction When( + this IAction actionBuilder, + Func> predicate) + { + if (actionBuilder == null) + { + throw new ArgumentNullException(nameof(actionBuilder)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return actionBuilder.When((anEvent, context) => predicate(anEvent)); + } + + /// + /// Continues configuring a handler for events of type + /// for projections of type with key of type + /// using context of type . + /// Provides an additional condition that needs to be satisfied in order for the event to be handled by the handler. + /// + /// The . + /// + /// The synchronous delegate that filters the events and + /// should return true for events that will be handled by the handler. + /// Takes the event and the context as the parameters. + /// + /// + /// that allows to continue configuring the handler. + /// + public static ICrudAction When( + this ICrudAction crudAction, + Func predicate) + { + if (crudAction == null) + { + throw new ArgumentNullException(nameof(crudAction)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return crudAction.When((anEvent, context) => Task.FromResult(predicate(anEvent, context))); + } + + /// + /// Continues configuring a handler for events of type + /// for projections of type with key of type + /// using context of type . + /// Provides an additional condition that needs to be satisfied in order for the event to be handled by the handler. + /// + /// The . + /// + /// The synchronous delegate that filters the events and + /// should return true for events that will be handled by the handler. + /// Takes the event as the parameter. + /// + /// + /// that allows to continue configuring the handler. + /// + public static ICrudAction When( + this ICrudAction crudAction, + Func predicate) + { + if (crudAction == null) + { + throw new ArgumentNullException(nameof(crudAction)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return crudAction.When((anEvent, context) => Task.FromResult(predicate(anEvent))); + } + + /// + /// Continues configuring a handler for events of type + /// for projections of type with key of type + /// using context of type . + /// Provides an additional condition that needs to be satisfied in order for the event to be handled by the handler. + /// + /// The . + /// + /// The asynchronous delegate that filters the events and + /// should return true for events that will be handled by the handler. + /// Takes the event as the parameter. + /// + /// + /// that allows to continue configuring the handler. + /// + public static ICrudAction When( + this ICrudAction crudAction, + Func> predicate) + { + if (crudAction == null) + { + throw new ArgumentNullException(nameof(crudAction)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return crudAction.When((anEvent, context) => predicate(anEvent)); + } + + /// + /// Finishes configuring a projection creation handler for events of type + /// for projections of type using context of type . + /// + /// The . + /// + /// The synchronous delegate that initializes the created projection. + /// Takes the projection, the event and the context as the parameters. + /// + public static void Using( + this ICreateAction action, + Action projector) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + action.Using((projection, anEvent, context) => + { + projector(projection, anEvent, context); + return SpecializedTasks.FalseTask; + }); + } + + /// + /// Finishes configuring a projection creation handler for events of type + /// for projections of type using context of type . + /// + /// The . + /// + /// The synchronous delegate that initializes the created projection. + /// Takes the projection and the event as the parameters. + /// + public static void Using( + this ICreateAction action, + Action projector) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + action.Using((projection, anEvent, context) => + { + projector(projection, anEvent); + return SpecializedTasks.FalseTask; + }); + } + + /// + /// Finishes configuring a projection creation handler for events of type + /// for projections of type using context of type . + /// + /// The . + /// + /// The asynchronous delegate that initializes the created projection. + /// Takes the projection and the event as the parameters. + /// + public static void Using( + this ICreateAction action, + Func projector) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + action.Using((projection, anEvent, context) => projector(projection, anEvent)); + } + + /// + /// Finishes configuring a projection creation handler for projections which do not exist yet + /// for events of type + /// for projections of type using context of type . + /// + /// + /// The . + /// + /// + /// The synchronous delegate that initializes the created projection. + /// Takes the projection, the event and the context as the parameters. + /// + public static void Using( + this ICreateIfDoesNotExistEventActionBuilder eventActionBuilder, + Action projector) + { + if (eventActionBuilder == null) + { + throw new ArgumentNullException(nameof(eventActionBuilder)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + eventActionBuilder.Using((projection, anEvent, context) => + { + projector(projection, anEvent, context); + return SpecializedTasks.FalseTask; + }); + } + + /// + /// Finishes configuring a projection creation handler for projections which do not exist yet + /// for events of type + /// for projections of type using context of type . + /// + /// + /// The . + /// + /// + /// The synchronous delegate that initializes the created projection. + /// Takes the projection and the event as the parameters. + /// + public static void Using( + this ICreateIfDoesNotExistEventActionBuilder eventActionBuilder, + Action projector) + { + if (eventActionBuilder == null) + { + throw new ArgumentNullException(nameof(eventActionBuilder)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + eventActionBuilder.Using((projection, anEvent, context) => + { + projector(projection, anEvent); + return SpecializedTasks.FalseTask; + }); + } + + /// + /// Finishes configuring a projection creation handler for projections which do not exist yet + /// for events of type + /// for projections of type using context of type . + /// + /// + /// The . + /// + /// + /// The asynchronous delegate that initializes the created projection. + /// Takes the projection and the event as the parameters. + /// + public static void Using( + this ICreateIfDoesNotExistEventActionBuilder eventActionBuilder, + Func projector) + { + if (eventActionBuilder == null) + { + throw new ArgumentNullException(nameof(eventActionBuilder)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + eventActionBuilder.Using((projection, anEvent, context) => projector(projection, anEvent)); + } + + /// + /// Finishes configuring a projection updating handler for events of type + /// for projections of type using context of type . + /// + /// + /// The . + /// + /// + /// The synchronous delegate that updates the projection. + /// Takes the projection, the event and the context as the parameters. + /// + public static void Using( + this IUpdateAction action, + Action projector) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + action.Using((projection, anEvent, context) => + { + projector(projection, anEvent, context); + return SpecializedTasks.FalseTask; + }); + } + + /// + /// Finishes configuring a projection updating handler for events of type + /// for projections of type using context of type . + /// + /// + /// The . + /// + /// + /// The synchronous delegate that updates the projection. + /// Takes the projection and the event as the parameters. + /// + public static void Using( + this IUpdateAction action, + Action projector) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + action.Using((projection, anEvent, context) => + { + projector(projection, anEvent); + return SpecializedTasks.FalseTask; + }); + } + + /// + /// Finishes configuring a projection updating handler for events of type + /// for projections of type using context of type . + /// + /// + /// The . + /// + /// + /// The asynchronous delegate that updates the projection. + /// Takes the projection and the event as the parameters. + /// + public static void Using( + this IUpdateAction action, + Func projector) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + action.Using((projection, anEvent, context) => projector(projection, anEvent)); + } + + /// + /// Finishes configuring a projection updating handler for projections which do already exist + /// for events of type + /// for projections of type using context of type . + /// + /// + /// The . + /// + /// + /// The synchronous delegate that updates the projection. + /// Takes the projection, the event and the context as the parameters. + /// + public static void Using( + this IUpdateIfExistsEventActionBuilder eventActionBuilder, + Action projector) + { + if (eventActionBuilder == null) + { + throw new ArgumentNullException(nameof(eventActionBuilder)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + eventActionBuilder.Using((projection, anEvent, context) => + { + projector(projection, anEvent, context); + return SpecializedTasks.FalseTask; + }); + } + + /// + /// Finishes configuring a projection updating handler for projections which do already exist + /// for events of type + /// for projections of type using context of type . + /// + /// + /// The . + /// + /// + /// The synchronous delegate that updates the projection. + /// Takes the projection and the event as the parameters. + /// + public static void Using( + this IUpdateIfExistsEventActionBuilder eventActionBuilder, + Action projector) + { + if (eventActionBuilder == null) + { + throw new ArgumentNullException(nameof(eventActionBuilder)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + eventActionBuilder.Using((projection, anEvent, context) => + { + projector(projection, anEvent); + return SpecializedTasks.FalseTask; + }); + } + + /// + /// Finishes configuring a projection updating handler for projections which do already exist + /// for events of type + /// for projections of type using context of type . + /// + /// + /// The . + /// + /// + /// The asynchronous delegate that updates the projection. + /// Takes the projection and the event as the parameters. + /// + public static void Using( + this IUpdateIfExistsEventActionBuilder eventActionBuilder, + Func projector) + { + if (eventActionBuilder == null) + { + throw new ArgumentNullException(nameof(eventActionBuilder)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + eventActionBuilder.Using((projection, anEvent, context) => projector(projection, anEvent)); + } + + /// + /// Finishes configuring a projection creation or updating handler for events of type + /// for projections of type using context of type . + /// + /// + /// The . + /// + /// + /// The synchronous delegate that initializes the created projection or updates the existing projection. + /// Takes the projection, the event and the context as the parameters. + /// + public static void Using( + this ICreateOrUpdateEventActionBuilder eventActionBuilder, + Action projector) + { + if (eventActionBuilder == null) + { + throw new ArgumentNullException(nameof(eventActionBuilder)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + eventActionBuilder.Using((projection, anEvent, context) => + { + projector(projection, anEvent, context); + return SpecializedTasks.FalseTask; + }); + } + + /// + /// Finishes configuring a projection creation or updating handler for events of type + /// for projections of type using context of type . + /// + /// + /// The . + /// + /// + /// The synchronous delegate that initializes the created projection or updates the existing projection. + /// Takes the projection and the event as the parameters. + /// + public static void Using( + this ICreateOrUpdateEventActionBuilder eventActionBuilder, + Action projector) + { + if (eventActionBuilder == null) + { + throw new ArgumentNullException(nameof(eventActionBuilder)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + eventActionBuilder.Using((projection, anEvent, context) => + { + projector(projection, anEvent); + return SpecializedTasks.FalseTask; + }); + } + + /// + /// Finishes configuring a projection creation or updating handler for events of type + /// for projections of type using context of type . + /// + /// + /// The . + /// + /// + /// The asynchronous delegate that initializes the created projection or updates the existing projection. + /// Takes the projection and the event as the parameters. + /// + public static void Using( + this ICreateOrUpdateEventActionBuilder eventActionBuilder, + Func projector) + { + if (eventActionBuilder == null) + { + throw new ArgumentNullException(nameof(eventActionBuilder)); + } + + if (projector == null) + { + throw new ArgumentNullException(nameof(projector)); + } + + eventActionBuilder.Using((projection, anEvent, context) => projector(projection, anEvent)); + } + } +} \ No newline at end of file diff --git a/Src/LiquidProjections/IEventMap.cs b/Src/LiquidProjections/IEventMap.cs index 43ef1f3..e7e9a54 100644 --- a/Src/LiquidProjections/IEventMap.cs +++ b/Src/LiquidProjections/IEventMap.cs @@ -14,7 +14,8 @@ public interface IEventMap /// Handles asynchronously. /// /// - /// Returns a value indicating whether the event was handled by any event. + /// Returns a value indicating whether the map was configured to handle the event, + /// taking into account any local or global filters. /// Task Handle(object anEvent, TContext context); } diff --git a/Src/LiquidProjections/IEventMapBuilder.cs b/Src/LiquidProjections/IEventMapBuilder.cs index 0406c93..364865d 100644 --- a/Src/LiquidProjections/IEventMapBuilder.cs +++ b/Src/LiquidProjections/IEventMapBuilder.cs @@ -11,12 +11,7 @@ public interface IEventMapBuilder /// /// Builds the resulting event map. Can only be called once. /// - IEventMap Build(); - - /// - /// Configures the event map to handle custom actions via the provided delegate . - /// - void HandleCustomActionsAs(CustomHandler handler); + IEventMap Build(ProjectorMap projector); } /// @@ -33,154 +28,102 @@ public interface IEventMapBuilder /// Type of the projections. /// Type of the projection keys. /// Type of the context. - public interface IEventMapBuilder : IEventMapBuilder + public interface IEventMapBuilder { /// - /// Configures the event map to handle projection creation and updating - /// via the provided delegate . - /// - void HandleProjectionModificationsAs(ProjectionModificationHandler handler); - - /// - /// Configures the event map to handle projection deletion - /// via the provided delegate . + /// Builds the resulting event map. Can only be called once. /// - void HandleProjectionDeletionsAs(ProjectionDeletionHandler handler); + IEventMap Build(ProjectorMap projector); } /// - /// Handles projection creation and updating asynchronously using context . - /// - /// Type of the projections. - /// Type of the projection keys. - /// Type of the context. - /// Key of the projection. - /// The context. - /// - /// The delegate that must be invoked to handle the event for the provided projection - /// and modify the projection accordingly. - /// - /// Additional options . - public delegate Task ProjectionModificationHandler( - TKey key, - TContext context, - Func projector, - ProjectionModificationOptions options); - - /// - /// Provides additional options for . + /// Defines the contract for a projector that can handle the CRUD operations needed to handle + /// events as mapped through the . /// - public class ProjectionModificationOptions + public class ProjectorMap { - /// Behavior when the projection does not exists. - /// Behavior when the projection already exists. - public ProjectionModificationOptions( - MissingProjectionModificationBehavior missingProjectionBehavior, - ExistingProjectionModificationBehavior existingProjectionBehavior) - { - MissingProjectionBehavior = missingProjectionBehavior; - ExistingProjectionBehavior = existingProjectionBehavior; - } - - /// - /// Behavior when the projection does not exists. - /// - public MissingProjectionModificationBehavior MissingProjectionBehavior { get; } - - /// - /// Behavior when the projection already exists. - /// - public ExistingProjectionModificationBehavior ExistingProjectionBehavior { get; } + public CustomHandler Custom { get; set; } = (context, projector) + => throw new NotSupportedException("No handler has been set-up for custom actions."); } /// - /// Specifies behavior for - /// when the projection does not exists. + /// Defines the contract for a projector that can handle the CRUD operations needed to handle + /// events as mapped through the /// - public enum MissingProjectionModificationBehavior + public class ProjectorMap : ProjectorMap { - /// - /// Creates a new projection when the projection does not exists. - /// - Create, + public CreationHandler Create { get; set; } = (key, context, projector, shouldOverwrite) => + throw new NotSupportedException("No handler has been set-up for creations."); - /// - /// Does nothing when the projection does not exists. - /// - Ignore, + public UpdateHandler Update { get; set; } = (key, context, projector, createIfMissing) => + throw new NotSupportedException("No handler has been set-up for updates."); - /// - /// Throws an exception when the projection does not exists. - /// - Throw + public DeletionHandler Delete { get; set; } = (key, context) => + throw new NotSupportedException("No handler has been set-up for deletions."); } /// - /// Specifies behavior for - /// when the projection already exists. + /// Defines a handler for creating projections based on an event. /// - public enum ExistingProjectionModificationBehavior - { - /// - /// Updates the projection when it already exists. - /// - Update, - - /// - /// Does nothing when the projection already exists. - /// - Ignore, - - /// - /// Throws an exception when the projection already exists. - /// - Throw - } - - /// - /// Handles projection deletion asynchronously using context . - /// - /// The type of the projection keys. - /// The type of the context. - /// The key of the projection. - /// The context. - /// Additional options . - public delegate Task ProjectionDeletionHandler( + /// + /// The key of projection as extracted from the event during its mapping configuration. + /// + /// + /// An object providing information about the current event and any projector-specific metadata. + /// + /// + /// The delegate that must be invoked to handle the event for the provided projection + /// and modify the projection accordingly. + /// + /// + /// Should be called by the handler to determine how to handle existing projections by the same key. + /// If it returns true then the handler should use the to update the + /// state of the existing projection, or false to ignore the call. Can throw an exception if that was + /// requested through the event map. + /// + public delegate Task CreationHandler( TKey key, TContext context, - ProjectionDeletionOptions options); + Func projector, + Func shouldOverwite); /// - /// Provides additional options for . + /// Defines a handler for updating projections based on an event. /// - public class ProjectionDeletionOptions - { - /// Behavior when the projection does not exists. - public ProjectionDeletionOptions(MissingProjectionDeletionBehavior missingProjectionBehavior) - { - MissingProjectionBehavior = missingProjectionBehavior; - } - - /// - /// Behavior when the projection does not exists. - /// - public MissingProjectionDeletionBehavior MissingProjectionBehavior { get; } - } + /// + /// The key of projection as extracted from the event during its mapping configuration. + /// + /// + /// An object providing information about the current event and any projector-specific metadata. + /// + /// + /// The delegate that must be invoked to handle the event for the provided projection + /// and modify the projection accordingly. + /// + /// + /// Should be called by the handler to determine whether it should create a missing projection. Depending on + /// how the event was mapped, it can throw an exception that should not be caught by the projector. + /// + public delegate Task UpdateHandler( + TKey key, + TContext context, + Func projector, + Func createIfMissing); - /// - /// Specifies behavior for - /// when the projection does not exists. + /// + /// Defines a handler for deleting projections based on an event. /// - public enum MissingProjectionDeletionBehavior - { - /// - /// Does nothing when the projection does not exists. - /// - Ignore, - - /// - /// Throws an exception when the projection does not exists. - /// - Throw - } -} \ No newline at end of file + /// + /// The key of projection as extracted from the event during its mapping configuration. + /// + /// + /// An object providing information about the current event and any projector-specific metadata. + /// + /// + /// Returns a value indicating if deleting the projection succeeded. Should return false if the projection did not exist, + /// + public delegate Task DeletionHandler( + TKey key, + TContext context); +} + \ No newline at end of file diff --git a/Src/LiquidProjections/MapBuilding/IEventMappingBuilder.cs b/Src/LiquidProjections/MapBuilding/IAction.cs similarity index 74% rename from Src/LiquidProjections/MapBuilding/IEventMappingBuilder.cs rename to Src/LiquidProjections/MapBuilding/IAction.cs index dc15576..49260d3 100644 --- a/Src/LiquidProjections/MapBuilding/IEventMappingBuilder.cs +++ b/Src/LiquidProjections/MapBuilding/IAction.cs @@ -7,7 +7,7 @@ namespace LiquidProjections.MapBuilding /// Allows to configure event map how to handle custom actions for events of type /// using context . /// - public interface IEventMappingBuilder + public interface IAction { /// /// Finishes configuring a custom handler for events of type . @@ -28,9 +28,9 @@ public interface IEventMappingBuilder /// Takes the event and the context as the parameters. /// /// - /// that allows to continue configuring the handler. + /// that allows to continue configuring the handler. /// - IEventMappingBuilder When(Func> predicate); + IAction When(Func> predicate); } /// @@ -39,7 +39,7 @@ public interface IEventMappingBuilder /// with key of type /// using context . /// - public interface IEventMappingBuilder : IEventMappingBuilder + public interface ICrudAction : IAction { /// /// Continues configuring a handler for events of type . @@ -48,9 +48,9 @@ public interface IEventMappingBuilder : /// /// The delegate that determines the projection key for the event. /// - /// that allows to continue configuring the handler. + /// that allows to continue configuring the handler. /// - ICreateEventActionBuilder AsCreateOf(Func getKey); + ICreateAction AsCreateOf(Func getKey); /// /// Continues configuring a handler for events of type . @@ -62,8 +62,8 @@ public interface IEventMappingBuilder : /// /// that allows to continue configuring the handler. /// - ICreateIfDoesNotExistEventActionBuilder AsCreateIfDoesNotExistOf( - Func getKey); + [Obsolete("Use AsCreateOf().IgnoringDuplicates() instead")] + ICreateAction AsCreateIfDoesNotExistOf(Func getKey); /// /// Continues configuring a handler for events of type . @@ -72,9 +72,9 @@ ICreateIfDoesNotExistEventActionBuilder AsCreateI /// /// The delegate that determines the projection key for the event. /// - /// that allows to continue configuring the handler. + /// that allows to continue configuring the handler. /// - IUpdateEventActionBuilder AsUpdateOf(Func getKey); + IUpdateAction AsUpdateOf(Func getKey); /// /// Continues configuring a handler for events of type . @@ -83,9 +83,10 @@ ICreateIfDoesNotExistEventActionBuilder AsCreateI /// /// The delegate that determines the projection key for the event. /// - /// that allows to continue configuring the handler. + /// that allows to continue configuring the handler. /// - IUpdateIfExistsEventActionBuilder AsUpdateIfExistsOf(Func getKey); + [Obsolete("Use AsUpdateOf().IgnoringMissing() instead")] + IUpdateAction AsUpdateIfExistsOf(Func getKey); /// /// Continues configuring a handler for events of type . @@ -97,7 +98,8 @@ ICreateIfDoesNotExistEventActionBuilder AsCreateI /// /// that allows to continue configuring the handler. /// - ICreateOrUpdateEventActionBuilder AsCreateOrUpdateOf(Func getKey); + [Obsolete("Use AsCreateOf().OverwritingDuplicates() instead")] + ICreateAction AsCreateOrUpdateOf(Func getKey); /// /// Finishes configuring a handler for events of type . @@ -105,7 +107,7 @@ ICreateIfDoesNotExistEventActionBuilder AsCreateI /// An exception will be thrown if a projection with such key does not exist. /// /// The delegate that determines the projection key for the event. - void AsDeleteOf(Func getKey); + IDeleteAction AsDeleteOf(Func getKey); /// /// Finishes configuring a handler for events of type . @@ -113,7 +115,8 @@ ICreateIfDoesNotExistEventActionBuilder AsCreateI /// The event will not be handled if the projection with such key does not exist. /// /// The delegate that determines the projection key for the event. - void AsDeleteIfExistsOf(Func getKey); + [Obsolete("Use AsDeleteOf().IgnoringMissing() instead")] + IDeleteAction AsDeleteIfExistsOf(Func getKey); /// /// Continues configuring a handler for events of type . @@ -125,8 +128,8 @@ ICreateIfDoesNotExistEventActionBuilder AsCreateI /// Takes the event and the context as the parameters. /// /// - /// that allows to continue configuring the handler. + /// that allows to continue configuring the handler. /// - new IEventMappingBuilder When(Func> predicate); + new ICrudAction When(Func> predicate); } } diff --git a/Src/LiquidProjections/MapBuilding/ICreateAction.cs b/Src/LiquidProjections/MapBuilding/ICreateAction.cs new file mode 100644 index 0000000..76389cd --- /dev/null +++ b/Src/LiquidProjections/MapBuilding/ICreateAction.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; + +namespace LiquidProjections.MapBuilding +{ + /// + /// Allows to configure event map how to handle projection creation + /// for events of type and projections of type + /// using context . + /// + public interface ICreateAction + { + /// + /// Finishes configuring a projection creation handler. + /// + /// + /// The asynchronous delegate that initializes the created projection. + /// Takes the projection, the event and the context as the parameters. + /// + ICreateAction Using(Func projector); + + /// + /// Tells the implementing projector that duplicates should be ignored. + /// + ICreateAction IgnoringDuplicates(); + + /// + /// Configures the action to overwrite any duplicates. + /// + ICreateAction OverwritingDuplicates(); + + /// + /// Allows custom handling of duplicates found while trying to create a new projection. + /// + /// + /// A predicate that allows the handler to decide whether or not to overwrite the duplicate projection. + /// + ICreateAction HandlingDuplicatesUsing(Func shouldOverwrite); + } +} \ No newline at end of file diff --git a/Src/LiquidProjections/MapBuilding/ICreateEventActionBuilder.cs b/Src/LiquidProjections/MapBuilding/ICreateEventActionBuilder.cs deleted file mode 100644 index a9bc064..0000000 --- a/Src/LiquidProjections/MapBuilding/ICreateEventActionBuilder.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace LiquidProjections.MapBuilding -{ - /// - /// Allows to configure event map how to handle projection creation - /// for events of type and projections of type - /// using context . - /// - public interface ICreateEventActionBuilder - { - /// - /// Finishes configuring a projection creation handler. - /// - /// - /// The asynchronous delegate that initializes the created projection. - /// Takes the projection, the event and the context as the parameters. - /// - void Using(Func projector); - } -} \ No newline at end of file diff --git a/Src/LiquidProjections/MapBuilding/IDeleteAction.cs b/Src/LiquidProjections/MapBuilding/IDeleteAction.cs new file mode 100644 index 0000000..cd5a1ee --- /dev/null +++ b/Src/LiquidProjections/MapBuilding/IDeleteAction.cs @@ -0,0 +1,22 @@ +using System; + +namespace LiquidProjections.MapBuilding +{ + public interface IDeleteAction + { + /// + /// Causes the mapping to throw if deleting the projection failed or the projection did not exist anymore. + /// + IDeleteAction ThrowingIfMissing(); + + /// + /// Configures the mapping to ignore any failed attempts to delete a projection. + /// + IDeleteAction IgnoringMisses(); + + /// + /// Allows the consumer to handle missing projections in a custom way. + /// + IDeleteAction HandlingMissesUsing(Action action); + } +} \ No newline at end of file diff --git a/Src/LiquidProjections/MapBuilding/IUpdateAction.cs b/Src/LiquidProjections/MapBuilding/IUpdateAction.cs new file mode 100644 index 0000000..c3ab240 --- /dev/null +++ b/Src/LiquidProjections/MapBuilding/IUpdateAction.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; + +namespace LiquidProjections.MapBuilding +{ + /// + /// Allows to configure event map how to handle projection updating + /// for events of type and projections of type + /// using context . + /// + public interface IUpdateAction + { + /// + /// Finishes configuring a projection updating handler. + /// + /// + /// The asynchronous delegate that updates the projection. + /// Takes the projection, the event and the context as the parameters. + /// + IUpdateAction Using(Func updateAction); + + /// + /// Configures the update action to throw a if the projection was missing. + /// + IUpdateAction ThrowingIfMissing(); + + /// + /// Configures the update action to ignore a missing projection and continue with the next event. + /// + IUpdateAction IgnoringMisses(); + + /// + /// Configures the update action to create the projection if it is missing. + /// + IUpdateAction CreatingIfMissing(); + + /// + /// Allows some custom code to be executed when the a projection was missing and to optionally tell the projector + /// to create it. + /// + IUpdateAction HandlingMissesUsing(Func action); + } +} \ No newline at end of file diff --git a/Src/LiquidProjections/MapBuilding/IUpdateEventActionBuilder.cs b/Src/LiquidProjections/MapBuilding/IUpdateEventActionBuilder.cs deleted file mode 100644 index 602f625..0000000 --- a/Src/LiquidProjections/MapBuilding/IUpdateEventActionBuilder.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace LiquidProjections.MapBuilding -{ - /// - /// Allows to configure event map how to handle projection updating - /// for events of type and projections of type - /// using context . - /// - public interface IUpdateEventActionBuilder - { - /// - /// Finishes configuring a projection updating handler. - /// - /// - /// The asynchronous delegate that updates the projection. - /// Takes the projection, the event and the context as the parameters. - /// - void Using(Func projector); - } -} \ No newline at end of file diff --git a/Src/LiquidProjections/Projector.cs b/Src/LiquidProjections/Projector.cs index 6bb58a6..becde49 100644 --- a/Src/LiquidProjections/Projector.cs +++ b/Src/LiquidProjections/Projector.cs @@ -24,8 +24,10 @@ private static IEventMap BuildMap(IEventMapBuilder + { + Custom = (context, projector) => projector() + }); } public Projector(IEventMap map, IEnumerable children = null) @@ -52,11 +54,6 @@ public ShouldRetry ShouldRetry } } - private static void SetupHandlers(IEventMapBuilder eventMapBuilder) - { - eventMapBuilder.HandleCustomActionsAs((context, projector) => projector()); - } - /// /// Instructs the projector to handle a collection of ordered transactions. /// diff --git a/Tests/LiquidProjections.Specs/EventMapSpecs.cs b/Tests/LiquidProjections.Specs/EventMapSpecs.cs index 5bf2b92..680815a 100644 --- a/Tests/LiquidProjections.Specs/EventMapSpecs.cs +++ b/Tests/LiquidProjections.Specs/EventMapSpecs.cs @@ -8,13 +8,12 @@ namespace LiquidProjections.Specs { namespace EventMapSpecs { - public class When_an_event_is_mapped_as_a_create : GivenWhenThen + public class When_event_should_create_a_new_projection : GivenWhenThen { private ProductCatalogEntry projection; private IEventMap map; - private ProjectionModificationOptions options; - public When_an_event_is_mapped_as_a_create() + public When_event_should_create_a_new_projection() { Given(() => { @@ -26,34 +25,23 @@ public When_an_event_is_mapped_as_a_create() return Task.FromResult(0); }); - mapBuilder.HandleProjectionModificationsAs(async (key, context, projector, options) => + map = mapBuilder.Build(new ProjectorMap { - projection = new ProductCatalogEntry + Create = async (key, context, projector, shouldOverwrite) => { - Id = key, - }; + projection = new ProductCatalogEntry + { + Id = key, + }; - this.options = options; - await projector(projection); + await projector(projection); + } }); - - mapBuilder.HandleProjectionDeletionsAs((key, context, options) => - { - throw new InvalidOperationException("Deletion should not be called."); - }); - - mapBuilder.HandleCustomActionsAs((context, projector) => - { - throw new InvalidOperationException("Custom action should not be called."); - }); - - map = mapBuilder.Build(); }); When(async () => { - await map.Handle( - new ProductAddedToCatalogEvent + await map.Handle(new ProductAddedToCatalogEvent { Category = "Hybrids", ProductKey = "c350E" @@ -72,27 +60,15 @@ public void It_should_properly_pass_the_mapping_to_the_creating_handler() Deleted = false }); } - - [Fact] - public void It_should_create_projection_if_it_does_not_exist() - { - options.MissingProjectionBehavior.Should().Be(MissingProjectionModificationBehavior.Create); - } - - [Fact] - public void It_should_throw_if_the_projection_already_exists() - { - options.ExistingProjectionBehavior.Should().Be(ExistingProjectionModificationBehavior.Throw); - } } - public class When_an_event_is_mapped_as_a_create_if_does_not_exist : GivenWhenThen + public class When_a_creating_event_must_ignore_an_existing_projection : GivenWhenThen { - private ProductCatalogEntry projection; + private ProductCatalogEntry existingProjection; + private IEventMap map; - private ProjectionModificationOptions options; - public When_an_event_is_mapped_as_a_create_if_does_not_exist() + public When_a_creating_event_must_ignore_an_existing_projection() { Given(() => { @@ -100,35 +76,29 @@ public When_an_event_is_mapped_as_a_create_if_does_not_exist() mapBuilder .Map() - .AsCreateIfDoesNotExistOf(e => e.ProductKey) + .AsCreateOf(e => e.ProductKey).IgnoringDuplicates() .Using((p, e, ctx) => { p.Category = e.Category; return Task.FromResult(0); }); - - mapBuilder.HandleProjectionModificationsAs(async (key, context, projector, options) => + + existingProjection = new ProductCatalogEntry { - projection = new ProductCatalogEntry - { - Id = key, - }; - - this.options = options; - await projector(projection); - }); - - mapBuilder.HandleProjectionDeletionsAs((key, context, options) => - { - throw new InvalidOperationException("Deletion should not be called."); - }); + Id = "c350E", + Category = "Fosile", + }; - mapBuilder.HandleCustomActionsAs((context, projector) => + map = mapBuilder.Build(new ProjectorMap { - throw new InvalidOperationException("Custom action should not be called."); + Create = async (key, context, projector, shouldOverwrite) => + { + if (shouldOverwrite(existingProjection)) + { + await projector(existingProjection); + } + } }); - - map = mapBuilder.Build(); }); When(async () => @@ -144,36 +114,85 @@ await map.Handle( } [Fact] - public void It_should_properly_pass_the_mapping_to_the_creating_handler() + public void It_should_leave_the_existing_projection_untouched() { - projection.Should().BeEquivalentTo(new + existingProjection.Should().BeEquivalentTo(new { Id = "c350E", - Category = "Hybrids", - Deleted = false + Category = "Fosile" }); } + } - [Fact] - public void It_should_create_projection_if_it_does_not_exist() + public class When_a_creating_event_should_overwrite_an_existing_projection : GivenWhenThen + { + private ProductCatalogEntry existingProjection; + private IEventMap map; + + public When_a_creating_event_should_overwrite_an_existing_projection() { - options.MissingProjectionBehavior.Should().Be(MissingProjectionModificationBehavior.Create); + Given(() => + { + var mapBuilder = new EventMapBuilder(); + + mapBuilder + .Map() + .AsCreateOf(e => e.ProductKey) + .OverwritingDuplicates() + .Using((p, e, ctx) => + { + p.Category = e.Category; + return Task.FromResult(0); + }); + + existingProjection = new ProductCatalogEntry + { + Id = "c350E", + Category = "OldCategory", + }; + + map = mapBuilder.Build(new ProjectorMap + { + Create = async (key, context, projector, shouldOverwrite) => + { + if (shouldOverwrite(existingProjection)) + { + await projector(existingProjection); + } + } + }); + }); + + When(async () => + { + await map.Handle( + new ProductAddedToCatalogEvent + { + Category = "NewCategory", + ProductKey = "c350E" + }, + new ProjectionContext()); + }); } [Fact] - public void It_should_do_nothing_if_the_projection_already_exists() + public void It_should_tell_the_creating_handler_to_overwrite_the_existing_proejction() { - options.ExistingProjectionBehavior.Should().Be(ExistingProjectionModificationBehavior.Ignore); + existingProjection.Should().BeEquivalentTo(new + { + Id = "c350E", + Category = "NewCategory", + }); } } - public class When_an_event_is_mapped_as_a_create_or_update : GivenWhenThen + public class When_a_creating_event_should_allow_manual_handling_of_duplicates : GivenWhenThen { - private ProductCatalogEntry projection; + private ProductCatalogEntry existingProjection; private IEventMap map; - private ProjectionModificationOptions options; + private ProductCatalogEntry duplicateProjection; - public When_an_event_is_mapped_as_a_create_or_update() + public When_a_creating_event_should_allow_manual_handling_of_duplicates() { Given(() => { @@ -181,43 +200,41 @@ public When_an_event_is_mapped_as_a_create_or_update() mapBuilder .Map() - .AsCreateOrUpdateOf(e => e.ProductKey) + .AsCreateOf(e => e.ProductKey) + .HandlingDuplicatesUsing((duplicate, @event, context) => + { + duplicateProjection = existingProjection; + return true; + }) .Using((p, e, ctx) => { p.Category = e.Category; return Task.FromResult(0); }); - mapBuilder.HandleProjectionModificationsAs(async (key, context, projector, options) => + existingProjection = new ProductCatalogEntry { - projection = new ProductCatalogEntry - { - Id = key, - }; - - this.options = options; - await projector(projection); - }); - - mapBuilder.HandleProjectionDeletionsAs((key, context, options) => - { - throw new InvalidOperationException("Deletion should not be called."); - }); + Id = "c350E", + Category = "OldCategory", + }; - mapBuilder.HandleCustomActionsAs((context, projector) => + map = mapBuilder.Build(new ProjectorMap { - throw new InvalidOperationException("Custom action should not be called."); + Create = async (key, context, projector, shouldOverwrite) => + { + if (shouldOverwrite(existingProjection)) + { + await projector(existingProjection); + } + } }); - - map = mapBuilder.Build(); }); When(async () => { - await map.Handle( - new ProductAddedToCatalogEvent + await map.Handle(new ProductAddedToCatalogEvent { - Category = "Hybrids", + Category = "NewCategory", ProductKey = "c350E" }, new ProjectionContext()); @@ -225,36 +242,28 @@ await map.Handle( } [Fact] - public void It_should_properly_pass_the_mapping_to_the_creating_handler() + public void It_should_honor_the_custom_handlings_wish_to_overwrite() { - projection.Should().BeEquivalentTo(new + existingProjection.Should().BeEquivalentTo(new { Id = "c350E", - Category = "Hybrids", - Deleted = false + Category = "NewCategory", }); } [Fact] - public void It_should_create_projection_if_it_does_not_exist() + public void It_should_pass_the_duplicate_to_the_handler() { - options.MissingProjectionBehavior.Should().Be(MissingProjectionModificationBehavior.Create); - } - - [Fact] - public void It_should_update_the_projection_if_it_already_exists() - { - options.ExistingProjectionBehavior.Should().Be(ExistingProjectionModificationBehavior.Update); + duplicateProjection.Should().BeSameAs(existingProjection); } } - public class When_an_event_is_mapped_as_an_update : GivenWhenThen + public class When_an_updating_event_should_throw_on_misses : GivenWhenThen { - private ProductCatalogEntry projection; + private ProductCatalogEntry existingProjection; private IEventMap map; - private ProjectionModificationOptions options; - public When_an_event_is_mapped_as_an_update() + public When_an_updating_event_should_throw_on_misses() { Given(() => { @@ -266,74 +275,95 @@ public When_an_event_is_mapped_as_an_update() return Task.FromResult(0); }); - mapBuilder.HandleProjectionModificationsAs(async (key, context, projector, options) => + existingProjection = new ProductCatalogEntry { - projection = new ProductCatalogEntry - { - Id = key, - }; - - this.options = options; - await projector(projection); - }); - - mapBuilder.HandleProjectionDeletionsAs((key, context, options) => - { - throw new InvalidOperationException("Deletion should not be called."); - }); + Id = "c350E", + Category = "OldCategory", + }; - mapBuilder.HandleCustomActionsAs((context, projector) => + map = mapBuilder.Build(new ProjectorMap { - throw new InvalidOperationException("Custom action should not be called."); + Update = async (key, context, projector, createIfMissing) => + { + if (createIfMissing()) + { + await projector(existingProjection); + } + } }); - - map = mapBuilder.Build(); }); - When(async () => + WhenLater(async () => { await map.Handle( new ProductAddedToCatalogEvent { - Category = "Hybrids", - ProductKey = "c350E" + ProductKey = "c350E", + Category = "NewCategory" }, new ProjectionContext()); }); } [Fact] - public void It_should_properly_pass_the_mapping_to_the_updating_handler() + public void It_should_throw_without_affecting_the_projection() { - projection.Should().BeEquivalentTo(new - { - Id = "c350E", - Category = "Hybrids", - Deleted = false - }); + WhenAction.Should().Throw(); + + existingProjection.Category.Should().Be("OldCategory"); } + } + public class When_an_updating_event_should_ignore_missing_projections : GivenWhenThen + { + private IEventMap map; - [Fact] - public void It_should_throw_if_the_projection_does_not_exist() + public When_an_updating_event_should_ignore_missing_projections() { - options.MissingProjectionBehavior.Should().Be(MissingProjectionModificationBehavior.Throw); + Given(() => + { + var mapBuilder = new EventMapBuilder(); + + mapBuilder + .Map() + .AsUpdateOf(e => e.ProductKey) + .IgnoringMisses() + .Using((p, e, ctx) => + { + p.Category = e.Category; + return Task.FromResult(0); + }); + + map = mapBuilder.Build(new ProjectorMap + { + Update = (key, context, projector, createIfMissing) => Task.FromResult(false) + }); + }); + + WhenLater(async () => + { + await map.Handle( + new ProductAddedToCatalogEvent + { + Category = "Hybrids", + ProductKey = "c350E" + }, + new ProjectionContext()); + }); } [Fact] - public void It_should_update_the_projection_if_it_exists() + public void It_should_not_throw() { - options.ExistingProjectionBehavior.Should().Be(ExistingProjectionModificationBehavior.Update); + WhenAction.Should().NotThrow(); } } - - public class When_an_event_is_mapped_as_an_update_if_exists : GivenWhenThen + public class When_an_updating_event_should_create_a_missing_projection : GivenWhenThen { - private ProductCatalogEntry projection; private IEventMap map; - private ProjectionModificationOptions options; + private bool shouldCreate; - public When_an_event_is_mapped_as_an_update_if_exists() + public When_an_updating_event_should_create_a_missing_projection() { Given(() => { @@ -341,35 +371,23 @@ public When_an_event_is_mapped_as_an_update_if_exists() mapBuilder .Map() - .AsUpdateIfExistsOf(e => e.ProductKey) + .AsUpdateOf(e => e.ProductKey) + .CreatingIfMissing() .Using((p, e, ctx) => { p.Category = e.Category; return Task.FromResult(0); }); - mapBuilder.HandleProjectionModificationsAs(async (key, context, projector, options) => + map = mapBuilder.Build(new ProjectorMap { - projection = new ProductCatalogEntry + Update = (key, context, projector, createIfMissing) => { - Id = key, - }; - - this.options = options; - await projector(projection); - }); - - mapBuilder.HandleProjectionDeletionsAs((key, context, options) => - { - throw new InvalidOperationException("Deletion should not be called."); - }); - - mapBuilder.HandleCustomActionsAs((context, projector) => - { - throw new InvalidOperationException("Custom action should not be called."); + shouldCreate = true; + + return Task.FromResult(0); + } }); - - map = mapBuilder.Build(); }); When(async () => @@ -385,66 +403,89 @@ await map.Handle( } [Fact] - public void It_should_properly_pass_the_mapping_to_the_updating_handler() + public void It_should_not_throw() { - projection.Should().BeEquivalentTo(new - { - Id = "c350E", - Category = "Hybrids", - Deleted = false - }); + shouldCreate.Should().BeTrue("because that's how the map was configured"); } + } + public class When_an_updating_event_should_handle_misses_manually : GivenWhenThen + { + private IEventMap map; + private string missedKey; - [Fact] - public void It_should_do_nothing_if_the_projection_does_not_exist() + public When_an_updating_event_should_handle_misses_manually() { - options.MissingProjectionBehavior.Should().Be(MissingProjectionModificationBehavior.Ignore); + Given(() => + { + var mapBuilder = new EventMapBuilder(); + + mapBuilder + .Map() + .AsUpdateOf(e => e.ProductKey) + .HandlingMissesUsing((key, context) => + { + missedKey = key; + return true; + }) + .Using((p, e, ctx) => + { + p.Category = e.Category; + return Task.FromResult(0); + }); + + map = mapBuilder.Build(new ProjectorMap + { + Update = (key, context, projector, createIfMissing) => + { + createIfMissing(); + return Task.FromResult(0); + } + }); + }); + + When(async () => + { + await map.Handle( + new ProductAddedToCatalogEvent + { + Category = "Hybrids", + ProductKey = "c350E" + }, + new ProjectionContext()); + }); } [Fact] - public void It_should_update_the_projection_if_it_exists() + public void It_should_give_the_custom_handler_a_chance_to_handle_it() { - options.ExistingProjectionBehavior.Should().Be(ExistingProjectionModificationBehavior.Update); + missedKey.Should().Be("c350E"); } } public class When_an_event_is_mapped_as_a_delete : GivenWhenThen { - private ProductCatalogEntry projection; private IEventMap map; - private ProjectionDeletionOptions options; + private bool isDeleted; public When_an_event_is_mapped_as_a_delete() { Given(() => { var mapBuilder = new EventMapBuilder(); - mapBuilder.Map().AsDeleteOf(e => e.ProductKey); + mapBuilder + .Map() + .AsDeleteOf(e => e.ProductKey) + .ThrowingIfMissing(); - mapBuilder.HandleProjectionDeletionsAs((key, context, options) => + map = mapBuilder.Build(new ProjectorMap { - projection = new ProductCatalogEntry + Delete = (key, context) => { - Id = key, - Deleted = true - }; - - this.options = options; - return Task.FromResult(0); - }); - - mapBuilder.HandleProjectionModificationsAs((key, context, projector, options) => - { - throw new InvalidOperationException("Modification should not be called."); - }); - - mapBuilder.HandleCustomActionsAs((context, projector) => - { - throw new InvalidOperationException("Custom action should not be called."); + isDeleted = true; + return Task.FromResult(true); + } }); - - map = mapBuilder.Build(); }); When(async () => @@ -461,60 +502,73 @@ await map.Handle( [Fact] public void It_should_properly_pass_the_mapping_to_the_deleting_handler() { - projection.Should().BeEquivalentTo(new + isDeleted.Should().BeTrue(); + } + } + + public class When_deleting_a_non_existing_event_should_be_ignored : GivenWhenThen + { + private IEventMap map; + + public When_deleting_a_non_existing_event_should_be_ignored() + { + Given(() => { - Id = "c350E", - Category = (string) null, - Deleted = true + var mapBuilder = new EventMapBuilder(); + mapBuilder + .Map() + .AsDeleteOf(e => e.ProductKey) + .IgnoringMisses(); + + map = mapBuilder.Build(new ProjectorMap + { + Delete = (key, context) => Task.FromResult(false) + }); + }); + + WhenLater(async () => + { + await map.Handle( + new ProductDiscontinuedEvent + { + ProductKey = "c350E" + }, + new ProjectionContext()); }); } [Fact] - public void It_should_throw_if_the_projection_does_not_exist() + public void It_should_not_throw() { - options.MissingProjectionBehavior.Should().Be(MissingProjectionDeletionBehavior.Throw); + WhenAction.Should().NotThrow(); } } - public class When_an_event_is_mapped_as_a_delete_if_exists : GivenWhenThen + public class When_deleting_a_non_existing_event_should_be_handled_manually : GivenWhenThen { - private ProductCatalogEntry projection; private IEventMap map; - private ProjectionDeletionOptions options; + private object missedKey; - public When_an_event_is_mapped_as_a_delete_if_exists() + public When_deleting_a_non_existing_event_should_be_handled_manually() { Given(() => { var mapBuilder = new EventMapBuilder(); - mapBuilder.Map().AsDeleteIfExistsOf(e => e.ProductKey); - - mapBuilder.HandleProjectionDeletionsAs((key, context, options) => - { - projection = new ProductCatalogEntry + mapBuilder + .Map() + .AsDeleteOf(e => e.ProductKey) + .HandlingMissesUsing((key, context) => { - Id = key, - Deleted = true - }; - - this.options = options; - return Task.FromResult(0); - }); - - mapBuilder.HandleProjectionModificationsAs((key, context, projector, options) => - { - throw new InvalidOperationException("Modification should not be called."); - }); + missedKey = key; + }); - mapBuilder.HandleCustomActionsAs((context, projector) => + map = mapBuilder.Build(new ProjectorMap { - throw new InvalidOperationException("Custom action should not be called."); + Delete = (key, context) => Task.FromResult(false) }); - - map = mapBuilder.Build(); }); - When(async () => + WhenLater(async () => { await map.Handle( new ProductDiscontinuedEvent @@ -526,35 +580,22 @@ await map.Handle( } [Fact] - public void It_should_properly_pass_the_mapping_to_the_deleting_handler() - { - projection.Should().BeEquivalentTo(new - { - Id = "c350E", - Category = (string)null, - Deleted = true - }); - } - - [Fact] - public void It_should_do_nothing_if_the_projection_does_not_exist() + public void It_should_not_throw() { - options.MissingProjectionBehavior.Should().Be(MissingProjectionDeletionBehavior.Ignore); + WhenAction.Should().NotThrow(); } } public class When_an_event_is_mapped_as_a_custom_action : GivenWhenThen { - private string involvedKey; private IEventMap map; + private string involvedKey; public When_an_event_is_mapped_as_a_custom_action() { Given(() => { var mapBuilder = new EventMapBuilder(); - mapBuilder.HandleCustomActionsAs((context, projector) => projector()); - mapBuilder.Map().As((@event, context) => { involvedKey = @event.ProductKey; @@ -562,7 +603,10 @@ public When_an_event_is_mapped_as_a_custom_action() return Task.FromResult(0); }); - map = mapBuilder.Build(); + map = mapBuilder.Build(new ProjectorMap + { + Custom = (context, projector) => projector() + }); }); When(async () => @@ -585,11 +629,7 @@ public void It_should_properly_pass_the_mapping_to_the_custom_handler() public class When_a_condition_is_not_met : GivenWhenThen { - private readonly ProductCatalogEntry projection = new ProductCatalogEntry - { - Id = "c350E", - Category = "Electrics" - }; + private string involvedKey = null; private IEventMap map; public When_a_condition_is_not_met() @@ -597,21 +637,20 @@ public When_a_condition_is_not_met() Given(() => { var mapBuilder = new EventMapBuilder(); - mapBuilder.Map() + mapBuilder + .Map() .When(e => e.Category == "Electric") .As((e, ctx) => { - projection.Category = e.Category; + involvedKey = e.ProductKey; return Task.FromResult(0); }); - mapBuilder.HandleCustomActionsAs((context, projector) => + map = mapBuilder.Build(new ProjectorMap { - throw new InvalidOperationException("Custom action should not be called."); + Custom = (context, projector) => projector() }); - - map = mapBuilder.Build(); }); When(async () => @@ -629,21 +668,13 @@ await map.Handle( [Fact] public void It_should_not_invoke_any_handler() { - projection.Should().BeEquivalentTo(new - { - Id = "c350E", - Category = "Electrics", - Deleted = false - }); + involvedKey.Should().BeNull(); } } public class When_a_condition_is_met : GivenWhenThen { - private readonly ProductCatalogEntry projection = new ProductCatalogEntry - { - Id = "c350E" - }; + private string involvedKey; private IEventMap map; public When_a_condition_is_met() @@ -651,18 +682,19 @@ public When_a_condition_is_met() Given(() => { var mapBuilder = new EventMapBuilder(); - mapBuilder.HandleCustomActionsAs((context, projector) => projector()); - - mapBuilder.Map() + mapBuilder + .Map() .When(e => e.Category == "Hybrids") .As((e, ctx) => { - projection.Category = e.Category; - + involvedKey = e.ProductKey; return Task.FromResult(0); }); - map = mapBuilder.Build(); + map = mapBuilder.Build(new ProjectorMap + { + Custom = (context, projector) => projector() + }); }); When(async () => @@ -680,12 +712,7 @@ await map.Handle( [Fact] public void It_should_invoke_the_right_handler() { - projection.Should().BeEquivalentTo(new - { - Id = "c350E", - Category = "Hybrids", - Deleted = false - }); + involvedKey.Should().Be("c350E"); } } @@ -704,14 +731,14 @@ public When_multiple_conditions_are_registered() mapBuilder.Map() .When(e => e.Category != "Hybrids") .When(e => e.Category != "Electrics") - .As((e, ctx) => {}); + .As((e, ctx) => + { + }); - mapBuilder.HandleCustomActionsAs((context, projector) => + var map = mapBuilder.Build(new ProjectorMap { - throw new InvalidOperationException("Custom action should not be called."); + Custom = (context, projector) =>throw new InvalidOperationException("Custom action should not be called.") }); - - var map = mapBuilder.Build(); }; }); } @@ -735,22 +762,6 @@ public When_an_event_is_mapped_as_a_custom_action_on_a_projection() { var mapBuilder = new EventMapBuilder(); - mapBuilder.HandleCustomActionsAs((context, projector) => - { - customActionDecoratorExecuted = true; - return projector(); - }); - - mapBuilder.HandleProjectionModificationsAs((key, context, projector, options) => - { - throw new InvalidOperationException("Modification should not be called."); - }); - - mapBuilder.HandleProjectionDeletionsAs((key, context, options) => - { - throw new InvalidOperationException("Deletion should not be called."); - }); - mapBuilder.Map().As((@event, context) => { involvedKey = @event.ProductKey; @@ -758,7 +769,14 @@ public When_an_event_is_mapped_as_a_custom_action_on_a_projection() return Task.FromResult(0); }); - map = mapBuilder.Build(); + map = mapBuilder.Build(new ProjectorMap + { + Custom = (context, projector) => + { + customActionDecoratorExecuted = true; + return projector(); + } + }); }); When(async () => @@ -795,15 +813,6 @@ public When_a_condition_is_not_met_on_a_projection() Given(() => { var mapBuilder = new EventMapBuilder(); - mapBuilder.HandleProjectionModificationsAs(async (key, context, projector, options) => - { - projection = new ProductCatalogEntry - { - Id = key, - }; - - await projector(projection); - }); mapBuilder .Map() @@ -816,17 +825,18 @@ public When_a_condition_is_not_met_on_a_projection() return Task.FromResult(0); }); - mapBuilder.HandleProjectionDeletionsAs((key, context, options) => + map = mapBuilder.Build(new ProjectorMap { - throw new InvalidOperationException("Deletion should not be called."); - }); + Update = async (key, context, projector, createIfMissing) => + { + projection = new ProductCatalogEntry + { + Id = key, + }; - mapBuilder.HandleCustomActionsAs((context, projector) => - { - throw new InvalidOperationException("Custom action should not be called."); + await projector(projection); + } }); - - map = mapBuilder.Build(); }); When(async () => @@ -859,26 +869,6 @@ public When_a_condition_is_met_on_a_projection() { var mapBuilder = new EventMapBuilder(); - mapBuilder.HandleProjectionModificationsAs(async (key, context, projector, options) => - { - projection = new ProductCatalogEntry - { - Id = key, - }; - - await projector(projection); - }); - - mapBuilder.HandleProjectionDeletionsAs((key, context, options) => - { - throw new InvalidOperationException("Deletion should not be called."); - }); - - mapBuilder.HandleCustomActionsAs((context, projector) => - { - throw new InvalidOperationException("Custom action should not be called."); - }); - mapBuilder .Map() .When(e => e.Category == "Hybrids") @@ -889,7 +879,18 @@ public When_a_condition_is_met_on_a_projection() return Task.FromResult(0); }); - map = mapBuilder.Build(); + map = mapBuilder.Build(new ProjectorMap + { + Update = async (key, context, projector, createIfMissing) => + { + projection = new ProductCatalogEntry + { + Id = key, + }; + + await projector(projection); + } + }); }); When(async () => @@ -928,21 +929,6 @@ public When_multiple_conditions_are_registered_on_a_projection() { var mapBuilder = new EventMapBuilder(); - mapBuilder.HandleProjectionModificationsAs((key, context, projector, options) => - { - throw new InvalidOperationException("Modification should not be called."); - }); - - mapBuilder.HandleProjectionDeletionsAs((key, context, options) => - { - throw new InvalidOperationException("Deletion should not be called."); - }); - - mapBuilder.HandleCustomActionsAs((context, projector) => - { - throw new InvalidOperationException("Custom action should not be called."); - }); - mapBuilder.Map() .When(e => e.Category == "Hybrids") .AsUpdateOf(e => e.ProductKey).Using((p, e, ctx) => p.Category = e.Category); @@ -951,7 +937,7 @@ public When_multiple_conditions_are_registered_on_a_projection() .When(e => e.Category == "Electrics") .AsDeleteOf(e => e.ProductKey); - var map = mapBuilder.Build(); + var map = mapBuilder.Build(new ProjectorMap()); }; }); } From e4bd20a2f495b42e756a1d473b3020545a1fc1d0 Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Wed, 21 Feb 2018 19:31:34 +0100 Subject: [PATCH 3/4] Added support for map-level filters using the `Where` method. Closes #101 --- Src/LiquidProjections/EventMap.cs | 39 ++++-- Src/LiquidProjections/EventMapBuilder.cs | 19 +++ .../LiquidProjections.Specs/EventMapSpecs.cs | 117 ++++++++++++++++++ 3 files changed, 168 insertions(+), 7 deletions(-) diff --git a/Src/LiquidProjections/EventMap.cs b/Src/LiquidProjections/EventMap.cs index 83397e5..5a205a8 100644 --- a/Src/LiquidProjections/EventMap.cs +++ b/Src/LiquidProjections/EventMap.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace LiquidProjections @@ -10,6 +11,8 @@ namespace LiquidProjections public class EventMap : IEventMap { private readonly Dictionary> mappings = new Dictionary>(); + private readonly List>> filters = + new List>>(); internal void Add(Func action) { @@ -21,6 +24,11 @@ internal void Add(Func action) mappings[typeof(TEvent)].Add((@event, context) => action((TEvent)@event, context)); } + internal void AddFilter(Func> filter) + { + filters.Add(filter); + } + public async Task Handle(object anEvent, TContext context) { if (anEvent == null) @@ -33,21 +41,38 @@ public async Task Handle(object anEvent, TContext context) throw new ArgumentNullException(nameof(context)); } - Type key = anEvent.GetType(); - - if (mappings.TryGetValue(key, out var handlers)) + if (await PassesFilter(anEvent, context)) { - foreach (Handler handler in handlers) + Type key = anEvent.GetType(); + + if (mappings.TryGetValue(key, out var handlers)) { - await handler(anEvent, context); - } + foreach (Handler handler in handlers) + { + await handler(anEvent, context); + } - return true; + return true; + } } return false; } + private async Task PassesFilter(object anEvent, TContext context) + { + if (filters.Count > 0) + { + bool[] results = await Task.WhenAll(filters.Select(filter => filter(anEvent, context))); + + return results.All(x => x); + } + else + { + return true; + } + } + private delegate Task Handler(object @event, TContext context); } } \ No newline at end of file diff --git a/Src/LiquidProjections/EventMapBuilder.cs b/Src/LiquidProjections/EventMapBuilder.cs index 63cc5fd..7fcaa75 100644 --- a/Src/LiquidProjections/EventMapBuilder.cs +++ b/Src/LiquidProjections/EventMapBuilder.cs @@ -13,6 +13,15 @@ public sealed class EventMapBuilder : IEventMapBuilder private readonly EventMap eventMap = new EventMap(); private ProjectorMap projector; + /// + /// Ensures that only events matching the predicate are processed. + /// + public EventMapBuilder Where(Func> filter) + { + eventMap.AddFilter(filter); + return this; + } + /// /// Starts configuring a new handler for events of type . /// @@ -127,6 +136,16 @@ public sealed class EventMapBuilder : IEventMapBuil private readonly EventMapBuilder innerBuilder = new EventMapBuilder(); private ProjectorMap projector; + + /// + /// Ensures that only events matching the predicate are processed. + /// + public EventMapBuilder Where(Func> predicate) + { + innerBuilder.Where(predicate); + return this; + } + /// /// Starts configuring a new handler for events of type . /// diff --git a/Tests/LiquidProjections.Specs/EventMapSpecs.cs b/Tests/LiquidProjections.Specs/EventMapSpecs.cs index 680815a..915e787 100644 --- a/Tests/LiquidProjections.Specs/EventMapSpecs.cs +++ b/Tests/LiquidProjections.Specs/EventMapSpecs.cs @@ -627,6 +627,60 @@ public void It_should_properly_pass_the_mapping_to_the_custom_handler() } } + public class When_a_global_filter_is_not_met : GivenWhenThen + { + private string involvedKey = null; + private IEventMap map; + + public When_a_global_filter_is_not_met() + { + Given(() => + { + var mapBuilder = new EventMapBuilder() + .Where((@event, context) => + { + if (@event is ProductAddedToCatalogEvent addedEvent) + { + return Task.FromResult(addedEvent.Category == "Electric"); + } + + return Task.FromResult(true); + }); + + mapBuilder + .Map() + .As((e, ctx) => + { + involvedKey = e.ProductKey; + + return Task.FromResult(0); + }); + + map = mapBuilder.Build(new ProjectorMap + { + Custom = (context, projector) => projector() + }); + }); + + When(async () => + { + await map.Handle( + new ProductAddedToCatalogEvent + { + Category = "Hybrids", + ProductKey = "c350E" + }, + new object()); + }); + } + + [Fact] + public void It_should_not_invoke_any_handler() + { + involvedKey.Should().BeNull(); + } + } + public class When_a_condition_is_not_met : GivenWhenThen { private string involvedKey = null; @@ -803,6 +857,69 @@ public void It_should_allow_decorating_the_custom_handler() } } + public class When_a_global_filter_is_not_met_on_a_projection : GivenWhenThen + { + private ProductCatalogEntry projection; + private IEventMap map; + + public When_a_global_filter_is_not_met_on_a_projection() + { + Given(() => + { + var mapBuilder = new EventMapBuilder() + .Where((@event, context) => + { + if (@event is ProductAddedToCatalogEvent addedEvent) + { + return Task.FromResult(addedEvent.Category == "Electric"); + } + + return Task.FromResult(true); + }); + + mapBuilder + .Map() + .AsUpdateOf(e => e.ProductKey) + .Using((p, e, ctx) => + { + p.Category = e.Category; + + return Task.FromResult(0); + }); + + map = mapBuilder.Build(new ProjectorMap + { + Update = async (key, context, projector, createIfMissing) => + { + projection = new ProductCatalogEntry + { + Id = key, + }; + + await projector(projection); + } + }); + }); + + When(async () => + { + await map.Handle( + new ProductAddedToCatalogEvent + { + Category = "Hybrids", + ProductKey = "c350E" + }, + new ProjectionContext()); + }); + } + + [Fact] + public void It_should_not_invoke_any_handler() + { + projection.Should().BeNull(); + } + } + public class When_a_condition_is_not_met_on_a_projection : GivenWhenThen { private ProductCatalogEntry projection; From d6d2306b0035441929448788ff7eb0be8d21755b Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Tue, 13 Mar 2018 13:42:18 +0100 Subject: [PATCH 4/4] Ensure correct numbering of PRs. --- GitVersion.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 GitVersion.yml diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..f77567e --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,6 @@ +branches: + (pull|pull\-requests|pr)[/-]: + mode: ContinuousDeployment + tag: pr +ignore: + sha: []