11 Minuten Lesezeit (2148 Worte)

Microservices

Die letzten Jahre haben tiefgreifende Änderungen in den IT-Landschaften mit sich gebracht. So stellen beispielsweise der Boom mobiler Geräte und das stark veränderte Nutzerverhalten große Herausforderungen an heutige Softwaresysteme. Die Frage, wie in immer kürzeren Zyklen Software bei gleichbleibend hoher Qualität auf neue Anforderungen angepasst werden kann, ist dabei von zentraler Bedeutung. Themen wie Agile, DevOps oder Continous-Delivery versuchen die Probleme zu adressieren und zeigen vielversprechende Ansätze. Auch seitens der Softwarearchitektur scheint sich ein Trend herauszukristallisieren, weg von einzelnen großen monolithischen Systemen, hin zu kleineren, teils autonomen Systemen. Mit den Microservices wird seit einiger Zeit ein Architektumuster diskutiert, das diesen Ansatz verfolgt. Dieser Artikel möchte die Idee der Microservices näher beleuchten und aufzeigen, welche Vor- und Nachteile sie mit sich bringen.

Grundidee

Die Idee der Microservices wurde 2011 erstmals auf einem Workshop für Softwarearchitekturen nahe Venedig diskutiert. Zu den Beteiligten gehörten u.a. James Lewis und Martin Fowler von der Firma ThoughtWorks, die die Diskussion um das Thema auch heute maßgeblich antreiben. 

Ausgehend von der Annahme, dass die Ideen der Agile- und DevOps-Bewegung gut und richtig sind und Konzepte wie Continous-Delivery wichtige und wünschenswerte Aspekte in der Softwareentwicklung darstellen, stellt sich die Frage, wie eine Softwarearchitektur aussehen soll, die dies bestmöglich unterstützen kann. 

Für das Architekturmuster der Microservices gibt es keine festgelegte Definition, aber es existieren einige charakteristische Eigenschaften, die Microservices erfüllen sollten. Darunter befinden sich einige Konzepte, die schon seit jeher zu den bewährten Grundprinzipien guter Softwarearchitekturen zählen. Sie werden hier aber nochmal in einen bestimmten Kontext gerückt.

Modularisierung

Dem Prinzip "Teile und Herrsche" folgend wird ein Gesamtsystem in modulare Einheiten heruntergebrochen. Die dabei entstehenden kleineren Komponenten werden jeweils durch Services repräsentiert. Diese Services sollen im Wesentlichen der ursprünglichen Definition einer Softwarekomponente genügen, d.h. sie müssen unabhängig vom Rest des Gesamtsystems austauschbar sein.

Dies gelingt nur dann effizient, wenn die Komponenten möglichst lose gekoppelt sind und eine hohe Kohäsion[1] aufweisen. Es muss besonderes Augenmerk auf die Ausgestaltung der Schnittstellen gelegt werden, da Änderungen hier besonders große Auswirkungen haben und später oft nur mit erheblichen Kosten und Mühen durchgeführt werden können.

Des Weiteren betrachtet man Services als völlig autonome Prozesse (Abb.: 1a,1b), die evtl. auch in einer eigenen Ablaufumgebung ausgeführt werden. Damit sind sie unabhängig vom Gesamtsystem und können einfacher ausgetauscht und betrieben werden. Dies bringt enorme Vorteile für die Skalierbarkeit solcher Systeme, hat aber auch den Nachteil, dass Aufrufe über Prozessgrenzen hinweg durchgeführt werden müssen, was tendenziell „teurer" ist als klassische Prozeduraufrufe. Die Ausgestaltung der Schnittstellen muss somit etwas grobgranularer erfolgen, da sich sonst ein überproportional hoher Kommunikationsaufwand ergäbe. Die technischen Ausgestaltungsmöglichkeiten werden später noch genauer erläutert. [1] Eine einzelne Komponente ist für eine einzelne, genau definierte (abgegrenzte) Aufgabe zuständig.

