Spring Boot Apps with Kong API Gateway using OpenAPI & Declarative Configuration

No Comments

No matter what architecture you’re planning to build: an API Gateway is mostly part of a modern setup. So why not connect your Spring Boot apps with Kong API Gateway using a standard like OpenAPI and configuration-as-code?!

Finding a good way to integrate

I have to admit that I’am a little late to the party. Many of my colleagues have already written posts on Kong API Gateway on our codecentric blog. If you want to read more about Kong as an API Gateway, I can recommend having a look. Getting involved has been on my list for a long time. So here we go. 🙂

Spring Boot connected with Kong

Logo sources: Kong logo, Spring Boot logo

I always want to know how I can integrate new technologies into my tool belt. So why not try to integrate Kong as an API Gateway for Spring Boot Apps? Maybe that’s also a problem you have, since you landed here on this article. And it shouldn’t be a big problem, right? Hmm, googling around, I wasn’t convinced about the solutions I found.

Many of the implementations needed code changes or the usage of custom annotations on the Spring side in order to do the integration. A lot of them relying on libraries with a suspiciously small user base. Or I found many articles describing manual steps, where I would need to click on this and that in order to connect my app with Kong. None of these approaches really seem to work out in the long run. So I began to dive a bit deeper into the topic. And I developed some requirements for the solution I was trying to find:

1. There shouldn’t be any need to change the code of my Spring app! Since there’s no silver bullet, I always want to be able to use other tools with minimal effort if my requirements change. And I also want to integrate my existing Spring Boot apps into the API Gateway seamlessly.
2. I had no clue about Kong, but I wanted to use it really as any other tool that is able to remain in my toolbelt: It should be configurable 100% by code – so that no clicks will be needed at all.
3. Many posts about Kong involve a huge Docker Compose file with 3 to 4 services solely needed to fire up a API Gateway instance. I like full-blown setups for production use, but I wanted to have a setup where Kong only needs one service – like other solutions I saw with for example Spring Cloud Gateway or Traefik.
4. The integration of Spring Boot and Kong should be fully automated. Since code is here to change, every change affecting my app’s REST interface should be reflected automatically in the API Gateway. And I want to be able to automatically verify that in my CI/CD pipeline.

The setup

Finally I found a setup that should work for many use cases and would also meet my requirements. Let’s take a look onto the following diagram:

the setup of the blog post using OpenAPI & Insomnia Inso

Logo sources: OpenAPI logo, Kong logo, Insomnia logo, Inso logo

So the idea is to use OpenAPI as the integration glue here. I think that’s a great specification – even without using an API Gateway. Maybe it’s even already there in your project?! In order to simplify the usage of OpenAPI with Spring, there’s the great springdoc-openapi project which we’ll use in our setup.

In addition, we’ll also focus on Kong’s Declarative Configuration option. This will have a bunch of benefits. First, this approach enables us to use Kong as fully configurable by code. It also enables a really elegant setup, where we only need one single Kong service – since there’s also a DB-less mode, where Kong doesn’t need any database. And finally using the openapi-2-kong functionality of the Insomnia CLI (“Inso”) we can directly generate the Kong Declarative Configuration file from our OpenAPI specification that is derived from our Spring Boot REST API. As we don’t want to get a headache by using too many buzzwords, we should get our hands dirty and simply build this setup from the ground up! To really be able to comprehend every single step, you may also want to look into the example project on GitHub.

Creating a Spring Boot app or choose an existing one

This is the easy part. We all know where to start if we want to kick off a new Spring Boot project. Go to start.spring.io and generate a Spring REST app skeleton matching your needs. You may also choose one of your existing apps to start with. I simply took the weatherbackend app from this Spring Cloud-based project, which was part of a blog post I wrote in 2017 about a Spring Cloud Netflix-stack-based setup. Additionally I also wanted to get a feeling for the differences between the setup back then compared to the usage of Kong API Gateway today.

The weatherbackend Spring Boot app is using standard dependencies like spring-boot-starter-web to implement some Spring MVC based REST endpoints. The class WeatherBackendAPI.java looks like something you would expect:

package io.jonashackt.weatherbackend.api;
 
import io.jonashackt.weatherbackend.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
 
@RestController
@RequestMapping("/weather")
public class WeatherBackendAPI {
 
    @PostMapping(value = "/general/outlook", produces = "application/json")
    public @ResponseBody GeneralOutlook generateGeneralOutlook(@RequestBody Weather weather) throws JsonProcessingException {
        ...
        return outlook;
    }
 
    @GetMapping(value = "/general/outlook", produces = "application/json")
    public @ResponseBody String infoAboutGeneralOutlook() throws JsonProcessingException {
        ...
        return "Try a POST also against this URL! Just send some body with it like: '" + weatherJson + "'";
    }
 
    @GetMapping(value = "/{name}", produces = "text/plain")
    public String whatsTheSenseInThat(@PathVariable("name") String name) {
        return "Hello " + name + "! This is a RESTful HttpService written in Spring. :)";
    }
}

Generating the OpenAPI spec with the springdoc-openapi-maven-plugin

Now that we have a running Spring Boot app in place, we need to take a look at the OpenAPI spec generation. As already stated, there is the springdoc-openapi-maven-plugin waiting to help us:

The aim of springdoc-openapi-maven-plugin is to generate JSON and yaml OpenAPI description during build time. The plugin works during integration-tests phase and generates the OpenAPI description. The plugin works in conjunction with spring-boot-maven plugin.

In order to successfully use the springdoc-openapi-maven-plugin, we also need to add the springdoc-openapi-ui plugin (for Tomcat / Spring MVC based apps) or the springdoc-openapi-webflux-ui plugin (for Reactive WebFlux / Netty based apps) as a dependency in our pom.xml:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.4.8</version>
</dependency>

Without using this additional dependency, the springdoc-openapi-maven-plugin will run into errors like [ERROR] An error has occured: Response code 404. As described in this stackoverflow answer , we need to use both plugins to successfully generate our OpenAPI spec file. And because we added the springdoc-openapi-ui dependency, we are also able to access the live API documentation already at localhost:8080/swagger-ui.html.

Now we can add the springdoc-openapi-maven-plugin to our pom.xml:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>pre-integration-test</id>
            <goals>
                <goal>start</goal>
            </goals>
        </execution>
        <execution>
            <id>post-integration-test</id>
            <goals>
                <goal>stop</goal>
            </goals>
        </execution>
    </executions>
</plugin>
<plugin>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-maven-plugin</artifactId>
    <version>1.1</version>
    <executions>
        <execution>
            <phase>integration-test</phase>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
</plugin>

As you see, we also need to tell the spring-boot-maven-plugin to start and stop the integration test phases, since the springdoc-openapi-maven-plugin will use the live documentation of a running Spring Boot app to generate the OpenAPI spec file. In order to finally generate the file, simply execute Maven with: mvn verify. The output should look like this:

...
[INFO] --- springdoc-openapi-maven-plugin:1.1:generate (default) @ hellobackend ---
2020-11-04 10:26:09.579  INFO 42143 --- [ctor-http-nio-2] o.springdoc.api.AbstractOpenApiResource  : Init duration for springdoc-openapi is: 29 ms
...

This indicates that the OpenAPI spec generation was successful. Therefore, we need to take a look into the weatherbackend/target directory, where a file called openapi.json should be present now. And this is great news! Without changing any code, we generated our OpenAPI spec file. 🙂

Tweak the API information in the generated OpenAPI spec

We could just go on with that openapi.json file, but we may want to tweak it slightly. Since if we move forward, we will notice that some information should be changed to achieve a better integration into Kong. Especially the element "title": "OpenAPI definition" is later used as the Kong service name. Also the element "url": "http://localhost:8080" is used to configure the upstream service endpoint protocol, host and port inside the Kong service definition.

I know that “this is code”. But again we don’t need to change any existing code and we don’t need to introduce new classes/annotations into our normal Spring code. We simply create a new separate class that uses the @OpenAPIDefinition annotation to configure the needed service info. So let’s create a class like OpenAPIConfig.java that looks as follows:

