Contract-First API Development with OpenAPI Generator and Connexion

Anıl Can Aydın
Jan 20, 2019 · 6 min read

Most of us have already used the applications using REST API concept whether by being aware or not. It’s a quite popular approach in web development. Although, it’s known that it has a bunch of benefits, it cannot be denied that it comes with some side effects.

In this blog post, it’s aimed to explain contract-first REST API design approach for application development through a basic example. From top to the bottom of this article, an API will be defined, implemented, and consumed by a client (an application, or basically a Postman collection).

REST It is

Assuming that you are expected to build an application, which performs some crud operations on an object, has a graphical user interface such as web, desktop, mobile or command line application, and you decided to use RESTful architecture to implement this application. Where to start? Which one will be the correct starting point? Should I build the client app first? Should I build the backend app first?

If you work alone, this is not as critical as in the case that you work as a group of developers who need to share the implementation of application components.

Teams must synchronize while implementing the corresponding parts of the application itself. Let’s say we have two teams one implementing the client side, and the other one developing the server side. They must decide on the data and structure will be transferred between each party.

When to decide

I have seen that this decision process can sometimes be inside the implementation and requirements of the implementation can alter the contract. Of course, the requirements may change over time. However, if the frequency of having the change is more than expected, it can be a good idea to bring an understanding process under the spotlight.

Code First

In the code first approach, developers implement the application requirements directly. There is no contract or specification at first. It also requires a strong communication between the teams implementing application parts.

If the delivery time matters, a small or an internal API needed to be developed, Code First approach can be the convenient one. Also, a specification or a documentation can be written later for the consumers of the API.

Contract First API with Open API Specification

Obviously, Contract First concept claims that it should be written API’s contract first before writing any code. According to the specification, a contract can be written in YAML or JSON file, and it is both human and machine readable.

This structured file is the definition and structure of your API. By using this definition clients and servers can be generated.

Advantages of this approach can be listed as:

  • Reusability
  • Separation of concerns
  • Development teams does not block each other
  • Good and consistent communication between teams
  • Easy documentation
  • Consistent API models

While having these advantages, it also has following disadvantages when compared to code first approach:

  • Slower delivery. Delivery cannot be as fast as in the Code First approach. The reason is that teams should compromise on the specification before the implementation.
  • Maintaining the specification file. Although this is not an obvious disadvantage; for huge projects, number of specification files may require to manage them in another repository. Also, depending on the API models, specification file length makes itself harder to read and maintain.

Dirty Hands

Let’s make our hands a bit dirty.

Assuming that we are expected to build a cat adoption center application to be able to:

  • Add new cat record whenever a new cat comes to the center
  • Retrieve a single cat’s information
  • Update a single cat’s information whenever it is needed
  • Remove from the system whenever the cat is adopted
  • List all the cats in the center

We can implement the following endpoints to satisfy application requirements:

/cats :

  • POST: add a cat
  • GET: list all cats

/cats/{cat_id} :

  • GET: retrieve a single cat with given cat_id
  • PUT: update a cat with given cat_id
  • DELETE: delete a cat with given cat_id

Considering the given requirements, our contract can be something like the following:

{project-root}/cat-api-spec.yaml

