4

I had a discussion about standardizing return structures in an API and the best way to enforce it across our services. The quickest way we went with was just to have our controllers in .NET Web Api return the same templated class so we came up with something like this

class ResponseObject<T> 
{
    public T Data {get;set;}
    public Permissions UserPermissions {get;set;}
    public Message ObjectMessage {get;set;}
    public Status ObjectStatus {get;set;}
}

And each controller returns this strcuture and fills in the common properties. We also made some helper functions so that controllers that shared the same return type T weren't rewriting the same things over and over.

This works OK for the most part but my idea was instead of every controller doing this, to turn it into an action filter.

public class MyResponseHandler : ActionFilterAttribute
{
    public override OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        var objectContext = actionExecutedContext.Response.Content as ObjectContent;
        if(objectContent != null)
        {
            var val = objectContent.Value;
            var type = objectContent.ObjectType;

            actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(actionExecutedContext.Response.StatusCode, 
                                                                                          new ResponseObject<type>(){data = val})
        }
    }
}

For the other properties in the common ResponseObject my idea was that these could also be handled with more action filters in the pipeline to fill them in. I thought this was a good approach because the developers would only have to worry about returning the object they really cared about for the controller and then the pipeline would handle everything the same way for each type of object. In the end we deemed it overkill and there was some concern that the pipeline would have too much logic and decisions made in it leading to a bunch of hard to debug errors. But I'm curious to see what other people thought of this approach or if anyone has done anything similar to enforce return structures in an API.

  • Just curious, what is purpose was for standardising data structures of responses? In most of the cases when you trying to generalize some types, you end up with complicated logic for converting. – Fabio Apr 9 '17 at 2:21
  • I edited my answer for a little more context. We have a couple services that were returning data differently. Some services would embed a permissions property in the object itself, some would create wrapper classes so there were separate data and permissions properties. Our front end and our tests it was becoming a chore to keep track of which API returned which form and worse if different APIs used the same property name for different purposes so we though it would be good to have a standard return type with properties that we're more defined. – ThrowsException Apr 9 '17 at 2:31
  • Seems like this mapping became the part of your business logic. Moving this mapping to action filters, will bound mapping with web API framework and will hide it from developers. In addition you will not be able to unit test controllers for correct response, because real response will be generated later in pipeline. – Fabio Apr 9 '17 at 2:41
  • Yea this was one of the concerns that the pipeline would abstract too much away. I guess the take away like you said is that it's business logic at the point and is just as much a part of retrieving the data as it is figuring out all the other stuff that goes with it. – ThrowsException Apr 9 '17 at 2:47
  • a very interesting problem. i don't know the answer. it gets even more interesting if you are forced to change the return type due to changing requirements - what if one client needs x return type while another needs y return type? – BKSpurgeon Apr 9 '17 at 3:50
2

As with all things of this nature, the answer is: it depends. You are addressing the important thing, which is having a consistent API (there is nothing worse than an API that’s inconsistent in the way you use it) but how you will implement this will of course have advantages and disadvantages. What I suggest is to choose the safest path and the more controllable implementation.

The thing is this: you can't predict the future. By moving this implementation to the pipeline what you are doing is predicting that all your controllers will fit a certain way of working. All should generate the same response structure but some might have particularities in how they do that (what if you always return the same thing but with different permissions? How are you going to control the permissions in the pipeline when your method only returns the response data?). Abstractions leak and when they do people tend to keep the abstraction but hack it to still fit on the new situations.

I’ve used both ways of doing this and I tend to prefer the implementation where you return ResponseObject<T> instead of having the response structure created by the pipeline. It’s more work because you need to always write it by hand. But it’s explicit. With proper helper methods, this becomes easy, even in the future when things you didn’t see coming start to happen.

With the other implementation, what I found out was that you start to put various parts of unrelated logic into this layer and that it becomes harder to trace errors because the response isn’t over when you return from your method.

Like I said, all depends on your application’s complexity and use cases. If your app is simple you can also have this handled in the pipeline and have the implementation remain just a dumb layer. YMMV

  • I figured this would most likely be a YMMV question. I was just wondering if someone could come out and really shoot holes in it to make it a non viable solution. – ThrowsException Apr 11 '17 at 23:38
  • @ThrowsException: All depends on your application’s specifics. Nobody can tell you solution A is good; solution B is bad without fully knowing what you are building. Most of the times you discover that some parts of A might be bad, while some parts of B might be good. And sometimes you find out about it long after you have decided to go with either A, or B. You can make a choice (based on experience, logical reasoning, heuristics, etc) for A or B but you won’t know which is better unless you implement both and then compare results after the project is over. – Bogdan Apr 12 '17 at 12:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.