-
Has own Visual Studio Addin for easier use.
-
Small size.
-
Capture Business-use-cases and convert it into a modular, highly testable chunk of codes. One step closer to dissecting & migrating monolithic apps. A combination of Command, Request-Response, Mediator and Abstract Class Factory pattern.
-
Provides a common ground for projects with large number of developers.
-
.Net5 is now supported.
- ExecuteAsync will always create a new instance of the engine.
- The lifetime of the engine ends as soon as ExecuteAsync is completed.
- If this behavior does not suit your needs, just use dependency injection via the constructor.
[Fact]
public async Task Command_ShouldBeCalledOnce()
{
var command = new TestAsyncCommand(); //<-Data
await mediator.ExecuteAsync(command); //<-Execute
command.EngineInvocationCount.ShouldBe(1);
}TestAsyncCommand contains the needed data for await mediator.ExecuteAsync(command);.
public class TestAsyncCommand : ICommandAsync
{
public int EngineInvocationCount { get; set; }
/// <summary>
/// This request will be processed by <see cref="TestAsyncCommandEngine"/>.
/// </summary>
public TestAsyncCommand()
{
EngineInvocationCount = 0;
}
}TestAsyncCommandEngine will be auto-discovered and executed by await mediator.ExecuteAsync(command);.
/// <summary>
/// DI signature: ICommandAsyncEngine<TestAsyncCommand>.
/// <inheritdoc cref="ICommandAsyncEngine{TestAsyncCommand}"/>
/// </summary>
public class TestAsyncCommandEngine : ICommandAsyncEngine<TestAsyncCommand>
{
public async Task ExecuteAsync(TestAsyncCommand commandAsync)
{
await Task.Run(() => commandAsync.EngineInvocationCount++);
}
} [Fact]
public async Task Response_ShouldReturnCorrectValue()
{
var request = new TestAsyncRequest(Guid.NewGuid()); //<-Request
var response = await mediator.ExecuteAsync(request); //<-Execute
response.ResponseToRequestId.ShouldBe(request.Id); //<-Return Value
}TestAsyncRequest contains the needed data for TestRequestAsyncEngine.
public class TestAsyncRequest : IRequestAsync<TestAsyncRequest, TestResponse>
{
public Guid Id { get; private set; }
/// <summary>
/// This request will be processed by <see cref="TestRequestAsyncEngine"/>.
/// </summary>
public TestAsyncRequest(Guid id)
{
Id = id;
}
}TestResponse is the return value of await mediator.ExecuteAsync(request);.
/// <summary>
/// TestRequestAsyncEngine return value.
/// </summary>
public class TestResponse : IResponse
{
public Guid ResponseToRequestId { get; } //<-Return Value
public TestResponse(Guid responseToRequestId)
{
ResponseToRequestId = responseToRequestId;
}
}TestRequestAsyncEngine will be auto-discovered and executed by var response = await mediator.ExecuteAsync(request);.
/// <summary>
/// DI signature: IRequestAsyncEngine<TestAsyncRequest, TestResponse>.
/// <inheritdoc cref="IRequestAsyncEngine{TestAsyncRequest, TestResponse}"/>
/// </summary>
public class TestRequestAsyncEngine : IRequestAsyncEngine<TestAsyncRequest, TestResponse>
{
public async Task<TestResponse> ExecuteAsync(TestAsyncRequest request)
{
return await Task.Run(() => new TestResponse(request.Id));
}
}Requires Scrutor for the automated registration.
See IntegrationTestDiFixture.cs for more details.
private static void ConfigureServices(IServiceCollection services)
{
services.Scan(scan => scan
.FromAssemblyDependencies(typeof(IntegrationTestDiFixture).Assembly)
// Register IMediator
.AddClasses(classes => classes.AssignableTo<IMediator>())
.AsSelfWithInterfaces()
.WithSingletonLifetime()
// Register RequestEngines, CommandEngines - Async
.AddClasses(classes => classes.AssignableTo(typeof(IRequestAsyncEngine<,>)))
.AsImplementedInterfaces()
.WithTransientLifetime()
// Register RequestEngines, CommandEngines
.AddClasses(classes => classes.AssignableTo(typeof(IRequestEngine<,>)))
.AsImplementedInterfaces()
.WithTransientLifetime()
// Register CommandEngines - Async
.AddClasses(classes => classes.AssignableTo(typeof(ICommandAsyncEngine<>)))
.AsImplementedInterfaces()
.WithTransientLifetime()
// Register CommandEngines
.AddClasses(classes => classes.AssignableTo(typeof(ICommandEngine<>)))
.AsImplementedInterfaces()
.WithTransientLifetime()
// IDiDiscoverableTransient
.AddClasses(classes => classes.AssignableTo(typeof(IDiDiscoverableTransient)))
.AsImplementedInterfaces()
.WithTransientLifetime()
);
}- Simply inject IMediator in the constructor. See ConsumerClassWithMediator.cs for more details.
public class ConsumerClassWithMediator : IConsumerClassWithMediator
{
private readonly IMediator mediator;
public ConsumerClassWithMediator(IMediator mediator) //<-Inject IMediator
{
this.mediator = mediator;
}
public async Task<TestResponse> DoSomething()
{
var request = new TestAsyncRequest(Guid.NewGuid()); //<-Request
var response = await mediator.ExecuteAsync(request); //<-Execute
return response; //<-Return Value
}
}
- This is the equivalent class without leveraging IMediator. See ConsumerClassWithoutMediator.cs for more details.
public class ConsumerClassWithoutMediator : IConsumerClassWithoutMediator
{
private readonly IRequestAsyncEngine<TestAsyncRequest, TestResponse> engine;
public ConsumerClassWithoutMediator(IRequestAsyncEngine<TestAsyncRequest, TestResponse> engine) //<-Normal dependency injection. Inject engine
{
this.engine = engine;
}
public async Task<TestResponse> DoSomething()
{
var request = new TestAsyncRequest(Guid.NewGuid()); //<-Request
var response = await engine.ExecuteAsync(request); //<-Execute
return response; //<-Return Value
}
}
