tl;dr: Instead of inspecting a Domain result to determine how to present it, consider using a Domain Payload Object to wrap the results of Domain interaction and simulataneously indicate the status of the attempted interaction. The Domain already knows what the results mean; let it provide that information explicitly instead of attempting to re-discover it at presentation time.

In Action-Domain-Responder the Action passes input to the Domain layer, which then returns some data for the Action to pass to the Responder. In simple scenarios, it might be enough for the Responder to inspect the data to determine how it should present that data. In more complex scenarios, though, it would make more sense for the Domain to pass back the data in a way that indicates the status of the data. Instead of the Responder inspecting the Domain results, the Domain should tell us what kind of results they are.

For example, let’s look at an example of some older code to update a Blog post. This is MVC-ish code, not ADR code; we’ll refactor along the way.

<?php class BlogController { // POST /blog/{id} public function update($id) { $blog = $this->model->fetch($id); if (! $blog) { // 404 Not Found // (no blog entry with that ID) $this->response->status->set(404); $this->view->setData(array('id' => $id)); $content = $this->view->render('not-found'); $this->response->body->setContent($content); return; } $data = $this->request->post->get('blog'); if (! $blog->update($data)) { // update failure, but why? if (! $blog->isValid()) { // 422 Unprocessable Entity // (not valid) $this->response->status->set(422); $this->view->setData(array('blog' => $blog)); $content = $this->view->render('update'); $this->response->body->setContent($content); return; } else { // 500 Server Error // (i.e., valid data, but update failed for some other reason) $this->response->status->set(500); return; } } // 200 OK // (i.e., the update worked) $this->response->status->set(200); $this->view->setData(array('blog' => $blog)); $content = $this->view->render('update'); $this->response->body->setContent($content); } } ?>

We can see that there is some amount of model work going on here (look for a blog post, attempt to update it if it exists, check for error conditions on the update attempt). There is also some amount of presentation work going on; remember, the view is not the template – the view is the response. So, even though the view templates are separated, the HTTP status codes are also part of the presentation, meaning that there is an insuffcient level of separation of concerns.

In converting this to Action-Domain-Responder, we can pretty easily extract the model work to a Domain, and the presentation work to a Responder, resulting in something like the following. (Note that the Domain layer now adds values to the returned $blog entity to indicate different failure states.)

<?php class BlogUpdateAction { // POST /blog/{id} public function __invoke($id) { $data = $this->request->post->get('blog'); $blog = $this->domain->update($id, $data); $this->responder->setData('id' => $id, 'blog' => $blog); $this->responder->__invoke(); } } class BlogUpdateResponder { public function __invoke() { if (! $this->data->blog) { // 404 Not Found // (no blog entry with that ID) $this->response->setStatus(404); $this->view->setData($this->data); $content = $this->view->render('not-found'); $this->response->body->setContent($content); return; } if ($this->data->blog->updateFailed()) { // 500 Server Error // (i.e., valid data, but update failed for some other reason) $this->response->status->set(500); return; } if (! $this->data->blog->isValid()) { // 422 Unprocessable Entity // (invalid data submitted) $this->response->setStatus(422); $this->view->setData($this->data); $content = $this->view->render('update'); $this->response->body->setContent($content); return; } // 200 OK // (i.e., the update worked) $this->view->setData($this->data); $content = $this->view->render('update'); $this->response->body->setContent($content); } } ?>

But at this point we’re still inspecting the Domain result to see how we should present it. This strikes me as a lot of work to determine something the Domain already knows.

Instead of re-discovering the Domain status in the Responder, we should let the Domain tell us not only the data, but also what to think about that data. The Domain should give us an indication as to what it tried to do, and whether it succeeded or not. Then we can completely skip the inspection of the Domain results and present those results without lots of additional work.

The key to doing this is something called a Domain Payload Object. (Initially I called this a “Domain Result” but my recent reading of Vernon’s Implementing Domain Driven Design revealed the term to me. I love finding the right word for a concept!)

With a Domain Payload, we wrap the Domain results in an object that carries those results for us. We can then extend the semantics of the Domain Payload to tell us what kind of payload it carries. Something as simple as the class name of the Domain Payload can give us that information.

In the ADR example code we will find a series of Domain Payload objects. While these map closely to HTTP response codes for simplicity’s sake, other Domains are very likely to have different kinds of payload statuses. The point is that each Payload object explicitly tells us what the results indicate: entity not found, invalid data in the entity, database error, successful update, and so on.

The BlogUpdateAction remains straightforward. However, the example BlogService‘s update() method now does all the update work, and it wraps all returned results in a Domain Payload that indicates the result status.

Finally, the BlogUpdateResponder, which itself extends an AbstractResponder, can match a Domain Payload class name to a method that presents the payload results.

Voila: no more inspection of the results to figure out presentation. We let the Domain tell us what it tried to do and whether it worked or not (and what the cause of the failure was, if any). At the presentation layer, our Responder can honor (or ignore) that information at its convenience.