Command Query Separation for .NET and C#
Commands change the state of a system but [traditionally] do not return a value. They write (create, update, delete) data.
Commands implement the marker interface ICommand
and command handlers implement ICommandHandler<in TCommand>
.
public class FooCommand : ICommand
{
public string Value { get; set; }
}
public class FooCommandHandler : ICommandHandler<FooCommand>
{
private readonly ICultureService _cultureService;
public FooCommandHandler(ICultureService cultureService)
{
_cultureService = cultureService;
}
public async Task HandleAsync(FooCommand command, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(command.Value)) throw new FooCommandException("Value cannot be null or empty", 1337, "Try setting the value to 'en-US'");
_cultureService.SetCurrentCulture(command.Value);
await Task.CompletedTask;
}
}
Commands can also return a result.
public class BazCommand : ICommand<Baz>
{
public string Value { get; set; }
}
public class Baz
{
public bool Success { get; set; }
}
public class BazCommandHandler : ICommandHandler<BazCommand, Baz>
{
private readonly ICultureService _cultureService;
public BazCommandHandler(ICultureService cultureService)
{
_cultureService = cultureService;
}
public async Task<Baz> HandleAsync(BazCommand command, CancellationToken cancellationToken)
{
var result = new Baz();
try
{
_cultureService.SetCurrentCulture(command.Value);
result.Success = true;
}
catch
{
// TODO: log
}
return await Task.FromResult(result);
}
}
Commands with result implement the marker interface ICommand<TResult>
and command handlers implement ICommandHandler<in TCommand, TResult>
.
Queries return a result and do not change the observable state of the system (are free of side effects). They read and return data.
Queries implement the marker interface IQuery<TResult>
and query handlers implement IQueryHandler<in TQuery, TResult>
.
public class BarQuery : IQuery<Bar>
{
public int Id { get; set; }
}
public class Bar
{
public int Id { get; set; }
public string Value { get; set; }
}
public class BarQueryHandler : IQueryHandler<BarQuery, Bar>
{
private readonly IDateTimeProxy _dateTime;
public BarQueryHandler(IDateTimeProxy dateTime)
{
_dateTime = dateTime;
}
public async Task<Bar> HandleAsync(BarQuery query, CancellationToken cancellationToken)
{
var result = new Bar { Id = query.Id, Value = _dateTime.Now.ToString("F") };
return await Task.FromResult(result);
}
}