27 Entwicklung mit dem Contract-First-Ansatz unter Verwendung von OpenAPI und dem OpenAPI-Generator

Im Gegensatz zum Code-First-Ansatz, bei dem die API-Dokumentation aus dem Code generiert wird, beginnt der Contract-First-Ansatz mit der Definition der API-Spezifikation. Diese Spezifikation dient als Vertrag zwischen Frontend- und Backend-Teams sowie zwischen Dienstanbietern und Nutzern. Der große Vorteil dieses Ansatzes liegt in der klaren Definition der API, noch bevor der erste Codezeile geschrieben wird. OpenAPI (früher als Swagger bekannt) ist ein weit verbreiteter Standard für die Definition von RESTful APIs. Der OpenAPI-Generator kann dann verwendet werden, um aus dieser Spezifikation Server-Stubs, Client-Bibliotheken und API-Dokumentationen automatisch zu generieren.

27.1 Definition einer OpenAPI-Spezifikation

Der erste Schritt im Contract-First-Ansatz ist die Erstellung einer OpenAPI-Spezifikation. Dies ist eine YAML- oder JSON-Datei, die die Endpunkte, Operationen, Parameter, Nachrichtenformate und Authentifizierungsmechanismen Ihrer API detailliert beschreibt. Hier ist ein einfaches Beispiel einer OpenAPI-Spezifikation in YAML:

openapi: 3.0.0
info:
  title: Beispielservice
  description: Dies ist ein einfacher Beispiel-Service
  version: 1.0.0
servers:
  - url: 'http://example.com/api'
paths:
  /greeting:
    get:
      summary: Gibt eine Begrüßungsnachricht zurück
      responses:
        '200':
          description: Eine Begrüßungsnachricht
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string

27.2 Verwendung des OpenAPI-Generators

Nachdem die OpenAPI-Spezifikation definiert wurde, können Sie den OpenAPI-Generator verwenden, um daraus Code zu generieren. Der OpenAPI-Generator unterstützt zahlreiche Programmiersprachen und Frameworks, sodass Sie Server-Stubs, Client-Bibliotheken und Dokumentationen entsprechend Ihrem Technologiestack generieren können.

27.2.1 Installation des OpenAPI-Generators

Der OpenAPI-Generator kann über verschiedene Wege installiert werden, beispielsweise als Java-Befehlszeilen-Tool, als Docker-Container oder als Plugin für Build-Tools wie Maven oder Gradle. Eine Möglichkeit ist die Installation des CLI-Tools über Homebrew:

brew install openapi-generator

27.2.2 Generierung von Server-Stubs

Um beispielsweise Server-Stubs für ein Spring Boot-Projekt zu generieren, verwenden Sie folgenden Befehl:

openapi-generator-cli generate -i path/to/your/openapi.yaml -g spring -o path/to/output/directory

Dieser Befehl generiert ein Spring Boot-Projekt im angegebenen Ausgabeverzeichnis, das die Modelle, API-Interfaces und die Konfiguration basierend auf Ihrer OpenAPI-Spezifikation enthält.

27.2.3 Integration in den Build-Prozess

Sie können den OpenAPI-Generator auch in Ihren Build-Prozess integrieren, um die Generierung automatisch als Teil Ihres Build- oder CI/CD-Prozesses durchzuführen. Hier ist ein Beispiel, wie Sie das Maven-Plugin in Ihre pom.xml einbinden können:

<build>
  <plugins>
    <plugin>
      <groupId>org.openapitools</groupId>
      <artifactId>openapi-generator-maven-plugin</artifactId>
      <version>{openapi-generator-version}</version>
      <executions>
        <execution>
          <goals>
            <goal>generate</goal>
          </goals>
          <configuration>
            <inputSpec>${project.basedir}/src/main/resources/openapi.yaml</inputSpec>
            <generatorName>spring</generatorName>
            <output>${project.build.directory}/generated-sources</output>
            <apiPackage>com.example.api</apiPackage>
            <modelPackage>com.example.model</modelPackage>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Oder in Gradle:

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.4'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'org.openapi.generator' version '7.4.0'
    id 'org.springdoc.openapi-gradle-plugin' version "1.8.0"
}

...

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

    // lombok
    implementation 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    annotationProcessor 'org.projectlombok:lombok'

    // Swagger / OpenAPI
    implementation(group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.2.0')
    implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
    implementation group: 'org.openapitools', name: 'jackson-databind-nullable', version: '0.2.6'

    // Java Validation API
    implementation group: 'jakarta.validation', name: 'jakarta.validation-api', version: '3.0.0'

    // IO
    implementation group: 'commons-io', name: 'commons-io', version: '2.11.0'

    // Validate
    implementation group: 'org.hibernate', name: 'hibernate-validator', version: '8.0.1.Final'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

...

openApiGenerate {
    generatorName = 'spring'
    inputSpec = "$projectDir/src/main/resources/static/contract.yaml"
    // Compatible with a number of Gradle lazy APIs that accept also java.io.File
    Provider<RegularFile> output = layout.buildDirectory.file("generated")
    outputDir = output.map { it.asFile.path }
    apiPackage = 'de.trainercalendar.api'
    invokerPackage = 'de.trainercalendar.invoker'
    modelPackage = 'de.trainercalendar.models'
    configOptions = [
        additionalModelTypeAnnotations: '@lombok.Builder',
        dateLibrary         : 'java8',
        interfaceOnly       : 'true',
        skipDefaultInterface: 'true',
        useSpringBoot3      : 'true'
    ]
}

compileJava.dependsOn tasks.openApiGenerate
compileJava.options.compilerArgs = ['-Xlint:unchecked', '-Xlint:deprecation', '-parameters']
sourceSets.main.java.srcDir layout.buildDirectory.dir("generated/src/main/java")

27.3 Vorteile des Contract-First-Ansatzes

Der Contract-First-Ansatz bietet mehrere Vorteile:

Parallelentwicklung: Frontend- und Backend-Teams können parallel arbeiten, da die API-Schnittstellen von Anfang an festgelegt sind. - Automatisierte Codegenerierung: Reduziert manuellen Aufwand und minimiert menschliche Fehler bei der Implementierung der API. - Konsistenz**: Die generierten Server-Stubs und Client-Bibliotheken sind konsistent mit der API-Spezifikation, was die Integration vereinfacht.