Die Contract-First-Entwicklung ist ein Ansatz in der API-Entwicklung, bei dem die API-Spezifikation vor der Implementierung der Geschäftslogik erstellt wird. Dieser Ansatz legt den Fokus auf die Definition klarer und präziser Verträge zwischen den verschiedenen Komponenten eines Systems, wie zum Beispiel zwischen Frontend- und Backend-Teams oder zwischen verschiedenen Microservices. Durch die frühzeitige Festlegung des API-Vertrags wird sichergestellt, dass alle Beteiligten ein gemeinsames Verständnis der Schnittstellen und deren Funktionalitäten haben. In diesem Kapitel vertiefen wir uns in die Contract-First-Entwicklung, beleuchten deren Vorteile und Herausforderungen und zeigen eine praktische Implementierung mit Spring Boot.
Die Contract-First-Entwicklung kann effektiv in Spring Boot-Projekten umgesetzt werden, indem OpenAPI-Spezifikationen erstellt und Tools wie der OpenAPI Generator zur Code-Generierung verwendet werden. Hier zeigen wir eine Schritt-für-Schritt-Anleitung zur Umsetzung.
Zunächst erstellen wir eine OpenAPI-Spezifikation
(api.yaml), die die Endpunkte, Datenmodelle und
Sicherheitsmechanismen unserer API definiert.
openapi: 3.0.1
info:
title: Bücher API
description: Eine API zur Verwaltung von Büchern.
version: 1.0.0
servers:
- url: http://localhost:8080/api
paths:
/books:
get:
summary: Liste aller Bücher abrufen
responses:
'200':
description: Erfolgreiche Antwort
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Book'
post:
summary: Ein neues Buch erstellen
requestBody:
description: Buchdaten
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
responses:
'201':
description: Buch erstellt
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
/books/{id}:
get:
summary: Ein Buch anhand der ID abrufen
parameters:
- in: path
name: id
schema:
type: integer
format: int64
required: true
description: Die ID des Buches
responses:
'200':
description: Erfolgreiche Antwort
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
'404':
description: Buch nicht gefunden
components:
schemas:
Book:
type: object
properties:
id:
type: integer
format: int64
title:
type: string
author:
type: string
required:
- title
- authorUm die OpenAPI-Spezifikation in Server-Stubs und Modelle zu
übersetzen, verwenden wir den OpenAPI Generator Maven Plugin. Fügen Sie
das Plugin zu Ihrer pom.xml hinzu.
<build>
<plugins>
<!-- OpenAPI Generator Plugin -->
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>6.3.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>
<generatorName>spring</generatorName>
<output>${project.build.directory}/generated-sources/openapi</output>
<configOptions>
<interfaceOnly>true</interfaceOnly>
<delegatePattern>true</delegatePattern>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>Führen Sie den folgenden Maven-Befehl aus, um den Code basierend auf der OpenAPI-Spezifikation zu generieren:
mvn clean installDieser Befehl erstellt die Server-Stubs und Modelle im Verzeichnis
${project.build.directory}/generated-sources/openapi.
Nach der Code-Generierung implementieren wir die Geschäftslogik, indem wir die generierten Controller-Methoden ausfüllen.
package com.example.generated.api;
import com.example.generated.model.Book;
import com.example.generated.repository.BookRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import java.util.List;
import java.util.Optional;
@Controller
public class BookControllerImpl implements BookController {
private final BookRepository bookRepository;
public BookControllerImpl(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
@Override
public ResponseEntity<List<Book>> getBooks() {
List<Book> books = bookRepository.findAll();
return ResponseEntity.ok(books);
}
@Override
public ResponseEntity<Book> getBookById(Long id) {
Optional<Book> book = bookRepository.findById(id);
return book.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@Override
public ResponseEntity<Book> createBook(Book book) {
Book savedBook = bookRepository.save(book);
return ResponseEntity.status(201).body(savedBook);
}
// Weitere Methoden (updateBook, deleteBook etc.)
}Erstellen Sie das Datenmodell und das Repository für die Buchverwaltung.
package com.example.generated.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private Long id;
private String title;
private String author;
}package com.example.generated.repository;
import com.example.generated.model.Book;
import org.springframework.stereotype.Repository;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Repository
public class BookRepository {
private
final Map<Long, Book> bookStore = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong();
public Book save(Book book) {
if (book.getId() == null) {
book.setId(idGenerator.incrementAndGet());
}
bookStore.put(book.getId(), book);
return book;
}
public Optional<Book> findById(Long id) {
return Optional.ofNullable(bookStore.get(id));
}
public List<Book> findAll() {
return new ArrayList<>(bookStore.values());
}
public boolean deleteById(Long id) {
return bookStore.remove(id) != null;
}
}Nach der Implementierung können Sie die API testen, indem Sie
Anfragen an die definierten Endpunkte senden. Nutzen Sie Tools wie
Postman, cURL oder die automatisch generierte Swagger UI unter
http://localhost:8080/swagger-ui.html, um die API zu
erkunden und zu testen.
curl -X POST "http://localhost:8080/api/books" \
-H "Content-Type: application/json" \
-d '{"title": "Spring Boot in Action", "author": "Craig Walls"}'components, um Redundanzen zu vermeiden und die Wartbarkeit
zu erhöhen.Angenommen, Sie entwickeln eine Microservices-Architektur für eine E-Commerce-Plattform mit mehreren Diensten wie Benutzerverwaltung, Produktkatalog, Bestellabwicklung und Zahlungsabwicklung. Hier ist, wie die Contract-First-Entwicklung angewendet werden könnte:
user-service.yaml,
product-service.yaml, etc.src/main/resources/api/), um die Übersicht zu
behalten.Die Contract-First-Entwicklung legt den API-Vertrag vor der Implementierung der Geschäftslogik fest, was klare Verträge, bessere Zusammenarbeit und erhöhte Konsistenz gewährleistet. Durch die Nutzung von OpenAPI-Spezifikationen und Tools wie dem OpenAPI Generator können Entwickler automatisiert Server-Stubs und Modelle generieren, was die Entwicklung beschleunigt und Fehler minimiert.