Vom Monolithen zu Microservices. Sam Newman
sein, dass wir für uns und die Anwender unserer Software nicht das Richtige tun. Wir werden die Vor- und Nachteile von Monolithen und Microservices in Kapitel 3 weiter untersuchen und Werkzeuge vorstellen, die Ihnen dabei helfen, herauszufinden, was in Ihrer Situation richtig ist.
Zu Kopplung und Kohäsion
Es ist wichtig, die ausgleichenden Kräfte zwischen Kopplung und Kohäsion zu verstehen, wenn Sie die Grenzen von Microservices definieren. Bei der Kopplung geht es darum, dass, sobald wir etwas ändern, etwas anderes ebenfalls geändert werden muss. Bei der Kohäsion geht es darum, wie wir zusammengehörigen Code gruppieren. Diese Konzepte sind direkt miteinander verbunden. Constantines Gesetz drückt diese Beziehung sehr gut aus:
Eine Struktur ist stabil, wenn die Kohäsion stark und die Kopplung schwach ist.
– Larry Constantine
Das scheint eine vernünftige und nützliche Beobachtung zu sein. Haben wir zwei Bereiche eng miteinander verbundenen Codes, ist die Kohäsion gering, da die zusammengehörige Funktionalität über beide Bereiche verteilt ist. Ebenso haben wir eine starke Kopplung, denn wenn sich dieser Code ändert, müssen beide Bereiche angepasst werden.
Ändert sich die Struktur unseres Codesystems, wird das teuer werden, da in verteilten Systemen die Kosten für Änderungen über Servicegrenzen hinweg so hoch sind. Wenn Sie Änderungen über einen oder mehrere unabhängig deploybare Services hinweg vornehmen und dabei vielleicht sogar Serviceverträge brechen müssen, kann das ziemlich aufwendig sein.
Das Problem bei Monolithen ist, dass es allzu häufig das Gegenteil von beidem ist. Statt in Richtung Kohäsion zu tendieren und die Dinge zusammenzuhalten, die zusammengehören sollten, stecken wir allen möglichen unzusammenhängenden Code zusammen. Und lose Kopplung existiert damit auch nicht wirklich: Will ich eine Zeile meines Codes ändern, kann ich das vielleicht noch problemlos tun, aber ich kann diese Änderung nicht deployen, ohne potenziell einen Großteil des restlichen Monolithen zu beeinträchtigen – und ich muss mit Sicherheit das gesamte System neu deployen.
Wir wollen Stabilität auch deshalb, weil unser Ziel wo immer möglich die Umsetzung des Konzepts der unabhängigen Deploybarkeit ist – wir wollen also eine Änderung an unserem Service vornehmen und ihn in die Produktivumgebung deployen können, ohne etwas anderes ändern zu müssen. Damit das funktioniert, brauchen wir bei den von uns konsumierten Services Stabilität, und wir müssen einen stabilen Vertrag für die Services anbieten, die uns konsumieren.
Angesichts der vielen Informationen rund um diese Begriffe wäre es verrückt von mir, hier allzu umfangreich darauf einzugehen, aber ich denke, eine Zusammenfassung wäre trotzdem angebracht, insbesondere um diese Ideen in den Kontext von Microservices-Architekturen zu bringen. Schließlich beeinflussen diese Konzepte von Kohäsion und Kopplung sehr stark unser Denken über die Microservices-Architektur. Und das ist nicht überraschend – Kohäsion und Kopplung sind Aspekte modularer Software, und was ist Microservices-Architektur anderes als Module, die über Netzwerke kommunizieren und unabhängig deployt werden können?
Eine kurze Geschichte von Kopplung und Kohäsion
Die Konzepte von Kohäsion und Kopplung gibt es im Computerumfeld schon ziemlich lange. Ursprünglich wurden sie von Larry Constantine 1968 beschrieben. Diese Zwillingsideen von Kopplung und Kohärenz bildeten dann eine Grundlage dessen, wie wir über das Schreiben von Computerprogrammen denken. Bücher wie Structured Design von Larry Constantine und Edward Yourdon (Prentice Hall 1979) beeinflussten im Folgenden ganze Generationen von Programmierern (dieses Buch war Pflichtlektüre für mein Studium – nahezu 20 Jahre nach der Erstveröffentlichung).
Larry skizzierte seine Konzepte von Kohäsion und Kopplung erstmals 1968 (ein besonders vielversprechendes Jahr für die Informatik) auf dem National Symposium on Modular Programming – derselben Konferenz, auf der auch Conways Gesetz seinen Namen erhielt. Ebenfalls in diesem Jahr gab es zwei mittlerweile berüchtigte von der NATO geförderte Konferenzen, bei denen das Konzept des Software Engineering Bekanntheit erlangte (ein Begriff, der zuvor von Margaret H. Hamilton geprägt wurde).
Kohäsion
Eine der prägnantesten Definitionen, die ich für Kohäsion gehört habe, ist: »Code, der sich gemeinsam ändert, bleibt auch zusammen.« Für unsere Zwecke ist das eine ziemlich gute Definition. Wie schon besprochen, optimieren wir unsere Microservices-Architektur auf das Ziel hin, Änderungen in der Businessfunktionalität möglichst einfach zu machen – daher wollen wir, dass die Funktionalität auf eine Art und Weise gruppiert ist, dass wir Änderungen an so wenigen Stellen wie möglich vornehmen müssen.
Möchte ich ändern, wie die Rechnungsgenehmigung gemanagt wird, will ich die anzupassende Funktionalität nicht über mehrere Server hinweg finden müssen, um dann das Release dieser geänderten Services zu koordinieren, mit dem die neue Funktionalität ausgerollt wird. Stattdessen will ich dafür sorgen, dass die Änderung Anpassungen an so wenig Services wie möglich erfordert, um die Änderungskosten niedrig zu halten.
Kopplung
Information Hiding ist leichter gesagt als getan – so wie der Versuch, abzunehmen.
– David Parnas, The Secret History Of Information Hiding
Wir mögen Kohäsion, aber bei Kopplung sind wir argwöhnisch. Je mehr Dinge »gekoppelt« sind, desto mehr müssen sie auch zusammen geändert werden. Es gibt jedoch unterschiedliche Arten der Kopplung, und jeder Typ kann eine andere Lösung erfordern.
Es existieren viele Vorarbeiten zum Kategorisieren von Kopplungstypen, insbesondere von Meyer, Yourdan und Contantine. Ich stelle Ihnen hier meine eigene Kategorisierung vor – nicht weil ich meine, dass die bisherigen falsch sind, sondern weil ich diese Kategorisierung hilfreicher finde, wenn es darum geht, Menschen dabei zu helfen, Aspekte rund um das Koppeln verteilter Systeme zu verstehen. Daher ist das auch keine allumfassende Klassifikation der verschiedenen Formen der Kopplung.
Information Hiding
Ein Konzept, das bei Diskussionen rund um das Koppeln immer wieder auftaucht, ist die Technik des Information Hiding. Dieses Konzept, das erstmals von David Parnas im Jahr 1971 beschrieben wurde, entstand aus seiner Arbeit rund um das Definieren von Modulgrenzen.7
Die zentrale Idee des Information Hiding ist, die Teile des Codes, die sich häufig ändern, von den eher statischen Teilen getrennt zu halten. Wir wollen, dass die Modulgrenzen statisch sind, und es sollten die Teile der Modulimplementierung verborgen werden, die sich eher ändern. Interne Änderungen können auf sichere Art und Weise vorgenommen werden, solange die Modulkompatibilität gewährleistet ist.
Ich persönlich nutze den Ansatz, an der Grenze eines Moduls (oder eines Microservice) so wenig wie möglich preiszugeben. Wurde etwas Teil einer Modulschnittstelle, lässt es sich nur schwer wieder zurücknehmen. Aber verbergen Sie es jetzt, können Sie sich später immer noch dazu entscheiden, es zu veröffentlichen.
Dazu in Bezug steht das Konzept der Kapselung bei objektorientierter Software, aber abhängig von der von Ihnen akzeptierten Definition muss es nicht das Gleiche sein. Kapselung in der OO-Programmierung wird dazu genutzt, eines oder mehrere Dinge in einem Container zusammenzubringen – denken Sie an eine Klasse, die sowohl Felder wie auch die Methoden enthält, die mit diesen Feldern arbeiten. Sie könnten dann die Sichtbarkeitsregeln der Klasse nutzen, um Teile der Implementierung zu verbergen.
Eine längere Erläuterung der Geschichte des Information Hiding finden Sie in Parnas’ »The Secret History of Information Hiding«.8
Implementierungskopplung