When designing an HTTP API you have to decide on its level of granularity. In my experience there are three levels to choose from: The resource, aggregate and facade level. Each level determines how detailed each endpoint is and as a consequence how many endpoints there are. Besides that, each level also determines where most of the integration work takes place.
As a rule of thumb: fine-grained means less details per endpoint, more endpoints and a simpler server-side, because integration is done client-side. Coarse-grained means more details per endpoint, fewer endpoints and a simpler client-side, because integration is done server-side.
As a rule of thumb: fine-grained means less details per endpoint, more endpoints and a simpler server-side, because integration is done client-side. Coarse-grained means more details per endpoint, fewer endpoints and a simpler client-side, because integration is done server-side.
Sidenote: This is a post about systems integration. With integration here I mean the work that is required to combine data from various endpoints and to call endpoints in the correct order in order to create, update or delete data. I assume the work still needs to be done, it just depends if it's done server-side, or if the work is off-loaded to the client.
If you know the needs of your clients (an SPA and mobile apps for example), it can reduce duplication of work by doing some of the integration work server-side and choosing coarser-grained endpoints. If, on the other hand, you're designing a public API and don't know your clients in advance, it makes sense to choose finer-grained endpoints, because that allows for maximum flexibility of the clients.
Resource level
This is the finest level and often at the level of a (relational) database table. Links to other resources are defined based on the URL, rather than embedded in the response. For example:
GET /books/42 { "isbn": "9780007322503", "title": "The Two Towers" }
And:
GET /books/42/authors [ { "id": 99, "name": "JRR Tolkien" } ]
And:
GET /books/42/reviews [ { "id": 1234, "rating": 5, "name": "Boris", "review": "Great read." } ]
Considerations
- Clients have as much control as possible on how they use the API.
- It keeps the server-side implementation simple, but shifts the integration work to the client
- There's the danger of the N+1 problem: If you're returning just the IDs of linked resources, clients need to query for the details of the linked resource for every item returned in order to display them.
- Clients must have a detailed knowledge on how to use the API, for example in which order to call endpoints.
Aggregate level
The aggregate level combines data in a response that logically belongs together. For example:
GET /books/42 { "isbn": "9780007322503", "title": "The Two Towers", "authors": [ { "id": 99, "name": "JRR Tolkien" } ] }
And a separate endpoint, potentially from a different (micro) service:
GET /books/42/reviews [ { "id": 1234, "rating": 5, "name": "Boris", "review": "Great read." } ]
Considerations
- Reduces the number of requests needed in order to populate a view.
- Puts more integration work on the server-side. You'd have to determine which related resources remain linked, which ones are deleted and which ones are added.
Facade level
The facade level is the work of the API gateway (also known as the back-end for front-end). It combines data from multiple aggregate and resource level endpoints into one response in order to render a page fully. I wouldn't recommend putting a facade level endpoint in a 'normal' API. For example:
GET /books/42 { "isbn": "9780007322503", "title": "The Two Towers", "authors": [ { "id": 99, "name": "JRR Tolkien" } ], "reviews": [ { "id": 1234, "rating": 5, "name": "Boris", "review": "Great read." } ] }
Considerations
- Multiple requests can be done by the gateway simultaneously to various back-end services, reducing the number of requests of clients and reducing the total response time to clients.
- Can provide cached or alternative data if any of the internal requests fails.
- It is a single point of failure.
- Might often lead to merge conflicts, because multiple people or teams are working on it at the same time.
Conclusion
In reality I expect to see a combination of the resource and aggregate level within one API where there is a distinction between read endpoints on the aggregate level and create, update and delete endpoints on the resource level. It really depends on the project whether it is worthwhile to introduce an API gateway at the facade level.