DevOps & Continuous Delivery - JAX-Konferenz https://jax.de/blog/devops-continuous-delivery/ Java, Architecture & Software Innovation Fri, 18 Oct 2024 13:22:14 +0000 de-DE hourly 1 https://wordpress.org/?v=6.5.2 Chaos Monkey für Spring Boot https://jax.de/blog/chaos-monkey-fuer-spring-boot-chaos-engineering/ Tue, 15 Aug 2023 07:04:38 +0000 https://jax.de/?p=88898 Mit Chaos Engineering wird bewusst Chaos in ein System eingeführt, um dessen Reaktion auf unvorhergesehene Ereignisse zu testen und Schwachstellen aufzudecken. Der „Chaos Monkey for Spring Boot“ ist eine populäre Implementierung für Chaos Engineering in Spring-Boot-Anwendungen. Es ist ein leistungsfähiges Werkzeug, das die Robustheit von Spring-Boot-Anwendungen durch die gezielte Einführung von Chaostests verbessert.

The post Chaos Monkey für Spring Boot appeared first on JAX.

]]>
Chaos Monkey for Spring Boot [1] ermöglicht es Entwicklern, Ausfallszenarien zu simulieren und die Reaktion ihres Systems darauf zu überwachen. Dadurch können potenzielle Schwachstellen identifiziert und Engpässe aufgedeckt werden, um die Stabilität und Ausfallsicherheit der Anwendung zu verbessern. Beispielsweise ermöglicht Chaos Monkey den Entwicklern das plötzliche Abschalten eines Dienstes, Netzwerkstörungen oder die Simulation hoher Lasten auf das System. Durch diese gezielte Einführung von Chaostests und die Behebung der Schwachstellen ist sichergestellt, dass ihre Anwendung auch unter schwierigen Bedingungen zuverlässig funktioniert. Der Chaos Monkey for Spring Boot bietet zahlreiche Funktionalitäten und Konfigurationsoptionen, um den Testprozess zu steuern und die gewünschten Szenarien zu simulieren. Dieser Artikel bietet einen detaillierten Einblick in den Chaos Monkey for Spring Boot. Darüber hinaus werden seine Einsatzszenarien, Best Practices und Implementierungsmöglichkeiten diskutiert.

NEUES AUS DER JAVA-ENTERPRISE-WELT

Serverside Java-Track entdecken

 

Vorstellung

Das Projekt Chaos Monkey for Spring Boot wurde ursprünglich anno 2017 von Benjamin Wilms bei codecentric ins Leben gerufen und über die Jahre von ihm und seinen Kollegen weiterentwickelt. Das Projekt wird aktiv gepflegt und an neue Spring-Boot-Versionen angepasst. Inspiriert von den Principles of Chaos Engineering [2] und mit einem Fokus auf Spring Boot bietet das Chaos-Monkey-Projekt eine Möglichkeit, Anwendungen besser zur Laufzeit zu testen. Das kann lokal, aber auch in der Cloud geschehen. Die Fragen, die sich jeder Entwickler stellt, egal wie viele Unit- und Integrationstests existieren: Wie verhält sich unsere Anwendung im Betrieb? Was passiert bei langsamen Datenbankabfragen? Wie verhält sich die Anwendung bei langsamen Antworten oder Fehlern anderer Services? Was passiert, wenn Discovery Service oder Discovery Client Fehler produzieren? Oder sogar zu viel Speicher oder CPU verbrauchen? Um all diese Probleme zu simulieren und Störungen zu injizieren, unterstützt uns nun der Chaos Monkey.

Implementierung und Konfiguration

Es gibt zwei Möglichkeiten, Chaos Monkey for Spring Boot in einer bestehenden Spring-Boot-Anwendung zu aktivieren. Entweder fügt man ihn zu den regulären Anwendungsabhängigkeiten im Build- und Dependency-Management-Tool hinzu (Listing 1) oder man bindet ihn als externe Abhängigkeit beim Start der Spring-Boot-Anwendung ein. Da sich das Tool nicht im offiziellen Release-Train von Spring Boot befindet, muss die passende Version selbst definiert werden.

// Maven
<dependency>
  <groupId>de.codecentric</groupId>
  <artifactId>chaos-monkey-spring-boot</artifactId>
  <version>3.0.1</version>
</dependency>
 
// Gradle
plugins {
  id 'java'
  id 'org.springframework.boot' version '3.0.1'
  id 'io.spring.dependency-management' version '1.1.0'
}

Beim Start der Spring-Boot-Anwendung mit aktiviertem „chaos-monkey“-Profil wird die Funktionalität für die ausgewählten Watchers aktiv. Die Eigenschaften können über Umgebungsvariablen, System Properties oder Application Properties konfiguriert werden (Listing 2). Die Konfiguration über Properties bietet alle verfügbaren Optionen, erfordert aber einen Neustart, um Änderungen zu übernehmen. Um das zu vermeiden, können die Konfiguration über Actuator REST API vorgenommen werden.

spring.profiles.active=chaos-monkey
chaos.monkey.enabled=true
management.endpoints.web.exposure.include=health,info,chaosmonkey
 
chaos.monkey.watcher.service=true
chaos.monkey.assaults.latencyActive=true

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Chaos Monkey Watchers und Assaults

Ein Watcher ist eine Komponente von Chaos Monkey for Spring Boot, die die Anwendung nach Spring Beans durchsucht. Je nach Konfiguration werden Annotation Watchers (Abb. 1) z. B. für @Component, Actuator Watchers und Outgoing Request Watchers z. B. für RestTemplate und WebClient registriert. Der Alternative Bean Watcher verwendet im Gegensatz zu den anderen Watchers ausschließlich den Bean-Namen. Mit Hilfe von Spring AOP wird ein Proxy für die Watcher-Funktionalität zur Verfügung gestellt. Beim Aufruf einer öffentlichen Methode auf einer Watched Bean entscheidet dann der Chaos Monkey, ob ein Angriff oder eben keine Aktion ausgeführt wird. Dieses Verhalten kann durch die Konfiguration beeinflusst werden.

Abb. 1: Annotation Watchers und Assaults

Assaults sind das Kernelement von Chaos Monkey und ihre Anwendung erfolgt je nach Konfiguration. Es gibt verschiedene Arten von Assaults: Request Assaults greifen spezifische Punkte der Anwendung an und können Latenz oder Exceptions verursachen. Runtime Assaults hingegen greifen die gesamte Anwendung an und können diese sogar zum Absturz bringen (AppKiller Assault). Möglich ist auch, nur den Speicher (Memory Assault) oder die CPU (CPU Assault) der Java Virtual Machine anzugreifen. Dank Chaos Monkey for Spring Boot können alle Arten von Runtime Exceptions geworfen und die benötigten Exceptions zur Laufzeit konfiguriert werden. Die Runtime Assaults können entweder durch Scheduling oder durch Chaos Monkey Endpoints ausgelöst werden.

Konfiguration des Chaos Monkey

Essenziell im Umgang ist, dass eine Änderung der Watcher-Konfiguration stets einen Neustart der Anwendung zur Folge hat. Das liegt an der Proxy-basierten Implementierung des Tools. Daher muss die gewünschte Konfiguration folglich bereits beim Start der Anwendung festgelegt werden. Jedoch können Watchers via Spring Boot Actuators auch später noch zur Laufzeit umkonfiguriert werden. Da es Spring Boot mittels des Actuators ferner ermöglicht, diese über JMX auszulesen bzw. anzupassen, bietet der Chaos Monkey diese Funktionalität auch für die Konfiguration an.

Welche Spring Beans benötigen nun gegenwärtig einen Watcher? In vielen Fällen ist ein Watcher für Service-Komponenten ausreichend. Runtime Exceptions werden z. B. durch die Spring-Data-Implementierung geworfen und erst im Web-Layer als Fehler an den Aufrufer zurückgegeben. Ob nun die Data Access Exception im Repository oder im Service durch den Assault erzeugt wird, ist in diesem Fall nicht relevant. Sollte der Service jedoch Fallback-Strategien implementieren, wäre die Repository-Schicht vorzuziehen (Listing 3).

#spring.profiles.active=chaos-monkey
 
management.endpoint.chaosmonkey.enabled=true
management.endpoint.chaosmonkeyjmx.enabled=false
 
chaos.monkey.watcher.controller=false
chaos.monkey.watcher.restController=false
chaos.monkey.watcher.service=true
chaos.monkey.watcher.repository=false
chaos.monkey.watcher.component=false
chaos.monkey.watcher.beans=<List of bean names>
chaos.monkey.watcher.exclude=<List of class names>

Wie zuvor erwähnt, erfolgt die Konfiguration der Assaults fortan über den Actuator Endpoint. Dies ermöglicht auch die Ausführung von Chaostests in einer CI/CD Pipeline. Folgende Beispiele zeigen mögliche Problemfälle. Die in Listing 4 gezeigte Konfiguration erzeugt zufällige Runtime Exceptions mit dem Exception Assault. Der Assault-Level in der Konfiguration definiert, wie viele Requests angegriffen werden sollen. In unserem Beispiel ist das jeder fünfte. Ob nun eine normale Runtime Exception oder eine andere ausgelöst werden soll, kann konfiguriert werden.

curl -X POST http://localhost:8080/actuator/chaosmonkey/assaults \
-H 'Content-Type: application/json' \
-d \
‘
{
  "level": 5,
  "latencyActive": false,
  "exceptionsActive": true,
  "killApplicationActive": false
}’

Im nächsten Beispiel (Listing 5) wird der AppKill Assault konfiguriert. Dieser kann über eine CronExpression oder einen Actuator-Aufruf ausgelöst werden.

curl -X POST http://localhost:8080/actuator/chaosmonkey/assaults \
-H 'Content-Type: application/json' \
-d \
‘
{
  "latencyActive": false,
  "exceptionsActive": false,
  "killApplicationActive": true,
  "killApplicationExpression": "*/1 * * * * ?"
}'

Im letzten Beispiel (Listing 6) sollen hängende Threads simuliert werden. Das Hinzufügen einer zusätzlichen Latenz von 100 s bei jeder Antwort (Level 1) führt dazu, dass die Anwendung nicht mehr reagiert.

curl -X POST http://localhost:8080/actuator/chaosmonkey/assaults \
-H ‘Content-Type: application/json’ \
-d \
‘
{
  "level": 1,
  "latencyRangeStart": 99999,
  "latencyRangeEnd": 100000,
  "latencyActive": true,
  "killApplicationActive": false,
  "exceptionsActive": false
}

Das sind nur drei Beispiele, die verschiedene Konfigurationen aufzeigen. Darüber hinaus existieren noch unzählige weitere Möglichkeiten – der Kreativität sind keine Grenzen gesetzt. Sind die Experimente einmal definiert und konfiguriert, kann man mit einem Tool wie Gatling [3] oder dem Chaos Toolkit [4] Tests durchführen und die Reports und Metriken analysieren.

SIE LIEBEN JAVA?

Den Core-Java-Track entdecken

 

Metriken der Attacken

Die Chaos-Monkey-Metriken sind über das Spring Boot Actuator API und Micrometer [5] verfügbar. Damit können die gesammelten Metriken für die Chaostests aus der Anwendung extrahiert werden (Listing 7). Diese Metriken bieten wertvolle Einblicke in das Verhalten der Anwendung während der Chaostests. Sie ermöglichen es Entwicklern, die Auswirkungen der verschiedenen Angriffe auf Performance, Latenz, Speicherverbrauch und andere wichtige Aspekte zu überwachen und zu analysieren. Der Einsatz von Chaos-Monkey-Metriken ermöglicht es Entwicklern, die Auswirkungen von Störungen und Unregelmäßigkeiten auf die Anwendung besser zu verstehen und gezielte Optimierungen vorzunehmen. Außerdem ist es möglich, die Metriken einzusetzen, um die Effektivität der implementierten Resilienz-Patterns zu bewerten und potenzielle Schwachstellen zu identifizieren.

curl -X GET http://localhost:8080/actuator/metrics \
-H 'Content-Type: application/json'
 
#Application Metrics
chaos_monkey_application_request_count_total
chaos_monkey_application_request_count_assaulted
#Assault Exception
chaos_monkey_assault_exception_count
#Assault Latency
chaos_monkey_assault_latency_count_gauge
chaos_monkey_assault_latency_count_total
#Assault KillApp
chaos_monkey_assault_killapp_count
#Watcher Metrics
chaos_monkey_assault_component_watcher_total
chaos_monkey_assault_controller_watcher_total

Patterns für die Behebung der Schwachstellen

