Hier möchte ich einen Überblick darüber geben welche Muster und Vorgehensweisen zum Thema Makro-Architektur empfehlenswert sind, und auch was inzwischen als überholt gilt. Beginnen möchte ich mit den Anti-Pattern in Kapitel 3.1 um danach in 3.2 die jeweils besseren Alternativen aufzuzeigen. Danach gibt es noch eigene Kapitel zu den Themen Domain Driven Design (3.3) und Integration und Konsistenz in Architekturen und verteilten Systemen (3.4).

3.1 Antipattern

3.1.1 Reuse

Ein weit verbreiteter Irrglaube ist, dass es das Ziel jeder guten Architektur wäre, für möglichst viel Wiederverwendung zu sorgen. In TOGAF wird es an mehreren Stellen erwähnt, genauso wie es Gartner es als ultimatives Ziel einer SOA sieht. In Wahrheit ist das aber ein Trugschluss.

Um die Absurdität auf den Punkt zu bringen, bedeutet Maximierung von Reuse den Versuch möglichst viele externe Abhängigkeiten in der Systemlandschaft zu haben. Stellen Sie sich bitte folgendes vor: Eine Bank hat ein System für Girokonten und eines für Sparbücher. Nun soll für die User des Sparbuch-Systems auch ein Zugriff auf gewisse Girokonto-Daten gewährt werden. Sie könnten nun dem Girokonto System ein Daten-Export Service verpassen, welches dann vom Sparbuch UI benützt wird. Aber macht das Sinn? Wäre es nicht sinnvoller das gewünschte Feature im Girokonto-System anzubieten und im Sparbuch System einfach einen Menüpunkt dorthin zur Verfügung zu stellen?

Ein hoher Grad an "Wiederverwendung" kann ein Indiz für falsch verstandene Separation Of Concerns sein und anzeigen dass die Schnitte in der Systemlandschaft an den falschen Stellen getätig wurden. An erster Stelle und auf oberster Abstraktionsebene sollten Sie immer nach fachlichen Kriterien schneiden.

Es geht in Wirklichkeit üblicherweise um etwas anderes: Vermeidung von Redundanzen. Versuchen Sie dazu aber besser Funktionalität möglichst gut lokal zu kapseln, denn so können Sie das selbe Ziel erreichen ohne in die Falle der vielen Dependencies zu tappen! Empfehlenswert dazu ist dieser Blog Post von Udi Dahan[1]. Das Problem mit dem Reuse ist nämlich: Das Ersetzen eines Services wird umso schwieriger, je öfter es wiederverwendet wird und je mehr Abhängigkeiten es hat.

Nach welchem Paradigma sollte man dann stattdessen ein Service erstellen? Als Alternative zur "maximalen Wiederverwendung" möchte ich Ihnen an dieser Stelle "einfache Ersetzbarkeit" vorschlagen, oder Replaceability. Ein paar Dubletten im Code werden die Existenz eines Unternehmens kaum gefährden, während das aber durch eine große und wichtige Komponente, die irgendwelche Probleme macht und dabei nicht so ohne weiteres durch eine neue ersetzt werden kann, sehr wohl der Fall sein kann. Wenn Sie für jede Komponente die Sie erstellen einen einfachen Plan haben wie sie ersetzt werden könnte, dann können Sie fast jedes Risiko eingehen, weil Sie im Falle eines Fehlschlages diesen jederzeit rückgängig machen können. Microservice Architekturen sind im Grunde nichts anderes als eine Umsetzung dieses Paradigmas.

3.1.2 Kanonische Modelle

In einem verteilten Systemen tauschen die einzelnen Systemteile Daten aus, auf welche Art und Weise auch immer. Da klingt es doch nach einer guten Idee, diese Datenformate einmalig global festzulegen, oder? Das ist es aber in den wenigsten Fällen, oder um Eric Evans, Erfinder des Domain Driven Designs[2] zu zitieren: "total unification of the domain model for a large system will not be feasible or cost-effective"

