How not to design Rest services?
This is not a theoritical introduction to Rest - there’s something called the ‘Great Internet’ for that. But, what I do want to (cautiously) write about is how I mess up the design principles carefully laid out by smarter people.
Practice 1: Use verbs in resource names
We typically use the following API names -
GET https://api.com/contact
PATCH https://api.com/contact
This is not a problem in the normal course of designing services. But, when it is time to get some complex queries - I always do a rain dance and come up with stupid names.
GET https://api.com/getAccountSalesActivitySummaryForMonth
Best practice
- do not use verbs, use nouns
- represent the resource in the name (if there is more than one resource, pick your favourite)
- use simple URLs
Practice 2: Patterns that no one understands
APIs like below are partly descriptive but make it difficult to maintain consistency in naming conventions.
GET https://api.com/getAccountSalesActivitySummaryForMonth
POST https://api.com/createMaintenanceActivitiesForQuarter
PUT https://api.com/updateAssignmentForDay
Best practice
- already covered in the previous best practice ( I had a brain freeze while starting practice 2 )
Practice 3: Use param in place of a proper URI
If I need a specific activity for a contact -
GET https://api.com/activities/?q={contactId='1-ABC'}
Why do this? Well, I can reuse services on the server side and write same services to -
- bring in the activities for the particular contact (or)
- for any of the thousand queries that a client throws at me.
The practice of using ids as queries is not “standard” and not clearly understood by everyone. I live in constant fear of the maintenance guy ringing my doorbell.
Best practice
The following is most often seen to fetch related records.
GET https://api.com/contact/1-ABC/activities
Practice 4: Use PATCH and PUT interchangeably
I tend to use -
- Use PATCH if you are incrementally updating the record
- Use PUT when you want the new record to completely update the previous record - lock, stock and without id (may be?)
But, I have not strictly adhered to the said practice.
There are many who disagree with the PUT/PATCH argument and have been waging a war about where to use what.
Best practice
Follow the above method or otherwise. But, just follow ONE way of doing things.
If you are working in ASP.NET that scaffolds PUT even for a PATCH - get prepared for end of the world. Change the scaffolding or change the tech stack.
Practice 5: Create distinct APIs for role-based filters
I don’t quite know when I started this bizarre practice.
Let say I have to filter data to address the below requirements -
- A rep should see her records
- A manager should see her records and her reps’ records
- A CEO should see no records until the company gets bankrupt
I create three APIs -
GET https://api.com/my-deal/
GET https://api.com/mgr-deal/
GET https://api.com/no-deal/
Internally these fetch data from the same service (getDealsForRole
), but only after setting the appropriate filters.
Why do that? I find it easier to manage security and filters in controller/services rather than routes. The resources also are self-contained to manage errors, and can apply complex filter criteria to client-calls or calls from other services (e.g. manager should see reps’ deals but not her private activities).
The problem of applying role-based filters is easily done by using a standard ACL or even by introducing filters, but not all tech stacks have standard ACLs and the damage has already been done for quite a few applications.
Best practice
- Use standard ACLs - if your tech stack supports it. Else, pray and use below alternatives.
- Apply security at the gate and this is at the route-level.
- You could also develop ACL such that the filters get applied without service explicitly requesting for it
Practice 6: Stuff response with everything, you may not get a second chance
My typical service gets to serve related records along with the main entity.
For e.g. -
GET https://api.com/account
The account request may be stuffed to fetch account, account’s contacts and deals.
Normally I would avoid fetching all the data that the client did not request for, but I just hate to do too many requests to server. Without all the data, the client has to fetch -
- list of records
- optionally the detail record at some point of time
- related records
Rather than see the streamlined “fetch”, I include contacts and deals along with the account request. And, before becoming fully aware of what I did - I start throwing queries at database that use some weird joins to fetch all kinds of records everywhere.
- Close coupling of client-server is a problem if you have to serve multiple types of clients. Should never be used for public services
- will reduce traffic but increases payload
- sooner of later I will design a “simpler” service since the main service took so long to fetch results (and the madness continues)
Best practice
Keep your services contained and don’t let them go crazy.
There is nothing wrong in firing a query to get details (they are separate components anyway).
Finis
I will stop here.
Not because I don’t know what other mistakes I make. It is just that I realized how little I know after going through this post more than once.
It is probably time for me to hit that refresh button. I am switching to RPC y’all.