In einer verteilten Architektur, in der verschiedene Services miteinander kommunizieren, treten oftmals unvermeidbare Fehler und Störungen auf. Resilience Patterns [6] dienen dazu, diese Fehler abzufangen, abzuschwächen und die Gesamtstabilität des Systems zu gewährleisten. Ein häufig verwendetes Pattern ist das Circuit Breaker Pattern. Dieses unterbricht die Kommunikation mit einem Dienst, falls dieser nicht ordnungsgemäß funktioniert, und stattdessen Fehlerbehandlungsmechanismen einsetzt. Das Fallback Pattern stellt alternativ Funktionalität oder Daten bereit, sofern ein Dienst nicht verfügbar ist. Retry Patterns ermöglichen das erneute Senden von Anfragen, wenn diese aufgrund von temporären Netzwerkproblemen fehlschlagen. Das Timeout Pattern definiert maximale Zeitlimits für Anfragen, um lange Wartezeiten zu vermeiden und die Reaktionsfähigkeit des Systems zu gewährleisten. Das Bulkhead Pattern und das Rate-Limiter Pattern sind weitere wichtige Resilience Patterns. Sie zielen darauf ab, die Auswirkungen von Fehlern in einem Dienst zu begrenzen. Daneben schützen sie andere Teile des Systems vor Ausfällen, indem sie Ressourcen isolieren und in separate Pools aufteilen. Durch den Einsatz dieser und weiterer Resilience Patterns können Microservices optimaler auf unvorhergesehene Situationen reagieren, Ausfälle isolieren und eine höhere Verfügbarkeit und Ausfallsicherheit erreichen. Die effektive Anwendung dieser Patterns ist entscheidend, um die Zuverlässigkeit des gesamten Microservices-Ökosystems zu gewährleisten.

Die Bibliothek Resilience4j [7] bietet umfassende Unterstützung für die Implementierung von Resilience Patterns in Microservices und lässt sich nahtlos in Spring-Boot-Anwendungen integrieren. Durch die Integration mit Spring Boot können Entwickler die Funktionalität von Resilience4j nutzen, indem sie kurzerhand die entsprechenden Abhängigkeiten und Konfigurationen hinzufügen. Die Resilience4j Annotations und Wrapper erleichtern die Anwendung der Resilience Patterns auf Spring-Boot-Komponenten wie Services, Controller oder Repository-Klassen. Ferner ermöglicht die Integration mit Spring Boot die Verwendung von Spring Boot Actuator zur Überwachung und Erfassung von Metriken für die Resilience Patterns. Infolgedessen können Entwickler das Verhalten ihrer Microservices in Bezug auf Fehlertoleranz und Resilienz effektiv überwachen und analysieren. Die nahtlose Integration erleichtert Entwicklern die Implementierung einer robusten und widerstandsfähigen Architektur in ihren Microservices.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Fazit

Chaos Monkey for Spring Boot ist ein sehr nützliches Werkzeug für alle Softwareentwickler, die sich mit Chaos Engineering beschäftigen möchten. Durch die Implementierung mit Spring AOP Proxies können Seiteneffekte innerhalb der Anwendung simuliert werden, ohne die reale Infrastruktur zu beeinflussen. Dies ermöglicht es Entwicklern, Probleme auf einfache Weise zu simulieren und zu reproduzieren, sogar auf ihren eigenen Computern. Sobald man damit beginnt, Chaosexperimente zu definieren, wird deutlich, dass zunächst normalerweise das Monitoring verbessert werden muss, um die auftretenden Probleme zu identifizieren. Anschließend werden typische Resilienz-Patterns verwendet, um die entdeckten Schwachstellen zu beheben. Chaos Monkey for Spring Boot erleichtert also nicht nur das Experimentieren mit Chaos, sondern unterstützt auch die Entwicklung resilienter Anwendungen.


Links & Literatur

[1] Chaos Engineering for Spring Boot: https://github.com/codecentric/chaos-monkey-spring-boot

[2] Principles of Chaos Engineering: https://principlesofchaos.org

[3] Gatling: https://gatling.io

[4] Chaos Toolkit: https://chaostoolkit.org

[5] Micrometer: https://micrometer.io

[6] Resilience Patterns: https://github.com/lucasnscr/Resilience-Patterns

[7] https://resilience4j.readme.io/docs

The post Chaos Monkey für Spring Boot appeared first on JAX.

]]>
Wie die CD Pipeline mit CDC und BDD die E2E-Tests ablöst https://jax.de/blog/wie-die-cd-pipeline-mit-cdc-und-bdd-die-e2e-tests-abloest/ Mon, 12 Sep 2022 14:15:32 +0000 https://jax.de/?p=87484 Mit einer ausgeklügelten Kombination aus Behaviour-driven Development (BDD) und Consumer-driven Contracts (CDC) lassen sich nachgelagerte E2E-Tests nach dem Shift-Left-Prinzip innerhalb der eigenen CD Pipelines umsetzen. Wir stellen einen Ansatz dazu vor.

The post Wie die CD Pipeline mit CDC und BDD die E2E-Tests ablöst appeared first on JAX.

]]>
Um echtes Continuous Delivery (CD) zu betreiben und trotzdem durchgetestete Software zu liefern, benötigen wir automatisierte E2E-Tests. Klassischerweise werden diese vom nachgelagerten Testteam durchgeführt. In einem agilen Umfeld möchten wir jedoch Abhängigkeiten von anderen Teams vermeiden und die Software trotz asynchroner oder synchroner Schnittstellen selbst mit der eigenen CD Pipeline durchtesten und deployen – in der Produktion. CD sieht in der Theorie etwa so aus: Code, Push, CD-Pipeline, Deploy to PROD. In der Praxis könnte sich aber folgende Debatte entwickeln:

Der Product Owner (PO) sagt: „Stopp! Ihr könnt doch Software nicht ohne in einer explizit aufgesetzten Integrationsumgebung durchgeführte End-to-End-Tests (E2E) in die Produktion ausliefern.“

Dev: „Why not? Wir haben alles mit der Pipeline getestet.“

PO: „Aber doch nur innerhalb eurer Microservices! Was ist mit den Interaktionen zu den anderen Systemen?! Die können nur in einer Integrationsumgebung ordentlich als End-to-End-Test getestet werden.“

Dev: „In so einem Vorgehen können wir nicht nach Continuous Delivery arbeiten. Und wieso brauchen wir E2E-Tests auf einer Integrationsumgebung, wenn wir innerhalb unserer Pipeline alles automatisiert testen?“

PO: „Nichts für ungut, aber ihr könnt nicht alle Fälle, die da draußen in der richtigen Welt passieren können, in euren Tests abbilden.“

Dev: „Eigentlich haben wir alle fachlichen Fälle, die du uns als Anforderung hoch priorisiert hast, auch mit fachlichen Tests abgedeckt. Und alle andere Fälle lassen wir nicht zu. Das ist unsere Definition of Done (DoD).“

PO: „Vielleicht habt ihr fachlich alle Fälle sauber mit Tests abgedeckt, aber schon die Schnittstellen und die Interaktion mit den anderen Systemen, da kann doch einiges schiefgehen. Andere Systeme könnten die Schnittstellen geändert haben.“

Dev: „Wir haben doch alle unsere Schnittstellen mit den Consumer-driven Contracts abgesichert.“

PO: „Und was ist mit der technischen Verbindung, URL, Topic etc.? Woher wissen wir, dass die Verbindung mit den anderen Systemen in der Produktivumgebung funktioniert, ohne das einmal auf einer anderen Umgebung getestet zu haben?“

Dev: „Ja, das stimmt, dafür haben wir Infrastructure-as-a-Code-Unit-Tests und auch – als letzte Sicherheit – Health Checks. Sobald eine Verbindung nicht erfolgreich aufgebaut werden kann, wird die neue Version der Anwendung automatisiert heruntergefahren. Das neue Deployment wird dadurch gestoppt. Die alte Version läuft weiterhin.“

PO: „Trotzdem, die E2E-Tests werden vom Testexperten durchgeführt und auf diese Qualitätskontrolle von außen können wir nicht verzichten.“

Dev: „Dadurch sind wir jedoch abhängig vom Testmanagementteam und können nicht automatisiert in die Produktion deployen. Außerdem steht das dem Fail-Fast-Prinzip entgegen, nach dem wir unsere Fehler sofort – am besten, sobald sie entstanden sind – entdecken können, um sie direkt zu beheben.“

Abb. 1: Nachgelagertes Testteam

So oder ähnlich könnte aktuell eine Debatte über die Integrationstests bei verteilten Systemen ablaufen. Diese POs lassen sich nicht überzeugen, oder? Dabei wissen wir es in der Dev-Community doch mittlerweile besser. Es ist State of the Art, alle Tests zu automatisieren und eine CD Pipeline bis in die Produktion aufzusetzen, oder etwa nicht? Giganten wie Amazon oder Facebook liefern täglich mehrere Tausend Änderungen in die Produktion aus.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Testanforderungen

Vielleicht sollten wir unsere Vorgehensweise hin zu proaktiv ändern. Wenn wir aus der Softwareentwicklung heraus einen Prozess-Change in Richtung Continuous Delivery herbeiführen wollen, müssen wir eventuell selbst die Frage an den Entscheider stellen – falls das Team nicht selbst entscheiden darf und noch nicht gänzlich nach dem DevOps-Ansatz arbeitet. Nehmen wir an, dass der PO entscheidet, so würde die Anforderung sicherlich lauten: Die deployte Software soll in der Produktion fehlerfrei laufen. Dafür soll die Testpyramide vollständig abgetestet sein, und der E2E-Test wird in der Integrationsumgebung durchgeführt.

Ungeachtet der Tatsache, dass ein Ausfall der Software auch durch äußere Umstände herbeigeführt werden kann und nicht nur durch interne Softwarefehler, ist mit „fehlerfrei“ implizit auch die Ausfallsicherheit und anderweitige Sicherheit gemeint. Wir brauchen robuste Software, die ausfallsicher auf einer ausfallsicheren Laufzeitumgebung zur Verfügung gestellt wird. Dabei soll die Integration mit externen Schnittstellenpartnern ebenfalls ausfallsicher aufgebaut sein.

Das ist ganz schön viel – wie kommen wir auf die Idee, dies mit der CD abdecken zu können? Eins nach dem anderen. Die Testpyramide ist komplex, lässt sich jedoch auseinanderbauen.

Abb. 2: Klassische Zuordnung der Tests auf die CI/CD Pipeline

Infrastructure as a Code (IaC) hilft uns, alles, was mit der Laufzeitumgebung zu tun hat, als DevOps-Team in die eigene Verantwortung zu holen. Dass IaC auch mit eigenen Unit-Tests abgesichert sein soll, ist klar – ansonsten können wir keine Garantien ausgeben. Mit den Health Checks und den Smoke-Tests können wir die Verfügbarkeit der erwarteten Infrastruktur und die Konfiguration der Integration direkt nach dem Deployment prüfen.

Wie schaut es mit Security-, Last- und Performancetests aus? Auch sie dürfen wir nicht vernachlässigen, und es gibt auch entsprechende Lösungen. In diesem Artikel konzentrieren wir uns jedoch auf die fachlichen E2E-Tests.

Unser Ziel: Automatisierung der E2E-Test, jedoch nur im jeweiligen Verantwortungsbereich des DevOps-Teams. Von der Oberfläche bis zu den öffentlichen Schnittstellen mit den Partnersystemen. Übergreifende, echte E2E-Tests sollen somit nach dem Shift-Left-Prinzip in viele kleine Teile aufgeteilt und die E2E-Verantwortung an die jeweiligen Teams delegiert werden. Aus Sicht des DevOps-Teams und des dazugehörigen Softwareprodukts wird die Testphase in die Entwicklung integriert.

Abb. 3: Vorziehen der nachgelagerten Tests nach dem Shift-Left-Prinzip

Letztendlich sollen die E2E-Fälle vollständig abgedeckt sein. Doch wie können wir sicherstellen, dass trotzdem der gesamte Prozess funktioniert, auch wenn wir nur ein Teil der gesamten E2E-Prozesse testen?

Wir benötigen eine Möglichkeit, die E2E-Tests auf die Teams aufzuteilen, sodass jedes Team seinen Anteil in eigener Obhut auch mit eigenen automatisierten Tests abdecken kann – gleichzeitig soll jedoch sichergestellt sein, dass alle E2E-Testcases (Geschäftsfälle) abgedeckt werden und an den Schnittstellen bei allen Schnittstellenpartnern das gleiche Verständnis der Nutzung der Schnittstelle herrscht.

Der erste Teil ist einfach. Das erwartete Verhalten des jeweiligen Teilsystems, das in dem Prozess interagiert, kann mit automatisierten Black-Box-Integrationstests abgedeckt werden. Optimalerweise wird hierfür Behaviour-driven Development eingesetzt – dazu kommen wir noch.

Dass es jedoch genau an den Schnittstellen trotz aller Abstimmungen zu Fehlern kommt, wenn diese zum ersten Mal nach der Implementierung/Anpassung integrativ zusammengeschaltet werden, wissen wir leider aus eigener Erfahrung. Wir können dem PO nicht übelnehmen, dass er uns das nicht abkauft.

DIE MODERNISIERUNG MIT DEVOPS

DevOps & CI/CD-Track entdecken

 

APIs, APIs, APIs

