Mikrodienste entwerfen und strukturieren Best Practices zu Microservice-Design, Logik und APIs

Autor / Redakteur: Georg Lauer * / Stephan Augsten |

Wie sieht gutes Microservice-Design aus? Wenn Micro klein ist – wie klein sind dann die Services? Wie können APIs und Frameworks dafür aussehen? Was kann eine lose Kopplung gefährden? Und wie stellt man Datenpersistenz sicher? Ein Blick auf die Entwicklung aus einer System-Perspektive.

Anbieter zum Thema

Microservices müssen ein großes Ganzes ergeben, aber sich gleichzeitig auch flexibel strukturieren lassen.
Microservices müssen ein großes Ganzes ergeben, aber sich gleichzeitig auch flexibel strukturieren lassen.
(Bild gemeinfrei: Aenigmatis-3D - Pixabay.com)

Nachdem wir uns in den vorigen Artikeln zu Microservices mit deren prinzipiellen Eigenschaften, den institutionellen Voraussetzungen und organisatorischen Vorarbeiten beschäftigt haben, geht es jetzt an das eigentliche Design. Eins vorneweg: Die optimale Größe des Microservices bemisst sich nicht nach Kriterien wie Code-Zeilen. Es kommt vielmehr auf den Business-Kontext an, also auf die Funktion und den spezifischen Kontext des Microservices im System.

Die Aufteilungslogik hinter Microservices

Bei der Einführung von Microservices zerlegt man bestehende Komponenten, um ihre Qualität schneller verbessern zu können, ohne dass sie an Zuverlässigkeit verlieren. Es stellt sich grundsätzlich die Frage, nach welcher Logik man ein größeres System herunterbrechen sollte.

  • Es kann sinnvoll sein, für rechenintensive Dienste andere Technologien zu nutzen (sei es C, Rust oder Go) als für I/O-fokussierte Features (etwa Node.js).
  • Eine geographische Verteilung bietet sich an, wenn Qualifikationen im Team unterschiedlich verteilt sind – oder man auf rechtliche, geschäftliche oder kulturelle Anforderungen Rücksicht nehmen muss.
  • Im Domain-Driven-Design setzt man auf Kontext: Diese Art zu modellieren versucht, einem System Grenzen zu geben, die naturgemäß in der Praxis nicht einheitlich sind – eine ideale Vorlage für Microservices.

Die optimale Größe

Grundsätzlich gilt: je kleiner, desto besser. Kleinere „Batch sizes“ sind ohnehin bereits die Grundlage von Methoden wie Agile Development, Lean Startup und Continuous Delivery. Je kleiner der Microservice, desto einfacher ist die schnelle Entwicklung (agil), desto schneller lässt der Service sich weitergeben (lean) und desto häufiger kann er live geschaltet werden (continuous delivery). Orientiert man sich am Domain-Driven-Design, darf ein Microservice nicht so klein werden, dass er einen gegebenen Kontext zerhackt. Ergänzt man diese Modellierung um andere Ansätze, wird man flexibler.

API Design

API Design ist genauso wichtig wie der Quellcode selbst. Microservice-Komponenten entfalten erst dann ihren Nutzen, wenn sie mit anderen Komponenten im System kommunizieren können. Das läuft bei allen Komponenten über Programmierschnittstellen, also APIs. Diese APIs sind lose gekoppelt, um die unabhängige Nutzung der Komponenten zu sichern. Die Erstellung von APIs kann sich an zwei Methoden orientieren:

a) Nachrichten-orientiert

APIs bieten einerseits einen allgemeinen Einstiegspunkt in eine Komponente (z. B. IP-Adresse und Portnummer), ermöglichen es aber andererseits, aufgabenspezifische Nachrichten zu übermitteln. Dies ermöglicht Änderungen am Inhalt der Nachrichten und die sichere, stetige Überarbeitung von Komponenten.

Netflix zum Beispiel nutzt dafür Formate wie Avro, Protobuf und Thrift über TCP/IP für die interne Kommunikation und JSON über http für die Kommunikation zu den Anwendern (also zu Mobiltelefonen, Browsern etc.). Dieser Ansatz – ein System, in dem etliche Services Botschaften über APIs austauschen – ist extrem effizient.

b) Hypermedia-gestützt

Aber es geht noch besser: Bei Implementierungen, die Hypermedia nutzen, enthalten die zwischen den Komponenten übertragenen Nachrichten Daten sowie Beschreibungen möglicher Aktionen (z.B. Metadaten, Links und Formulare). Hier sind nicht nur die Daten, sondern auch die Kontrollen lose gekoppelt. Dieser Ansatz ist effizienter: Er hebt die Message-basierte Version auf eine neue Ebene, indem Botschaften zwischen den Komponenten mehr als nur Inhalte enthalten. Sie geben zum Beispiel Hinweise, was man mit der API tun könnte.

Sind nicht nur die Daten lose gekoppelt, sondern auch die Aktionen, so werden die APIs leichter auffindbar, verwendbar und passen besser zum Konzept der Microservice-Architektur. Das API Gateway von Amazon und die APIs von AppStream unterstützen Antworten im Format der Hypertext Application Language (HAL). Diese APIs arbeiten ähnlich wie HTML für Browser: http-Botschaften werden im HTML-Format an eine IP-Adresse und eine Port-Nummer (meist 80 oder 443) geschickt.

