Skip to main content
Skip table of contents

Getting Started - Code Generator (SDK) Integration

Introduction

The examples in the Getting Started Guidelines show fairly simple integration with relatively native code.

However, it is often not really useful to "transcribe" entire models.
Most importantly, copy errors can occur, which prevents data from being mapped, or changes can be made to the API that are only detected during runtime. Finding and fixing these changes can be time-consuming.

In order to always be in sync with the current API and not put effort into writing the models and basic API clients, it is recommended to use a code generator.

Implementation Guide

See a full working project here: finAPI Data Intelligence Product Platform Examples (Bitbucket)

The guidelines and the example project are written in Kotlin with Spring Boot. But it is easily adoptable for other languages and frameworks.

Please also note that the code presented here and also the repository is only an illustration of a possible implementation. Therefore, the code is kept very simple and should not be used 1:1 in a production environment.

This guide is based on the sample project and uses Gradle for integration.
As code generators, we use OpenAPI Generator. These are usually much better maintained than the original generators from Smartbears.

Notes for different integrations

Generally, it is recommended to use the generators in a submodule or a separate module on which the domain-oriented code depends.

The advantage of outsourcing to a separate module would be that you do not depend on the availability of the Internet. In addition, one can write tests for the client to be able to ensure the basic functionality.

However, the negative side of this approach is that API changes may not be detected unless you run the build system regularly to generate an up-to-date version and test it to detect any problems.

In a submodule, you don't have this disadvantage, because the generated code is always fresh. However, it can be that one cannot compile the own project, should the Internet not be available, as far as one fetches the OpenAPI files dynamically.
To compensate for this weakness, one could also store the OpenAPI file locally, but must also update it regularly.

For this example, we simply add the client generation to the existing project. However, we use an extra source directory so that the files are visible but also separated.

Preparation

We want to place the client under /apiclient/src.
This allows us to ignore some unneeded files via the .openapi-generator-ignore file and keep the generated code out of our repository via the .gitignore.
This should always be generated cleanly. Otherwise, there would be changed files on every build that the system wants to commit.

So we create the directory /apiclient in our project.

Integrate Generator into Gradle Build

Adding Generator to Gradle Classpath

For this, we need to modify the build.gradle file.
In the example project this can be viewed here.

First, we add the openapi-generator-gradle-plugin to the classpath as a dependency in the buildscript block (at the very beginning):

GROOVY
buildscript {
    [...]

    dependencies {
        [...]
        classpath "org.openapitools:openapi-generator-gradle-plugin:6.6.0"
    }
}

In the sample project this code can be found in the build.gradle at the bottom.

Now we still need to apply the plugins.
We also use the plugin en.undercouch.download so we can download the OpenAPI specification.

GROOVY
apply plugin: "org.openapi.generator"
apply plugin: "de.undercouch.download"

Download, generate, and clean tasks

Right after adding the plugins in the build.gradle file we define the OpenAPI file.

We introduce a variable openApiFileProcessController which contains only the file name of the API. The reason for this is that we need the filename in multiple contexts, but the URL itself only once.

In this example it is the file openapi-processctl.yaml:

GROOVY
def openApiFileProcessController = "openapi-processctl.yaml"
Download task

Now we can register a task that downloads the OpenAPI specification:

GROOVY
/**
 * Download API file
 */
tasks.register('download_ProcessControllerAPISpec', Download) {
    group "finAPI Process Controller"
    src "https://di-processctl-finapi-general-sandbox.finapi.io/${openApiFileProcessController}"
    dest buildDir
}

The task is of the type Download, which comes from the plugin en.undercouch.download.

In addition, we define the group finAPI Process Controller, so that we can see all relevant tasks grouped in the IDE.

As a source, we use the URL of the API of Sandbox. We add the previously defined OpenAPI file name from our variable. For now, the target is only our buildDir.

Generate task

In the next step, we create the task for generating the API.

GROOVY
/**
 * Generate REST API client for Process Controller.
 */
import org.openapitools.generator.gradle.plugin.tasks.GenerateTask
tasks.register("generateRestAPI_ProcessController", GenerateTask) {
    group "finAPI Process Controller"
    generatorName = "kotlin"
    inputSpec = "${buildDir}/${openApiFileProcessController}".toString()
    outputDir = "${projectDir}/apiclient/".toString()
    skipValidateSpec = true
    packageName = "io.finapi.client.processctl"
    apiPackage = "io.finapi.client.processctl.api"
    invokerPackage = "io.finapi.client"
    modelPackage = "io.finapi.client.processctl.models"
    globalProperties = [
            apiDocs: "false",
            apiTests: "false",
            modelTests: "false"
    ]
    configOptions = [
            library: "jvm-okhttp4",
            useSpringBoot3: "true",
            annotationLibrary: "none",
            documentationProvider: "none",
            modelMutable: "true",
            dateLibrary: "java8",
            enumPropertyNaming: "UPPERCASE"
    ]
}

