Das Problem mit Microservices

vg

Microservices versprechen Agilität, Skalierbarkeit und Ausfallsicherheit, aber zu viele Anwendungen nutzen diese Vorteile nicht, weil es grundlegende Probleme bei der Definition und Verbindung von Services gibt. Wie können wir das also beheben?

Schlecht gezogene Grenzen führen zu eng gekoppelten Diensten, wodurch ein System brüchig und schwer zu ändern ist. Gleichzeitig behindern statische Konfigurationen und starre Abhängigkeiten die Flexibilität, die erforderlich ist, um dynamische und sich entwickelnde Geschäfts-, Bereitstellungs- und Betriebsanforderungen zu erfüllen.

Diese Probleme führen häufig zu aufgeblähten Anwendungen, bei denen Dienste die Funktionalität duplizieren, Workflows anfällig sind und die Kommunikation zu einem Engpass wird. In Ja… Microservices REALLY ARE Technical Debt beschreibt Dave Farley die Gründe für – und die Folgen – dieser Probleme.

Im Kern ergeben sich diese Herausforderungen aus zwei miteinander verbundenen Problemen: dem Versagen, die destruktive Kopplung zwischen Diensten zu minimieren, und dem Fehlen eines flexiblen, skalierbaren Modells für deren Konfiguration und Verbindung.

Die Behebung dieser Probleme ist notwendig, um das volle Potenzial von Microservices-Anwendungen auszuschöpfen.

Effektive Microservices: Grenzen und Verbindungen

In der Softwareentwicklung kommt es bei der Entwicklung robuster, skalierbarer und anpassungsfähiger Systeme oft darauf an, wie gut wir die Dienste entwerfen und integrieren, aus denen diese Systeme bestehen. Zwei wichtige Überlegungen sind:

  • Wie wir Grenzen um einzelne Dienste definieren, um destruktive Kopplungen zu vermeiden.
  • Wie wir diese Dienste konfigurieren und verbinden, um die dynamische Anpassungsfähigkeit zu fördern.

Diese Überlegungen sind besonders wichtig für die Entwicklung von Microservices, da schlecht entworfene Grenzen und Verbindungen die versprochene Agilität und Wartbarkeit von Microservices leicht untergraben können.

In diesem Artikel werden wir versuchen, diese Herausforderungen anzugehen, indem wir hervorheben, wie domänengesteuertes Design (DDD), standardisierte bindbare Schnittstellen und dynamische Messagingmodelle skalierbare, ausfallsichere servicebasierte Systeme ermöglichen.

Die Herausforderung bei der Definition von Servicegrenzen

Services mit schlecht definierten Grenzen führen oft zu Systemen, die spröde, eng gekoppelt und teuer zu ändern sind. Wenn ein Dienst auf einen anderen Dienst zugreift, können sich Änderungen an letzterem auf das gesamte System auswirken und Aktualisierungen an mehreren Stellen erfordern.

Beispiele für destruktive Kopplung: Stellen Sie sich vor, dass ein Zahlungsabwicklungsdienst Methoden direkt auf einen Inventardienst aufruft, um die Lagerbestände nach einem erfolgreichen Kauf zu aktualisieren. Wenn der Inventardienst seine API oder interne Logik ändert, kann der Zahlungsdienst nicht funktionieren. Schlimmer noch, wenn der Inventardienst nicht mehr verfügbar ist, kann auch der Zahlungsvorgang fehlschlagen, obwohl die beiden Verantwortlichkeiten logisch voneinander getrennt sind.

Domain-Driven Design als Lösung: Domain-Driven Design (DDD) bietet eine Lösung, indem es Entwickler dazu anregt, Dienste als begrenzte Kontexte und Aggregate von Kontexten zu modellieren. Ein Bounded Context definiert eine klare Grenze um eine bestimmte Domäne innerhalb der Anwendung. Jeder Kontext kapselt verwandte Entitäten, Wertobjekte und Aggregate, um sicherzustellen, dass Interaktionen mit anderen Teilen des Systems implizit sind und durch klar definierte Verträge gesteuert werden.

Grenzen und Verbindungen
Grenzen und Verbindungen

In einer E-Commerce-Plattform werden die Zahlungs- und Inventardienste als separate Bounded Contexts modelliert, die jeweils für ihre eigene Domain verantwortlich sind. Die Wechselwirkungen zwischen ihnen werden durch implizite Schnittstellen und nicht durch direkte Abhängigkeiten vermittelt, wodurch das Risiko von Dominoeffekten verringert und die Modularität verbessert wird.

