Contract-first REST API

Thibault Duperron

bigM

Ex full-stack developper

Backend developper Scala, Java…​

From small applications

Frontend

Backend’s APIs

Storage

Code-first

Code something in the backend

Hack API to responds to frontend needs

Generate documentation at runtime

Quicker, easier, more seductive

API-First?

API-first software development refers to a set of practices that formally recognize APIs as a first-class artefact of software development, and emphasize their strategic and architectural importance.

— Restlet

Contract first

Start with a contract

Everyone can mock from it

Contract can easily be versioned

OAS3

openapi: 3.0.0
info:
  title: Ponies
  version: '1.0'
  description: Pony API
servers:
  - url: 'http://localhost:8080'
paths:
  /ponies:
    get:
      summary: List ponies
      operationId: get-ponies
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pony'
      tags:
        - Ponies
components:
  schemas:
    Pony:
      title: Pony
      type: object
      properties:
        id:
          type: integer
          description: Unique identifier for the given pony.
        name:
          type: string
        type:
          $ref: '#/components/schemas/PonyType'
      required:
        - id
        - name
        - type
    PonyType:
      type: string
      title: PonyType
      enum:
        - Earth
        - Unicorns
        - Pegasi

OAS3

openapi: 3.0.0
info:
  title: Ponies
  version: '1.0'
  description: Pony API
servers:
  - url: 'http://localhost:8080'

OAS3

paths:
  /ponies:
    get:
      summary: List ponies
      operationId: get-ponies
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pony'

OAS3

components:
  schemas:
    Pony:
      title: Pony
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        type:
          $ref: '#/components/schemas/PonyType'
      required:
        - name

My little contract

API

Contract creation

editors

And now?

OpenApi Generator

openapi generator

Fork of swagger-codegen (2018)

Multi generators

clients

DEMO !

keyboard

First generation

npx @openapitools/openapi-generator-cli generate \
                     -i api.yml \
                     -g spring \
                     -o ./spring

First result

curl -XGET -s localhost:8080/ponies | jq
{
  "name": "name",
  "id": 0,
  "friends": [
    null,
    null
  ]
}

Api first generation

npx @openapitools/openapi-generator-cli generate \
                     -i api.yml \
                     -g spring \
                     -o ./spring-api \
                     -p apiFirst=true

Plugin tweak

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>5.4.0</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>src/main/resources/openapi.yaml</inputSpec>
                <generatorName>spring</generatorName>
                <apiPackage>org.openapitools.api</apiPackage>
                <modelPackage>org.openapitools.model</modelPackage>
                <generateSupportingFiles>false</generateSupportingFiles>
                <configOptions>
                    <interfaceOnly>true</interfaceOnly>
                    <delegatePattern>true</delegatePattern>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

Client

npx @openapitools/openapi-generator-cli generate \
                     -i api.yml \
                     -g java \
                     -o ./client \
                     --library=feign
PoniesApi client = new ApiClient()
        .setBasePath("http://localhost:8080")
        .buildClient(PoniesApi.class);

client.getPonies()
        .forEach(it -> System.out.println("it = " + it));

Just a demo

Maven, Gradle, sbt…​

Java, Scala, Kotlin♥, Go…​

Springboot, Ktor, Play, Quarkus…​

Generated DTO vs Domain

Skip model generation and code them

Generated DTO vs Domain

Skip model generation and code them

Use mapper like mapstruct

API version handling

Generate client version is easy

API upgrade impacts = compile issue

Next steps

Custom generator template

Generate documentation

Generate mock services

Do I need that?

Mission Critical APIs.

When your API’s target audience are external customers

Questions

bigM

println("Thank you!")

Slides

Source code