20 Schwerpunkt auf Contract-First-Entwicklung

20.1 Einführung

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.

20.2 Grundprinzipien der Contract-First-Entwicklung

  1. API-Spezifikation zuerst:
  2. Klare Trennung von Design und Implementierung:
  3. Automatisierte Generierung von Code:
  4. Versionierung und Wartbarkeit:

20.3 Vorteile der Contract-First-Entwicklung

  1. Klare Verträge:
  2. Bessere Zusammenarbeit:
  3. Automatisierte Dokumentation:
  4. Erhöhte Konsistenz und Qualität:
  5. Einfache Erweiterbarkeit:

20.4 Herausforderungen der Contract-First-Entwicklung

  1. Höherer Initialaufwand:
  2. Komplexität bei Änderungen:
  3. Abhängigkeit von Tools:
  4. Erfordert Disziplin:

20.5 Implementierung der Contract-First-Entwicklung 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.

20.5.1 Schritt 1: Erstellung der OpenAPI-Spezifikation

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
        - author

20.5.2 Schritt 2: Konfiguration des OpenAPI Generators

Um 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>

20.5.3 Schritt 3: Code-Generierung ausführen

Führen Sie den folgenden Maven-Befehl aus, um den Code basierend auf der OpenAPI-Spezifikation zu generieren:

mvn clean install

Dieser Befehl erstellt die Server-Stubs und Modelle im Verzeichnis ${project.build.directory}/generated-sources/openapi.

20.5.4 Schritt 4: Implementierung der Geschäftslogik

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.)
}

20.5.5 Schritt 5: Erstellung des Datenmodells und Repository

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;
    }
}

20.5.6 Schritt 6: Testen der API

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"}'

20.6 Best Practices für Contract-First-Entwicklung

  1. Detaillierte und klare Spezifikationen erstellen:
  2. Verwenden Sie wiederverwendbare Komponenten:
  3. Automatisierte Tests integrieren:
  4. Versionsmanagement planen:
  5. Dokumentation pflegen:
  6. Sicherheitsmechanismen definieren:
  7. Feedback-Schleifen etablieren:

20.7 Fallstudie: Contract-First-Entwicklung in einem Microservices-Projekt

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:

  1. API-Spezifikation erstellen:
  2. Code-Generierung und Stub-Implementierung:
  3. Parallele Entwicklung von Frontend- und Backend-Teams:
  4. Integration und Testing:
  5. Einfache Erweiterbarkeit und Wartung:

20.8 Best Practices für Contract-First-Entwicklung in Spring Boot

  1. Verwendung von Springdoc OpenAPI:
  2. Trennung von Spezifikation und Implementierung:
  3. Nutzung von Dependency Injection:
  4. Automatisierung der Code-Generierung:
  5. Implementierung von Sicherheitsmechanismen:
  6. Dokumentation und Schulung:
  7. Kontinuierliche Validierung und Updates:

20.9 tl;dr

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.