fb
WSO2 12 Minuten

Abkürzungen bei der Software-Integrationsentwicklung

Sieben Schritte zu einer Minimal Viable Integration

Rob Blaauboer
Rob Blaauboer
Integration Consultant & WSO2 Trainer
Blog Taking shortcuts
Scrollen

Abkürzungen zu nehmen ist sehr menschlich. Wenn du mir nicht glaubst, dann schau dir einfach den trampelpfad an, der in dem untenstehenden Bild gezeigt wird. Würdest du ihn nicht nehmen?

Taking-Shortcuts-Example

Wenn du dich fragst, was ein gesuchter Weg ist, es ist ein anderes Wort für Abkürzung. Zum Beispiel könntest du eine Abkürzung nehmen, wenn du über ein Stück Gras gehst, um etwas Zeit zu sparen. Wir sind dazu geneigt, Abkürzungen zu nehmen, Dinge zu beschleunigen oder einfacher zu machen. Es liegt in unserer Natur. Auf Niederländisch nennt man das einen Elefantenpfad, denn Elefanten sind von Natur aus darauf programmiert, beim Reisen den kürzesten Weg zu nehmen. Trampelpfade als Phänomen sind gut dokumentiert und werden sogar auf der TED-Bühne diskutiert

Abkürzungen zu nehmen mag verlockend aussehen. Es kann dir Zeit sparen, aber es kann dich auch etwas kosten! Solltest du dich jetzt fragen, wovon ich spreche, ich spreche davon, Abkürzungen zu nehmen, wenn du Softwareintegrationen entwickelst. Das, was du am Anfang der Entwicklung von Integrationen auslassen könntest, kostet in vielen Fällen später mehr Zeit (und damit Geld) bei der Entwicklung. Also, Caveat Emptor!

Der richtige und falsche Weg der Softwareentwicklung

Wenn du ein neues Stück Code entwickelst, kannst du dies auf zwei Arten tun. Den richtigen Weg und den falschen Weg. Über den richtigen Weg lässt sich streiten. Und zu einem gewissen Grad hängt es auch von deiner Meinung über Softwareentwicklung, Codierung usw. ab. Aber es hängt auch von Aspekten der Konventionen innerhalb der Organisation ab, in der du arbeitest. 

Der falsche Weg ist auch umstritten. In dem Sinne, dass es einige Fälle gibt, in denen ein schnelles Präsentieren eines Stücks Code tatsächlich ausreichen würde. Aber solche Schnellansätze zeigen dir oft nichts, was ein Konzept beweisen oder widerlegen kann. Sie sind oft nur ein wegwerfbarer Prototyp im Frühstadium. Es mag wie eine große Sache aussehen, aber in Wirklichkeit ist es eine einfache Sache und nicht für etwas anderes als eine schnelle Demo gedacht.

Minimal Viable Integration (MVI)

In diesem Blog möchte ich über etwas Wichtiges sprechen. Über etwas, das wir bei Yenlo als Minimal Viable Integration (MVI) bezeichnen. 

Unserer Meinung nach gibt es einen minimalen Standard, an den du dich halten solltest, wenn du eine Integration mit den WSO2 Produkten durchführst. Du solltest zum Beispiel keine Kompromisse bei der Sicherheit eingehen. Aber es gibt noch mehr. 

Die Grundlagen und Risiken der Softwareentwicklung 

Wir kennen natürlich alle die Grundlagen. Dupliziere keinen Code, Endpunkte und andere Entwicklungsartefakte. Das Kopieren von Code ist eine reflexartige Reaktion und wir müssen lernen, diese zu kontrollieren. Kopiere nicht gedankenlos Code. Das sollte offensichtlich der grundlegende Teil aller 1×1 Kurse über Softwareentwicklung oder Konfiguration gewesen sein. Falls du trotzdem Code duplizierst, könntest du am Ende einen Alptraum der Wartung in den Händen halten. Außerdem ist die Möglichkeit von Fehlern viel, viel größer als bei der normalen Verwendung einer Funktion, einer Bibliothek oder einer anderen Form der Wiederverwendung von Code. Erstelle lieber separate Funktionen, Dienste oder Vorlagen, um wiederverwendbare Funktionalität zu erfassen.  

Nachdenken, bevor du es tust!

Ja, du musst die Dinge zu Ende denken, wenn du die Mehrfachverwendung und Wiederverwendbarkeit maximieren möchtest. Halte dem Druck stand, etwas schnell und unsauber zu erledigen. Erst denken, dann handeln, und sogar mit Gleichgesinnten darüber diskutieren ist eine gute Idee, von der dein Team und deine Stakeholder profitieren werden. Aber es erfordert mehr Zeit. 

