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.
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: stringNachdem 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.
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
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.
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")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.