Ein Unterfangen zur Vereinheitlichung der Modelle ist so gut wie immer witzlos. Sie würden damit einfach das falsche Ziel verfolgen, denn um mit einer großen und komplexen Systemlandschaft flexibel zu bleiben, müssen Sie Teilbereiche schaffen, die so gut wie möglich unabhängig voneinander arbeiten können. Wenn Sie aber die Schnittstellenmodelle generell vereinheitlichen, müssen sich alle Weiterentwicklungen miteinander abstimmen, und genau das wollen Sie ja eben nicht, denn damit würden Sie jedwede Agilität abwürgen. Lassen Sie die einzelnen Komponenten jeweils ihre eigenen Modelle und Schnittstellen definieren, und das jeweils für die Teilbereiche die sie jeweils abdecken. Für den Service Consumer gibt es in DDD dazu den vom Facade und durch das Adapter Pattern inspirierten Anti Corruption Layer. Lassen Sie sich also lieber vom Domain Driven Design mit seinen "Bounded Contexts" inspirieren wo jedes Modell nur innerhalb gewisser Grenzen seine Gültigkeit hat. Nähere Infos hierzu bietet u.a. dieser exzellente Artikel von Stefan Tilkov[3]

3.1.3 Service Versioning

Die Frage die sich jeder stellt, der eine Architektur für ein verteiltes System erstellt, ist die, was man denn nun machen soll wenn sich eine der Schnittstelen ändert? In klassischer SOA wurde früher dann meist der Service-Provider in der alten, und zusätzlich dazu in der neuen Version ebenso parallel zur Verfügung gestellt um den laufenden Betrieb nicht zu gefährden. Und das so lange bis alle betroffenen Consumer auf die neue Version umgestellt haben. Meist lief es so ab wie in dem hier folgenden Beispiel:

  • Service A, aktuell in Version 1.1.0 deployt, stellt Erweiterungen online. Eine "Impact Analysis" ergibt, dass davon potentiell die beiden Service Consumer B und C betroffen wären. Daher wird die Änderung als Version 1.2.0 parallel zur alten Version deployt. Die Teams von B und C unterliegen der Unternehmensrichtlinie, nach der sie innerhalb eines Quartals die Umstellung auf die neue Version machen müssen.
  • Während Service C bald auf die neue Version 1.2.0 von Service A umstellt, sind die Benutzer von Service B mit dessen Stabilität unzufrieden, und somit wird hier alles daran gesetzt die Probleme in den Griff zu kriegen. Irgendwelche Richtlinien der IT Abteilung sind dem Kunden egal, und daher ist das Team von Service B die nächsten Monate mit Refacotrings beschäftigt.
  • Inzwischen müssen aber neue Features von Service A raus, und somit erstellt man eine neue Version 1.3.0
  • Team B bemerkt inzwischen, dass ihre Probleme teilweise von einem Bug in Service A verursacht werden, und um den Betrieb nicht zu gefährden wird kurzerhand eine neue Version von Service A deployt, nämlich 1.1.1

Nun laufen inzwischen 3 Varianten von Service A, nämlich 1.1.1, 1.2.0 und 1.3.0. Stellen Sie sich das Spiel mit einer Systemlandschaft aus 100 Services oder mehr über einen Zeitraum von 10 Jahren vor. Sie sehen also, dass es keine gute Idee ist Services in verschiedenen Versionen anzubieten.

Viel besser ist es also, wenn Sie eine neue Version eines Service einfach als rückwärtskompatiblen Evolutionsschritt jederzeit deployen können. Um das ohne schlaflose Nächste hinzubekommen bieten sich die Pattern an, welche ich Ihnen in diesem Kapitel noch vorstellen werde, wie Consumer Driven Contract Tests oder auch das Robustness Principle. Eine Alternative wären natürlich auch aufwändige System-Integrationstests.

3.2 Pattern

3.2.1 Consumer Driven Contract Tests