Die andere Seite der Medaille, das andere Extrem, kann dich regelrecht lähmen. Das nennt man Analyse-Lähmung. Sie entsteht häufig, wenn du es versäumst, dein Denken auf einen realistischen Problemumfang zu beschränken. Die Grenzen in Bezug auf die Komplexität sollten nicht über den begrenzten Kontext hinausgehen. Besonders wenn du beginnst, Dinge auf einer tieferen Ebene zu manipulieren (z.B. das Ändern von Standardkomponenten), wird es für dich immer schwieriger zu verstehen, wie die Leute deine Arbeit nutzen und für deine Kollegen, die Änderungen zu verstehen, die du vorgenommen hast. Vergiss nicht, dass nicht jeder so brillant ist wie du.

Sieben Schritte zu einer möglichst erfolgreichen Integration

Was ist also diese minimale funktionierende Integration, von der wir hier sprechen? Nun, sie stellt sicher, dass das Stück Code, das du entwickelst, für den Zweck geeignet ist. Außerdem können Dinge zum Vorschein kommen, die man anfangs vielleicht nicht erwartet, die aber auf lange Sicht einen erheblichen Mehrwert darstellen. Deshalb meine vorherige Behauptung, dass Abkürzungen zu nehmen dich etwas kosten wird. Diese sieben Schritte zu befolgen wird mehr von deiner Zeit in Anspruch nehmen, aber ein MVI-basierter Ansatz ist notwendig, wenn du die Qualität deiner Integrationen verbessern und die Auswirkungen potenzieller Fehler minimieren willst. So wie Sicherheitsgurte in Autos helfen können, Verletzungen zu minimieren, wenn etwas schief geht. Die Tatsache, dass sie gesetzlich vorgeschrieben sind, verdeutlicht ihre Wichtigkeit. 

Nachfolgend werde ich die sieben verschiedenen Praktiken zu einer möglichst fehlerfreien Integration näher erläutern, zusammen mit einem Hinweis auf die Schritte, die du durchführen kannst.

#1 Achte darauf, dass du alle Situationen berücksichtigst

Eine dieser Dinge ist die Auswahl des passenden Mediators. Oftmals gibt es mehrere Möglichkeiten, die Integrationslogik mit Mediatoren zu implementieren. Beim Filter Mediator zum Beispiel wird ein binäres IF THEN ELSE verwendet, das für Situationen ausreicht, in denen du eine Auswahl zwischen zwei hast. Aber gibt es nicht eine dritte Möglichkeit? 

Stelle dir vor, du hast zwei Abteilungen, die Nachrichten empfangen müssen, je nach Information in der Nachricht. Man könnte sagen, wenn die Nachricht nicht für Abteilung #1 ist, ist sie automatisch für Abteilung #2. Aber was ist, wenn eine Nachricht eintrifft, die fehlerhaft ist und nicht an Abteilung #1 oder #2 gehen sollte? Der binäre Charakter des Filters wird sie an #1 oder #2 senden, je nach Einstellung, und sie nicht korrekt behandeln. Also musst du einen zusätzlichen Filter hinzufügen. Bevor du dich versiehst, hast du eine chaotische Sequenz erstellt.

In Wirklichkeit gibt es fast nie ein binäres Problem. Fast immer gibt es Ausnahmen von der binären Regel. Oder mindestens potenzielle zukünftige Ausnahmen. Indem du nun einfach den Switch Mediator von Anfang an verwendest, wird dir deine anfängliche Implementierung nicht wesentlich mehr Arbeit bringen, aber es wird sofort die Wartbarkeit deines Codes verbessern. 

→ Zu ergreifende Maßnahmen: Wechsle zum Schalter.

#2 Verarbeite deine Nachricht vorsichtig

Eine der wichtigsten Lektionen, die du bei der Integration lernst, ist, den Status nicht in einem Integrationsdienst zu halten. Zum einen schränkt dies deine Skalierbarkeit ein. Zum anderen riskierst du einen Datenverlust bei einem Failover. A-B-Tests und Canary-Deployments werden komplexer und Zero-Downtime-Upgrades können unmöglich werden.

Es ist in der Praxis überraschend schwierig, die Beibehaltung des Zustands zu vermeiden, insbesondere bei Integrationsdiensten, die eine längere Bearbeitungszeit benötigen (können). Es gibt ein paar Möglichkeiten, wie man solche Integrationen robuster machen kann. Benutze keinen lokalen Speicher. Niemals. Verwende stattdessen einen verteilten Objektspeicher oder ein Netzlaufwerk, um deine Dateien zu speichern. Verwende Warteschlangen, wann immer du über einen unzuverlässigen Kanal kommunizierst. Bedenke, dass du dich bei jedem Wiederholungsversuch darauf verlässt, dass deine Nachricht noch verfügbar ist. Denke nicht, dass deine In-Memory Nachricht persistent ist. Dann sind da noch die Zwischenzustände. Falls ein Nachrichtenpfad aus einer Sequenz von Schritten aufgebaut ist, die sich nicht wiederholen lassen, solltest du jeden Schritt verfolgen. Erwäge den Aufbau einer Event-Pipeline, um die Verarbeitung der Nachricht zu verfolgen. Manche Event-Broker bieten sogar die Möglichkeit, die Verarbeitung genau an dem Punkt neu zu starten, an dem etwas schiefgelaufen ist. 