Abb. 1a: monolithische Systeme

Abb. 1b: Microservice-Architekturen

Separation Of Concerns [1] 

Bei der Zerlegung eines Systems in Teilkomponenten, stellt sich immer die Frage nach deren Zuschnitt. Gemeint sind damit die Zuständigkeiten und Grenzen der einzelnen Komponenten. Hier gibt es bzgl. der Microservices eine klare Aussage: Services sollen sich strikt an der Geschäftslogik orientieren. Konkret soll ein Service als eine Art Produkt verstanden werden. Diesem Gedanken folgend, soll es dann ein Team geben, das dieses Produkt vom Design bis zum Betrieb betreut. 

Diese Betrachtungsweise erleichtert die agile Entwicklung und die Aufteilung des Gesamtsystems auf einzelne Teams, außerdem entspricht es stark dem DevOps-Gedanken (Abb.: 2a). Das Team ist verantwortlich für die Entwicklung und den Betrieb eines Services (Produktes). Es muss entsprechend über alle benötigten Skills verfügen. Als Nebeneffekt wird die Aufteilung des Teilsystems in rein technische Domänen verhindert, wie sie in klassischen Projekten gemäß Conway's Law oft entsteht (Abb.: 2b). [1] Trennung der Zuständigkeiten

Abb. 2a: Teams betreuen „Produkte" (Services)

Abb. 2b: Conway's Law bei monolithischen Systemen

 Logik in Komponenten

Als weiteren wichtigen Punkt soll sich bei Microservices die Fachlogik ausschließlich in den (Service-)Komponenten befinden. Das hört sich zunächst selbstverständlich an, deutlicher wird der Focus noch, wenn man es aus einer anderen Perspektive beschreibt: Es soll keine Intelligenz in Infrastrukturkomponenten vorhanden sein.

Explizit soll verhindert werden, dass sich Code für Datenkonvertierung, Routing oder andere Funktionalitäten in der Kommunikationsinfrastruktur verstecken kann. Der Einsatz schwergewichtiger Komponenten wie z.B. eines Enterprise Service Busses ist also nicht erwünscht.

Kommunikation

Damit stellt sich sofort die Frage nach den Kommunikationsmechanismen, die für die Nutzung der Services verwendet werden sollen. Die Forderung nach loser Koppelung mit der Konsequenz, dass sich daraus Services als autonome Prozesse darstellen, wurde weiter oben schon erläutert. Da wir im aktuellen Umfeld natürlich über verteilte Anwendungen in einem Netzwerk sprechen, liegt der Schluss nahe, auch die dafür passenden Mechanismen und Protokolle zu verwenden.

Der Vorschlag lautet, in Microservice-Architekturen möglichst zwei Ansätze zu verwenden:

  • RESTful Request-Response Kommunikation über HTTP

oder

  • leichtgewichtiges Messaging

Der erste Punkt ist wegen der HTTP-inhärenten Möglichkeiten wie z.B. Caching naheliegend. Der RESTful-Ansatz ist längst etabliert und das Web ist der beste Beweis für dessen Tauglichkeit.

Leichtgewichtiges Messaging meint hier den Nachrichtenaustausch über einfach gehaltene Infrastrukturkomponenten, die außer dem Nachrichtentransport keinerlei zusätzliche Aufgaben übernehmen. Das sind klassischerweise Queues oder ähnliche Messaging-Komponenten.

Spätestens mit dem zweiten Punkt sollte klar geworden sein, dass ein Großteil der Kommunikation in Microservice-Architekturen asynchron abläuft. Die damit verbundenen Entwicklungsaufwände sind höher als in rein synchron kommunizierenden Systemen, was der erhöhten Komplexität in der Koordinierung der Serviceaufrufe geschuldet ist.

