API-First Approach with Micronaut

API-First Approach with Micronaut

In the API-First approach, you make sure the API is well designed with the help of a specification language like OpenAPI, even before the actual implementation starts.

It has the advantage that both, server and client sides, can start working on the implementation once the contract has been signed up, we can leverage the designed API specification to generate code for a specific language or framework.

We are going to setup a micronaut project with the openapi generator to generate server stubs using a custom micronaut codegen library, as at the time of this writing, the openapi generator does not support generating server stubs for micronaut out-of-the-box, only for client API.

Create a micronaut project

Create a micronaut project, either from the micronaut launch site:

Untitled

Using this curl command:

curl --location --request GET 'https://launch.micronaut.io/create/default/com.example.petstore?lang=JAVA&build=GRADLE&test=JUNIT&javaVersion=JDK_11' --output petstore.zip

Or using the micronaut cli tool:

mn create-app --build=gradle --jdk=11 --lang=java --test=junit com.example.petstore

We can use either gradle or maven, configuration for both is provided below.

Adding API Specification

We will use the Petstore API specification from editor.swagger.io, for this tutorial, we will use the OpenAPI 3 version, but the swagger 2.0 version should work as well. To convert the specification to OpenAPI 3, click the "Edit" menu, then "Convert to OpenAPI 3" and finally click "Convert" in the banner to confirm, copy the content in a specs/petstore.yaml file inside the project.

Untitled

Gradle Configuration

We need to add the OpenAPI Generator Gradle Plugin to our build.gradle file as below:

plugins {
    id("com.github.johnrengelman.shadow") version "7.1.0"
    id("io.micronaut.application") version "2.0.8"
    id("org.openapi.generator") version "5.3.0"
}

Next, let's add the micronaut codegen, the OpenAPI Generator plugin needs to be able to find the custom codegen class in the classpath, so it needs to be added in the buildscript block:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'io.kokuwa.micronaut:micronaut-openapi-codegen:2.2.1'
    }
}

Now we can configure the openApiGenerate task, the generatorName option is set to "micronaut", as that is the name of the custom codegen, in the inputSpec option we need to point to our API specification file, in this case, /specs/petstore.yaml, the rest of the options and their descriptions can be found here, and the config options for the custom codegen can be found here, for this example, we are using supportAsync as true so it generates reactivex return types.

openApiGenerate {
    generatorName = "micronaut"
    inputSpec = "$rootDir/specs/petstore.yaml".toString()
    outputDir = "$buildDir/generated".toString()
    apiPackage = "com.zephyrok.api"
    invokerPackage = "com.zephyrok.invoker"
    modelPackage = "com.zephyrok.model"
    configOptions = [
            supportAsync: "true"
    ]
}

We can add a dependency for the compileJava task on the openApiGenerate task, so the stubs are generated when the project is built.

compileJava.dependsOn tasks.openApiGenerate

For the generated source to be compiled, we need to add the generated sources directory to the main source sets:

sourceSets.main.java.srcDirs += "$buildDir/generated/generated-sources/openapi"

The custom codegen generates references to types that are not included in the micronaut libraries, so the below dependencies needs to be added to the project:

implementation "javax.inject:javax.inject:1"
implementation "com.google.code.findbugs:jsr305:3.0.2"
implementation "io.reactivex.rxjava2:rxjava:2.2.21"

Maven Configuration

OpenAPI generator also provides a maven plugin, this is an equivalent configuration to the gradle setup for the custom micronaut codegen with the same options:

<build>
  <plugins>      
    <plugin>
      <groupId>org.openapitools</groupId>
      <artifactId>openapi-generator-maven-plugin</artifactId>
      <version>5.3.0</version>
      <executions>
        <execution>
          <goals>
            <goal>generate</goal>
          </goals>
          <configuration>
            <inputSpec>${project.basedir}/specs/petstore.yaml</inputSpec>
            <generatorName>micronaut</generatorName>
            <apiPackage>com.zephyrok.api</apiPackage>
            <invokerPackage>com.zephyrok.invoker</invokerPackage>
            <modelPackage>com.zephyrok.model</modelPackage>
            <output>${project.build.directory}</output>
            <configOptions>
              <supportAsync>true</supportAsync>
            </configOptions>
          </configuration>
        </execution>
      </executions>
      <dependencies>
        <dependency>
          <groupId>io.kokuwa.micronaut</groupId>
          <artifactId>micronaut-openapi-codegen</artifactId>
          <version>2.2.1</version>
        </dependency>
      </dependencies>
    </plugin>
  </plugins>
</build>

We need to add the generated sources to the compile phase with the help of build-helper-maven-plugin:

    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>build-helper-maven-plugin</artifactId>
      <version>3.2.0</version>
      <executions>
        <execution>
          <phase>generate-sources</phase>
          <goals>
            <goal>add-source</goal>
          </goals>
          <configuration>
            <sources>
              <source>${project.build.directory}/generated-sources/openapi</source>
            </sources>
          </configuration>
        </execution>
      </executions>
    </plugin>

Likewise, we need to add some dependencies in order for the project to build correctly:

    <dependency>
      <groupId>javax.inject</groupId>
      <artifactId>javax.inject</artifactId>
      <version>1</version>
    </dependency>
    <dependency>
      <groupId>com.google.code.findbugs</groupId>
      <artifactId>jsr305</artifactId>
      <version>3.0.2</version>
    </dependency>
    <dependency>
      <groupId>io.reactivex.rxjava2</groupId>
      <artifactId>rxjava</artifactId>
      <version>2.2.21</version>
    </dependency>

Fixing petstore.yaml

If we try to build the project with the current Petstore API specification, either with ./gradlew clean build or ./mvnw clean package, it will fail with the below error.

Operation createUser has no documented response code, only default.

OpenAPI generator complains because some operations don't have a documented response code, to fix this, search for all the occurrences of:

responses:
  default:

and replace them with:

responses:
  200:

With these changes, the project will now build correctly, but still, there will be several warnings from OpenAPI Generator, like this one:

Multiple schemas found in the OAS 'content' section, returning only the first one (application/json)
Multiple schemas found in the OAS 'content' section, returning only the first one (application/xml)

To fix those, remove the application/xml section from the responses to keep only the application/json response.

There will be one warning left:

ApiResponse (reserved word) cannot be used as model name. Renamed to ModelApiResponse

It complains about the usage of a reserved word (ApiResponse), reserved words for java are defined in the AbstractJavaCodegen class.

To fix it, replace ApiResponse with something like UploadImageResponse, find the $ref referencing ApiResponse, and update it as well.

The complete example can be found here.