Kommen wir nochmal auf das Dilemma aus 3.1.3 zu sprechen. Ein Service möchte seine Schnittstelle ändern, und wir möchten mit möglichst viel Selbstvertrauen mit der neuen Version rausgehen können ohne den Betrieb zu gefährden. Eine Möglichkeit ist es dabei immer, auf System-Integrationstests zu setzen, die aber teuer und teilweise schwierig durchzuführen sind. Eine einfachere Alternative dafür stellen die sogenannten Consumer Driven Contract Tests dar. Bitte werfen Sie einen Blick auf die folgende Abbildung:

Abbildung 3.1

Abbildung 3.1

Die Idee ist die, dass jeder Service Consumer dem Service, welches er benützt, auch einen Testfall zur Verfügung stellt. Dieser Testfall deckt die Erwartungen des Consumers an den Service Provider ab. Man dreht damit quasi die Richtung um, und überlässt den Service Consumern die Beweisführung, dass eine neue Version des Service Providers problemlos in Produktion gehen darf. Mit so einem Testfall sagt der Consumer quasi: Wenn dieser Testfall durchläuft, so sind alle meine Erwartungen von mir an dich erfüllt. Somit sinkt auch der Bedarf an Integrationstests und man bleibt flexibel und agil!

3.2.2 Circuit Breaker

Viele kennen die folgende Situation aus der eigenen leidvollen Erfahrung: Ein Service bekommt im Betrieb Probleme, die sich automatisch auf alle Service Consumer auswirken, und sich daraufhin zu einem systemweiten Problem aufschaukeln. Durch Ressourcenengpässe kommt es teilweise zu Time-Outs, die aber leider die jeweiligen Consumer nicht davon abhalten können trotzdem weiterhin Anfragen zu schicken. Diese weiteren Anfragen zwingen dann das System endgültig in die Knie, und manchmal auch die Service Consumer mit dazu. Dabei wäre das ganz einfach zu verhindern gewesen, und zwar durch das sogenannte Circuit Breaker Pattern wie z.B. im Buch Release It! von Michael T. Nygard[4] beschrieben.

Der Consumer ruft den Provider dann nicht mehr direkt auf, sondern jeweils im Umweg über eine "Sicherung" (in Englisch: Circuit Breaker). Bemerkt dieser Probleme im Antwortverhalten des Providers, so sendet er eine Weile keine weiteren Anfragen des Consumers ab. Nach einer Weile versucht er es dann immer wieder mal, und wenn der Provider wieder läuft gehen die Anfragen wieder wie gewohnt dorthin.

Üblicherweise wird eine Default Antwort definiert, die der Circuit Breaker liefert falls der Provider offline ist, oder er schaltet wenn möglich auf einen anderen Service Provider um, so es alternative Quellen geben sollte. Der Phantase sind hier keine Grenzen gesetzt. Nähere Infos dazu auf der Seite von Martin Fowler. Die mit Abstand populärste Implementierung dieses Pattern bietet dabei Hystrix von Netflix an. Diese ist Open Source verfügbar.

3.2.3 Bulkhead

Das Bulkhead Pattern ist ebenfalls sehr gut im Buch Release It! von Michael T. Nygard[4] beschrieben. Das Bulkhead Pattern soll genauso wie ein Circuit Breaker die Stabilität des Gesamtsystems gewährleisten. Die Idee ist die gleiche, wie bei Schotten eines Schiffes. Läuft ein Schiff an einer Stelle leck, so werden andere bereiche abgeschottet, sodass diese nicht auch noch mit Wasser vollaufen können.

In der IT können solche Schotten auf alle möglichen Arten und Weisen repräsentiert werden, so könnten z.B. Teile der Systemlandschaft auf jeweils eigenen Server und Netzwerkteilbereichen im Rechenzentrum betrieben werden. Fällt ein Teilnetzwerk aus, so kann der Rest des Systems unter Umständen noch weiter betrieben werden.

3.2.4 Robustness Principle

