Controllers and Domain Exceptions

A few months ago I had a great email conversation with a correspondent about how to handle business logic exceptions in his controller code. His message follows, lightly edited for brevity and clarity:

I think controller has single responsibility – to mediate communication between caller’s context and actual business logic services. (I believe business logic services should be unaware of caller’s context, be it HTTP request or CLI.)

Given that services should be unaware of who called them, they should not throw HTTP-specific exceptions. So instead of throwing a Symfony HttpNotFound Exception, the service would throw ObjectNotFound (which is HTTP agnostic) in cases where DB record could not be found.

Yet at the same time the logic that converts exceptions to HTTP responses expects HTTP-specific Symfony exceptions. This means that the exception thrown by service needs to be transformed into Symfony exception.

One of solutions I see to this is that the controller could take that responsibility. It would catch domain exceptions thrown by service and wrap them into appropriate HTTP exceptions.

class FooController
{
    public function fooAction()
    {
        try {
            $this->service->doSomething('foo');
        } catch (ObjectNotFound $e) {
            throw new NotFoundHttpException('Not Found', $e);
        } catch (InvalidDataException $e) {
            throw new BadRequestHttpException('Invalid value', $e);
        } // ...
    }
}

The downside I see with this approach is that if I have many controllers I will have code duplication. This also could lead to big amount of catch blocks, because of many possible exceptions that could be thrown.

Another approach would be to not have try/catch blocks in controller and let the exceptions thrown by service bubble up the stack, leaving the exception handling to exception handler. This approach would solve the code duplication issue and many try/catch block issue. However, because the response builder only accepts Symfony exceptions, they would need to be mapped somewhere.

It also feels to me that this way the controller is made cleaner, but part of controllers responsibility is delegated to something else, thus breaking encapsulation. I feel like it’s controllers job to decide what status code should be retuned in each case, yet at the same time, cases usually are the same.

I truly hope you will be able to share your thoughts on this and the ways you would tackle this.

If you find yourself in this situation, the first question to ask yourself is, “Why am I handling domain exceptions in my user interface code?” (Remember: Model-View-Controller and Action-Domain-Responder are user interface patterns; in this case, the user interface is composed of an HTTP request and response.) Domain exceptions should be handled by the domain logic in a domain-appropriate fashion.

My correspondent’s first intuition (using domain-level exceptions, not HTTP-specific ones) has the right spirit. However, instead of having the domain service throw exceptions for the user interface controller to catch and handle, I suggest that the service return a Domain Payload with domain-specific status reporting. Then the user interface can inspect the Domain Payload to determine how to respond. I expand on that approach in this post.

By way of example, instead of this in your controller …

class FooController
{
    public function fooAction()
    {
        try {
            $this->service->doSomething('foo');
        } catch (ObjectNotFound $e) {
            throw new NotFoundHttpException('Not Found', $e);
        } catch (InvalidDataException $e) {
            throw new BadRequestHttpException('Invalid value', $e);
        } // ...
    }
}

… try something more like this:

class FooController
{
    public function fooAction()
    {
        $payload = $this->service->doSomething('foo');
        switch ($payload->getStatus()) {
            case $payload::OBJECT_NOT_FOUND:
                throw new NotFoundHttpException($payload->getMessage());
            case $payload::INVALID_DATA:
                throw new BadRequestHttpException($payload->getMessage());
            // ...
        }
    }
}

(I am not a fan of using exceptions to manage flow control; I’d rather return a new Response object. However, I am trying to stick as closely to the original example as possible so that the differences are more easily examined.)

The idea here is to keep domain logic in the domain layer (in this case, a service). The service should validate the input, and if it fails, return a “not-valid” payload. The service should catch all exceptions, and return a payload that describes the kind of error that occurred. You can then refine your controller to examine the Domain Payload and handle it however you want.

Using a Domain Payload in this way is not a huge leap. Essentially you move from a try/catch block and exception classes, to a switch/case block and status constants. What’s important is that you are now handling domain-level exceptions in the domain and not in the user interface layer. You are also encapsulating the status information reported by the domain, so that you can pass the Domain Payload object around for something other than the controller to inspect and handle.

Encapsulation via Domain Payload opens the path to a more significant refactoring that will help reduce repetition of response-building logic across many controller actions. That next refactoring is to separate out the response-building work to a Responder, and use the Responder in the controller action to return a response. You can then pass the Domain Payload to the Responder for it to handle.

class FooController
{
    public function fooAction()
    {
        $payload = $this->service->doSomething('foo');
        return $this->responder->respond($payload);
    }
}

class FooResponder
{
    public function respond($payload)
    {
        switch ($payload->getStatus()) {
            case $payload::OBJECT_NOT_FOUND:
                throw new NotFoundHttpException('Not Found', $e);
            case $payload::INVALID_DATA:
                throw new BadRequestHttpException('Invalid value', $e);
            // ...
        }
    }
}

Once you do that, you’ll realize the majority of your response-building logic can go into a common or base Responder. Custom cases can then be be handled by controller- or format-specific Responders.

And then you’ll realize all your Action logic is all pretty much the same: collect input from the Request, pass that input to a Domain-layer service to get back a Domain Payload result, and pass that result to a Responder to get back a Response. At that point you’ll be able to get rid of controllers entirely, in favor of a single standardized action handler.

“Action Injection” As A Code Smell

Circumstance has conspired to put Action Injection discussions in front of me multiple times in the past few days. Having seen the approach several times before, I have come to think that if Action Injection is the answer, you might be asking the wrong question. I find it to be a code smell, one that indicates the system needs refactoring or reorganizing.

