21 OpenAPI und API-Vertragsmanagement in Angular

Nach der Betrachtung der grundlegenden REST-API-Kommunikation in Angular ist es wichtig, einen Schritt zurückzutreten und über die Strukturierung und Definition von APIs selbst nachzudenken. Hier kommt OpenAPI ins Spiel – ein standardisiertes Format zur Beschreibung von HTTP-basierten APIs, das die Zusammenarbeit zwischen Frontend- und Backend-Teams erheblich verbessern kann.

21.1 Was ist OpenAPI?

OpenAPI (früher bekannt als Swagger) ist eine Spezifikation für maschinenlesbare API-Beschreibungen. Mit OpenAPI können Entwickler APIs standardisiert dokumentieren, wodurch es einfacher wird:

Ein OpenAPI-Dokument liegt typischerweise als JSON- oder YAML-Datei vor und beschreibt alle Aspekte einer API, einschließlich:

21.2 Contract-First vs. Code-First

Bei der API-Entwicklung gibt es zwei grundlegende Ansätze: Contract-First und Code-First. Beide haben ihre Vor- und Nachteile, aber besonders im Kontext von Angular-Anwendungen ist der Contract-First-Ansatz oft vorteilhaft.

21.2.1 Contract-First Ansatz

Beim Contract-First-Ansatz wird die API-Spezifikation (der “Vertrag”) zuerst erstellt, bevor irgendein Code geschrieben wird. Der Workflow sieht typischerweise so aus:

  1. Erstellen der OpenAPI-Spezifikation
  2. Validieren des Vertrags mit allen beteiligten Teams
  3. Generieren von Server-Stubs und Client-SDKs aus der Spezifikation
  4. Implementieren der Geschäftslogik auf Basis der generierten Stubs

Vorteile:

Nachteile:

21.2.2 Code-First Ansatz

Beim Code-First-Ansatz wird die API-Implementierung zuerst entwickelt, und die API-Spezifikation wird aus dem Code abgeleitet:

  1. Implementieren der API im Code
  2. Generieren der OpenAPI-Spezifikation aus dem Code
  3. Erstellen von Client-SDKs basierend auf der generierten Spezifikation

Vorteile:

Nachteile:

21.3 Der Vertrag als Single Source of Truth

In modernen API-getriebenen Anwendungen ist es entscheidend, eine “Single Source of Truth” für die API-Definition zu haben. Dieser Ansatz bietet mehrere Vorteile:

21.3.1 Verbesserte Kommunikation

Der OpenAPI-Vertrag fungiert als gemeinsame Sprache zwischen Frontend- und Backend-Entwicklern, Produktmanagern und anderen Stakeholdern. Er reduziert Missverständnisse und stellt sicher, dass alle ein gemeinsames Verständnis der API haben.

21.3.2 Automatisierte Codegenerierung

Mit OpenAPI können sowohl serverseitige Implementierungen als auch clientseitige SDKs automatisch generiert werden. Dies reduziert manuelle Arbeit und damit verbundene Fehler.

21.3.3 Präzise Dokumentation

Eine gut geschriebene OpenAPI-Spezifikation dient als aussagekräftige Dokumentation, die immer aktuell bleibt, wenn der Vertrag als Single Source of Truth behandelt wird.

21.3.4 Typsicherheit

Generierte Client-SDKs bieten vollständige Typsicherheit in TypeScript, was die Anzahl der Laufzeitfehler durch falsche API-Nutzung reduziert.

21.4 Entkopplung durch Protokollebene

Eine der größten Stärken des OpenAPI-Ansatzes ist die Entkopplung durch Abstraktion auf die Protokollebene. Dies bedeutet:

21.4.1 Technologieunabhängigkeit

Der API-Vertrag ist unabhängig von der verwendeten Programmiersprache oder dem Framework. Das Backend kann in Java, C#, Python oder jeder anderen Sprache implementiert sein, während das Frontend Angular, React oder ein anderes Framework verwenden kann. Der Vertrag fungiert als Brücke zwischen diesen verschiedenen Technologien.

21.4.2 Evolutionäre Entwicklung