package io.jonashackt.weatherbackend.api;
 
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.servers.Server;
 
@OpenAPIDefinition(
        info = @Info(
                title = "weatherbackend",
                version = "v2.0"
        ),
        servers = @Server(url = "http://weatherbackend:8080")
)
public class OpenAPIConfig {
}

Using the @Info annotation’s field title we can specify the Kong service name. And with the url field of the @Server annotation we define how Kong internally accesses our service later.

Having that class in place, we can do another generation of our openapi.json by running mvn verify -DskipTests=true. This should have the new information propagated (you may need to reformat the code inside you IDE to not just see a one-liner):

{
  "openapi": "3.0.1",
  "info": {
    "title": "weatherbackend",
    "version": "v2.0"
  },
  "servers": [
    {
      "url": "http://weatherbackend:8080",
      "variables": {}
    }
  ],
  "paths": {
    "/weather/general/outlook": {
      "get": {
        "tags": [
          "weather-backend-api"
        ],
        "operationId": "infoAboutGeneralOutlook",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },

Generating Kong Declarative Configiguration from OpenAPI spec

We could start generating the Kong Declarative Configuration file from our OpenAPI spec using Insomnia Designer and the Kong bundle plugin. But as we need to do this generation every time our Spring API code changes, this wouldn’t match our requirements. Because otherwise the configuration in Kong would differ more and more with every change! Additionally we want to be able to run the generation process on our CI servers as well.

And there’s another cool tool to help us here: Insomnia Inso CLI. Because Inso CLI encorporates a OpenAPI to Kong Configuration converting functionality using the npm library openapi-2-kong. In order to use Inso CLI, we need to install it using npm:

npm i -g insomnia-inso

If you’re a MacOS user like me and run into problems like clang: error: no such file or directory: '/usr/include', you may want to look at this stackoverflow answer.

Now with Inso CLI readily installed, we can finally go from openapi.json to kong.yml. All we have to do is use the inso generate config command as described in the docs. We should add the option --type declarative, since the output should result in a Kong declarative configuration file. Additionally, we need to tell Inso CLI where to find our OpenAPI spec file at weatherbackend/target/openapi.json. The last part is to define where the Kong declarative configuration file should be located using the --output kong/kong.yml parameter. So here’s the fully working Inso CLI command:

inso generate config weatherbackend/target/openapi.json --output kong/kong.yml --type declarative --verbose

If you want to see a bit more of an info what the inso CLI is doing, you can also add --verbose to the command. After executing the command, we should find a new file inside our project at kong/kong.yml, which contains our desired Kong Declarative Configuration:

_format_version: "1.1"
services:
  - name: weatherbackend
    url: http://weatherbackend:8080
    plugins: []
    routes:
      - tags:
          - OAS3_import
        name: weatherbackend-path-get
        methods:
          - GET
        paths:
          - /weather/general/outlook
        strip_path: false
      - tags:
          - OAS3_import
        name: weatherbackend-path_1-post
        methods:
          - POST
        paths:
          - /weather/general/outlook
        strip_path: false
      - tags:
          - OAS3_import
        name: weatherbackend-path_2-get
        methods:
          - GET
        paths:
          - /weather/(?<name>\S+)$
        strip_path: false
    tags:
      - OAS3_import
upstreams:
  - name: weatherbackend
    targets:
      - target: weatherbackend:8080
    tags:
      - OAS3_import

Really cool! We found an automatable way on how to generate Kong Declarative Configuration from our OpenAPI spec 🙂

Running the Kong Declarative Configuration generation inside the Maven build

Because our API code inside our Spring Boot app evolves and changes, we should initialize a re-generation of our Kong Declarative Configuration file everytime we change our Spring Boot app’s code. Playing with different possibilities from where to trigger the automatic re-generation (Docker, Compose, CI server, …), I found a really simple solution to bind this crucial step to our standard build process.

I simply used the exec-maven-plugin to integrate the inso CLI execution into our standard build process (I’am sure you can do this using different build tools also). Although the exec-maven-plugin‘s XML syntax may look a bit strange at first sight, it makes totally sense to have the generation of our kong.yml also directly coupled to our build process. Therefore let’s have a look at the needed addition to the pom.xml:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>3.0.0</version>
    <executions>
        <execution>
            <id>execute-inso-cli</id>
            <phase>verify</phase>
            <goals>
                <goal>exec</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <arguments>
            <argument>generate</argument>
            <argument>config</argument>
            <argument>target/openapi.json</argument>
            <argument>--output</argument>
            <argument>../kong/kong.yml</argument>
            <argument>--type</argument>
            <argument>declarative</argument>
            <argument>--verbose</argument>
        </arguments>
    </configuration>
</plugin>

Using mvn exec:exec, we are now able to execute inso CLI through Maven:

$ mvn exec:exec
[INFO] Scanning for projects...
[INFO]
[INFO] ------------< io.jonashackt.weatherbackend:weatherbackend >-------------
[INFO] Building weatherbackend 2.3.5.RELEASE
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:3.0.0:exec (default-cli) @ weatherbackend ---
› Data store configured from app data directory at /Users/jonashecht/Library/Application Support/Insomnia Designer
› Load api specification with identifier target/openapi.json from data store
› Found 0.
› Generating config from file target/openapi.json
Configuration generated to "../kong/kong.yml".
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.671 s
[INFO] Finished at: 2020-11-05T14:05:04+01:00
[INFO] ------------------------------------------------------------------------

As you can see, the inso CLI logging Configuration generated to "kong/kong.yml". is part of the output.

In addition, we can push the integration into our build process even further: as mentioned by Pascal, we can even bind the execution of the exec-maven-plugin to the standard Maven build. All we have to do is use the tag to bind the execution to the verify phase:

<executions>
    <execution>
        <id>execute-inso-cli</id>
        <phase>verify</phase>
        <goals>
            <goal>exec</goal>
        </goals>
    </execution>
</executions>

And that’s pretty cool, since that’s exactly Maven phase where the generation of the OpenAPI spec also takes place. Now with this addition, a normal mvn verify does every needed step for us to generate a Kong Declarative Configuration file from our Spring Boot REST endpoints! Just have a look at the build log (I’ve shortened that one a bit):

$ mvn verify
...
[INFO] --- spring-boot-maven-plugin:2.3.5.RELEASE:start (pre-integration-test) @ weatherbackend ---
[INFO] Attaching agents: []
 
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.5.RELEASE)
...
2020-11-12 08:33:23.006  INFO 23312 --- [           main] i.j.w.WeatherBackendApplication          : Started WeatherBackendApplication in 1.867 seconds (JVM running for 2.371)
[INFO]
[INFO] --- springdoc-openapi-maven-plugin:1.1:generate (default) @ weatherbackend ---
2020-11-12 08:33:23.581  INFO 23312 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-11-12 08:33:23.581  INFO 23312 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-11-12 08:33:23.585  INFO 23312 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 4 ms
2020-11-12 08:33:23.815  INFO 23312 --- [nio-8080-exec-1] o.springdoc.api.AbstractOpenApiResource  : Init duration for springdoc-openapi is: 207 ms
...
[INFO]
[INFO] --- exec-maven-plugin:3.0.0:exec (execute-inso-cli) @ weatherbackend ---
› Data store configured from app data directory at /Users/jonashecht/Library/Application Support/Insomnia Designer
› Load api specification with identifier target/openapi.json from data store
› Found 0.
› Generating config from file target/openapi.json
Configuration generated to "../kong/kong.yml".
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  11.185 s
[INFO] Finished at: 2020-11-05T14:07:54+01:00
[INFO] ------------------------------------------------------------------------

This means that a standard Maven build command like mvn package will build & test our Spring Boot app, then generate the openapi.json using the springdoc-openapi-maven-plugin and finally generate the kong.yml via Inso CLI executed by the exec-maven-plugin!

Docker Compose with Kong DB-less deployment & declarative configuration

Up to this point, the setup pretty much met every requirement I had set myself at the beginning. But there’s one thing missing: A streamlined Kong deployment. For the course of this post I wanted to stay with the simplest possible infrastructure for the moment: a Docker Compose setup.

Looking at the official Docker Compose file, I found two (!!) database migration services, one database service and one service for Kong. I was really overwhelmed at first sight. But digging into the topic of how to deploy Kong, I found the DB-less deployment based on Kong Declarative Configuration in the official docs. Hey! Didn’t we generate Declarative Configuration already? Would it be possible to simply deploy Kong based on this kong.yml?

To my surprise, I found out: yes, it’s possible! And as the docs state the DB-less deployment has some advantages over a deployment using a database:

1. a reduced number of dependencies: no need to manage a database installation if the entire setup for your use cases fits in memory
2. it is a good fit for automation in CI/CD scenarios: configuration for entities can be kept in a single source of truth managed via a Git repository
3. it enables more deployment options for Kong

Of course there are some drawbacks also (as always). Not all plugins support this mode and there is no central configuration database if you want to run multiple Kong nodes. But I guess for our setup here we can live very well with that. So let’s just create a docker-compose.yml:

version: '3.7'

services:
  kong:
    image: kong:2.2.0
    environment:
      KONG_ADMIN_ACCESS_LOG: /dev/stdout
      KONG_ADMIN_ERROR_LOG: /dev/stderr
      KONG_ADMIN_LISTEN: '0.0.0.0:8001'
      KONG_DATABASE: "off"
      KONG_DECLARATIVE_CONFIG: /usr/local/kong/declarative/kong.yml
      KONG_PROXY_ACCESS_LOG: /dev/stdout
      KONG_PROXY_ERROR_LOG: /dev/stderr
    volumes:
      - ./kong/:/usr/local/kong/declarative
    networks:
      - kong-net
    ports:
      - "8000:8000/tcp"
      - "127.0.0.1:8001:8001/tcp"
      - "8443:8443/tcp"
      - "127.0.0.1:8444:8444/tcp"
    healthcheck:
      test: ["CMD", "kong", "health"]
      interval: 10s
      timeout: 10s
      retries: 10
    restart: on-failure
    deploy:
      restart_policy:
        condition: on-failure
 
  # no portbinding here - the actual services should be accessible through Kong
  weatherbackend:
    build: ./weatherbackend
    ports:
      - "8080"
    networks:
      - kong-net
    tty:
      true
    restart:
      unless-stopped

networks:
  kong-net:
    external: false

I litterally threw everything out we don’t really really need in a DB-less scenario. No kong-migrations, kong-migrations-up, kong-db services – and no extra Dockerfile as stated in this blog post. With that we only have a single kong service for the API gateway – and a weatherbackend Spring Boot service.

In order to make the DB-less deployment work, we need to use some special Kong environment variables. First we switch to DB-less mode using KONG_DATABASE: "off". Then we tell Kong where to get its Declarative Configuration file via the variable KONG_DECLARATIVE_CONFIG: /usr/local/kong/declarative/kong.yml.

The final thing is to make our generated kong.yml available inside the Kong container at /usr/local/kong/declarative/kong.yml. Therefore I used a simple volume mount like this: ./kong/:/usr/local/kong/declarative. With this solution there’s also no need to manually create the Volume as described in the docs. Or to create another Dockerfile solely to load the config file into the Kong container, as stated in some posts. And we don’t even need to use decK here, since this tool is only needed to sync Declarative Configuration with the Kong database. 🙂 Now this thing started to be fun! So let’s fire up our Kong setup by running a well-known docker-compose up:

An commandline recording of the Docker-Compose startup process

How cool is that? We just should have an eye on one thing: the crucial part here is that Kong successfully loads the declarative configuration file and logs something like [kong] init.lua:354 declarative config loaded from /usr/local/kong/declarative/kong.yml.

Having fired up our whole setup, we could now have a look at Kong’s admin API by opening localhost:8001 in our Browser. We can also double-check localhost:8001/status, where we have a good overview of Kong’s current availability.

Accessing our Spring Boot app through Kong

Now let’s look if our Spring Boot app is ready to be accessed through our API Gateway. Specifically, we need to examine the configured Kong services and find out if the OpenAPI spec import worked in the way we’d expected it in the first place. Therefore let’s look at the list of all currently registered Kong services at localhost:8001/services:

Kong admin API services overview

An important point here is the host(name) and port of our Spring Boot service. The service access through Kong relies on this configuration and it is defined as target inside the upstreams section of the Kong Declarative Configuration kong.yml:

...
upstreams:
  - name: weatherbackend
    targets:
      - target: weatherbackend:8080
    tags:
      - OAS3_import

We already tweaked the API information in the generated OpenAPI spec to make it suitable for our Docker-Compose setup, because here Docker generates a DNS name called weatherbackend for us, which is based on the Docker-Compose service name. If you choose another setup, take a closer look at this configuration! Kong doesn’t ship with DNS resolver or anything. Be aware that this is a thing you need to take care of yourself. Especially if you run into errors like connect() failed (111: Connection refused) while connecting to upstream.

Now we should have everything in place to access our Spring Boot service through Kong! We can now use Postman, Insomnia Core or another HTTP client to access our Spring Boot app with a GET on localhost:8000/weather/MaxTheKongUser

Service access with Postman

Looking into our Docker Compose log, we should also see the successful responses from our weatherbackend service:

weatherbackend_1  | 2020-11-05 07:54:48.381  INFO 7 --- [nio-8080-exec-1] i.j.controller.WeatherBackendController  : Request for /{name} with GET
kong_1            | 172.19.0.1 - - [05/Nov/2020:07:54:48 +0000] "GET /weather/MaxTheKongUser HTTP/1.1" 200 133 "-" "PostmanRuntime/7.26.1"
weatherbackend_1  | 2020-11-05 07:54:59.951  INFO 7 --- [nio-8080-exec-2] i.j.controller.WeatherBackendController  : Request for /{name} with GET
kong_1            | 172.19.0.1 - - [05/Nov/2020:07:54:59 +0000] "GET /weather/MonicaTheKongUser HTTP/1.1" 200 136 "-" "PostmanRuntime/7.26.1"
weatherbackend_1  | 2020-11-05 07:55:06.573  INFO 7 --- [nio-8080-exec-3] i.j.controller.WeatherBackendController  : Request for /{name} with GET
kong_1            | 172.19.0.1 - - [05/Nov/2020:07:55:06 +0000] "GET /weather/MartinTheKongUser HTTP/1.1" 200 136 "-" "PostmanRuntime/7.26.1"

If you want to know more about the URI paths that are configured in Kong, simply take a look at the /servicename/routes api at localhost:8001/services/weatherbackend/routes.

Automatically re-generating OpenAPI spec & Kong Declarative Configuration using your CI/CD server

As we want to make sure everything works as expected every time code changes, we need to include the whole process into our CI/CD pipeline! For this post , I went with TravisCI since I needed quite a flexible solution that is capable of running a full Maven build, the npm based Inso CLI and fire up a Docker-Compose setup at the same time. But this should be reproducible on every other CI platform that is able to handle all those things also. The example project on GitHub ships with a fully working .travis.yml. Let’s look at the first part of it:

language: node_js
node_js:
  - 15

services:
  - docker

script:
  # Install insomnia-inso (Inso CLI) which is needed by our Maven build process later
  - npm install insomnia-inso
  - node_modules/insomnia-inso/bin/inso --version
 
  # Install Java & Maven with SDKMAN
  - curl -s "https://get.sdkman.io" | bash
  - source "$HOME/.sdkman/bin/sdkman-init.sh"
  - sdk install java 15.0.1.hs-adpt
  - sdk install maven
 
  # Build Spring Boot app with Maven
  # This also generates OpenAPI spec file at weatherbackend/target/openapi.json
  # and the Kong declarative config at kong/kong.yml from the OpenAPI spec with Inso CLI
  - mvn clean verify --file weatherbackend/pom.xml --no-transfer-progress -Dinso.executable.path=node_modules/insomnia-inso/bin/inso
 
  # Show kong.yml
  - cat kong/kong.yml
...

There are some things to note about the pipeline here. First, I advise you to choose the latest node_js image possible or you may experience strange errors of insomnia-inso. I chose Travis’ node_js version 15. Another source of error might be the inso executable itself since TravisCI wasn’t able to find it:

ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.0.0:exec (execute-inso-cli) on project weatherbackend: Command execution failed.: Cannot run program "inso" (in directory "/home/travis/build/jonashackt/spring-boot-openapi-kong/weatherbackend"): error=2, No such file or directory -> [Help 1]

I found a solution for that problem while having a look at this stackoverflow answer. We simply need to override the inso executable path on TravisCI. Therefore need to alter our pom.xml slightly to use a new property called ${inso.executable.path}:

<properties>
    ...
    <inso.executable.path>inso</inso.executable.path>
</properties>
...
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>3.0.0</version>
...
    <configuration>
        <executable>${inso.executable.path}</executable>
        <arguments>
...

With this change we should be able to run our normal mvn verify locally – and a special mvn verify -Dinso.executable.path=inso-special-path on TravisCI like this:

mvn clean verify --file weatherbackend/pom.xml --no-transfer-progress -Dinso.executable.path=node_modules/insomnia-inso/bin/inso

Right after the Maven build finished, we also sneak peak into the generated kong.yml via cat kong/kong.yml – since this is the prerequisite for Kong to start up correctly configured later.

Continuously test-drive the Spring Boot service access through Kong

As we want our Kong instance to always use the latest generated Declarative Configuration file, it is crucial to fire up the infrastructure only after a fresh Maven build. Now that the build is done, we can finally start Kong with the latest API definition. This is also reflected in the second part of the .travis.yml:

...
  # Fire Up Docker Compose setup with Kong
  - docker-compose up -d
 
  # Let's wait until Kong is available (we need to improve this)
  - sleep 10
 
  # Also have a look into the Kong & Spring Boot app logs
  - docker ps -a
  - docker-compose logs kong
  - docker-compose logs weatherbackend
 
  # Have a look at the /services endpoint of Kong's admin API
  - curl http://localhost:8001/services
 
  # Verify that we can call our Spring Boot service through Kong
  - curl http://localhost:8000/weather/MaxTheKongUser
 
  # Again look into Kong logs to see the service call
  - docker-compose logs kong

Right after our Docker-Compose setup with docker-compose up -d we need to wait for the containers to spin up. On TravisCI we can simply use sleep here. Thereafter, the containers should both be ready and we can take a look into the Kong & Spring Boot app logs with docker-compose logs kong & docker-compose logs weatherbackend.

After checking the service admin API with curl http://localhost:8001/services we finally curl for our service through Kong with curl http://localhost:8000/weather/MaxTheKongUser – just as we would do it on our local machine.

Integrating Spring Boot & Kong is fun

Wow! That was quite a journey connecting our Spring Boot apps with Kong. Using springdoc-openapi, we found a way to elegantly generate OpenAPI spec files from our Spring code – without touching it. So existing apps should also be able to use the setup. Leveraging Insomnia Inso CLI, we managed to generate Kong Declarative Configuration from our OpenAPI file, and all of this completely inside our build process. No manual steps required! Together with the DB-less Kong infrastructure setup, we directly choose the Declarative Configuration as the way to elegantly configure Kong. And as icing on the cake, all this is 100% automated inside our CI/CD pipeline so that every code change triggers a re-generation of the Kong configuration. I guess it is safe to say that we met the initial requirements of this article 🙂

Hopefully this article inspires you to get your hands on API Gateway. In some future posts we may take a look at how to integrate different API gateways with Spring Boot. Stay tuned!

Jonas Hecht

After getting in love with Spring, Jonas also found his taste for to all those container and infrastructure stuff. Now he focusses on bringing methods like testdriven development and continuous integration to the world of infrastructure code. He founded the codecentric site in Erfurt/Thuringia and is dedicated to the local community, organizing the Java User Group Thüringen, DevOps Thüringen & IoT Thüringen Meetups. He loves to write blog posts & give lectures at Thuringian universities. Spare time is reserved for his family and mountain biking.

Comment

Your email address will not be published. Required fields are marked *