Event Channels haben einen zusätzlichen Bonus. Wie Topics erleichtern sie die Erweiterbarkeit. Wenn du ein Ziel zur Ereignis-Pipeline hinzufügen musst, kannst du einfach ein Abonnement zu einem Thema hinzufügen. Presto! 

→ Zu ergreifende Maßnahmen: Wähle eine Message-Queue-Lösung, erwäge eine Event-Pipeline

→ Lösungen: ActiveMQ, RabbitMQ (Queues), Kafka

#3 Investiere in Observierbarkeit

Ganz egal, wie sehr du versuchst, alles richtig zu machen, es wird immer etwas schief gehen. Die Integration ist von Natur aus ein dezentraler Prozess und die Kommunikationswege sind nicht auf Zuverlässigkeit ausgelegt. Datenpakete können verloren gehen, Kabel können brechen, Ports in einem Switch können ausfallen, Netzwerküberlastungen können die Dinge verlangsamen, Quotas können überschritten werden und so weiter. All das mag selten vorkommen, aber wenn man im großen Stil arbeitet, kann es dich durchaus überraschen, wenn es passiert. Offensichtlich willst du keine Zeit damit verbringen, zu analysieren, was bei der Integration schiefgelaufen ist, wenn das Problem im Netzwerk liegt. Andererseits ist die Annahme, dass das Netzwerk die Ursache für Störungen ist, während in Wirklichkeit deine Integration versagt, in bestimmten Grenzfällen vielleicht sogar noch schlimmer. 

Um Integrationsprobleme in den Griff zu bekommen, solltest du in Beobachtbarkeit investieren. Sammle Logdateien von allen Knoten in der Verarbeitungspipeline. Registriere Kreuzkorrelationen. Sammle Statistiken für deine langfristige Trendanalyse. Investiere etwas Zeit in die richtige Nutzung des Log Mediators. Baue Kompetenz auf, um deine Logdaten bei Bedarf schnell zu analysieren. Plane Zeit ein, um deine Logdaten regelmäßig zu überprüfen. Schließlich ist Vorbeugung immer besser als eine heldenhafte Heilung.

→ Zu ergreifende Maßnahme: Untersuche eine Log-Lösung 

→ Lösungen: ELK Stack, Loki, Jaeger

#4 Modularisierung der Praxis

Immer wenn du versucht bist, einen riesigen Monitor zu bestellen, der dir hilft, komplexe Abläufe zu verwalten, solltest du wahrscheinlich darüber nachdenken, sie in überschaubare Stücke zu zerlegen. Vorzugsweise in Stücke, die in späteren Integrationen wiederverwendet werden können. Es gibt allerdings eine feine Grenze, die man nicht überschreiten sollte. Die modulare Komplexität kann zu einer Herausforderung für sich selbst werden. Du könntest riskieren, Endlosschleifen durch zirkuläre Abhängigkeiten zu erzeugen. In gleicher Weise kannst du mit komplexen Event-Streams einen Event-Sturm verursachen, der den Stream überflutet. Zum Glück kommen neue Design-Tools auf den Markt, die dir helfen, den Überblick über deine Themen zu behalten. 

→ Zu ergreifende Maßnahmen: Analysiere den Code, um zu bestimmen, wo und wann die Modulierung implementiert werden sollte, wobei du die Performance und die Wartbarkeit im Auge behältst.

→ Lösungen: Nicht anwendbar

#5 Unzustellbare Briefe zustellen

Wenn alles andere scheitert, könntest du in Versuchung geraten, eine fehlerhafte Nachricht einfach zu löschen und den Fall zu schließen. Oftmals ist dies jedoch nicht das beste Verhalten. Wenn es ein nicht lösbares Problem bei der Verarbeitung einer eingehenden Nachricht oder Datei gibt – sie enthält einige illegale Zeichen, hat zu viele Zeichen in einem bestimmten Parameter, hat ein obligatorisches Element ausgelassen, hat einen inkonsistenten Header oder Footer, einen Wert außerhalb der Liste der vordefinierten Werte, ein Array außerhalb der Grenzen – muss typischerweise jemand eingreifen und es korrigieren. Manchmal sind Programme so gebaut, dass sie mit Ausschussmeldungen umgehen oder erwartete Inkonsistenzen automatisch korrigieren. Wenn nicht, ist das Verschieben einer fehlerhaften Nachricht in eine Dead-Letter-Queue die Maßnahme der letzten Instanz. Dies sollte einen Operator alarmieren, der sich darum kümmert.  

