Zum Inhalt springen

3. Drittanbieter integrieren

Zu Zen-Modus wechseln

Niemand baut heute eine Zahlungsabwicklung selbst. Niemand schreibt eine eigene Kartendarstellung, einen eigenen Mailversand oder eine eigene Bildkonvertierung, wenn es das fertig gibt. Zukaufen ist der Normalfall, und das ist gut so.

Trotzdem ist jede dieser Einbindungen eine Entscheidung mit Folgen über Jahre, und sie wird meistens nebenbei getroffen: Man sucht, findet etwas, installiert es, es funktioniert. Die Rechnung kommt später, in Form eines Dienstes, der seine Preise ändert, einer Bibliothek, die niemand mehr pflegt, oder eines Ausfalls, der die eigene Anwendung mitnimmt.

Diese zwei Wochen behandeln beides: die Entscheidung und die Technik. Am Ende ist mindestens ein fremder Dienst in Ihrem Projekt eingebunden, und Ihre Anwendung bleibt benutzbar, wenn er ausfällt. Das Zweite ist der schwierigere Teil.

KriteriumFrage
KernkompetenzIst das der Teil, der Ihr Projekt ausmacht?
Aufwand jetztWie lange bräuchten Sie für eine brauchbare eigene Fassung?
Wartung über JahreWer pflegt es, wenn sich Formate, Vorschriften oder Schnittstellen ändern?
KostenWas kostet es bei Ihrem tatsächlichen Volumen, nicht bei null?
RisikoWas passiert, wenn es ausfällt, teurer wird oder eingestellt wird?
DatenschutzWelche Daten verlassen Ihr System, und wohin?

Die erste Zeile entscheidet die meisten Fälle. Was Ihr Projekt ausmacht, bauen Sie selbst, sonst haben Sie kein Projekt. Alles andere kaufen Sie zu, wenn es etwas Passendes gibt. Ein Vereinsverwaltungssystem, dessen Alleinstellungsmerkmal eine selbstgeschriebene Kartendarstellung ist, hat sich verlaufen.

Die vierte Zeile wird regelmäßig falsch gerechnet. Kostenlose Stufen sind auf Entwicklung zugeschnitten, nicht auf Betrieb. Rechnen Sie mit Ihrem Zielwert aus Kapitel 1: Bei 500 Konten und 30 gleichzeitigen Benutzern, wie viele Aufrufe sind das im Monat, und was kostet die Stufe darüber?

Fremdcode ist nie umsonst, auch wenn er nichts kostet.

Einarbeitung. Eine fremde Bibliothek zu verstehen dauert oft länger, als die eigenen 200 Zeilen zu schreiben, die man tatsächlich gebraucht hätte.

Anpassungsgrenzen. Fremdcode passt zu 90 Prozent. Die letzten 10 Prozent sind der Teil, den Ihr Kunde sehen will, und dort beginnt der Kampf gegen die Bibliothek.

Aktualisierungszwang. Sie erben den Rhythmus des Anbieters. Ein Hauptversionssprung ist Ihre Arbeit, auch wenn Sie inhaltlich nichts wollten.

Abhängigkeit. Was der Anbieter ändert, ändert er ohne Sie zu fragen. Preise, Kontingente, Schnittstellen, Geschäftsmodell.

Jede dieser Entscheidungen bekommt einen Eintrag in docs/entscheidungen.md: Alternativen, Kriterien, Entscheidung, Konsequenzen. Dieselbe Frage kommt bei jeder größeren Komponente wieder, und beim dritten Mal möchten Sie die Begründung vom ersten Mal nachlesen können.

Bevor eine Bibliothek oder ein Dienst ins Projekt kommt, zehn Minuten Prüfung:

  • Wartungsaktivität. Wann kam die letzte Veröffentlichung? Wie schnell werden Sicherheitsmeldungen beantwortet?
  • Verbreitung. Wie viele Projekte hängen daran? Verbreitung ersetzt keine Qualität, aber sie sorgt dafür, dass Fehler gefunden werden.
  • Offene Fehler. Nicht die Anzahl zählt, sondern das Alter und die Reaktion darauf.
  • Dokumentation. Gibt es eine, und beschreibt sie den Fall, den Sie haben?
  • Größe und Anhang. Wie viel wiegt das Paket im Bündel, und wie viele weitere Pakete zieht es mit?
  • Bus-Faktor. Wie viele Menschen pflegen das? Bei genau einem ist das ein Risiko, das in die Risikoliste gehört.
