What Are the Core Principles of Good API Design?
In an outstanding talk given at Google in 2007, software engineer and technical author, Joshua Bloch, states that APIs are a hugely important business resource. This is primarily because if the API is exposed to customers, they may choose to invest heavily in it, making it harder for them to transition away.
Conversely, Bloch says, “badly designed APIs can cause an endless stream of support calls, making it extremely difficult for a company to make progress.”
Bloch, who led the design and implementation of numerous Java platform features including the Java Collections Framework, further points out that, “Thinking in terms of API design tends to improve the quality of the programs you write.”
Even if, as a programmer, you don’t work on a publicly facing API, you still create APIs all the time. Good programming is modular, and the inter-module boundaries are themselves APIs. Equally, if you work on a modern, distributed, microservices-type system, the service boundaries are again APIs, albeit architected a little differently.
However, API design is an area that many programmers seem to struggle with, so what are the characteristics of a good one?
Names Are Critical
At a high level, the API should be easy to learn and write to, and hard to misuse. Your API will also need to evolve, and a good design takes this into account.
How you name things really matters since an API is, in effect, a small language that its users need to learn.
Richardson drew attention to the fact that for developers, names shape our mental model.
“It is quite a lot of work to go back and change a mental model, not in terms of the code necessarily, but in terms of how you think about things.”
In view of this, it really is worth taking time to come up with a name that very precisely conveys what a given API does.
The standard tools of a writer — a dictionary and a thesaurus — could prove helpful. If you are finding something particularly hard to name, this might indicate that it is trying to do too many things at once. Much as an overly complex sentence might need to be split into two, be prepared to split up an overly complex module if you need to.
Avoid cryptic abbreviations that don’t make sense, and watch out for a lack of consistency, such as using multiple words that mean the same thing. For example, if you need to return the base salary for a given employee, avoid creating two methods, such as
getBaseSalary() — If your API has a
remove() method and a
delete() method, would you know the difference, or even if there is one?
The language used should be internally consistent with any other APIs that the organization or vendor exposes. This need for consistency means it can be helpful to have a degree of centralized governance.
Some larger enterprises, for example, will extend the role of a senior technical writer to help engineering teams with the consistent naming of methods, attributes and fields.
If you are working with a REST-style system, Daniel Bryant, independent consultant and co-author of the O’Reilly book “Mastering API Architecture” suggests looking at a pre-existing set of API guidelines, since these can help you with consistency in API behavior. For an HTTP-based API, OpenAPI is one he recommends for consideration, and there are others including Atlassian, Google and Microsoft.
It is also worth saying that while all APIs need appropriate names, the names themselves are domain-specific; an API written for a quant, for example, would use very different language from an API written for a retailer. Ideally, the chosen terms should match those the business already uses and at the very least understands.
To ensure this, Bryant told The New Stack that it’s best to conduct user research and make sure you cover all the groups that might use the API.
“QA folks will have different ideas about how your API should work compared with how developers would see it,” he said. “I’ve often seen developers design an API without asking who is going to use it, and ending up exposing the internal domain model.”
He recommends thinking in terms of “Jobs-to-be-Done,” as in: What is your key task? What is your workflow? How do you approach it? How would you like to approach it? The last question is key since inertia can build up around well-established processes.
“You can rock people’s worlds if you can simplify stuff down, and there are usually good opportunities when systems have evolved over time,” Bryant said.
The Principle of Least Surprise
Your API should also be idiomatic to the programming language it is written against and respect the way that language works. For example, if the API is to be used with Java, use exceptions for errors, rather than returning an error code as you might in C.
APIs should follow the principle of least surprise. Part of the way this can be achieved is through symmetry; if you have to add and remove methods, these should be applied everywhere they are appropriate.
A good API comprises a small number of concepts; if I’m learning it, I shouldn’t have to learn too many things. This doesn’t necessarily apply to the number of methods, classes or parameters, but rather the conceptual surface area that the API covers. Ideally, an API should only set out to achieve one thing.
It is also best to avoid adding anything for the sake of it. “When in doubt, leave it out,” as Bloch puts it. You can usually add something to an API if it turns out to be needed, but you can never remove things once an API is public.
As noted earlier, your API will need to evolve over time, so a key part of the design is to be able to make changes further down the line without destroying everything.
“Ultimately the key to this is that the API should reflect reality,” Richardson said. “So, for example, if a person can have more than one address or phone number, even if you only care about one at the moment, only allowing for one address isn’t reality. And ignoring reality will always bite you.”
APIs Are Forever
One common antipattern, according to Richardson, is implementing a partial API because that is the only piece you need right now. The danger here is that you don’t think the API through properly and end up with something that doesn’t work in other cases.
“You need to put way more thought into API design than anything else,” Richardson said, “because you don’t get to change it once it’s built.”
A second issue is around encapsulation and the leaking of implementation details.
“If an implementation detail leaks, you can’t change it,” Richardson said. “So you need to think, what operations are going on here? What is the data structure really about?”
Commonly, error handling is a place that gets overlooked. If you are using a database as a backend store, for instance, don’t let a SQL error propagate out, because if you subsequently want to change the storage mechanism, you can’t.
In common with any other aspect of software development, it is a mistake to imagine that you can lock yourself in a room and work on the API in isolation. If you do this, you risk being too invested in your design, even when it goes wrong. Much better to test your ideas frequently with stakeholders, just as you would with any other system.
Before you start coding an API, it is a good idea to write a short spec that you can show to stakeholders, detailing what it does and how it will work. Keeping the spec short makes it more likely it will be read, and prevents you from getting too invested in your solution upfront. If you’ve spent months writing a 100-page spec, you are much less likely to admit that it isn’t any good.
Documentation is one of the most undervalued facets, not only in API design but in computing more generally. Technical writers are frequently undervalued and underpaid, and documentation is treated as an afterthought at best, exemplified by the maxim “the code is the documentation.” This is dangerous nonsense.
While you want your API to be easy to understand and learn, the documentation for it is still extremely important. It should be complete and comprehensive and at the very least cover each method, what every field does and what the error conditions are.
“You want it to list every possible error code that can be returned and under what circumstances,” Richardson emphasized.
Take time to refine and revise the documentation, and avoid common problems such as using acronyms that won’t be readily understood.
Continue to write code against your API as you develop it. Bloch notes that developers often stop part way through the process, but continuing to write to the API throughout implementation gives you a real feeling for when and how it works.
“This code isn’t wasted,” Bloch notes, “since it helps you produce a better product, and also provides a set of examples that other programmers can learn from.”
These examples are critical because they will be copied repeatedly by other developers, and will fundamentally influence how an API is used.