As Developers, we build API’s regardless if we intentionally designed them or not – it could be some form of abstraction for data persistence, how you invoke talking to another service that you have internally within your organisation etc.
If we were trying to intentionally try to design a good API, first, we should probably understand what makes a good API – or probably a better start, what is an API anyway?
When it comes to software, APIs are literally everywhere. APIs go hand in hand with one of the most fundamental concepts in computer science: abstraction. Abstraction is just a way of organizing the complexity of a system so that complicated actions can be handled in a simple way. webopedia
I like this definition because it describes an API at it’s most fundamental level, Application Programming Interface – it’s an interface that our program can call to do some action that hides the small details of all the operations required away from us. This allows us to keep focused on the problem at hand, rather than worry about all the little details.
Examples
Before starting to define what a good API means, I wanted to look through some examples of API’s that have been widely adopted by Developers.
jQuery
jQuery was arguably one of the most used library in the web. One of the things that made it so successful is it’s great API.
First, there’s it’s consistency – almost everything in jQuery follows the chainable (also known as fluent) pattern, and the plugin system is easy to use, if you have some application that uses jQuery, you can turn in into a plugin. The guideline for creating plugins follows encourages chainable pattern to keep further consistent in the entire ecosystem.
The selector API is simple, it follows an already familiar concept to anyone that has a little bit of knowledge of CSS selectors, there’s hardly anything new to learn here, so the barrier to learning the selector API is knowing your CSS selectors.
One of the other factors that made jQuery super easy to pick up is the great documentation and great support from the community. Having good documentation for API as comprehensive as jQuery is crucial.
Last, but not least, the main reason why jQuery was very much needed at the time when it was built – this was written in the era when browsers all had their different quirks, the DOM selector API wasn’t great – if you ever wrote plain JavaScript for different browsers at the time – you really appreciate the amount of things jQuery does.
Express.js
Express keeps its API really simple – so simple infact, that there’s only really one concept that you need to know to be effective with express – the middleware.
Pretty much everything that you do in express is done via a middleware. For example, routes are simply a middleware that matches the request route and invokes the function if it’s a match.
One of the great things with keeping an API this simple is that it keeps the patterns used throughout your application very consistent.
ElasticSearch Web API
ElasticSearch’s Web API does not follow REST conventions, and that’s fine. I would argue, they have the best API around for search. They’ve built an interface on top of Lucene with a whole load of other goodies that solves issues around scalability and ease of use. The most brilliant thing is – it does exactly what it sets out to do, even if you started with no knowledge of Lucene, you could still get very far with it.
One of the great things about ElasticSearch is the simplicity of the main concepts that you need to learn – for basic querying, there’s query and filter, and for grouping (bucketing) results, there is aggregations For indexing, there’s mapping, analysers and tokenisers, alias and indexing itself.
Think for a minute how much ElasticSearch does, and how you might solve each one of them individually, and you really start to appreciate everything it does. All the huge problems involved in trying to create an effective search solution, even pagination via API, what sort of caching you’d have to put in place to have quick access etc. and all the scaling issues involved.
ElasticSearch does so many complex operations under the hood, served to us as Developers, in a relatively simple, and flexible API – so much so, that it covers 99% of all search feature usecases.
What’s Good API Design?
From the examples given, we can observe a few things that make up a good API.
1. Consistent
Common trait of all the API’s mentioned as an example is consistency.
This means being consistent as possible with the concepts used throughout your API both in behaviour and naming. For example, if you’re providing some type of fluent API to build up big arguments / objects, then do for all cases given the same criteria.
For naming, you want to use verbs consistently, such as “get”, “update”, “delete” etc. Never have “get” and “retrieve” or “delete” and “remove” to co-exist. This should also apply to concepts, if you name things by patterns that they use as a suffix, such as “Factory”, “Builder”, “Model”, “DTO” etc. or even application or domain specific concepts, stick with the names consistently throughout the API.
In summary, be consistent with:
– Names/Terminologies/Concepts
– Parameter Order
– Patterns used
2. Sane Defaults
Whenever possible and where it makes sense, it’s always good to choose a sane default if no configuration or argument is given. Key word here is definitely “where it makes sense” – there are certain things that you want to be absolutely explicit – for example, configuration for database connection.
What does it mean to have sane defaults? – this means configuration that cover majority of use cases, for example – if I had an API that allowed me to create a local copy of a file from some cloud storage, we can provide a default buffer size that’s performant enough for majority of use cases, and allow the user of the API to change this for their own needs.
3. Simple
Elegance of a solution is usually synonymous with the simplicity of your solution – but what does simplicity really mean in terms of API design?
It achieves the main purpose with minimum surface area
When I say, minimum surface area, it means the minimum you can get away with that still meets the requirements – for example, if I had a requirement to be able to parse a math expression string to it’s a boolean value, then I can simply define an interface as:
interface IMathExpressionEvaluator
{
Task<decimal> Evaluate(string mathExpression)
}
Notice that I did not expose anything about the details about the Parse Tree – it satisfies the requirement as is without any further clutter to the interface – this is the absolute minimum required to satisfy the requirement.
Of course, depending on your requirements – you may also need some extra flexibility, and in some cases, being more flexible by adding some more generic concepts can also be beneficial to making an API easier to use at the same time – there isn’t really a silver bullet here, the advice is design a few ways of that fulfill the same requirements until you arrive to the best version of the API that satisfies the requirements.
The thing with Software, is that we’re in the complexity management business, the more features and the more intricate the features are, the more complexity we accumulate in the form of code – now, as developers – we want to hide away as much of that complexity as we can in the form of abstractions, usually done through API’s – and these API’s will change as we uncover more use-cases – and this is also where extra flexibility comes in.
The other thing to point out is that API as a word is quite generic, and an API is exposed at many levels, so this can be an API that only ever gets exposed to certain parts of your codebase, depending on segregation, as a form of a shared library used by other teams within the organisation, as a callable API within the organisation, or extend far beyond that and serve actual customers from outside the organisation itself. Depending on this, we may have to think about flexibility, this requires some balancing – think too ahead, and you may end up with an over-engineered solution to the problem, think too in the moment requirements, and you risk having to maintain many versions.
Further reading
To explore these ideas in greater detail, I suggest reading Philosophy of Software Design book by John Ousterhout – he advocates this concept of “Shallow interface” and “Deep Modules” approach John gives some great examples in his book – it’s a highly recommended read on this subject.
What are the things you look for in an API?