Super Simple Swagger example – part 1

Introduction

Everyone has an opinion on the best way to do microservice architecture, including me. The following is a series of blog posts where I lay out an opinionated “template architecture” for how to do Microservices in a way that is scalable and sustainable.

The principles I find important are:

  • API first
  • Vendor agnostic
  • Cheap to operate
  • Standardised communication/service mesh layer
  • Language and framework agnostic
  • Black box testable
  • Unit of deployment agnostic

Outline

The first post will be talking about how to make a super simple Swagger API definition and the use this to generate a Python Flask server.

The second post will talk about generating a simple JavaScript client for our API and how to host/run it.

The third post will talk about operational concerns including costs, portability, traceability, deployment, scale ability, backups, service discovery and registration etc…

The fourth post will talk about testing, both at the unit test level and at the “black box”/integration testing/end to end testing

The fifth post will talk about shared libraries and advanced topics

Defining a Trivial Swagger (OpenAPI) API

So, in order to get started, we can define an dummy API which lets us retrieve information about books. The below is a simple Swagger (also now the OpenAPI standard) API to do with books:

swagger: "2.0"
info:
description: "This is a super simple swagger sample Book server"
version: "1.0.0"
title: "Swagger bookserver"
termsOfService: "http://google.com/"
contact:
email: "foo@bar.io"
license:
name: "Apache 2.0"
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
host: "books.foobar.io"
basePath: "/v2"
tags:
name: "book"
description: "Everything about your Books"
externalDocs:
description: "Find out more"
url: "http://foobar.io"
name: "review"
description: "Access to Book reviews"
schemes:
"https"
"http"
paths:
/book:
get:
tags:
"book"
summary: "Get list of all books"
description: ""
operationId: "getBooks"
consumes:
"application/json"
"application/xml"
produces:
"application/xml"
"application/json"
parameters:
in: "query"
name: "author"
description: "The author of the books to retrieve"
required: false
type: "string"
name: "page"
in: "query"
description: "Page number of results to return."
required: false
type: "string"
responses:
200:
description: "Successful operation"
schema:
$ref: "#/definitions/Books"
post:
tags:
"book"
summary: "Add a new book to the site"
description: ""
operationId: "addBook"
consumes:
"application/json"
"application/xml"
produces:
"application/xml"
"application/json"
parameters:
in: "body"
name: "body"
description: "Book object that needs to be added to the site"
required: true
schema:
$ref: "#/definitions/Book"
responses:
405:
description: "Invalid input"
put:
tags:
"book"
summary: "Update an existing book"
description: ""
operationId: "updateBook"
consumes:
"application/json"
"application/xml"
produces:
"application/xml"
"application/json"
parameters:
in: "body"
name: "body"
description: "Book object that needs to be updated on the site"
required: true
schema:
$ref: "#/definitions/Book"
responses:
400:
description: "Invalid ID supplied"
404:
description: "Book not found"
405:
description: "Validation exception"
definitions:
Book:
type: "object"
required:
"name"
"author"
properties:
id:
type: "integer"
format: "int64"
name:
type: "string"
example: "Lord of the Rings"
status:
type: "string"
description: "book status on the site"
enum:
"reviewed"
"sold"
xml:
name: "Book"
Books:
type: "array"
items:
$ref: "#/definitions/Book"
externalDocs:
description: "Find out more about Books"
url: "http://books.example.io"

view raw
book.yml
hosted with ❤ by GitHub

#!/bin/bash
mkdir book-server
cd book-server
swagger-codegen generate \
-i https://gist.githubusercontent.com/srkiNZ84/3a8f7deb11cf368e25607cf0a66bc140/raw/cac66ce550489538f415734ded075fea192ae94f/book.yml \
-l python-flask

view raw
generate-flask.sh
hosted with ❤ by GitHub

[main] INFO io.swagger.parser.Swagger20Parser – reading from https://gist.githubusercontent.com/srkiNZ84/3a8f7deb11cf368e25607cf0a66bc140/raw/cac66ce550489538f415734ded075fea192ae94f/book.yml
[main] INFO io.swagger.codegen.ignore.CodegenIgnoreProcessor – No .swagger-codegen-ignore file found.
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/models/book.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/models/books.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/controllers/book_controller.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/test/test_book_controller.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./README.md
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./setup.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./tox.ini
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./test-requirements.txt
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./requirements.txt
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./git_push.sh
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./.gitignore
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./.travis.yml
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./Dockerfile
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./.dockerignore
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/__init__.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/__main__.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/encoder.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/util.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/controllers/__init__.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/models/__init__.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/models/base_model_.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/test/__init__.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/swagger/swagger.yaml
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./.swagger-codegen-ignore
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./.swagger-codegen/VERSION

