Think before you speak write code: Define the specs and create the Swagger file
The first question we need to answer before we start development is always one and the same:
What problem am I trying to solve?
For our scenario, let’s say we are GraubLaser, a factory in Graubünden, Switzerland that makes expensive laser engraving machines. Why? 1) because lasers are cool and also 2) because at some point I was interviewed by this company – GraubLaser is not its real name- and while I decided not to join them as it’s around 80km from where I live, the people were super nice and the machines really cool.
Let’s imagine that we are extending our invoicing system with the ability to:
- Ask our financial data provider, GraubFinance, if a customer is creditworthy, so that we allow them to order an expensive item with less risk of not getting paid.
- Tell GraubFinance if a customer is creditworthy or not, following a transaction we had with them.
In REST terms one way (there are many!) to implement this would be for GraubFinance to create a service on their system for us to call. The spec sheet could look like this:
Service URLs:
http://api-test.graubfinance.ch/api/1.0/CustomerTrust
http://api.graubfinance.ch/api/1.0/CustomerTrust
GET : Reads info about a certain customer
Parameters (in path):
- taxid : the customer's tax id, string, e.g. CHE-123.456.789
Returns (json in message body):
- A CustomerTrustInfo object with the following properties:
- name : the company name, string, e.g. GlarusAdvertising AG
- taxid : the customer's tax id, string, e.g. CHE-123.456.789
- trustlevel : the creditworthiness level, enum as string, e.g. OK
- valid values :
- OK (no problems reported)
- WARN (minor problems reported, e.g. delays in payments but not arrears)
- BAD (arrears reported)
POST: Sends info about a certain customer following a transaction we had with them
Parameters (in path):
- taxid : the customer's tax id, string, e.g. CHE-123.456.789
Content (json in message body):
- A CustomerTrustReport object
- reportid : a unique id from our reporting system, string, e.g. 2dcc02d9-6402-4ce9-bf44-3d2cbe8bcd5e
- reporttaxid : our own tax id, string, e.g. CHE-123.456.789 (indicating which of our subsidiaries had the transaction)
- taxid : the customer's tax id, string, e.g. CHE-123.456.789
- trustlevel : the creditworthiness level, enum as string, e.g. OK
- valid values :
- OK (no problems encountered)
- WARN (minor problems encountered, e.g. delays in payments but not arrears)
- BAD (arrears encountered)
Returns (json in message body):
- A ReportStatus object
- reportid : the id from the CustomerTrustReport
- status : indicates if the report could be processed and saved, , enum as string, e.g. OK
- valid values :
- OK (no problems encountered)
- ERROR (an exception occured)
- details : (only in case o error) gives problem details, string, e.g. Error: InsufficientDiskSpace exception
Authentication:
Standard OAuth 2.0 bearer JWT token required which must contain the scope "CustomerTrust". If the token is missing or invalid, the service must return HTTP 401. If the token is valid but does not contain the correct scope, the service must return HTTP 403.
So basically the GET operation would give us info about a certain customer, identified by a tax id. The POST would give the provider info about our experience with them. As authentication is a big subject by itself, we won’t talk a lot about it here.
So the Swagger file could look like this (notice that for convenience we added the mock and the local dev machine URLs):
openapi: "3.0.2"
info:
title: GraubFinance Customer Trust service
version: "1.0"
servers:
- url: http://localhost:8888/api/1.0/
description: local dev machine
- url: https://graubfinancemock.azurewebsites.net/api/1.0/
description: mocking service
- url: http://api-test.graubfinance.ch/api/1.0/
description: staging
- url: http://api.graubfinance.ch/api/1.0/
description: production
paths:
/CustomerTrust/{taxid}:
get:
operationId: GetCustomerTrustInfo
summary: Reads info about a certain customer
parameters:
- name: taxid
in: path
description: Customer's tax id
required: true
schema:
type: string
example: "CHE-123.456.789"
responses:
'200':
description: CustomerTrustInfo
content:
application/json:
schema:
type: object
properties:
name:
type: string
example: "GlarusAdvertising AG"
taxid:
type: string
example: "CHE-123.456.789"
trustlevel:
type: string
enum: [OK, WARN, BAD]
example: "OK"
'401':
description: Unauthorized, JWT token not present or invalid
'403':
description: JWT token valid but does not contain necessary scope
'404':
description: Customer tax id not found
post:
operationId: PostCustomerTrustReport
summary: Sends info about a certain customer following a financial transaction
parameters:
- name: taxid
in: path
description: Customer's tax id
required: true
schema:
type: string
example: "CHE-123.456.789"
requestBody:
content:
application/json:
schema:
type: object
properties:
reportid:
type: string
example: "2dcc02d9-6402-4ce9-bf44-3d2cbe8bcd5e"
reporttaxid:
type: string
example: "CHE-123.456.789"
taxid:
type: string
example: "CHE-123.456.789"
trustlevel:
type: string
enum: [OK, WARN, BAD]
example: "OK"
responses:
'200':
description: Success
'401':
description: Unauthorized, JWT token not present or invalid
'403':
description: JWT token valid but does not contain necessary scope
'404':
description: Customer tax id not found
components:
securitySchemes:
OAuth2:
type: http
scheme: bearer
bearerFormat: JWT
security:
- OAuth2: [CustomerTrust]
You could certainly improve the API spec -I would- but I don’t want to focus on this.
We should keep this somewhere. Create a directory for our project, let’s call it GraubFinanceMockService. Inside this create another one called, say, openapi, and save the swagger file inside as CustomerTrust.yaml. We’ll use the folder structure in the next steps and ultimately add it to source control.
The real purpose of this tutorial begins now. So let’s create the fake service!
One thought on “Design by contract Tutorial, part 1/6: [OpenAPI] Mock your interfaces using Swagger, Wiremock, Docker, Azure Devops, Terraform and Azure”