diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9afe32d..c71d2ee 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -15,12 +15,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - - name: Setup .NET9 - uses: actions/setup-dotnet@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v5 with: - dotnet-version: 9.x + dotnet-version: 10.x - name: Restore dependencies run: dotnet restore diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 2b348e2..db4af05 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -11,12 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - - name: Setup .NET9 - uses: actions/setup-dotnet@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v5 with: - dotnet-version: 9.x + dotnet-version: 10.x - name: Restore dependencies run: dotnet restore diff --git a/CHANGELOG.md b/CHANGELOG.md index ffe66a8..b37b01d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -Nothing yet. +### Added + +- `EventBus.GetHandlersAsync` method. +- `EventBus` unit tests. + +### Changed + +- Improved `EventBus` implementation. + +### Fixed + +- GitHub Actions. +- LICENSE Year. +- NuGet upgrades. ## [10.0.0] - 2025-11-29 diff --git a/LICENSE b/LICENSE index 0487f9b..70fdfae 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Logitar +Copyright (c) 2025 Logitar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/demo/Logitar.EventSourcing.Demo/Logitar.EventSourcing.Demo.csproj b/demo/Logitar.EventSourcing.Demo/Logitar.EventSourcing.Demo.csproj index bb30054..82456d0 100644 --- a/demo/Logitar.EventSourcing.Demo/Logitar.EventSourcing.Demo.csproj +++ b/demo/Logitar.EventSourcing.Demo/Logitar.EventSourcing.Demo.csproj @@ -24,7 +24,7 @@ - + all diff --git a/lib/Logitar.EventSourcing.EntityFrameworkCore.PostgreSQL/LICENSE b/lib/Logitar.EventSourcing.EntityFrameworkCore.PostgreSQL/LICENSE index 0487f9b..70fdfae 100644 --- a/lib/Logitar.EventSourcing.EntityFrameworkCore.PostgreSQL/LICENSE +++ b/lib/Logitar.EventSourcing.EntityFrameworkCore.PostgreSQL/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Logitar +Copyright (c) 2025 Logitar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/Logitar.EventSourcing.EntityFrameworkCore.PostgreSQL/Logitar.EventSourcing.EntityFrameworkCore.PostgreSQL.csproj b/lib/Logitar.EventSourcing.EntityFrameworkCore.PostgreSQL/Logitar.EventSourcing.EntityFrameworkCore.PostgreSQL.csproj index e4fbbc4..d31cef6 100644 --- a/lib/Logitar.EventSourcing.EntityFrameworkCore.PostgreSQL/Logitar.EventSourcing.EntityFrameworkCore.PostgreSQL.csproj +++ b/lib/Logitar.EventSourcing.EntityFrameworkCore.PostgreSQL/Logitar.EventSourcing.EntityFrameworkCore.PostgreSQL.csproj @@ -9,7 +9,7 @@ Francis Pion Logitar.NET Provides an implementation of a relational event store to be used with the Event Sourcing architecture pattern, Entity Framework Core and PostgreSQL. - © 2024 Logitar All Rights Reserved. + © 2025 Logitar All Rights Reserved. logitar.png README.md git @@ -39,7 +39,7 @@ - + diff --git a/lib/Logitar.EventSourcing.EntityFrameworkCore.Relational/LICENSE b/lib/Logitar.EventSourcing.EntityFrameworkCore.Relational/LICENSE index 0487f9b..70fdfae 100644 --- a/lib/Logitar.EventSourcing.EntityFrameworkCore.Relational/LICENSE +++ b/lib/Logitar.EventSourcing.EntityFrameworkCore.Relational/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Logitar +Copyright (c) 2025 Logitar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/Logitar.EventSourcing.EntityFrameworkCore.Relational/Logitar.EventSourcing.EntityFrameworkCore.Relational.csproj b/lib/Logitar.EventSourcing.EntityFrameworkCore.Relational/Logitar.EventSourcing.EntityFrameworkCore.Relational.csproj index 8678140..53a6328 100644 --- a/lib/Logitar.EventSourcing.EntityFrameworkCore.Relational/Logitar.EventSourcing.EntityFrameworkCore.Relational.csproj +++ b/lib/Logitar.EventSourcing.EntityFrameworkCore.Relational/Logitar.EventSourcing.EntityFrameworkCore.Relational.csproj @@ -9,7 +9,7 @@ Francis Pion Logitar.NET Provides an abstraction of a relational event store to be used with the Event Sourcing architecture pattern and Entity Framework Core. - © 2024 Logitar All Rights Reserved. + © 2025 Logitar All Rights Reserved. logitar.png README.md git @@ -39,7 +39,7 @@ - + diff --git a/lib/Logitar.EventSourcing.EntityFrameworkCore.SqlServer/LICENSE b/lib/Logitar.EventSourcing.EntityFrameworkCore.SqlServer/LICENSE index 0487f9b..70fdfae 100644 --- a/lib/Logitar.EventSourcing.EntityFrameworkCore.SqlServer/LICENSE +++ b/lib/Logitar.EventSourcing.EntityFrameworkCore.SqlServer/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Logitar +Copyright (c) 2025 Logitar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/Logitar.EventSourcing.EntityFrameworkCore.SqlServer/Logitar.EventSourcing.EntityFrameworkCore.SqlServer.csproj b/lib/Logitar.EventSourcing.EntityFrameworkCore.SqlServer/Logitar.EventSourcing.EntityFrameworkCore.SqlServer.csproj index a096f0a..cf0836a 100644 --- a/lib/Logitar.EventSourcing.EntityFrameworkCore.SqlServer/Logitar.EventSourcing.EntityFrameworkCore.SqlServer.csproj +++ b/lib/Logitar.EventSourcing.EntityFrameworkCore.SqlServer/Logitar.EventSourcing.EntityFrameworkCore.SqlServer.csproj @@ -9,7 +9,7 @@ Francis Pion Logitar.NET Provides an implementation of a relational event store to be used with the Event Sourcing architecture pattern, Entity Framework Core and Microsoft SQL Server. - © 2024 Logitar All Rights Reserved. + © 2025 Logitar All Rights Reserved. logitar.png README.md git @@ -39,7 +39,7 @@ - + diff --git a/lib/Logitar.EventSourcing.Infrastructure/EventBus.cs b/lib/Logitar.EventSourcing.Infrastructure/EventBus.cs index f54fa6c..26b6d46 100644 --- a/lib/Logitar.EventSourcing.Infrastructure/EventBus.cs +++ b/lib/Logitar.EventSourcing.Infrastructure/EventBus.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using System.Reflection; namespace Logitar.EventSourcing.Infrastructure; @@ -8,10 +7,15 @@ namespace Logitar.EventSourcing.Infrastructure; /// public class EventBus : IEventBus { + /// + /// The name of the handler method. + /// + protected const string HandlerName = nameof(IEventHandler<>.HandleAsync); + /// /// Gets the service provider. /// - protected IServiceProvider ServiceProvider { get; } + protected virtual IServiceProvider ServiceProvider { get; } /// /// Initializes a new instance of the class. @@ -30,22 +34,40 @@ public EventBus(IServiceProvider serviceProvider) /// The asynchronous operation. public async Task PublishAsync(IEvent @event, CancellationToken cancellationToken) { - IEnumerable handlers = ServiceProvider.GetServices(typeof(IEventHandler<>).MakeGenericType(@event.GetType())); - if (handlers.Any()) + IReadOnlyCollection handlers = await GetHandlersAsync(@event, cancellationToken); + if (handlers.Count > 0) { Type[] parameterTypes = [@event.GetType(), typeof(CancellationToken)]; object[] parameters = [@event, cancellationToken]; - foreach (object? handler in handlers) + foreach (object handler in handlers) { - if (handler is not null) + Type handlerType = handler.GetType(); + MethodInfo handle = handler.GetType().GetMethod(HandlerName, parameterTypes) + ?? throw new InvalidOperationException($"The handler {handlerType} must define a '{HandlerName}' method."); + if (handle.Invoke(handler, parameters) is Task task) { - MethodInfo? handle = handler.GetType().GetMethod(nameof(IEventHandler<>.HandleAsync), parameterTypes); - if (handle is not null) - { - await (Task)handle.Invoke(handler, parameters)!; - } + await task; + } + else + { + throw new InvalidOperationException($"The handler {handlerType} {HandlerName} method must return a {nameof(Task)}."); } } } } + + /// + /// Finds the handlers of the specified event. + /// + /// The event. + /// The cancellation token. + /// The event handlers. + protected virtual async Task> GetHandlersAsync(IEvent @event, CancellationToken cancellationToken) + { + return ServiceProvider.GetServices(typeof(IEventHandler<>).MakeGenericType(@event.GetType())) + .Where(handler => handler is not null) + .Select(handler => handler!) + .ToList() + .AsReadOnly(); + } } diff --git a/lib/Logitar.EventSourcing.Infrastructure/LICENSE b/lib/Logitar.EventSourcing.Infrastructure/LICENSE index 0487f9b..70fdfae 100644 --- a/lib/Logitar.EventSourcing.Infrastructure/LICENSE +++ b/lib/Logitar.EventSourcing.Infrastructure/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Logitar +Copyright (c) 2025 Logitar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/Logitar.EventSourcing.Infrastructure/Logitar.EventSourcing.Infrastructure.csproj b/lib/Logitar.EventSourcing.Infrastructure/Logitar.EventSourcing.Infrastructure.csproj index e7fae6e..1b0df9f 100644 --- a/lib/Logitar.EventSourcing.Infrastructure/Logitar.EventSourcing.Infrastructure.csproj +++ b/lib/Logitar.EventSourcing.Infrastructure/Logitar.EventSourcing.Infrastructure.csproj @@ -9,7 +9,7 @@ Francis Pion Logitar.NET Provides an abstraction of an event store to be used with the Event Sourcing architecture pattern. - © 2024 Logitar All Rights Reserved. + © 2025 Logitar All Rights Reserved. logitar.png README.md git @@ -39,6 +39,7 @@ + diff --git a/lib/Logitar.EventSourcing.Kurrent/LICENSE b/lib/Logitar.EventSourcing.Kurrent/LICENSE index 0487f9b..70fdfae 100644 --- a/lib/Logitar.EventSourcing.Kurrent/LICENSE +++ b/lib/Logitar.EventSourcing.Kurrent/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Logitar +Copyright (c) 2025 Logitar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/Logitar.EventSourcing.Kurrent/Logitar.EventSourcing.Kurrent.csproj b/lib/Logitar.EventSourcing.Kurrent/Logitar.EventSourcing.Kurrent.csproj index 40339e9..b1c05f7 100644 --- a/lib/Logitar.EventSourcing.Kurrent/Logitar.EventSourcing.Kurrent.csproj +++ b/lib/Logitar.EventSourcing.Kurrent/Logitar.EventSourcing.Kurrent.csproj @@ -9,7 +9,7 @@ Francis Pion Logitar.NET Provides an implementation of an event store to be used with the Event Sourcing architecture pattern, and EventStoreDB/Kurrent. - © 2024 Logitar All Rights Reserved. + © 2025 Logitar All Rights Reserved. logitar.png README.md git diff --git a/lib/Logitar.EventSourcing/LICENSE b/lib/Logitar.EventSourcing/LICENSE index 0487f9b..70fdfae 100644 --- a/lib/Logitar.EventSourcing/LICENSE +++ b/lib/Logitar.EventSourcing/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Logitar +Copyright (c) 2025 Logitar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/Logitar.EventSourcing/Logitar.EventSourcing.csproj b/lib/Logitar.EventSourcing/Logitar.EventSourcing.csproj index 843512d..c4f609d 100644 --- a/lib/Logitar.EventSourcing/Logitar.EventSourcing.csproj +++ b/lib/Logitar.EventSourcing/Logitar.EventSourcing.csproj @@ -9,7 +9,7 @@ Francis Pion Logitar.NET Provides an implementation of the Event Sourcing architecture pattern. - © 2024 Logitar All Rights Reserved. + © 2025 Logitar All Rights Reserved. logitar.png README.md git @@ -35,7 +35,7 @@ - + diff --git a/tests/Logitar.EventSourcing.Infrastructure.UnitTests/EventBusTests.cs b/tests/Logitar.EventSourcing.Infrastructure.UnitTests/EventBusTests.cs new file mode 100644 index 0000000..a709581 --- /dev/null +++ b/tests/Logitar.EventSourcing.Infrastructure.UnitTests/EventBusTests.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.DependencyInjection; +using Moq; + +namespace Logitar.EventSourcing.Infrastructure; + +[Trait(Traits.Category, Categories.Unit)] +public class EventBusTests +{ + private readonly Mock> _userGenderChangedHandler = new(); + private readonly Mock> _userLocaleChangedHandler1 = new(); + private readonly Mock> _userLocaleChangedHandler2 = new(); + + public EventBusTests() + { + } + + [Fact(DisplayName = "PublishAsync: it should call all found event handlers.")] + public async Task Given_Handlers_When_PublishAsync_Then_AllCalled() + { + IServiceProvider serviceProvider = new ServiceCollection() + .AddSingleton(_userGenderChangedHandler.Object) + .AddSingleton(_userLocaleChangedHandler1.Object) + .AddSingleton(_userLocaleChangedHandler2.Object) + .BuildServiceProvider(); + EventBus eventBus = new(serviceProvider); + + UserLocaleChanged changed = new(CultureInfo.GetCultureInfo("fr-CA")); + CancellationToken cancellationToken = default; + + await eventBus.PublishAsync(changed, cancellationToken); + + _userLocaleChangedHandler1.Verify(x => x.HandleAsync(changed, cancellationToken), Times.Once); + _userLocaleChangedHandler2.Verify(x => x.HandleAsync(changed, cancellationToken), Times.Once); + } + + [Fact(DisplayName = "PublishAsync: it should not call any handler when there are none.")] + public async Task Given_NoHandler_When_PublishAsync_Then_NoneCalled() + { + IServiceProvider serviceProvider = new ServiceCollection() + .AddSingleton(_userGenderChangedHandler.Object) + .BuildServiceProvider(); + EventBus eventBus = new(serviceProvider); + + UserLocaleChanged changed = new(CultureInfo.GetCultureInfo("fr-CA")); + CancellationToken cancellationToken = default; + + await eventBus.PublishAsync(changed, cancellationToken); + + _userGenderChangedHandler.Verify(x => x.HandleAsync(It.IsAny(), It.IsAny()), Times.Never); + } +} diff --git a/tests/Logitar.EventSourcing.Infrastructure.UnitTests/Gender.cs b/tests/Logitar.EventSourcing.Infrastructure.UnitTests/Gender.cs index 77d8489..46bc965 100644 --- a/tests/Logitar.EventSourcing.Infrastructure.UnitTests/Gender.cs +++ b/tests/Logitar.EventSourcing.Infrastructure.UnitTests/Gender.cs @@ -1,6 +1,6 @@ namespace Logitar.EventSourcing.Infrastructure; -internal enum Gender +public enum Gender { Female, Male, diff --git a/tests/Logitar.EventSourcing.Infrastructure.UnitTests/Logitar.EventSourcing.Infrastructure.UnitTests.csproj b/tests/Logitar.EventSourcing.Infrastructure.UnitTests/Logitar.EventSourcing.Infrastructure.UnitTests.csproj index 0f58440..2800649 100644 --- a/tests/Logitar.EventSourcing.Infrastructure.UnitTests/Logitar.EventSourcing.Infrastructure.UnitTests.csproj +++ b/tests/Logitar.EventSourcing.Infrastructure.UnitTests/Logitar.EventSourcing.Infrastructure.UnitTests.csproj @@ -21,6 +21,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/Logitar.EventSourcing.Infrastructure.UnitTests/UserGenderChanged.cs b/tests/Logitar.EventSourcing.Infrastructure.UnitTests/UserGenderChanged.cs index 74c3831..d6d6c93 100644 --- a/tests/Logitar.EventSourcing.Infrastructure.UnitTests/UserGenderChanged.cs +++ b/tests/Logitar.EventSourcing.Infrastructure.UnitTests/UserGenderChanged.cs @@ -1,3 +1,3 @@ namespace Logitar.EventSourcing.Infrastructure; -internal record UserGenderChanged(Gender? Gender) : DomainEvent; +public record UserGenderChanged(Gender? Gender) : DomainEvent; diff --git a/tests/Logitar.EventSourcing.Infrastructure.UnitTests/UserLocaleChanged.cs b/tests/Logitar.EventSourcing.Infrastructure.UnitTests/UserLocaleChanged.cs index d0bac3e..01d483e 100644 --- a/tests/Logitar.EventSourcing.Infrastructure.UnitTests/UserLocaleChanged.cs +++ b/tests/Logitar.EventSourcing.Infrastructure.UnitTests/UserLocaleChanged.cs @@ -1,3 +1,3 @@ namespace Logitar.EventSourcing.Infrastructure; -internal record UserLocaleChanged(CultureInfo Locale) : IEvent; +public record UserLocaleChanged(CultureInfo Locale) : IEvent;