Using contract-first to build an HTTP Application with OpenAPI and Gradle

Matías Laíno
The Glovo Tech Blog
8 min readOct 3, 2023

Say you have an application that will expose an API to be consumed by multiple consumers. These consumers need to know the definition of your API to be able to speak to it (what URIs to use, HTTP verbs, request body, query parameters, etc.), this means we need a document, a contract, to share with them so they can build their clients. Wouldn’t it be nice if we could write this contract in a language we all understood? And even better, maybe even have our tooling understand it so it could help us write both the server and the client code.

Yes, yes it would!

I’m going to explain how to do this using Spring Boot, Gradle, and OpenAPI.

At Glovo we use a contract-first approach when developing web APIs, this means that we first define the API contracts using a common language and then we write application code that matches that contract; having this contract in a common language allows consumers of these APIs to generate clients, hopefully automatically.

But actually, we want a bit more than just having a common language, let’s set some goals:

  • The contract, as mentioned, should be in a common language, a standard.
  • We want our tooling to be aware of the contract so that it’s enforced by the server when writing the actual API implementation.
  • We want automatic generation of objects defined by the API definition (“spec”), it’s tedious to have to write boilerplate code that can easily be generated by a machine.
  • Automatically generated code shouldn’t be stored in the repository, it’s generally a waste of space, pollutes Pull Requests, lets the build system take care of it!

For a common language to define the specs, we’ll use OpenAPI (the artist formerly known as Swagger).

I understand things better with a practical example, so let’s make one step-by-step.

Let’s say we want to expose an endpoint that publishes a list of Books; a book, in this example, is a simple object that has a “title” and an “author”, both strings.

First I’ll build a very simple Spring Boot application using Spring Initializr because I’m very lazy.

Be sure to select Spring-Boot-Web (org.springframework.boot:spring-boot-starter-test dependency) so we can declare some nice REST controllers.

Now, we want to define what the API is going to look like in an OpenAPI spec, this spec can later be shared with other teams that might want to use our API. We’ll put the spec spec.yaml (name it whatever you want) in a folder specs

openapi: 3.0.0
info:
version: 1.0.0
title: Sample API
description: A sample API to illustrate OpenAPI concepts
paths:
/books:
get:
description: Returns a list of stuff
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Book"

components:
schemas:
Book:
type: object
properties:
author:
type: string
title:
type: string
required:
- author
- title

Simple, huh? It declares a single path /books on which consumers can do a GET and on an HTTP 200 they will get an array of books; the array items are declared as a reference to a Book schema, which just has an author and title fields.

Now we have our API definition, the next step is to implement it. As mentioned before, we want the boilerplate portion of the API implementation to be done for us (we’ve already described in our spec that a GET /booksreturns a list of books, and we already defined what those books look like, I don’t want to write that again in code), and we also want to be absolutely sure that our implementation follows our contract, otherwise we might break something for consumers if it doesn’t.

To achieve this, we need an OpenAPI generator, we’ll choose the Kotlin generator (even though I’ve found my share of bugs with it in versions up to 5.4.0); if you’re on a Java project, you can use the spring generator. The generator is a Gradle plugin that will generate Gradle tasks which we can insert into our build.

I like having the API interfaces in a submodule so that it can be easily shared between all the submodules in my project. Let’s create an apis/server folder and put a simple build.gradle.kts in it.

plugins {
kotlin("jvm")

`java-library`
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

repositories {
mavenCentral()
}

dependencies {
}

kotlin {
jvmToolchain(17)
}

tasks.withType<Test> {
useJUnitPlatform()
}

Let’s go to settings.gradle and define a submodule apis/server.

rootProject.name = "demo"

include(":apis:server")

Now apis/server should be recognized as a submodule of our project. Let’s do the class generation from our OpenAPI spec! Let’s add the OpenAPI generator plugin and configure it to take our spec.yaml file to our apis/server/build.gradle.kts :

plugins {
...
id("org.openapi.generator") version "6.6.0"
}

...

openApiGenerate {
generatorName.set("kotlin-spring")
inputSpec.set("$rootDir/specs/main.spec.yaml")
apiPackage.set("com.mlaino.examples.openapi.apis.server")
configOptions.putAll(
mapOf(
Pair("gradleBuildFile", "false"),
Pair("useSpringBoot3", "true"),
Pair("documentationProvider", "none"),
)
)
generateApiTests.set(false)
}

The interesting bit here is the openApiGenerate closure, which defines how the generator should behave when processing our specs, let’s look at each config:

  • generatorName: the plugin basically grabs the spec file, and outputs the classes that match the definitions within; the generator is the piece of code that knows how to translate from OpenAPI to another language, there’s tons of generators, in our case we will use the kotlin-spring generator, because we want the classes to be written in Kotlin (there’s also a beta of a kotlin-vertx generator, and also a kotlin-server generator I haven’t tried yet). Notice here that the generator we’ve picked is in the servers section of the list, this is because there’s also a clients section, to generate client code, we’ll just focus on server-side code generation here.
  • inputSpec: self-explanatory, it’s where the input spec file is.
  • apiPackage: the package for our output classes.
  • generateApiTests: The generator will attempt to write some unit tests for the generated classes, you can disable these if you like.
  • configOptions: All the config options we’ve set until now have been global properties, in here we define a map of generator-specific properties, which means that the properties that are valid for the kotlin-spring generator might not be valid or might not work the same for other generators. In here we just configure the following: use spring boot 3 (you can not set this if you are on Spring Boot 2), don’t create a build.gradle file, and don’t use a documentation provider (this is to generate documentation from our specs, the thing we won’t do here this time).

After refreshing Gradle, we should see the new project along with the OpenAPI tasks.

If we run the openApiGenerate task, we will see the sources being created and placed by default in the gradle build folder:

If we try to run the apis/server/openapi tools/openApiGenerate task and then we compile it with build, we will get compilation errors:

Unresolved reference: springframework
Unresolved reference: jakarta

Let’s add the spring plugins, the spring boot web starter and jakarta dependencies:

plugins {
id("org.springframework.boot") version "3.1.0"
id("io.spring.dependency-management") version "1.1.0"
...
}

dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("jakarta.validation:jakarta.validation-api:3.0.2")
}

