Erhalten Sie Zugang zu diesem und mehr als 300000 Büchern ab EUR 5,99 monatlich.
Microservices haben viele Vorteile: Effizient mehr Features umsetzen, Software schneller in Produktion bringen, Robustheit und einfache Skalierbarkeit zählen dazu. Aber die Implementierung einer Microservices-Architektur und die Auswahl der notwendigen Technologien sind schwierige Herausforderungen. Dieses Buch zeigt Microservices-Rezepte, die Architekten anpassen und zu einem Microservices-Menü kombinieren können. So kann die Implementierung der Microservices individuell auf die Anforderungen im Projekt angepasst werden. Eberhard Wolff führt zunächst in Microservices, Self-contained Systems, Mikro- und Makro-Architektur und die Migration hin zu Microservices ein. Der zweite Teil zeigt die Microservices-Rezepte: Basis-Technologien wie Docker oder PaaS, Frontend-Integration mit Links, JavaScript oder ESI (Edge Side Includes). Es schließen sich asynchrone Microservices mit Apache Kafka oder REST Atom an. Bei den synchronen Ansätzen bespricht das Buch REST mit dem Netflix-Stack, Consul und Kubernetes. Zu jedem Rezept gibt es Hinweise zu Variations- und Kombinationsmöglichkeiten. Der Ausblick greift den Betrieb von Microservices auf und zeigt außerdem, wie der Leser ganz konkret mit Microservices beginnen kann. Das Buch bietet das technische Rüstzeug, um eine Microservices-Architektur umzusetzen. Demo-Projekte und Anregungen für die Vertiefung im Selbststudium runden das Buch ab.
Sie lesen das E-Book in den Legimi-Apps auf:
Seitenzahl: 403
Das E-Book (TTS) können Sie hören im Abo „Legimi Premium” in Legimi-Apps auf:
Eberhard Wolff arbeitet seit mehr als fünfzehn Jahren als Architekt und Berater – oft an der Schnittstelle zwischen Business und Technologie. Er ist Fellow bei der innoQ. Als Autor hat er über hundert Artikel und Bücher geschrieben – u.a. über Continuous Delivery – und als Sprecher auf internationalen Konferenzen vorgetragen. Sein technologischer Schwerpunkt liegt auf modernen Architekturansätzen – Cloud, Continuous Delivery, DevOps, Microservices oder NoSQL spielen oft eine Rolle.
Sie können dieses E-Book ebenfalls und kostenlos in der englischen Version hier herunterladen.
Zu diesem Buch – sowie zu vielen weiteren dpunkt.büchern – können Sie auch das entsprechende E-Book im PDF-Format herunterladen. Werden Sie dazu einfach Mitglied bei dpunkt.plus+:
www.dpunkt.plus
Eberhard Wolff
Grundlagen, Konzepte und Rezepte
Eberhard [email protected]
Lektorat: René Schönfeldt
Projektmanagement: Miriam Metsch
Copy-Editing: Petra Kienle, Fürstenfeldbruck
Satz: III-satz, www.drei-satz.de
Herstellung: Susanne Bröckelmann
Umschlaggestaltung: Helmut Kraus, www.exclam.de
Druck und Bindung: M.P. Media-Print Informationstechnologie GmbH, 33100 Paderborn
Bibliografische Information der Deutschen Nationalbibliothek
Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.
ISBN:
Buch 978-3-86490-526-1
PDF 978-3-96088-461-3
ePub 978-3-96088-462-0
mobi 978-3-96088-463-7
1. Auflage 2018
Copyright © 2018 dpunkt.verlag GmbH
Wieblinger Weg 17
69123 Heidelberg
Die vorliegende Publikation ist urheberrechtlich geschützt. Alle Rechte vorbehalten. Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder die Verwendung in elektronischen Systemen.
Es wird darauf hingewiesen, dass die im Buch verwendeten Soft- und Hardware-Bezeichnungen sowie Markennamen und Produktbezeichnungen der jeweiligen Firmen im Allgemeinen warenzeichen-, marken- oder patentrechtlichem Schutz unterliegen.
Alle Angaben und Programme in diesem Buch wurden mit größter Sorgfalt kontrolliert. Weder Autor noch Verlag können jedoch für Schäden haftbar gemacht werden, die in Zusammenhang mit der Verwendung dieses Buches stehen.
5 4 3 2 1
Einleitung
Teil IArchitekturgrundlagen
1Microservices
2Mikro- und Makro-Architektur
3Self-contained System (SCS)
4Migration
Teil IITechnologie-Stacks
5Docker-Einführung
6Technische Mikro-Architektur
7Konzept: Frontend-Integration
8Rezept: Links und clientseitige Integration
9Rezept: serverseitige Integration mit Edge Side Includes (ESI)
10Konzept: Asynchrone Microservices
11Rezept: Messaging und Kafka
12Rezept: Asynchrone Kommunikation mit Atom und REST
13Konzept: Synchrone Microservices
14Rezept: REST mit dem Netflix-Stack
15Rezept: REST mit Consul und Apache httpd
16Konzept: Microservices-Plattformen
17Rezept: Docker-Container mit Kubernetes
18Rezept: PaaS mit Cloud Foundry
Teil IIIBetrieb
19Konzept: Betrieb
20Rezept: Monitoring mit Prometheus
21Rezept: Log-Analyse mit dem Elastic Stack
22Rezept: Tracing mit Zipkin
23Und nun?
Anhang
AInstallation der Umgebung
BMaven-Kommandos
CDocker- und Docker-Compose-Kommandos
Index
Einleitung
Teil IArchitekturgrundlagen
1Microservices
1.1Microservices: Definition
1.2Gründe für Microservices
1.3Herausforderungen
1.4Independent-Systems-Architecture-Prinzipien (ISA)
1.5Bedingungen
1.6Prinzipien
1.7Bewertung
1.8Variationen
1.9Fazit
2Mikro- und Makro-Architektur
2.1Bounded Context und Strategic Design
2.2Technische Mikro- und Makro-Architektur
2.3Betrieb: Mikro- oder Makro-Architektur
2.4Mikro-Architektur bevorzugen!
2.5Organisatorische Aspekte
2.6Variationen
2.7Fazit
3Self-contained System (SCS)
3.1Gründe für den Begriff Self-contained Systems
3.2Self-contained Systems: Definition
3.3Ein Beispiel
3.4SCS und Microservices
3.5Herausforderungen
3.6Variationen
3.7Fazit
4Migration
4.1Gründe für eine Migration
4.2Typische Migrationsstrategie
4.3Alternative Strategien
4.4Build, Betrieb und Organisation
4.5Variationen
4.6Fazit
Teil IITechnologie-Stacks
5Docker-Einführung
5.1Docker für Microservices: Gründe
5.2Docker-Grundlagen
5.3Docker-Installation und Docker-Kommandos
5.4Docker-Hosts mit Docker Machine installieren
5.5Dockerfiles
5.6Docker Compose
5.7Variationen
5.8Fazit
6Technische Mikro-Architektur
6.1Anforderungen
6.2Reactive
6.3Spring Boot
6.4Go
6.5Variationen
6.6Fazit
7Konzept: Frontend-Integration
7.1Frontend: Monolith oder modular?
7.2Optionen
7.3Resource-oriented Client Architecture (ROCA)
7.4Herausforderungen
7.5Vorteile
7.6Variationen
7.7Fazit
8Rezept: Links und clientseitige Integration
8.1Überblick
8.2Beispiel
8.3Rezept-Variationen
8.4Experimente
8.5Fazit
9Rezept: serverseitige Integration mit Edge Side Includes (ESI)
9.1ESI: Konzepte
9.2Beispiel
9.3Varnish
9.4Rezept-Variationen
9.5Experimente
9.6Fazit
10Konzept: Asynchrone Microservices
10.1Definition
10.2Events
10.3Herausforderungen
10.4Vorteile
10.5Variationen
10.6Fazit
11Rezept: Messaging und Kafka
11.1Message-oriented Middleware (MOM)
11.2Die Architektur von Kafka
11.3Events mit Kafka
11.4Beispiel
11.5Rezept-Variationen
11.6Experimente
11.7Fazit
12Rezept: Asynchrone Kommunikation mit Atom und REST
12.1Das Atom-Format
12.2Beispiel
12.3Rezept-Variationen
12.4Experimente
12.5Fazit
13Konzept: Synchrone Microservices
13.1Definition
13.2Herausforderungen
13.3Vorteile
13.4Variationen
13.5Fazit
14Rezept: REST mit dem Netflix-Stack
14.1Beispiel
14.2Eureka: Service Discovery
14.3Router: Zuul
14.4Lastverteilung: Ribbon
14.5Resilience: Hystrix
14.6Rezept-Variationen
14.7Experimente
14.8Fazit
15Rezept: REST mit Consul und Apache httpd
15.1Beispiel
15.2Service Discovery: Consul
15.3Routing: Apache httpd
15.4Consul Template
15.5Consul und Spring Boot
15.6DNS und Registrator
15.7Rezept-Variationen
15.8Experimente
15.9Fazit
16Konzept: Microservices-Plattformen
16.1Definition
16.2Variationen
16.3Fazit
17Rezept: Docker-Container mit Kubernetes
17.1Kubernetes
17.2Das Beispiel mit Kubernetes
17.3Beispiel im Detail
17.4Weitere Kubernetes-Features
17.5Rezept-Variationen
17.6Experimente
17.7Fazit
18Rezept: PaaS mit Cloud Foundry
18.1PaaS: Definition
18.2Cloud Foundry
18.3Das Beispiel mit Cloud Foundry
18.4Rezept-Variationen
18.5Experimente
18.6Serverless
18.7Fazit
Teil IIIBetrieb
19Konzept: Betrieb
19.1Warum Betrieb wichtig ist
19.2Ansätze für den Betrieb von Microservices
19.3Auswirkungen der behandelten Technologien
19.4Fazit
20Rezept: Monitoring mit Prometheus
20.1Grundlagen
20.2Metriken bei Microservices
20.3Metriken mit Prometheus
20.4Beispiel mit Prometheus
20.5Rezept-Variationen
20.6Experimente
20.7Fazit
21Rezept: Log-Analyse mit dem Elastic Stack
21.1Grundlagen
21.2Logging mit dem Elastic Stack
21.3Beispiel
21.4Rezept-Variationen
21.5Experimente
21.6Fazit
22Rezept: Tracing mit Zipkin
22.1Grundlagen
22.2Tracing mit Zipkin
22.3Beispiel
22.4Rezept-Variationen
22.5Fazit
23Und nun?
Anhang
AInstallation der Umgebung
BMaven-Kommandos
CDocker- und Docker-Compose-Kommandos
Index
Microservices sind einer der wichtigsten Software-Architektur-Trends, grundlegende Werke über Microservices gibt es schon. Unter anderem auch das Microservices-Buch (http://microservices-buch.de)1 vom Autor dieses Werks. Warum noch ein weiteres Buch über Microservices?
Es ist eine Sache, eine Architektur zu definieren. Sie umzusetzen, ist eine ganz andere Sache. Dieses Buch stellt technologische Ansätze für die Umsetzung von Microservices vor und zeigt die jeweiligen Vor- und Nachteile.
Dabei geht es um Technologien für ein Microservices-System als Ganzes. Jeder Microservice kann anders implementiert werden. Daher sind die technologischen Entscheidungen für die Frameworks innerhalb der Microservices nicht so wichtig wie die Entscheidungen für das gesamte System. Die Entscheidung für ein Framework kann in jedem Microservice revidiert werden. Technologien für das Gesamtsystem sind kaum änderbar.
Um Microservices zu verstehen, ist eine Einführung in die Architektur, ihre Vor- und Nachteile und Spielweisen unerlässlich. Die Grundlagen sind in dem Buch soweit erläutert, wie sie für das Verständnis der praktischen Umsetzungen notwendig sind.
Microservices benötigen Lösungen für verschiedene Herausforderungen. Dazu zählen Konzepte zur Integration (Frontend-Integration, synchrone und asynchrone Microservices) und zum Betrieb (Monitoring, Log-Analyse, Tracing). Microservices-Plattformen wie PaaS oder Kubernetes stellen vollständige Lösungen für den Betrieb von Microservices dar.
Das Buch nutzt Rezepte als Metapher für die Technologien, mit denen die Konzepte umgesetzt werden können. Jeder Ansatz hat viel mit einem Rezept gemeinsam:
Jedes Rezept ist
praktisch
beschrieben, einschließlich einer technischen Implementierung als Beispiel. Bei den Beispielen liegt der Fokus auf
Einfachheit
. Jedes Beispiel kann leicht nachvollzogen, erweitert und modifiziert werden.
Das Buch bietet dem Leser eine
Vielzahl von Rezepten
. Der Leser muss aus diesen Rezepten für sein Projekt
eine Auswahl
treffen, so wie ein Koch es für sein Menü tut. Das Buch zeigt verschiedene Optionen. In der Praxis muss fast jedes Projekt anders angegangen werden. Dazu bieten die Rezepte die Basis.
Zu jedem Rezept gibt es
Rezept-Variationen
. Schließlich kann ein Rezept auf viele verschiedene Arten und Weisen umgesetzt werden. Das gilt auch für die Technologien in diesem Buch. Manchmal sind die Variationen so einfach, dass sie direkt als
Experiment
in dem ablauffähigen Beispiel umgesetzt werden können.
Für jedes Rezept gibt es ein ablauffähiges Beispiel mit der konkreten Technologie. Die Beispiele sind einzeln ablauffähig und bauen nicht aufeinander auf. So kann der Leser sich mit den für ihn interessantesten Rezepten und Beispielen beschäftigen, ohne sich dabei mit anderen Beispielen befassen zu müssen.
So liefert das Buch einen Einstieg, um einen Überblick über die Technologien zu bekommen und einen Technologie-Stack auszuwählen. Danach kann der Leser sich anhand der im Buch enthaltenen Links weiter in die relevanten Technologien vertiefen.
Dieses Buch besteht aus drei Teilen.
Teil I gibt eine Einführung in die Architektur-Grundlagen, die mit Kapitel 1 beginnt.
Kapitel 1
klärt den Begriff »Microservice« und
Kapitel 3
erläutert Self-contained Systems als besonders praktikablen Ansatz für Microservices.
In einem Microservices-System gibt es die Ebenen der Mikro- und Makro-Architektur, die globale und lokale Entscheidungen darstellen (
Kapitel 2
).
Oft sollen alte Systeme in Microservices migriert werden (
Kapitel 4
).
Abb. 1Überblick über Teil I
Technologie-Stacks stehen im Mittelpunkt von Teil II, der mit Kapitel 5 beginnt.
Docker
ist die Basis vieler Microservices-Architekturen (
Kapitel 5
). Es erleichtert das Ausrollen von Software und den Betrieb der Services.
Die
technische Mikro-Architektur
(
Kapitel 6
) beschreibt Technologien, die zur Implementierung eines Microservice genutzt werden können.
Eine Möglichkeit zur Integration ist das Konzept zur
Integration am WebFrontend
(
Kapitel 7
). Die Frontend-Integration führt zu einer losen Kopplung der Microservices und einer hohen Flexibilität.
Das Rezept aus
Kapitel 8
setzt für die Web-Frontend-Integration auf
Links
und auf
JavaScript
für das dynamische Nachladen von Inhalten. Dieser Ansatz ist einfach realisierbar und nutzt gängige Web-Technologien.
Auf dem Server kann die Integration mit
ESI (Edge Side Includes)
erfolgen (
Kapitel 9
). ESI ist in Caches implementiert, sodass das System eine höhere Performance und Zuverlässigkeit erreichen kann.
Das Konzept der
asynchronen Kommunikation
steht im Mittelpunkt von
Kapitel 10
. Asynchrone Kommunikation verbessert die Zuverlässigkeit und entkoppelt das Systems.
Eine asynchrone Technologie ist
Apache Kafka
(
Kapitel 11
), mit der Messages verschickt werden können. Kafka speichert die Nachrichten dauerhaft ab und erlaubt so die Rekonstruktion des Zustands eines Microservices aus den Nachrichten.
Die Alternative für asynchrone Kommunikation ist
Atom
(
Kapitel 12
), ein HTTP- und REST-basiertes Protokoll. Atom nutzt eine REST-Infrastruktur und kann daher sehr einfach umgesetzt werden.
Kapitel 13
stellt vor, wie das Konzept
synchroner Microservices
umgesetzt werden kann. Die synchrone Kommunikation zwischen Microservices wird in der Praxis sehr häufig genutzt, obwohl dieser Ansatz bei Antwortzeiten und Zuverlässigkeit Herausforderungen bereithalten kann.
Der
Netflix-Stack
(
Kapitel 14
) bietet Eureka für Service Discovery, Ribbon für Load Balancing, Hystrix für Resilience und Zuul für Routing. Netflix wird vor allem in der Java Community breit genutzt.
Consul
(
Kapitel 15
) ist eine Alternative für Service Discovery. Consul hat sehr viele Features und kann mit einer breiten Palette an Technologien genutzt werden.
Kapitel 16
erläutert das Konzept der
Microservices-Plattformen
, die den Betrieb und die Kommunikation der Microservices unterstützen.
Kubernetes
(
Kapitel 17
) ist eine Microservices-Plattform, die Docker Container ausführen kann, aber auch Service Discovery und Load Balancing löst. Der Microservice bleibt von dieser Infrastruktur unabhängig.
PaaS (Platform as a Service)
ist eine weitere Microservices-Plattform (
Kapitel 18
), die am Beispiel Cloud Foundry erläutert wird. Cloud Foundry ist sehr flexibel und kann auch im eigenen Rechenzentrum betrieben werden.
Abb. 2Überblick über Teil II
Den Betrieb einer Vielzahl von Microservices sicherzustellen, ist eine große Herausforderung. Teil III (ab Kapitel 19) diskutiert mögliche Rezepte zur Lösung.
Das
Kapitel 19
erläutert
Grundlagen
, und warum der Betrieb von Microservices so schwierig ist.
Im
Kapitel 20
geht es um
Monitoring
und das Werkzeug Prometheus. Prometheus unterstützt multidimensionale Datenstrukturen und kann die Monitoring-Werte auch von vielen Microservice-Instanzen analysieren.
Die
Analyse von Log-Daten
steht im Mittelpunkt von
Kapitel 21
. Als Werkzeug zeigt das Kapitel den Elastic Stack. Dieser Stack ist sehr weit verbreitet und stellt eine gute Basis für die Analyse auch großer Log-Datenmengen dar.
Tracing
verfolgt Aufrufe zwischen Microservices (
Kapitel 22
). Dazu kommt Zipkin zum Einsatz. Zipkin unterstützt verschiedene Plattformen und stellt einen De-facto-Standard für Tracing dar.
Abb. 3Überblick über Teil III
Abschließend bietet das Kapitel 23 noch einen Ausblick.
Die Anhänge erklären die Installation der Software (Anhang A), die Benutzung des Build-Werkzeugs Maven (Anhang B) sowie Docker und Docker Compose (Anhang C), mit denen die Umgebungen für die Beispiele betrieben werden.
Das Buch erläutert Grundlagen und technische Aspekte von Microservices. Es ist für verschiedene Zielgruppen interessant:
Entwicklern
bietet
Teil II
eine Hilfe bei der Auswahl eines geeigneten Technologie-Stacks. Die Beispielprojekte dienen als Basis für das Einarbeiten in die Technologien. Die Microservices in den Beispielprojekten sind in Java mit dem Spring-Framework geschrieben, aber die Technologien in den Beispielen dienen zur Integration von Microservices, sodass weitere Microservices in anderen Sprachen ergänzt werden können.
Teil III
rundet das Buch in Richtung Betrieb ab, der für Entwickler immer wichtiger wird, und
Teil I
erläutert die grundlegenden Architektur-Konzepte.
Architekten
vermittelt
Teil I
das grundlegende Wissen über Microservices.
Teil II
und
Teil III
zeigen praktische Rezepte und Technologien, um die Architekturen umzusetzen. Damit geht das Buch weiter als ein reines Microservices-Architektur-Buch.
Für Experten aus den Bereichen
DevOps
und
Betrieb
stellen die Rezepte in
Teil III
eine Basis für eine Technologie-Bewertung von Betriebsaspekten wie Log-Analyse, Monitoring und Tracing von Microservices dar.
Teil II
zeigt Technologien für Deployment wie Docker, Kubernetes oder Cloud Foundry.
Teil I
beschreibt als Hintergrund die Konzepte hinter dem Microservices-Architektur-Ansatz.
Manager
bekommen in
Teil I
einen Überblick über die Vorteile des Architektur-Ansatzes und die besonderen Herausforderungen. Sie können bei Interesse an technischen Details
Teil II
und
Teil III
lesen.
Das Buch setzt grundlegendes Wissen über Software-Architektur und Software-Entwicklung voraus. Die praktischen Beispiele sind so dokumentiert, dass sie mit wenig Vorwissen ausgeführt werden können. Das Buch fokussiert auf Technologien, die für Microservices in verschiedenen Programmiersprachen genutzt werden können. Die Beispiele sind in Java mit den Frameworks Spring Boot und Spring Cloud geschrieben, sodass für Änderungen an dem Code Java-Kentnisse notwendig sind.
Das Buch vermittelt vor allem Technologien. Zu jeder Technologie in jedem Kapitel gibt es ein Beispiel. Um schnell praktische Erfahrungen mit den Technologien zu sammeln und anhand der Beispiele nachzuvollziehen, gibt es einen Quick Start:
Zunächst muss auf dem Rechner die notwendige Software
installiert
sein. Die Installation beschreibt
Anhang A
.
Der Build der Beispiele erfolgt mit
Maven
. Den Umgang mit Maven erläutert
Anhang B
.
Die Beispiele setzen alle auf
Docker
und
Docker Compose
auf. Das
Anhang C
beschreibt die wichtigsten Befehle für Docker und Docker Compose.
Sowohl für den Build mit Maven als auch für Docker und Docker Compose enthalten die Kapitel Anleitungen zum Troubeshooting.
Die Beispiele sind in folgenden Abschnitten erläutert:
Konzept
Rezept
Abschnitt
Frontend-Integration
Links & clientseitige Integration
8.2
Frontend-Integration
Edge Side Includes (ESI)
9.2
Asynchrone Microservices
Kafka
11.4
Asynchrone Microservices
REST & Atom
12.2
Synchrone Microservices
Netflix-Stack
14.1
Synchrone Microservices
Consul & Apache httpd
15.1
Microservices-Plattform
Kubernetes
17.3
Microservices-Plattform
Cloud Foundry
18.3
Betrieb
Monitoring mit Prometheus
20.4
Betrieb
Log-Analyse mit Elastic Stack
21.3
Betrieb
Tracing mit Zipkin
22.2
Die Projekte sind alle auf GitHub verfügbar. In den Projekten gibt es jeweils eine Datei WIE-LAUFEN.md mit einer Schritt-für-Schritt-Anleitung, wie die Demos installiert und gestartet werden können.
Die Beispiele bauen nicht aufeinander auf. Dadurch ist es möglich, mit einem beliebigen Beispiel loszulegen.
Ich möchte allen danken, mit denen ich über Microservices diskutiert habe, die mir Fragen gestellt oder mit mir zusammengearbeitet haben. Es sind viel zu viele, um sie alle zu nennen. Der Dialog hilft sehr und macht Spaß!
Viele der Ideen und auch die Umsetzungen sind ohne meine Kollegen bei der innoQ nicht denkbar. Insbesondere möchte ich Alexander Heusingfeld, Christian Stettler, Christine Koppelt, Daniel Westheide, Gerald Preissler, Jörg Müller, Lucas Dohmen, Marc Giersch, Michael Simons, Michael Vitz, Philipp Neugebauer, Simon Kölsch, Sophie Kuna und Stefan Lauer danken.
Weiteres wichtiges Feedback kam von Merten Driemeyer und Olcay Tümce.
Schließlich habe ich meinen Freunden, Eltern und Verwandten zu danken, die ich für das Buch oft vernachlässigt habe – insbesondere meiner Frau.
Und natürlich gilt mein Dank all jenen, die an den in diesem Buch erwähnten Technologien gearbeitet und so die Grundlagen für Microservices gelegt haben.
Bei den Entwicklern der Werkzeuge von https://www.softcover.io/ möchte ich mich ebenfalls bedanken.
Last but not least möchte ich dem dpunkt.verlag und René Schönfeldt danken, der mich sehr professionell bei der Erstellung des Buchs unterstützt hat.
Die Website zum Buch ist http://microservices-praxisbuch.de/. Dort finden sich die Errata und Links zu den Beispielen.
Der erste Teil des Buchs stellt die grundlegenden Ideen der Microservices-Architektur vor.
Das Kapitel 1 klärt die Grundlagen von Microservices: Was sind Microservices? Welche Vor- und Nachteile hat diese Architektur?
Das Kapitel 3 beschreibt Self-contained Systems. Sie sind eine Sammlung von Best Practices für Microservices-Architekturen, bei der eine starke Unabhängigkeit und Web-Anwendungen im Mittelpunkt stehen. Neben Vor- und Nachteilen geht es um mögliche Variationen dieser Idee.
Microservices bieten viele Freiheiten. Dennoch müssen einige Entscheidungen übergreifend über alle Microservices eines Systems getroffen werden. Das Kapitel 2 stellt das Konzept der Mikro- und Makro-Architektur vor. Die Mikro-Architektur umfasst alle Entscheidungen, die für jeden Microservice anders getroffen werden können. Die Makro-Architektur sind die Entscheidungen, die für alle Microservices gelten. Neben den Bestandteilen einer Mikro- und Makro-Architektur stellt das Kapitel auch vor, wer eine Makro-Architektur entwirft.
Die meisten Microservices-Projekte migrieren ein vorhandenes System in eine Microservices-Architektur. Daher stellt das Kapitel 4 mögliche Ziele einer Migration und verschiedene Migrationsstrategien vor.
Dieses Kapitel bietet eine Einführung in das Thema »Microservices«. Das Studium dieses Kapitels vermittelt dem Leser:
Vorteile (
Abschnitt 1.2
) und Nachteile (
Abschnitt 1.3
) von Microservices, um die Einsetzbarkeit dieses Architektur-Ansatzes in einem konkreten Projekt abschätzen zu können.
Die Vorteile zeigen auf, welche Probleme Microservices lösen und wie der Architekturansatz für bestimmte Szenarien angepasst werden kann.
Die Nachteile verdeutlichen, wo technische Risiken auftauchen können und wie man mit ihnen umgehen kann.
Schließlich haben Vor- und Nachteile Einfluss auf Technologie- und Architektur-Entscheidungen, die Vorteile verstärken und Nachteile vermindern sollen.
Leider gibt es für den Begriff »Microservice« keine allgemein anerkannte Definition. Im Rahmen dieses Buchs gilt folgende Definition:
Microservices sind unabhängig deploybare Module.
Beispielsweise kann ein E-Commerce-System in Module für den Bestellprozess, die Registrierung oder die Produktsuche aufgeteilt werden. Normalerweise wären alle diese Module gemeinsam in einer Anwendung implementiert. Dann kann eine Änderung in einem der Module nur in Produktion gebracht werden, indem eine neue Version der Anwendung und damit aller Module in Produktion gebracht wird. Wenn die Module aber als Microservices umgesetzt sind, kann der Bestellprozess nicht nur unabhängig von den anderen Modulen geändert werden, sondern er kann sogar unabhängig in Produktion gebracht werden.
Das beschleunigt das Deployment und verringert die Anzahl der notwendigen Tests, da nur ein Modul deployt wird. Im Extremfall wird durch die größere Entkopplung ein großes Projekt zu einer Menge kleinerer Projekte, die jeweils einen der Microservices verantworten.
Technisch ist es dazu notwendig, dass der Microservice ein eigener Prozess ist. Besser wäre eine eigene virtuelle Maschine oder ein Docker-Container, die Microservices noch stärker entkoppeln. Ein Deployment ersetzt dann den Docker-Container durch einen neuen Docker-Container, fährt die neue Version hoch und lässt dann die Request auf die Version umschwenken. Die anderen Microservices bleiben davon unbeeinflusst.
Diese Definition von Microservices als unabhängig deploybare Module hat mehrere Vorteile:
Sie ist sehr
kompakt
.
Sie ist sehr
allgemein
und umfasst praktisch alle Arten von Systemen, die üblicherweise als Microservices bezeichnet werden.
Die Definition beruft sich auf
Module
und damit auf ein altes, gut verstandenes Konzept. So können viele Ideen zur Modularisierung übernommen werden. Außerdem wird so deutlich, dass Microservices Teile eines größeren Systems sind und niemals für sich stehen können. Deswegen müssen Microservices zwangsläufig mit anderen Microservices integriert werden.
Das unabhängige Deployment ist eine Eigenschaft, die zu vielen Vorteilen führt (siehe
Abschnitt 1.2
) und daher sehr wichtig ist. So zeigt die Definition trotz der Kürze, was die
wesentliche Eigenschaft
eines Microservices tatsächlich ist.
Ein System, das nicht aus Microservices besteht, kann nur als Ganzes deployt werden. Es ist ein Deployment-Monolith. Natürlich kann der Deployment-Monolith in Module aufgeteilt sein. Über den internen Aufbau sagt dieser Begriff nichts aus.
Die Definition von Microservices trifft keine Aussage über die Größe eines Microservice. Der Name »Microservice« legt den Verdacht nahe, dass es um besonders kleine Services geht. Aber in der Praxis findet man sehr unterschiedliche Größen von Microservices. Einige Microservices beschäftigen ein ganzes Team, während andere nur hundert Zeilen lang sind. Die Größe eignet sich also tatsächlich nicht als Teil der Definition.
Für die Nutzung von Microservices gibt es eine Vielzahl von Gründen.
Ein Grund für den Einsatz von Microservices ist die Skalierung der Entwicklung. Große Teams sollen gemeinsam an einem komplexen Projekt arbeiten. Mithilfe von Microservices können die Teams weitgehend unabhängig arbeiten:
Die meisten technischen Entscheidungen können die Teams allein treffen. Wenn die Microservices als Docker-Container ausgeliefert werden, muss jeder Docker-Container nur eine Schnittstelle für andere Container anbieten. Der interne Aufbau des Containers ist egal, solange die Schnittstelle vorhanden ist und korrekt funktioniert. Deswegen ist es beispielsweise egal, in welcher Programmiersprache der Microservice geschrieben wurde. Also kann das Team diese Entscheidung allein treffen. Natürlich kann die Wahl der Programmiersprache eingeschränkt werden, um Wildwuchs und zu große Komplexität zu vermeiden. Aber auch wenn die Wahl der Programmiersprache in einem Projekt eingeschränkt worden ist: Ein Bug Fix für eine Library kann ein Team immer noch unabhängig von den anderen Teams in einen Microservice einbauen.
Wenn ein neues Feature nur Änderungen an einem Microservice benötigt, kann es nicht nur unabhängig entwickelt werden, sondern es kann auch unabhängig in Produktion gebracht werden. So können die Teams vollständig unabhängig an Features arbeiten und sind fachlich unabhängig.
Durch Microservices können die Teams somit fachlich und technisch unabhängig arbeiten. Das erlaubt es, auch große Projekte ohne großen Koordinierungsaufwand zu stemmen.
Die Wartung und Erweiterung eines Legacy-Systems ist eine Herausforderung, weil der Code meistens schlecht strukturiert ist und Änderungen oft nicht durch Tests abgesichert sind. Dazu kann noch eine veraltete technologische Basis kommen.
Microservices helfen bei der Arbeit mit Legacy-Systemen, weil der Code nicht unbedingt geändert werden muss. Stattdessen können neben das alte System neue Microservices gestellt werden. Dazu ist eine Integration zwischen dem alten System und den Microservices notwendig – beispielsweise per Datenreplikation, per REST, Messaging oder auf der UI-Ebene. Außerdem müssen Probleme wie ein einheitliches Single Sign On über das alte System und die neuen Microservices gelöst werden.
Dafür sind die Microservices dann praktisch ein Greenfield: Es gibt keine vorhandene Codebasis, auf die aufgesetzt werden muss. Ebenso kann ein komplett anderer Technologiestack genutzt werden. Das erleichtert die Arbeit gegenüber einer Modifikation des Legacy-Systems erheblich.
Microservices versprechen, dass Systeme auch langfristig wartbar bleiben.
Ein wichtiger Grund dafür ist die Ersetzbarkeit der Microservices. Wenn ein einzelner Microservice nicht mehr wartbar ist, kann er neu geschrieben werden. Das ist im Vergleich zu einem Deployment-Monolithen mit weniger Aufwand verbunden, weil die Microservices kleiner sind als ein Deployment-Monolith.
Allerdings ist es schwierig, einen Microservice zu ersetzen, von dem viele andere Microservices abhängen, weil Änderungen die anderen Microservices beeinflussen können. Also müssen für die Ersetzbarkeit auch die Abhängigkeiten zwischen den Microservices gemanagt werden.
Die Ersetzbarkeit ist eine wesentliche Stärke von Microservices. Viele Entwickler arbeiten daran, Legacy-Systeme zu ersetzen. Aber beim Entwurf eines neues Systems wird viel zu selten die Frage gestellt, wie das System abgelöst werden kann, wenn es zu einem Legacy-System geworden ist. Die Ersetzbarkeit von Microservices ist eine mögliche Antwort.
Für die Wartbarkeit müssen die Abhängigkeiten zwischen den Microservices langfristig gemanagt werden. Auf dieser Ebene haben klassische Architekturen oft Schwierigkeiten: Ein Entwickler schreibt Code und führt dabei unabsichtlich eine neue Abhängigkeit zwischen zwei Modulen ein, die eigentlich in der Architektur verboten war. Das merkt der Entwickler üblicherweise noch nicht einmal, weil er nicht die Architektur-Ebene, sondern nur die Code-Ebene des Systems im Blick hat. Aus welchem Modul die Klasse stammt, zu der er gerade eine Abhängigkeit einführt, ist oft nicht sofort zu erkennen. So entstehen mit der Zeit immer mehr Abhängigkeiten. Gegen die ursprüngliche Architektur mit den geplanten Abhängigkeiten wird immer mehr verstoßen und am Ende steht ein völlig unstrukturiertes System.
Microservices haben klare Grenzen durch ihre Schnittstelle – egal ob die Schnittstelle als REST-Schnittstelle oder durch Messaging implementiert ist. Wenn ein Entwickler eine neue Abhängigkeit zu einer solchen Schnittstelle einführt, merkt er das, weil die Schnittstelle entsprechend bedient werden muss. Aus diesem Grund ist es unwahrscheinlich, dass auf der Ebene der Abhängigkeiten zwischen den Microservices Architektur-Verstöße geschehen. Die Schnittstellen der Microservices sind sozusagen Architektur-Firewalls, weil sie ArchitekturVerstöße aufhalten. Das Konzept einer Architektur-Firewall setzen auch Architektur-Managementwerkzeuge wie Sonargraph (https://www.hello2morrow.com/products/sonargraph), Structure101 (http://structure101.com/) oder jQAssistant (https://jqassistant.org/) um. Fortgeschrittene Modul-Konzepte können ebenfalls solche Firewalls erzeugen. In der Java-Welt beschränkt OSGi (https://www.osgi.org/) andere Module auf den Zugriff über die Schnittstelle. Der Zugriff kann sogar auf einzelne Packages oder Klassen eingeschränkt werden.
Also bleiben einzelne Microservices wartbar, weil sie ersetzt werden können, wenn sie nicht mehr wartbar sind. Die Architektur auf Ebene der Abhängigkeiten zwischen den Microservices bleibt ebenfalls wartbar, weil Entwickler Abhängigkeiten zwischen Microservices nicht mehr unbeabsichtigt einbauen können.
Daher können Microservices langfristig eine hohe Qualität der Architektur sicherstellen und damit eine nachhaltige Entwicklung, bei der die Änderungsgeschwindigkeit auch langfristig nicht abnimmt.
Continuous Delivery1 ist ein Ansatz, bei dem Software kontinuierlich in Produktion gebracht wird. Dazu wird eine Continuous-Delivery-Pipeline genutzt. Die Pipeline bringt die Software durch die verschiedenen Phasen in Produktion (siehe Abbildung 1–1).
Abb. 1–1Continuous-Delivery-Pipeline
Typischerweise wird die Software in der Commit-Phase kompiliert, die Unit Tests und eine statische Code-Analyse werden durchgeführt. In der Akzeptanztestphase überprüfen automatisierte Tests die fachlich korrekte Funktion der Software. Die Kapazitätstests überprüfen die Performance für die zu erwartende Last. Explorative Tests dienen dazu, bisher noch nicht bedachte Tests durchzuführen oder neue Funktionalitäten zu testen. Die explorativen Tests können so Aspekte untersuchen, die automatisierte Tests noch nicht abdecken. Am Ende wird die Software in Produktion gebracht.
Microservices stellen unabhängig deploybare Module dar. Also hat jeder Microservice eine eigene Continuous-Delivery-Pipeline. Das erleichtert Continuous Delivery:
Der Durchlauf durch die Continuous-Delivery-Pipelines ist wesentlich
schneller
, weil die Deployment-Einheiten kleiner sind. Daher ist das Deployment schneller und so können Tests schneller Umgebungen aufbauen. Auch die Tests sind schneller, da sie weniger Funktionalitäten testen müssen. Nur die Features im jeweiligen Microservice müssen getestet werden, während bei einem Deployment-Monolithen wegen möglicher Regressionen die gesamte Funktionalität getestet werden muss.
Der Aufbau der Continuous-Delivery-Pipeline ist
einfacher
. Der Aufbau einer Umgebung für einen Deployment-Monolithen ist kompliziert. Meistens werden leistungsfähige Server benötigt. Ebenso sind oft Drittsysteme für Tests notwendig. Ein Microservice braucht weniger leistungsfähige Hardware. Es sind auch nicht so viele Drittsysteme in den Testumgebungen notwendig. Es kann allerdings notwendig sein, die Microservices zusammen in einem Integrationstest zu testen. Das kann diesen Vorteil zunichte machen.
Das Deployment eines Microservice hat ein
geringeres Risiko
als das Deployment eines Deployment-Monolithen. Bei einem Deployment Monolithen wird das komplette System neu deployt, bei einem Microservice nur ein Modul. Dabei sind weniger Probleme zu erwarten, weil weniger Funktionalität geändert wird.
Microservices helfen also bei Continuous Delivery. Die bessere Unterstützung von Continuous Delivery alleine kann schon ein Grund für eine Migration eines Deployment-Monolithen zu Microservices sein.
Microservices-Architekturen können aber nur dann funktionieren, wenn das Deployment automatisiert ist. Microservices erhöhen die Anzahl der deploybaren Einheiten gegenüber einem Deployment-Monolithen erheblich. Das ist nur machbar, wenn die Deployment-Prozesse automatisiert werden.
Tatsächlich unabhängiges Deployment bedeutet, dass die Continuous-Delivery-Pipelines vollständig unabhängig sind. Integrationstests widersprechen dieser Unabhängigkeit: Sie führen Abhängigkeiten zwischen den Continuous-Delivery-Pipelines verschiedener Microservices ein. Also müssen die Integrationstests auf ein Minimum reduziert werden. Abhängig von der Kommunikationsart gibt es dafür unterschiedliche Ansätze (siehe Abschnitt 13.1 und Abschnitt 10.3).
Microservices-Systeme sind robuster. Wenn in einem Microservice ein Speicherleck existiert, stürzt nur dieser Microservice ab. Die anderen Microservices laufen weiter. Natürlich müssen die anderen Microservices den Ausfall eines Microservice kompensieren. Man spricht von Resilience (etwa Widerstandsfähigkeit). Microservices können dazu beispielsweise Werte cachen und diese Werte bei einem Ausfall nutzen. Oder es gibt einen Fallback mit einem vereinfachten Algorithmus.
Ohne Resilience kann die Verfügbarkeit eines Microservices-Systems problematisch sein. Dass irgendein Microservice ausfällt, ist recht wahrscheinlich. Durch die Aufteilung in mehrere Prozesse sind viel mehr Server an dem System beteiligt. Jeder dieser Server kann ausfallen. Die Kommunikation zwischen den Microservices verläuft über das Netzwerk. Das Netzwerk kann ebenfalls ausfallen. Also müssen die Microservices Resilience umsetzten, um Robustheit zu erreichen.
Der Abschnitt 14.5 zeigt, wie Resilience in einem synchronen Microservice-System konkret umgesetzt werden kann.
Jeder Microservice kann unabhängig skaliert werden: Es ist möglich, mehr Instanzen eines Microservices zu starten und die Last für den Microservice auf die Instanzen zu verteilen. Das kann die Skalierbarkeit eines Systems erheblich verbessern. Dazu müssen die Microservices natürlich entsprechende Voraussetzungen schaffen. So dürfen die Microservices keinen State enthalten. Sonst können Clients nicht auf eine andere Instanz umschwenken, die ja den State dann nicht hätte.
Mehr Instanzen eines Deployment-Monolithen zu starten, kann aufgrund der benötigten Hardware schwierig sein. Außerdem kann der Aufbau einer Umgebung für einen Deployment-Monolithen komplex sein: So können zusätzliche Dienste notwendig sein oder eine komplexe Infrastruktur mit Datenbanken und weiteren Software-Komponenten. Bei einem Microservice kann die Skalierung feingranularer erfolgen, sodass üblicherweise weniger zusätzliche Dienste notwendig sind und Rahmenbedingungen weniger komplex sind.
Jeder Microservice kann mit einer eigenen Technologie umgesetzt werden. Das erleichtert die Migration auf eine neue Technologie, da man jeden Microservice einzeln migrieren kann. Ebenso ist es einfacher und risikoärmer, Erfahrungen mit neuen Technologien zu sammeln. Sie können zunächst nur für einen Microservice genutzt werden, bevor sie in mehreren Microservices zum Einsatz kommen.
Microservices können untereinander isoliert werden. So ist es möglich, zwischen den Microservices Firewalls in die Kommunikation einzuführen. Außerdem kann die Kommunikation zwischen den Microservices abgesichert werden, um zu garantieren, dass die Kommunikation tatsächlich von einem anderen Microservice kommt und authentisch ist. So kann verhindert werden, dass bei einer Übernahme eines Microservices auch andere Microservices kompromittiert sind.
Letztendlich lassen sich viele Vorteile der Microservices auf eine stärkere Isolation zurückführen.
Abb. 1–2Isolation als Quelle der Vorteile
Microservices können isoliert deployt werden, was Continuous Delivery vereinfacht. Sie sind bezüglich Ausfällen isoliert, was der Robustheit zugute kommt. Gleiches gilt für Skalierbarkeit: Jeder Microservice kann isoliert von anderen Microservices skaliert werden. Die eingesetzten Technologien können isoliert für einen Microservice bestimmt werden, was Technologiewahlfreiheit ermöglicht. Die Microservices sind so isoliert, dass sie nur über das Netzwerk miteinander kommunizieren. Die Kommunikation kann daher durch Firewalls abgesichert werden, was der Sicherheit zugute kommt.
Weil dank der starken Isolation die Modulgrenzen kaum noch aus Versehen überschritten werden können, wird die Architektur kaum noch verletzt. Das sichert die Architektur im Großen ab. Jeder Microservice kann isoliert durch einen neuen ersetzt werden. So kann risikoarm ein Microservice abgelöst werden und die Architektur der einzelnen Microservices sauber gehalten werden. Dadurch ermöglicht die Isolation eine langfristige Wartbarkeit der Software.
Mit der Isolation treiben Microservices die Entkopplung als wichtige Eigenschaft von Modulen auf die Spitze: Während Module normalerweise nur bezüglich Änderungen am Code und bezüglich der Architektur voneinander entkoppelt sind, geht die Entkopplung der Microservices darüber weit hinaus.
Welcher Grund für Microservices der wichtigste ist, hängt vom jeweiligen Szenario ab. Der Einsatz von Microservices in einem Greenfield-System ist eher die Ausnahme als die Regel. Oft gilt es, einen Deployment-Monolithen durch ein Microservices-System zu ersetzen (siehe auch Kapitel 4). Dabei spielen unterschiedliche Vorteile eine Rolle:
Die einfachere
Skalierung der Entwicklung
kann ein wichtiger Grund für die Einführung von Microservices in einem solchen Szenario sein. Oft haben große Organisationen Schwierigkeiten, einen Deployment-Monolithen mit einer Vielzahl von Entwicklern schnell genug weiterzuentwickeln.
Die
einfache Migration
weg von dem Legacy-Deployment-Monolithen erleichtert die Einführung von Microservices in einem solchen Szenario.
Continuous Delivery
ist oft ein weiteres Ziel: Die Geschwindigkeit, mit der Änderungen in Produktion gebracht werden können, soll erhöht werden. Oft ist auch die Zuverlässigkeit, mit der eine Änderung in Produktion gebracht werden kann, nicht ausreichend.
Die Skalierung der Entwicklung ist nicht das einzige Szenario für eine Migration. Wenn ein einziges Scrum-Team ein System mit Microservices umsetzen will, kann die Skalierung der Entwicklung kein sinnvoller Grund sein, weil die Entwicklungsorganisation dazu nicht ausreichend groß ist. Dennoch kann es andere Gründe geben: Continuous Delivery, technische Gründe wie Robustheit, unabhängige Skalierung, Technologiewahlfreiheit oder nachhaltige Entwicklung können in einem solchen Szenario eine Rolle spielen.
Abhängig von den Zielen kann ein Team bei der Umsetzung von Microservices Kompromisse eingehen. Wenn Robustheit ein Ziel der Microservices-Einführung ist, müssen die Microservices als getrennte Docker-Container umgesetzt werden. Jeder Docker-Container kann unabhängig abstürzen. Ist Robustheit kein Ziel, kommen Alternativen in Frage. Beispielsweise können mehrere Microservices als Java-Web-Anwendungen gemeinsam auf einem Java-Application-Server laufen. Sie laufen dann alle in einem Prozess und sind bezüglich der Robustheit nicht isoliert. Ein Speicherleck in einem Microservice bringt alle Microservices zum Absturz. Aber dafür ist die Lösung einfacher zu betreiben und kann der bessere Trade-Off sein.
Die technischen und organisatorischen Vorteile weisen auf zwei Ebenen hin, in denen ein System in Microservices unterteilt werden kann:
Eine
grobgranulare fachliche Unterteilung
gewährt den Teams eine unabhängige Entwicklung und ermöglicht es, dass ein neues Feature mit einem Deployment eines Microservices ausgerollt wird. Beispielsweise können in einem ECommerce-System die Registrierung der Kunden oder der Bestellprozess solche grobgranularen Microservices sein.
Aus
technischen Gründen
können dann einige Microservices
weiter unterteilt
werden. Wenn beispielsweise der letzte Schritt des Bestellprozesses unter besonders hoher Last steht, dann kann dieser letzter Schritt in einem eigenen Microservices implementiert werden. Der Microservice kann dann isoliert von anderen Microservices skaliert werden. Der Microservice gehört zum fachlichen Kontext des Bestellprozess, aber ist aus technischen Gründen als eigener Microservice umgesetzt.
Abb. 1–3Zwei Ebenen von Microservices
Abbildung 1–3 zeigt ein Beispiel für zwei Ebenen: Eine ECommerce-Anwendung ist fachlich in die Microservices Suche, Check Out, Inkasso und Lieferung aufgeteilt. Die Suche ist weiter aufgeteilt: Die Volltext-Suche ist von der Suche in Kategorien getrennt. Ein Grund dafür kann die getrennte Skalierung sein: Mit dieser Architektur kann die Volltext-Suche getrennt von der Suche in Kategorien skaliert werden, was ein Vorteil ist, wenn sie unterschiedlich hohe Last handhaben müssen. Ein weiterer Grund können unterschiedliche Technologien sein: Die Volltext-Suche kann mit einer Volltext-Suchmaschine umgesetzt sein, die für die Suche in Kategorien ungeeignet ist.
Es ist schwer, eine typische Anzahl von Microservices in einem System anzugeben. Wenn man der Aufteilung aus diesem Kapitel folgt, dann sollten sich 10–20 grobgranulare Fachlichkeiten ergeben und für jede von ihnen ein bis drei Microservices. Es gibt allerdings auch Systeme mit weitaus mehr Microservices.
Microservices haben nicht nur Vorteile, sondern halten auch Herausforderungen bereit:
Der
Betrieb
eines Microservices-Systems ist aufwendiger als der Betrieb eines Deployment-Monolithen. Das liegt daran, dass in einem Microservice-System viel mehr deploybare Einheiten existieren, die alle deployt und gemonitort werden müssen. Das ist nur machbar, wenn der Betrieb weitgehend automatisiert ist und die korrekte Funktion durch entsprechendes Monitoring sichergestellt wird.
Teil III
zeigt verschiedene Lösungen dafür.
Microservices müssen tatsächlich
getrennt deploybar
sein. Eine Aufteilung beispielsweise in Docker-Container ist dafür eine Voraussetzung, reicht alleine aber nicht aus. Auch das Testen muss getrennt werden. Wenn alle Microservices zusammen getestet werden müssen, dann kann ein Microservice den Test-Stage blockieren und andere Microservices können nicht deployt werden. Änderungen an Schnittstellen müssen so umgesetzt werden, dass ein unabhängiges Deployment der Microservices immer noch möglich ist. Der Microservice, der die Schnittstelle implementiert, muss beispielsweise die neue und die alte Schnittstelle anbieten, sodass er deployt werden kann, ohne dass der aufrufende Microservice zur selben Zeit deployt werden muss.
Änderungen, die mehrere Microservices
betreffen, sind schwieriger umzusetzen als Änderungen, die mehrere Module eines Deployment-Monolithen umfassen. In einem Microservices-System sind für solche Änderungen mehrere Deloyments notwendig. Diese Deployments müssen koordiniert werden. Bei einem Deployment-Monolithen wäre nur ein Deployment notwendig.
In einem Microservices-System kann die
Übersicht
über die Microservices verloren gehen. Die Erfahrung zeigt allerdings, dass in der Praxis Änderungen bei einem guten fachlichen Schnitt auf einen oder wenige Microservices begrenzt werden können. Daher kommt einem Überblick über das System eine geringere Bedeutung zu, weil die Interaktion der Microservices die Entwicklung wegen der hohen Unabhängigkeit kaum beeinflusst.
Wichtig ist vor allem, dass Microservices nur genutzt werden sollten, wenn sie in einem Szenario die einfachste Lösung sind. Die oben genannten Vorteile sollten die Nachteile beispielsweise wegen der höheren Komplexität im Deployment mehr als wett machen. Schließlich ist es kaum sinnvoll, absichtlich eine zu komplexe Lösung zu nutzen.
ISA (http://isa-principles.org) (Independent Systems Architecture) ist eine Sammlung von grundlegenden Prinzipien für Microservices. Sie basiert auf Erfahrungen mit Microservices in vielen verschiedenen Projekten.
Bei den Prinzipien wird »muss« verwendet für Prinzipien, die unbedingt eingehalten werden müssen. »Sollte« beschreibt Prinzipien, die viele Vorteile haben, aber nicht unbedingt eingehalten werden müssen.
Das System muss in
Module
unterteilt werden, die
Schnittstellen
bieten. Der Zugriff auf andere Module ist nur über diese Schnittstellen möglich. Module dürfen daher nicht direkt von den Implementierungsdetails eines anderen Moduls abhängen, wie z.B. dem Datenmodell in der Datenbank.
Module müssen
separate Prozesse, Container oder virtuelle Maschinen
sein, um die Unabhängigkeit zu maximieren.
Das System muss zwei klar getrennte Ebenen von Architekturentscheidungen haben:
Die
Makro-Architektur
umfasst Entscheidungen, die alle Module betreffen. Alle weiteren Prinzipien sind Teil der Makro-Architektur.
Die
Mikro-Architektur
sind jene Entscheidungen, die für jedes Modul anders getroffen werden können.
Die Wahl der
Integrations-Optionen
muss für das System begrenzt und standardisiert sein. Die Integration kann mit synchroner oder asynchroner Kommunikation stattfinden und/oder auf Frontend-Ebene.
Kommunikation
muss auf einige Protokolle wie REST oder Messaging begrenzt sein. Auch Metadaten, z.B. zur Authentifizierung, müssen standardisiert sein.
Jedes Modul muss seine
eigene unabhängige Continuous-Delivery-Pipeline
haben. Tests sind Teil der Continuous-Delivery-Pipeline, so dass die Tests der Module unabhängig sein müssen.
Der
Betrieb
sollte standardisiert werden. Dies beinhaltet Konfiguration, Deployment, Log-Analyse, Tracing, Monitoring und Alarme. Es kann Ausnahmen vom Standard geben, wenn ein Modul sehr spezifische Anforderungen hat.
Standards
für Betrieb, Integration oder Kommunikation sollten auf Schnittstellenebene definiert werden. Das Protokoll kann als REST standardisiert sein, und Datenstrukturen können standardisiert werden. Aber jedes Modul sollte frei sein, eine andere REST-Bibliothek zu verwenden.
Module müssen
resilient
sein. Sie dürfen nicht ausfallen, wenn andere Module nicht verfügbar sind oder Kommunikationsprobleme auftreten. Sie müssen in der Lage sein, heruntergefahren zu werden, ohne Daten oder Zustand zu verlieren. Es muss möglich sein, sie auf andere Umgebungen (Server, Netzwerke, Konfigurationen usw.) zu verschieben.
Independent Systems Architecture und die Konzepte in diesem Buch haben viele Gemeinsamkeiten. Auch dieses Buch definiert Microservices als Module (Prinzip 1), die unabhängig deploybar sind. Unabhängiges Deployment kann nur durch Container und ähnliche Mechanismen sichergestellt werden (Prinzip 2). Prinzip 6 (unabhängige Deployment Pipelines) führt das unabhängige Deployment weiter aus.
Kapitel 2 betrachtet Mikro- und Makro-Architektur (Prinzip 3) näher. Gerade Integration (Prinzip 4) und Kommunikation (Prinzip 5) sind wichtige Eckpunkte dieses Kapitels. Ebenso spielt der Betrieb (Prinzip 6 und 8) auch in diesem Buch eine wichtige Rolle. Die Technologien sind meistens standardisiert. Kapitel 2 diskutiert Gründe für die Standardisierung und Ausnahmen noch genauer. Schließlich kann das Ziel der Robustheit, auf das dieses Kapitel schon eingegangen ist, nur mit Resilience (Prinzip 9) erreicht werden.
Die ISA-Prinzipien stellen also eine gute Zusammenfassung der Prinzipien aus diesem Kapitel und der Ideen zur Mikro- und Makro-Architektur aus diesem Buch dar.
Abhängig vom konkreten Szenario können Microservices-Varianten wie Self-contained Systems (siehe Kapitel 3) genutzt werden.
Teil II (ab Kapitel 5) und Teil III (ab Kapitel 19) des Buches zeigen unterschiedliche technologische Variationen. Dazu zählen synchrone Kommunikation, asynchrone Kommunikation und UI-Integration. Die Kombination von einem oder mehrerer Rezepte aus diesen Teilen ergibt eine individuelle Microservices-Architektur.
Das folgende Vorgehen hilft dabei, die richtigen Rezepte zu finden:
Priorisiere für ein dir bekanntes Projekt die Vorteile des Einsatzes von Microservices.
Wäge ab, welche der Herausforderungen in dem Projekt ein hohes Risiko darstellen könnten.
Anschließend können die möglichen technischen und architekturellen Lösungen dagegen abgewogen werden, ob sie bei diesen Anforderungen eine sinnvolle Lösung darstellen.
Für eine konkrete Aufteilung der Microservices und technische Entscheidungen sind zusätzliche Konzepte notwendig. Daher steht die Aufteilung im Mittelpunkt von Abschnitt 2.6.
Microservices sind eine extreme Art der Modularisierung. Das getrennten Deployment ist die Grundlage für eine sehr starke Entkopplung.
Daraus ergeben sich zahlreiche Vorteile. Ein wesentlicher Vorteil ist Isolation in verschiedenen Ebenen. Das vereinfacht nicht nur das Deployment, sondern auch ein Ausfall kann auf einen Microservice begrenzt werden. Microservices können einzeln skaliert werden, Technologie-Entscheidungen haben nur Auswirkungen auf einen Microservice und auch ein Sicherheitsproblem kann auf einen Microservice begrenzt werden. Durch die Isolation kann ein Microservices-System mit einem großen Team einfacher entwickelt werden, weil weniger Koordination zwischen den Teams notwendig ist. Und natürlich sorgen die kleineren Deployment-Artefakte dafür, dass Continuous Delivery einfacher umgesetzt werden kann. Die Ablösung eines Legacy-Systems kann mit Microservices ebenfalls einfacher erfolgen, weil neue Microservices das System ergänzen können, ohne dass dazu größere Änderungen am Code notwendig wären.
Die Herausforderungen liegen vor allem im Betrieb. Mit passenden technologischen Entscheidungen sollten die beabsichtigten Vorteile verstärkt und gleichzeitig die Nachteile minimiert werden.
Die Integration und Kommunikation zwischen den Microservices ist natürlich komplexer als die Aufrufe zwischen Modulen in einem Deployment-Monolithen. Die zusätzliche technische Komplexität stellt eine weitere wichtige Herausforderung bei Microservice-Architekturen dar.
Microservices erlauben es, durch die Isolation auf verschiedenen Ebenen Software noch stärker zu modularisieren und zu isolieren (siehe Abschnitt 1.2). Aber Microservices sind Module eines Gesamtsystems. Also müssen sie integriert werden. Die Architektur steht damit vor einer Herausforderung: Auf der einen Seite muss sichergestellt werden, dass die Microservices als Teile des Gesamtsystems zusammenpassen. Auf der anderen Seite dürfen die Freiheiten in den Microservices nicht zu sehr eingeschränkt werden, weil sonst die Isolation und die Unabhängigkeit der Microservices nicht erreicht werden können, die für die meisten Vorteile der Microservices-Architektur verantwortlich sind.
Daher bietet es sich an, die Architektur in eine Mikro- und eine Makro-Architektur aufzuteilen. Die Mikro-Architektur umfasst alle Entscheidungen, die für jeden Microservices anders getroffen werden können. Die Makro-Architektur beinhaltet die Entscheidungen, die global getroffen werden und für alle Microservices gelten.
Abb. 2–1Mikro- und Makro-Architektur
Abbildung 2–1 zeigt die Idee: Es gibt eine Makro-Architektur, die für alle Microservices übergreifend gilt. Die Mikro-Architektur gilt jeweils für einen Microservices, sodass jeder Microservice seine eigene Mikro-Architektur hat.
Dieses Kapitel zeigt das Vorgehen für eine solche Architektur-Aufteilung:
Zunächst vermittelt das Kapitel die
fachliche Aufteilung
in Microservices und zeigt, dass
Domain-driven Design
und
Bounded Context
gute Werkzeuge für eine solche Aufteilung sind.
Dann erläutert das Kapitel die Entscheidungen, die im Rahmen der
technischen Mikro- und Makro-Architektur
getroffen werden müssen und wie ein
DevOps-Modell
die Entscheidungen beeinflusst.
Schließlich wird die Frage beantwortet,
wer
die Aufteilung in Mikro- und Makro-Architektur vornimmt und die Regeln in der Makro-Architektur festlegt.
Bei der fachlichen Aufteilung ist das Konzept von Mikro- und Makro-Architektur schon lange üblich: Es gibt eine Makro-Architektur, welche die Fachlichkeit in grobgranulare Module aufteilt. In den Modulen erfolgt eine weitere Aufteilung als Mikro-Architektur.
So kann beispielsweise ein ECommerce-Systeme in Module für die Registrierung von Kunden und für die Annahme von Bestellungen unterteilt werden. Die Annahme der Bestellungen kann in weitere kleinere Module zerlegt werden. Das können beispielsweise die Validierung der Daten oder die Berechnung der Versandkosten sein. Die interne Aufteilung des Registrierungs-Moduls ist nach außen nicht sichtbar und kann geändert werden, ohne dass es andere Module beeinflusst. Genau diese Flexibilität, ein Modul ohne Beeinflussung anderer Module zu ändern, ist ein wesentlicher Vorteil der modularen Software-Entwicklung.
Abb. 2–2Beispiel für eine fachliche Aufteilung
Abbildung 2–2 zeigt ein Beispiel für eine Aufteilung eines Systems in mehrere fachliche Module. In dieser Aufteilung hat jedes Modul ein eigenes Domänenmodell:
Für die
Suche
müssen für Produkte Daten wie Beschreibungen, Bilder oder Preise abgelegt sein. Für die Kunden wären die notwendigen Daten die Empfehlungen, die z.B. aus den bereits abgearbeiteten Bestellungen ermittelt werden können.
Der
Check Out
muss Buch darüber führen, was im Einkaufswagen liegt. Für Produkte sind nur grundlegende Informationen wie ein Name oder der Preis notwendig. Ebenso sind nur wenige Daten über den Kunden notwendig. Wichtigster Bestandteil des Domänenmodells dieses Moduls ist der Einkaufswagen.
Für
Inkasso
müssen vom Kunden die Bezahlweise und die dafür notwendigen Informationen wie die Kreditkartennummer bekannt sein.
Bei der
Lieferung
sind vom Kunden die Lieferadresse und von den Produkten Größe und Gewicht notwendig.
In dieser Aufstellung kann man erkennen, dass für die verschiedenen Module unterschiedliche Domänenmodelle notwenig sind. Nicht nur die Daten für Kunde und Produkt unterscheiden sich, sondern das gesamte Modell und auch die Logik.
Für die fachliche Modellierung eines Systems bietet Domain-driven Design (DDD) eine Sammlung von Patterns. Für Microservices sind vor allem die Patterns im Bereich von Strategic Design interessant. Sie beschreiben, wie eine Domäne unterteilt werden kann. Domain-driven Design bietet noch viel mehr Patterns, die z.B. bei der Bildung einzelner Modelle helfen können. Für einen tieferen Einstieg empfiehlt sich das ursprüngliche DDD-Buch,1 das den Begriff Domain-driven Design eingeführt hat und DDD umfassend beschreibt, oder das kompakte DDD-Buch,2 das vor allem auf Strategic Design, Bounded Context und Domain Events fokussiert.
Domain-driven Design spricht von einem Bounded Context: Jedes Domänenmodell ist nur in einem begrenzten Kontext gültig. Suche, Check Out, Inkasso und Lieferung sind also solche Bounded Contexts, weil sie jeweils ein eigenes Domänenmodell haben.
Es wäre zwar denkbar, ein Domänenmodell umzusetzen, dass mehrere Bounded Contexts aus dem Beispiel umfasst. Aber ein solches Modell wäre nicht die einfachste Lösung. Beispielsweise muss eine Preisänderung eines Produkts sich auf die Suche auswirken, aber es darf nicht dazu führen, dass im Inkasso bereits getätigte Bestellungen teurer werden. Es ist einfacher, in der Suche nur den aktuellen Preis eines Produkts zu speichern und im Inkasso den Preis des Produkts in jeder Bestellung zu speichern, der auch Rabatte und andere komplexe Einflussfaktoren beinhalten kann. Der einfachste Entwurf besteht also aus mehreren spezialisierten Domänenmodellen, die jeweils nur in einem Kontext gültig sind. Jedes der Domänenmodelle hat eine eigene Modellierung für Geschäftsobjekte wie Kunden oder Produkte.
Die Aufteilung des Systems in verschiedene Bounded Contexts ist ein Teil des Strategic Designs, das zu den Praktiken aus dem Domain-driven Design (DDD) gehört. Das Strategic Design beschreibt die Integration von Bounded Contexts.
Abb. 2–3Strategic-Design-Grundlagen
Abbildung 2–3 zeigt die grundlegenden Begriffe im Strategic Design:
Die
Bounded Contexts
beschreiben jeweils den Gültigkeitsbereich eines
Domänenmodells
.
Die Bounded Contexts können miteinander kommunizieren: Der
Upstream
(deutsch: vorgeschaltet) Bounded Context stellt dem
Downstream
(deutsch: nachgeschaltet) Bounded Context Informationen zur Verfügung. Technisch kann der Upstream Bounded Context neue Informationen an den Downstream Bounded Context schicken oder der Downstream Bounded Context kann neue Informationen beim Upstream Bounded Context abholen. Die technische Kommunikationsrichtung kann also eine andere sein als die Richtung, in die die Informationen fließen.
Wie die Kommunikation genau abläuft, beschreibt DDD in verschiedenen Patterns. Diese Patterns beschreiben nicht nur einen Ansatz auf der ArchitekturEbene, sondern auch das Miteinander auf organisatorischer Ebene:
Conformist
(Konformist) bedeutet, dass ein Bounded Context ein Domänenmodell von einem anderen Bounded Context einfach übernimmt. In
Abbildung 2–4
benutzen Bounded Context 1 und 2 jeweils dasselbe Domänenmodell 1. Ein Beispiel können Statistiken als Teil eines Data Warehouses sein. Sie nutzen das Domänenmodell aus den anderen Bounded Contexts und extrahieren die für sie interessanten Daten. Ein Mitspracherecht bei Änderungen am Bounded Context haben sie beim Conformist-Pattern nicht.
Abb. 2–4Conformist: Domänenmodell wird übernommen
Bei einem
Anti-corruption Layer
(Antikorruptionsschicht, ACL) enthält der Downstream Bounded Context eine Schicht, um das eigene Domänenmodell vom Modell des Upstream Bounded Contexts zu entkoppeln. Das ist insbesondere zusammen mit Conformist sinnvoll, um so ein eigenes, vom anderen Modell entkoppeltes Modell zu erstellen. In
Abbildung 2–5