Terminal-Fenster
npm view <package> time.modified maintainers # last change, who is behind it
npm ls <package> # who pulls it into your project
npx license-checker --summary # which licenses are in the project

Die Frage, die am seltensten gestellt und am spätesten beantwortet wird.

LizenzartBeispieleBedeutet praktisch
PermissivMIT, BSD, Apache-2.0Verwendung fast ohne Auflagen, Lizenztext beilegen; Apache-2.0 regelt zusätzlich Patente
Datei-CopyleftMPL-2.0Änderungen an den Dateien selbst müssen offen bleiben, Ihr übriger Code nicht
CopyleftGPL-2.0, GPL-3.0Wer das Programm weitergibt, muss den eigenen Quellcode unter dieselbe Lizenz stellen
Netzwerk-CopyleftAGPL-3.0Schon der Betrieb als Webdienst gilt als Weitergabe
Quelloffen, aber nicht freiSSPL, BSLEinsehbar, kommerzielle Nutzung eingeschränkt; kein Open Source im engeren Sinn
Proprietärkommerzielle ErweiterungenNur das, was der Vertrag erlaubt

Für ein Schulprojekt, das die Schule nicht weitergibt, sind die meisten dieser Zeilen unkritisch. Für ein Produkt, das Sie danach verkaufen oder als Dienst betreiben, sind sie es nicht, und die vierte Zeile ist die, die überrascht: Bei AGPL genügt es, die Anwendung im Netz anzubieten, damit die Offenlegungspflicht greift.

Prüfen Sie das maschinell, statt es zu hoffen:

Terminal-Fenster
npx license-checker --production --failOn "AGPL-3.0;SSPL-1.0"

Lesen Sie das Preismodell, bevor Sie den Schlüssel erzeugen, und achten Sie auf drei Punkte: Was passiert beim Überschreiten der kostenlosen Stufe? Wird abgeschaltet, gedrosselt oder abgerechnet? Und wie erfahren Sie davon?

Ein abgeschaltetes Kontingent ist ein Ausfall, eine automatische Abrechnung ist eine Rechnung. Beides muss man wissen wollen, und beides gehört überwacht (Kapitel 10).

Ein Dienst, der personenbezogene Daten verarbeitet, braucht einen Auftragsverarbeitungsvertrag, einen bekannten Serverstandort und eine tragfähige Grundlage, falls die Daten die EU verlassen. Ohne das ist die technische Eignung gleichgültig. Kapitel 4 liefert die Prüfliste; treffen Sie die Auswahl jetzt schon mit diesem Wissen im Hinterkopf, sonst bauen Sie in zwei Wochen wieder aus.

WegWann sinnvoll
Offizielles SDKAnbieter pflegt es aktiv, Sie brauchen viele Funktionen
Aus OpenAPI erzeugter ClientEs gibt eine gepflegte Beschreibung, Sie wollen eigene Typen
Eigener kleiner ClientSie brauchen zwei Endpunkte; ein SDK mit 40 Abhängigkeiten wäre unverhältnismäßig

Ein SDK nimmt Arbeit ab und bringt Abhängigkeiten, eigene Fehlerklassen und manchmal eigene Telemetrie mit. Bei zwei Aufrufen ist ein eigener fetch die kleinere Lösung.

// Wrong: ends up in the browser bundle and is therefore public
const key = process.env.NEXT_PUBLIC_MAPS_API_KEY;
// Right: readable on the server only
const key = process.env.MAPS_API_KEY;

Das ist derselbe Bau- gegen Laufzeitfehler wie im Vorjahr, nur mit teureren Folgen: Ein veröffentlichter Schlüssel wird von automatisierten Scannern innerhalb von Stunden gefunden und benutzt, und abgerechnet wird bei Ihnen. Braucht der Browser wirklich Daten des fremden Dienstes, dann über einen eigenen Endpunkt, der die Anfrage weiterreicht und dabei Rechte und Menge prüft.

Für die Authentifizierung gegenüber dem fremden Dienst gibt es drei übliche Wege: einen API-Schlüssel im Kopf der Anfrage, OAuth 2.0 mit Client Credentials (Sie holen sich mit Kennung und Geheimnis ein kurzlebiges Token) und signierte Anfragen, bei denen Sie den Anfragekörper mit einem gemeinsamen Geheimnis unterschreiben. Welchen Weg Sie gehen, bestimmt der Anbieter.