Beachte jedoch, dass der Ausfall einer Nachricht einen Ripple-Effekt haben kann. Falls es wichtig ist, die Ordnung in einer Verarbeitungskette aufrechtzuerhalten, sollte ein Fehler die gesamte Kette stoppen, um verarbeitet zu werden. Insbesondere bei der Stapelverarbeitung kann eine gründliche Validierung des gesamten Stapels eine Voraussetzung für den Beginn der Verarbeitung sein. 

→ Zu ergreifende Maßnahmen: Staube dein Wissen über Enterprise Integration Patterns ab und recherchiere Message Queues

→ Lösungen: Regelmäßige Message Queues, Incident Handling

#6 Als Optimist entwickeln, als Pessimist testen

Wenn es ums Testen geht, solltest du nichts für selbstverständlich halten. Zunächst einmal muss das funktionale Testen vollständig sein. Jedes Problem, das du dir vorstellen kannst, sollte in deinen Testskripten enthalten sein. Teste nicht nur gegen die Stubs, die du erstellt hast, sondern auch gegen die tatsächlichen Systeme, mit denen du integrierst. Keine Ausrede. Und weil die Integration von Natur aus fehleranfällig ist, musst du auch in Betracht ziehen, etwas Chaos in deine Tests einzubauen.

Gründliches Testen kann eine Menge Arbeit sein. An dieser Stelle kann ein Tool enorm hilfreich sein. Es erspart eine Menge Zeit und erhöht die Qualität des Testens. Nimm zum Beispiel einen automatisierten Konformitätsscan deiner APIs. Oder einen Fault Injection Simulator. Bedenke, je mehr du deine Probleme nach links verschieben kannst und sie aufspüren kannst, bevor sie problematisch werden, desto einfacher sind sie zu lösen. Wenn du gleichzeitig überzeugend nachweisen kannst, dass ein Problem tatsächlich nur eine begrenzte Auswirkung hat, brauchst du keine Zeit zu verschwenden, um es für deine Minimum Viable Integration zu beheben.

→ Zu ergreifende Maßnahmen: Umfangreiche Testskripte erstellen

→ Lösungen: Testing Tools wie Rest Assured, Postman, SoapUI aber auch Testing Suites

#7 Vermeide es, den Kreislauf zu verkürzen

Wenn ein Service streikt, kann das Senden weiterer Nachrichten das Problem verschlimmern. Sogar ein erneuter Versuch nach einem Timeout kann problematisch sein. Andererseits ist eine einzige Zeitüberschreitung kein Anzeichen dafür, dass ein Dienst streikt. Es kann eine Herausforderung sein, ein solches Verhalten festzustellen, besonders in großen, verteilten Systemen. Dann brauchst du einen intelligenten Adapter, damit diese Dienste nicht überflutet werden. Ein einzelner Punkt in deiner Architektur, der den gesamten Datenverkehr zu einem Backend kanalisiert und mit temporären Integrationsproblemen elegant umgehen kann. Ein API Gateway, zum Beispiel. 

Ein Circuit Breaker ist ein fortschrittlicher Algorithmus, der mit temporären Integrationsproblemen umgehen kann. Er ist intelligent genug, um den Verbindung zu einem fehlerhaften Knoten zu unterbrechen. Er ist außerdem klug genug, um die Verbindung automatisch wiederherzustellen, sobald er dafür verantwortlich ist. In der Tat ist es ein interaktiver Drosselungsmechanismus, der auf die Gesundheit eines Endpunktes abgestimmt ist. Insbesondere wenn deine minimale Integration auf Komponenten oder Dienste von Drittanbietern angewiesen ist, die noch nicht kampferprobt sind, kann ein Circuit Breaker den Tag retten.

→ Zu ergreifende Maßnahme: Implementiere einen Circuit Breaker in deiner API. 

→ Lösungen: WSO2 API Microgateway, Istio

Nimm dir Zeit und beeil dich

Diese 7 Schritte werden dafür sorgen, dass du keine Abkürzungen nimmst, die du später bereuen wirst. Eine Abkürzung bringt dich vielleicht schneller ans Ziel, aber nicht auf Dauer. Der ganze Gewinn, den du durch die Abkürzung bekommen hast, verpufft, weil fragile Lösungen dazu neigen, diejenigen zu verletzen, die sie benutzen und dich zurück beißen, wenn du es am wenigsten erwartest.