Ein weiterer sehr wichtiger Punkt, den es in diesem Zusammenhang zu berücksichtigen gilt, ist die Fehlertoleranz. Insbesondere die Frage nach der Nichterreichbarkeit eines Services steht hier im Mittelpunkt. Grundsätzlich gilt, dass jeder Service auftretende Fehler im Sinne der Gesamtanwendung behandeln muss. Eine entsprechend Fail-Safe-Strategie ist hier unerlässlich. Das verantwortliche Team muss sich der Wirkung eines Ausfalls für das Gesamtsystem bewusst sein und entsprechende Vorkehrungen treffen, sowie die geeigneten Testszenarien für die Qualitätssicherung entwickeln.

Wahl der Mittel

„Wenn Du als einziges Werkzeug einen Hammer besitzt, dann sieht jedes Problem wie ein Nagel aus." Diese oft zitierte Metapher adressiert eine Microservices-Architektur sehr konkret. Die Services sollen ausdrücklich die am besten geeigneten Mittel zu Lösung des vorliegenden Problems verwenden. In Konsequenz bedeutet dies eine völlige Freiheit bei der Wahl der Programmiersprache, Ablaufumgebung, Standards etc.

Es gilt jedoch auch, dass das was getan werden kann, nicht getan werden muss. Das entscheidende Kriterium ist und bleibt die Fachlichkeit. Sollte die Problemdomäne eine effiziente Lösung auf Basis eines vom Rest des Gesamtssystems abweichenden Ansatzes ermöglichen, so darf dieser auch gewählt werden. In klassischen Architekturen ist dies meist so nicht vorgesehen, da oft übergeordnete (koordinierende) Plattformen zum Einsatz kommen, die technische Einschränkungen zur Folge haben und solche Optionen nicht ermöglichen.

Verteilung der Datenhaltung

Durch die freie Wahl der Mittel wird, wie oben beschrieben, die Koordination des Gesamtsystems quasi dezentralisiert. Jeder Service trägt in Eigenregie mit den eigenen Mitteln und Methoden zu einem Teil der Lösung des Gesamtproblems bei.

Konsequenterweise erlaubt eine Microservices-Architektur auch die Dezentralisierung der Datenhaltung. Das bezieht sich nicht nur auf die Technologiefrage (RDBMS, NoSQL, Files, Memory, …), sondern auch auf die logische Datenaufteilung. Einzelne Services haben es oft nur mit Teilaspekten der jeweiligen Gesamtdatenbestände zu tun, andere teilen sich gemeinsame Datenpools (Abb.: 3a und 3b).

Abb. 3a: Datenhaltung bei Monolithen 

Abb. 3b: Datenhaltung in Microservice-Architekturen

Beim Zuschnitt der Service-Komponenten dürfen deshalb die Konsequenzen für das Datenmodell nicht vernachlässigt werden. Vorgeschlagen wird hier, sich am Konzept der „Bounded Contexts" aus Eric Evans' Ansatz des „Domain Driven Design" zu orientieren. Evans beschreibt darin, wie aus dem Gesamtdatenmodell sinnvoll Teilbereiche definiert werden und dann im Gesamtkontext verwaltet werden können.

Abgrenzung zur „klassischen" SOA

Serviceorientierte Architekturen (SOA) sind vom Prinzip nichts Neues. Leider ist das Thema mittlerweile sogar etwas in Verruf geraten, da die hohen Erwartungen oft nicht erfüllt werden konnten. Die Gründe dafür sind vielschichtig, sollen hier aber nicht weiter betrachtet werden.

Trotzdem ist das Konzept des Services als Architekturelement prinzipiell ein gutes Strukturierungsmittel und hat sich in verschiedensten Ausprägungen mehr als bewährt. Auch wenn es viele Parallelen gibt, sollen hier noch einmal kurz zwei der wesentlichen Unterschiede zur „klassischen" SOA dargestellt werden.

Zum einen fokussieren Microservices die Modularisierung oder Zerlegung eines monolithischen Systems. In der klassischen SOA-Idee liegt der Schwerpunkt dagegen eher auf der Integration von vielen Monolithen.