APIs, wie wir die Schnittstellen lieber nennen, müssen zwischen den Parteien abgestimmt sein. Wir Menschen sind jedoch nicht gut darin, fehlerfrei miteinander zu kommunizieren. Deshalb haben wir Spezifikationsstandards für die Schnittstellen entworfen. Angefangen vom CSV über Cobol Copybook bis zu XSD, WSDL und JSON Schema, OpenAPI, AsyncAPI, Avro, Protobuf etc. Mittels des Schema-First-API-Ansatzes kann das zentral abgestimmte Schema an Schnittstellenparteien verteilt werden, um den Server Proxy und die Client Stubs daraus zu generieren. Wenn wir die Schemaspezifikation dann noch für jedes einzelne Feld durch Regular Expressions absichern, kann nichts mehr schiefgehen, oder?

Leider doch, und zwar dann, wenn die Schnittstelle zwar technisch korrekt eingebunden ist, aber die Bedeutung fachlich missverstanden wurde. Dann tauchen die resultierenden Fehler erst bei der ersten echten Integration auf.

Eine zentrale Schemadefinition ist außerdem nicht für alle Schnittstellen optimal, wenn es darum geht, die Geschwindigkeiten der jeweiligen Teams voneinander zu entkoppeln. Ein zentrales Schema koppelt die beteiligten Parteien eng aneinander. Sobald eine Änderung aufgrund einer Partei erfolgen muss, müssen oft alle anderen beteiligten Parteien die Änderungen im Gleichschritt umsetzen und veröffentlichen. Daher geht die Entwicklung mittlerweile zum Code-First-API-Ansatz, nach dem die Schnittstelle vom Provider zuerst implementiert wird und – davon ausgehend – nach einer Beschreibung die Implementierung der Schnittstellennutzer-(Consumer-)Anbindung erfolgt. Die Consumer implementieren nur diejenigen Teile der Schnittstellen, die sie tatsächlich benötigen. Wenn sich die Schnittstelle ändert und die Änderung rückwärtskompatibel umgesetzt wurde, muss nur der Consumer angepasst werden, der diese Änderung nutzen möchte. Zeitlich gesehen, kann diese Anpassung nachgelagert geschehen. Damit erreicht man eine lose Koppelung zwischen den Schnittstellenpartnern.

Ein weiteres Argument für das Testen der nach dem Code-First-API-Ansatz implementierten Schnittstellen ohne zentrales Schema ist, dass beim Schema-First-Ansatz nicht nur die fachliche Einbindung fehlerhaft sein kann, sondern auch die technische Implementierung des API selbst.

Consumer-driven Contracts

In dem beschriebenen Szenario, in dem die Consumer eines API nur die Untermenge des vollständigen API verwenden, ist es für den API-Provider interessant, die Nutzungsweise der jeweiligen Consumer zu kennen. Dazu zwei Beispiele:

  1. Ein Feld, das von keinem Consumer verwendet wird, kann so einfach aus dem API entfernt werden.

  2. Bei den Anpassungen an einem Feld müssen nur die betroffenen Consumer informiert werden.

Dies ist die zentrale Idee des Consumer-driven Contract-(CDC-)Ansatzes. Beim CDC handelt jeder Consumer eines API dessen Nutzungsweise in Form eines Vertrags aus und stellt diese dem API-Provider zur Verfügung. Diese Verträge werden sowohl auf der Consumer- als auch auf der Providerseite für das unabhängige und damit entkoppelte Testen einer Schnittstelle herangezogen. Dadurch kommen wir der PO-Anforderung nach, dass alle Schnittstellen getestet werden müssen.

In einem solchen CDC-Vertrag (oder nur CDC) definiert jeder API Consumer für sich folgende Rahmenbedingungen der API-Nutzung, von denen der Consumer selbst ausgeht:

  1. Bei synchronen APIs:

    1. Struktur der Anfrage (Request), die der API Consumer bei der API-Anfrage erstellen wird
      1. Header
        1. Key-Value-Wertepaare
      2. Payload
        1. Schemaähnliche Definition der Request-Struktur
    2. Struktur der Antwort (Response), die der API Consumer bei der API-Anfrage als Antwort erwartet
      1. Header
        1. Key-Value-Wertepaare
      2. Payload
        1. Schemaähnliche Definition der Response-Struktur
  2. Bei asynchronen APIs
    1. Struktur der Nachricht (Message), die der API Consumer bei der API-Nutzung erwartet
      1. Header
        1. Key-Value-Wertepaare
      2. Payload
        1. Schemaähnliche Definition der Response-Struktur

Provider States: Das Ganze definiert der Consumer jeweils je genutztem Provider State. Das ist der Zustand des Providers oder des angefragten/publizierten Datensatzes. Dazu zwei Beispiele:

  1. Für den Provider State „Datensatz nicht vorhanden (404)“ sieht eine Antwort anders aus, als wenn ein Datensatz vorhanden ist.

  2. Falls der Schnitt des API unterschiedliche Datensatzausprägungen zulässt, die dann jeweils andere Pflichtfelder in der Antwort/Nachricht haben, wäre bei einem Person-API, bei dem auch die Beziehungen der Personen ausgegeben werden, ein möglicher Provider State: „Person mit Beziehungen“. Bei der Person mit Beziehungen sieht die Antwort/Nachricht anders aus, als wenn keine Beziehungen vorhanden sind.

Beim CDC-Ansatz rückt mit der Definition der Provider States die fachliche Nutzung der APIs in den Vordergrund. Erst durch diese fachliche Perspektive des API können wir für das gleiche API, sofern es in unterschiedlichen fachlichen Fällen anders genutzt wird, die jeweilige Nutzungsweise definieren und testen. Das API wird also abhängig von der fachlichen Nutzung getestet. Ein Test für den Provider State „Person mit Beziehungen“ prüft, ob die Beziehungen korrekt über das API gemeldet wurden. Bei einer Person ohne erfasste Beziehungen, aber mit „Biometrischen Daten“ sieht die API Response/Event wieder anders aus. Die API-Tests können damit für alle unterschiedlichen Ausprägungen als Provider States definiert werden. Zu viele Provider States weisen allerdings auf einen schlechten Schnitt des API hin.

Provider-Tests: Nachdem der Consumer für alle genutzten Provider States den CDC definiert hat, kann er diesen zum Zweck der Information und Verifikation an den Provider kommunizieren. Erst bei der Rückmeldung der erfolgreichen Verifikation des CDC durch den Provider kann der Consumer davon ausgehen, dass von ihm getroffene Annahmen bei der Erstellung des CDC auch vom Provider erfüllt werden können. Beim CDC Testing spricht man dabei von Providert-Test.

Consumer-Tests: Bevor der Consumer den CDC an den Provider kommuniziert, soll er für sich selbst prüfen, ob der von ihm erstellte CDC tatsächlich mit der API-Implementierung übereinstimmt. In dem Fall sprechen wir von einem Consumer-Test.

CDC Broker: Um die Publikation und Durchführung der CDC-Tests zu erleichtern, bieten alle CDC-Testing-Frameworks einen zentralen Knoten, der die publizierten CDCs entgegennimmt und den Providern für die Verifikation zur Verfügung stellt. Da die CDC-Tests automatisiert in den CD Pipelines ablaufen sollen, ist daher der CDC Broker von immenser Bedeutung für den Ansatz, denn erst durch ihn ist die Automatisierung erreichbar.

Der CDC Broker protokolliert die Testläufe. Wenn es also zu einer Abweichung in der API-Implementierung kommt, wird dies im CDC Broker registriert und die Tests der Teams schlagen Alarm. Zusätzlich hat der CDC Broker eine rudimentäre Weboberfläche, welche die auf dem Broker veröffentlichten Verträge anzeigt und weitere Informationen dazu wiedergibt.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Schritte des Consumer-driven Contracts

Um den vollständigen CDC-Prozess abzuschließen, sind folgende Schritte notwendig: Die Vorbedingung ist, dass der Provider die API-Doku mit allen möglichen Provider States bereitgestellt hat:

  1. Consumer

    1. Consumer definiert den CDC
    2. Consumer verifiziert den CDC intern im Consumer-Test
    3. Nach dem erfolgreichen Consumer-Test publiziert der Consumer seinen CDC auf dem zentralen CDC Broker
  2. Provider

    1. Provider verifiziert alle für ihn publizierten CDCs und meldet das Ergebnis an den Broker zurück. Wichtig hierbei: Die Provider-Verifikation sollte von Consumern automatisiert angetriggert werden können, um die Consumer CD Pipeline nicht zu blocken.

Erst bei der erfolgreichen Verifikation durch den Provider kann der Consumer sicher sein, dass die API-Implementierung in der Integration funktionieren wird, und seine Änderungen produktiv veröffentlichen.

Abb. 4: Schritte des Consumer-driven Contracts

PACT [1] ist zurzeit das bekannteste CDC-Framework, gefolgt von Spring Cloud Contract [2]. Ein Vergleich dieser beiden Frameworks im Detail wurde bereits von unseren Kollegen durchgeführt und kann auf entwickler.de nachgeschlagen werden [3].

Behaviour-driven Development

Mit dem CDC-Ansatz können wir sicherstellen, dass die Schnittstellen funktionieren. Dies ist jedoch nur ein Teil der E2E-Tests. Abseits der Schnittstellen ist der Kern jeder Anwendung die eigene fachliche Logik. Viel bekannter als CDC ist der Ansatz Behaviour-driven Development (BDD).

Beim BDD-Ansatz wird das erwartete Soll-Verhalten einer Anwendung in Form der fachlichen Akzeptanzkriterien definiert. Dabei bedient sich der BDD-Ansatz der Idee des Test-driven Development (TDD). TDD wird jedoch beim BDD eine Stufe höher, nämlich auf die fachlichen Anforderungen, angewendet. So kann der PO bei der Definition der Anforderungen in einer User Story auch die BDD-Akzeptanzkriterien definieren. Erst wenn diese erfüllt werden, ist die Fachlichkeit korrekt implementiert. Die Akzeptanzkriterien sollen dabei so klar und eindeutig definiert werden, dass sie auch zu deren eigener Verifikation eingesetzt werden können. Die Akzeptanzkriterien bilden damit auch die fachliche Testspezifikation mit den Testdaten ab.

Die Vorteile liegen für das Dev-Team auf der Hand: Durch die Testspezifikation wird ein Beispielablauf mit sinnvollen fachlichen Beispieldaten (Testdaten) beschrieben, was bei der Entwicklung eventuelle Missverständnisse aus einer rein abstrakten Beschreibung reduziert – nach dem Motto „Ein schlechtes Beispiel ist immer noch besser als eine bloße Beschreibung“. Und diese soll bereits vorab nach der TDD-Idee implementiert werden. Ein erwartetes Verhalten wird so vorab getestet und erst dann als erfolgreich getestet gemeldet, wenn die Anforderung wie spezifiziert umgesetzt wurde. Der PO kann dann die User Story bewiesenermaßen abschließen. Die implementierten Tests bleiben erhalten und dienen als Regressionstests für weitere Anpassungen der Anwendung.

Die Spezifikation der Akzeptanzkriterien folgt dabei dem klassischen Muster der Testspezifikation. Zuerst werden die Vorbedingungen definiert (GIVEN). Dementsprechend kann das definierte Verhalten ausgelöst/angestoßen werden (WHEN). Letztendlich werden die Ergebnisse verifiziert (THEN).

Durch den Einsatz vom Domain-Specific-Language-(DSL-)Lösungen wird ermöglicht, dass diese Spezifikation in der fachlichen Domänensprache erfolgt. Dadurch bleibt die Spezifikation auch als Dokumentation des Verhaltens der Anwendung für eine breite Zielgruppe nutzbar.

Die Anforderungen werden als fachliche Szenarios definiert. Mehrere Szenarien spezifizieren eine Funktion und können alle innerhalb einer feature-Datei abgelegt werden. Die BDD-Frameworks führen diese als automatisierte Tests aus, was wiederum eine Voraussetzung für den Einsatz innerhalb der CD Pipelines ist. Das Beispiel in Listing 1 stellt eine derartige Funktion mit mehreren Szenarien dar.

Feature: Anlage einer Adresse 
  Durch diese Funktion wird eine neue Adresse angelegt
 
    @positive
    Scenario Outline: Erfolgreiche Anlage einer neuen Inland-Adresse
      Given Person <person> existiert
      When neue Adresse mit folgenden Daten angelegt wird:
        person: <person>
        strasseNr: <strasseNr>
        plzOrt: <plzOrt>
      Then wurde die Adresse erfolgreich als Inland-Adresse erfasst
 
    Examples:
    | person         | strasseNr       | plzOrt            | 
    | Helena Adam    | Musterstr. 12   | 12345 Musterstadt |
    | Manuell Ernst  | Heßbrühlstr. nn | 88888 Ortens      |
    | Sabine Lustig  | Nope Drope 234f | 90009 Zentopia    |