Die Entkopplung ermöglicht es beiden Seiten, sich unabhängig voneinander weiterzuentwickeln, solange der Vertrag eingehalten wird. Das Backend kann intern umgeschrieben werden, ohne das Frontend zu beeinträchtigen, und umgekehrt.

21.4.3 Versionierung

APIs können klar versioniert werden, was eine kontrollierte Evolution der Schnittstelle ermöglicht. Alte Clients können weiterhin mit älteren API-Versionen arbeiten, während neue Funktionen in neueren Versionen eingeführt werden.

21.4.4 Testbarkeit

Die klare Definition der Schnittstelle erleichtert das Testen. Mock-Server können automatisch aus der Spezifikation generiert werden, und Tests können gegen die im Vertrag definierten Erwartungen validiert werden.

21.5 OpenAPI-Integration in Angular-Projekten

Die Integration von OpenAPI in Angular-Projekte erfolgt typischerweise durch den openapi-generator, der TypeScript-Client-Code aus einer OpenAPI-Spezifikation erzeugt.

21.5.1 Einrichtung mit npm-Skripten

Um OpenAPI-Generierung in den Build-Prozess zu integrieren, können npm-Skripte in der package.json definiert werden:

{
  "name": "my-angular-app",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "prestart": "npm run generate-api",
    "prebuild": "npm run generate-api",
    "start": "ng serve",
    "build": "ng build",
    "generate-api": "openapi-generator-cli generate -i ./src/assets/api-specs/api.yaml -g typescript-angular -o ./src/app/api --additional-properties=ngVersion=19.0.0,npmName=api-client,supportsES6=true,modelPropertyNaming=original"
  },
  "dependencies": {
    "@angular/animations": "^19.0.0",
    "@angular/common": "^19.0.0",
    "@angular/compiler": "^19.0.0",
    "@angular/core": "^19.0.0",
    // ...andere Abhängigkeiten
  },
  "devDependencies": {
    "@openapitools/openapi-generator-cli": "^2.7.0",
    // ...andere Dev-Abhängigkeiten
  }
}

Die definierten Skripte sorgen dafür, dass:

21.5.2 Installation des openapi-generator

Um den openapi-generator zu installieren:

npm install --save-dev @openapitools/openapi-generator-cli

21.6 Umfangreiches Beispiel: Implementierung einer Contract-First API

Im Folgenden wird ein umfassendes Beispiel für die Integration von OpenAPI in ein Angular-Projekt vorgestellt.

21.6.1 Schritt 1: Erstellen der OpenAPI-Spezifikation

Zuerst erstellen wir eine OpenAPI-Spezifikation für eine einfache Benutzerverwaltungs-API:

# src/assets/api-specs/api.yaml
openapi: 3.0.3
info:
  title: User Management API
  description: API für die Verwaltung von Benutzern
  version: 1.0.0
servers:
  - url: https://api.example.com/v1
    description: Produktionsserver
  - url: https://dev-api.example.com/v1
    description: Entwicklungsserver
