6

So the scenario is that I'm asked to redesign and refactor a REST API by combining multiple endpoints that do similar tasks.

For example, now I have 10 different endpoints that are all used to upload different resources e.g.upload_job, upload_script, upload_action, upload_sound ... The main idea is to reduce the number of endpoints by combining them in to a single upload endpoint. Where the file uploads to will be determined by the file-type parameter in the POST request body.

This feels like a clear violation of the Single Responsibility Principle so what I'm asking is that if combining multiple upload endpoint into a single upload endpoint is a bad practice?

Also, if it is a bad practice, what are some of the alternatives that can reduce the number of endpoints?

  • 1
    How much do the current endpoints share? File-Upload seems like a natural candidate for "solve the problem once". – whipdancer Mar 4 at 20:15
  • The current endpoints are all named "upload_{insert file type here}" and do common things like error handling by checking file extension types. But they do save the data to different data structures and are formatted differently when stored. All of the data is stored locally on an Android tablet. – lincredibleJC Mar 4 at 21:08
  • 2
    Calling the endpoints "upload_*" smells of an API that isn't actually RESTful. The canonical approach is to use the POST or PUT method to upload, so you'd have a "POST /api/jobs", a "POST /api/scripts" and so on. The actual code in client and server can be largely shared for the different kinds of resources (so there's a single method doing the uploading, which gets parameterized with the resource path and data, and on the server side is a single method that receives an uploaded resource and stores it at the right place. – Hans-Martin Mosner Mar 5 at 11:58
  • When you think about a responsibility, consider the business function. I actually consider a person behind a desk, in comes the first customer... and places a package on your desk with a label, grunts, and walks off. Then comes the next customer, etc... Figure out what is the same and what is different. If its all the same then there is one responsibility here. If it isn't there may be two or more kinds of customer (business function/responsibility). Try and break that down, is it that some customers are ignoring half of the service, or is it that some customers are entitled to more? – Kain0_0 Mar 6 at 4:22
10

At some point, when designing functions, whether they be API endpoints, library methods, etc, you need to determine what a "single responsibility" is. There is no definitive best practice on this kind of thing. Every choice has tradeoffs.

How does the functionality differ between each endpoint? If they share a lot of functionality, it makes sense that they are part of the same responsibility.

Let's take basic arithmetic for example. Add, subtract, multiple, and divide. If my only goal was to reduce the number of endpoints, I'd do:

doMath(string operator, string... operands)

But that endpoint should raise some flags. It's very vague and non-descriptive. Not very ideal. I wouldn't even be able to begin using this function/endpoint without accurate and updated documentation. I could go the complete opposite direction, too, and avoid parameters altogether:

add5and5()
add10and5()
multiple4and8()
...

It should be pretty obvious why this makes little to no sense. Obviously, we need some sort of middle ground:

add(int op1, int op2)
subtract(int op1, int op2)
multiply(int op1, int op2)
divide(int op1, int op2)

Not a bad solution now. But why are 4 endpoints better than 1? Well, we only need to do basic arithmetic. Therefore, we can be reasonably sure our API won't get super bloated later on. However, if we had an API that literally did every kind of math, maybe it actually would make sense to group the basic arithmetic into a single endpoint or function!

Similarly, if you plan on adding more and more "upload" endpoints, your API will start to get a bit bloated unless you combine them. Of course, how bloated your API can be is up to you and your use-case. Figure out how your clients will consume the API to figure that out.

Ask yourself these questions:

  • Do these endpoints share a lot of functionality?
  • Is it likely more "upload" functionality will need to be added to the API?
  • Does the API do more than uploads?
  • Will significant refactoring be required in your consumers if you change the endpoints' signatures in the future?

The more of these questions you can say "yes" to, the more benefit you achieve by reducing the number of endpoints by offloading functionality into your parameter.

  • I'm working on more of an internal debugging dashboard to be served on an Android tablet so the only consumers would be the internal dev and testing teams. They do have similar components like the checking of file extensions, but they process them differently afterward. So, no, they do slightly more than uploads. More upload functionality will definitely be added in the future. With this, I'lll probably merge the endpoints but separate the file processing portions from the uploading. Thanks for the help! – lincredibleJC Mar 4 at 21:23
1

I agree with @Hans-MartinMosner that the restful way is to POST or PUT to a logical resource such as /api/script or /api/sound.

The fact that data (aka files) are different is then modelled by the logical resource name. parameters can then be used that may be specific to the type or may be applicable to all resource types.

It is then a matter of judgement whether you have endpoints with lots of optional parameters that cover a lot of variations of many subresource types that are more specific but that share a lot of similar parameters.

This tweet series has an interesting view in when to break up a method:

when [a variable is] declared then we immediately hit an if, both of whose branches set it, this is a strong clue that the if is an extractable method.

We can generalise that to say that if you have an optional parameter, and immediately have a case statement or if statement that has two paths with an early return its a strong indication that you should consider making it into two endpoints. Yet if it is purely an internal implementation details you wouldn't want to do that as it wouldn't be clear to clients using your API and they would have to read documentation of both methods to understand which to use.

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.