From b64e27c8ff4a3fcfee04616e5777c28bea4b022a Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Tue, 29 Oct 2024 22:12:26 +0000 Subject: [PATCH] update DomainEventHandler to return results --- .../EventDispatchHelperTests.cs | 22 +++++++++++------- .../TestObjects/TestDomainEventHandler.cs | 5 +++- .../EventHandling/IDomainEventHandler.cs | 6 +++-- .../ProjectionEngine/EventHandler.cs | 8 ++++--- .../SubscriptionWorker/EventDispatchHelper.cs | 23 ++++++++++++++----- .../PersistentSubscription.cs | 16 +++++++++---- 6 files changed, 55 insertions(+), 25 deletions(-) diff --git a/Shared.EventStore.Tests/EventDispatchHelperTests.cs b/Shared.EventStore.Tests/EventDispatchHelperTests.cs index daffe01f..557f9400 100644 --- a/Shared.EventStore.Tests/EventDispatchHelperTests.cs +++ b/Shared.EventStore.Tests/EventDispatchHelperTests.cs @@ -1,4 +1,6 @@ -namespace Shared.EventStore.Tests; +using SimpleResults; + +namespace Shared.EventStore.Tests; using System; using System.Collections.Generic; @@ -18,9 +20,8 @@ public async Task EventDispatchHelper_DispatchToHandlers_AllSuccessful(){ AggregateNameSetEvent @event = new AggregateNameSetEvent(TestData.AggregateId, TestData.EventId, TestData.EstateName); List handlers = new List(); handlers.Add(new TestDomainEventHandler()); - Should.NotThrow(async () => { - await @event.DispatchToHandlers(handlers, CancellationToken.None); - }); + Result result = await @event.DispatchToHandlers(handlers, CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); } [Fact] @@ -29,10 +30,15 @@ public async Task EventDispatchHelper_DispatchToHandlers_HandlerThrowsException_ AggregateNameSetEvent @event = new AggregateNameSetEvent(TestData.AggregateId, TestData.EventId, TestData.EstateName); List handlers = new List(); Mock domainEventHandler = new Mock(); - domainEventHandler.Setup(s => s.Handle(It.IsAny(), It.IsAny())).Throws(); + domainEventHandler.Setup(s => s.Handle(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Failure); + Mock domainEventHandler2 = new Mock(); + domainEventHandler2.Setup(s => s.Handle(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Failure); handlers.Add(domainEventHandler.Object); - Should.Throw(async () => { - await @event.DispatchToHandlers(handlers, CancellationToken.None); - }); + handlers.Add(domainEventHandler2.Object); + Result dispatchResult = await @event.DispatchToHandlers(handlers, CancellationToken.None); + dispatchResult.IsFailed.ShouldBeTrue(); + } } \ No newline at end of file diff --git a/Shared.EventStore.Tests/TestObjects/TestDomainEventHandler.cs b/Shared.EventStore.Tests/TestObjects/TestDomainEventHandler.cs index 73a481cd..297f2717 100644 --- a/Shared.EventStore.Tests/TestObjects/TestDomainEventHandler.cs +++ b/Shared.EventStore.Tests/TestObjects/TestDomainEventHandler.cs @@ -1,3 +1,5 @@ +using SimpleResults; + namespace Shared.EventStore.Tests.TestObjects; using System.Collections.Generic; @@ -10,8 +12,9 @@ public class TestDomainEventHandler : IDomainEventHandler { public List DomainEvents = new(); - public async Task Handle(IDomainEvent domainEvent, CancellationToken cancellationToken) + public async Task Handle(IDomainEvent domainEvent, CancellationToken cancellationToken) { DomainEvents.Add(domainEvent); + return Result.Success(); } } \ No newline at end of file diff --git a/Shared.EventStore/EventHandling/IDomainEventHandler.cs b/Shared.EventStore/EventHandling/IDomainEventHandler.cs index 3e5185a7..76578e09 100644 --- a/Shared.EventStore/EventHandling/IDomainEventHandler.cs +++ b/Shared.EventStore/EventHandling/IDomainEventHandler.cs @@ -1,4 +1,6 @@ -namespace Shared.EventStore.EventHandling +using SimpleResults; + +namespace Shared.EventStore.EventHandling { using System.Threading; using System.Threading.Tasks; @@ -14,7 +16,7 @@ public interface IDomainEventHandler /// The domain event. /// The cancellation token. /// - Task Handle(IDomainEvent domainEvent, + Task Handle(IDomainEvent domainEvent, CancellationToken cancellationToken); #endregion diff --git a/Shared.EventStore/ProjectionEngine/EventHandler.cs b/Shared.EventStore/ProjectionEngine/EventHandler.cs index c873d3a6..fcf64408 100644 --- a/Shared.EventStore/ProjectionEngine/EventHandler.cs +++ b/Shared.EventStore/ProjectionEngine/EventHandler.cs @@ -1,4 +1,6 @@ -namespace Shared.EventStore.ProjectionEngine; +using SimpleResults; + +namespace Shared.EventStore.ProjectionEngine; using System; using System.Collections.Generic; @@ -36,14 +38,14 @@ public EventHandler(Func resolver){ #region Methods - public async Task Handle(IDomainEvent domainEvent, + public async Task Handle(IDomainEvent domainEvent, CancellationToken cancellationToken){ // Lookup the event type in the config String handlerType = ConfigurationReader.GetValue("AppSettings:EventStateConfig", domainEvent.GetType().Name); IDomainEventHandler handler = this.Resolver(handlerType); - await handler.Handle(domainEvent, cancellationToken); + return await handler.Handle(domainEvent, cancellationToken); } #endregion diff --git a/Shared.EventStore/SubscriptionWorker/EventDispatchHelper.cs b/Shared.EventStore/SubscriptionWorker/EventDispatchHelper.cs index 309d876c..737e7d15 100644 --- a/Shared.EventStore/SubscriptionWorker/EventDispatchHelper.cs +++ b/Shared.EventStore/SubscriptionWorker/EventDispatchHelper.cs @@ -1,4 +1,7 @@ -namespace Shared.EventStore.SubscriptionWorker +using Shared.Exceptions; +using SimpleResults; + +namespace Shared.EventStore.SubscriptionWorker { using System; using System.Collections.Generic; @@ -18,25 +21,33 @@ public static class EventDispatchHelper /// The event. /// The event handlers. /// The cancellation token. - public static async Task DispatchToHandlers(this IDomainEvent @event, + public static async Task DispatchToHandlers(this IDomainEvent @event, List eventHandlers, CancellationToken cancellationToken) { // Now execute all the tasks - Task all = Task.WhenAll(eventHandlers.Select(x => x.Handle(@event, cancellationToken))); + Task all = Task.WhenAll(eventHandlers.Select(x => x.Handle(@event, cancellationToken))); try { - await all; + Result[] results = await all; + if (results.Any(r => r.IsFailed)) { + IEnumerable failedResults = results.Where(r => r.IsFailed).Select(r => r.Message); + String errors = String.Join(Environment.NewLine, failedResults); + // We have a failed result so need to do something with it + return Result.Failure($"One or more event handlers have failed. Error Messages [{errors}]"); + } } - catch (Exception) + catch (Exception ex) { if (all.Exception != null) { Logger.Logger.LogError(all.Exception); - throw all.Exception; + return Result.Failure(all.Exception.GetExceptionMessages()); } + return Result.Failure(ex.GetExceptionMessages()); } + return Result.Success(); } #endregion diff --git a/Shared.EventStore/SubscriptionWorker/PersistentSubscription.cs b/Shared.EventStore/SubscriptionWorker/PersistentSubscription.cs index adfcb332..465e9369 100644 --- a/Shared.EventStore/SubscriptionWorker/PersistentSubscription.cs +++ b/Shared.EventStore/SubscriptionWorker/PersistentSubscription.cs @@ -1,4 +1,6 @@ -namespace Shared.EventStore.SubscriptionWorker +using SimpleResults; + +namespace Shared.EventStore.SubscriptionWorker { using System; using System.Collections.Generic; @@ -128,13 +130,17 @@ internal static async Task EventAppeared(global::EventStore.Client.PersistentSub // Log a warning out Logger.Logger.LogWarning( $"No event handlers configured for Event Type [{domainEvent.GetType().Name}]"); - await PersistentSubscriptionsHelper.AckEvent(persistentSubscription, resolvedEvent); return; } - await domainEvent.DispatchToHandlers(domainEventHandlers, cts.Token); - - await PersistentSubscriptionsHelper.AckEvent(persistentSubscription, resolvedEvent); + Result result = await domainEvent.DispatchToHandlers(domainEventHandlers, cts.Token); + if (result.IsSuccess) { + await PersistentSubscriptionsHelper.AckEvent(persistentSubscription, resolvedEvent); + } + else { + Exception ex = new($"Failed to process the event type {resolvedEvent.Event.EventType} {resolvedEvent.GetResolvedEventDataAsString()} Result was {result.Message}"); + Logger.Logger.LogError(ex); + } } } catch (Exception e)