Action-Domain-Responder, Content Negotiation, and Routers

By | July 17, 2014

While talking about Action-Domain-Responder on the Crafting Code Tour, one of the common questions I got was: “Where does content negotiation happen?” My response was always: “Where does it happen in Model-View-Controller?” That opened up a discussion on how content negotiation is a tricky bit that can go in different places, depending on how you want the concerns separated, and is not a problem specific to ADR.

However, I’ve not really been satisfied with that outcome. I enjoyed the question and the discussion, but it never seemed to resolve itself. We were left with this tension between resource conservation and proper separation of concerns. Should negotiation happen in the the Action (Controller), the Domain (Model), or the Responder (View)?

At first it seems like this is clearly a (re)presentation issue, and as such ought to go in the Responder or View. But if the Responder cannot present an acceptable content type for the request, that means we have done a lot of work in the Domain to build objects that will be discarded in favor of a “406 Not Acceptable” response. This is not a good use of our limited resources.

Perhaps the Domain is the place for negotiation? I think we can dismiss this outright. The Domain should not be in charge of returning different presentations of its data.

Finally, we might try negotiation in the Action (Controller). Here we examine the request, and query the Responder to see what content types it can present in responses. (Alternatively, we embed the available content types in both the Action and Responder, duplicating that information somewhat.) If the negotiation fails in the Action, we skip the Domain work and instruct the Responder to return a “406 Not Acceptable”. But that means the Action is now responsible for at least a little bit of the response-building logic. It’s not horrible, but it does not seem as clean as it could be.

After thinking about this for a while, I am beginning to think it is reasonable to perform what I will call a “first filter” on the Accept header at the Front Controller level, specifically in the Router. We already consider the Router as a guard to map incoming requests to appropriate Actions, inspecting the path, HTTP method, and other request information. Inspecting the acceptable types seems a reasonable addition to these elements.

A full content negotiation at the Router level is probably overkill. Really, all the Router needs to know is what content types are provided through particular Route (whether an MVC or ADR one). The matching logic can do a naive check of the Accept request header to see if one of the provided types is present with a non-zero “q” value. If none of the types is present, the Router can move along to the next route, possibly tracking the failure so a Dispatcher can directly invoke a Responder for routing failures. This way, the Router never invokes a non-matching Action, thereby conserving the Domain resources. If the match is successful, the Responder can do the “real” content negotiation work, using an Accept header value passed to it as input from the Action along with the Domain data.

As a proof of concept, I have modified the Aura.Router library to recognize “accept” specifications on the route, and the tests indicate it seems to work just fine.

