Inspired by the ideas from "clean architecture" and MediatR.
InteractR is used as a way to create a clean separation between the client and the domain / business logic.
Install from nuget.
PM > Install-Package InteractR -Version 8.0.0
class GreetUseCase : IUseCase<IGreetUseCaseOutputPort> {
public string Name {get;}
public GreetUseCase(name) {
Guard.AgainstNullOrEmpty(name, nameof(name)); // Throw is name is null or empty
Name = name;
}
}
class GreetUseCaseInteractor : IInteractor<GreetUseCase, IGreetUseCaseOutputPort>
{
public Task<UseCaseResult> Execute(GreetUseCase useCase, IGreetUseCaseOutputPort outputPort, CancellationToken cancellationToken)
{
outputPort.DisplayGreeting($"Hello, {useCase.Name}");
return Task.FromResult(new UseCaseResult(true));
}
}
public class ConsoleOutput : IGreetUseCaseOutputPort {
public void DisplayGreeting(string message) {
Console.WriteLine(message);
}
}
// Registration
var resolver = new SelfContainedResolver();
resolver.Register(new GreetUseCaseInteractor());
var interactorHub = new Hub(_resolver);
var console = new ConsoleOutput();
await interactorHub.Execute(new GreetUseCase("John Doe"), console);
// Would display Hello, John Doe in a console application.
public class GreetingPagePresenter : IGreetUseCaseOutputPort, IGreetingPagePresenter {
private string _greeting;
public void DisplayGreeting(string message) {
_greeting = message;
}
...
public GreetingPageViewModel Present() {
var viewModel = new GreetingPageViewModel();
viewModel.Greeting = _greeting;
return viewModel;
}
}
Registration and execution
// Registration
var resolver = new SelfContainedResolver();
resolver.Register(new GreetUseCaseInteractor());
var interactorHub = new Hub(resolver);
var presenter = new GreetingPagePresenter();
await interactorHub.Execute(new GreetUseCase("John Doe"), presenter);
return View(presenter.Present());
InteractR supports a middleware pipeline from 2.0.0 that allowes developers to control the flow of what happends before, after or if a interactor executes at all.
Middleware can perform tasks related to a use case before an interactor executes or after, it can also terminate the pipeline. The letter might be usefull if for example some conditions are not met or a feature-flag is set to off.
As interactors don't produce a return model for what to be displayed, Middlewares cannot manipulate the output directly. However as the OutputPort is part of the method signature the output methods can be called.
You can register 3 types of middleware: Global, Generic and Specific.
By implementing the IMiddleware
interface you can register a middleware handler that is running for ALL usecases.
example for handling FeatureToggles
public class FeatureToggleMiddleware : IMiddleware {
private reaodnly IFeatureTogglesService _featureTogglesService;
/// ...
public Task<UseCaseResult> Execute<TUseCase>(TUseCase usecase, Func<TUseCase, Task<UseCaseResult>> next, CancellationToken cancellationToken)
{
if (_featureTogglesService.OnFor(usecase))
return next.Invoke(usecase);
return new UseCaseResult(false, new List<IUseCaseFailure> {
new UseCaseFailure(UseCaseOffFailureCode, "Usecase Is turned off")
});
}
}
By implementing the IMiddleware<T>
Interface you can register a middleware handler that is running for usecases with a generic type association.
example for handling authorization policies.
public class PolicyHandlerMiddleware : IMiddleware<IHasPolicy> {
private readonly IAuthorizationService _authorizationService;
/// ...
public async Task<UseCaseResult> Execute<TUseCase>(TUseCase usecase, Func<TUseCase, Task<UseCaseResult>> next, CancellationToken cancellationToken)
where TUseCase : IHasPolicy
{
var authResult = await _authorizationService.Authorize(usecase, usecase.Policy);
if (authResult.Succeeded)
return await next(usecase);
return new UseCaseResult(false, new List<IUseCaseFailure> {
new UseCaseFailure(UnauthorizedFailureCode, "failed to authorize user")
});
}
}
public class FooMiddleware : IMiddleware<FooUseCase, IFooOutputPort> {
public Task<UseCaseResult> Execute(FooUseCase usecase, IFooOutputPort outputPort, Func<FooUseCase, Task<UseCaseResult>> next, CancellationToken cancellationToken) {
// Do some stuff before interactor
return next.Invoke(usecase); // remove this to terminate the pipeline.
// Do some stuff after interactor
}
}
var resolver = new SelfContainedResolver();
resolver.Register(new FooMiddleWare());
Or you can register the middleware with any Dependency Injection Container and use either a provided resolver or roll your own.
Autofac - InteractR.Resolver.Autofac
Ninject - InteractR.Resolver.Ninject
StructureMap - InteractR.Resolver.StructureMap
Lamar - InteractR.Resolver.Lamar
- Execute Use Case Interactor.
- Support for pipelines to enable feature flagging / feature toggling.
- Support for Global "Catch all" Middleware in a usecase pipeline
- "Assembly scan" resolver that will auto register interactors in the assemblies.
- Add more "Dependency Injection Container" Resolvers.