The task is of type GenerateTask, which comes from the Code Generator.
We also add this task to the finAPI Process Controller group.
Under inputSpec we define where the generator plugin should find the OpenAPI specification. Here we use again our buildDir together with the variable openApiFileProcessController.

As outputDir we specify our previously created directory /apiclient/ which is inside our projectDir.

The rest of the parameters is configuration of the packages and the generator.
With Spring Boot 3 you should make sure that the parameter useSpringBoot3: "true" is present under configOptions.
Furthermore, we use the okhttp4 library as HTTP client.
Otherwise, the generator may use the old javax packages. However, Spring Boot 3 has switched to the jakarta packages in the meantime.

Clean task

Of course, we want to remove old generated files before we build to avoid dead code that may cause problems.

So we create the following task:

GROOVY
/**
 * Clean task.
 */
tasks.register('cleanApi_ProcessController', Delete) {
    group "finAPI Process Controller"
    delete "${projectDir}/apiclient/src"
    delete "${projectDir}/apiclient/.openapi-generator"
}

The task itself is of type Delete.
As before, the task is added to the finAPI Process Controller group.

We now always delete the two directories

  • /apiclient/src

  • /apiclient/.openapi-generator
    in our projectDir.

Since we do not write our own code under /apiclient, this is the fastest way. However, we cannot delete the /apiclient directory, because we need to add a file here later.

Defining the task dependencies

Now we have all the required tasks together and can define the dependencies of the tasks.

GROOVY
tasks.generateRestAPI_ProcessController.dependsOn(tasks.download_ProcessControllerAPISpec)
tasks.generateRestAPI_ProcessController.dependsOn(tasks.cleanApi_ProcessController)
tasks.compileKotlin.dependsOn(tasks.generateRestAPI_ProcessController)
tasks.clean.dependsOn(tasks.cleanApi_ProcessController)

First, we make sure that the OpenAPI file is downloaded first. For this, we create a dependency from the "generate task" to the "download task".

The second dependency says that the “generate task” always depends on the “clean task”. This means that old generated files are always deleted before new ones are generated.

The third dependency is between the “compile task” and the “generate task”. This ensures that the API client sources are always generated before the compile task.

Last but not least, we append the “API clean task” to the “general clean task”, so that the generated sources are also deleted during a gradlew clean.

Adding new source directories to the Gradle configuration

In order for Gradle to recognize the directory /apiclient/src/main/kotlin as the source directory, we still have to configure this.

A very simple variant would be to extend the sourceSets for main.java with the apiclient/src/main/kotlin:

GROOVY
// define source sets for generated classes
sourceSets {
    main {
        java {
            srcDirs = [
                    'apiclient/src/main/kotlin',
                    'src/main/java',
                    'src/main/kotlin'
            ]
        }
    }
}

Ignoring unused and unwanted files

By default, the generator is configured to create the API Client as a standalone project.
However, we do not need this and it has a significant disadvantage:
We want to generate the code during a build phase and not download it from an API via a client, unpack it, and only then build it.

This means that if we were to create a Gradle project now, the generator might overwrite important files (e.g. settings.gradle or build.gradle.kts).

Also, we don't need a Spring Boot main class to start the application, only the client. This also means that we do not need an application.yaml file.

In the /apiclient directory, we create a file .openapi-generator-ignore.

In this file, we can store files or directories that we do not want to generate.

So the content of the file could look like this:

TEXT
docs/
gradle/
src/main/kotlin/io/finapi/client/Application.kt
src/main/resources/application.yaml
pom.xml
build.gradle.kts
build.gradle
gradlew
gradlew.bat
README.md
settings.gradle

This would give us relatively cleanly generated code that we can use soon.

Adding required dependencies

Since the generated code has dependencies, of course, so that it can be compiled. We define these in the dependencies block.
In the case of the example project, we have moved the dependencies to the dependencies.gradle file for clearer maintenance. This can be found here.

As dependencies, we need the following libraries:

GROOVY
dependencies {
    [..]
    // required for code generator
    implementation "io.github.openfeign:feign-okhttp:12.4"
    implementation "com.squareup.moshi:moshi-kotlin:1.15.0"
    implementation "jakarta.validation:jakarta.validation-api"
}

Ignoring generated files in Git

The last step is to ignore the generated files in Git.

For this, we add the following lines to the .gitignore file:

TEXT
# api generator
/apiclient/src
/apiclient/.openapi-generator

Now the /apiclient/src and the apiclient/.openapi-generator directory will be ignored, but not our .openapi-generator-ignore file, which we created earlier under /apiclient.

Integrate Generator into Maven Build

For this example, we do not use the "normal" generation to target/generated-sources, but create a new directory /apiclient to have easier access to the code.