swagger: '2.0'
info:
description: This is a simple Cat Information Center Api
version: 1.0.0
title: OpenAPI Cat Information Service
consumes:
- application/json
produces:
- application/json
tags:
- name: cat
description: Everything about your Cats
schemes:
- http
paths:
/cats:
post:
tags:
- cat
summary: Add a new cat to the store
description: Creates a new cat in the store
operationId: addCat
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: body
description: Cat object that needs to be added to the store
required: true
schema:
$ref: '#/definitions/Cat'
responses:
'201':
description: Successfully created
schema:
$ref: '#/definitions/Cat'
'400':
description: Bad request
...
/cats/{cat_id}:
get:
tags:
- cat
summary: Retrieve a single cat
description: Retrieve a single cat
operationId: getCat
consumes:
- application/json
produces:
- application/json
parameters:
- in: path
name: cat_id
description: Id of the cat desired to be retrieved
required: true
type: string
responses:
'200':
description: Succesful retrieval of cat
schema:
$ref: '#/definitions/Cat'
'400':
description: Bad request
'404':
description: Cat not found
...
definitions:
Cat:
title: Cat
description: A cat
required:
- name
- breed
properties:
id:
type: string
name:
type: string
description: Name of the cat
example: Garfield
breed:
type: string
description: Breed of the cat
enum:
- Abyssinian
- Aegean
- American Bobtail
...
CatsResponse:
type: object
description: Response object contains all the cats
properties:
cats:
type: array
items:
$ref: '#/definitions/Cat'
description: List of cats

Although definition itself is pretty straightforward, for a further explanation this is a nice resource. Also, the contract can be verified and visualized from here.

Complete code, spec and the postman collection.

Our contract is already written. We know the details of our API model. It means that both server and client applications can be built by using this contract.

For the model generation, we need a tool which parses the contract and by understanding the needs, generates API models.

OpenApi Generator employed for the generation process.

After installation of the OpenApi Generator, run the following under the project folder to generate server code and models:

openapi-generator generate -i cat-api-spec.yaml -g python-flask -o . -s

-s options skips the overwriting of files. For the sake of methods you already implemented this is critical. Without -s option, whenever generator is called, the methods will be overwritten.

There are bunches of server and client generation options. I chose python-flask for only demonstration purposes.

If all is well, the directory structure must be seen as in the following:

.
├── Dockerfile
├── README.md
├── cat-api-spec.yaml
├── git_push.sh
├── openapi_server
│ ├── __init__.py
│ ├── __main__.py
│ ├── controllers
│ │ ├── __init__.py
│ │ └── cat_controller.py
│ ├── encoder.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── base_model_.py
│ │ ├── cat.py
│ │ └── cats_response.py
│ ├── openapi
│ │ └── openapi.yaml
│ ├── test
│ │ ├── __init__.py
│ │ └── test_cat_controller.py
│ └── util.py
├── requirements.txt
├── setup.py
├── test-requirements.txt
└── tox.ini

Boilerplate server code generated. Now, all needed to be done is to implement our business logic in the controller. My simple implementation is as in the following snippet:

import connexionfrom openapi_server.models.cat import Cat  # noqa: E501
from openapi_server.models.cats_response import CatsResponse # noqa: E501
from uuid import uuid4
SAMPLE_CAT_STORAGE = {
"1": {
"id": "1",
"name": "Garfield",
"breed": "Persian"
},
"2": {
"id": "2",
"name": "Serafettin",
"breed": "Turkish Angora"
},
"3": {
"id": "3",
"name": "Kitty",
"breed": "British Longhair"
}
}
def add_cat(cat=None): # noqa: E501
"""Add a new cat to the store
Creates a new cat in the store # noqa: E501 :param cat: Cat object that needs to be added to the store
:type cat: dict | bytes
:rtype: Cat
"""
if connexion.request.is_json:
cat = Cat.from_dict(connexion.request.get_json()) # noqa: E501
cat.id = uuid4().hex
SAMPLE_CAT_STORAGE[cat.id] = cat.to_dict()
return cat, 201
...

Also, the instructions to run the code can be found in README.md.

Complete code, spec and the postman collection.

Take Home

For the ones who are not satisfied with a Postman collection to test; they can generate client code by using the same schema, in desired language.

For a python client:

openapi-generator generate -i cat-api-spec.yaml -g python -o . -s

For the list of client and server generators, check this out.

Conclusion

I tried to exemplify Contract-First API Development concept. I hope it would be helpful, and joyful too. Thanks for reading. Happy coding!

Commencis

We help leading brands to grow and scale in digital…