Zum anderen sollen in Microservice-Architekturen die Abläufe dezentralisiert werden. Einhergehend mit einer deutlichen Reduzierung der Komplexität der Kommunikationsinfrastruktur, soll sich die Intelligenz des Systems möglichst ausschließlich in den Services befinden. Das bedeutet eine Abkehr von beispielsweise schwergewichtigen, mit zusätzlichen Funktionalitäten überfrachteten ESBs oder SOAP-basierten Webservices.

Trade-Offs

Softwarearchitektur muss immer an den gegebenen Anforderungen und Randbedingungen gemessen werden. Besonders die nicht-funktionalen Anforderungen entpuppen sich meistens als die wesentlichen Architekturtreiber. Eine Architektur wird in der Regel so ausgestaltet, dass sie die Anforderungen gemäß ihren Prioritäten erfüllt. Wichtig ist es aber zu verstehen, dass eine Architektur nicht für beliebig viele Anforderungen optimiert werden kann, da es bzgl. einiger Qualitätsmerkmale regelrechte Zielkonflikte gibt (z.B. Benutzerfreundlichkeit und Sicherheit).

Wie zu Beginn beschrieben, haben Microservice-Architekturen eine Ausrichtung auf spezielle Anwendungstypen, für die bestimmte nicht-funktionale Anforderungen wie z.B. Skalierbarkeit und Erweiterbarkeit im Vordergrund stehen. Zwangsläufig müssen deshalb in verschiedenen Bereichen Kompromisse eingegangen werden. Einige dieser Trade-Offs sollen im Folgenden kurz skizziert werden.

Schnittstellendesign

Den Schnittstellen kommt in lose gekoppelten, verteilten Systemen eine entscheidende Bedeutung zu. Da die Nutzung der Schnittstellen wie bereits erwähnt meist über Prozessgrenzen stattfindet (z.B. RESTful Webservices oder Messaging-Mechanismen) muss die Wahl der Granularität wohl überlegt sein. Eine zu feine Granularität führt schnell zu übermäßiger Schnittstellennutzung und damit zu enormen Kommunikationsaufwänden.

Je loser Systeme gekoppelt sind, umso empfindlicher werden deren Schnittstellen gegenüber Änderungen. In Folge müssen solche Systeme sehr viel sorgfältiger geplant werden als Monolithen. Das führt ganz allgemein zu erhöhten Aufwänden und erfordert zusätzliches Risikomanagement.

Asynchronität

Ein weiterer Punkt, der ebenfalls mit der Verteilung des Systems und dem Schnittstellendesign zu tun hat, ist die Art in der die Schnittstellen genutzt werden. Ein großer Teil der Kommunikation in Microservice-Architekturen ist asynchron. Das ist prinzipiell schwierig zu koordinieren und bringt somit zusätzliche Komplexität ins System. Ein klassisches Beispiel für einen Aspekt, der in diesen solchen Situation oft dazu neigt, Probleme zu verursachen, ist das Transaktionmanagement, was nahtlos zum nächsten Punkt führt.

Querschnittsaspekte