Wie das Beispiel in Listing 1 zeigt, wurde für drei unterschiedliche Durchläufe (Exampels) ein Szenario-Outline (Template) definiert. Bei der Durchführung des Tests muss als Vorbedingung eine Person mit passendem Namen angelegt werden. Dabei scheinen weitere Pflichtfelder der Person irrelevant zu sein. Der Test muss sich also darum kümmern, dass hinter dem Schritt Person <person> existiert eine Person mit dem jeweiligen Namen angelegt wird.

Unter When wird dann die Adresse zu der bestehenden Person erfasst und gespeichert. Das ganze Szenario bearbeitet nur die positiven Fälle, was hier mit dem Custom-Tag @positive dargestellt wird. Fehlerfälle sind hierbei Out of Scope und würden in einem eigenen Szenario abgebildet, was wohl der PO durch eine weitere User Story spezifizieren wird.

Schließlich muss der Then-Schritt die zuletzt erfasste Adresse auslesen und prüfen, ob diese mit dem Inlandkennzeichen wie gefordert erfasst wurde.

Das BDD-Framework kann solch eine Featurespezifikation als Unit-Test ausführen und die Ergebnisse sowohl für die CD Pipeline wie auch als Reports melden. Eines der am meisten eingesetzten Frameworks im Java-Umfeld ist Cucumber [4]. Falls man auf den Glue-Code (Ausimplementierung der Testschritte) verzichten und etwas technischere Beschreibungen der Szenarien in Kauf nehmen möchte, kann man mit dem Karate Framework [5] die Implementierung der Tests schneller umsetzen.

 

Kein Mut zur Testlücke bei der fachlichen Testabdeckung

Mit BDD-Tests decken wir die Fachlichkeit ab, mit CDC-Tests die Schnittstellen. Können wir damit die klassischen E2E-Tests bereits ad acta legen? Noch nicht ganz.

Die E2E-Tests stellen sicher, dass alles im Zusammenspiel funktioniert. Wir haben jedoch nur sichergestellt, dass die jeweiligen BDD-Tests und die jeweiligen CDC-Tests eigene Bereiche abdecken. Um den gesamten Geschäftsfall abzudecken, müssten wir diesen sowohl in BDD-Tests als auch in CDC-Tests stückeln und alle teilnehmenden Anwendungen damit abdecken. Erst wenn wir sowohl die Fachlichkeit als auch die Schnittstellennutzung für den jeweiligen Geschäftsfall testen – und dies in der gesamten Kette bei allen teilnehmenden Anwendungen gemacht wurde – ist keine Testlücke mehr im E2E-Geschäftsprozess vorhanden.

Da aktuell kein Framework BDD und CDC in einem kann, haben wir beide Ansätze kombiniert. Da beide auf die fachliche Ausrichtung (bei CDC Provider States) setzen, ist es uns möglich, die fachlichen E2E-Tests so aufzuteilen, dass sowohl die vollständige Umsetzung der Fachlichkeit als auch die der Schnittstellen je Geschäftsfall getestet werden kann.

Abb. 5: Aufteilung des E2E-Tests auf die BDD- und CDC-Tests je Geschäftsfall

Das sieht nun nach viel Arbeit aus, aber das täuscht. Das ist nur die Folge des Shift-Left-Prinzips, in dem die klassisch nachgelagerten E2E-Testaufwände in die Entwicklungsphase hineinverlagert werden. In der Summe sind die Gesamtaufwände durch die frühzeitige Fehlererkennung und Vollautomatisierung der Tests geringer.

Agile-driven Integrationstests

Das A und O beim Ansatz Agile-driven Integrationstests sind die Identifikation der fachlichen Ereignisse (Events) und die Definition der Interaktionen zwischen Benutzer und Anwendung sowie zwischen den Anwendungen selbst. In einem Domain-driven-Design-(DDD-)Kontext kann dies beispielsweise als Ergebnis eines Event-Storming-Workshops entstehen. Sobald die wichtigsten Events (Pivotal-Events) identifiziert wurden, können sie in der Folge von den beteiligten Teams im Detail spezifiziert werden. Um auch alle unterschiedlichen Geschäftsfälle sorgfältig testen zu können, ist es hierbei notwendig, dass auch die unterschiedlichen Ausprägungen der Pivotal-Events als Provider States identifiziert und als Referenzevents definiert werden. Diese Definitionen der Referenzevents je Provider States dienen als Grundlage für BDD- und CDC-Tests. In Abbildung 6 ist der gesamte Ansatz vereinfacht dargestellt.

Abb. 6: Agile-driven Integrationstests

Die Vorbedingung des CDC-Ansatzes gilt auch hier: Der Provider hat die API-Dokumentation mit allen möglichen Provider States bereitgestellt.

Wie in Abbildung 6 in den Schritten 1.1 und 2.1 gezeigt, definieren sowohl Provider als auch Consumer die Referenzevents – oder Requests-Responses-Objekte im Falle des synchronen API – für jede benötigte Ausprägung des Geschäftsprozesses als eigenständigen Provider State. Im nächsten Schritt wird, zunächst ausgehend von den Referenzevents der Provider States, die jeweilige Logik mit den BDD-Tests (Schritte 1.2 und 2.2) getestet. Auf der Consumer-Seite kann der jeweilige für den Provider State erstellte BDD-Test mittels Referenzevent direkt ausgelöst werden. Auf der Provider-Seite dagegen werden die Referenzevents als Expected Events mit dem Ergebnisevent (Actual Event) aus den BDD-Tests verglichen. Wurde die jeweilige fachliche Logik erfolgreich getestet, können sowohl Provider als auch Consumer die CDC-Tests umsetzen und hierfür jeweils die Referenzevents für die Provider States als Basis einsetzen (Schritte 1.3–1.5 und 2.3).

Für die aktuellen CDC-Tools muss die Definition der Verträge in der jeweiligen DSL (Pact DSL bei Pact und Groovy DSL bei Spring Cloud Contract) weiterhin vom Consumer ausprogrammiert werden. Der Consumer-Test wird jedoch durch die Referenzevents stark vereinfacht. Da die fachliche Logik bereits mit dem BDD-Test verifiziert wurde, muss der Consumer-Test jetzt nur sicherstellen, dass der publizierte Vertrag mit den definierten Referenzevents je Provider State übereinstimmt. Der Provider dagegen kann für die Durchführung der Provider-Tests eigene Referenzevents 1:1 verwenden.

Wichtig ist: Die jeweiligen Consumer- und Provider-Schritte können entkoppelt voneinander umgesetzt werden. Die CDC Tools bieten hierfür Unterstützung mit Versionsnummern für Consumer/Provider. Die Teams sollen trotzdem die Änderungen des API miteinander abstimmen, können jedoch entkoppelt voneinander die Anpassungen implementieren und diese auch bis in die Produktion, unabhängig vom jeweiligen Deployment der anderen, veröffentlichen.

Hands-on – das Event-driven-Architecture-Szenario

Bevor wir in die Praxis starten, wollen wir einen Blick auf unser fachliches Szenario werfen. Für unser Beispiel wollen wir zwei Bounded Contexts (aus dem Domain-driven Design) miteinander interagieren lassen. Für jeden Bounded Context ist ein separateres Scrum-Team zuständig, dessen Mitglieder alle in komplett getrennten Umgebungen arbeiten. Eigene Technik, eigene Sprache.

In Abbildung 7 sehen wir links den Bounded Context Personen, mit den Entitäten Person, Anschrift und biometrische Daten, und rechts den Bounded Context Grüße, mit den Entitäten Person und Gruß. Wichtig dabei ist, dass die Person in Personen keine direkte Beziehung zu der Person in Grüße hat.

Abb. 7: Bounded Context

Die Entitäten können ähnliche oder gleiche Konzepte haben, müssen aber nicht. Sie haben in jedem Bounded Context ihre eigene Daseinsberechtigung und ihren eigenen fachlichen Wert. Der Bounded Context Personen verantwortet die Verwaltung der Personendaten. Jegliche Änderung der Daten einer Person wird in einem Event Person geändert publiziert.

Der Bounded Context Grüße benötigt die Personendaten in eigenem Kontext. Er konsumiert die publizierten Events und wandelt die Daten in seine Interpretation der Person um. Wenn dies abgeschlossen ist, kann der Nutzer einen Gruß an die Person erzeugen.

Abb. 8: DDD Pivotal Event

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

CDC-Vertrag

Da wir möchten, dass beide Bounded Contexts unabhängig voneinander arbeiten (siehe Consumer-driven Contracts) soll das asynchrone API mit dem Event Person geändert durch den CDC-Vertrag spezifiziert und getestet werden. Das bedeutet, dass sich die Bounded Contexts auf ein Austauschformat einigen. Üblicherweise präsentiert der Eventproduzent dabei sein komplettes Event, und der Konsument gibt an, welche Teile daraus er benötigt. Diese werden dann gemeinsam mit Beispieldaten im Vertrag (engl. Contract) festgehalten. Sind sich beide Seiten einig, werden die Verträge in den Source Code überführt. In dem Beispiel verwenden wir hierfür das Framework PACT [1].

In Listing 2 sehen wir, wie eine solche Einigung aussehen kann. Wir haben uns hier für JSON als Austauschformat entschieden, da das Event auch mit JSON publiziert wird. Im Bereich header werden die vom Consumer erwarteten Headerinformationen beschrieben. Im Bereich payload</em sehen wir den beispielhaften Eventinhalt für den Provider State Person mit Geburtstag, der vom Consumer für eine Person mit Geburtstagsangaben erwartet wird. Dieser Provider State ist für den Consumer relevant, da er für den eigenen Geschäftsfall Geburtstagsgrüße versenden möchte.

{
  "header": {
    "action": "create",
    "contentType": "application/json",
    "event": "PersonChanged",
    "kafka_topic": "person",
    "providerState": "PersonWithBirthday"
  },
  "payload": {
    "id": 1,
    "surname": "Adam",
    "forename": "Helena",
    "email": "[email protected]",
    "birthday": "1984-08-06"
  }
}

Consumer

Den Anfang macht der Consumer. Dort können wir eine einfache Unit-Test-Klasse für den CDC-Vertrag als eigene PACT-Methode definieren (Listing 3).

@Pact(consumer = "greeting-service-person-consumer")
MessagePact createPersonWithBirthdayPact(MessagePactBuilder builder) {
  PactDslJsonBody body = new PactDslJsonBody();
  body.integerType("id", 1L)
    .stringType("surname", "Adam")
    .stringType("forename", "Helena")
    .stringType("email", "[email protected]")
    .localDate("birthday", "YYYY-MM-dd", LocalDate.of(1984, 8, 6));
 
  Map<String, Object> metadata = new HashMap<>();
  metadata.put("Content-Type", "application/json");
  metadata.put("kafka_topic", "person");
  metadata.put("action", "create");
  metadata.put("event", "PersonChanged");
  metadata.put("providerState", "PersonChanged");
 
  return builder.given("PersonWithBirthday")
    .expectsToReceive("PersonChanged")
    .withMetadata(metadata)
    .withContent(body)
    .toPact();
}

Über die PACT-DSL können wir das Schema unserer Payload schnell und einfach definieren. Neben üblichen Standardtypen können auch Arrays und geschachtelte Objekte definiert werden. Auch ist die Spezifikation der Enumerationen und der Regular Expressions möglich.

Über den MessagePactBuilder</em werden neben der definierten Payload weitere Metainformationen angegeben. Die Methode given erwartet den Namen des Provider States, und die Methode expectsToReceive befüllt man optimalerweise mit dem Namen des Events. Die Header können als Key Value Map spezifiziert werden.

Als Nächstes wird die eigentliche Consumer-Test-Methode zur Verifikation des eigenen PACT-Vertrags zum definierten Provider State implementiert (Listing 4). Über den Parameter pactMethod der Annotation @PactTestFor wird die PACT-Vertrag-Methode angegeben, die verifiziert werden soll. Mit providerType kann der API-Typ als synchron oder asynchron festgelegt werden.

@Test
@PactTestFor(
    pactMethod = "createPersonWithBirthdayPact",
    providerType = ProviderType.ASYNCH,
    pactVersion = PactSpecVersion.V3)
void verifyPersonWithBirthdayPact(MessagePact messagePact) {
    
  new ConsumerVerifier("PersonChanged", "PersonWithBirthday")
    .verify(messagePact);
}

Sobald die Consumer-Verifikation erfolgreich implementiert wurde und der Consumer-Test mit dem Maven-PACT-Plug-in gestartet wurde, wird der PACT-Vertrag für den angegebenen Provider State erstellt und im Maven-Target-Verzeichnis als JSON-Datei abgelegt (Listing 5). Üblicherweise ist das target/pacts. Die Datei wird immer nach den angegebenen Consumers und Providern benannt.

