werden Release-Zeitpläne vorgeplant – meist in einem sich wiederholenden Muster. Steht das Release an, werden alle Änderungen seit dem letzten Release Train deployt. Das kann für manche Menschen eine nützliche Technik sein, aber ich sehe sie weniger als das ultimative Ziel, sondern eher als einen Übergangsschritt hin zu ordentlichen Release-on-Demand-Techniken. Ich habe sogar schon in Organisationen gearbeitet, die alle Services in einem System als Teil des Release-Train-Prozesses auf einmal deployt haben, ohne darüber nachzudenken, ob diese Services überhaupt geändert werden mussten.
Ein Deployment bringt manchmal Risiken mit sich. Es gibt viele Möglichkeiten, diese Risiken zu verringern, und eine davon ist, nur das zu ändern, was geändert werden muss. Reduzieren wir die Deployment-Kopplung – vielleicht durch das Aufteilen großer Prozesse in unabhängig deploybare Microservices –, können wir das Risiko jedes Deployments verringern, indem wir dessen Scope verkleinern.
Kleinere Releases sorgen für geringere Risiken. Es kann weniger schiefgehen. Wenn etwas passiert, ist es leichter, den Verursacher herauszufinden und zu klären, wie man es behebt, weil wir weniger geändert haben. Wenn Sie Wege finden, die Größe von Releases zu reduzieren, gelangen Sie zum Kern des Continuous Delivery, was die Wichtigkeit von schnellem Feedback und Release-on-Demand-Methoden unterstreicht.9 Je kleiner der Scope des Release ist, desto leichter und sicherer lässt es sich ausrollen, und desto schneller erhalten wir Feedback. Mein eigenes Interesse an Microservices entstand aus meinem Fokus auf Continuous Delivery – ich suchte nach Architekturen, die das Umsetzen von Continuous Delivery erleichtern.
Für das Verringern des Deployment-Coupling sind natürlich keine Microservices erforderlich. Runtimes wie Erlang erlauben das Hot-Deployment neuer Modulversionen in einen laufenden Prozess. Nach und nach werden vielleicht immer mehr von uns Zugriff auf solche Möglichkeiten durch die Technologie-Stacks haben, die wir tagtäglich einsetzen.10
Domänenkopplung
In einem System, das aus mehreren unabhängigen Services besteht, muss es ganz grundlegend irgendeine Interaktion zwischen den Teilnehmern geben. In einer Microservices-Architektur ist Domänenkopplung das Ergebnis – die Interaktionen zwischen Services modellieren die Interaktionen in unserer realen Domäne. Wollen Sie eine Bestellung tätigen, müssen Sie wissen, welche Dinge im Warenkorb eines Kunden liegen. Möchten Sie ein Produkt ausliefern, müssen Sie wissen, wohin es geliefert werden soll. In unserer Microservices-Architektur können diese Informationen per definitionem in unterschiedlichen Services liegen.
Nehmen wir als konkretes Beispiel unsere Music Corp. Wir haben ein Lager mit Waren. Bestellen Kunden CDs, müssen die Mitarbeiter im Lager wissen, welche CDs ausgewählt und verpackt werden müssen und wohin das Paket gehen soll. Die Informationen über die Bestellung müssen daher mit den Leuten im Lager geteilt werden.
Abbildung 1-13: Eine Bestellung wird an das Lager geschickt, um das Paket zu packen.
Abbildung 1-13 zeigt ein Beispiel dafür: Ein Order-Processing-Service schickt alle Details der Bestellung an den Warehouse-Service, der dann dafür sorgt, dass das gewünschte Produkt verpackt wird. Als Teil dieser Operation nutzt der Warehouse-Service die Customer-ID, um Informationen über den Kunden aus dem eigenständigen Customer-Service abzurufen, sodass wir wissen, wie wir ihn benachrichtigen können, wenn die Bestellung verschickt wird.
In dieser Situation teilen wir die gesamte Bestellung mit dem Lager, was eventuell nicht sinnvoll ist – das Lager muss nur wissen, was zu verpacken ist und wohin es geschickt werden soll. Die Mitarbeitenden dort müssen nicht wissen, wie viel das Produkt kostet (wenn sie eine Rechnung mit in das Paket legen müssen, könnte diese als fertiges PDF mitgegeben werden). Wir könnten sogar Probleme mit Informationen bekommen, die sich möglichst nicht verbreiten sollten – würden wir die vollständige Bestellung verteilen, könnten zum Beispiel auch Kreditkartendaten an Services gehen, die diese gar nicht brauchen.
Abbildung 1-14: Mit einer Pick Instruction reduzieren wir die Menge an Informationen, die wir an den Warehouse-Service schicken.
Stattdessen überlegen wir uns vielleicht ein neues Domänenkonzept mit einer »Pick Instruction«, die nur die Informationen enthält, die das Lagerteam benötigt (siehe Abbildung 1-14). Das ist ein weiteres Beispiel für Information Hiding.
Wir könnten die Kopplung noch weiter verringern, indem wir dafür sorgen, dass der Warehouse-Service überhaupt nichts über den Kunden wissen muss – stattdessen könnten wir alle notwendigen Details über die Pick Instruction mitgeben (siehe Abbildung 1-15).
Abbildung 1-15: Durch das Einfügen weiterer Informationen in die Pick Instruction kann ein Anruf beim Customer-Service vermieden werden.
Damit das funktioniert, muss Order Processing vermutlich irgendwo auf den Customer-Service zugreifen, um diese Pick Instruction überhaupt erzeugen zu können. Aber es ist sehr wahrscheinlich, dass das Order Processing die Kundeninformationen schon aus anderen Gründen benötigt, daher wird das eher kein Problem sein. Dieser Prozess des »Verschickens« einer Pick Instruction beinhaltet einen API-Aufruf vom Order-Processing an den Warehouse-Service.
Eine Alternative könnte darin bestehen, Order Processing eine Art von Event verschicken zu lassen, das das Warehouse empfängt (siehe Abbildung 1-16). Indem wir ein Event feuern, das vom Warehouse aufgenommen wird, drehen wir letztendlich die Abhängigkeiten um. War zuvor das Order Processing davon abhängig, dass der Warehouse-Service dafür sorgen kann, dass die Bestellung verschickt wird, lauscht der Warehouse-Service nun auf Events vom Order-Processing-Service. Beide Ansätze haben ihre Vorteile, und es hängt von einem weitergehenden Verständnis für die Interaktionen zwischen der Order-Processing-Logik und der im Warehouse-Service gekapselten Funktionalität ab, für welchen ich mich entscheiden würde. Dabei kann Domänenmodellierung helfen, was wir uns gleich anschauen werden.
Abbildung 1-16: Ein Event feuern, das der Warehouse-Service empfangen kann und das gerade genug Informationen enthält, damit die Bestellung verpackt und verschickt werden kann
Damit der Warehouse-Service überhaupt etwas tun kann, sind auf jeden Fall irgendwelche Informationen notwendig. Dieser Teil der Domänenkopplung lässt sich nicht vermeiden. Aber indem wir sorgfältig darüber nachdenken, was wir bereitstellen und wie wir das tun, können wir immer noch versuchen, die Kopplung zu minimieren.
Gerade genug Domain-Driven Design
Wie gerade besprochen, hat ein Modellieren rund um eine Businessdomäne signifikante Vorteile für unsere Microservices-Architektur. Die Frage ist, wie wir zu diesem Modell kommen – und hier kommt das Domain-Driven Design ins Spiel.
Es ist keine neue Idee, unsere Programme die reale Welt, in der sie operieren, besser repräsentieren zu lassen. OO-Programmiersprachen wie Simula wurden entwickelt, um uns zu erlauben, reale Domänen zu modellieren. Aber man braucht mehr als die Kenntnisse von Programmiersprachen, um diese Idee Wirklichkeit werden zu lassen.
In Eric Evans Domain-Driven Design11 wurde eine Reihe wichtiger