I don’t like writing client apps that use APIs. Writing HTTP client code to talk to APIs is verbose, repetitive, chatty and slow. This is in addition to addressing latency and bandwidth constraints and core functionality of the client app – such as building a snappy UI or supporting some other business use case.
First – the interface mismatch. APIs are almost always designed from the producer’s point of view. If I’m building an API, I would look at my data model, my performance/scalabilty requirements, and design the API to meet the use cases that I think are the most common across all consumers my of my API. That is, my goal would be to maximize the use and reuse of my API. In this process, I am bound to tradeoff special requirements of my consumers and try to stick to a common denominator. This creates a mismatch between what the consumer needs and what I’m offering. Here is an example.
A client app needs to search a list of products to get their IDs, some details and some user generated content like reviews for each product. There are three APIs that the client needs to interact with to get this data – one to search products, one to get details for each product, and another to get reviews for each product.
This is not a made up or hypothetical example. What the client needs is this.
A single API, that takes a keyword, and returns certain fields for each product found – no more or no less.
What the client got are three APIs that give some data that includes what the client needs plus some more. The API design makes sense for the producers of these APIs – search folks are focused on indexing and serving low latecny results, the details API wants to serve all the gazillion details that are relevant to products, and the reviews API wants to focus on low latency writes and potentially stale reads. But, for the consumer, an ideal interface is one that gives just the fields it needs with one single request.
Second – writing client code is slow, repetitive and verbose. For the above example, here is what the client needs to do:
- Call the search API to find products, and extract product IDs from the response.
- Call the details API
ntimes – once per product ID – to get the details, and then extract the fields needed from the response
- Call the reviews API
ntimes – once per product ID – to get reviews, and the extract the fields needed from the response
Unless you got canned SDKs for these APIs, writing code to make
2*n HTTP requests and process the responses can take a while. In one of the Java implementations I looked at, the code was over 300 lines long – that was after excluding the request making and response parsing code which was already factored into into supporting libraries. If the client needs to implement ten such use cases, you got 3000 lines of code just doing data retrieval.
Third – Getting enhancements you need from producer teams slows you down. For my example above, having a bulk API for details and reviews simplifies the client to some extent. But for both good and bad reasons, API changes that the clients need don’t happen immediately. They take time, usually some sprints. Sometimes never. The reality is that teams have their own priorities. Getting a single API for the above won’t likely happen!
Fourth – requests for use cases like this are chatty. Three hundred lines of code may not be a big deal, but making so many HTTP requests is. Here is a common evolution of an implementation. To simplify the discussion, let’s assume that we have bulk APIs for details and reviews.
Take 1: Use blocking IO to make
3 HTTP requests.
Code complexity of this implementation may be low, but it takes
t is the time for each API request.
Take 2: If latency is a concern, parallelize the requests. After the search API returns product IDs, you can make
2 requests in parallel and join the responses.
Code complexity now increases, but it only takes
t1 + max(t) for the whole retrieval, where
t1 is the time taken to search.
Now imagine that the client needs to call yet another API based on review IDs.
Take 3: Orchestrate the retrieval based on dependencies.
In another example I’ve seen recently, a native app makes 17 HTTP requests with some dependencies before painting the UI. The code is over 3000 lines long! Of a team of 3 developers, one developer is dedicated to writing and maintaining this code.
Now move the client farther from the API servers. In addition to the code complexity, the client will have to deal with cost of the network. You may want to move all the request dance to some middle tier that is closer to the API servers than the client.
Fifth – consistent RESTful APIs don’t matter as much as we think. I would love to see every API to be RESTful, consistent, hypertext driven, and more importantly, interoperable. The reality is that getting consistent APIs is hard – particularly those that are done by distributed teams. For the record – I vehemently hate SOAP APIs and the SOAP mindset. I dislike RPC-style operation names tunneled over HTTP. I frown and cringe whenever I see unnecessary custom headers and complicated formats. I wish POST+XML goes away. I wish every API gets rewritten to the modern understanding of HTTP and REST, serving JSON.
But I would rather spend my time enabling interoperability than preaching for consistency.
Hypermedia does not help either. Hypermedia can help navigation among resources, but navigation is not a concern in the above use case. The client app’s challenges are aggregation, parallelization, and orchestration of forks and joins.
So, what can we do about it?
Why would I write a long blog post if I have nothing to offer?
I’m excited to reveal that, at eBay, my team has been working on a new platform to simplify use cases like the above:
- Bring down number of lines of code to call APIs
- Automatically orchestrate API calls as needed
- Create new consumer-centric APIs that wrap existing producer-centric APIs
- Cut down bandwitdh usage and latency
Best of all, this platform will soon be on github. If you are interested in taking a sneak peek and want to provide feedback, please email me (subbu/at/subbu/dot/org) with your github ID. Please see ql.io for more info.