Martin Fowler states that,
Command module executes validations and consequential logic
which aligns with every CQRS demo app that I've studied.
That is to say: validation -- does this Jedi exist? -- and consequential logic -- if not, create it -- are all responsibilities of the command(s). The logic is not encapsulated within an interface of a database context. Implementations of CQRS seems to exercise Fowler's opinion on this by having Commands and Queries that contain all the business logic while directly invoking a relatively flat and simple service or database context. As an example,
public async Task<Guid> Handle(GetOrCreateJedi request, CancellationToken cancellationToken) {
var entity = _context.Jedi.FirstOrDefault(j => j.FirstName == request.FirstName
&& j.LastName == request.LastName);
if (entity == null) {
entity = new Jedi()
{
Id = Guid.NewGuid(),
FirstName = request.FirstName,
LastName = request.LastName
};
_context.Jedi.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
}
return entity.Id;
}
// the controller
public async Task<ActionResult> GetOrCreate([FromQuery] GetOrCreateJedi command)
{
var id = await Mediator.Send(command);
return Ok(id);
}
The more traditional MVC approach seems to boil down to a controller invoking a context where the interface either abstracts:
- the underlying business logic: "If the Jedi exists, return it - else, create it and then return it", or
- direct calls to the database.
This allows for much simpler mocking.
public async Task<ActionResult> GetOrCreate(string firstName, string lastName) {
var id = await _jediDbContext.GetJedi(id);
if (id == null) {
id = await _jediDbContext.CreateAndSaveJediAsync(
return Ok(id);
}
//mock
_mockContext.Setup(c => c.Jedi.GetJedi(It.IsAny<Guid>).Returns(... whatever ...);
In the above example, we don't need to worry about mocking everything inside the GetJedi() method. If we had to, we'd find ourselves challenged with trying to mock non-extensible methods like that in _context.Jedi.FirstOrDefault(). I'd like to create interfaces for these database contexts because I would like to construct unit tests with mocking but...
Is it an antipattern in CQRS to create database interfaces that contain business logic?