The full pom.xml can be shown here: pom.xml.sdk.example (Bitbucket)

Download the OpenAPI Specification

The first thing we need is a plugin to download the OpenAPI specification.

We also define the variable openApiFileProcessController so that we can use the name of the file multiple times without having to specify it multiple times.

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <properties>
        <java.version>17</java.version>
        <kotlin.version>1.8.22</kotlin.version>
        <openApiFileProcessController>openapi-processctl.yaml</openApiFileProcessController>
    </properties>
    
    [...]
    
    <build>
        [...]
        <plugins>
            <plugin>
                <groupId>com.googlecode.maven-download-plugin</groupId>
                <artifactId>download-maven-plugin</artifactId>
                <version>1.6.8</version>
                <executions>
                    <execution>
                        <id>download_ProcessControllerAPISpec</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>wget</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <url>https://di-processctl-finapi-general-sandbox.finapi.io/${openApiFileProcessController}</url>
                    <outputDirectory>${project.build.directory}/</outputDirectory>
                    <library>jvm-okhttp4</library>
                    <library>jvm-okhttp4</library>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Now we have the OpenAPI specification in the /target directory.

Integrate the Code Generator

Next, we add the code generator in the plugins section, just below the download plugin.

This is configured to use the kotlin generator (<generatorName>) and generate to ${project.basedir}/apiclient.

In addition, we set the cleanupOutput flag to true to get a clean on the directory before generation.

The other parameters are mostly the configuration of the generator for packages and to not generate some unnecessary things, because we just want to have the client code.

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    [...]
    
    <build>
        [...]
        <plugins>
            <plugin>
                <groupId>org.openapitools</groupId>
                <artifactId>openapi-generator-maven-plugin</artifactId>
                <version>6.6.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <inputSpec>${project.build.directory}/${openApiFileProcessController}</inputSpec>
                            <output>${project.basedir}/apiclient</output>
                            <generatorName>kotlin</generatorName>
                            <cleanupOutput>true</cleanupOutput>
                            <ignoreFileOverride>${project.basedir}/.openapi-generator-ignore</ignoreFileOverride>

                            <packageName>io.finapi.client.processctl</packageName>
                            <apiPackage>io.finapi.client.processctl.api</apiPackage>
                            <invokerPackage>io.finapi.client</invokerPackage>
                            <modelPackage>io.finapi.client.processctl.models</modelPackage>

                            <generateApiDocumentation>false</generateApiDocumentation>
                            <generateApiTests>false</generateApiTests>
                            <generateModelTests>false</generateModelTests>
                            <generateModelDocumentation>false</generateModelDocumentation>
                            <library>jvm-okhttp4</library>

                            <configOptions>
                                <sourceFolder>src/main/kotlin</sourceFolder>
                            </configOptions>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Define the new Folder as Source Folder

Basically, we now already have the code, but we still need to define apiclient/src/main/kotlin as a source directory:

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    [...]

    <build>
        [...]
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <execution>
                        <id>add-source</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>apiclient/src/main/kotlin/</source>
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugin>
    </build>
</project>

Adding Dependencies

To be able to compile now, we have to add a few dependencies:

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    [...]
    
    <dependencies>
        [...]
        <!-- Required for Generator -->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
            <version>12.4</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.moshi</groupId>
            <artifactId>moshi-kotlin</artifactId>
            <version>1.15.0</version>
        </dependency>
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    [...]
</project>

Ignoring unused and unwanted files

Since the generator creates a complete project, but we only want the source code for the client, we have to ignore some things.
For this, we create the file .openapi-generator-ignore in the root directory of the project:

CODE
**/docs/
**/gradle/
**/src/main/kotlin/io/finapi/client/Application.kt
**/src/main/resources/application.yaml
**/pom.xml
**/build.gradle.kts
**/build.gradle
**/gradlew
**/gradlew.bat
**/README.md
**/settings.gradle
**/.openapi-generator-ignore

Ignoring generated files in Git

In the case of Maven, we can add the apiclient folder to our .gitignore file.

Integrate Process Controller Create User with generated client

See a full working project here: finAPI Data Intelligence Product Platform Examples (Bitbucket)
Code from this guide can be found here: finAPI SDK Usage (Bitbucket)
Environment overview can be found here: Environments

Now we integrate the generated client and use it to create a process via the process controller and convert the process token into an access_token.

The process itself is described in a bit more detail under Obtain Authorization via Process Controller . It may therefore be worth reading this article as well.

Create client bean

To use the generated client, we need to initialize it.

We use the UserAndProcessTokenManagementApi client to create a simple user and get the access_token.

Our new class ProcessCtlSdkClient, therefore, needs as constructor parameters the URL of the process controller, our client_id, and our client_secret.

In addition, we need the DiProcessRepository to manage the state.

