Action-Domain-Responder, Content Negotiation, and Routers
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.
Read the Reddit discussion about this post here.