Now it should build just fine.

The apis/server project is generating classes from the OpenAPI spec, and it’s building into an artifact that can be imported by our main project. Slight problem: this requires manually running the OpenAPI task to generate the source code so that the build task can include it in the JAR, let’s make it so that on build the OpenAPI task is automatically run, so that we can forget to do that again, let’s add the following two lines to our build.gradle.kts file:

sourceSets.getByName("main").kotlin.srcDir("$buildDir/generate-resources")
tasks.compileKotlin.get().dependsOn(tasks.openApiGenerate)

These do:

  • Add the sourcecode found in the $buildDir/generate-resources folder to the main sourceset, this will ensure it’s compiled.
  • Make the build task depends on the openApiGenerate task running before, so that Gradle runs it for us.

We have our server contracts being compiled automatically, let’s add them to the main build in the main build.gradle.kts file:

dependencies {
...
implementation(project(":apis:server"))
}

Refresh gradle so that the new dependency is detected, and then everything is finally ready to be used, if you browse for a class called “BookApiController” you should find this:

package com.mlaino.examples.openapi.apis.server

import org.openapitools.model.Book
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity

import org.springframework.web.bind.annotation.*
import org.springframework.validation.annotation.Validated
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.beans.factory.annotation.Autowired

import jakarta.validation.Valid
import jakarta.validation.constraints.DecimalMax
import jakarta.validation.constraints.DecimalMin
import jakarta.validation.constraints.Email
import jakarta.validation.constraints.Max
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Pattern
import jakarta.validation.constraints.Size

import kotlin.collections.List
import kotlin.collections.Map

@RestController
@Validated
@RequestMapping("\${api.base-path:}")
class BooksApiController() {


@RequestMapping(
method = [RequestMethod.GET],
value = ["/books"],
produces = ["application/json"]
)
fun booksGet(): ResponseEntity<List<Book>> {
return ResponseEntity(HttpStatus.NOT_IMPLEMENTED)
}
}

Pretty neat! Also if you look for the Book class, you should also see:

package org.openapitools.model

import java.util.Objects
import com.fasterxml.jackson.annotation.JsonProperty
import jakarta.validation.constraints.DecimalMax
import jakarta.validation.constraints.DecimalMin
import jakarta.validation.constraints.Email
import jakarta.validation.constraints.Max
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Pattern
import jakarta.validation.constraints.Size
import jakarta.validation.Valid

/**
*
* @param author
* @param title
*/
data class Book(

@get:JsonProperty("author", required = true) val author: kotlin.String,

@get:JsonProperty("title", required = true) val title: kotlin.String
) {

}

The controller class is pretty empty, as you can see, it’ll fail with an HTTP 501 if you try to do a GET to fetch the books, so it’s up to you to give it a proper implementation, all of this has been about writing a spec and getting the code autogenerated from it, let’s finish the example with a simple implementation:

@RestController
class BookApi : BooksApiController() {
override fun booksGet(): ResponseEntity<List<Book>> {
return ResponseEntity.ok(
listOf(
Book("Isaac Asimov", "Foundation"),
Book("Dune", "Frank Herbert"),
Book("Hyperion", "Dan Simmons"),
Book("At the Mountains of Madness", "H.P. Lovecraft"),
Book("The Lord of the Rings", "J.R.R. Tolkien"),
)
)
}
}

Now doing a GET on /books will return our result:

[{"author":"Isaac Asimov","title":"Foundation"},{"author":"Dune","title":"Frank Herbert"},{"author":"Hyperion","title":"Dan Simmons"},{"author":"At the Mountains of Madness","title":"H.P. Lovecraft"},{"author":"The Lord of the Rings","title":"J.R.R. Tolkien"}]

Our spec can now be shared with other teams that might need to communicate with our service, Glovo we have all of our specs automatically ingested and published in Backstage, so we can browse there to our hearts’ content and see what each published API looks like, and use those specs to generate our clients.

Note that this explanation has been only to generate server classes (hence all the Spring Web annotations, API boilerplate code, etc.), you can use the OpenAPI generators to generate client code too, which you can share in an artifact so that teams can use that instead of generating it themselves.

Summary of the steps we took:

  • We created a simple Spring Boot application using Spring Initializr, with Spring Web included.
  • We added an apis/server submodule to our gradle build.
  • We defined an OpenAPI yaml spec for the API that we want to expose.
  • We configured apis/server to have the OpenAPI Generator plugin, and configured it to read our spec and produce code from it. (TODO no va en source control).
  • We set up the Build task so that it’d require the OpenAPI generate task to run beforehand so that the generated classes are always available during build.
  • We added our submodule as a dependency to the main module.
  • We gave the API a proper sample implementation.

Hope you find this useful as a starting point for building your own contracts in OpenAPI for your applications.

Link here to the code

--

--