{
  "consumer": {
    "name": "greeting-service-person-consumer"
  },
  "messages": [
    {
      "contents": {
        "birthday": "1984-08-06",
        "email": "[email protected]",
        "forename": "Helena",
        "id": 1,
        "surname": "Adam"
      },
      "description": "PersonChanged",
      "matchingRules": {
        "body": {
          "$.birthday": {
            "combine": "AND",
            "matchers": [
              {
                "date": "YYYY-MM-dd",
                "match": "date"
              }
            ]
          },
          "$.email": {
            "combine": "AND",
            "matchers": [
              {
                "match": "type"
              }
            ]
          },
          "$.forename": {
            "combine": "AND",
            "matchers": [
              {
                "match": "type"
              }
            ]
          },
          "$.id": {
            "combine": "AND",
            "matchers": [
              {
                "match": "integer"
              }
            ]
          },
          "$.surname": {
            "combine": "AND",
            "matchers": [
              {
                "match": "type"
              }
            ]
          }
        }
      },
      "metaData": {
        "action": "create",
        "contentType": "application/json",
        "event": "PersonChanged",
        "kafka_topic": "person",
        "providerState": "PersonChanged"
      },
      "providerStates": [
        {
          "name": "PersonWithBirthday"
        }
      ]
    }
  ],
  "metadata": {
    "pact-jvm": {
      "version": "4.3.6"
    },
    "pactSpecification": {
      "version": "3.0.0"
    }
  },
  "provider": {
    "name": "person-service-person-producer"
  }
}

In dem PACT-Vertrag ist das in Listing 3 definierte Format in der PACT-eigenen Notation zu erkennen.

ConsumerVerifier

Die Consumer-Verifikation des PACT-Vertrags findet in unserem Beispiel mit Hilfe von ConsumerVerifier statt. Hierbei handelt es sich um eine eigens in Listing 2 definierte, nach dem Referenz-Event-Format ausgelegte Standard-Helper-Implementierung (Listing 6), die allgemein für Consumer-Tests genutzt werden kann. Damit ist die individuelle Implementierung der Verifikation obsolet, was ein weiterer Vorteil dieses Ansatzes ist, da wir dadurch sehr schlanke CDC-Tests erhalten.

public ConsumerVerifier(final String eventName, final String providerState) {
  this.eventName = eventName;
  this.providerState = providerState;
  mapper = new ObjectMapper();
  final Path eventPath = EVENTS_PATH.resolve(eventName)
  .resolve(providerState + ".json");
 
  final JsonNode event;
  try {
    event = mapper.readTree(eventPath.toFile());
  } catch (final IOException e) {…}
  header = new HashMap<>();
 
  final JsonNode eventHeader = event.get("header");
  eventHeader
    .fieldNames()
    .forEachRemaining(name -> 
    header.put(name, eventHeader.get(name).textValue()));
  payload = event.get("payload");
}

Zur Verifikation erzeugt uns das PACT-Framework aus dem MessagePact-Objekt eine gültige Payload und die dazugehörigen Header. Diese Informationen haben wir in Listing 2 in unseren Test-Resources als JSON-Datei abgelegt.

Der ConsumerVerifier liest nun die Referenzdatei aus und trennt Header und Payload voneinander. Die Daten aus MessagePact und Referenzdatei werden jeweils mit dem Jackson ObjectMapper in JsonNodes gelesen. Mit Hilfe von AssertJ (Listing 7) lassen sich dann beide Werte (actual vs. expected) sehr einfach vergleichen (Schritt 1.4 aus Abb. 6).

public void verify(final MessagePact messagePact) {
  final byte[] pactMessage = 
  messagePact.getMessages().get(0).contentsAsBytes();
  final Map<String, Object> pactMetadata = 
  messagePact.getMessages().get(0).getMetadata();
  try { 
    assertThat(mapper.readTree(pactMessage)).isEqualTo(payload);
  } catch (IOException e) {…}
  assertThat(pactMetadata).isEqualTo(header);
}

Damit ist der Consumer-Test fertig und kann mit einem einfachen Befehl auf dem zentralen PACT-Broker zur Verifikation veröffentlicht werden: mvn clean verify pact:publish. Ob das Event tatsächlich korrekt konsumiert werden kann, wird mit dem BDD-Test geprüft.

 

Provider

Der Provider muss zur Implementierung des Provider-Tests nicht auf den Consumer warten. Der Provider-Test kann jedoch erst dann einen Consumer-driven Contract verifizieren, wenn dieser auf einem PACT Broker veröffentlicht wurde. Zur Implementierung des Provider-Tests brauchen wir die Methoden aus Listing 8.

@State("PersonWithBirthday")
public void createPersonWithBirthdayProviderState() {
  providerVerifier = new ProviderVerifier("PersonChanged", "PersonWithBirthday");
}
 
@PactVerifyProvider("PersonChanged")
public MessageAndMetadata personCreatedEvent() throws IOException {
  return providerVerifier.toMessageAndMetadata();
}
 
@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider.class)
void testTemplate(PactVerificationContext context) {
  providerVerifier.verify(Person.class);
  context.verifyInteraction();
}

Eine Methode, um den Provider State zu definieren: Diese wird mit @State und dem Namen des Provider States annotiert. In der Methode wird der ProviderVerifier mit den Parametern für Event und Provider State initiiert. Der ProviderVerifier funktioniert ähnlich wie sein Pendant, der ConsumerVerifier. Das heißt, er liest eine JSON-Datei anhand von Event und Provider State aus den Test-Resources.

Die zweite Methode, annotiert mit @PactVerifyProvider und dem Event als Parameter, dient dazu, die Daten für den Abgleich mit dem PACT zur Verfügung zu stellen. Dank des Ansatzes kann mit Hilfe der ProviderVerifier hier eine starke Vereinfachung durch Standardisierung erzielt werden.

Die Methode testTemplate führt nun die Prüfung des PACT aus. Zuerst wird geprüft, ob die Daten aus Listing 9 als PACT-Referenzdatei in die passende Java-Klasse gelesen werden können. Danach werden die Daten mit dem PACT abgeglichen. Nebenbemerkung: Wie in Listing 9 zu sehen, liefert der Provider ein weiteres Feld phone, das von dem Grüße-Consumer nicht gelesen wird.

{
  "header": {
    "action": "create",
    "contentType": "application/json",
    "event": "PersonChanged",
    "kafka_topic": "person",
    "providerState": "PersonChanged"
  },
  "payload": {
    "id": 1,
    "surname": "Adam",
    "forename": "Helena",
    "email": "[email protected]",
    "birthday": "1984-08-06",
    "phone": "+49 1234 56789"
  }
}

Der Provider kann nun seine Tests gegen den zentralen PACT Broker laufen lassen und verifiziert so stets die aktuelle, veröffentlichte Version des CDC-Vertrags:

mvn verify -Dpact.provider.version=1.0-SNAPSHOT 
  -Dpact.verifier.publishResults=true

Das Ergebnis sollte dann in etwa aussehen wie in Abbildung 9.

Abb. 9: PACT Broker GUI

Damit haben wir bis hierhin sichergestellt, dass das vom Consumer erwartete Schema des Austauschformats auf Consumer- und Provider-Seite zueinander passen.

Behavior-driven-Development-Test

Das hilft uns aber noch lange nicht beim Ablösen der externen E2E-Tests. Also werfen wir einen Blick darauf, wie wir unsere fachlichen Tests in Angriff nehmen. Im Optimalfall haben wir die Schnittstelle definiert und mit PACT getestet und damit schon ein fertiges Event als Beispiel vorliegen. Das nehmen wir zur Hand und schreiben erst einmal einen Test dafür (Listing 10).

Feature: Birthday Greeting
 
  Scenario: consume person with birthday event and send birthday greeting
    Given event PersonChanged with Provider-State PersonWithBirthday
    When event is published and consumed
    And I could greet the person with the email [email protected]
    Then Birthday greeting goes to "Helena Adam"

Mit Hilfe des Cucumber Frameworks [4] definieren wir ein paar einfache Schritte, in denen das Event veröffentlicht und verarbeitet und im Anschluss ein Gruß bei unserem Service angefragt wird. Lösen wir die einzelnen Schritte einmal nacheinander auf: Im ersten Schritt geben wir das Event und den Provider State vor: Given event PersonChanged with Provider-State PersonWithBirthday.

@Given("event {word} with Provider-State {word}")
public void eventWithProviderState(String event, String providerState) throws IOException {
  var eventPath = EVENTS_PATH
                  .resolve(event)
                  .resolve(providerState + ".json");
  var mapper = new ObjectMapper();
  var eventJson = mapper.readTree(eventPath.toFile());
  payload = eventJson.get("payload");
  header = eventJson.get("header");
}

In Listing 11 sehen wir die Logik dahinter. Über die @Given-Annotation registrieren wir unseren Schritt in Cucumber. Durch die zwei Platzhalter {word} holen wir uns die Parameter für unsere Methode: event und providerState. Anhand der Parameter identifizieren wir die JSON-Datei, die wir schon aus Listing 2 kennen. Sie enthält die Payload und die Header für den notwendigen Event. Mit Hilfe des Jackson ObjectMappers lesen wir nun die JSON-Datei ein, um sie anschließend separat als payload und header zu speichern.

Weiter geht es mit When event is published and consumed. Listing 12 zeigt uns, wie wir mit @When den Schritt registrieren und darin das Event an unseren Kafka Broker schicken. Die Methode createKafkaProducer() erstellt uns einen einfachen Kafka Producer, den wir direkt verwenden, um den ProducerRecord abzusenden. Den Value des Records erstellen wir mit JacksonNode.toString(), die Methode getHeader() wandelt die JsonNode header in ein Set mit Kafka-Headern um.

Damit die Nachricht nun von unserem Service verarbeitet werden kann, warten wir 500 Millisekunden. Eine elegantere Variante wäre, anhand von Metriken zu prüfen, wann ein Event verarbeitet wurde.

@When("event is published and consumed")
public void eventIsPublishedAndConsumed() throws InterruptedException {
  createKafkaProducer()
    .send(
      new ProducerRecord<>(
        "person", 0, "person", payload.toString(), getHeader()));
  Thread.sleep(500);
}

Jetzt sind wir so weit, dass wir mit And I could greet the person with the email [email protected] den Gruß von unserem Service auslesen können.

@And("I could greet the person with the email {word}")
public void greetPersonWithEmail(String email) {
  response = given().queryParam("email", email)
            .when().get("/greeting").body().prettyPrint();
}

Listing 13 registriert dafür einen weiteren BDD-Schritt mit @And. Um den REST Endpoint aufzurufen, verwenden wir REST-assured und speichern die Rückgabe in response. Schließlich prüfen wir mit dem Schritt Then Birthday greeting goes to “Helena Adam”, ob es sich um einen Geburtstaggruß handelt und ob er an die richtige Person gerichtet wurde. AssertJ wird bemüht, um zu prüfen, ob die >response auch dem Gruß an name entspricht.

@Then("Birthday greeting goes to {string}")
public void birthdayGreetingGoesTo(String name) {
  assertThat(response).isEqualTo("Happy Birthday " + name + "!");
}

Nun kann mit der Implementierung der Logik begonnen werden, bis der Test auf Grün springt. Damit sind die fachlichen Anforderungen an den Bounded Context Grüße erfüllt und getestet. Ähnlich wird der BDD-Test des Bounded Contexts Personen implementiert.

Das hier dargestellte Beispielprojekt kann auf GitLab zur besseren Nachvollziehbarkeit angesehen werden [6].

Fazit

Durch das Verbinden von CDC und BDD über eine Quelle (Listing 2) haben wir nun die Möglichkeit, die Tests unserer Logik auf Basis von CDC zu erstellen und kontinuierlich zu testen. Wenn sich also nun ein Provider State des PACT dergestalt ändert, dass die Schnittstelle nicht mehr funktionieren würde, merken wir das in unseren Tests. So ist die Integration von Schnittstelle und Logik gesichert und wir können bereits während der Entwicklung sehen, ob eine Schnittstelle später in Produktion fehlerhaft wäre, und dies sofort korrigieren, ohne dabei direkt abhängig von anderen Teams zu sein.

Wenn wir aus CDC-Tests bereits unterschiedliche fachliche Fälle abbilden können, stellt sich die Frage: Wieso brauchen wir BDD-Tests noch? Wieso testen wir die fachliche Logik nicht mit den CDC-Tests direkt? Zum einem: Nicht für alle fachlichen Funktionen sind CDC-Tests notwendig – hierfür werden laut der Testpyramide trotzdem fachliche Black-Box-Tests benötigt. Das bringt uns zu dem Punkt, dass wir fachliche Verhaltenstests (BDD-Tests) auf jeden Fall einsetzen. Zum anderen ist hier eine klare Trennung erreicht. Die fachliche Logik wird mit den BDD-Tests beschrieben (User Stories, Akzeptanzkriterien etc.) und getestet und die Schnittstellen mit den dann sehr schlanken CDC-Tests. Dies erhöht die Wartbarkeit.