14 thoughts on “Action-Domain-Responder, Content Negotiation, and Routers

  1. Matthew Weier O'Phinney

    For Apigility, we went with a similar direction. Since we’re using the ZF2 router currently, we had two options: (1) create custom routes that not only incorporated path matching, but also Accept header matching, or (2) move it into a listener after route matching to determine if we can provide the representation (this is similar to your idea of a “first filter” in some regards. We chose the second, as the first would have required providing an alternate router capable of tracking route matches that cannot fulfill the request.

    By moving the content negotiation outside of controllers or views, we can then alter the service container to tell it to use a different view renderer based on the results — and, as you note, we can return a 406 response early, before any heavy lifting is done.

    My personal take is that content negotiation falls outside of ADR — it becomes the responsibility of the Front Controller, as the results of content negotiation affect which ADR combination will be invoked (primarily by dictating what collaborators will be responsible for the response).

    Reply
    1. pmjones Post author

      First, I failed to note that you talked about this topic to me several months ago, providing the seed for my followup experimentation. Thanks for that.

      > My personal take is that content negotiation falls outside of ADR — it becomes the responsibility of the Front Controller, as the results of content negotiation affect which ADR combination will be invoked (primarily by dictating what collaborators will be responsible for the response).

      I think that’s a fair take. I think it’s also fair to assert that the negotiation per se is a presentation concern, but as you note, if one can negotiate the presentation layer in advance, the separation of concerns is still maintained.

      Finally, note that the Router-level matching is not negotiation proper, but more like a pre-filter that makes sure negotiation (when it occurs) will be successful one way or another.

      Again, and as always, thank you for your continued work and insights!

      Reply
  2. Dustin Wheeler

    I think you’re spot-on with it being somewhere in-between Front Controller and an Action. We approached this problem exactly as you presented in this article and have now arrived at a comfortable space whereby we reason about resources and their representations as truly separate things and it’s not just about presentation. To me, a custom media type is a technical mechanism to describe a specific projection or **aspect** of an individual resource.

    This not only means that the projected attributes might differ, but that now we have an opportunity to model richer use-cases / interactions with “RESTful” resources. You can, of course, take this to an unhealthy extreme and do some particularly horrid things, but that’s the case with anything.

    I’d be interested in exploring some specific cases if this seems “on the mark”. We have a bunch of ideas, but have simply been lining them up and feeling our way through what “feels right” strategically and over time.

    Reply
  3. Gary Hockin

    It’s probably worth checking out the prototype “Dash” router at https://github.com/DASPRiD/Dash. The readme says ZF3 but realistically this is designed to be a drop in router for any project. It has some scope for what you are suggesting here; for example it can return an UnsuccessfulMatch object for when the route url was matched but not the method (https://github.com/DASPRiD/Dash/blob/master/src/Dash/Router/MatchResult/UnsupportedRequest.php) – this allows for short circuiting the controller/view layers (in traditional MVC) or in your ADR model it would allow short circuiting the domain straight to the relevant responder.

    I’m very much interested in what you’re proposing here. Dash seems to be moving too many things into the domain of the router for some – I’ve questioned some of the additions myself, but I think that as a “first filter” matching method in the router is entirely sane.

    Reply
  4. Pingback: Paul Jones: Action-Domain-Responder, Content Negotiation, and Routers | facebooklikes

  5. Ian Littman

    Thanks for adding addAccept() into Aura.Router! May end up using that.

    Now for an architectural question: should a Responder be able to handle multiple content types? If so, and if you’ve got actions that are focused enough to always use one responder, the action could have the responder injected at construction time. The responder is in turn passed Accept-* headers. At that point the responder has what it needs to know whether it can deliver acceptable content, and can throw an exception (which can get turned into a 406) if negotiations break down.

    I’m thinking about something along these lines:

    https://gist.github.com/iansltx/2e22a69109f61c2cb839

    Now, the question becomes how specific responders should be. Is handing off a collection or a single instance of an object to the same responder admissible? What about the aforementioned multiple content types per responder? If Responders are relatively broad, injecting just one of them works. If they’re narrow, a factory or chain (based on negotiation) might work better.

    Reply
    1. pmjones Post author

      > should a Responder be able to handle multiple content types?

      I think that’s reasonable, especially if it is closely related to a single Action. (I thought I had an example of this but I do not.)

      As to the rest, those are implementation details regarding the Responder. Whether it applies broadly or narrowly, to a generic or specific entity (or collection) type, via direct injection or via factory, etc. is less the point than making sure we maintain a fully separated presentation concern. There is a wide range of “admissibility” here. (Hope that makes sense.)

      Reply
  6. Larry Garfield

    I agree that it’s touchy. In Drupal 8 we’ve gone back and forth on how/where to do conneg several times. Right now it’s happening mostly in the routing layer, BUT we also have “wrapping controllers” so that the same controller can serve different responses based on the Accept header. I want to move some of that logic to View listeners (I believe that would be responders in ADR?), but that’s not a high priority at the moment.

    The irony is that “proper” HTTP handling would be more WebMachine-like, which is semantically correct but as you note not necessarily efficient. Fun times. :-)

    Reply
    1. pmjones Post author

      Thanks Andrew!

      As to “why is it called a service instead of a model”: (1) It’s intended as stand-in for a Service Layer, so “Service” seemed appropriate. (2) Under ADR, I am trying to avoid the word “model” in favor of the word “domain”, both to avoid conflating MVC with ADR, and to suggest Domain Driven Design.

      As to “where do security policies go”: As with my original answer on content negotiation, I reply “Where does it go in MVC?” My guess is that there might be a first filter at the front controller level (router? dispatcher?), with the heavy lifting being done at the Domain (or Model) level.

      Finally, as to Australia, I’d love to, but I don’t think Brandon Savage’s 1967 Piper Cherokee will make it without refueling. However, if someone else wants to cover the travel costs … ;-)

      Reply
      1. Andrew Eddie

        Thanks. That makes sense.

        I’ll keep the idea of getting you to Oz in the back of my mind.

        Reply
  7. Andrew Eddie

    And one other thing is where/how would you suggest to introduce security policies (access control for CRUD) into the mix? Thanks.

    Reply
  8. Pingback: Episode 29: Dont Mention PHP 6 v PHP 7 | PHP Podcasts

Leave a Reply

Your email address will not be published. Required fields are marked *