paths:
  /users:
    get:
      summary: Liste aller Benutzer abrufen
      operationId: getUsers
      parameters:
        - name: page
          in: query
          description: Seitennummer für die Paginierung
          required: false
          schema:
            type: integer
            default: 1
        - name: pageSize
          in: query
          description: Anzahl der Einträge pro Seite
          required: false
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Erfolgreiche Operation
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Nicht autorisiert
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      summary: Neuen Benutzer erstellen
      operationId: createUser
      requestBody:
        description: Benutzerdaten zum Erstellen
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreate'
      responses:
        '201':
          description: Benutzer erfolgreich erstellt
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          description: Ungültige Anfrage
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Nicht autorisiert
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /users/{id}:
    get:
      summary: Benutzer nach ID abrufen
      operationId: getUserById
      parameters:
        - name: id
          in: path
          description: ID des Benutzers
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Erfolgreiche Operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          description: Benutzer nicht gefunden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    put:
      summary: Benutzer aktualisieren
      operationId: updateUser
      parameters:
        - name: id
          in: path
          description: ID des zu aktualisierenden Benutzers
          required: true
          schema:
            type: integer
      requestBody:
        description: Aktualisierte Benutzerdaten
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserUpdate'
      responses:
        '200':
          description: Benutzer erfolgreich aktualisiert
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          description: Ungültige Anfrage
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Benutzer nicht gefunden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      summary: Benutzer löschen
      operationId: deleteUser
      parameters:
        - name: id
          in: path
          description: ID des zu löschenden Benutzers
          required: true
          schema:
            type: integer
      responses:
        '204':
          description: Benutzer erfolgreich gelöscht
        '404':
          description: Benutzer nicht gefunden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
          format: int64
          readOnly: true
        username:
          type: string
        email:
          type: string
          format: email
        firstName:
          type: string
        lastName:
          type: string
        role:
          type: string
          enum: [admin, user, guest]
        createdAt:
          type: string
          format: date-time
          readOnly: true
        updatedAt:
          type: string
          format: date-time
          readOnly: true
      required:
        - id
        - username
        - email
    UserCreate:
      type: object
      properties:
        username:
          type: string
          minLength: 3
          maxLength: 50
        email:
          type: string
          format: email
        password:
          type: string
          format: password
          minLength: 8
        firstName:
          type: string
        lastName:
          type: string
        role:
          type: string
          enum: [admin, user, guest]
          default: user
      required:
        - username
        - email
        - password
    UserUpdate:
      type: object
      properties:
        username:
          type: string
          minLength: 3
          maxLength: 50
        email:
          type: string
          format: email
        firstName:
          type: string
        lastName:
          type: string
        role:
          type: string
          enum: [admin, user, guest]
    Pagination:
      type: object
      properties:
        page:
          type: integer
        pageSize:
          type: integer
        totalItems:
          type: integer
        totalPages:
          type: integer
      required:
        - page
        - pageSize
        - totalItems
        - totalPages
    Error:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        details:
          type: array
          items:
            type: string
      required:
        - code
        - message
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
security:
  - bearerAuth: []

Diese umfassende Spezifikation definiert:

21.6.2 Schritt 2: Generierung des API-Clients

Nach der Erstellung der OpenAPI-Spezifikation können wir den API-Client mit dem bereits konfigurierten npm-Skript generieren:

npm run generate-api

Dies erzeugt einen vollständig typisierten Angular-Client im Verzeichnis ./src/app/api.

21.6.3 Schritt 3: Erstellen eines API-Service-Wrappers

Obwohl der generierte Code direkt verwendet werden kann, ist es oft hilfreich, einen Wrapper-Service zu erstellen, der die Komplexität des generierten Codes verbirgt und projektspezifische Logik hinzufügt:

// src/app/services/user.service.ts
import { Injectable } from '@angular/core';
import { Observable, map, catchError } from 'rxjs';
import { 
  UserService as GeneratedUserService, 
  User, 
  UserCreate, 
  UserUpdate 
} from '../api';
import { ErrorHandlingService } from './error-handling.service';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  constructor(
    private generatedUserService: GeneratedUserService,
    private errorService: ErrorHandlingService
  ) {}

  /**
   * Ruft eine Liste aller Benutzer ab
   * @param page Seitennummer (beginnend mit 1)
   * @param pageSize Anzahl der Einträge pro Seite
   * @returns Observable mit Benutzern und Paginierungsinformationen
   */
  getUsers(page: number = 1, pageSize: number = 20): Observable<{
    users: User[];
    pagination: {
      currentPage: number;
      pageSize: number;
      totalItems: number;
      totalPages: number;
    }
  }> {
    return this.generatedUserService.getUsers(page, pageSize).pipe(
      map(response => ({
        users: response.data,
        pagination: {
          currentPage: response.pagination.page,
          pageSize: response.pagination.pageSize,
          totalItems: response.pagination.totalItems,
          totalPages: response.pagination.totalPages
        }
      })),
      catchError(error => this.errorService.handleError(error))
    );
  }

  /**
   * Ruft einen einzelnen Benutzer nach ID ab
   * @param id ID des Benutzers
   * @returns Observable mit Benutzerdaten
   */
  getUserById(id: number): Observable<User> {
    return this.generatedUserService.getUserById(id).pipe(
      catchError(error => this.errorService.handleError(error))
    );
  }

  /**
   * Erstellt einen neuen Benutzer
   * @param user Benutzerdaten für den neuen Benutzer
   * @returns Observable mit dem erstellten Benutzer
   */
  createUser(user: UserCreate): Observable<User> {
    return this.generatedUserService.createUser(user).pipe(
      catchError(error => this.errorService.handleError(error))
    );
  }

  /**
   * Aktualisiert einen Benutzer
   * @param id ID des zu aktualisierenden Benutzers
   * @param userData Aktualisierte Benutzerdaten
   * @returns Observable mit dem aktualisierten Benutzer
   */
  updateUser(id: number, userData: UserUpdate): Observable<User> {
    return this.generatedUserService.updateUser(id, userData).pipe(
      catchError(error => this.errorService.handleError(error))
    );
  }

  /**
   * Löscht einen Benutzer
   * @param id ID des zu löschenden Benutzers
   * @returns Observable, das bei Erfolg completed
   */
  deleteUser(id: number): Observable<void> {
    return this.generatedUserService.deleteUser(id).pipe(
      catchError(error => this.errorService.handleError(error))
    );
  }
}