Zu beachten ist, dass dieser Ansatz vorerst an den unternehmensinternen Schnittstellen angewendet werden kann. Das PACT Framework bietet für den zentralen Broker noch keine Authentifizierungs- und Autorisierungsfunktionalität. Daher ist an dieser Stelle das vollkommene Vertrauen aller Beteiligten notwendig. Möglich ist es, den PACT Broker hinter einem Reverse-Proxy-Server bereitzustellen und so den Zugang abzusichern. Auch ist es möglich, mehrere PACT Broker in unterschiedlichen Zonen zu betreiben. Über die Parameter kann einem PACT-Test der Ziel-PACT-Broker mitgeteilt werden, sodass damit auch mehrere unterschiedliche PACT Broker in einem Testprojekt möglich wären. So gesehen wäre die Bereitstellung eines dedizierten PACT Brokers für eine externe Schnittstelle ebenfalls möglich. Dieser sollte dann für die beiden Schnittstellenparteien zugänglich gemacht werden.

Letztendlich erreichen wir mit dem Agile-driven Integrationstest eine vollständige Testabdeckung aller Geschäftsfallkonstellationen in der fachlichen Logik und über die Schnittstellen – und das mit den automatisierten Tests, die jedes DevOps-Team für sich selbst implementiert und in der eigenen CD Pipeline ausführen kann.

Der PO dazu: „Wow – in dem Fall müssen wir unser DevOps-Team in der Testkompetenz verstärken, um alle diese Fälle abbilden zu können. Dass wir damit tatsächlich Continuous Delivery umsetzen können, das hätte ich nicht für möglich gehalten – aber es scheint tatsächlich umsetzbar zu sein. Ran an die Arbeit!“

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Links & Literatur

[1] https://pact.io/

[2] https://spring.io/projects/spring-cloud-contract

[3] https://entwickler.de/spring/consumer-driven-contracts-mit-spring-001

[4] https://cucumber.io/

[5] https://github.com/karatelabs/karate#readme

[6] https://gitlab.com/sidion/demo/2022/javamagazin/agiledrivenintegrationstests

The post Wie die CD Pipeline mit CDC und BDD die E2E-Tests ablöst appeared first on JAX.

]]>
Software-Architektur heute: „Es geht um Menschen, Technologien sind sekundär“ https://jax.de/blog/software-architecture-design/software-architektur-heute-es-geht-um-menschen-technologien-sind-sekundaer/ Thu, 25 Oct 2018 11:26:39 +0000 https://jax.de/?p=65554 Software-Architektur galt lange als die Disziplin, um in Software-Projekten für einen kohärenten Zusammenhang zu sorgen: Stabilität, Sicherheit, Planbarkeit stand im Vordergrund. Wir haben uns mit Henning Schwentner darüber unterhalten, wie sich dieses Bild verändert hat und welche Rolle Trends wie DevOps und DDD dabei spielen.

The post Software-Architektur heute: „Es geht um Menschen, Technologien sind sekundär“ appeared first on JAX.

]]>
W-JAX: Software-Architektur galt lange als die Disziplin, in Software-Projekten für einen kohärenten Zusammenhang zu sorgen: Es geht darum, Stabilität und Langlebigkeit zu gewährleisten, Standards einzuführen, für Sicherheit zu sorgen, Pläne und Dokumentationen zu erstellen, etc. Heute wird Software-Architektur oft auch anders diskutiert, und zwar im Sinne eines Change Management: Architekturen sollen flexibel, erweiterbar, austauschbar sein. Wie siehst du dich: Wie viel in deiner Arbeit ist Kirchenbauer, wie viel Change Manager?

Henning Schwentner: Beide Rollen sind untrennbar. Stabilität im Großen bekommen wir über Flexibilität im Kleinen. Das Interessante ist ja, dass insbesondere die großen Systeme, weil sie am längsten leben, gerade deshalb am flexibelsten sein müssen. Deswegen finde ich es gut, dass Trends wie Microservices und Self-Contained Systems die (im Prinzip alten) Ideen, wie man ein System vernünftig modularisiert, im Mainstream ankommen lassen. Der wichtigste Punkt ist: Wir wollen nicht ein großes Domänenmodell haben, sondern mehrere kleine. Die kleinen Domänenmodelle können dann verständlich, beherrschbar und flexibel sein.

W-JAX: Wie schafft man es, den richtigen Mix aus Stabilität und Flexibilität zu finden?

Henning Schwentner: Erstens: sich immer und immer wieder klar machen, dass Software kein Selbstzweck ist. Software (und auch Softwarearchitektur) bauen wir nicht für uns selbst, sondern für den Fachexperten. Zweitens: nicht nach dem einen Domänenmodell streben, das alle Probleme auf einmal löst. Das wird nämlich viel zu groß, fehleranfällig und schwer verständlich. Interessanterweise also gleichzeig instabil und unflexibel. Stattdessen wollen wir mehrere kleine Modelle.

W-JAX: Im Zuge der DevOps-Bewegung erweitert sich das Bild des Software-Architekten noch um eine weitere Facette: Es geht nämlich nicht nur um Anwendungsentwicklung, sondern immer mehr auch darum, wie sich Anwendungen in einer Continuous-Delivery-Landschaft einbetten. „You build it, you run it“ heißt da das Stichwort. Wie hat die DevOps-Bewegung die Rolle des Software-Architekten verändert? Was musst du als Architekt heute anders machen, als früher, als man die Anwendungen noch einfach über den Zaun hin zum Ops-Team geworfen hat?

Henning Schwentner: Mir geht es da wie vielen: Ich freue mich, dass die unnatürliche Trennung von Entwicklung und Betrieb aufgehoben wird. Als Entwickler und Architekt wird man jetzt von Anfang an darauf fokussiert, nicht eine ausführbare Datei, sonderen laufende Software auszuliefern. Und nur die hat Wert für den eigentlich wichtigen Menschen – unseren Anwender.

W-JAX: Ein weiterer Trend ist aktuell, das Design einer Software stark an den fachlichen Domänen auszurichten. Neben DDD als Theorie erobern gerade Microservices-Architekturen die Praxis. Neben den technologischen Aspekten, die Domänen-fokussierte Anwendungen mit sich bringen, geht es hier zentral auch darum, die beteiligten Leute erst einmal in ein Boot zu holen: Fachexperten, Entwickler und natürlich auch die Geschäftsleitung und Anwender bzw. Kunden. Ist man da als Software-Architekt nicht eigentlich zu 80% Projektmanager? Wie hältst du das persönlich: Wie stark nimmst du die Rolle des Projektmanagers ein, wie viel konzentrierst du dich auf Technologien?

Henning Schwentner: Von Jerry Weinberg wissen wir: »No matter how it looks at first, it’s always a people problem.« Die erste Aufgabe jedes Menschen, der mit Softwareentwicklung beschäftigt ist, ob er nun Programmierer, Architekt, Projektmanager oder wie auch immer heißt, ist, mit anderen Menschen zu kommunizieren. Mit Computern zu kommunizieren kommt frühestens auf Platz zwei.

Technologien sind nicht unwichtig, aber sekundär. Frag dich mal selbst: Von wievielen Programmen und Apps, die du einsetzt, weißt du, welche Programmiersprache, Frameworks usw. darin verwendet wird? Ist dir das wichtig? Oder willst du lieber, dass die ihren Job machen?

Die Gemeinheit ist: In der Software-Entwicklung gilt das Anna-Karenina-Prinzip, d.h. ein Projekt kann nur dann erfolgreich sein, wenn alle Faktoren stimmen. Technologien kennen und beherrschen wird deshalb immer wichtig und nötig sein.

W-JAX: Auf der W-JAX wird es ein Lab zum Thema „Event Storming“ geben. Wie funktioniert Event Storming – und weshalb sollte man es machen?

Henning Schwentner: Die Grundaufgabe von Software ist, unseren Anwender bei seiner Arbeit zu unterstützen. Damit wir das tun können, müssen wir seine Arbeit (d.h. die Domäne) verstehen. Event Storming ist ein Werkzeug, das uns dabei hilft. Bewaffnet mit haufenweise Klebezetteln wird Wissen aufgebaut, ausgetauscht und vertieft. Es hilft uns dabei, eine gemeinsame Sprache, Kontextgrenzen und Domänenmodelle zu finden.

W-JAX: Welchen Trend findest du im Bereich der Software-Architektur momentan besonders spannend – und warum?
Henning Schwentner: Mir ist das Thema „Domäne verstehen, um die richtige Software zu bauen“ sehr wichtig. Neben Event Storming ist Domain Storytelling ein tolles Werkzeug dafür. Wir lassen die Anwender ihre Geschichte erzählen. Dabei zeichnen wir sie mit einer einfach zu verstehenden Bildsprache auf. Die entstehenden Bilder (die sogenannten Domain Stories) verwendet man, um direkt rückzukoppeln, ob wir die Anwender richtig verstanden haben. Mehr Infos gibt’s auf domainstorytelling.org und hoffentlich bald in dem Buch, das Stefan Hofer und ich gerade darüber schreiben.

W-JAX: Vielen Dank für dieses Interview!

 

Cheat-Sheet: Die neuen JEPs im JDK 12


Unser Cheat-Sheet definiert für Sie, wie die neuen Features in Java 12 funktionieren. Von JEP 189 „Shenandoah“ bis JEP 346 „Promptly Return Unused Committed Memory from G1“ fassen wir für Sie zusammen, was sich genau ändern wird!

Cheat-Sheet sichern!

 

 

The post Software-Architektur heute: „Es geht um Menschen, Technologien sind sekundär“ appeared first on JAX.

]]>
Jenkins Tutorial: So baut man einen Jenkins-Cluster https://jax.de/blog/software-architecture-design/jenkins-cluster-verteilung-eines-deployment-servers/ Thu, 28 Jun 2018 12:11:07 +0000 https://jax.de/?p=64220 Sobald der Deployment-Prozess mit Jenkins mehrere Stufen annimmt und zusätzlich noch automatisierte Tests in größeren Projekten dazukommen, muss man sich mit dem Thema Skalierung auseinandersetzen. Erschwerend kann hinzukommen, dass mehrere Teams mit Jenkins arbeiten und die fertigen Applikationen für mehrere Kunden in unterschiedlichen produktiven Umgebung bereitstellen sollen. Eine Möglichkeit, Jenkins zu skalieren, ist der Aufbau eines Jenkins-Clusters.

The post Jenkins Tutorial: So baut man einen Jenkins-Cluster appeared first on JAX.

]]>

von Jörg Jackisch

Sobald wir mit mehreren Teams arbeiten und mehrere Projekte abwickeln und bereitstellen, müssen wir an die Skalierung von Jenkins denken. Dies lässt sich aber mit einer Standardinstallation von Jenkins nur sehr schwer umsetzen, wenn überhaupt. Denn die einzelnen Build-Prozesse auf dem überforderten Jenkins-Server nehmen immer mehr Zeit in Anspruch, was bei der produktiven Zeit der Entwickler und Tester verloren geht. Bei weiter steigender Last auf dem Server wird dieser außerdem zunehmend instabil und fällt öfter aus. Der Frust sowohl bei den Entwickler- als auch Testteams und Teamleitern bis hin zu den Managern ist dann groß. Dieser Frust durch instabile Infrastrukturkomponenten schlägt sich schließlich auch in der Produktivität und Qualität des gesamten Projekts nieder. Das wollen wir natürlich mit allen Mitteln vermeiden. Also muss Skalierung her.

Jenkins skalieren – wie geht das?

Jenkins lässt sich sowohl horizontal als auch vertikal skalieren. In der vertikalen Skalierung kann man den Server mit mehr Hardwareressourcen ausstatten, damit die Jenkins-Applikation performanter ist. Dazu gibt es mehrere Möglichkeiten. Die einfachste ist, die Anzahl der Prozessoren und den Arbeitsspeicher zu erweitern. Auch die I/O-Performance kann man verbessern, indem man bei den Speichermedien zu SSD wechselt und die unterschiedlichen Komponenten mit Fibre Channel verbindet, wenn das Serversystem es zulässt. Bei der vertikalen Skalierung stößt man aber häufig auf technische Grenzen. Hinzu kommt, dass auch die eingesetzten Werkzeuge und Programme, die innerhalb von Jenkins genutzt werden, Multithreading unterstützen müssen, sonst bringt Skalierbarkeit im vertikalen Sektor nicht den gewünschten Effekt. Außerdem bringt das Aufrüsten der Serverhardware keine Ausfallsicherheit mit.

Lesen Sie auch: Grundkurs Microservices: Warum Frameworks nicht genug sind

Da die vertikale Skalierung an Grenzen stößt und man damit keine nachhaltige und langfristige Steigerung erzielen kann, sollte man rechtzeitig die horizontale Skalierbarkeit prüfen und umsetzen. Die horizontale Skalierung beschreibt dabei das Clustering der Anwendung. Jenkins setzt dabei auf eine Master-Agent-(Slave-)Skalierung. Ziel ist es, die Anwendung auf viele Server zu verteilen. Dabei gibt es zwei Alternativen: Zum einen spricht man von einem High-Performance-Computing-(HPC-)Cluster, das dazu dient, die Rechenkapazität zu erhöhen. Zum anderen gibt es High-Available-(HA-)Cluster, die größeren Wert auf die Ausfallsicherheit des Systems legen. Man unterscheidet grundsätzlich zwischen Hardware- und Software-Clustern. In beiden Kategorien gibt es unterschiedliche Methoden der Umsetzung.

 

