Compare commits

2 Commits

Author SHA1 Message Date
7c3dd84971 readme and publish workflow
All checks were successful
.NET Test / test (push) Successful in 1m0s
.NET Publish / publish (push) Successful in 1m1s
2025-02-02 11:51:55 +04:00
d437d06c09 renamed Behavior 2025-02-02 11:51:34 +04:00
12 changed files with 235 additions and 120 deletions

View File

@@ -0,0 +1,38 @@
name: .NET Publish
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: https://github.com/actions/setup-dotnet@v4
with:
dotnet-version: 9.x
- name: Restore dependencies
run: dotnet restore --nologo
- name: Test
run: dotnet test --nologo --no-restore --configuration Release
- name: Create the package
env:
RELEASE_VERSION: ${{ gitea.ref_name }}
run: >
dotnet pack --no-restore --configuration Release --output nupkgs
`echo $RELEASE_VERSION | sed -E 's|^(v([0-9]+(\.[0-9]+){2}))(-([a-z0-9]+)){1}|/p:ReleaseVersion=\2 /p:VersionSuffix=\5|; s|^(v([0-9]+(\.[0-9]+){2}))$|/p:ReleaseVersion=\2|'`
- name: Publish the package to Gitea
run: dotnet nuget push --source ${{ vars.OUTPUT_NUGET_REGISTRY }} --api-key ${{ secrets.LOCAL_NUGET_PACKAGE_TOKEN }} nupkgs/*.nupkg
- name: Publish the package to NuGet.org
run: dotnet nuget push --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_PACKAGE_TOKEN }} nupkgs/*.nupkg

View File

@@ -7,11 +7,22 @@
<Description>Lightweight, easy-to-use C# library designed to simplify the implementation of the Command Query Responsibility Segregation (CQRS) pattern.</Description> <Description>Lightweight, easy-to-use C# library designed to simplify the implementation of the Command Query Responsibility Segregation (CQRS) pattern.</Description>
<Authors>JustFixMe</Authors> <Authors>JustFixMe</Authors>
<Copyright>Copyright (c) 2025 JustFixMe</Copyright> <Copyright>Copyright (c) 2025 JustFixMe</Copyright>
<RepositoryUrl>https://github.com/JustFixMe/Just.Core/</RepositoryUrl> <RepositoryUrl>https://github.com/JustFixMe/Just.Cqrs/</RepositoryUrl>
<PackageTags>c#;CQRS</PackageTags> <PackageTags>csharp;cqrs;cqrs-pattern;</PackageTags>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReadmeFile>readme.md</PackageReadmeFile> <PackageReadmeFile>readme.md</PackageReadmeFile>
<ReleaseVersion Condition=" '$(ReleaseVersion)' == '' ">1.0.0</ReleaseVersion>
<VersionSuffix Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</VersionSuffix>
<VersionPrefix Condition=" '$(VersionSuffix)' != '' ">$(ReleaseVersion)</VersionPrefix>
<Version Condition=" '$(VersionSuffix)' == '' ">$(ReleaseVersion)</Version>
<AssemblyVersion>$(ReleaseVersion)</AssemblyVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Include="..\..\readme.md" Pack="true" PackagePath=""/>
<None Include="..\..\LICENSE" Pack="true" PackagePath=""/>
</ItemGroup>
</Project> </Project>

View File

@@ -7,24 +7,90 @@ Inspired by [MediatR](https://github.com/jbogard/MediatR)
## Features ## Features
* Separate dispatching of Commands/Queries * Separate dispatching of Commands/Queries
* Middleware-like behaviours * Middleware-like Behaviors
## Compatibility
**Just.Cqrs** is built for .Net Standard 2.1 and .NET 8.0 and 9.0.
## Getting Started ## Getting Started
### Install from NuGet.org ### Install from NuGet.org
``` ```bash
# install the package using NuGet # install the package using NuGet
dotnet add package Just.Cqrs dotnet add package Just.Cqrs
``` ```
### Register in DI with ```IServiceCollection``` ### Register in DI with ```IServiceCollection```
```cs ```csharp
services.AddCqrs(opt => opt services.AddCqrs(opt => opt
.AddQueryHandler<SomeQueryHandler>() .AddQueryHandler<SomeQueryHandler>()
.AddCommandHandler<SomeCommandHandler>() .AddCommandHandler<SomeCommandHandler>()
.AddBehaviour<SomeBehaviour>() .AddBehavior<SomeQueryBehavior>()
.AddOpenBehaviour(typeof(SomeOpenBehaviour<,>)) .AddOpenBehavior(typeof(LoggingBehavior<,>))
); );
``` ```
## Example Usage
### Define a Query and Handler
```csharp
record GetUserByIdQuery(int UserId) : IKnownQuery<User>;
class GetUserByIdQueryHandler : IQueryHandler<GetUserByIdQuery, User>
{
public ValueTask<User> Handle(GetUserByIdQuery query, CancellationToken cancellationToken)
{
// Fetch user logic here
}
}
// Use Dispatcher to execute the query
class GetUserByIdUseCase(IQueryDispatcher dispatcher)
{
public async Task<IResult> Execute(int userId, CancellationToken cancellationToken)
{
var user = await dispatcher.Dispatch(new GetUserByIdQuery(userId), cancellationToken);
}
}
```
\* *the same principles apply to commands*
### Define a Behavior
```csharp
class LoggingBehavior<TRequest, TResult>(ILogger logger) : IDispatchBehavior<TRequest, TResult>
where TRequest: notnull
{
public async ValueTask<TResult> Handle(
TRequest request,
DispatchFurtherDelegate<TResult> next,
CancellationToken cancellationToken)
{
logger.LogInformation("Handling request: {RequestType}", typeof(TRequest).Name);
var result = await next();
logger.LogInformation("Request handled: {RequestType}", typeof(TRequest).Name);
return result;
}
}
class SomeQueryBehavior : IDispatchBehavior<SomeQuery, SomeQueryResult>
{
public async ValueTask<SomeQueryResult> Handle(
SomeQuery request,
DispatchFurtherDelegate<SomeQueryResult> next,
CancellationToken cancellationToken)
{
// do something
return await next();
}
}
```
## License
**Just.Cqrs** is licensed under the [MIT License](LICENSE).

View File

@@ -12,7 +12,7 @@ public delegate ValueTask<TResponse> DispatchFurtherDelegate<TResponse>();
/// <summary> /// <summary>
/// Marker interface for static type checking. Should not be used directly. /// Marker interface for static type checking. Should not be used directly.
/// </summary> /// </summary>
public interface IDispatchBehaviour public interface IDispatchBehavior
{ {
Type RequestType { get; } Type RequestType { get; }
Type ResponseType { get; } Type ResponseType { get; }
@@ -23,7 +23,7 @@ public interface IDispatchBehaviour
/// </summary> /// </summary>
/// <typeparam name="TRequest">Request type</typeparam> /// <typeparam name="TRequest">Request type</typeparam>
/// <typeparam name="TResponse">Result type of dispatching command/query</typeparam> /// <typeparam name="TResponse">Result type of dispatching command/query</typeparam>
public interface IDispatchBehaviour<in TRequest, TResponse> : IDispatchBehaviour public interface IDispatchBehavior<in TRequest, TResponse> : IDispatchBehavior
where TRequest : notnull where TRequest : notnull
{ {
ValueTask<TResponse> Handle( ValueTask<TResponse> Handle(
@@ -32,7 +32,7 @@ public interface IDispatchBehaviour<in TRequest, TResponse> : IDispatchBehaviour
CancellationToken cancellationToken); CancellationToken cancellationToken);
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
Type IDispatchBehaviour.RequestType => typeof(TRequest); Type IDispatchBehavior.RequestType => typeof(TRequest);
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
Type IDispatchBehaviour.ResponseType => typeof(TResponse); Type IDispatchBehavior.ResponseType => typeof(TResponse);
} }

View File

@@ -27,7 +27,7 @@ public static class CqrsServicesExtensions
{ {
services.TryAdd(new ServiceDescriptor(service, impl, lifetime)); services.TryAdd(new ServiceDescriptor(service, impl, lifetime));
} }
foreach (var (service, impl, lifetime) in options.Behaviours) foreach (var (service, impl, lifetime) in options.Behaviors)
{ {
services.Add(new ServiceDescriptor(service, impl, lifetime)); services.Add(new ServiceDescriptor(service, impl, lifetime));
} }
@@ -75,43 +75,43 @@ public static class CqrsServicesExtensions
return options; return options;
} }
public static CqrsServicesOptions AddOpenBehaviour(this CqrsServicesOptions options, Type behaviour, ServiceLifetime lifetime = ServiceLifetime.Singleton) public static CqrsServicesOptions AddOpenBehavior(this CqrsServicesOptions options, Type Behavior, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{ {
var interfaces = behaviour.FindInterfaces( var interfaces = Behavior.FindInterfaces(
static (x, t) => x.IsGenericType && x.GetGenericTypeDefinition() == (Type)t!, static (x, t) => x.IsGenericType && x.GetGenericTypeDefinition() == (Type)t!,
typeof(IDispatchBehaviour<,>)); typeof(IDispatchBehavior<,>));
if (interfaces.Length == 0) if (interfaces.Length == 0)
{ {
throw new ArgumentException("Supplied type does not implement IDispatchBehaviour<,> interface.", nameof(behaviour)); throw new ArgumentException("Supplied type does not implement IDispatchBehavior<,> interface.", nameof(Behavior));
} }
if (!behaviour.ContainsGenericParameters) if (!Behavior.ContainsGenericParameters)
{ {
throw new ArgumentException("Supplied type is not sutable for open behaviour.", nameof(behaviour)); throw new ArgumentException("Supplied type is not sutable for open Behavior.", nameof(Behavior));
} }
options.Behaviours.Add((typeof(IDispatchBehaviour<,>), behaviour, lifetime)); options.Behaviors.Add((typeof(IDispatchBehavior<,>), Behavior, lifetime));
return options; return options;
} }
public static CqrsServicesOptions AddBehaviour<TBehaviour>(this CqrsServicesOptions options, ServiceLifetime lifetime = ServiceLifetime.Singleton) public static CqrsServicesOptions AddBehavior<TBehavior>(this CqrsServicesOptions options, ServiceLifetime lifetime = ServiceLifetime.Singleton)
where TBehaviour : notnull, IDispatchBehaviour where TBehavior : notnull, IDispatchBehavior
{ {
var type = typeof(TBehaviour); var type = typeof(TBehavior);
var interfaces = type.FindInterfaces( var interfaces = type.FindInterfaces(
static (x, t) => x.IsGenericType && x.GetGenericTypeDefinition() == (Type)t!, static (x, t) => x.IsGenericType && x.GetGenericTypeDefinition() == (Type)t!,
typeof(IDispatchBehaviour<,>)); typeof(IDispatchBehavior<,>));
if (interfaces.Length == 0) if (interfaces.Length == 0)
{ {
throw new InvalidOperationException("Supplied type does not implement IDispatchBehaviour<,> interface."); throw new InvalidOperationException("Supplied type does not implement IDispatchBehavior<,> interface.");
} }
foreach (var interfaceType in interfaces) foreach (var interfaceType in interfaces)
{ {
options.Behaviours.Add(( options.Behaviors.Add((
interfaceType, interfaceType,
type, type,
lifetime)); lifetime));

View File

@@ -4,7 +4,7 @@ namespace Just.Cqrs;
public sealed class CqrsServicesOptions(IServiceCollection services) public sealed class CqrsServicesOptions(IServiceCollection services)
{ {
internal readonly List<(Type Service, Type Impl, ServiceLifetime Lifetime)> Behaviours = []; internal readonly List<(Type Service, Type Impl, ServiceLifetime Lifetime)> Behaviors = [];
internal readonly List<(Type Service, Type Impl, ServiceLifetime Lifetime)> CommandHandlers = []; internal readonly List<(Type Service, Type Impl, ServiceLifetime Lifetime)> CommandHandlers = [];
internal readonly List<(Type Service, Type Impl, ServiceLifetime Lifetime)> QueryHandlers = []; internal readonly List<(Type Service, Type Impl, ServiceLifetime Lifetime)> QueryHandlers = [];

View File

@@ -34,12 +34,12 @@ internal sealed class CommandDispatcherImpl(
where TCommand : notnull where TCommand : notnull
{ {
var handler = services.GetRequiredService<ICommandHandler<TCommand, TCommandResult>>(); var handler = services.GetRequiredService<ICommandHandler<TCommand, TCommandResult>>();
var pipeline = services.GetServices<IDispatchBehaviour<TCommand, TCommandResult>>(); var pipeline = services.GetServices<IDispatchBehavior<TCommand, TCommandResult>>();
using var pipelineEnumerator = pipeline.GetEnumerator(); using var pipelineEnumerator = pipeline.GetEnumerator();
return DispatchDelegateFactory(pipelineEnumerator).Invoke(); return DispatchDelegateFactory(pipelineEnumerator).Invoke();
DispatchFurtherDelegate<TCommandResult> DispatchDelegateFactory(IEnumerator<IDispatchBehaviour<TCommand, TCommandResult>> enumerator) => DispatchFurtherDelegate<TCommandResult> DispatchDelegateFactory(IEnumerator<IDispatchBehavior<TCommand, TCommandResult>> enumerator) =>
enumerator.MoveNext() enumerator.MoveNext()
? (() => enumerator.Current.Handle(command, DispatchDelegateFactory(enumerator), cancellationToken)) ? (() => enumerator.Current.Handle(command, DispatchDelegateFactory(enumerator), cancellationToken))
: (() => handler.Handle(command, cancellationToken)); : (() => handler.Handle(command, cancellationToken));

View File

@@ -34,12 +34,12 @@ internal sealed class QueryDispatcherImpl(
where TQuery : notnull where TQuery : notnull
{ {
var handler = services.GetRequiredService<IQueryHandler<TQuery, TQueryResult>>(); var handler = services.GetRequiredService<IQueryHandler<TQuery, TQueryResult>>();
var pipeline = services.GetServices<IDispatchBehaviour<TQuery, TQueryResult>>(); var pipeline = services.GetServices<IDispatchBehavior<TQuery, TQueryResult>>();
using var pipelineEnumerator = pipeline.GetEnumerator(); using var pipelineEnumerator = pipeline.GetEnumerator();
return DispatchDelegateFactory(pipelineEnumerator).Invoke(); return DispatchDelegateFactory(pipelineEnumerator).Invoke();
DispatchFurtherDelegate<TQueryResult> DispatchDelegateFactory(IEnumerator<IDispatchBehaviour<TQuery, TQueryResult>> enumerator) => DispatchFurtherDelegate<TQueryResult> DispatchDelegateFactory(IEnumerator<IDispatchBehavior<TQuery, TQueryResult>> enumerator) =>
enumerator.MoveNext() enumerator.MoveNext()
? (() => enumerator.Current.Handle(query, DispatchDelegateFactory(enumerator), cancellationToken)) ? (() => enumerator.Current.Handle(query, DispatchDelegateFactory(enumerator), cancellationToken))
: (() => handler.Handle(query, cancellationToken)); : (() => handler.Handle(query, cancellationToken));

View File

@@ -42,12 +42,12 @@ public class Dispatch
await commandHandler.Received(1).Handle(testCommand, CancellationToken.None); await commandHandler.Received(1).Handle(testCommand, CancellationToken.None);
} }
public class TestOpenBehaviour<TRequest, TResponse> : IDispatchBehaviour<TRequest, TResponse> public class TestOpenBehavior<TRequest, TResponse> : IDispatchBehavior<TRequest, TResponse>
where TRequest : notnull where TRequest : notnull
{ {
private readonly Action<TRequest> _callback; private readonly Action<TRequest> _callback;
public TestOpenBehaviour(Action<TRequest> callback) public TestOpenBehavior(Action<TRequest> callback)
{ {
_callback = callback; _callback = callback;
} }
@@ -60,7 +60,7 @@ public class Dispatch
} }
[Fact] [Fact]
public async Task WhenPipelineConfigured_ShouldCallAllBehavioursInOrder() public async Task WhenPipelineConfigured_ShouldCallAllBehaviorsInOrder()
{ {
// Given // Given
var testCommand = new TestCommand(); var testCommand = new TestCommand();
@@ -72,15 +72,15 @@ public class Dispatch
.Returns(testCommandResult) .Returns(testCommandResult)
.AndDoes(_ => calls.Add("commandHandler")); .AndDoes(_ => calls.Add("commandHandler"));
var firstBehaviour = Substitute.For<IDispatchBehaviour<TestCommand, TestCommandResult>>(); var firstBehavior = Substitute.For<IDispatchBehavior<TestCommand, TestCommandResult>>();
firstBehaviour.Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>()) firstBehavior.Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>())
.Returns(args => ((DispatchFurtherDelegate<TestCommandResult>)args[1]).Invoke()) .Returns(args => ((DispatchFurtherDelegate<TestCommandResult>)args[1]).Invoke())
.AndDoes(_ => calls.Add("firstBehaviour")); .AndDoes(_ => calls.Add("firstBehavior"));
var secondBehaviour = Substitute.For<IDispatchBehaviour<TestCommand, TestCommandResult>>(); var secondBehavior = Substitute.For<IDispatchBehavior<TestCommand, TestCommandResult>>();
secondBehaviour.Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>()) secondBehavior.Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>())
.Returns(args => ((DispatchFurtherDelegate<TestCommandResult>)args[1]).Invoke()) .Returns(args => ((DispatchFurtherDelegate<TestCommandResult>)args[1]).Invoke())
.AndDoes(_ => calls.Add("secondBehaviour")); .AndDoes(_ => calls.Add("secondBehavior"));
ServiceCollection serviceCollection = ServiceCollection serviceCollection =
[ [
@@ -90,22 +90,22 @@ public class Dispatch
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
new ServiceDescriptor( new ServiceDescriptor(
typeof(IDispatchBehaviour<TestCommand, TestCommandResult>), typeof(IDispatchBehavior<TestCommand, TestCommandResult>),
(IServiceProvider _) => firstBehaviour, (IServiceProvider _) => firstBehavior,
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
new ServiceDescriptor( new ServiceDescriptor(
typeof(IDispatchBehaviour<TestCommand, TestCommandResult>), typeof(IDispatchBehavior<TestCommand, TestCommandResult>),
(IServiceProvider _) => secondBehaviour, (IServiceProvider _) => secondBehavior,
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
new ServiceDescriptor( new ServiceDescriptor(
typeof(IDispatchBehaviour<,>), typeof(IDispatchBehavior<,>),
typeof(TestOpenBehaviour<,>), typeof(TestOpenBehavior<,>),
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
]; ];
serviceCollection.AddTransient<Action<TestCommand>>(_ => (TestCommand _) => calls.Add("thirdBehaviour")); serviceCollection.AddTransient<Action<TestCommand>>(_ => (TestCommand _) => calls.Add("thirdBehavior"));
var services = serviceCollection.BuildServiceProvider(); var services = serviceCollection.BuildServiceProvider();
var sut = new CommandDispatcherImpl(services, new ConcurrentMethodsCache()); var sut = new CommandDispatcherImpl(services, new ConcurrentMethodsCache());
@@ -115,11 +115,11 @@ public class Dispatch
// Then // Then
result.ShouldBeSameAs(testCommandResult); result.ShouldBeSameAs(testCommandResult);
await firstBehaviour.Received(1).Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>()); await firstBehavior.Received(1).Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>());
await secondBehaviour.Received(1).Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>()); await secondBehavior.Received(1).Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>());
await commandHandler.Received(1).Handle(testCommand, CancellationToken.None); await commandHandler.Received(1).Handle(testCommand, CancellationToken.None);
calls.ShouldBe(["firstBehaviour", "secondBehaviour", "thirdBehaviour", "commandHandler"]); calls.ShouldBe(["firstBehavior", "secondBehavior", "thirdBehavior", "commandHandler"]);
} }
[Fact] [Fact]
@@ -136,15 +136,15 @@ public class Dispatch
.Returns(testCommandResult) .Returns(testCommandResult)
.AndDoes(_ => calls.Add("commandHandler")); .AndDoes(_ => calls.Add("commandHandler"));
var firstBehaviour = Substitute.For<IDispatchBehaviour<TestCommand, TestCommandResult>>(); var firstBehavior = Substitute.For<IDispatchBehavior<TestCommand, TestCommandResult>>();
firstBehaviour.Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>()) firstBehavior.Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>())
.Returns(args => ((DispatchFurtherDelegate<TestCommandResult>)args[1]).Invoke()) .Returns(args => ((DispatchFurtherDelegate<TestCommandResult>)args[1]).Invoke())
.AndDoes(_ => calls.Add("firstBehaviour")); .AndDoes(_ => calls.Add("firstBehavior"));
var secondBehaviour = Substitute.For<IDispatchBehaviour<TestCommand, TestCommandResult>>(); var secondBehavior = Substitute.For<IDispatchBehavior<TestCommand, TestCommandResult>>();
secondBehaviour.Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>()) secondBehavior.Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>())
.Returns(args => ValueTask.FromResult(testCommandResultAborted)) .Returns(args => ValueTask.FromResult(testCommandResultAborted))
.AndDoes(_ => calls.Add("secondBehaviour")); .AndDoes(_ => calls.Add("secondBehavior"));
ServiceCollection serviceCollection = ServiceCollection serviceCollection =
[ [
@@ -154,22 +154,22 @@ public class Dispatch
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
new ServiceDescriptor( new ServiceDescriptor(
typeof(IDispatchBehaviour<TestCommand, TestCommandResult>), typeof(IDispatchBehavior<TestCommand, TestCommandResult>),
(IServiceProvider _) => firstBehaviour, (IServiceProvider _) => firstBehavior,
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
new ServiceDescriptor( new ServiceDescriptor(
typeof(IDispatchBehaviour<TestCommand, TestCommandResult>), typeof(IDispatchBehavior<TestCommand, TestCommandResult>),
(IServiceProvider _) => secondBehaviour, (IServiceProvider _) => secondBehavior,
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
new ServiceDescriptor( new ServiceDescriptor(
typeof(IDispatchBehaviour<,>), typeof(IDispatchBehavior<,>),
typeof(TestOpenBehaviour<,>), typeof(TestOpenBehavior<,>),
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
]; ];
serviceCollection.AddTransient<Action<TestCommand>>(_ => (TestCommand _) => calls.Add("thirdBehaviour")); serviceCollection.AddTransient<Action<TestCommand>>(_ => (TestCommand _) => calls.Add("thirdBehavior"));
var services = serviceCollection.BuildServiceProvider(); var services = serviceCollection.BuildServiceProvider();
var sut = new CommandDispatcherImpl(services, new ConcurrentMethodsCache()); var sut = new CommandDispatcherImpl(services, new ConcurrentMethodsCache());
@@ -179,10 +179,10 @@ public class Dispatch
// Then // Then
result.ShouldBeSameAs(testCommandResultAborted); result.ShouldBeSameAs(testCommandResultAborted);
await firstBehaviour.Received(1).Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>()); await firstBehavior.Received(1).Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>());
await secondBehaviour.Received(1).Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>()); await secondBehavior.Received(1).Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>());
await commandHandler.Received(0).Handle(testCommand, CancellationToken.None); await commandHandler.Received(0).Handle(testCommand, CancellationToken.None);
calls.ShouldBe(["firstBehaviour", "secondBehaviour"]); calls.ShouldBe(["firstBehavior", "secondBehavior"]);
} }
} }

View File

@@ -3,12 +3,12 @@ using Microsoft.Extensions.DependencyInjection;
namespace Cqrs.Tests.CqrsServicesExtensionsTests; namespace Cqrs.Tests.CqrsServicesExtensionsTests;
public class AddBehaviour public class AddBehavior
{ {
public class TestCommand {} public class TestCommand {}
public class TestCommandResult {} public class TestCommandResult {}
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public class NonGenericTestOpenBehaviour : IDispatchBehaviour<TestCommand, TestCommandResult> public class NonGenericTestOpenBehavior : IDispatchBehavior<TestCommand, TestCommandResult>
{ {
public ValueTask<TestCommandResult> Handle(TestCommand request, DispatchFurtherDelegate<TestCommandResult> next, CancellationToken cancellationToken) public ValueTask<TestCommandResult> Handle(TestCommand request, DispatchFurtherDelegate<TestCommandResult> next, CancellationToken cancellationToken)
{ {
@@ -20,27 +20,27 @@ public class AddBehaviour
[InlineData(ServiceLifetime.Transient)] [InlineData(ServiceLifetime.Transient)]
[InlineData(ServiceLifetime.Scoped)] [InlineData(ServiceLifetime.Scoped)]
[InlineData(ServiceLifetime.Singleton)] [InlineData(ServiceLifetime.Singleton)]
public void WhenCalled_ShouldRegisterDispatchBehaviour(ServiceLifetime lifetime) public void WhenCalled_ShouldRegisterDispatchBehavior(ServiceLifetime lifetime)
{ {
// Given // Given
ServiceCollection services = new(); ServiceCollection services = new();
// When // When
services.AddCqrs(opt => opt services.AddCqrs(opt => opt
.AddBehaviour<NonGenericTestOpenBehaviour>(lifetime)); .AddBehavior<NonGenericTestOpenBehavior>(lifetime));
// Then // Then
services.ShouldContain( services.ShouldContain(
elementPredicate: descriptor => elementPredicate: descriptor =>
descriptor.ServiceType == typeof(IDispatchBehaviour<TestCommand, TestCommandResult>) descriptor.ServiceType == typeof(IDispatchBehavior<TestCommand, TestCommandResult>)
&& descriptor.ImplementationType == typeof(NonGenericTestOpenBehaviour) && descriptor.ImplementationType == typeof(NonGenericTestOpenBehavior)
&& descriptor.Lifetime == lifetime, && descriptor.Lifetime == lifetime,
expectedCount: 1 expectedCount: 1
); );
} }
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public class InvalidTestBehaviour : IDispatchBehaviour public class InvalidTestBehavior : IDispatchBehavior
{ {
public Type RequestType => throw new NotImplementedException(); public Type RequestType => throw new NotImplementedException();
@@ -57,7 +57,7 @@ public class AddBehaviour
// Then // Then
Should.Throw<InvalidOperationException>(() => services.AddCqrs(opt => opt Should.Throw<InvalidOperationException>(() => services.AddCqrs(opt => opt
.AddBehaviour<InvalidTestBehaviour>()) .AddBehavior<InvalidTestBehavior>())
); );
} }
} }

View File

@@ -3,10 +3,10 @@ using Microsoft.Extensions.DependencyInjection;
namespace Cqrs.Tests.CqrsServicesExtensionsTests; namespace Cqrs.Tests.CqrsServicesExtensionsTests;
public class AddOpenBehaviour public class AddOpenBehavior
{ {
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public class TestOpenBehaviour<TRequest, TResponse> : IDispatchBehaviour<TRequest, TResponse> public class TestOpenBehavior<TRequest, TResponse> : IDispatchBehavior<TRequest, TResponse>
where TRequest: notnull where TRequest: notnull
{ {
public ValueTask<TResponse> Handle(TRequest request, DispatchFurtherDelegate<TResponse> next, CancellationToken cancellationToken) public ValueTask<TResponse> Handle(TRequest request, DispatchFurtherDelegate<TResponse> next, CancellationToken cancellationToken)
@@ -19,27 +19,27 @@ public class AddOpenBehaviour
[InlineData(ServiceLifetime.Transient)] [InlineData(ServiceLifetime.Transient)]
[InlineData(ServiceLifetime.Scoped)] [InlineData(ServiceLifetime.Scoped)]
[InlineData(ServiceLifetime.Singleton)] [InlineData(ServiceLifetime.Singleton)]
public void WhenCalled_ShouldRegisterOpenDispatchBehaviour(ServiceLifetime lifetime) public void WhenCalled_ShouldRegisterOpenDispatchBehavior(ServiceLifetime lifetime)
{ {
// Given // Given
ServiceCollection services = new(); ServiceCollection services = new();
// When // When
services.AddCqrs(opt => opt services.AddCqrs(opt => opt
.AddOpenBehaviour(typeof(TestOpenBehaviour<,>), lifetime)); .AddOpenBehavior(typeof(TestOpenBehavior<,>), lifetime));
// Then // Then
services.ShouldContain( services.ShouldContain(
elementPredicate: descriptor => elementPredicate: descriptor =>
descriptor.ServiceType == typeof(IDispatchBehaviour<,>) descriptor.ServiceType == typeof(IDispatchBehavior<,>)
&& descriptor.ImplementationType == typeof(TestOpenBehaviour<,>) && descriptor.ImplementationType == typeof(TestOpenBehavior<,>)
&& descriptor.Lifetime == lifetime, && descriptor.Lifetime == lifetime,
expectedCount: 1 expectedCount: 1
); );
} }
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public class InvalidOpenBehaviour : IDispatchBehaviour public class InvalidOpenBehavior : IDispatchBehavior
{ {
public Type RequestType => throw new NotImplementedException(); public Type RequestType => throw new NotImplementedException();
@@ -53,18 +53,18 @@ public class AddOpenBehaviour
ServiceCollection services = new(); ServiceCollection services = new();
// When // When
var invalidOpenDispatchBehaviourType = typeof(InvalidOpenBehaviour); var invalidOpenDispatchBehaviorType = typeof(InvalidOpenBehavior);
// Then // Then
Should.Throw<ArgumentException>(() => services.AddCqrs(opt => opt Should.Throw<ArgumentException>(() => services.AddCqrs(opt => opt
.AddOpenBehaviour(invalidOpenDispatchBehaviourType)) .AddOpenBehavior(invalidOpenDispatchBehaviorType))
); );
} }
public class TestCommand {} public class TestCommand {}
public class TestCommandResult {} public class TestCommandResult {}
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public class NonGenericTestOpenBehaviour : IDispatchBehaviour<TestCommand, TestCommandResult> public class NonGenericTestOpenBehavior : IDispatchBehavior<TestCommand, TestCommandResult>
{ {
public ValueTask<TestCommandResult> Handle(TestCommand request, DispatchFurtherDelegate<TestCommandResult> next, CancellationToken cancellationToken) public ValueTask<TestCommandResult> Handle(TestCommand request, DispatchFurtherDelegate<TestCommandResult> next, CancellationToken cancellationToken)
{ {
@@ -79,11 +79,11 @@ public class AddOpenBehaviour
ServiceCollection services = new(); ServiceCollection services = new();
// When // When
var nonGenericOpenDispatchBehaviourType = typeof(NonGenericTestOpenBehaviour); var nonGenericOpenDispatchBehaviorType = typeof(NonGenericTestOpenBehavior);
// Then // Then
Should.Throw<ArgumentException>(() => services.AddCqrs(opt => opt Should.Throw<ArgumentException>(() => services.AddCqrs(opt => opt
.AddOpenBehaviour(nonGenericOpenDispatchBehaviourType)) .AddOpenBehavior(nonGenericOpenDispatchBehaviorType))
); );
} }
} }

View File

@@ -42,12 +42,12 @@ public class Dispatch
await queryHandler.Received(1).Handle(testQuery, CancellationToken.None); await queryHandler.Received(1).Handle(testQuery, CancellationToken.None);
} }
public class TestOpenBehaviour<TRequest, TResponse> : IDispatchBehaviour<TRequest, TResponse> public class TestOpenBehavior<TRequest, TResponse> : IDispatchBehavior<TRequest, TResponse>
where TRequest : notnull where TRequest : notnull
{ {
private readonly Action<TRequest> _callback; private readonly Action<TRequest> _callback;
public TestOpenBehaviour(Action<TRequest> callback) public TestOpenBehavior(Action<TRequest> callback)
{ {
_callback = callback; _callback = callback;
} }
@@ -60,7 +60,7 @@ public class Dispatch
} }
[Fact] [Fact]
public async Task WhenPipelineConfigured_ShouldCallAllBehavioursInOrder() public async Task WhenPipelineConfigured_ShouldCallAllBehaviorsInOrder()
{ {
// Given // Given
var testQuery = new TestQuery(); var testQuery = new TestQuery();
@@ -72,15 +72,15 @@ public class Dispatch
.Returns(testQueryResult) .Returns(testQueryResult)
.AndDoes(_ => calls.Add("queryHandler")); .AndDoes(_ => calls.Add("queryHandler"));
var firstBehaviour = Substitute.For<IDispatchBehaviour<TestQuery, TestQueryResult>>(); var firstBehavior = Substitute.For<IDispatchBehavior<TestQuery, TestQueryResult>>();
firstBehaviour.Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>()) firstBehavior.Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>())
.Returns(args => ((DispatchFurtherDelegate<TestQueryResult>)args[1]).Invoke()) .Returns(args => ((DispatchFurtherDelegate<TestQueryResult>)args[1]).Invoke())
.AndDoes(_ => calls.Add("firstBehaviour")); .AndDoes(_ => calls.Add("firstBehavior"));
var secondBehaviour = Substitute.For<IDispatchBehaviour<TestQuery, TestQueryResult>>(); var secondBehavior = Substitute.For<IDispatchBehavior<TestQuery, TestQueryResult>>();
secondBehaviour.Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>()) secondBehavior.Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>())
.Returns(args => ((DispatchFurtherDelegate<TestQueryResult>)args[1]).Invoke()) .Returns(args => ((DispatchFurtherDelegate<TestQueryResult>)args[1]).Invoke())
.AndDoes(_ => calls.Add("secondBehaviour")); .AndDoes(_ => calls.Add("secondBehavior"));
ServiceCollection serviceCollection = ServiceCollection serviceCollection =
[ [
@@ -90,22 +90,22 @@ public class Dispatch
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
new ServiceDescriptor( new ServiceDescriptor(
typeof(IDispatchBehaviour<TestQuery, TestQueryResult>), typeof(IDispatchBehavior<TestQuery, TestQueryResult>),
(IServiceProvider _) => firstBehaviour, (IServiceProvider _) => firstBehavior,
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
new ServiceDescriptor( new ServiceDescriptor(
typeof(IDispatchBehaviour<TestQuery, TestQueryResult>), typeof(IDispatchBehavior<TestQuery, TestQueryResult>),
(IServiceProvider _) => secondBehaviour, (IServiceProvider _) => secondBehavior,
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
new ServiceDescriptor( new ServiceDescriptor(
typeof(IDispatchBehaviour<,>), typeof(IDispatchBehavior<,>),
typeof(TestOpenBehaviour<,>), typeof(TestOpenBehavior<,>),
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
]; ];
serviceCollection.AddTransient<Action<TestQuery>>(_ => (TestQuery _) => calls.Add("thirdBehaviour")); serviceCollection.AddTransient<Action<TestQuery>>(_ => (TestQuery _) => calls.Add("thirdBehavior"));
var services = serviceCollection.BuildServiceProvider(); var services = serviceCollection.BuildServiceProvider();
var sut = new QueryDispatcherImpl(services, new ConcurrentMethodsCache()); var sut = new QueryDispatcherImpl(services, new ConcurrentMethodsCache());
@@ -115,11 +115,11 @@ public class Dispatch
// Then // Then
result.ShouldBeSameAs(testQueryResult); result.ShouldBeSameAs(testQueryResult);
await firstBehaviour.Received(1).Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>()); await firstBehavior.Received(1).Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>());
await secondBehaviour.Received(1).Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>()); await secondBehavior.Received(1).Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>());
await queryHandler.Received(1).Handle(testQuery, CancellationToken.None); await queryHandler.Received(1).Handle(testQuery, CancellationToken.None);
calls.ShouldBe(["firstBehaviour", "secondBehaviour", "thirdBehaviour", "queryHandler"]); calls.ShouldBe(["firstBehavior", "secondBehavior", "thirdBehavior", "queryHandler"]);
} }
[Fact] [Fact]
@@ -136,15 +136,15 @@ public class Dispatch
.Returns(testQueryResult) .Returns(testQueryResult)
.AndDoes(_ => calls.Add("queryHandler")); .AndDoes(_ => calls.Add("queryHandler"));
var firstBehaviour = Substitute.For<IDispatchBehaviour<TestQuery, TestQueryResult>>(); var firstBehavior = Substitute.For<IDispatchBehavior<TestQuery, TestQueryResult>>();
firstBehaviour.Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>()) firstBehavior.Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>())
.Returns(args => ((DispatchFurtherDelegate<TestQueryResult>)args[1]).Invoke()) .Returns(args => ((DispatchFurtherDelegate<TestQueryResult>)args[1]).Invoke())
.AndDoes(_ => calls.Add("firstBehaviour")); .AndDoes(_ => calls.Add("firstBehavior"));
var secondBehaviour = Substitute.For<IDispatchBehaviour<TestQuery, TestQueryResult>>(); var secondBehavior = Substitute.For<IDispatchBehavior<TestQuery, TestQueryResult>>();
secondBehaviour.Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>()) secondBehavior.Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>())
.Returns(args => ValueTask.FromResult(testQueryResultAborted)) .Returns(args => ValueTask.FromResult(testQueryResultAborted))
.AndDoes(_ => calls.Add("secondBehaviour")); .AndDoes(_ => calls.Add("secondBehavior"));
ServiceCollection serviceCollection = ServiceCollection serviceCollection =
[ [
@@ -154,22 +154,22 @@ public class Dispatch
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
new ServiceDescriptor( new ServiceDescriptor(
typeof(IDispatchBehaviour<TestQuery, TestQueryResult>), typeof(IDispatchBehavior<TestQuery, TestQueryResult>),
(IServiceProvider _) => firstBehaviour, (IServiceProvider _) => firstBehavior,
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
new ServiceDescriptor( new ServiceDescriptor(
typeof(IDispatchBehaviour<TestQuery, TestQueryResult>), typeof(IDispatchBehavior<TestQuery, TestQueryResult>),
(IServiceProvider _) => secondBehaviour, (IServiceProvider _) => secondBehavior,
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
new ServiceDescriptor( new ServiceDescriptor(
typeof(IDispatchBehaviour<,>), typeof(IDispatchBehavior<,>),
typeof(TestOpenBehaviour<,>), typeof(TestOpenBehavior<,>),
ServiceLifetime.Transient ServiceLifetime.Transient
), ),
]; ];
serviceCollection.AddTransient<Action<TestQuery>>(_ => (TestQuery _) => calls.Add("thirdBehaviour")); serviceCollection.AddTransient<Action<TestQuery>>(_ => (TestQuery _) => calls.Add("thirdBehavior"));
var services = serviceCollection.BuildServiceProvider(); var services = serviceCollection.BuildServiceProvider();
var sut = new QueryDispatcherImpl(services, new ConcurrentMethodsCache()); var sut = new QueryDispatcherImpl(services, new ConcurrentMethodsCache());
@@ -179,10 +179,10 @@ public class Dispatch
// Then // Then
result.ShouldBeSameAs(testQueryResultAborted); result.ShouldBeSameAs(testQueryResultAborted);
await firstBehaviour.Received(1).Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>()); await firstBehavior.Received(1).Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>());
await secondBehaviour.Received(1).Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>()); await secondBehavior.Received(1).Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>());
await queryHandler.Received(0).Handle(testQuery, CancellationToken.None); await queryHandler.Received(0).Handle(testQuery, CancellationToken.None);
calls.ShouldBe(["firstBehaviour", "secondBehaviour"]); calls.ShouldBe(["firstBehavior", "secondBehavior"]);
} }
} }