Skip to content

guide api first

devonfw-core edited this page Dec 13, 2022 · 5 revisions

API first development guide

Cloud native promotes the use of microservices, which are loosely coupled and self-contained. These services communicate with each other using a well-defined interface or API, and no consumer needs to be aware of any implementation details of the provider. There could even be multiple providers of the same API, or we can decide to swap existing implementation for a new one, without disrupting our clients - all because we adhere to a well defined API. This guide focuses on HTTP interfaces following RESTful design principles.

API first strategy

API first strategy treats APIs as first class citizens in the delivery model. The APIs are modeled/designed first (usually as OpenAPI specification), often in a collaborative process between providers and consumers, and only once the APIs are defined do we start with development of the provider service. This requires a bit more time upfront, but also provides opportunity to think about the behaviour of the system before it gets implemented. Several other advatages of this approach:

  • provider and client implementation can run in parallel - we have the contract, client does not need to wait for provider to finish implementation

  • support for automation and great ecosystem - we can easily generate stubs and clients for most languages/frameworks, generate documentation and API catalogues using one of the many opensource and commercial tools

API first provider

After careful planning, we defined our future API. Now we would like to implement a provider of this API. We could manually create all the JAX-RS endpoints based on the schema and then test whether the actually provided interface conforms with the API schema. However, it is an error prone and laborious process, especially if the API gets big. Luckily, we can use great open source tooling to generate JAX-RS interfaces that conform to 100% with the schema, and then we simply implement them.

There are many open source tools, that allow code generation from OpenAPI schema files, for example OpenAPI Generator or Swagger codegen. Both are based on Java and even provide maven plugins to integrate them in our build.

OpenAPI Generator maven plugin

If we already have our backend maven project, using the maven plugin will be easiest for us. Lets define a new maven profile and add the OpenAPI generator plugin:

<profile>
  <id>apigen</id>
  <activation><activeByDefault>true</activeByDefault></activation>
  <build>
    <plugins>
      <plugin>
        <groupId>org.openapitools</groupId>
        <artifactId>openapi-generator-maven-plugin</artifactId>
        <version>5.1.0</version>
        <executions>
          <execution>
            <goals>
              <goal>generate</goal>
            </goals>
            <configuration>
              <!-- input spec can be a url, or a local file -->
              <inputSpec>https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml</inputSpec>
              <!-- <inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec> -->
              <generatorName>jaxrs-spec</generatorName>
              <apiPackage>com.petclinic.api</apiPackage>
              <modelPackage>com.petclinic.api.model</modelPackage>
              <!-- <library>quarkus</library> -->
              <configOptions>
                <sourceFolder>src/gen/java/main</sourceFolder>
                <useSwaggerAnnotations>false</useSwaggerAnnotations>
                <useTags>true</useTags>
                <interfaceOnly>true</interfaceOnly>
                <generatePom>false</generatePom>
              </configOptions>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</profile>

OpenAPI generator project includes support for many languages and frameworks - in our case we picked jaxrs-spec server generator. Many of the generator templates have further sub-generators, for example for your specific framework. We could, for example use quarkus sub-generator to bootstrap a full working Quarkus maven project, but in our case, we want to have more control over the process, therefore we opt for standard jaxrs template and pass some additional configuration:

  • apiPackage = generated package name for our endpoint interfaces

  • modelPackage = generated package name for our API models (DTOs)

  • sourceFolder = where do we want to output the generated files, relative to the output folder - if we regenerate the files with every build, then it is recommended to put them in ${project.build.directory}/generated-sources/openapi (default, can be changed via output plugin attribute). Final location of our generated Java files will then be ${project.build.directory}/generated-sources/openapi/src/gen/java/main

  • useTags = group the generated endpoints by schema tag attribute

  • interfaceOnly = we want to implement the endpoint logic ourselves, so we only want JAX-RS interfaces

  • generatePom = we are generating the files into existing project, so we dont want to generate additional maven pom

Let’s run the plugin now using mvn clean compile. If everything went well, we end up with something like this:

Generated sources by OpenAPI generator
Figure 1. Generated sources by OpenAPI generator

Depending on the configuration of your code generator, you might need to add some additional dependencies to your pom.xml, for example for bean validation or Jackson annotations.

The OpenAPI generator has many more configuration options that are outside the scope of this guide. You can find full documentation of the codegen plugin in the projects GitHub repository.

Generate once vs generate always

Depending on our needs, we may either want to generate the interfaces and models once and afterwards copy them to our project as general source code files, or treat them as immutable generated assets, that we generate anew with every build. Both scenarios have their pros and cons, and you’ll need to find out what best suits your project. In the example above, we use a profile with activeByDefault=true, which will cause the generator to run with every build. The generated files will be included as sources in our project, so we can import them in any other java class without issues.

In case you only want to generate your API resources once and version them afterwards in SCM, simply run the generator, outputting to some temporary location, then, copy them to src/main/java and you are done. Be careful if you manually modify the generated files afterwards and you want to re-generate them after an API schema update, you will lose any manual changes.

Implement the generated interfaces

To implement the generated interfaces, we simply create an impl class - rest controller bean - that implements the interface from our gen package:

package org.acme.rest.controller;

import java.util.List;

import com.petclinic.api.PetsApi;
import com.petclinic.api.model.Pet;

public class PetClinicController implements PetsApi {

    @Override
    public void createPets() {
        // TODO Auto-generated method stub

    }

    @Override
    public List<Pet> listPets(Integer limit) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Pet showPetById(String petId) {
        // TODO Auto-generated method stub
        return null;
    }

}

And now we can invoke our API endpoint as usual: http://localhost:8080/pets - because /pets is the @Path annotation value in the generated PetsApi interface.

Serving API docs / Swagger-UI

A common requirement is that our backend API provider should also provide an endpoint with the schema or a Swagger-UI application with that schema. In our example, we decided to generate the JAX-RS interface without Swagger/OpenAPI annotations, therefore the schema can not be re-constructed 1:1 from our code (missing method documentation, error handling, etc.).

When having a Quarkus application and using the Smallrye OpenAPI extension, we can tell Quarkus to serve a static version of the API as our openapi schema (the same file we used to generate the interfaces and models) and to disable the auto-generating of the schema. Follow the Quarkus OpenAPI documentation for more info.

Advanced topics

In some cases, we may have specific requirements or API extensions that are not supported by the existing generators. OpenAPI generator project allows us to define custom genererator, or to extend the existing generator templates. We can also selectively generate subset of the models or API endpoints, generate test code and much more.

Clone this wiki locally