Das Jenkins-Cluster in der Theorie

Bei der Einführung eines Jenkins-Clusters beginnt man mit der Einrichtung und Installation der Stand-alone-Variante von Jenkins. Er bildet die Basis des Clusters, verwaltet die gesamte Build-Umgebung und führt über seinen eigenen Executor die Build-Prozesse aus. Der erste Schritt ist es, den Jenkins-Server vertikal zu skalieren, zusätzlich passt man die JRE an. Damit wird man vorübergehend wieder eine stabile Infrastruktur erreichen.

Verschaffen Sie sich den Zugang zur Java-Welt mit unserem kostenlosen Newsletter!

In der Regel ist es allerdings nicht zu empfehlen, die Stand-alone-Variante in einer großen und produktiven Umgebung einzusetzen. Das sollte nur der erste Schritt bei der Einführung von Jenkins sein, denn hier kommt ein großes Sicherheitsrisiko hinzu. Eine Webapplikation führt die Prozesse mit dem Benutzer aus, mit dem der Jenkins-Server gestartet wurde. Meistens ist das ein Benutzer, der erhöhte Rechte besitzt. Das bedeutet, dass der Benutzer der Webapplikation Zugriff auf alle Ressourcen besitzt. Wenn ein Angreifer also Schadcode einschleusen kann, hat er Zugriff auf private oder geheime Daten.

Um vom einzelnen Server zu einer verteilten Lösung zu kommen, bietet Jenkins die Möglichkeit, mehrere sogenannte Worker-Prozesse auf unterschiedliche Server zu verteilen. Dabei bleibt der erste einzelne Server der Master und die weiteren dienen als sogenannte Agents. Dieses Prinzip nennt man Master/Agent Clustering. Dabei verwaltet der Masterserver die komplette Umgebung. Er verteilt einerseits die Deployment-Jobs, andererseits überwacht er, ob die jeweiligen Agents noch verfügbar sind. Der Master dient lediglich dazu, Informationen zu sammeln, Prozesse zu verwalten und letztendlich als grafische Oberfläche des gesamten Schwarms. Die Agents oder Worker sind Server, die nur vom Master Build-Prozesse entgegennehmen und bearbeiten.
Die Master/Agent-Methode gehört zu den HPC-Cluster-Methoden. Die Verteilung der Build-Jobs wird dabei auf alle vorhandenen Worker-Prozesse ausgelagert. Die Verteilung der Jobs lässt sich allerdings auch so konfigurieren, dass bestimmte Abläufe und Prozesse nur auf bestimmten Agents ausgeführt werden.

Damit der Master mit seinen Agents kommunizieren und nachvollziehen kann, ob sie noch verfügbar sind, kann man drei unterschiedliche bidirektionale Kommunikationsmethoden verwenden: Kommunikation per SSH (Secure Shell), JNLP-TCP (JNLP, Java Native Launch Protocol) oder JNLP-HTTP (Java Webstart). Falls es über keinen der vorhandenen Konnektoren möglich ist, eine Verbindung zwischen den Agents und dem Master aufzubauen, kann man auch ein eigenes Skript entwickeln. Als Programmiersprachen für eigene Konnektoren eignen sich vor allem Groovy oder Grails, doch auch Implementierungen mit der Programmiersprache Python sind gängig.

Bei der Methode mit dem SSH-Konnektor fungiert jeder Agent als SSH-Server und der Jenkins-Master ist der SSH-Client; in der Regel via Port 22 mithilfe eines SSH-Schlüssels, der auf dem Agent erstellt wird. Man kann die Verbindung aber auch ohne Schlüssel aufbauen. Dann wird sich der Server wie bei einer gewöhnlichen SSH-Kommunikation mit einem Benutzernamen und einem Passwort anmelden. Es ist empfehlenswert, den Benutzer zu nehmen, mit dem auch der Jenkins-Master läuft. Agents, die auf Microsoft Windows basieren, lassen sich mithilfe des Programms Cygwin verbinden. Cygwin [1] ist eine Sammlung von Open-Source-Werkzeugen, die unter Windows Funktionalitäten eines Linux-Systems bereitstellen.

Jenkins bietet mit dem JNLP-HTTP- und dem JNLP-TCP-Konnektor zwei Varianten, mit denen man mithilfe der Java-internen Protokolle Master und Agent miteinander kommunizieren lassen kann. Um JNLP zu verwenden, arbeitet Jenkins mit der Java-Web-Start-Technologie. Das ist wahrscheinlich die einfachste Art und Weise, Agent und Master zu verknüpfen, da man auf dem Agent lediglich die Java-Applikation des Masters ausführen muss und so bereits die Verbindung aufgebaut hat.

 

Der Software Architecture Track auf der JAX 2020

 

Das Jenkins-Cluster in der Praxis

Nach der ganzen Theorie erstellen wir jetzt ganz praktisch mit Docker einen Jenkins-Schwarm. Bei diesem Beispielszenario verbinden wir auf unterschiedliche Art und Weise vier Agents mit einem Masterserver. Es gibt also insgesamt fünf Server. Der Masterserver basiert auf einem Debian Linux, ebenso wie drei der Agents. Zusätzlich wird ein Server hinzugenommen, auf dem Windows läuft. Auf dem Masterknoten des Jenkins-Schwarms läuft das Jenkins Backend. Eine solch heterogene Verteilung bringt den Vorteil, dass man bestimmte Schritte innerhalb des Deployment-Prozesses auf einem bestimmten Betriebssystem ausführen kann. Als Beispiel dienen hier automatisierte Browsertests: Ich möchte meine Java-EE-Applikation sowohl auf einem Windows-Betriebssystem mit Microsoft Edge als auch auf einem Linux-Betriebssystem mit Mozilla Firefox testen.

Basierend auf einem Debian Linux oder einer ähnlichen Linux-Distribution muss man folgende Befehle ausführen, um eine lauffähige Jenkins-Umgebung zu erstellen:

 

 $> apt-get update && apt-get upgrade

 

Mit apt-get update wird der Paketmanager APT anhand seiner Konfiguration aktualisiert und mit dem Kommando apt-get upgrade das Betriebssystem. Das sollte man stets vor einer Installation einer neuen Software durchführen. Mit dem Befehl $> apt-get install default-jre wget wird die JRE mit all ihren Abhängigkeiten installiert. Hinzu kommt auch das Programm Wget, mit dem Jenkins zur sources-list-Datei von APT hinzugefügt wird. Zum Hinzufügen der Sources für APT benutzt man die Zeilen aus der Dokumentation von Jenkins [2]:

 

$> wget -q -O - https://pkg.jenkins.io/debian/jenkins-ci.org.key | apt-key add -
$> sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
$> apt-get update
$> apt-get install jenkins

 

Letztendlich wird mit dem letzten Kommando der Jenkins-Server installiert. Mit Docker kann man diese Installation abkürzen und virtualisiert den Server innerhalb eines Containers. Das Image, das dabei benutzt wird, befindet sich online im Docker Hub. Mit einem Befehl wird ein Container mit Linux erzeugt, auf dem ein Jenkins-Deployment-Server läuft:

 

$> docker run  --name=Master –link=slave3:2222 -d -ti -p 8080:8080 -p 50000:50000 jenkins:latest /bin/bash

 

Mit den Parametern –p werden die Ports 8080 und 50000 vom Container auf dieselben Ports auf dem Host gemappt. Docker lädt dabei jeden Image-Layer des Docker-Files von Jenkins einzeln herunter und richtet eine lauffähige Umgebung ein. Dabei wird die Bash auf dem Server gestartet, und man kann sich mit dem Befehl docker attach und der Container-ID mit der Bash des Servers verbinden. Zum Starten des Servers führt man den Befehl zum Starten von Jenkins aus: $> java -jar /usr/share/jenkins/jenkins.war &.

Nachdem der Jenkins-Masterserver gestartet ist, kann man in der Docker-Toolbox mit der Tastenkombination STRG + P + Q den Server wieder detachen.

Egal mit welcher Variante der Masterserver eingerichtet wurde, navigiert man im Browser seiner Wahl auf die jeweilige IP-Adresse des Servers. Bei Docker ist das die IP-Adresse des Docker Containers, auf dem der Jenkins-Server läuft, jeweils mit dem Port 8080. Unter http://IP_ADRESSE:8080/ wird jetzt der Set-up-Screen von Jenkins dargestellt. Dort muss man das initiale Passwort eingeben, das bei der Installation angezeigt wurde.

Falls man es übersehen hat, findet man es unter /var/jenkins_home/secrets/initialAdminPassword auf dem Server. Nach dem Set-up kann man sich beim Masterserver anmelden. Zu diesem Zeitpunkt hat man einen Stand-alone-Server eingerichtet, auf dem man die jeweiligen Projekte konfigurieren und einrichten kann.
Zum Erstellen der Slaveserver benutzt man wieder ein Docker Image, das ein Standard-Debian-Betriebssystem bereitstellt. Zum Starten der Server wird folgender Befehl ausgeführt:

 

docker run -ti --name=Slave1 -d debian:latest /bin/bash
docker run -ti --name=Slave2 -d debian:latest /bin/bash
docker run -ti --name=Slave3 –hostname=slave3 -p 2222:22 -d debian:latest /bin/bash

 

Dieser Befehl lädt das aktuelle Debian Image aus dem Docker Hub herunter und startet es als Daemon im Hintergrund (-d = detached). Mit dem Befehl docker ps –format “table {{.ID}}\t{{.Image}}\t{{.Names}}” sollten jetzt alle vier Linux-Server sichtbar sein.

Um die Linux Agents mit dem Masterserver zu verbinden, werden zuerst zwei der Linux-Server per JNLP verbunden. Einer wird per SSH die Verbindung mit dem Masterserver aufbauen. Im Jenkins-Master richtet man für die zwei weiteren Linux-Server jeweils einen weiteren Agent bzw. Knoten ein und lädt dann die Datei slave.jar auf den jeweiligen Slaveserver herunter. Die Knoten sollten sogenannte permanente Knoten sein, und als Startmethode wählt man LAUNCH AGENT VIA JAVA WEB START. Nach der Einrichtung der Agents wird dieser Befehl angezeigt:

 

Java -jar slave.jar -jnlpURL URL_DES_JENKINS_SERVERS -secret SECRET 

 

Als Parameter für die slave.jar werden die Verbindungsparameter benötigt. Der erste ist –jnlpURL, er teilt der slave.jar mit, wo sich der Server befindet. Der zweite ist ein Secret zur Verbindung (-secret). Den kompletten Befehl, um den Agent zum Master zu verbinden, sieht man, wenn man im Jenkins Backend durch die Punkte JENKINS VERWALTEN | KNOTEN VERWALTEN | SLAVE1 navigiert. Das Gleiche gilt für den zweiten Agent: JENKINS VERWALTEN | KNOTEN VERWALTEN | SLAVE2.

Der dritte Linux-Server kommuniziert per SSH mit dem Masterserver. Dazu geht man wieder mit dem Befehl docker attach in die Bash des Slaves und lädt als Erstes die slave.jar-Datei herunter. Ich benutze dafür Wget und verschiebe die Datei danach in das /bin-Verzeichnis. Zusätzlich muss man hier einen SSH-Server installieren, unter Debian Linux mit dem Befehl apt-get install openssh-server. Im nächsten Schritt legt man einen neuen Benutzer an und gibt ihm ein Passwort.

$> groupadd jenkins
$> useradd -g jenkins -d /home/jenkins -s /bin/bash jenkins
$> passwd jenkins

 

Nun erzeugt man einen SSH-Schlüssel, hinterlegt ihn auf dem Jenkins-Server und testet von ihm aus den Log-in.
Im Backend des Masterservers wird auch der dritte Knoten angelegt. Als Startmethode wird hier allerdings LAUNCH AGENT VIA EXECUTION OF COMMAND ON THE MASTER ausgewählt. Nach dieser Auswahl öffnet sich ein neues Konfigurationsfeld, in das man das Startkommando eintragen kann:

ssh -p 2222 [email protected] java -jar /bin/slave.jar

 

Alternativ bietet Jenkins auch die Startmethode STARTE SLAVE ÜBER SSH. Dort kann man den Hostnamen, Port, Benutzernamen und das Passwort eingeben. Der Jenkins-Server wird sich dann per SSH-Client auf den Server verbinden und ihn als Slave starten. In der Übersicht unter JENKINS VERWALTEN | KNOTEN sollten nun alle Agents dargestellt werden und online sein.

Zur Installation des Windows-Servers empfiehlt es sich, eine virtuelle Maschine zu benutzen. Dann folgt man den Installationsanweisungen des Set-ups von Microsoft. Man sollte allerdings bei der Auswahl der Version darauf achten, was man tatsächlich auf diesem Server ausführen möchte. In diesem Szenario empfiehlt sich Windows in der Clientversion 10, da man dort den Browser Edge zur Verfügung hat. Somit kann man seine Applikation auf Edge testen. Da man keine Serverversion des Betriebssystems benutzt, kann man auch weitere Versionen des Internet Explorers installieren.

