1

Good morning,

Let's say, we've a domain defining an exception such as ObjectNotFoundException which expect an identifier (VO), defined at the domain model.

Question

Can we throw domain exceptions from the request handlers directly, for instance:

    class ObjectRequestHandler implements RequestHandler
    {
        ...

        public function __invoke(Request $request, Response $response)
        {
            // Will self-validate and throw an exception if not a valid UUID
            $objectId = ObjectId::fromString(strval($request->param('object_id'])));

            $object = $this->repository->find((string)$objectId);

            if (NULL === $object) {
                // Exception defined at the domain level...
                throw new ObjectNotFoundException($objectId);
            }

            ...
        }
}

Doing this also lead to usage of the identifier VO in the request handler... It MUST be also noted that the throwed exception will be catched by the default exception handler which in turn, will prepare and send a JSON response.

Finally, note that the request handler here, is an implementation detail, not part of the question. Please don't comment about it.

Thank you.

1 Answers1

2

Your example shows the correct usage of the repository to fetch an object from the data store based on an identifier.

Let's unpack and expand the workflow a little more to fit the paradigms of DDD, to help answer the question:

  • API Controller (or Request Handler) would invoke an Application Service with request params sent by the callee.
  • Request params forwarded to the Application Service can be simple data (like JSON) or can be objects (like DTOs)
  • Application Service has access to the correct repository associated with the object.
  • Repositories are outside the domain layer
  • Application Service would load the objects into memory using these repositories before handing over the control to (or invoking a method in) the domain layer.
  • The ObjectNotFound error is thrown typically from the repository if no object is found for the given identifier
  • The domain layer typically receives all objects it needs to work on from the Application Service or builds objects using factory methods.
  • The actual process is all about assigning or transforming attribute values according to business rules while ensuring invariant rules are satisfied. So the kind of errors that Domain Layer throws is Business Rule Errors (or Validation Errors).

So,

  • ObjectNotFoundException is not a Domain Exception
  • You are not at the domain level yet, so calling the identifier as a ValueObject is incorrect

Ignoring Application Services for a moment, you are spot on in your usage of the concept. The code example is correct in structure. It's just the terms that need to be clarified.

Subhash
  • 3,121
  • 1
  • 19
  • 25
  • The exception is defined at the domain layer. This exception is also thrown from the command handlers such as the EditObjectCommand. Commands are part of the domain. This explain why I've asked if I can throw such exception also from the request handler directly, mostly when I've a GET request. In several examples, I've seen that NULL is returned from the repositories. My repository (the interface) is part of the domain but implemented at the infrastructure layer. You say that usage of identifier VO in request handler is bad. So, how I should process instead? Thank you. – Laurent DECLERCQ a.k.a Nuxwin Jul 21 '19 at 17:22
  • 1
    Let me clarify. When I say Domain layer, I mean Aggregates, Entities, Value Objects, and Domain Services. These are the artifacts used to express the domain in the purest way possible. Everything else is just details, including which database to use, which API framework fits best, and is there a cache involved. By that logic, infrastructure services do not belong in the domain layer. – Subhash Jul 21 '19 at 17:30
  • 1
    Even Commands do not belong to the domain following this logic. Commands are typically received from external sources and are used to derive the exact method or process to call in the domain layer. A Command Object is handled by either the Controller (Request Handler in your case) or Application Service to determine which domain method to invoke. And the invocation would be done on pre-built objects. For example, if the command object contained an identifier, the Application service would load the object from the repository and invoke a method on that object. – Subhash Jul 21 '19 at 17:32
  • 1
    The most important thing to understand is that the domain layer does not deal with or know about non-existent objects. Take a concrete example. Let's take a business rule for filling a shipping container with objects. If a new object needs to be added to the container, the domain layer would have rules about volume, size, shape, type of objects that would fit into the container, and take a call whether the new object would fit into it. It wouldn't worry about whether the container is actually present in the first place, or if the object to be added is theoretical or actually present. – Subhash Jul 21 '19 at 17:35
  • 1
    So an ObjectNotFoundException could only occur outside the domain layer, even before the domain layer is touched. – Subhash Jul 21 '19 at 17:36
  • Yes, so basically put.... I'll move the commands at the application layer... and the exception too. Then I'll throw the exception from application layer if needed, right? Thank you. – Laurent DECLERCQ a.k.a Nuxwin Jul 21 '19 at 17:38
  • 1
    Using the identifier VO from the request handler is not bad. The dependency still points inward, from the request handler to the domain. But consider for a moment what a `ValueObject` is. It is a business concept (like Account Balance, Email or a UUID identifier) with a specific structure and some rules associated with it. So it makes sense to call something a `ValueObject` only at the domain layer. Outside the domain layer, you could use the same `ValueObject` class/artifact, but you wouldn't be using for it for it's `ValueObject` properties. Let me know if that doesn't make sense. – Subhash Jul 21 '19 at 17:46
  • I think yes, in the request handler context, that would be a simple DTO, right? – Laurent DECLERCQ a.k.a Nuxwin Jul 21 '19 at 17:47
  • 1
    Yep! It would be a simple DTO. – Subhash Jul 21 '19 at 17:49