57 VIEWS

Consumer-Driven Contracts mit Pact – Teil 2: Implementierung mit Java

01.12.2020

Ein Beitrag von Sarah Mildenberger und Tobias Eberle.
Im diesem zweiten Beitrag der Blogpostreihe zu Consumer-Driven Contracts geht es um die konkrete Implementierung von Contracts mit Pact und Java. Für die Grundlagen von Contracts und Pact empfehlen wir zunächst Teil 1 der Blogpostreihe

Um Pact an einem Beispiel zeigen zu können, stellen wir uns einen kleinen, sehr vereinfachten Webshop vor. Dieser besteht aus den Backend-Services Inventory, Payment  und Shipment  und einem Frontend, dem Webshop. Der Webshop ist hier der Consumer, die Backend-Services sind die Provider.

Webshop-Übersicht

Wir betrachten für dieses Beispiel den Inventory-Service und den Webshop. Der Inventory-Service stellt die Schnittstelle /items/{itemId} zur Verfügung, durch die der Webshop die Informationen für ein bestimmtes Produkt abrufen kann. Er erwartet eine Antwort in der folgenden Struktur:

Mögliche Breaking API-Changes

Nun kann es passieren, dass der Provider seine API ändert, ohne den Consumer darüber zu informieren. Beispiele hierfür sind:

  • Der Provider (Inventory-Service) entfernt ein Feld von der Response, das von Consumern benötigt wird, z.B. das Feld „description“
    Konsequenz: Der Consumer (Webshop) benötigt diese Description, jetzt fehlt sie ihm jedoch
  • Provider ändert die URL eines Endpoints oder entfernt einen Endpoint, den der Consumer verwendet, z.B. /items/{itemId}/product/{productId}
    Konsequenz: Der Consumer weiß nicht mehr, wo er die Daten überhaupt anfragen kann
  • Provider ändert das Datumsformat eines Felds, z.B. „availableSince”: yyyy-MM-dddd.MM.yyyy
    Konsequenz: Der Consumer kann das Datum nicht mehr parsen

Genau diese möglichen Fehlerquellen werden wir jetzt mit Hilfe von Contracts mit Pact beseitigen.

Definition von Contracts auf der Seite des Consumers (Webshop)

Auf Seite des Consumers werden die Contracts definiert. Dies geschieht im Rahmen von Unit Tests. Es werden hierfür zwei Dinge benötigt:

  1. Die Definition eines Pacts
  2. Eine Test-Methode zur Verfikation des Pacts. Wenn dieser Test erfolgreich durchläuft, wird automatisch ein Pact-File erstellt und im Pact-Verzeichnis (z.B. target/pacts) abgelegt.

1. Definition eines Pacts

Wir definieren zunächst den erwarteten Body  mit Hilfe der Pact-DSL. Hier haben wir die Möglichkeit, genaue Erwartungswerte anzugeben (z.B. über “stringValue”: es wird genau dieser String erwartet) oder nur den erwarteten Datentyp (z.B. “stringType”: es wird ein String erwartet). Außerdem können wir ein erwartetes Datumsformat angeben (“date”).

Anschließend bauen wir den Pact. Mit “given” können wir einen Provider-State angeben, auf den wir später nocheinmal zurückkommen. Außerdem geben wir den Pfad, Header, den erwarteten Response-Status und den erwarteten Body an.

2. Verifikation des Pacts

Es wird außerdem eine Test-Methode benötigt, um den Pact zu verifizieren. Über die Annotation @PactVerification wird die Methode angegeben, die den zu verifizierenden Pact definiert.

Während der Ausführung der Unit-Tests wird  vom Pact-Framework die in dem Pact definierte Anfrage an einen Mock-Provider gestellt. Es können dann entsprechende Assertions durchgeführt werden. Ist der Test erfolgreich, wird das Pact-File erstellt.

Austausch des Pact-Files zwischen Consumer und Provider

Das Pact-File muss nun dem Provider übergeben werden, damit dieser prüfen kann, ob die von ihm bereitgestellte API den definierten Contracts entspricht. Dies kann händisch geschehen. In unserem Beispiel wird jedoch ein Pact-Broker eingesetzt, auf den der Consumer die Pact-Files hochlädt. Hierfür wird der Befehl mvn pact:publish verwendet. Der Provider holt die Pacts vom Broker ab. So müssen die Pacts nicht mehr manuell ausgetauscht werden und der gesamte Prozess kann automatisiert ausgeführt werden.

Ein weiterer Vorteil des Pact-Brokers ist, dass der Provider die Ergebnisse seiner Verifikation wieder an ihn zurückspielen kann. Der Consumer kann von dort dann abrufen, ob die APIs des Providers den Contracts entspricht, und ob er selbst deployed werden kann.

Verifikation der API des Providers mit Pact

Die Verifikation auf Provider-Seite läuft großteils automatisiert ab. Wir stellen eine Testklasse bereit, um Grundeinstellungen festzulegen. Hier wird auch über die Annotation @PactBroker angegeben, unter welcher Adresse der Pact-Broker zu erreichen ist, von dem die Pact-Files abgerufen werden können.

Die Tests selbst werden auf Grundlage des Pact-Files generiert und automatisiert während der Unit-Test-Phase ausgeführt. Hierbei wird vom Pact-Framework ein Consumer simuliert, der die in dem Pact definierten Anfragen an den Provider sendet und prüft, ob die Antworten den erwarteten Antworten ensprechen. Erst wenn alle Tests erfolgreich sind ist sicher, dass die API des Providers den Erwartungen des Consumers entspricht und erst dann darf er deployed werden. So können keine Breaking API-Changes deployed werden.

Provider States

Vorhin haben wir gesehen, dass wir in den Pacts sogenannte “Provider States” definieren können (“given” bei der Erstellung der Pacts). Diese dienen dazu, dass der Provider für die Tests bestimmte Zustände “einrichten” kann: Beispielsweise kann ein Item angelegt werden, mit dem der Test durchgeführt werden kann, oder es können Services gemockt werden, die ein Mock-Item zurückgeben. Für Tests müssen dann nicht Produktivdaten verwendet werden, sondern es kann die benötigte Test-Umgebung geschaffen werden.

Hier im Beispiel sehen wir, dass für den State “Item phone_1 exists” nichts getan wird. Das Item Phone 1 existiert bereits. Für den State “Item phone_2 is created” wird das Phone 2 angelegt.
Für den State “Item phone_3 exists” wird das Phone 3 angelegt (action = StateChangeAction.SETUP) und nach dem Test wieder gelöscht (action = StateChangeAction.TEARDOWN).

Noch Fragen?

Wir freuen uns über Kommentare! :)

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

*Pflichtfelder

*

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.