Egal wie gut Sie die Schnittselle eines Service Providers hinbekommen, irgendwann wird der Zeitpunkt kommen, wo Sie eine Änderung daran werden vornehmen müssen. Wenn die betroffenen Service Consumer dabei in dem was sie vom Provider als Antwort gesendet bekommen nicht so streng sind, dann wird Ihnen das das Leben bei der Weiterentwicklung des Providers wesentlich leichter machen.

Das führt uns zum Robustness Principle bzw. Postel´s Law. Es besagt dass man sehr genau auf das achten soll was man abschickt, aber eher liberal bei dem was man akzeptiert. Nehmen wir an irgend eine Art von Geschäftsfall kann verschiedene Stati als Werte haben wie "Aufrecht" oder "Ungültig". Ein Consumer einer solchen Information könnte nun einfach nur zwischen "Aufrecht" und "Sonstigem" unterscheiden, und einfach alle Werte ungleich "Aufrecht" als "Nicht Aufrecht" interpretieren. In diesem Fall könnte der Provider einfach einen neuen Status "In Schwebe" hinzufügen, ohne dass diese Consumer überhaupt geändert werden müssten.

3.2.5 Feature Toggles

Ein interessantes Pattern, vor allem wenn man eine Continuous Delivery erreichen möchte, sind sogenannte Feature Toggles. Dabei wird das neue Feature oder die Änderung einfach zum bestehenden Code "dazuprogammiert", aber es wird mit einem Switch versehen, der nur in der definierten Testumgebung aktiv ist. Vor den Inproduktionsnahmen laufen dann jeweils die Tests für die vorige Version, denn somit wird sichergestellt dass durch diese zunächst "stumme" Implementierung des neuen Features die bestehende Funktionalität nicht beeinträchtigt wird.

Parallel erstellen Sie Testfälle für die neue Version, und sobald diese fehlerfrei laufen können Sie in Produktion den Switch auf das neue Feature umstellen. Danach kann die alte Variante des Codes entfernt werden. So einfach kann der Weg zu einer Continuous Delivery also sein.

3.1.6 Event Sourcing und CQRS

Event Sourcing ist ein Muster, welches in den letzten Jahren immer populärer geworden ist. Ich möchte es anhand eines Beispiels erläutern. Einmal habe ich einen Bonuspunkte Shop für eine Versicherung gebaut, wo der Kunde für bestimmte Ereignisse (also Events) Punkte bekommen hat, die er dann für Vergünstigungen wie Thermeneintritte einlösen konnte. Nehmen wir einmal an ich hätte das wie folgt gespeichert:

# Kunde ID Punkte
1 123456 14
2 987654 129

Also zu jedem Kunden nur den aktuellen Stand dieser Bonuspunkte. Das wäre eine Zeit lang kein Problem, bis der erste Kunde wie hier vielleicht "123456" anruft und nachfragt, warum er denn nur so wenige Punkte habe? Mit dieser Abbildung in der Datenbank ist das im Nachhinein nicht mehr nachvollziehbar. In letzter Zeit bemerken immer mehr Unternehmen, dass es durchaus einen höheren Business Value bedeuten kann, wenn man Daten nicht nur komprimiert ablegt. Also anstatt das Endergebnis zu speichern, werden alle Ereignisse abgelegt, die zu diesem Ergebnis geführt haben. Das Ergebnis kann man dann immer noch jederzeit als Snapshot ermitteln. In diesem Fall würde das dann so aussehen:

# Kunde ID Datum Typ Punkte
1 123456 5.9.2016 Buchung - Prämie +8
2 123456 5.10.2016 Buchung - Prämie +8
3 123456 5.10.2016 Buchung - Prämie +8
4 123456 5.11.2016 Buchung - Prämie +8
5 123456 5.12.2016 Buchung - Prämie +8
6 123456 31.12.2016 Buchung - Freischadenbonus +22
7 123456 1.1.2017 Snapshot +62
8 123456 5.1.2017 Buchung - Prämie +9
9 123456 17.1.2017 Buchung - Kauf Thermeneintritt -66
10 123456 5.2.2017 Buchung - Prämie +9
11 123456 22.2.2017 Snapshot +14

