26 Feb Building API Products: API First
In this blog series, I want to talk about the experiences I’ve made building an API product and the best practices I learned doing so. This first article is about how we approach API first design and how it helps us build better APIs and is intended for developers who want to build API products and might also be interesting for product owners and testers.
Back in 2002, Jeff Bezos required all teams at Amazon to build APIs and to communicate with other teams exclusively via APIs. The famous “API Mandate” ends with the sentence “Anyone who doesn’t do this will be fired.”, which shows that for Jeff Bezos, APIs were crucial for Amazon’s success.
Building API products – no matter if public or only available internally – are key for many companies to create new revenue streams, reduce costs and to optimize their existing products.
APIs as first-class citizens
Providing easy to use, well documented and reliable APIs – be it for your frontend, other backend services or for external systems – is crucial for building good performing software systems. If you treat your interfaces as second class citizens, sooner or later you will break them or the documentation will be out of sync with the actual implementation. Following an API first approach and elevating your APIs to a first-class citizen status will improve your APIs dramatically.
API first vs code first
Code first means implementing the business logic before defining any interface for the system. Even though you can still have good interfaces for your implementation, the consequence often is that the API design follows the implementation. That’s often very obvious for systems that added their API after the system went first into production. For such systems, it’s hard to create well designed APIs.
When following an API first approach, everything evolves around the API. When we started implementing our first API product, the first file in the repository – besides the autogenerated README – was the API specification in the form of an OpenAPI specification file. Sure, we drafted a process and sketched an order in which an API consumer might call our APIs, and this draft influenced the creation of our first draft of the interface. But in the end, we adapted the process so that we could have a solid API rather than trying to design the API around the initial process draft.
From the OpenAPI definition, we then generated code stubs for our endpoints using the OpenAPI Generator. Yes, the generator has its flaws but changes in the API instantly have an effect on your code and in many cases make your app stop to work, which is a good thing since changes that break our code might also break our consumer’s code. Of course, we could have implemented the endpoint stubs manually and executed tests that check if our implementation meets the specification, which might be a good idea for complex APIs where you reach the limits of the OpenAPI generator. But for our first, rather simple API product, the generator does its job sufficiently well and we decided to go with it.
Providing an interface definition in a programming language agnostic way like OpenAPI also has the advantage that frontend developers, other departments within the company or external partners all use the same single source of truth. They are all able to generate their clients for their specific tech stacks and thus are able to integrate your API easily and conveniently.
But, API first does not end with code generation or writing the API definition first.
Uniform consumer experience
Big software companies like Google who have a big suite of software products have a uniform user experience for most of their products. If you open Google Calendar and Gmail, you can instantly see that both products were developed by the same company even though they were probably developed by different teams. This should be true for our APIs as well. When you have multiple APIs in your portfolio, the developer who wants to integrate your interface into their system should not be able to see that two APIs were implemented by two different teams. That’s why we decided to use an OpenAPI linter that enforces company-wide guidelines we defined.
We’ve decided to fork Zalando’s API guidelines and their linter Zally and adapted the guidelines and the corresponding linter rules for internal use only. By integrating the linter into our CI/CD pipelines, we make sure that our interface specifications meet our guidelines. If teams need changes in those guidelines, it must be a company-wide decision and must not contradict other guidelines or even make the linter fail on existing APIs.
If you break your API, you break your product
APIs might change drastically during the development cycle, especially when you are working with an iterative and incremental approach. This is not that much of a problem until you release your APIs and other systems consume it – be it inside or outside of your company.
Accidentally breaking an API is quite easy. Adding and removing fields to and from data objects, adding values to enums or deleting obsolete code is part of a software developers daily business. If you add a new required column to a database table that already contains data without setting a default value, the database system won’t allow you to apply the change, because it would break the table’s integrity. OpenAPI specifications on the other hand don’t talk back and careless changes will break the API and therefore all systems that consume it. This means that if you break your API, you break your product. Test automation helps us to avoid breaking the API.
In our case, we have API tests in a different repository than the actual application code. Those API tests run after every push to git and will fail if we break the API.
There are other tools that will help you automatically detect breaking changes to your API, like openapi-diff which compares two versions of your specification and will report an error if there are any breaking changes.
When developing API products, the product falls and stands with the API specification. It’s important to treat your API as a first-class citizen so that your consumers will be eager to integrate your product into theirs. Tooling and test automation are a crucial part of building a successful API product. Clear guidelines on how to write interfaces will help you to have a uniform consumer experience across multiple teams and products.
This blog post is only an introduction to a series about building API products. I plan to dive deeper into some of the topics that I only surfaced here in other articles and touch areas that I haven’t mentioned yet.
Stay tuned for the next post in this blog series where we will have a closer look at API test automation.