21.6.4 Schritt 4: Verwendung in Angular-Komponenten

Mit dem Wrapper-Service können wir nun in unseren Komponenten auf einfache Weise mit der API kommunizieren:

// src/app/user-list/user-list.component.ts
import { Component, OnInit, inject, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserService } from '../services/user.service';
import { User } from '../api';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-user-list',
  standalone: true,
  imports: [CommonModule, RouterLink],
  template: `
    <div class="user-container">
      <h1>Benutzerverwaltung</h1>
      
      <div class="user-actions">
        <button (click)="refreshUsers()">Aktualisieren</button>
        <button routerLink="/users/new" class="primary">Neuer Benutzer</button>
      </div>
      
      @if (loading()) {
        <div class="loading">Benutzer werden geladen...</div>
      } @else if (error()) {
        <div class="error">
          <p>Fehler beim Laden der Benutzer: {{ error() }}</p>
          <button (click)="refreshUsers()">Erneut versuchen</button>
        </div>
      } @else if (users().length === 0) {
        <div class="empty-state">
          <p>Keine Benutzer gefunden.</p>
        </div>
      } @else {
        <table class="user-table">
          <thead>
            <tr>
              <th>ID</th>
              <th>Benutzername</th>
              <th>E-Mail</th>
              <th>Name</th>
              <th>Rolle</th>
              <th>Aktionen</th>
            </tr>
          </thead>
          <tbody>
            @for (user of users(); track user.id) {
              <tr>
                <td>{{ user.id }}</td>
                <td>{{ user.username }}</td>
                <td>{{ user.email }}</td>
                <td>{{ user.firstName }} {{ user.lastName }}</td>
                <td>{{ user.role }}</td>
                <td class="actions">
                  <button routerLink="/users/{{ user.id }}">Details</button>
                  <button routerLink="/users/edit/{{ user.id }}">Bearbeiten</button>
                  <button (click)="confirmDelete(user)" class="danger">Löschen</button>
                </td>
              </tr>
            }
          </tbody>
        </table>
        
        <div class="pagination">
          <button 
            [disabled]="pagination().currentPage === 1" 
            (click)="goToPage(pagination().currentPage - 1)"
          >
            Zurück
          </button>
          <span>Seite {{ pagination().currentPage }} von {{ pagination().totalPages }}</span>
          <button 
            [disabled]="pagination().currentPage === pagination().totalPages" 
            (click)="goToPage(pagination().currentPage + 1)"
          >
            Weiter
          </button>
        </div>
      }
    </div>
  `,
  styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements OnInit {
  private userService = inject(UserService);
  
  // Signals für reaktive Zustandsverwaltung
  users = signal<User[]>([]);
  loading = signal<boolean>(false);
  error = signal<string | null>(null);
  pagination = signal<{
    currentPage: number;
    pageSize: number;
    totalItems: number;
    totalPages: number;
  }>({
    currentPage: 1,
    pageSize: 20,
    totalItems: 0,
    totalPages: 0
  });
  
  ngOnInit(): void {
    this.loadUsers();
  }
  
  loadUsers(page: number = 1): void {
    this.loading.set(true);
    this.error.set(null);
    
    this.userService.getUsers(page, this.pagination().pageSize).subscribe({
      next: (data) => {
        this.users.set(data.users);
        this.pagination.set(data.pagination);
        this.loading.set(false);
      },
      error: (err) => {
        console.error('Fehler beim Laden der Benutzer', err);
        this.error.set(err.message || 'Beim Laden der Benutzer ist ein Fehler aufgetreten.');
        this.loading.set(false);
      }
    });
  }
  
  refreshUsers(): void {
    this.loadUsers(this.pagination().currentPage);
  }
  
  goToPage(page: number): void {
    if (page >= 1 && page <= this.pagination().totalPages) {
      this.loadUsers(page);
    }
  }
  
  confirmDelete(user: User): void {
    if (confirm(`Möchten Sie den Benutzer "${user.username}" wirklich löschen?`)) {
      this.deleteUser(user.id);
    }
  }
  
  deleteUser(id: number): void {
    this.userService.deleteUser(id).subscribe({
      next: () => {
        this.users.update(users => users.filter(user => user.id !== id));
        // Wenn die letzte Seite leer wird, zur vorherigen Seite wechseln
        if (this.users().length === 0 && this.pagination().currentPage > 1) {
          this.goToPage(this.pagination().currentPage - 1);
        } else {
          this.refreshUsers();
        }
      },
      error: (err) => {
        console.error('Fehler beim Löschen des Benutzers', err);
        alert(`Fehler beim Löschen des Benutzers: ${err.message || 'Unbekannter Fehler'}`);
      }
    });
  }
}