We initialize the client as a class variable with the passed basePath in the shape of the processCtlUrl.

KOTLIN
@Service
@Suppress("TooGenericExceptionThrown")
class ProcessCtlSdkClient(
    @Value("\${finapi.instances.processctl.url}") private val processCtlUrl: String,
    @Value("\${finapi.security.credentials.clientId}") private val clientId: String,
    @Value("\${finapi.security.credentials.clientSecret}") private val clientSecret: String,
    private val processesRepository: DiProcessesRepository
) {
    private val client = UserAndProcessTokenManagementApi(
        basePath = processCtlUrl
    )
}

Call the “Create new Process” endpoint with the client

Now we add the createProcessToken() function to the class, which calls the createNewProcess() method in the client.

We pass a valid DiProcessEntity to the function so that we can track the status. This entity also contains its own processId, which we use as clientReference.

If everything was successful, we update the status to PROCESS_TOKEN_CREATED and return the result of the API query.

KOTLIN
@Service
@Suppress("TooGenericExceptionThrown")
class ProcessCtlSdkClient(
    @Value("\${finapi.instances.processctl.url}") private val processCtlUrl: String,
    @Value("\${finapi.security.credentials.clientId}") private val clientId: String,
    @Value("\${finapi.security.credentials.clientSecret}") private val clientSecret: String,
    private val processesRepository: DiProcessesRepository
) {
    fun createProcessToken(
        processEntity: DiProcessEntity
    ): StartProcessResponse {
        // create process token with generated API client
        val processToken = client.createNewProcess(
            startProcessRequest = StartProcessRequest(
                clientId = clientId,
                clientSecret = clientSecret,
                processId = ProcessId.USER_ONLY,
                clientReferences = mutableListOf(
                    ClientReferenceEntry(
                        clientReference = processEntity.processId
                    )
                )
            ),
            withQRCode = false
        )

        // save new state
        processEntity.status = EnumProcessStatus.PROCESS_TOKEN_CREATED
        processesRepository.save(processEntity)

        return processToken
    }
    
    [...]
}

Exchange Process Token with access_token

Now that we have a process token, we can replace it with an access_token to gain access to the API.

For this, we call the enterProcess() method in the client.
This method expects the processToken as a mandatory parameter.
In this example, however, we want to invalidate the process token directly so that no one else can request an access_token. Therefore we set the parameter invalidate=true.

KOTLIN
@Service
@Suppress("TooGenericExceptionThrown")
class ProcessCtlSdkClient(
    @Value("\${finapi.instances.processctl.url}") private val processCtlUrl: String,
    @Value("\${finapi.security.credentials.clientId}") private val clientId: String,
    @Value("\${finapi.security.credentials.clientSecret}") private val clientSecret: String,
    private val processesRepository: DiProcessesRepository
) {
    @Throws(RuntimeException::class)
    fun exchangeProcessTokenWithAccessToken(processId: String, processToken: UUID): AccessToken {
        // exchange process token with access_token
        val accessToken = client.enterProcess(
            processToken = processToken,
            invalidate = true
        )

        // update state of process in database or throw exception
        val processEntity = processesRepository.findFirstByProcessId(processId = processId)
            ?: throw RuntimeException("Unable to exchange Process Token. No Process Entity found.")
        processEntity.status = EnumProcessStatus.PROCESS_TOKEN_EXCHANGED

        return accessToken
    }
    
    [...]
}

Now to show how to combine both methods, we create a function createAndExchangeProcessToken().

This takes care of creating the process entity, calls the function createProcessToken() and exchangeProcessTokenWithAccessToken(), and returns the object AccessToken.

KOTLIN
@Service
@Suppress("TooGenericExceptionThrown")
class ProcessCtlSdkClient(
    @Value("\${finapi.instances.processctl.url}") private val processCtlUrl: String,
    @Value("\${finapi.security.credentials.clientId}") private val clientId: String,
    @Value("\${finapi.security.credentials.clientSecret}") private val clientSecret: String,
    private val processesRepository: DiProcessesRepository
) {
    fun createAndExchangeProcessToken(): AccessToken {
        val processEntity = processesRepository.save(
            DiProcessEntity(
                processId = UUID.randomUUID().toString(),
                status = EnumProcessStatus.PROCESS_TOKEN_INIT
            )
        )

        val processTokens = createProcessToken(
            processEntity = processEntity
        )

        if (processTokens.processes.isNullOrEmpty()) {
            processEntity.status = EnumProcessStatus.PROCESS_TOKEN_FAILED
            processesRepository.save(processEntity)
            throw RuntimeException("No processes created.")
        }

        // We have created only one client reference, so it contains only one process token.
        return exchangeProcessTokenWithAccessToken(
            processId = processEntity.processId,
            processToken = processTokens.processes!![0].processToken
        )
    }
    
    [...]
}

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.