9

I'm learning DDD and I'm thinking about throwing exceptions in certain situations. I understand that an object can not enter to a bad state so here the exceptions are fine, but in many examples exceptions are throwing also for example if we are trying add new user with exists email in database.

public function doIt(UserData $userData)
{
    $user = $this->userRepository->byEmail($userData->email());
    if ($user) {
        throw new UserAlreadyExistsException();
    }

    $this->userRepository->add(
        new User(
            $userData->email(),
            $userData->password()
        )
    );
}

So if user with this email exists, then we can in application service catch a exception, but we shouldn't control the operation of the application using the try-catch block.

How is best way for this?

  • 1
    Having domain service (handlers) throwing exceptions is fine. – Andy Mar 2 '18 at 18:37
16

Let's begin this with a short review of the problem space: One of the fundamental principals of DDD is to place the business rules as closely as possible to the places where they need to be enforced. This is an extremely important concept because it makes your system more "cohesive". Moving rules "upwards" is generally a sign of an anemic model; where the objects are just bags of data and the rules are injected with that data to be enforced.

An anemic model can make a lot of sense to developers just starting out with DDD. You create a User model and a EmailMustBeUnqiueRule that gets injected with the necessary information to validate the email. Simple. Elegant. The issue is that this "kind" of thinking is fundamentally procedural in nature. Not DDD. What ends up happening is you are left with a module with dozens of Rules neatly wrapped and encapsulated, but they are completely devoid of context to the point where they can no longer be changed because it's not clear when/where they are enforced. Does that make sense? It may be self-evident that a EmailMustBeUnqiueRule will be applied on the creation of a User, but what about UserIsInGoodStandingRule?. Slowly but surely, the grainularization of extracting the Rules out of their context leaves you with a system that is hard to understand (and thus cannot be changed). Rules should be encapsulated only when the actual crunching/execution is so verbose that your model starts to loose focus.

Now on to your specific question: The issue with having the Service/CommandHandler throw the Exception is that your business logic is starting to leak ("upwards") out of your domain. Why does your Service/CommandHandler need to know an email must be unique? The application service layer is generally used for coordination rather than implementation. The reason for this can be illustrated simply if we add a ChangeEmail method/command to your system. Now BOTH methods/command handlers would need to include your unique check. This is where a developer may be tempted to "extract" an EmailMustBeUniqueRule. As explained above, we don't want to go that route.

Some additional knowledge crunching can lead us to a more DDD answer. The uniqueness of an email is an invariant that must be enforced across a collection of User objects. Is there a concept in your domain that represents a "collection of User objects"? I think you can probably see where I'm going here.

For this particular case (and many more involving enforcing invariants across collections) the best place to implement this logic will be in your Repository. This is especially convenient because your Repository also "knows" the extra piece of infrastructure necessary to execute this kind of validation (the data store). In your case, I would place this check in the add method. This makes sense right? Conceptually, it is this method that truly adds a User to your system. The data store is an implementation detail.

  • Could I ask what do you mean by Moving rules "upwards" is generally a sign of an anemic model? Upwards means to the application layer ? or to the domain model ? – Anyname Donotcare Sep 2 '18 at 23:40
  • 1
    “Upwards” means towards your application layer (think of a layered architecture). I touched on this above, but the reason moving rules upwards is a sign of an anemic model is that it represents the separation of data and behavior in a way that defeats the entire purpose of having a core domain model. If validating an email is in a separate module than sending an email, your Email model must be a bag of data that is inspected for invariants before sending. See: softwareengineering.stackexchange.com/questions/372338/… – king-side-slide Sep 3 '18 at 14:26
  • 1
    Moving Domain Logic To Repository is wrong . you should enforce domain rule by aggregate root in domain layer . – Arash Nov 19 '18 at 11:29
  • @Arash Set validation is tricky, and often doesn't play well with DDD. You are left with either choosing to validate that some process will work before attempting that process (adding a User), or accepting that this specific kind of invariant won't be neatly encapsulated in your domain. The former represents a separation of data and behavior resulting in a procedural paradigm. This is less than ideal. Validation should exist with data, not around data. Furthermore, what benefit is there to create a domain service that only serves to check this one rule? Ostensibly, this service ... – king-side-slide Nov 19 '18 at 17:59
  • @Arash couples your Repository, User, and this invariant and therefore does not achieve the purist vision you are pushing for anyway. Repository implementations are part of the domain, and therefore can be given certain responsibilities. – king-side-slide Nov 19 '18 at 18:04

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.