How to design APIs that last

[Caveat: this is focused on HTTP-based, or REST APIs]

In the software world, we often refer to building ‘extensible’ designs. In essence, this means that we can build a system that is light and nimble, capable of changing and growing over time.

In the world of API design, we can’t always make rapid changes. Many API clients become dependent on existing functionality.

Think about this from the perspective of sustainability:
How long can we keep and grow this design?
How long before we have to start over on another version?

old_reliable_api

There are many other factors in wholistic design such as performance, usability, flow and many others. However, there are some initial design principles which help guide those other important aspects. These concepts will serve internal and external facing design well.

Rule #1 of API versioning

We can all agree API versioning rule #1 is not like fight club…we always seem to talk about it.
The practical rule is: don’t, as long as you can.

URI-based, header-based or other (versioning options by @urthen), always strive for long-lived, durable versions. API clients want long-term reliability. Supporting concurrent API versions is an exhausting and expensive undertaking. There are many complexities beyond deploying software and servers with parallel versions. Some of these complexities are solvable, but they’ll impede delivering value.

Here are a few considerations involved with creating a new version:

  • Your developer portals will need info architecture revamps to deal with the extra versions.
  • If one set of resources needs a v2, what’s the plan for all the others? Version completeness at every iteration should be a consideration. Using v11 on one resource while using v1 is likely to cause confusion.
  • Got hypermedia? Questions will arise about which version of any given resource you should be linking to.
  • Deprecation of your v1 APIs will be in your future the moment you release v2.
  • API client communication needs will grow rapidly. Planning for migrations, deprecation, and bugs in new versions will cost time and money.

Keep in mind this doesn’t mean zero change is a good policy.

  • Non-breaking changes mean you can make additive changes.
  • Use caution with any validation changes that could manifest new errors.
  • Don’t add so much to a resource that it’s bloat becomes an issue.

The longer you wait to version, the better your next version will be. More time allows for perspective on feedback about usage of your previous version.

Simply put: version durability is a winning strategy. Accomplishing that durability takes discipline.

More details on versioning risks from @andreaskrohn

Less versions, more URIs

Sometimes the need arises to significantly change the functionality of an existing API. This can mean scaling the resource up or down (see “The Goldilocks principle below”). Other times, it makes sense to massage the usability to fit into a broader flow of API calls.
It’s usually time to consider if the URI is going to change completely. If you’re changing the intent or granularity of the operations, you’re probably looking at a different resource.

This provides the opportunity to deploy parallel APIs on different URIs, all in “v1”. Meanwhile, plan to create a deprecation plan for your old URIs. Always communicate well in advance with your developer communities. If you’ve built something which provides better value on the new URIs, it’s an easier sell. This avoids the Herculean feat of deprecating all your platform’s functionality at once.

Custom media types are notable as a way to creating new functionality on the same URIs. Yet this can definitely lead to some confusion in usability. This is probably why we haven’t seen a broad trend of multiple custom media types on the same URI.

Resource-oriented

When you begin learning REST APIs, the notion of resources appears simple, and yet oddly abstract. The first concept to understand is that APIs are not just table updaters, or data transmission mechanisms. Think from your customer and developer’s perspectives on their goals. Create resources which complement that behavior.

There is a broad notion in the blogosphere that REST is four verbs and they spell CRUD. This is definitely not a truism, as some operations must fit into an RPC-shaped container, whether it feels right or not. Yet the more you can avoid RPC-style design, the longer your API can live and grow.

Think of every ‘controller resource’ as a dead end. You often can’t add sub-resources to RPC-style operations.

As a contrived example, /activate might seem to make sense, but there is no resource identifier. You can’t make up a resource ID to get to /activate/{id}/related-subresource. Additionally, how do you know about all the activation operations that took place over time?

Learn from history

When an RPC design seems to make the most sense, ask questions about the operation’s history. Who will want to know how often this action took place? Often, creating a CRUD-style resource allows for future query ability of this history of those items.

In our prior /activate example, let’s make our /activations more resource oriented as /activations. Nouns serve us better for the sake of history than verbs. Now we can query out /activations?start_date=2014-03-01 and gain insight on prior activations. For internal administrative use, this design extension is often invaluable. Resource-orientation avoids duplicating resource elements and adding complexity by extending your design with /activation-history.

Additionally, now we can add sub-resources. /activations/{id}/devices is elegant. To achieve this you must think about resource identifiers early on in your system design.

The principle here is that you can’t fit everything into a CRUD shape, but you should usually try.

The Goldilocks principle

When resource design is on the table, think small, and work your way up…just not too small.

Also use caution in over-granularity of resources, as HTTP chattiness is likely. The most common mistake early on is to map resources to each table in your system. Usable customer-facing resources almost always are a composite of underlying data.

Conversely, starting with monolithic resources creates problems. Think of this as an entropy problem: the more material you put into a resource, the greater the chaos that ensues. There are often potential network and system performance implications, of course. Additionally, large resources often display tight coupled implementations. The underlying systems could be better served in their own bounded contexts.
Any breaking change in one underlying system breaks the entire API.

Time to find middle ground. You can often aggregate smaller resources into larger composites as you identify chatty behavior. An elegant solution is using an orchestration layer. Regardless, the risk of starting with granular resources is your composites will end up more complex. Thinking early about logical bounded contexts in your systems can help define some of these boundaries as well.

Make uniqueness first-class

Since we tend to organize systems and resources with hierarchy it would seem we should manifest that hierarchy in our URIs. As an example, /assemblies/{id}/sub-assemblies/{id} seems natural at first blush. What happens when we have to add parts to our sub-assemblies? Now we’re fringing into usability problems with /assemblies/{id}/sub-assemblies/{id}/parts/{id}.

When API clients use multiple identifiers to interact with resources, they will be force to write state management code to traverse this hierarchy. In our example, to GET the part we need, we would also need to remember the assembly and sub-assembly IDs.

Ensure your resources use unique identifiers as often as possible. If you need to GET a part, it’s simply GET /parts/{id}. If you need to provide relationships between resources, use one level of sub-resources. /sub-assemblies/{id}/parts or even /assemblies/{id}/parts will work just fine, assuming all the resource IDs are unique.

Clearly there are many use cases where this doesn’t work out, and composite identifiers are requisite. As a rule of thumb, it’s a great starting point.

Hide implementation details

We often take for granted underlying architectural components, vendor relationships and internal jargon. Today’s backend is tomorrow’s trash bin. The moment that implementation details make it through to your APIs uniform interface, the trap is sprung. Most often, implementation details leak out in errors. API clients’s app behavior is relying on error objects. If it changes…your clients break.
– Stick with well-known industry terminology when possible
– Always provide errors with specific fault conditions, without referencing any underlying systems or vendors.
– Use caution naming URIs/resources/fields based on product names. Re-branding and product re-org can wreak havoc.

TL;DR

  1. Avoid the complexity of API versioning
  2. Be smart about ‘just right’ sized resources
  3. RPC operations lead to dead ends
  4. Use unique identifiers to interact with a resource
  5. Don’t expose implementation details

Long live v1!

Last updated by at .

6 thoughts on “How to design APIs that last

  1. Pingback: Links of the month (September Edition) | Jan @ Development

  2. Pingback: Windows 10 - The Daily Six Pack: October 2, 2014

  3. Pingback: How to design APIs that last | API Magazine: Ab...

  4. Pingback: Practical Advice - Stages Of The API Lifecycle (Part 1/4)3scale – The API Management Solution

Leave a Reply