In Row 7, am Jahresende, wurde automatisch ein Snapshot erstellt. Ein Callcenter Mitarbeiter hat am 22. Februar einen weiteren Snapshot angefordert, und kann außerdem jederzeit nachvollziehen wie dieser Punktestand zustande gekommen ist. Eine einfache Möglichkeit Event Sourcing umzusetzen bietet übrigens diese als Open Source verfügbare Software, für die auch kommerzieller Support angeboten wird!

CQRS

Ganz ähnlich verhält sich das Pattern CQRS oder Command Query Responsibility Segregation, welches gut mit Event Sourcing kombiniert werden kann. Dabei werden die Schreib- von den Lesezugriffen getrennt. Dadurch können schreibende Zugriffe durch den Verzicht auf Indizes in ihrer Geschwindigkeit optimiert werden, während die Lesezugriffe zusätzlich noch mit einem Cache versehen werden.

3.3 Domain Driven Design DDD

Beim Domain Driven Design[2] geht es zunächst einmal darum, eine gemeinsame Sprache (aka Ubiquitous Langauge) zwischen Kunde und IT zu finden. Gemeinsam zerlegt man die Ziel-Domäne in einzelne Sub-Domänen, wofür die IT dann die einzelnen Modelle erzeugt welche jeweils im Rahmen ihres Bounded Contexts Gültigkeit besitzen. Im Optimalfall entspricht dann eine Sub-Domäne aus Kundensicht genau einem Bounded-Context aus IT Sicht. Hier ein Beispiel wie ich selbst einmal die Versicherungs-Domäne in ihre Sub-Domains zerlegt habe:

Abbildung 3.2

Abbildung 3.2

  • Core Domains - Die sind der eigentliche Kern des Systems, welcher den meisten Anwendernutzen finden oder auch die höchsten Einnahmen bringen werden. Es darf davon übrigens mehr als nur 1 geben. Keineswegs stehen die Core-Domains übrigens irgendwie "in der Mitte", sondern sind in erster Linie einfach "wichtiger" als der Rest.
  • Supporting Domains - Unterstützen die Core Domains. Hier wird üblicherweise nicht so viel in Qualität investiert wie in den Core Domains.
  • Generic Domains - Allgemeine Dinge, die in den meisten Fällen durch vorhandene oder kommerzielle Lösungen abgedeckt werden können. Ein typisches Beispiel wäre Buchhaltung oder das Versenden von EMails.

Die Begriffe des Domain Driven Designs sorgen immer wieder für Verwirrung. Ich versuche es an dieser Stelle auf den Punkt zu bringen: Wenn eine Domäne in ihre Subdomänen zerlegt wird, wird sie also zunächst einmal in ihre einzelnen Teilprobleme zerlegt. Diese werden dann in die oben erwähnten Kategorien eingeteilt. Im Sinne der Ubiquitious Langauge wird dann üblicherweise ein Analysis Model erstellt welches dann in einem Code-Model (der Implementierung) abgebildet wird. Diese Modell-Implementierung ist dann jeweils in den Grenzen eines Bounded-Context gültig, wobei die Relation von Subdomäne zu Bounded-Context nicht 1:1 sein muss. Ich habe im Beispiel von Abbildung 3.5 empfohlen die komplexeren Sub-Domänen auf mehrere Kontexte aufzuteilen.

