SaFi Bank Space : WIP - Design-First Approach in API Layer (Controller)

Reference-documents for initial reading: https://www.visual-paradigm.com/guide/development/code-first-vs-design-first/ https://www.polak.ro/2021/04/02/api-first-and-code-generation-done-right.html

This document explains how to use the openapi-generator to come-up with the underlying Controller/DTO codes based on an API contract (OpenAPI/Swagger specifications).

Coming up with the API contract first, allows for a readable document that both the business-stakeholders and developers can easily understand. This also allows for a cleaner way of developing the controller components, leaving it out to the generator to build components containing the annotations on controller and DTOs which developers would otherwise code manually. This way, developers will simply focus on the business-logic part of the controller, by overriding the generated controller-components containing the intended core-service calls.

How Tos

1. Build a version of OpenAPI specs (contract) based on requirements (e.g. euronet-gateway-api-1.0.0.yaml)

openapi: 3.0.1
info:
  title: Card Transaction API
  version: 1.0.0
paths:
  /transaction/card-outbound-auth:
    post:
      tags:
        - Transaction
      summary: Initiates new transaction to reserve fund.
      description: Returns information about initialized transaction status.
      operationId: initiateCardTransactionOutboundAuth
      parameters:
        - name: idempotency-key
          in: header
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OutboundAuthTransactionRequestDto"
      responses:
        "201":
          description: Transaction was created internally and is being processed by the payment gateway
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CardTransactionOutboundAuthResponseDto"
        "400":
          description: Requested payload contains invalid properties
        "500":
          description: Internal Server Error

components:
  schemas:
    OutboundAuthTransactionRequestDto:
      allOf:
        - $ref: "#/components/schemas/CommonRequestDto"
        - $ref: "#/components/schemas/ExtendedRequestDto"

#... removed other configuration

2. OpenAPI generator plugin is added to the domain-service buil containing:

a. plugin-definition with common config options (which can also be further extracted a common plugin)

b. openApiGenerate task (adding it as a dependency for the compile-task

c. reference to the generated-source directory

plugins {
    id("org.openapi.generator") version "6.2.0"
}

openApiGenerate {
    generatorName.set("java-micronaut-server")
    inputSpec.set("$projectDir/src/main/resources/openapi/euronet-gateway-api-1.0.0.yaml".toString())
    outputDir.set("$buildDir/generated/openapi".toString())
    templateDir.set("$projectDir/src/main/resources/openapi/templates".toString())
    apiPackage.set("ph.safibank.euronetgateway.api")
    modelPackage.set("ph.safibank.euronetgateway.model")
    generateApiDocumentation.set(false)
    generateApiTests.set(false)
    generateModelTests.set(false)

    configOptions.set(
        mapOf(
            "generateControllerAsAbstract" to "true",
            "templateDir" to "$projectDir/src/main/resources/openapi/templates",
            "oas3" to "true",
            "build" to "gradle",
            "micronautVersion" to "3.7.0",
            "dateLibrary" to "java8",
            "reactive" to "false",
            "delegatePattern" to "true",
            "interfaceOnly" to "true",
            "skipDefaultInterface" to "false",
            "serializationLibrary" to "jackson",
            "openApiNullable" to "false",
            "micronautVersion" to "${project.properties["micronautVersion"]}"
        )
    )
}


tasks {
    compileKotlin {
        kotlinOptions {
            jvmTarget = "17"
        }
        dependsOn("openApiGenerate")
    }
}


sourceSets.main.get().java {
    srcDir("$buildDir/generated/openapi/src/main/java")
}

3. Override the generated Controller component and add the required core-service logic

Generated components:

Override the generated controller-component: