Question

I am currently trying to digest the information related to invariants and validation in DDD. If I get it correctly validation is not a concern of the Domain and should be done outside to prevent invariants from ever occurring. On the other hand invariants must be enforced in the domain, particularly in Aggregates.

What confuses me is actually two things:

  • How to differentiate business rules (invariants) from validation
  • How to respect the DRY principle

Let me elaborate on it. Consider we have a Domain Model covering Tenders. Two major actors are bidding process organizer (Organizer) and bidding process participant (Participant). The Organizer publishes a Tender Announcement which contains information about Stage terms and requirements (for example starting maximum price). Tender is a process consisting of several Stages. Every Stage has its terms. The first Stage is “Call for bids”. During this Stage the Participant is allowed to send his offer (Proposal).

There are two basic requirements:

  1. Proposal price must be less than starting maximum price
  2. Participant is allowed to submit his offer only during "Call for bids" Stage

Technically we may implement it like this (Details are omitted):

class SubmitHandler
{

    /**
    * Send proposal
    *
    * @param SubmitCommand $command
    */
   public function execute($command)
   {
       $this->isReadyToBeSend($command);

       $participant = $this->participantRepository->find($command->id);
       $participant->submitProposal();

   }

   private function isReadyToBeSend($command)
   {
        $result = $this->validate($command);
        if (!$result->isValid()) {
            throw new ProposalException($result->getMessages()[0]->getMessage());
        }
   }

   public function validate($command)
   {
       // Here we check if starting price is provided 
       // and it is less than starting maximum price 
       // as well as the Call for bids Stage is still active 
       // so that we are allowed to submit proposals

       return Validator::validateForSending($command);
   }

   public function canBeExecuted($command)
   {
       return $this->validate($command)->isValid();
   }
}

// In the UI we send command to the handler
$commandHandler->handle($submitCommand);


class Participant extends AggregateRoot
{
   public function submitProposal()
   {
      // here we must enforce the invariants
      // but the code seems to be almost the same as
      // in the validator in the Command Handler
      $this->isReadyToBeSent();
   }

   // throws exceptions if invariants are broken
   private function isReadyToBeSent()
   {
       $this->isPriceCorrect();
       $this->AreTermsCorrect();
   }
}

Considering everything mentioned above, what is the subtle difference between invariants and validation in the given context? Should the code be duplicated in the validator and in the Aggregate? (I don't want to inject the validator into the entity)

Thank you very much.

UPDATE:

I suppose I was not clear enough. To cut the long story short I have two things to consider:

  1. The difference between business rules and invariants.
  2. Sticking to DRY violating SRP and vice versa.

I and another fellow developer had a discussion lately and we came to the conclusion which is as follows:

  • Invariants are some rules which must be obeyed independently from business rules. The code might be the same even though conceptually they are two different things.
  • The DRY principle in this context would probably be violated in order to comply with SRP principle.

Correct me if I am wrong.

Was it helpful?

Solution

Despite the fact that I've written a lot of DDD code, I'm frankly still unsure of the terminology and I'm not sure there's a community consensus. I've largely stopped using the jargon of DDD and found I had a lot less gnawing questions like the one you're posing.

So another way to state the problem using practical terms is...

Getting outbid by someone with a lower bid would really piss off your users, as would placing a bid on a closed auction. So we need to make sure that doesn't happen.

Validation when reading data

When you display the screen for the user to enter a bid, you're of course going to have some validation for the user that the bid must be larger than the previous bid (e.g via jQuery) and that a bid can only be accepted when in the CallForBids stage (e.g. by only displaying the form.

You have to do this validation or you'd be giving the user an awfully crappy experience - allowing them to enter a bid only to be told the auction is closed. So we know you have to express those rules in some way when reading data. However, the key thing is this:

You cannot guarantee that whatever you display on the screen based off the information at the time A will be true by the time the user performs an action which writes data at time B.

So the validation here does not have to be airtight. Don't sweat it that much. Even if you screw up by duplicating the logic, we can't 100% guarantee a write will go through anyway.

Validation when writing data

Data on the screen gets stale as we observed above: The user may have entered a bid after the auction has closed or the minimum bid may have gone up since the data was displayed on the screen. Therefore, in order to avoid a system whose state is in violation of the business rules (and therefore is unreliable and has no integrity)...

You have to check against the business rules when writing data and you have to do this within the same transaction or you can't guarantee consistency.

(There's also eventual consistency, but that's a whole other ball of wax that's outside the scope of this answer.)

So what's that mean for you?

  • Your command handler is the aggregate/transactional boundary/whatever the hell people are calling it this week.
  • That command cannot succeed (should throw an exception) if either of those 2 rules are broken. No state should be changed.
  • That command should be assumed to succeed. That is, from your MVC controller do not add any special error handling for a failed command. Command failure is pretty rare, but it will happen from time to time.
  • Any internal implementation doesn't really matter: If you want to have 2 classes, one to enforce each rule, go right ahead. It doesn't really matter to the business as long as the rules are enforced within a single transaction. And that's what you should focus on.
  • And thats what you should be testing as well - only that the exception is thrown when the command's parameters are invalid with respect to the state of the system. (You would test success only if it publishes an event that will trigger other handlers.)

Hope that clears it up.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top