Die wichtigste Entwurfsentscheidung dieses Kapitels: Das fremde Datenmodell kommt nicht in Ihre Anwendung. Sie definieren Ihre Begriffe, und eine Adapterschicht übersetzt.

// src/services/payment/types.ts - our own model
export type Payment = {
id: string;
amountCents: number;
status: "open" | "paid" | "canceled";
};
export interface PaymentProvider {
create(amountCents: number, reference: string): Promise<Payment>;
status(id: string): Promise<Payment>;
}
// src/services/payment/provider-xy.ts - the translation, only here
const statusMap: Record<string, Payment["status"]> = {
requires_payment_method: "open",
processing: "open",
succeeded: "paid",
canceled: "canceled",
};
export const providerXY: PaymentProvider = {
async create(amountCents, reference) {
const raw = await xyClient.paymentIntents.create({
amount: amountCents,
currency: "eur",
metadata: { reference },
});
return { id: raw.id, amountCents: raw.amount, status: statusMap[raw.status] };
},
// ...
};

Der Aufwand ist eine Stunde, der Nutzen dreifach. Ein Anbieterwechsel betrifft genau einen Ordner statt des halben Projekts. Ihre Datenbank enthält Ihre Statuswerte und nicht die Zeichenketten eines fremden Unternehmens. Und in Tests setzen Sie einfach eine zweite Umsetzung derselben Schnittstelle ein, die nichts aufruft und nichts kostet.

Fast jeder Dienst hat einen Sandkastenbetrieb mit eigenen Schlüsseln und Testdaten, etwa Kartennummern, die einen Fehlschlag erzwingen. Genau dafür haben Sie zwei Umgebungen aus Kapitel 1: Testschlüssel in der Testumgebung, Produktivschlüssel in der Produktion, und die Werte liegen nicht in derselben Datei.

In automatisierten Tests rufen Sie den fremden Dienst gar nicht auf. Sie testen gegen Ihre eigene Schnittstelle mit einer Attrappe. Sonst sind Ihre Tests langsam, kostenpflichtig und rot, sobald jemand anderer eine Störung hat.

Was passiertWoran Sie es merkenWas hilft
Antwort dauert ewiggar nichts, Ihre Anfrage hängtZeitüberschreitung
Server antwortet mit 5xxFehlerantwortWiederholung mit Zurückweichen
Kontingent erschöpft429Drosselung, Warteschlange, Alarm
Daten sind falsch oder unvollständigIhre Prüfung schlägt aneigene Validierung am Rand
Schnittstelle wurde geändertplötzlich Fehler ohne eigene ÄnderungVersionsbindung, Vertragstest
Dienst wird eingestelltMail, oft mit kurzer FristAusstiegspfad

Die erste Zeile ist die gefährlichste, weil sie nicht wie ein Fehler aussieht. Ohne Zeitüberschreitung wartet Ihre Anfrage, Ihr Verbindungspool füllt sich, und ein hängender fremder Dienst legt Ihre eigene Anwendung lahm.