Auf Grund der oben beschriebenen freien Wahl der Mittel und der Verteilung der einzelnen Services ist es extrem schwierig, querschnittliche Aspekte wie z.B. Logging, Security oder Transaktionshandling in Microservice-Architekturen durchgängig einheitlich zu implementieren. Bei Verwendung komplett unterschiedlicher technologischer Plattformen ist es in der Regel sogar unmöglich. So kann es passieren, dass deutlich höhere Aufwände entstehen, die dem Fakt geschuldet sind, dass bestimmte Querschnittsfunktionalitäten schlicht mehrfach implementiert werden müssen. Dies steht im Widerspruch zum DRY-Prinzip („Don't repeat yourself"), was eine möglichst hohe Wiederverwendung propagiert.

Know-How-Bedarf

Die Option, Services mittels unterschiedlicher Technologien und Plattformen zu realisieren, bietet eine Wahlfreiheit, die auch ihre Schattenseiten hat. In gewisser Weise wird hier der Grundsatz der Einfachheit (KISS – „Keep It Simple Stupid") verletzt. Die Komplexität steigt naturgemäß durch z.B. zusätzliche Sprachen oder Technologien an.

Insgesamt steigt der Know-How-Bedarf bei den beteiligten Teams deutlich an. Das betrifft nicht nur Fragen der Implementierung, sondern in besonderem Maße auch die betrieblichen Aspekte. Integrationsthemen gewinnen ebenfalls deutlich an Bedeutung. Die Wartung und der Betrieb von einzelnen Systemen wie beispielsweise Datenbanken oder Applikationsservern ist oft schon schwierig genug. In Microservice-Architekturen könnte aber die Notwendigkeit bestehen, gleich mehrere unterschiedliche Systeme dieser Art parallel betreuen zu müssen.

Die gleiche Argumentation gilt analog für das Testen bzw. die Qualitätssicherung solcher Systeme. Die Szenarien und Vorgehensweisen verlangen von allen Beteiligten umfangreiche Kenntnisse und Erfahrungen in diesen Bereichen.

Fazit

Der Service als Architekturelement ist in einer Softwarearchitektur ein mächtiges Strukturierungsmittel. Kombiniert mit einer Anzahl von Best Practices aus Softwareentwicklung und Vorgehensmodellen wirken Microservices ein wenig wie die Wiederentdeckung von Einfachheit und Klarheit.

Auf den zweiten Blick lassen sich aber auch Schattenseiten erkennen. Klare Strukturen in Services und Kommunikation verschleiern ein wenig die Komplexität in betrieblichen und planerischen Aspekten.

James Lewis und Martin Fowler haben zur Diskussion und zum Erfahrungsaustauch über Microservice-Architekturen aufgerufen. Da noch keine repräsentative Anzahl von Erfahrungsberichten zu entsprechenden Projekten vorliegt, tun auch sie sich mit einer Zukunftsprognose noch schwer.

Trotzdem bleiben Microservices ein interessanter Ansatz, den es in Zukunft zu beobachten gilt. Insbesondere unter Betrachtung der momentanen Entwicklung im Bereich DevOps und der weiter wachsenden Bedeutung von Cloud- und Mobile-Computing, könnten Microservices ein adäquates Mittel zur effizienten Entwicklung solcher Systeme sein.


{loadmoduleid 179}

  Erläuterungen

DevOps und Continous Delivery: DevOps ist ein Kunstwort aus „Development" (Software-Entwicklung) und „Operations" (IT-Betrieb). Es bezeichnet den gewünschten Schulterschluss beider Bereiche, um den traditionellen Konflikt zwischen ihnen aufzulösen. Ziel soll eine effizientere und schnellere Entwicklung von Systemen sein. Einbezogen wird ausdrücklich eine vereinfachte und kontinuierliche Auslieferung (Continous Delivery) und sowie der Betrieb unter qualitätssichernden Aspekten.

Conway's Law: Das Gesetz von Conway ist eine nach dem US-amerikanischen Informatiker Melvin Edward Conway benannte Beobachtung, dass die Strukturen von Systemen durch die Kommunikationsstrukturen der sie umsetzenden Organisationen vorbestimmt sind. (Quelle: Wikipedia)

ESB (Enterprise Service Bus): Schwergewichtige Infrastrukturkomponente, die Dienste zu Aspekten wie Routing, Security, Datenkonvertierung, u.a. zur Verfügung stellt. Zentraler Bestandteil einer klassischen SOA..

Senior Chief Consultant bei Object Systems

 

Kommentare

Derzeit gibt es keine Kommentare. Schreibe den ersten Kommentar!
Donnerstag, 18. April 2024

Sicherheitscode (Captcha)

×
Informiert bleiben!

Bei Updates im Blog, informieren wir per E-Mail.

Weitere Artikel in der Kategorie