Patterns and Abstractions
My central architectural pattern of choice is CQRS.
Commands
Actions that change something and return nothing
public interface ICommandHandler<TCommand> where TCommand : ICommand
{
void Run(TCommand command);
}
Queries
Actions that return something and change nothing
public interface IQuery<TResult>
{
}
public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Execute(TQuery query);
}
Mediators
A combination of commands and queries
There are two mediator abstractions. The first mediates calls to commands and handlers and ultimately returns nothing
public interface IBusinessTransaction
{
}
public interface IBusinessTransactionHandler<TRequest> where TRequest : IBusinessTransaction
{
void Negotiate(TRequest request);
}
The second mediates calls to commands and handlers and ultimately returns something
public interface IBusinessTransaction<TResponse> : IBusinessTransaction
{
}
public interface IBusinessTransactionHandler<TRequest, TResponse> where TRequest : IBusinessTransaction<TResponse>
{
TResponse Negotiate(TRequest request);
}
Events (Observers)
Side effects
public interface IEvent
{
}
public interface ISubscriber<TEvent> where TEvent : IEvent
{
void Handle(TEvent param);
}
Here are some example events: here we have 3 generic events to cater for pre and post an activity. These events are designed to be triggered before and after calling each QueryHandler<,>
and CommandHandler<>
.
public sealed class OnBefore<TRequest> : IEvent
{
public OnBefore(TRequest response)
{
this.Request = response;
}
public TRequest Request { get; private set; }
}
public sealed class OnAfter<TRequest> : IEvent
{
public OnAfter(TRequest request)
{
this.Request = request;
}
public TRequest Request { get; private set; }
}
public sealed class OnAfter<TRequest, TResponse> : IEvent
{
public OnAfter(TRequest request, TResponse response)
{
this.Request = request;
this.Response = response;
}
public TRequest Request { get; private set; }
public TResponse Response { get; private set; }
}
Decorators
Cross cutting concerns
Decorators can be logically divided into 2 groups.
Specific cross cutting concerns (A Strategy)
Decorators that affect only one thing. Rather than open up the existing fully unit tested code (in doing so we would break the S and the O in the SOLID principles) we wrap existing code with a decorator.
General cross cutting concerns (Aspect Oriented Programming)
Code that should be applied to all (or the majority of) a specific service type. User activity logging is an example of this. All commands (and queries) can be wrapped with a generic decorator that records certain details on the requested action.