const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.MAPS_API_KEY}` },
signal: AbortSignal.timeout(3000), // without this you wait indefinitely
});
export async function withRetry<T>(call: () => Promise<T>, attempts = 3) {
let lastError: unknown;
for (let i = 0; i < attempts; i++) {
try {
return await call();
} catch (error) {
lastError = error;
if (!isRetryable(error)) throw error; // 4xx is not retried
const delay = 200 * 2 ** i + Math.random() * 200; // backoff with jitter
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
throw lastError;
}

Zwei Details entscheiden darüber, ob das hilft oder schadet. Die Streuung verhindert, dass alle Ihre Instanzen gleichzeitig erneut anfragen und den erholenden Dienst wieder umwerfen. Und wiederholt wird nur, was idempotent ist: Ein Lesezugriff jederzeit, eine Zahlung nur mit dem Idempotency-Key aus dem Vorjahr, sonst zahlt der Kunde zweimal.

Wenn ein Dienst dauerhaft ausfällt, ist jede weitere Anfrage verlorene Zeit. Ein Circuit Breaker merkt sich das und antwortet für eine Weile sofort mit einem Fehler, statt drei Sekunden zu warten.

stateDiagram-v2
    [*] --> Geschlossen
    Geschlossen --> Offen: Fehlerquote über Schwelle
    Offen --> Halboffen: nach Wartezeit
    Halboffen --> Geschlossen: Probeanfrage erfolgreich
    Halboffen --> Offen: Probeanfrage schlägt fehl

Der Nutzen ist doppelt: Ihre Anwendung bleibt schnell, obwohl der fremde Dienst steht, und der fremde Dienst bekommt Gelegenheit, sich zu erholen, statt von Ihren Wiederholungen weiter geflutet zu werden.

Die eigentliche Entwurfsfrage lautet nicht „wie verhindere ich den Ausfall”, sondern „was tut meine Anwendung währenddessen”. Das entscheiden Sie je Funktion, vorher, und schreiben es auf.

Fremder DienstFällt ausIhre Anwendung tut dann
KartendarstellungKarte fehltAdresse als Text, Hinweis statt leerer Fläche
MailversandBestätigung geht nicht rausVorgang speichern, Mail einreihen, später senden
Zahlungkeine Abbuchung möglichBuchung als „Zahlung offen” anlegen, Frist setzen
Adresssuchekeine Vorschlägefreie Eingabe zulassen
Anmeldung über fremden Anbieterniemand kommt hineinzweiter Anmeldeweg vorhanden

Die letzte Zeile ist die, die man erst im Ernstfall vermisst. Wer die Anmeldung ausschließlich über einen fremden Anbieter führt, hat dessen Verfügbarkeit zur eigenen gemacht.

Und weil das alles nur hilft, wenn man es bemerkt: Jeder fremde Dienst bekommt in Kapitel 10 seine eigenen Messwerte, nämlich Fehlerrate, Antwortzeit und Restkontingent.

Manche Ereignisse kommen nicht auf Anfrage, sondern von selbst: Die Zahlung ist bestätigt, das Video ist umgewandelt, das Abonnement ist ausgelaufen. Der fremde Dienst ruft dafür einen Endpunkt bei Ihnen auf, und dieser Endpunkt ist öffentlich erreichbar. Vier Punkte machen ihn sicher.

Signatur prüfen, über den rohen Körper. Der Anbieter unterschreibt den Anfragekörper mit einem gemeinsamen Geheimnis. Geprüft wird über genau die Bytes, die angekommen sind, nicht über das wieder zusammengesetzte JSON, weil dabei Leerzeichen und Reihenfolge verlorengehen.

Wiederholungen aushalten. Anbieter senden erneut, wenn sie keine schnelle Bestätigung bekommen. Dasselbe Ereignis kommt also mehrfach an, und Ihre Verarbeitung muss das aushalten, ohne zweimal zu buchen.

Reihenfolge nicht voraussetzen. Ereignisse können überholen. Verlassen Sie sich auf Zeitstempel oder Versionsnummern im Ereignis, nicht auf die Ankunftsreihenfolge.

Sofort antworten. Erst bestätigen, dann arbeiten. Wer im Endpunkt zehn Sekunden lang ein PDF baut, bekommt eine Zeitüberschreitung beim Absender und danach dasselbe Ereignis noch dreimal.

src/app/api/webhooks/payment/route.ts
import { createHmac, timingSafeEqual } from "node:crypto";
export async function POST(request: Request) {
const rawBody = await request.text(); // raw, not parsed
const signature = request.headers.get("x-provider-signature") ?? "";
const expected = createHmac("sha256", process.env.WEBHOOK_SECRET!)
.update(rawBody)
.digest("hex");
const matches =
signature.length === expected.length &&
timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!matches) return new Response(null, { status: 401 });
const event = JSON.parse(rawBody);
if (await alreadyProcessed(event.id)) {
return Response.json({ ok: true }); // a repeat, nothing to do
}
await storeEvent(event); // traceability
await enqueue(event); // do the actual work later
return Response.json({ ok: true });
}

Für die Entwicklung auf dem eigenen Rechner braucht der Anbieter einen Weg zu Ihnen. Entweder über das Kommandozeilenwerkzeug des Anbieters, das Ereignisse weiterleitet, oder über einen Tunnel. Und speichern Sie jedes empfangene Ereignis roh; wenn eine Zahlung strittig ist, ist dieses Protokoll Ihr einziger Beleg.

Am Ende jeder Einbindung steht eine unangenehme Frage: Was tun wir, wenn dieser Anbieter morgen nicht mehr da ist?

Prüfen Sie zwei Dinge. Wie viele Stellen im Code kennen den Anbieter? Wenn es genau ein Ordner ist, haben Sie Ihre Adapterschicht richtig gebaut. Welche Daten liegen nur dort? Alles, was ausschließlich beim Anbieter existiert, ist im Ernstfall verloren, also holen Sie es regelmäßig zu sich und probieren Sie den Export einmal aus.

Der Ausstiegspfad passt in einen Satz und gehört in docs/entscheidungen.md:

Kartendienst: ersetzbar durch OpenStreetMap mit MapLibre.
Aufwand geschätzt zwei Tage, betroffen ist src/dienste/karten/.
Adressdaten liegen bei uns, es geht nur um die Darstellung.

Dasselbe gilt eine Nummer kleiner für Bibliotheken im eigenen Projekt: Aktualisierungen laufen über ein Werkzeug, das Vorschläge einbringt, Sicherheitsmeldungen werden gelesen, und was nicht mehr gebraucht wird, fliegt raus. Die Lieferkette als Ganzes, mit Softwarestückliste und Prüfung im Build, ist Thema von Kapitel 7.

  1. API-Schlüssel im Frontend-Bündel oder im Repository. Beides wird automatisiert gefunden.
  2. Aufruf ohne Zeitüberschreitung. Der fremde Dienst hängt, Ihre Anwendung hängt mit.
  3. Wiederholung ohne Begrenzung und ohne Streuung. Aus einer fremden Störung wird Ihre eigene.
  4. Nicht idempotente Vorgänge wiederholt. Der Kunde zahlt zweimal.
  5. Fremdes Datenmodell bis in die Datenbank durchgereicht. Beim Anbieterwechsel bricht alles.
  6. Lizenz nie gelesen, bis jemand fragt.
  7. Kostenlose Stufe in der Entwicklung, Kostenexplosion oder Abschaltung in Produktion.
  8. Webhook ohne Signaturprüfung. Jeder kann Ihnen dann bestätigen, dass bezahlt wurde.
  9. Signatur über das geparste JSON statt über den rohen Körper. Schlägt zufällig fehl und niemand weiß warum.
  10. Im Webhook arbeiten statt einreihen. Zeitüberschreitung beim Absender, danach kommt alles mehrfach.
  11. Fehlerbehandlung nie ausgelöst. Beim ersten echten Ausfall stellt sich dann heraus, dass sie nicht greift.
  12. Anmeldung nur über einen fremden Anbieter. Dessen Ausfall ist dann Ihr Totalausfall.
  • Beurteilen: die Entscheidung zwischen Eigenentwicklung und Fremdkomponente nach Kriterien begründen und protokollieren.
  • Analysieren: eine Bibliothek oder einen Dienst nach Wartung, Verbreitung, Größe und Bus-Faktor bewerten.
  • Beurteilen: Lizenzarten unterscheiden und die Folgen für Schulprojekt und Produkt benennen.
  • Anwenden: einen Drittanbieter-Service über seine Schnittstelle anbinden, mit Schlüsseln, die den Server nicht verlassen.
  • Erschaffen: eine Adapterschicht bauen, die das fremde Datenmodell vom eigenen trennt.
  • Anwenden: Ausfälle mit Zeitüberschreitung, Wiederholung samt Streuung, Circuit Breaker und Fallback abfedern.
  • Beurteilen: je Funktion festlegen, was bei Ausfall des fremden Dienstes noch möglich sein muss.
  • Anwenden: einen Webhook-Endpunkt mit Signaturprüfung, Idempotenz und schneller Antwort bauen.
  • Erklären: Abhängigkeitsrisiken benennen und einen Ausstiegspfad je Komponente beschreiben.
  • Mindestens ein fremder Dienst ist eingebunden und liefert echten Nutzen.
  • Die Auswahl ist in docs/entscheidungen.md begründet, mit Alternativen, Lizenz, Kostenmodell und Datenschutzlage.
  • Der Zugriff liegt hinter einer eigenen Schicht mit eigenen Typen; der Anbietername kommt außerhalb dieses Ordners nicht vor.
  • Schlüssel sind je Umgebung getrennt und nirgends im Frontend.
  • Zeitüberschreitung, Wiederholung und ein Verhalten bei Ausfall sind umgesetzt und durch einen provozierten Ausfall nachgewiesen, mit Bildschirmfoto dessen, was der Benutzer sieht.
  • Falls der Dienst zurückruft: Webhook mit Signaturprüfung, Idempotenz und Ereignisprotokoll.
  • Ausstiegspfad in einem Satz, mit geschätztem Aufwand.
  • Aufgabe - Komponentenvergleich nach Kriterienkatalog
  • Aufgabe - Fremden Dienst anbinden und kapseln
  • Aufgabe - Ausfall provozieren: Circuit Breaker und Fallback
  • Aufgabe - Webhook empfangen und absichern