view raw
generate-output.txt
hosted with ❤ by GitHub

The example explicitly leaves out any authentication and only defines a single endpoint with a GET/POST verb and a single object type.

We can verify that our API definition is valid by opening the Swagger Online Editor and pasting it in the editor pane.

So, now we have an API, which defines a “contract” between the client and the server and in an ideal world we would put this into version control (either in it’s own repository or into a monorepo, depending on whether our CI/CD server could trigger off of paths).

Generating the Server Stub

Now that we have an API definition, we can make use of the swagger-codegen tool to use it to generate a Flask “server stub” which we can use as a template for our application. We can install swagger-codegen on OS X with the Homebrew package manager and the following command:

brew install swagger-codegen

After we’ve installed the codegen tool, we can run commands to generate the Flask server:

mkdir book-server
cd book-server
swagger-codegen generate \
-i https://gist.githubusercontent.com/srkiNZ84/3a8f7deb11cf368e25607cf0a66bc140/raw/cac66ce550489538f415734ded075fea192ae94f/book.yml \
-l python-flask

The arguments passed to the command tell it to generate code, point it at our YML file containing the API definition and finally tell the command what kind of server code to generate. (For a list of all of the possible code/framework outputs have a look at the swagger-codegen documentation).

Assuming that the command runs successfully, you should have output such as the below:

[main] INFO io.swagger.parser.Swagger20Parser – reading from https://gist.githubusercontent.com/srkiNZ84/3a8f7deb11cf368e25607cf0a66bc140/raw/cac66ce550489538f415734ded075fea192ae94f/book.yml
[main] INFO io.swagger.codegen.ignore.CodegenIgnoreProcessor – No .swagger-codegen-ignore file found.
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/models/book.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/models/books.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/controllers/book_controller.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/test/test_book_controller.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./README.md
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./setup.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./tox.ini
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./test-requirements.txt
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./requirements.txt
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./git_push.sh
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./.gitignore
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./.travis.yml
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./Dockerfile
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./.dockerignore
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/__init__.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/__main__.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/encoder.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/util.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/controllers/__init__.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/models/__init__.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/models/base_model_.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/test/__init__.py
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./swagger_server/swagger/swagger.yaml
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./.swagger-codegen-ignore
[main] INFO io.swagger.codegen.AbstractGenerator – writing file /private/tmp/book-server/./.swagger-codegen/VERSION

view raw
generate-output.txt
hosted with ❤ by GitHub

We still need to install the Python Flask requirements and start up the server.

Looking at the contents of the directory, we can see that the generator has generated a Flask application as well as a Dockerfile and other code and configuration:

$ ll
total 88
drwxr-xr-x 15 srdan wheel 480B 10 Aug 22:03 .
drwxrwxrwt 11 root wheel 352B 10 Aug 22:02 ..
-rw-r--r-- 1 srdan wheel 885B 10 Aug 22:03 .dockerignore
-rw-r--r-- 1 srdan wheel 786B 10 Aug 22:03 .gitignore
drwxr-xr-x 3 srdan wheel 96B 10 Aug 22:03 .swagger-codegen
-rw-r--r-- 1 srdan wheel 1.0K 10 Aug 22:03 .swagger-codegen-ignore
-rw-r--r-- 1 srdan wheel 349B 10 Aug 22:03 .travis.yml
-rw-r--r-- 1 srdan wheel 246B 10 Aug 22:03 Dockerfile
-rw-r--r-- 1 srdan wheel 1.1K 10 Aug 22:03 README.md
-rw-r--r-- 1 srdan wheel 1.6K 10 Aug 22:03 git_push.sh
-rw-r--r-- 1 srdan wheel 66B 10 Aug 22:03 requirements.txt
-rw-r--r-- 1 srdan wheel 785B 10 Aug 22:03 setup.py
drwxr-xr-x 10 srdan wheel 320B 10 Aug 22:03 swagger_server
-rw-r--r-- 1 srdan wheel 90B 10 Aug 22:03 test-requirements.txt
-rw-r--r-- 1 srdan wheel 143B 10 Aug 22:03 tox.ini

We need to install the Python dependencies with “pip”:

pip3 install -r requirements.txt

To start the server, we can run:

python3 -m swagger_server

We should then be able to see our application running at the URL:

http://0.0.0.0:8080/v2/book

With the Swagger API definition available at:

http://0.0.0.0:8080/v2/swagger.json

We can then start filling out the logic of our application to make it behave like we want.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.