Action Injection …

As far as I can tell, the term “Action Injection” originates with Alex Meyer-Gleaves in a 2010 article on ASP.NET MVC development. He summarizes Action Injection in this way:

Your [controller class] constructor is provided the dependencies [by the DI container] that are shared by all actions in your controller, and each individual action [method] can request any additional dependencies that it needs.

To expand on that, let’s say you have a controller class with several action methods in it. You realize after a while that the different action methods have slightly different dependencies. For example, some of the methods need a logger, while others need a template system, or access to the router. But you don’t want to pollute the controller class constructor with these method-specific dependencies, since those dependencies will be used only if that particular action method gets invoked.

With Action Injection, when you pull the controller from your dependency injection container and call a particular action method, the DI container will automatically pass the right dependencies for the method call arguments. Voila: now you can define all the common dependencies as parameters on the controller constructor, and all the action-specific dependencies as parameters on the method definition.

You can see a PHP-specific description of the problem, with Action Injection as the solution, in this Symfony pull request:

https://github.com/symfony/symfony/pull/21771

You can also hear about it in this presentation from Beau Simensen, from around 32:21 to 34:31:

https://www.youtube.com/watch?v=JyrgwMagwEM&feature=youtu.be&t=1941

The Yii and Laravel containers appear to support this behavior as well, using the term “method injection.” (I think that’s a misnomer; the term appears overloaded at best, as sometimes it may mean “methods called by the DI container at object-creation time” instead of “resolving method arguments at call-time”.) Perhaps other DI containers support Action Injection as well.

… As A Code Smell

The explicit reason for using Action Injection is “to reduce dependencies or overhead” when constructing an object. You don’t want to have to pass in a dozen dependencies, when only three are used in every method, and the others are used only in specific methods.

But the fact that your controller has so many dependencies, used only in some cases and not in others, should be an indicator that the class is doing too much. Indeed, it’s doing so much that you cannot call its action methods directly; you have to use the dependency injection container not only to build the controller object but also to invoke its action methods.

Reorganizing To Avoid Action Injection

What approaches exist to help you avoid the Action Injection code smell? I assert that the better solution is to change how you organize your controller structures.

Instead of thinking in terms of “a controller class with action methods,” think in terms of “a controller namespace with action classes.” Actions are the targets for your routes anyway, not controller classes per se, so it makes sense to upgrade actions to “first-class” elements of the system. (Think of them as single-action controllers, if you like.)

Thus, instead of …

<?php
namespace App;

class BlogController {
    public function browse() { ... }
    public function read() { ... }
    public function edit() { ... }
    public function add() { ... }
    public function delete() { ... }
}

… reorganize to:

<?php
namespace App\BlogController;

class Browse { ... }
class Read { ... }
class Edit { ... }
class Add { ... }
class Delete { ... }

Then the DI container can use plain old constructor injection to create the Action object, with all of its particular dependencies. To invoke the Action, call a well-known method with the user input from the route or request. (I like __invoke() but others may prefer exec() or something similar.)

In fact, I realized only after watching the Simensen clip a second time that his example is a single-action controller in everything but name. His example code was this …

<?php
class Home {
    /**
     * @Route("/myaction", name="my_action")
     */
    public function myAction(
        Request $request,
        Router $router,
        Twig $twig
    ) {
        if (!$request->isMethod('GET')) {
            return new RedirectResponse(
                $router->generateUrl('my_action'),
                301
            );
        }

        return new Response(
            $twig->render('mytemplate.html.twig')
        );
    }
}

… but the controller action method parameters might just as well be Action class constructor parameters:

<?php
namespace Home;

class MyAction {

    public function __construct(
        Request $request,
        Router $router,
        Twig $twig
    ) {
        $this->request = $request;
        $this->router = $router;
        $this->twig = $twig;
    }

    /**
     * @Route("/myaction", name="my_action")
     */
    public function __invoke() {
        if (!$this->request->isMethod('GET')) {
            return new RedirectResponse(
                $this->router->generateUrl('my_action'),
                301
            );
        }

        return new Response(
            $this->twig->render('mytemplate.html.twig')
        );
    }
}

(UPDATE: That example code looks like it originates from Kevin Dunglas and his DunglasActionBundle — which is itself a single-action controller implementation for Symfony.)

Action Domain Responder

For more on this organizational structure, please read my Action Domain Responder offering. ADR is a refinement of MVC that is tuned specifically to server-side request/response over-the-network interactions.

But you need not go full ADR to avoid Action Injection. Just using single-action controllers will do the trick. Then you can have well-factored single-responsibility controller classes that do not require a DI container in order to call their action methods, and Action Injection becomes a thing of the past.

Toward A Better Separation of Session Behaviors in PHP

Andrew Shell asks, What is the best way to handle sessions with ADR? (The problem is that the built-in PHP session extension combines the concerns of reading input, managing storage, and sending output; the solution is a domain-layer session-data manager.)

I’ve reached a point with a couple of my Radar projects where I need to add a login and set permissions. I’m trying to figure out the best way to handle this, especially with PSR-7 and ADR. …

[In Action-Domain-Responder] it’s ok to read the session cookie in an Input class, and it’s ok to write the cookie in a Responder class, but pretty much everything else should be in the Domain layer. …

[In the Domain layer,] Cadre.DomainSession takes a session id (or generates one) and loads session data from storage. It’s smart enough to handle regenerating session ids and cleaning up expired sessions.

Read the whole article at FutureProof PHP for examples and links!

UPDATE: Reddit discussion.