Mandatory way of versioning REST API:
Generating OpenApi documentation must be part of every service’s build.
To ensure consistent versioning, the following must be done in ever service:
openapi.properties
must define the version of the API with an integer. It must contain a line similar tomicronaut.openapi.expand.api.version=1
openapi.properties
must have the target file name the same as the service name. For example:micronaut.openapi.target.file=build/tmp/kapt3/classes/main/META-INF/swagger/output-manager.yml
Application.kt
must refer to the api version in the@OpenAPIDefinition
using the following string as version:version = "\${api.version}"
(note the\
, it is important to be there!)
There is a check in every pull request that requires the increment of the API version if there is any change in the generated OpenApi service-name.yml
file.
Optional steps to generate client libraries:
To avoid boilerplate code for DTO models and http-clients, repeated throughout different apps consuming REST APIs of other micro-services, we can generate API client libraries.
The client library should abstract away all the details of making a REST call. Works as a plug-and-play library that is simply added as a dependency, injected as a bean and ready to use.
Implemented using Open API specification, client code is generated based on the same generated yaml spec, used by swagger. Client lib is a micronaut HTTP client, so it can be easily injected.
To enable generation of client libraries the following has to be done:
build.gradle.kts
must define thegroup
of the project like this:group = project.properties["group"]!!
gradle.properties
must contain thegroup
definition, for examplegroup=ph.safibank.exampleservice
add
id("ph.safibank.common.generated.client.publisher") version "1"
to the plugin list. Make sure that our plugin repository is defined in thesettings.gradle.kts
pluginManagement { repositories { gradlePluginPortal() maven { url = uri("https://asia-southeast1-maven.pkg.dev/safi-repos/safi-maven") } } }
Add the client generation step to the service’s release and deployment Github workflow. Here is an example: https://github.com/SafiBank/SaFiMono/blob/main/.github/workflows/app-output-manager.yml#L20-L24
Generate-API-client: uses: ./.github/workflows/_app-generate-api-client.yml with: microservice_name: output-manager secrets: inherit
Details and explanation
Lifecycle:
The mandatory first three steps are there to have a single source of truth in the service for the REST version it provides.
The optional steps and the added plugin does the following:
generates client source code from openapi spec
each module exposing REST API should already have configuration and setup for swagger
during the build oas yaml spec is generated in:
services/{module}-manager/build/tmp/kapt3/classes/main/META-INF/swagger/{module}-manager.yml
the yaml spec is used to generate client sources
build and package generated project into jar
gradle builds sources generated in
$buildDir/generated/openapi
directorythis creates a jar file with the same version as the surrounding project
publish jar into safi maven repo
How to
1. Use in dependant app:
add dependency in
gradle.build.kts
, e.g.:
dependencies { implementation("ph.safibank.accountmanager:template-service-api-client:1") }
set base path property in
application.yaml
, e.g.:key for each api-client can be found in generated
DefaultApi
interface)
account-manager-api-client-base-path: ${SAFI_ACCOUNT_MANAGER_URL}
import, inject and use in code with optionally overwriting it’s default name:
import ph.safibank.accountmanager.client.api.DefaultApi as AccountManagerClient // other imports open class TransactionService( @Inject val accountManagerRestClient: AccountManagerClient ) { // bunch of other code. accountManagerRestClient.getAccount(id, true) // bunch of other code
FAQ:
Q: Need to have more insight into the communication?
A: The debugging is a bit problematic, but to have more control and insight, increase logging for http-client. Trace level gives you even the payloads.
<logger name="io.micronaut.http.client" level="TRACE" />
Q: Not sure if your client was generated or not available when used as dependency?
A: Several things can be verified about the build generating a client lib and publishing
Check that Generate API client action was triggered and successful in your module (should be using
_app-generate-api-client.yml
action)check maven repository at https://console.cloud.google.com/artifacts/maven/safi-repos/asia-southeast1/safi-maven - can be type in artifact or group id
Q: Do I need to keep the clients always at the last version?
A: No, our REST API versioning approach is trying to enforce backward compatibility. Using older versions should be OK. You’ll only need to upgrade if you need functionality provided only by a newer version or if there is an announcement about a breaking change. Be mindful about other teams and try to use the latest available version anyways.