#
You're reading a compendium of Rizqi’s misadventures in design & coding.

Permalink

Response Objects For Your Domain Layer

Represent domain responses as a rich object, not just a return value - and keep your controllers clean!

This looks pretty familiar, doesn’t it?

// vanilla "service" class - replace with handler class if you're allergic and prefer command buses
class  UserService {
    /**
     * @return User
     */
    public function register ($email, $password) {
        $user = $this->userRepository->put(new User($email, $password));
        return $user;
    }

    /**
     * @return User[]
     */
    public function listUsers () {
        return $this->userRepository->getAll();
    }
}

It does some stuff, and then returns a related entity or a collection of them. Maybe some operations don’t return anything at all. Maybe some return a hodge-podge of related items, like a User and an array of Message objects. They return whatever the controller needs. And sometimes, because they return variable results, controllers get a bit messy — let’s grab a user’s inbox:

try {
    $result = $mailService->getInbox($user);
} catch (MailServerUnreachableException $e) {
    return view('inbox.error')->with('message', "Sorry, we're having an issue right now!");
} catch (InboxBannedException $e) {
    return view('inbox.error')->with('message', "Your inbox has been blocked!");
}
// a string response means the user doesn't have an inbox, but we can give them a url to do so
if (is_string($result)) {
    return view('inbox.blank')->with('message', "You don't have an inbox yet!")->with('invite', $result);
} else {
    return view('inbox')->with('inbox', $result)->with('count', $result->getCount());
}

That’s a mess. We’re using exceptions as flow control (more on that in a minute), abusing PHP’s soft typing, and using both try-catches as well as ifs — and it’s not immediately obvious to a person writing a controller like this that they need to check for exception X or condition Y. The domain doesn’t express what it’ll give back, most likely because it was designed with assumptions in the developer’s head. What happens when a InboxMovedException gets thrown? It’s a bug waiting to happen!

There’s a fundamental problem here — the domain isn’t supposed to know what the controller wants.

Response objects encapsulate the result of an operation, with all its properties and entities attached. This means a developer writing a controller only has to look at the response class to know what to expect. Here’s a similar take on the above:

$response = $mailService->getInbox($user);
if ($response instanceof FailedToReachInboxResponse) {
    return view('inbox.error')->with('message', $response->getErrorMessage());
} else if ($response instanceof NoInboxForUserResponse) {
    return view('inbox.blank')
        ->with('message', "You don't have an inbox yet!")
        ->with('invite', $response->getInvitationUrl());
} else {
    return view('inbox')
        ->with('inbox', $response->getMessages())
        ->with('count', $response->getCount());
}

You’ll notice that we created multiple response classes here - a FailedToReachInboxResponse has a message attached, a NoInboxForUserResponse means the user should make an inbox now, and the default — presumably a UserInboxResponse or similar — contains a successful inbox response.

Off the bat, the controller code is simpler — there’s no complicated code flow, we’re just using what class the response is. If you prefer, you can also have them all fit an interface with distinguishing methods, like $response->isOk(). Messages, entities, collections — everything is expressively embedded in the response. Writing new controllers (say, a CLI command) that uses this method is instantly easier.

I particularly like how we avoid the whole “are exceptions for flow control” debate here; some people argue that exceptions should only be used for errors, while others argue that anything “not along the main flow” can be an exception. Here, we have no exceptions — everything is explicitly described for what it is.

In a Command Bus

I’m a fan of the command bus — the single choke point philosophy is really useful. Some command bus proponents suggest that commands should never return a response at all; commands represent a user intention, so they just execute, without necessarily telling the user what came of it (not immediately, at least). This has practical uses: you can easily queue up commands, distribute them to workers, or mail them to Egypt without the user knowing any better.

This doesn’t fly in my work, though.

Where I work, the user almost always needs some sort of response immediately. Even if the job is queued or long-running, the frontend still needs to know what its job ID is, so it can show the user a progress bar off to the side — we don’t want a nurse idling next to a terminal waiting to see if their request for more anesthesia went through. The user needs feedback now, so my commands always return response objects.

We can actually use the command bus to enforce responses; we can build middleware or decorators that detect if a handler is somehow returning something other than a Response interface, which usually indicates a sloppy return 'foo' somewhere by a sleepy developer. This completely guarantees that the controller layer will never see anything but response objects.

If we’re feeling particularly crazy, we can take it a step further (this assumes we’re using Tactician, my favored command bus package):

class NoExceptionsMiddleware implements Middleware
{
    public function execute(Command $command, callable $next)
    {
        try {
            $result = $next($command);
        } catch (Exception $e) {
            return new SystemErrorResponse($e);
        }
        return $result;
    }
}

Now any stray exceptions being triggered in the domain layer, for any reason will be caught and turned into a system error, which your controllers can expect. Be careful, as this might mask issues in the codebase — make sure exceptions have been logged somewhere lower down the stack, before turning them into error responses.

As a View Model Boundary

We can also use response objects to draw a line in the sand for view models. View models are like regular old models - except they ditch the ability to change, and are provided handy methods for display purposes.

You’ve seen this before in a view:

<?php $invoice->isPaid() ? $invoice->formatFee() : "Unpaid" ?>

This means the Invoice model has methods like isPaid() and formatFee(). While the former probably has its uses within the domain, a formatting method really doesn’t belong on the same model where we assign and save changes. Formatting is purely for displaying! And thus we build separate view models:

class InvoiceView {
    function __construct(Invoice $invoice) {
        $this->isPaid = $invoice->isPaid();
        $this->fee = $invoice->getFee();
    }

    public function isPaid() {
        return $this->isPaid;
    }

    public function formatFee() {
        // any extra formatting logic goes here
        return $this->formatMoney($this->fee, '$');
    }
}

In this example I’m copying properties in the constructor — you can see how this gets tedious, so some people just embed the original model within the view model. That’s okay, it just makes some things harder (e.g. serializing the view model).

View models are fantastic for defining a clear boundary for what controllers and views do; you’ll never be worried of a view accidentally mutating a model (or worse, persisting it). And the models themselves are clean from extra formatting fluff your views want.

You can do the conversion straight in a response object:

class InvoiceSentResponse {
    function __construct (Invoice $invoice) {
        $this->invoice = new InvoiceView($invoice); 
    }

    public function getInvoice() {
        return $this->invoice;
    }
}

Usual Disclaimer

This pattern doesn’t fit everywhere. It involves the creation of many different classes (and potentially, interfaces). I use response objects heavily on large, complex codebases with multiple developers on different parts of the ship, where one guy finds himself writing a command handler while the user interface is still being fleshed out.

Similar to the command bus itself, make sure you know what you’re doing before willy-nilly using response objects.

But where they’re useful, they’re so damn good.