21.6.5 Schritt 5: Einrichten eines Mock-Servers für die Entwicklung

Während der Entwicklung ist es oft nützlich, einen Mock-Server basierend auf der OpenAPI-Spezifikation zu erstellen. Prism ist ein hervorragendes Tool für diesen Zweck. Fügen wir ein weiteres npm-Skript zur package.json hinzu:

{
  "scripts": {
    // ...andere Skripte
    "mock-api": "prism mock -p 4010 ./src/assets/api-specs/api.yaml"
  },
  "devDependencies": {
    // ...andere Abhängigkeiten
    "@stoplight/prism-cli": "^4.10.5"
  }
}

Installieren Sie Prism:

npm install --save-dev @stoplight/prism-cli

Starten Sie den Mock-Server:

npm run mock-api

Der Mock-Server ist nun unter http://localhost:4010 verfügbar und liefert Antworten gemäß der OpenAPI-Spezifikation.

21.6.6 Schritt 6: Konfiguration der API-Basis-URL basierend auf der Umgebung

Um zwischen dem Mock-Server in der Entwicklung und dem tatsächlichen Backend in der Produktion zu wechseln, können wir Angular-Umgebungskonfigurationen verwenden:

// src/environments/environment.ts
export const environment = {
  production: false,
  apiBaseUrl: 'http://localhost:4010' // Mock-Server während der Entwicklung
};

// src/environments/environment.prod.ts
export const environment = {
  production: true,
  apiBaseUrl: 'https://api.example.com/v1' // Produktions-API
};

Wir müssen nun die Konfiguration der API-Basis-URL in unserer Anwendung anpassen:

// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { BASE_PATH } from './api';
import { environment } from '../environments/environment';
import { authInterceptor } from './interceptors/auth.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(
      withInterceptors([authInterceptor])
    ),
    { provide: BASE_PATH, useValue: environment.apiBaseUrl }
  ]
};

21.7 Vorteile der OpenAPI-Integration in Angular

Die Integration von OpenAPI in Angular-Projekte bietet zahlreiche Vorteile:

21.7.1 Typsicherheit und Intellisense

Der generierte TypeScript-Code bietet vollständige Typsicherheit und Intellisense-Unterstützung in IDEs:

21.7.2 Automatische Aktualisierung bei API-Änderungen

Wenn sich die API ändert, wird durch die automatische Generierung sichergestellt, dass der Client-Code synchron bleibt:

21.7.3 Konsistente Fehlerbehandlung

Die generierte API-Client-Klasse ermöglicht eine einheitliche Fehlerbehandlung für alle API-Aufrufe.

21.7.4 Einfache Mocks für Tests und Entwicklung

Mit der OpenAPI-Spezifikation können Mock-Server generiert werden, die realistische Antworten liefern:

21.8 Best Practices für OpenAPI in Angular-Projekten

21.8.1 Versionierung der API-Spezifikation

Behandeln Sie die OpenAPI-Spezifikation als Teil Ihres Codes und versionieren Sie sie zusammen mit Ihrer Anwendung:

21.8.2 Integration in CI/CD-Pipelines

Automatisieren Sie die Generierung der API-Clients in Ihren CI/CD-Pipelines:

# .github/workflows/build.yml oder ähnliches
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20'
      - name: Install dependencies
        run: npm ci
      - name: Generate API client
        run: npm run generate-api
      - name: Build
        run: npm run build

21.8.3 Strikte Validierung der OpenAPI-Spezifikation

Validieren Sie Ihre OpenAPI-Spezifikation, um Inkonsistenzen zu vermeiden:

{
  "scripts": {
    "validate-api": "openapi-generator-cli validate -i ./src/assets/api-specs/api.yaml"
  }
}

21.8.4 Zentrale API-Service-Schicht

Kapseln Sie den generierten API-Client in einer eigenen Service-Schicht:

21.8.5 Staging-spezifische API-Konfigurationen

Konfigurieren Sie die API-Basis-URL und andere Parameter für verschiedene Umgebungen:

// src/app/api.module.ts
import { NgModule, ModuleWithProviders } from '@angular/core';
import { Configuration } from './api/configuration';
import { ApiModule as GeneratedApiModule } from './api/api.module';

@NgModule({
  imports: [GeneratedApiModule]
})
export class ApiModule {
  static forRoot(baseUrl: string): ModuleWithProviders<ApiModule> {
    return {
      ngModule: ApiModule,
      providers: [
        {
          provide: Configuration,
          useValue: new Configuration({
            basePath: baseUrl,
            withCredentials: true
          })
        }
      ]
    };
  }
}

21.8.6 Mock-Server für Entwicklung und Tests

Verwenden Sie einen Mock-Server während der Entwicklung:

{
  "scripts": {
    "start:mock": "concurrently \"npm run mock-api\" \"npm run start\"",
    "mock-api": "prism mock -p 4010 ./src/assets/api-specs/api.yaml"
  }
}

21.8.7 Regelmäßige Aktualisierung der API-Dependencies

Halten Sie den openapi-generator und andere API-bezogene Abhängigkeiten aktuell:

npm update @openapitools/openapi-generator-cli

21.8.8 API-Versionen-Management

Planen Sie für API-Evolutionen mit klarer Versionierung:

21.8.9 Fehlerbehandlung und -modellierung

Definieren Sie konsistente Fehlerstrukturen in Ihrer API:

# Teil der OpenAPI-Spezifikation
components:
  schemas:
    Error:
      type: object
      properties:
        code:
          type: string
          description: Eindeutiger Fehlercode
        message:
          type: string
          description: Benutzerfreundliche Fehlermeldung
        details:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              message:
                type: string

21.8.10 Dokumentation erstellen und pflegen

Nutzen Sie Tools wie Swagger UI oder ReDoc, um eine interaktive Dokumentation zu generieren:

{
  "scripts": {
    "api-docs": "redoc-cli serve ./src/assets/api-specs/api.yaml -p 8080"
  },
  "devDependencies": {
    "redoc-cli": "^0.13.20"
  }
}

21.8.11 API-Änderungen testen

Testen Sie API-Änderungen gründlich, bevor Sie sie in die Produktion überführen:

// Beispiel für einen API-Integrationstest
describe('UserAPI', () => {
  it('should get user by id', (done) => {
    userService.getUserById(1).subscribe(user => {
      expect(user).toBeDefined();
      expect(user.id).toBe(1);
      done();
    });
  });
});

21.8.12 API-Breaking-Changes managen

Einführung von Breaking Changes sollte sorgfältig geplant werden: