1

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:

  1. the underlying business logic: "If the Jedi exists, return it - else, create it and then return it", or
  2. 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?

3
+150

What is a Business Relevant Detail, and What is an Implementation Detail?

The Business of the Jedi School is certainly interested in enrolling all Jedi. They certainly have processes where they survey for Jedi, and enrol them if they haven't been enrolled yet.

Now is it a Business Relevant Detail to explain how to:

  • enrol an unenrolled Jedi? Yes.
  • insert a record for that Jedi into a table if that record doesn't already exist? No

The difference is that enrolling a Jedi is a Business domain concern. The Jedi was not enrolled, and there needs to be a process to change that state of affairs. There is probably an interface somewhere with Enrol(Jedi) on it. That is probably implemented by some domain object that details what that means. That domain object directly or through some chain, accesses a JediRepository (or similar). That JediRepository is another interface and there is no reason why it cannot look like:

interface JediRepository
{
    //remembers the Jedi, even if it could already recall them.
    void Remember(Jedi);
    ...
}

or

interface JediRepository
{
    //returns false if it could not be added
    bool Add(Jedi);
    ...
}

or

interface JediRepository
{
    //throws if it could not be added
    void Add(Jedi);
    ...
}

Which one you pick (or something else) entirely depends on what the business rules deem as important, and how those business rules want to discover that information.

  • Do they care about the fact that this Jedi was not in the Repository before?
  • Is it an exceptional occurrence to add the Jedi twice?
  • Are the rules already aware of the fact that the Jedi doesn't exist?
  • Is there some sort of race condition?

In short the operations exposed by the JediRepository are there so that the business rules can easily express themselves. A quick test of how good the interface is is to write the same business rule using different variants of the repository interface. The more concise and readable the business rule without losing business relevant details, the better those operations support the domain.

How JediRepository is implemented is up to you. It could be:

  • a thin wrapper around a stored procedure in some database.
  • a micromanager issuing crud operations to some service (like a database)
  • a wrapper around a flat file
  • or something else entirely

So to bring this all together and answer the question.

Yes it is an anti-pattern to put business -relevant- logic in the database.

BUT

It is good engineering to provide abstracted operations that the Database/Data access Layer implement. Even if they are non trivial.

2

"Business logic" can mean many things. Commonly, it refers to the BLL, i.e. inbetween the DAL and your presentation layer. However, "business logic" can also refer to any particular algorithm that is part of the requirements.

For example, the DAL may be expected to perform auditing on its data. Or maybe the DAL secretly keeps a historical log of all data mutations, unbeknownst to the BLL. That, too, is a form of business logic, if there are business requirements that demand the feature to exist.

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.

Almost with every design question in software development, there is an implicit "it depends on how complex you need it to be" caveat.

Examples are inherently intended to be simple, and therefore not throw more stuff into the mix just for the hell of it. In other words, CQRS examples will generally not bother with detailed DAL layers, as it's simply not the focus of the example.

Don't fall into the trap of building your expectations to match what you've seen in topic-specific examples, because that is a biased observation. In reality, you have multiple designs all flowing together and there is no "there can be only one" attitude, even though examples on a particular topic do tend to only showcase their particular topic.

Whether or not your DAL consists of simple methods that the BLL pieces together, or more complex methods that pre-chew everything, is a design decision you have to make. Like I said, it depends on how complex you need it to be.

For example, if finding out if a Jedi exists requires a lot of steps (check with the Council, check if they are a Force ghost, check for non-native inhabitants of Tatooine with no backstory and a last name that matches a Jedi who has gone AWOL), and this Jedi-lookup logic is used in multiple places, you'll want to abstract that whole lookup process and provide a method that does it all for you. Otherwise, you are very prone to forgetting to perform all checks when you write a new command/query, or to forget updating one of the commands if the lookup logic ever changes.

That is just one prime example of why you'd want your DAL to contain more than just straightforward CRUD methods.

When you mean "business logic" as in BLL logic, then it shouldn't hit the DAL, because it inherently doesn't belong there. But that is not the same as saying that the DAL cannot contain any logic whatsoever. There could be business requirements (or good practice reasons, like above) which lead to the DAL needing to perform some specific logic as well, which you could call "the business logic of the DAL".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.