The slippery slope

# api# architecture# backend# webdev
The slippery slopeJustC

Recently came across a post on LinkedIn related to query params in URI. In very simple words, the...

Recently came across a post on LinkedIn related to query params in URI. In very simple words, the problem was related to GETting alternate representation of a resource.
Let's take an example of simple api call

Request:
GET /users/1

Response:
HTTP/1.1 200 Requested user details
Content-Type:  application/json

{"username": "Name", "displayName": "Display Name"}
Enter fullscreen mode Exit fullscreen mode

If requester is only interested in displayName, the request can be made as.

Request:
GET /user/1?fields=displayName

Response:
HTTP/1.1 200 Requested user details
Content-Type: application/json

{"displayName": "Display Name"}
Enter fullscreen mode Exit fullscreen mode

It looks reasonable. It works.
But what exactly are we asking the URI to represent?
The request simply means, we are looking for a way to apply projection to retrieve only the displayName and it seems legit. Is it though?

Let's look at RFC 3986, especially the part where it talks about syntax of URI or Uniform Resource Identifier.

A URI is composed of five main components, following this hierarchical pattern:

scheme ":" hier-part [ "?" query ] [ "#" fragment ] 
Enter fullscreen mode Exit fullscreen mode
  • Scheme: The protocol used (e.g., http, ftp, mailto). It is case-insensitive.
  • Authority: Usually contains the host (IP or registered name) and optional port and userinfo (e.g., //www.example.com:80).
  • Path: A sequence of segments that identifies a resource within the scope of the scheme and authority.
  • Query: Non-hierarchical data, often key=value pairs, preceded by ?.
  • Fragment: Identifies a secondary resource within the primary one (e.g., a page anchor), preceded by #. It is processed by the user agent, not the server.

Still seems legit until you start see more such patterns

GET /users/1?fields=displayName
Enter fullscreen mode Exit fullscreen mode

How about

GET /users/?sort=
Enter fullscreen mode Exit fullscreen mode

or

GET /users/?filter=
Enter fullscreen mode Exit fullscreen mode

Let's rewrite the the first example little differently

GET /users?id=1&fields=displayName
Enter fullscreen mode Exit fullscreen mode

Now the oddity starts to become visible. id is, obviously, the search param, but fields is a projection. Query params sort, fields, filter, etc. are used as mini-instructions on top of limited set of HTTP verbs. People start using it, and it literally starts shaping up as a standard.

Anything wrong?

Not really. RFC 3986 is clear that stuff following "?" is just bunch of key-value pairs. In fact the entire URI is opaque, if you decipher the RFC. So much so, you would hear

a good REST API treats the query string as a structured DSL (Domain Specific Language) for the resource.

That's the beauty of ReST. Being just a guideline, you see such pragmatic shortcuts that work in real life and often more useful.

RFC allows it.
The concern is not correctness, it's architectural drift.

The Purist view

I must present a different take on the topic, otherwise this post is meaningless. The DSL keywords sort, fields, filter, etc. start crowding the limited set of verbs, and I, as a developer of API, am now forced to maintain documentation around them. Also is a fact that these mini-instructions become part of set of reserved words.

  • URI -> DSL
  • DSL -> Implicit contract
  • Contract -> Hidden protocol

If projection is a representation concern rather than resource selection, then HTTP already provides a mechanism: content negotiation.

GET /users/1
Accept: application/json; fields=displayName
Enter fullscreen mode Exit fullscreen mode

You might have seen this in a different form as

GET /users/1
Accept: application/json; q=0.9;charset=UTF-8
Enter fullscreen mode Exit fullscreen mode

The string following the media type can be used to convey additional hints about the media-type interpretation. The key-value pairs that trail the media-type are conveniently called as media-type parameters. These are open-ended too, you can have as many as you prefer, just have server implementation to process them.

You want more standard driven approach, follow RFC 6906. It adds more structure to asking for representation variations

GET /users/1
Accept: application/json; profile="http://www.example.com/profiles/user-summary"
Enter fullscreen mode Exit fullscreen mode

Or you can come up with your own custom media-type

GET /users/1
Accept: application/vnd.user.displayname+json
Enter fullscreen mode Exit fullscreen mode

Then why is it not used?

There are few very practical reasons rooted in the way web works.

Links are essential part of creations responses. It is much easier to convey the newly created url as

Location: /user/1?fields=displayName
Enter fullscreen mode Exit fullscreen mode

than bunch of additional instructions to the client on top of regular GET. You would need to enrich the response with more headers.

Caches are not smart enough to understand these nuances. One has to sprinkle "Vary:" headers to make caches understand what to cache. It is more of hit and miss with different cache implementations.

Purpose of this post is not advocacy of purist approach. I just want to make a point that "Common practice does not automatically make it architectural practice." The roots should not be forgotten and pragmatic shortcuts must be explicitly registered as shortcuts than standard. Once the real reason for the shortcuts is forgotten, soon a slippery slope makes it faster to lose the grip of standards.

“Architecture erodes not through wrong decisions, but through forgotten reasons.”