Lesen Sie auch: Das Beste aus beiden Welten – Objektfunktionale Programmierung mit Vavr

Unter Windows benutzt man nahezu immer den Verbindungsaufbau per JNLP-HTTP, da dies eine sehr einfache Variante ist, Slaves mit dem Java Web Start zum Jenkins-Masterserver hinzuzufügen. Um den Windows-Server zum Schwarm als Agent hinzuzufügen, verbindet man sich per Remote Desktop zum Windows-Server. Auf diesem benutzt man einen Browser und navigiert zum Jenkins-Master. Nachdem man sich dort eingeloggt hat, klickt man in der Jenkins-Oberfläche auf den Menüpunkt JENKINS VERWALTEN | KNOTEN VERWALTEN und wählt den Menüpunkt NEUER KNOTEN, um einen neuen Agent hinzuzufügen. Im nächsten Fenster füllt man das Textfeld mit dem Namen seines Knotens aus, hier MS Windows Server, und wählt PERMANENT AGENT. Nach der Bestätigung kommt man zur Konfiguration des Knotens.

Das wichtigste Feld bei diesem Formular ist die Startmethode. Dort wählt man in unserem Beispiel LAUNCH AGENT VIA JAVA WEB START. Nach dem Speichern dieses Formulars kommt man zurück zur Übersicht der Knoten. Dort ist nun unser angelegter Windows-Knoten verfügbar, der allerdings mit einem roten Kreuz markiert ist, was bedeutet, dass der Agent nicht verbunden ist. Mit einem Klick auf den Agent kommt man zur Übersichtsseite, wo man mit Klick auf den Java-Webstart-Launch-Knopf die JNLP-Verdingung herstellen kann. Dazu wird eine slave-agent.jnlp-Datei heruntergeladen, die gestartet werden muss, und schon ist der Agent verbunden.

Quarkus-Spickzettel


Quarkus – das Supersonic Subatomic Java Framework. Wollen Sie zeitgemäße Anwendungen mit Quarkus entwickeln? In unserem brandaktuellen Quarkus-Spickzettel finden Sie alles, was Sie zum Loslegen brauchen.

 

Jetzt herunterladen!

 

Fazit

Zu Beginn eines Projekts sollte die Planung für den Deployment-Prozess und die Skalierung in der Architekturplanung enthalten sein. Man sieht, wie schnell und einfach man mit Docker einen Jenkins-Schwarm erstellen kann. Läuft der Cluster in einer produktiven Umgebung, kann langfristig und nachhaltig eine qualitativ hochwertige Software garantiert werden.

Links & Literatur
[1] Cygwin: https://www.cygwin.com
[2] Jenkins: https://wiki.jenkins-ci.org/display/JENKINS/Installing+Jenkins+on+Ubuntu

 

The post Jenkins Tutorial: So baut man einen Jenkins-Cluster appeared first on JAX.

]]>
Fluch oder Segen? – Fünf Thesen zu Scrum https://jax.de/blog/devops-continuous-delivery/fluch-oder-segen-fuenf-thesen-zu-scrum/ Wed, 18 Oct 2017 09:06:51 +0000 https://jax.de/?p=60995 Scrum hielt als Vorgehensmodell für das Projektmanagement in den letzten Jahren in vielen Unternehmen Einzug. Ganz so toll, wie von vielen propagiert, läuft es aber nicht immer damit. Das motivierte Bernhard Löwenstein dazu, fünf Thesen zu formulieren, die in Folge von fünf Scrum-Experten auf Herz und Nieren geprüft werden.

The post Fluch oder Segen? – Fünf Thesen zu Scrum appeared first on JAX.

]]>

THESE 1

Bernhard Löwenstein: Die meisten Unternehmen machen nicht Scrum, sondern verwenden lediglich einen Scrum-ähnlichen Prozess. So werden beispielsweise immer wieder die Daily Stand-ups oder die Retrospektiven nicht gemacht. Dadurch geht aber manch großer Vorteil verloren, dessen sich die Unternehmen aber gar nicht bewusst sind.

Frank Düsterbeck: Diese These unterstütze ich. Die Sätze „Das Beste aus beiden Welten“ oder „Wir haben Scrum auf unsere Belange zugeschnitten“ höre ich relativ häufig. Ursache hierfür ist nicht selten die Angst, mit allen Konsequenzen von Scrum umgehen zu müssen – angefangen bei den Rollen bis zum potenziell auslieferbaren Produkt. Scrum ist ein Problemsensor, und als solcher zeigt es alle Hindernisse sehr schnell auf: in der Organisation, bei den Menschen und deren Interaktionen, bei den Prozessen und Praktiken und in den Technologien. Das muss man aushalten können und an die Ursachen ran. Die konsequente Abarbeitung der Hindernisse zur Optimierung des Wertstroms schaffen aber nur die wenigsten. Den Sensor beispielsweise durch das Weglassen der Retros zu verlieren, ist manchmal viel einfacher, bedeutet aber nichts anderes als die Ursachen zu ignorieren und das wirkliche Potenzial von Scrum nicht zu nutzen.

 

THESE 2

Bernhard LöwensteinBei den früher klassischen Vorgehensmodellen gab es immer einen großen Aufschrei der Entwickler, wenn Codeteile im Nachhinein geändert werden mussten. Bei Scrum sind in den meisten Projekten die zwischendurch durchgeführten Codeadaptierungen noch wesentlich umfangreicher, allerdings wird dies nicht bewusst wahrgenommen. Um es mit einer Metapher auszudrücken: Am Ende steht zwar ein fertiges Haus, aber auf dem Weg dahin hat man oft in Summe auch ein ganzes Haus wieder eingerissen.

Frank Düsterbeck: Das sehe ich anders. Scrum – wenn ernst genommen – führt die Entwickler dahin, von vornherein Wert zu schöpfen. Projekte, in denen erst das gesamte Datenmodell und dann die Stammdaten ausprogrammiert werden, sollten demnach der Vergangenheit angehören. Gerade in letzteren Projekten war es eher notwendig, größere Teile oder sogar alles noch mal über den Haufen zu werfen. Insbesondere, weil man im Vornherein eben nicht alles weiß. Eine iterative, evolutionäre Entwicklung wie mit Scrum schützt vor Fehlentwicklungen und führt daher nicht zwangsweise zu mehr Codeadaptierung.

Scrum – wenn ernst genommen – führt die Entwickler dahin, von vornherein Wert zu schöpfen.

 

Der Agile & Culture Track auf der JAX 2018

THESE 3

Bernhard LöwensteinEs wird bei Scrum vorab kaum mehr etwas spezifiziert, sondern die Produktivität wird vorrangig daran gemessen, wie schnell die Entwickler loslegen und etwas Herzeigbares liefern. Das endet oftmals in Code, der deutlich eleganter geschrieben hätte werden können, oder in Architekturen, die sich später als suboptimal herausstellen.

Frank Düsterbeck: Das sehe ich anders. In Scrum ist es durchaus sinnvoll und notwendig, mit geeigneten Praktiken (z. B. Personas, Story Mapping) vorab Erkenntnisse zu gewinnen und das Product Backlog für die ersten Sprints vorzubereiten, um nicht ins Blaue loszucoden. Natürlich ist es wichtig, schnell Wert zu schöpfen. Dies sollte immer das Ziel sein. Denn nur dann werden wir die Hindernisse, die der Wertschöpfung entgegenstehen, konsequent eliminieren. Das hat aber meiner Meinung nach keinen negativen Einfluss auf die Codequalität. Diese hängt eher vom Selbstverständnis der Entwickler, dessen Manifestation in der Definition of Done und der konsequenten Einhaltung dieser ab. Gerade in rein sequenziellen Projektumfeldern, in denen Refactoring kein kontinuierlicher Prozess ist, leidet die innere Qualität stärker als in agilen Umfeldern, in denen Refactoring kontinuierlich stattfindet.

THESE 4

Bernhard LöwensteinEntwickler lieben Scrum vor allem deshalb, da die lästige Arbeit des Dokumentierens auf ein Minimum reduziert wird und das Programmieren im Vordergrund steht. Das erzeugt den Eindruck, dass man stets produktiv ist, auch wenn dies gesamtheitlich betrachtet nicht so der Fall ist.

Frank Düsterbeck: Längst nicht alle Entwickler lieben Scrum – ganz im Gegenteil! Durch die hohe Transparenz können sich Menschen, die beispielsweise in einer Misstrauenskultur gearbeitet haben, stark unter Druck gesetzt fühlen und Scrum negativ empfinden. Es gibt eben keine Möglichkeit, sich zu verstecken, sondern Vertrauen und Offenheit sind die Basis des guten Miteinanders. Was die Dokumentation angeht, so macht Scrum dort keine Vorgaben. Das bedeutet: Auch in Scrum-Umfeldern kann viel Dokumentation anfallen (z. B. in der Medizintechnik). Die These ist eher eine beliebte Fehlinterpretation der agilen Werte und hat mit Scrum eigentlich gar nichts zu tun. 

In Scrum ist es durchaus sinnvoll und notwendig, mit geeigneten Praktiken (z. B. Personas, Story Mapping) vorab Erkenntnisse zu gewinnen und das Product Backlog für die ersten Sprints vorzubereiten, um nicht ins Blaue loszucoden.

 

THESE 5

Bernhard LöwensteinWer Scrum mit den früher klassischen Vorgehensmodellen vergleicht, macht oft den Fehler, dass er Scrum inklusive der heute bereitstehenden Werkzeuge und Techniken mit dem früher klassischen Prozess exklusive dieser Werkzeuge und Techniken vergleicht (da es diese eben damals noch nicht gab). Klarerweise scheint Scrum dann den „alten“ Vorgehensmodellen deutlich überlegen zu sein.

Frank Düsterbeck: Die heute modernen Praktiken und Methoden, die es teilweise schon länger als Scrum gibt (z. B. Automatisierung, XP, CCD, Story Mapping, Design Thinking, Microservices), können selbstverständlich auch in klassischen Umfeldern hilfreich sein. Durch die iterative Vorgehensweise, das Rollenmodell, die Transparenz, Inspektion und Adaption hat Scrum jedoch eindeutige Vorteile gegenüber klassischen sequenziellen Modellen.

Man darf auch nicht außer Acht lassen, dass einige klassische Vorgehensweisen Iterationen bzw. Rückkopplungen der verschiedenen Phasen eingeführt haben, da eine rein sequenzielle Abarbeitung einer komplexen Problemstellung eben falsch ist. Diese Rückkopplungen habe ich persönlich aber in noch keinem einzigen Wasserfallprojekt als bewussten Prozessschritt gesehen – schade!

 

Frank Düsterbeck auf der W-JAX 2017


● Selbstorganisation und agile Teams – zwischen Autonomie, Selbstbeschränkung und Chaos

6. NOV 2017
14:15 – 15:00

The post Fluch oder Segen? – Fünf Thesen zu Scrum appeared first on JAX.

]]>
Datenarchitekturen (nicht nur) für Microservices https://jax.de/blog/devops-continuous-delivery/datenarchitekturen-nicht-nur-fuer-microservices/ Tue, 30 May 2017 13:52:19 +0000 https://jax.de/?p=48321 Microservices sollen keine gemeinsame Datenbank haben. Warum eigentlich? Und was ist mit der dabei entstehenden Redundanz? Eberhard Wolff klärt in seiner Session, welche Ansätze für die Datenhaltung für Microservices-Systeme sinnvoll sind. En passant lernen Sie moderne Best Practices kennen, die auch in anderen Anwendungsszenarien nützlich sind.

The post Datenarchitekturen (nicht nur) für Microservices appeared first on JAX.

]]>
Viele Architekturen kranken an zu großen und inflexiblen Datentöpfen. Dieser Vortrag zeigt die Gründe für die Probleme: die Missachtung von Bounded Context und eine viel zu enge Kopplung der Datenmodelle. Ansätze wie Datenreplikation zeigen, wie solche Probleme gelöst werden können, ohne dass dabei zu große Redundanzen bei den Daten entstehen.

Datenarchitekturen nicht nur für Microservices from JAX TV on Vimeo.

Eberhard Wolff ist Fellow bei innoQ und arbeitet seit mehr als fünfzehn Jahren als Architekt und Berater, oft an der Schnittstelle zwischen Business und Technologie. Er ist Autor zahlreicher Artikel und Bücher, u. a. zu Continuous Delivery und Microservices und trägt regelmäßig als Sprecher auf internationalen Konferenzen vor. Sein technologischer Schwerpunkt sind moderne Architektur- und Entwicklungsansätze wie Cloud, Continuous Delivery, DevOps, Microservices und NoSQL.

The post Datenarchitekturen (nicht nur) für Microservices appeared first on JAX.

]]>