Konfigurieren und Verbinden von Diensten: Dynamische Komposition

Herkömmliche Ansätze zum Verbinden von Diensten beruhen häufig auf statischen Konfigurationen oder Abhängigkeiten. Dieser Ansatz schränkt die Flexibilität ein, da Änderungen an einem Dienst möglicherweise eine umfangreiche Neukonfiguration oder erneute Bereitstellung anderer Dienste erfordern.

Dynamisch verbundene Services
Dynamisch verbundene Services

Die Integration eines neuen Versanddienstes in ein Auftragsverwaltungssystem kann das manuelle Aktualisieren von Konfigurationsdateien, das Ändern von Code und das erneute Bereitstellen der Anwendung umfassen. Diese Notwendigkeit bremst nicht nur die Innovation, sondern birgt auch Fehlermöglichkeiten.

Die Leistungsfähigkeit bindbarer Schnittstellen: Eine bindbare Schnittstelle bietet einen standardisierten – und impliziten – Vertrag für die Serviceinteraktion. Alle Dienste stellen einen einheitlichen Satz von Methoden bereit, die an HTTP-Methoden ausgerichtet sind, z. B. GET, POST, REPLACE und DELETE. Diese Schnittstellen orientieren sich an RESTful-Prinzipien und nutzen Konzepte wie Zustandslosigkeit und Ressourcenorientierung, um die Kommunikation zu vereinfachen.

Bindbare Schnittstellen ermöglichen es, Dienste dynamisch zu verbinden und zur Laufzeit dynamisch zu skalieren. Anstatt Verbindungen fest zu codieren, können Dienste einander on-the-fly erkennen und miteinander verbinden.

Wenn also ein Versanddienst hinzugefügt wird, kann er sich zur Laufzeit beim System registrieren und seine Dienste sofort verfügbar machen, ohne dass Änderungen am vorhandenen Code erforderlich sind.

Dynamische Komposition in der Praxis: Stellen Sie sich einen Workflow vor, bei dem ein Auftragsverwaltungssystem zwischen Inventar-, Zahlungs- und Versanddiensten koordiniert.

Mithilfe von bindbaren Schnittstellen kann ein Nachrichtenmoderator dynamisch verfügbare übereinstimmende Dienste ermitteln, eine Bindung an sie herstellen und den Workflow organisieren, ohne dass vordefinierte Verbindungen erforderlich sind – und der gebundene Dienst kann seine Präferenzen (Themenabonnements, akzeptable Messaging-Downgrades unter Last usw.) beim Moderator registrieren.

Messagingmodelle für entkoppelte Kommunikation

Einschränkungen des traditionellen Messagings: Viele Systeme verlassen sich auf synchrones Messaging mit eng gekoppelten Mustern, was zu Engpässen führen und die Skalierbarkeit einschränken kann – wenn keine sofortige Reaktion erforderlich ist, könnte asynchrones Messaging verwendet werden, um Engpässe zu minimieren und Dienstinstanzen dynamisch zu skalieren. Manchmal muss eine Nachricht synchron sein, manchmal nicht – halten Sie sich also alle Optionen offen.

Dynamisches Messaging durch Moderation: Ein Nachrichtenmoderator führt einen flexiblen, dynamischen Ansatz für das Messaging ein. Es gibt einen Nachrichtenmoderator pro Dienstknoten.

Innerhalb desselben Dienstknotens werden Nachrichten zwischen Diensten als Standardmethodenaufrufe implementiert. Nur Nachrichten, die zwischen Dienstknoten wechseln, werden in Netzwerkaufrufe umgewandelt.

Als intelligenter Vermittler leitet es Nachrichten dynamisch weiter, basierend auf ihren Zieldienst- und Laufzeitbedingungen. Dieser Ansatz unterstützt mehrere Kommunikationsmuster, darunter:

  • Synchrones Messaging: Sofortige Anforderungs-/Antwort-Workflows. Wenn eine sofortige Antwort erforderlich ist, kann ein Dienst eine synchrone Nachricht senden und auf eine Antwort warten, bevor er fortfährt
  • Asynchrones Messaging: Entkoppelte Interaktionen, bei denen Sender und Empfänger unabhängig voneinander arbeiten. Wenn ein Dienst keine Antwort benötigt, bevor er fortfährt, kann er eine asynchrone Nachricht senden und ohne Wartezeit fortfahren.
  • Ereignisgesteuerte Kommunikation: Veröffentlichen und Abonnieren von Ereignissen für die Echtzeit-Systemkoordination. Wenn ein Dienst ein Ereignis veröffentlicht, kann er sofort fortfahren, mit der Maßgabe, dass interessierte Abonnenten die Ereignisnachricht erhalten.