Enge Kopplung vermeiden

Lose Kopplung ist ein essenzielles Merkmal von Microservices. Doch macht man Geschäftsfunktionen zu Design-Elementen für APIs, so kommt man schnell in einen Kopplungskonflikt.

Risiko eins: Geteilte Daten. Oft ist es nicht möglich, Daten nur in einem Microservice zu nutzen – Daten werden für viele Funktionen gleichzeitig benötigt. Nun gibt es einige Methoden, die eine enge Kopplung vermeiden helfen. So könnte man beispielsweise nicht die Daten, die einen Zustand beschreiben, speichern, sondern Events, die dazu geführt haben (wie Geldbewegungen auf einem Konto, die zu einem Kontostand führen).

Ergänzend trennt man die einzelnen Abfragen – beispielsweise Datenauswertungen für Reports – von den Updates (ein Ansatz namens Command Query Responsibility Segregation CQRS). Ein Service kann dann Benachrichtigungen über Veränderungen (also Events) quasi abonnieren. Mit CQRS kann man Datenmodelle komplett trennen – ein Microservice muss das Modell eines anderen nicht einmal kennen. Allerdings erhöht dieses Vorgehen die Komplexität der Implementierung und sollte nur angewandt werden, wenn wirklich nötig.

Darüber hinaus stellt sich die Frage, wie man dieses „Abonnement“ als http API oder Microservice standardisieren kann. Dazu muss man den Workflow auf einen bestehenden Standard wie PubSubHubbub (ein Standard, der eigentlich für RSS und Atom Feeds im Blogging-Kontext entwickelt wurde, aber gut in einem Hypermedia API-Enabled Workflow performt) aufsetzen und weitere Standards definieren.

Risiko zwei: Workflows. Viele Prozesse bestehen aus mehreren Schritten, von denen jeder essenziell ist. Hierfür gibt es sogenannte „Sagas“, die transaktionsähnliche, reversible Workflows in verteilten, lose gekoppelten Umgebungen ermöglichen. Jeder Arbeitsschritt wird autonom ausgeführt und fügt der weiterzuleitenden Botschaft einen „Routing Slip“ hinzu – eine Information, wie die Transaktion rückgängig gemacht werden kann. Geht in einem Schritt etwas schief, hilft der Routing Slip, um die kompensierende Aktion auszulösen. Damit erreichen Sagas eine hohe Fehlertoleranz und passen gut in eine Microservice-Architektur.

Und was ist mit Abhängigkeiten?

Traditionell wurden IT-Architekturen mit zentralisiertem Speicher entwickelt. Dies macht die Organisation einfacher, denn so können sich Teams spezialisieren (also etwa aufteilen in Datenbank- oder Systemadministratoren), die diese komplexen Systeme warten. Bei einer klaren Trennung von operativem Betrieb und Development haben die Entwickler mit dieser Komplexität nichts zu tun.

Die neuen Ansätze wie DevOps wollen genau dies nicht mehr. Microservices hingegen embedden alle ihre Abhängigkeiten, um unabhängig einsetzbar zu bleiben. Danach aber müsste jeder Microservice seine eigene Datenbank, seinen Key-Value-Store, seinen Suchindex, die Queue etc. embedden – und das ist unhaltbar.

In der Praxis muss nicht jeder Microservice jede einzelne Abhängigkeit enthalten, nur um mobil und unabhängig zu sein. Warum? Es genügt sicherzustellen, dass der Anwendungsort diese „heavy assets“ wie Datenbankcluster bereitstellt – allerdings so, dass sie schnell anwendbar und erkennbar sind.

Microservices hingegen müssen so geschrieben sein, dass sie diese Assets beim Einsatz schnell erkennen und nutzen. Verschiebt man beispielsweise einen Microservice in ein anderes Rechenzentrum, sollte man dort auch einen funktionierenden Cassandra Cluster o.ä. erwarten und muss nur eine Möglichkeit finden, ihn auf die notwendigen Daten zugreifen zu lassen. Die Entscheidung „einbetten oder voraussetzen“ sollte sich daran orientieren, welche Option die Mobilität des Microservices erhöht.

Keine Regeln, aber Best Practices

Georg Lauer
Georg Lauer
(Bild: CA Technologies)

Zwar kommt man nur in den Genuss der Vorteile von Microservices, wenn diese auch gut designt sind. Allgemeingültige Regeln, wie Standards oder APIs aussehen könnten, gibt es allerdings nicht. Immerhin aber gibt es bereits einige Beispiele, wie das Design gut funktionieren kann und Wege, die Fallen dabei zu vermeiden. Sicherlich werden sich weiterhin Erfolgsrezepte herauskristallisieren.

Vieles aber ist – unabhängig von gutem Design – auch abhängig von der dahinterliegenden Architektur. Darüber reden wir in unserem nächsten Artikel.

* Georg Lauer ist als Senior Principal Business Technology Architect bei CA Technologies tätig.

(ID:45096334)