In DDD wird auch definiert, wie die einzelnen voneinander abgegrenzten Kontexte wieder miteinander verbunden werden können. Ein Shared Kernel beispielsweise definiert ein gewisses Set an gemeinsamen Elementen die geteilt werden. Ein Anticorruption Layer konvertiert das Modell einer Subdomäne in ein anderes um diese inhaltlich voneinander zu entkoppeln. Eine Published Language ist ein bestimmter Teil der gesamten Domäne, welcher als gemeinsame Sprache aller Sub-Domänen definiert wird. Bei einer Published Language ist allerdings insofern Vorsicht geboten, da sie breit eingesetzt wird und später daher nicht mehr geändert werden kann. Man darf dabei nicht in die Falle eines Kanonischen Modells tappen, welches hier weiter oben zu Recht als Anti-Pattern angeführt wird.

Als weiterführende Literatur empfehle ich Ihnen das wirklich sehr gute Buch Pattern, Principles, and Practices of Domain-Driven Design von Scott Millett und Nick Tune[5]

3.4 Integration und Konsistenz

3.4.1 DB Integration mittels 2 Phase Commit

Abbildung 3.6

Abbildung 3.6

Das folgende hypothetische Szenario einer Banken-IT soll uns durch Kapitel 3.4 begleiten: Wir nehmen an dass es sich bei Service A um das System zur Verwaltung der Girokonten handelt, während System B sich um die Sparbücher kümmert. Beide sollen bewusst getrent voneinander bleiben, auch was ihre Datenbanken angeht. Wie gehe ich aber mit dem Wunch nach Konsistenz zwischen diesen beiden Datenbanken um? Eine einfache Möglichkeit wären verteilte Transaktionen. Falls beide Datenbanksysteme XA fähig sind, so wäre das mit sogenannten 2 Phase Commits möglich. In der Praxis ist davon aber eher abzuraten, und zwar aus folgenden Gründen:

  • Die einzelnen Datenbanktransaktionen bleiben lange offen, weil sie auf eine andere Datenbanktransaktion warten. Damit macht man keinem Administrator wirklich Freude. Das kann unter Umständen dazu führen, dass Probleme einer Datenbank sich direkt auch auf andere Datenbanken auswirken.
  • Sie sind in der Auswahl Ihrer Datenbanken darauf beschränkt, immer welche zu wählen die auch XA fähig sind.

3.4.2 Eventual Consistency und Kompensationstransaktionen

In den allermeisten Anwendungsfällen wird eine sogenannte Eventual Consistency ausreichend sein. Es wird den Kunden mit Sicherheit nicht stören, wenn das Geld am Sparbuch erst mit ein paar Sekunden Verspätung erscheint, Hauptsache es geht nicht verloren. Eventual Consistency bedeutet dabei übrigens korrekt übersetzt soviel wie schlussendliche Konsistenz, und meint also nicht dass die Daten nur eventuell miteinander konsistent sind, sondern temporär unter Umständen für kurze Zeit nicht, im Endeffekt aber auf jeden Fall.

Um das umzusetzen bieten sich sogenannte Kompensationstransaktionen an, in diesem Fall könnte das so aussehen:

  1. Zunächst wird das Geld vom Girokonto abgebucht (Transaktion 1)
  2. Danach wird das Geld auf das Sparbuch gebucht (Transaktion 2), sollte das nicht möglich sein weil das Sparbuch beispielsweise aufgelöst wurde, so wird das Geld wieder auf das Girokonto zurückgebucht (Transaktion 3)

Das bedeutet demnach, dass es unter Umständen zu einer Korrekturbuchung kommt. Diese erfolgt ggf. in der sogenannten Kompensationstransaktion Nr. 3 in diesem Beispiel. In weiterer Folge möchte Ich Ihnen 3 Möglichkeiten vorstellen, um so etwas umzusetzen:

  • Choreografie über Lightweight Messaging
  • Orchestrierung über BPMN Standard
  • Orchestrierung über einen globalen und zentralen ESB (Klares Antipattern)

3.4.3 Choreografie

In einer Cherografie würde das Quell-System A (Girokonto) im einfachsten Fall nur einen Command an das Sparbuch-System B schicken. Sollte System B aus irgendeinem Grund die Buchung nicht verarbeiten können, so kann es einen Publish/Subscribe Event auf den Message-Bus stellen. System A kann diese Art von Events abonniert haben und entsprechend die Rückbuchung vornehmen.