Dynamisches Messaging und Ausfallsicherheit: Der Moderator erhöht auch die Resilienz. Wenn eine lokale Instanz eines Diensts vorübergehend nicht verfügbar ist oder die Antwortzeiten zu stark beeinträchtigt sind, kann ein Moderator die Nachricht automatisch an eine andere verfügbare Instanz desselben Diensts auf einem anderen Dienstknoten weiterleiten oder, wenn dies für den Absender akzeptabel ist, die Nachricht für eine spätere Zustellung in die Warteschlange stellen.

Darüber hinaus stärkt der Moderator durch das Validieren, Signieren und Verschlüsseln von Nachrichten – und das Protokollieren von Nachrichten und Antworten – zuverlässig die Sicherheit und Beobachtbarkeit.

Ganzheitlicher Ansatz für das Anwendungsdesign

Einheitliche Komposition und Botschaft: Die Kombination aus bindbaren Schnittstellen und dynamischem Messaging ermöglicht einen einheitlichen Ansatz für das Anwendungsdesign. Dienste können dynamisch in Workflows zusammengefasst werden, während sie nahtlos über moderierte Nachrichten kommunizieren. Dies reduziert den Aufwand für die manuelle Konfiguration und sorgt dafür, dass sich das System leichter an neue Anforderungen anpassen kann.

Praktische Modularität: Durch die Einhaltung der DDD-Prinzipien und die Nutzung standardisierter Schnittstellen können Entwickler wirklich modulare Systeme entwerfen. Klare Grenzen zwischen den Diensten verringern das Risiko einer destruktiven Kopplung, während die dynamische Komposition die Zusammenarbeit von Diensten auf flexible, skalierbare Weise ermöglicht.

Implizite Arbeitsabläufe: Kombiniert Aggregate, Messaging und Ereignisveröffentlichung, um Anwendungsworkflows über bindbare Komponenten hinweg zu koordinieren. Beispiel: 1) Wenn eine neue Bestellung erstellt wird, wird eine Nachricht an das Auftragsaggregat gesendet, das die Bestellung validiert und ein Ereignis für den nächsten Schritt im Workflow veröffentlicht. 2) Jeder bindbare Dienst (z. B. Inventar, Versand, Abrechnung) abonniert diese Ereignisse und verarbeitet sie unabhängig, sodass der Workflow dynamisch und ohne hartcodierte Abhängigkeiten fortgesetzt werden kann.

Skalierbarkeit in der Praxis: Dynamisches Messaging unterstützt verteilte Architekturen, sodass Systeme horizontal über Knoten hinweg skaliert werden können. Beispielsweise kann ein Auftragsverwaltungssystem über mehrere Serviceknoten hinweg betrieben werden, wobei der Nachrichtenmoderator sicherstellt, dass Nachrichten effizient zwischen den Knoten weitergeleitet werden.

Verbesserte Entwicklerproduktivität: Durch die Abstraktion der Komplexität von Konfiguration und Messaging können sich Entwickler auf die Geschäftslogik konzentrieren, anstatt auf Integrationsdetails auf niedriger Ebene. Dies führt zu schnelleren Entwicklungszyklen, weniger Fehlern und einer besseren Abstimmung zwischen Anwendungs- und Geschäftsanforderungen.

Fazit

Die Effektivität einer Microservices-Anwendung hängt davon ab, wie gut sie Grenzen definiert und Verbindungen verwaltet. Durch die Nutzung von domänengesteuertem Design für die Zerlegung, die Einführung bindbarer Schnittstellen für standardisierte Interaktionen und den Einsatz dynamischer Messagingmodelle für mehr Flexibilität können moderne Microservices-Architekturen die Einschränkungen herkömmlicher Ansätze überwinden.

Diese Prinzipien ermöglichen nicht nur anpassungsfähigere und skalierbarere Systeme, sondern ermöglichen es Entwicklern auch, Anwendungen zu erstellen, die besser auf die Geschäftsziele abgestimmt sind.

Unabhängig davon, ob Sie eine neue Anwendung erstellen oder eine vorhandene modernisieren, ist die Fokussierung auf Grenzen und Verbindungen der Schlüssel zum langfristigen Erfolg beim servicebasierten Anwendungsdesign.

Weiterer interessanter Beitrag: Okta Bcrypt-Vorfall: Was wir lernen können

com

Newsletter Anmeldung

Bleiben Sie informiert! Wir informieren Sie über alle neuen Beiträge (max. 1 Mail pro Woche – versprochen)