In creating a development workflow for releasing HTTP+JSON APIs, many teams settle on the API description and documentation standard OpenAPI. A natural conversation that takes place after settling on OpenAPI is whether or not you should develop your API using a “design-first” method or a “code-first” method. I believe that narrowing the discussion to these two extremes overly simplifies the discussion. Instead, I prefer to talk about API development as a continuum of development process that revolve around the prominence of the API description document in the process.
The False Dichotomy: Design-First or Code-First
First let’s describe the model that many teams refer to: either designing your API first by writing an OpenAPI description document before writing any code, or coding your API first by writing an implementation and then developing the OpenAPI description document after the API has been implemented.
The Design-First Process
A design-first API process focuses on designing and documenting the API contract before writing any code to implement it. In this model, the API specification is the artifact used to drive the rest of the development process.
Proponents of this process believe that focusing on designing the API using an OpenAPI description before any implementation is done allows you to get early feedback from API consumers who can view the documentation before the API is built, and who can generate mock servers to implement the client in parallel with the server implementation. In theory, a design-first process can help catch potential problems with the API early and avoids wasting time and money writing code which is not going to solve a problem.
The Code-First Process
A code-first API process focuses on implementing the API first, and then creating the API description document after the implementation. In this model, the API implementation is the source of truth for the API and drives the rest of the development process.
Proponents of this process believe that writing and maintaining an API description document is difficult and error prone, and that it slows down the development process and time to market for new APIs and new product features.
The API Development Process Continuum
Many blog posts (and API management vendors) like to claim that there is a stark contrast between a code-first and a design-first process. I believe that these two approaches actually exist along a continuum and we do ourselves a disservice by trying to draw a clear separation between them and narrowing our discussion to just those two options.
The first change necessary in developing a more realistic model of the API development process is to remove the “design-first” nomenclature. Naming a development process where you specify the API description before writing code a design-first method makes the implicit assumption that any other method leads to APIs that have no design thought put into them. This is misleading because it is equally possible to create a nicely designed API by coding first and then creating the documentation as long as you adhere to the design standards that would need to be put in place for the design-first method anyways.
The second change necessary in developing a more realistic model of the API development process is to acknowledge that there is often not a stark dividing line between design and implementation. By leveraging development frameworks that can automate the tedious and manual work necessary to write an API description document, we can let computers do one of the tasks they are good at (compiling machine readable language), and allow the developer to do what they are good at (designing and implementing a usable API).
Combining these two changes, I ended up with four different development methods across a continuum. This continuum revolves around the centrality of the API description document to the process:
- API Description First
- Coded API Description
- API Framework
- Implementation First
In this continuum I deliberately dropped the terms design-first and code-first to remove the implicit bias around the word design being absent from the code-first model. I also explicitly added the API description document — typically the OpenAPI specification — to the model.
Method 1: API Description First
API Description First involves writing your API description document as the very first step in the development process. You can use this description document to create a mock server early in the development process, get feedback from your customers, and then commit to the final API description before starting an official implementation.
With the API description document in hand, you can kick start the implementation by leveraging code generation projects that generation portions of the server and allow you to focus on implementing the business logic.
Focusing on the API description document first helps establish API design as a first class priority of engineering teams. This framing creates a lot of benefits in how teams think about APIs a core feature of the product they are developing.
The main negative I’ve found when developing API description documents is that it involves writing out a large amount of boilerplate and special keywords in YAML or JSON. This is a tedious and error-prone proposition when done by hand. For example, we can take the ubiquitous Petstore example from the OpenAPI project.
{
"/pet":{
"post":{
"tags":[
"pet"
],
"summary":"Add a new pet to the store",
"description":"",
"operationId":"addPet",
"consumes":[
"application/json",
"application/xml"
],
"produces":[
"application/json",
"application/xml"
],
"parameters":[
{
"in":"body",
"name":"body",
"description":"Pet object that needs to be added to the store",
"required":true,
"schema":{
"$ref":"#/definitions/Pet"
}
}
],
"responses":{
"405":{
"description":"Invalid input"
}
},
"security":[
{
"petstore_auth":[
"write:pets",
"read:pets"
]
}
]
}
}
}
This is only a partially complete API and you can already see the number of lines of JSON you need to write to correctly describe the endpoint. Designing a more complex API with 50 endpoints, or handling multiple endpoints with slight differences between them, will drive the most patient engineering teams crazy.
Coded API Description
Coded API Description involves leveraging a programming language to create your API description. For example, Square has developed a domain-specific language to create their OpenAPI description documents, and projects like spot leverage an existing programming language (TypeScript) to author API descriptions.
Leveraging a programming language to write your API description allows you to use familiar concepts like variables and assignment to reduce the boilerplate necessary in writing a full API description. You also get the advantage of leveraging a compiler that can provide type checking to make sure your API description is syntactically and semantically correct.
The following example API written uses spot and is written in Typescript. TypeScript classes define the API and interfaces are used to represent the request and response bodies. Annotations specific to spot are used to fill out the rest of the API description.
import { api, endpoint, request, response, body } from "@airtasker/spot";
@api({
name: "My API"
})
class Api {}
@endpoint({
method: "POST",
path: "/users"
})
class CreateUser {
@request
request(@body body: CreateUserRequest) {}
@response({ status: 201 })
response(@body body: CreateUserResponse) {}
}
interface CreateUserRequest {
firstName: string;
lastName: string;
}
interface CreateUserResponse {
firstName: string;
lastName: string;
role: string;
}
With this TypeScript code in hand, you can use spot to generate your OpenAPI description document that can be leveraged in an API Description First process. In my opinion, the fact that tools exist explicitly to make authoring OpenAPI descriptions palatable is a testament to the difficulty in using the OpenAPI format in real-world projects.
API Framework
The API Framework method mixes the API implementation with its description through a web framework that is purpose built to support API development. Since many web frameworks already support request/response validation and the ability to describe API endpoints using middleware or annotations, they can be extended to support the full API development lifecycle by including the notion of an API description in the framework. These frameworks allow you to develop your API in ways that conform to an API description and they leverage the existing framework to make sure that the API description accurately reflects the implementation over time.
Although many frameworks exist, I use Spring HATEOAS as the core example here.
Spring HATEOAS provides APIs to ease creating REST representations that
follow RESTful design principles and builds off of Spring MVC to provide
a complete API development framework. For example, the following block of
code uses the RepresentationModel
class provided by Spring HATEOAS to
define a JSON request/response body.
package com.example.resthateoas;
import org.springframework.hateoas.RepresentationModel;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Greeting extends RepresentationModel<Greeting> {
private final String content;
@JsonCreator
public Greeting(@JsonProperty("content") String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
And the following code sample returns the Greeting
to as
a ResponseEntity
.
package com.example.resthateoas;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@RestController
public class GreetingController {
private static final String TEMPLATE = "Hello, %s!";
@RequestMapping("/greeting")
public HttpEntity<Greeting> greeting(
@RequestParam(value = "name", defaultValue = "World") String name) {
Greeting greeting = new Greeting(String.format(TEMPLATE, name));
greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());
return new ResponseEntity<>(greeting, HttpStatus.OK);
}
}
Leveraging Spring HATEOAS ensures that your requests and responses are serialized using the HAL+JSON. Conforming to the HAL+JSON standard makes Spring HATEOAS more closely align to an API description document. You can also use this framework (and others) to generate an API description that conforms to OpenAPI using the Springdoc project.
Implementation First
What I refer to as Implementation First is what most people refer to as a Code-First method. In this method, a team writes their API with no consideration for the API description document. Once the API is complete and ready for users, they take the time to document the API using an API description format like OpenAPI.
With an implementation first method, it is possible for application developers to start implementing the API much faster if they start coding the API directly from the product requirements document. Libraries supporting scaffolding server code, functional testing, and deployment automation can make the implementation first method quick to get started and end up with good results. When developing internal APIs that aren’t exposed as a product to customers, the implementation first approach offers speed, automation and reduced process complexity.
The issue with Implementation First is that it can be a lot of work to go back and write documentation for an existing API. It can especially feel like a chore when the documentation will get out of date with the implementation as you make new changes to the API.
The primary criticism’s that most people have with a Code First API development process are that 1) there isn’t enough care put into API design and 2) the API description will get out-of-sync with the implementation. When reviewing these problems against this modified continuum of API development methodologies you can see that only the very last method, which I am calling Implementation First suffers from these problems.
With this modified continuum in mind, what should an API architect or application development do to establish an API development process for their team? My suggestion is to strike Implementation First from the list of approaches, for the same reasons as the Code-First approach — treating API documentation as an afterthought will make it difficult to support the API into the future. Having done that, I believe that each of the remaining three API development processes can lead to excellent outcomes. Unfortunately, the developer experience of the API Description First method is the least appealing to application development teams and I’ve had challenges selling this method to teams more familiar with programming languages and frameworks than API description documents.
The remaining two methods (Coded API Description and API Framework) are what I believe to be the optimal middle ground — they allow application development teams to work with familiar tools and processes that leverage their existing experience. By describing your API using code, or developing your API using a framework that adheres to an API description you can inject enough API design thought and best practices into the development process without needing to inject the additional complexity of an API description document. Choosing either of these two approaches will lead to great APIs and happy developers!