Abbildung 3.3

Abbildung 3.3

Dieses Vorgehen hat gegenüber dem folgenden Ansatz der Orchestrierung einen gewaltigen Vorteil: Andere Systeme können den Event von System B ebenfalls abonniert haben, wie beispielsweise System C welches für einen Callcenter Mitarbeiter einen Task erzeugt. Dieser beinhaltet einen Auftrag den Kunden anzurufen, um ihm die fehlerhafte Buchung mitzuteilen. So ist die Systemlandschaft also einfacher zu erweitern, und andere Systeme können ohne Änderungen an der bestehenden Lösung ebenfalls auf die bestehenden Events reagieren. Eine Choreografie setzt demnach das Open Closed Prinzip besser um als andere Ansätze.

3.4.4 Orchestrierung

Abbildung 3.4

Abbildung 3.4

Bei einer Orchestrierung würde sich das jeweilge Quell-System selbst um die exakte Durchführung des Ablaufes kümmern. Es würde jeden der einzelnen Schritte selbst anstoßen und entsprechend auf die Antworten reagieren. Es muss in der Lage sein mit der weiteren Verarbeitung zu warten, weil es nicht davon ausgehen darf, dass auch alle Ziel-Systeme immer verfügbar sind, und daher u.U. eine BPM Technologie einsetzen. In der Grafik sieht man auch dass es hier zu wesentlich mehr Punkt zu Punkt Verbindungen kommt, als bei einer Choreografie. Nähere Infos zur Orchestrierung finden Sie auf der Website von Gregor Hohpe

Setzen Sie bitte zur Orchestrierung übrigens am Besten immer auf die von der Object Management Group definierten Standards, wie BPMN und DMN. Lassen Sie die Finger von teuren und proprietären ESB Tool Suites. Wenn Sie sich am Standard orientieren, so können Sie die Implementierung jederzeit wechseln, und werden nicht von einem ESB Hersteller abhängig. Inzwischen gibt es übrigens auch frei verfügbare Implementierungen dieser Standards.

3.4.5 Enterprise Service Bus Integration

In Old-School SOA hat man gerne damit argumentiert, dass jede externe Abhängigkeit über einen ESB gehen müsse. In der Theorie zur Enterprise Architektur wird diese Ansicht gerne auch heute noch vertreten. Alle externen Abhängigkeiten und die dazu eventuell notwendige Orchestrierung sollten dort gekapselt sein. Eine mögliche Architektur sieht dann nach einer Weile so aus:

Abbildung 3.5

Abbildung 3.5

Was Sie in dieser Abbildung sehen ist das sogenannte "Knot-Antipattern", oder ein SOA Spaghetti-Knödel. Logik aus den einzelnen Domänen wandert dabei immer weiter in die zentrale ESB Komponente. Und Ablauflogik eines Prozesses ist auch nur Logik, die genauso in eine der Sub-Domänen gekapselt gehört, wie jede andere Logik der Sub-Domäne auch. Ein komplexer systemübergreifender Ablauf zur Anlage eines Giro-Kontos einer Bank gehört demnach nicht in den zentralen ESB, sondern genauso wie die Persistenz und die Business-Logik selbst in die Komponente die diese Sub-Domäne implementiert.

Logik "in der Mitte" stellt also eine krasse Verletzung des Separation Of Concerns Prinzips dar. Heute ist die einhellige Meinung unter Architekten, dass die Logik immer in den "Endpoints" gekapselt gehört, und nichts in der "Pipe" dazwischen verloren hat. Dieser Grundsatz wird heute als "Dumb Pipes and Smart Endpoints" bezeichnet. In Kapitel 3.4.3 sehen Sie eine korrekte Umsetzung dieses Prinzips. 2006 war es Martin Fowler[6], der als erster die bis dahin gängige Praxis anzweifelte, alle Verbindungslogik in einem zentralen ESB zu erledigen.