Bei Verteilten Systemen sind die einzelnen Komponenten im Netzwerk verteilt, und kommunizieren über Remote Schnittstellen miteinander. Zur Abgrenzung von Verteilten Systemen, oder Distributed Systems, möchte ich zuerst noch ein paar Worte zu monolithischen Architekturen verlieren:

4.1 Monolithen

Monolithische Makro Architekturen kommen zusehends aus der Mode, da diese zweifelsohne ihre Nachteile haben. Die Entwicklung wird schwerfällig und erfordert einen vergleichsweise hohen Abstimmungsbedarf. Die Kopplungen auch auf der Makro Ebene erfolgen dabei immer noch Code und die Flexibilität ist gering.

Das größte Problem welches Sie mit Monolithen früher oder später haben werden, ist die Schwierigkeit eine technologische Migration durchzuführen. Egal für welche Technologieplattform Sie sich entscheiden, sie wird früher oder später abzulösen sein, weil sie moderneren Anforderungen nicht mehr gewachsen sein wird, oder weil es schlichtweg kaum Personal mehr gibt welches diese Technologie noch beherrscht. In einem Verteilten System, ohne Koppelung auf Technologie-Ebene, stellt das kein allzugroßes Problem dar, da Sie einfach die einzelnen Bestandteile schrittweise auswechseln können. Bei einem Monolithen wird das ungleich schwieriger sein.

Davon abgesehen sind monolithische Architekturen meiner Meinung nach besser als ihr Ruf. Es stimmt nämlich schlichtweg nicht, was in jedem Microservice-Talk auf Konferenzen behauptet wird, nämlich dass Monolithen prinzipiell unstrukturiert wären. Natürlich kann man mit Tools wie Sonargraph herrlich struktrurierte und gut wartbare Monolithen bauen. Und Monolithen haben auch klare Vorteile: Ein strukturelles Refactoring in einem Monolithen ist ungleich einfacher als in einem Verteilten System. Durch Pattern wie Feature Toggles kann man auch eine Continuous Delivery umsetzen. Die Plattform etsy ist dabei ein Beispiel für eine riesigen Monolith welcher zigmal am Tag deployt wird. Es ist also möglich!

Um den von mir sehr geschätzten Simon Brown[1] zu zitieren:

If you can’t build a well-structured monolith, what makes you think microservices is the answer?

Bei Architektur geht es schließlich nicht um Monolith vs. Microservices, sondern in erster Linie um Struktur. Ein gut strukturierter Monolith lässt sich meistens mit überschaubarem Aufwand auch in eine Microservice Architektur migrieren. Alles andere ist nur eine Frage von pro und contra, welches es für beide Architekturansätze zweifelsohne gibt. Monolithen sind teilweise auch heute noch für manche Anwendungsfälle eine gute Wahl!

Ebenfalls von Simon Brown stammt die sehr gute Idee eines Architekturansatzes, der zwischen Monolith und Verteiltem System angesetzt ist. Er nennt diesen Ansatz "Modular Monolith", nähere Infos dazu auf seiner Website codingthearchitecture.com. Wenn Sie einen solchen modularen Monolithen bauen, und auf eine lose Koppelung zwischen den Modulen achten, haben Sie nämlich jederzeit die Möglichkeit zu einem Refactoring. Ein gut struktrurierter Monolith lässt sich eben jederzeit Schritt für Schritt umbauen in ein Verteiltes System.

4.2 Idempotenz

Jeder Service Consumer/Client welcher remote eine Nachricht zu einem Provider/Server zur Verarbeitung schickt, hat das selbe Problem: Im Falle des Ausbleibens einer Antwort kann er sich nie sicher sein ob die Verarbeitung am Server stattgefunden hat, oder nicht. Was mache ich also als Consumer, wenn ich einen neuen Datensatz zur Verarbeitung an einen Provider schicke und die Antwort ausbleibt?

Im Falle eines idempotenten Services ist das kein Problem, dann hat man nämlich die Garantie, dass man den Request wiederholen kann, ohne dass es für den Server einen Unterschied machen würde. Idempotenz garantiert, dass egal wie oft der selbe Request vom Client geschickt wird, es keinen Unterschied für den Status am Server macht. Wobei sich allerdings die Antwort die vom Server/Provider kommt sehr wohl unterscheiden darf. Ein Server kann auf den allerersten Request mit einem "OK" reagieren, und auf die Wiederholung mit einem "Bereits erhalten" antworten. Idempotenz löst also eines der größten Probleme, welches Sie automatisch haben werden sobald Sie ein Verteiltes System bauen. Streben Sie also bei Remote-Schnittstellen immer Idempotenz an.

Ich empfehle bei Verteilten Systemen übrigens immer, Idempotenz zur Not künstlich herzustellen. Dazu kann man einen Idempotency Filter einzusetzen, der automatisch Dubletten ausfiltert. Oder jeder Request wird vom Absender immer mit einer UUID als Resource-ID versehen, die dann am Server geprüft wird, ob sie bereits verarbeitet wurde.

4.3 REST

Bei REST, oder Representational State Transfer, handelt es sich um eine Philosophie der Kommunikation in vereilten Systemen, die sich an der Remote-Kommunikation im World-Wide-Web orientiert. Schnittstellen werden dabei prinzipiell als Ressourcen abgebildet, und für die Kommunikation sind die Verben des Webs vorgesehen wie GET, PUT, POST oder DELETE.

Einer der Häufigsten Irrtümer, welchen ich in meiner Karriere als Architekt begegnet bin, ist es jeden JSON String welcher über http versendet wird als "REST Service" zu bezeichnen. Tatsächlich ist das nach Richardsons Maturity Model[2] Rest Level 0. Die weiteren Ebenen auf dem Weg zu einer REST Architektur sind:

Level 1

Alle Remote-Procedure-Calls (RPCs) sind als Ressourcen abgebildet. Aus einem Service zur Reservierung eines Termins wird eine Termin-Ressource welche remote angesprochen werden kann.

Level 2

Die Kommunikation mit den Ressourcen erfolgt ausschließlich über http Verben. Die Antworten wie Response-Codes halten sich an die Standards des Webs. Dies hat den Vorteil, dass einem Client klar ist ob eine Kommunikation idempotent ist, da dies für die Verben des Webs genau definiert ist. So ist z.B. ein PUT per definition immer idempotent, während ein POST das prinzipiell nicht ist.

Level 3

Für die 3. und letzte Stufe muss die Schnittstelle eine Form von HATEOAS umsetzen. Das bedeutet, dass der Client von der ersten Anfrage an über URIs die den Antworten hinzugefügt werden, zu den folgenden Requests/Ressourcen weitergeleitet wird. Dafür gibt es inzwischen einige definierte Standards wie z.B. HAL. Ich persönliche empfehle Level 3 vor allem dann, wenn man eine öffentliche API für Developer interessant machen möchte, indem man ihnen das Erlernen möglichst einfach macht.

Ich persönlich bin ein großer Fan von REST bis Level 2! Anstelle von Level 3 möchte ich aber an dieser Stelle auf eine "Alternative" hinweisen: nämlich die REST API statt dessen mit der auf swagger basierenden Open API Specification zu dokumentieren. Schnell geht das über den Online verfügbaren Swagger Hub.

REST vs. SOAP

Zu einem religiös anmutenden Glaubenskrieg ist inzwischen der Kampf zwischen den Spezifikationen SOAP und REST entbrannt. Meiner Meinung nach hat SOAP den großen Vorteil, dass es ein offizieller Standard ist, während REST eher eine lockere Anhäufung von Best-Practices darstellt. REST ist im Endeffekt aber eleganter, da:

  • Es durch Level 1 jeder Schnittstelle automatisch struktur in jedes Service bringt.
  • Es durch die Standards von Level 2 von jedem Entwickler schneller verstanden wird. Infrastruktur könnte Netzwerknachrichten analysieren, und Statistiken erstellen, weil an den Returncodes erkennbar ist ob ein Request erfolgreich abgearbeitet wurde oder nicht.
  • Es bei Level 3 für außenstehende relativ einfach zu verstehen ist.

4.4 Messaging

Wo Remote Procedure Calls darüber definiert sind, dass sie synchron erfolgen, so geht es beim Messaging um asynchrone Kommunikation. Man reduziert die Abhängigkeit zwischen Sender und Empfänger also um die zeitliche Annahme, was bedeutet, dass Sender und Empfänger nicht gleichzeitig online sein müssen.

Apropos REST vs. SOAP Debatte aus Kapitel 4.3.: Ein oft vorgebrachtes Argument gegen REST ist, dass es bei REST kein Pendant zum WS-Reliable-Messaging Standard gäbe. Mit dem folgenden Pattern lässt sich aber Abhilfe schaffen. Nehmen wir gleich einmal den Worst-Case als Beispiel: Sie wollen in einem Service asynchron eine Message wie einen Command empfangen, und danach einen Event über die erfolgte Bearbeitung abfeuern. Dabei soll sichergestellt sein, dass keine Message doppelt verarbeitet wird, und auch dass keine verloren geht. Bitte werfen Sie einen Blick auf die folgende Abbildung 4.1:

Reliable Messaging Pattern

Abbildung 4.1

  1. Der Message Broker stößt eine Verarbeitung in ihrem Service z.B. über ein JEE Message Driven Bean an.
  2. Die Message wird in einer eigenen Tabelle abgelegt, die die zu verarbeitenden Messages enthält. Der Status dabei ist "offen". Dubletten werden hier durch entsprechende Definition des Datenbanktabelle vermieden. Diese Transaktionsspanne in der Datenbank wird jeweils durch die Breite des Pfeils unten in der Abbildung dargestellt.
  3. Dem Message Broker wird der erolgreiche Empfang der Nachricht mitgeteilt, danach sollte keine weiter Zustellug erfolgen.
  4. Die Message aus der Tabelle wird verarbeitet, die Änderungen im Datenmodell durchgeführt, der Status der eingehenden Nachricht wird auf "erledigt" gestellt und die ausgehende Message wird mit dem Status "offen" erzeugt. Alles in einer Transaktion.
  5. Die ausgehende Message wird an die Messaing Infrastruktur gesendet. Üblicherweise werden Dubletten von einer solchen Infrastruktur ohnehint nicht akzeptiert, wofür Sorge zu tragen ist.
  6. Die fertig verarbeitete ausgehende Message wird in der Tabelle mit dem Status "erledigt" versehen.

Dann brauchen Sie noch folgendes: Einen Job, welcher regelmäßig die Datenbank-Tabellen für "Incoming" und "Outgoing Mesages" dursucht. Findet dieser dort Nachrichten, welche schon etwas länger existieren, aber nicht fertig verarbeitet wurden, so sind von diesem die weiteren Schritte automatisch anzustoßen. Nachrichten die auch längere Zeit nicht verarbeitet werden können, sollten auf eine Fehlerliste wandern.

Dieses Pattern funktioniert auch in Document-Stores wie der MongoDB, wo keine Transaktionen über ein Dokument hinaus möglich sind. In diesem Fall ist einfach das jeweilige Dokument um die Teile zu erweitern, wo Sie die ein- und ausgehenden Messages abspeichern können.

Das CAP (oder Brewer-) Theorem besagt, dass von den folgenden 3 Eigenschaften:

  • Consistency / Konsistenz
  • Availability / Verfügbarkeit
  • Partition Tolerance / Partitionstoleranz

immer nur 2 gleichtzeitig erfüllt werden können. Ist also ein System immer verfügbar und sind die Daten dabei konsistent, dann ist das nur möglich wenn wirklich niemals ein Knoten ausfällt. Können Knoten auch ausfallen, so muss automatisch entweder auf die Verfügbarkeit, oder auf die Konsistenz der Daten verzichtet werden. Nähere Infos dazu auf der Wikipedia Seite dazu. Ein paar Beispiele aus der Welt der Datenbanken:

  • CA - Typisch für klassische Relationale Datenbanksysteme
  • CP - Wie MongoDB (Document-Oriented) oder REDIS (Key / Value)
  • AP - Wie CouchDB (Document-Oriented)

Interessant zu wissen ist, dass das CAP Theorem dabei nicht nur auf Datenbanken angewendet werden kann, sondern auf jede Art von Verteiltem System. Apropos Datenbanken: Dass C in CAP möchte ich noch etwas näher erläutern, es führt nämlich oft zu Verwirrung, weil es nämlich etwas anderes bedeutet als das C in ACID. Beim C in CAP geht es darum, dass alle Knoten des Verteilten Systems immer die gleichen Antworten liefern, und nicht ein Knoten eine Zeit lang eine andere als der nächste Knoten. Beim C in ACID wiederum, dass die einzelnen Datenteile, die üblicherweise im Tabellen aufgeteilt sind, auch zusammenpassen. Also die Daten in sich auch konsistent sind!

4.6 Service Orientierung

Kommen wir nun zu Service-Orientierung, und somit zu der eigentlichen Frage: Nach welchen Mustern kann man ein verteiltes System bauen? Zunächst einmal möchte ich mal definieren was alle Ansätze aus der Welt der Service-Orientierung gemeinsam haben: Die einzelnen Komponenten sind über das Netzwerk miteinendaer verbunden, dabei gibt es üblicherweise eher keine Datenbankintegration und auch keinen bis wenig Code-Reuse. Auch 2-Phase-Commit Transaktionen über Datenbankgrenzen hinweg sind eher verpönt, was unter dem Strich bedeutet, dass wir uns nun automatisch im Bereich der Eventual Consistency befinden. Konsistenz gibt es also am Ende immer, aber temporär können Daten zueinander inkonsistent sein. Integration passiert demnach über Choreografie oder Orchestrierung, was bedeutet dass Konsistenz durch Kompensationstransaktionen hergestellt wird. Udi Dahan[3] hat für mich das Thema Service-Orientierung am besten auf den Punkt gebracht, indem er für mich die perfekte Definition von Service als eine Komponente in einer Service-Orientierten Welt geliefert hat:

A service is an autonomous technical authority for a specific business capability. Any piece of data or rule must be owned by only one service.

Da steckt im Grunde alles drin, nämlich auch die Antwort auf die Frage: An welchen Grenzen soll man denn ein Service schneiden? Zur Abgrenzung möchte ich an dieser Stelle definieren was demnach KEIN Service ist: Eine Remote Schnittstelle (wie SOAP), welche keinem anderen Zweck dient als Create, Read, Update und Delete (CRUD) Zugriffe auf ein bestimmtes Set von Daten zu liefern. Solche Komponenten, wie ich ihnen leider nur allzuoft begegnet bin, sind demnach keine Services weil:

  • Was passiert mit diesen Daten? Sie nur abzuspeichern kann ja unmöglich bereits einen Unternehmenswert haben. Wo ist die Business-Logik dazu? Wie wird das dargestellt oder ausgewertet? Diese Logik gehört ebenfalls IN das Service hinein!
  • SQL ist, von einigen Dialekten mal abgesehen, ein recht gut definierter Standard. Es bringt also kaum einen Vorteil Datenzugriffe nocheinmal zu kapseln, sei es mittels SOAP oder REST oder was auch immer.

Natürlich werden die meisten Services dabei remote Schnittstellen haben. Diese sollten aber im Sinne des Informationen Hiding Principles nur nach außen hin als API anbieten, was auch wirklich von anderen Services benötigt wird. Ein User Interface betrachte ich dabei gerne als eine spezielle Art von API, die für menschliche Service Consumer gedacht ist, und sollte meiner Meinung nach auch immer Bestandteil des Services sein.

Der Hauptvorteil gegenüber einer monolithischen Architektur, und der Hauptgrund warum man sich das antun sollte ist für mich übrigens der: Jede Technologie kommt irgendwann einmal an ihr Ende. Auch JAVA ist nur das Cobol von morgen. Mit einem Monolithen werden Sie sich mit der Migration immer ungleich schwerer tun, als mit einem Verteilten System. Genug der einleitenden Theorie, sehen wir uns jetzt konkret einige Möglichkeiten an, Service-Orientierung umzusetzen:

4.6.1 SOA (Service Orientierte Architektur) nach TOGAF

Es ist gar nicht so einfach, eine Definition zu finden, was klassische SOA eigentlich ist. Fündig bin ich geworden in der TOGAF Spezifikationen von der Open Group, wo unter anderem eine Referenzarchitektur für SOA definiert ist:

SOA nach TOGAF

Abbildung 4.2

Wir sehen also gleich, dass TOGAF klar in Layern strukturiert, was wir im Kapitel 2.3 über Separation of Concerns bereits als Fehler identifiziert haben. Auch gibt es einen dezidierten Prozess-Layer welcher mit einem ESB abgebildet werden soll, was wir im Kapitel 3.4.5 als Antipattern definiert haben.

Wenn wir dann noch einen näheren Blick auf den Service-Layer werfen, dann unterscheidet TOGAF dort zwischen Daten- und Business-Application-Services. Das habe ich bereits in Kapitel 6 als eine überholte Denkweise dargestellt.

Andere Definitionen von SOA, wie die von Gartner bleiben wiederum so abstrakt, dass auch ich damit leben könnte. Eine Event-driven SOA mit Einsatz des Composite UI Pattern ist etwas, mit dem auch ich mich gut anfreunden kann. Was Gartner hier beschreibt entspricht mehr meiner Definition von Verteiltem System oder Service-Orientierung generell, und darunter würden auch Microservices fallen. Gartner macht allerdings dabei immer noch den Fehler, den Gedanken des Reuse dabei zu überbewerten. Unter dem Strich kann man nur feststellen dass all diese Begriff nicht wirklich definiert sind, und es auch keinen Konsens in der Architektur-Community gibt was genau sie wirklich bedeuten.

4.6.2 Microservices

Genauso wie SOA ist auch der Begriff Microservices nicht näher definiert. Einigkeit herrscht aus meiner Sicht über folgende Punkte:

  • Tendentiell kleinere, leicht austauschbare Services, wobei der Focus eher auf Ersetzbarkeit als auf Wiederverwendung liegt.
  • Jedes Microservice wird nur von einem Team bearbeitet, dadurch Verringerung des Abstimmungsbedarfs. Es ist aber sehr wohl erlaubt, dass ein Team mehr als 1 Microservice bearbeitet.
  • Eher kleine Teams ohne weitere Sub-Strukturen, wie bei Amazon wo die 2-Pizza Regel besagt, dass eim Team nicht so groß sein darf, dass sie durch 2 Pizzen nicht satt zu kriegen wären. Dadurch wird die Kommunikation einfach und effizient.
  • Integration der Services erfolgt über REST bzw. lightweight Messaging.
  • Moderne Programmierparadigmen wie die auf dieser Website vorgestellten Pattern zum Thema Architektur.
  • Reuse von Code zwischen den einzelnen Microservices ist nur in Ausnahmefällen erlaubt.

Verschiedene Meinungen innerhalb der Community gibt es allerdings zu folgenden Themen:

  • Konkrete maximale "Servicegröße" - Die einen sind der Meinung, dass ein Microservice maximal 100 oder 1000 LOC z.B. JAVA haben sollte. Für andere sind Microservices eher ein Sammelsurium an modernen Paradigmen zur Service-Orientierung, als Gegensatz zu klassischer SOA wie hier in Kapitel 4.1 dargestellt.
  • Ob ein Microservice auch ein User-Interface enthält - Für manche ist die einfachste Art und Weise Microservices zu integrieren, die über das User-Interface. Damit das funktioniert, muss ein Microservice natürlich auch das UI beinhalten. Für andere wiederum sollten Services und UI strikt getrennt werden, und dann kommt oft auch ein API-Gateway wie Netflix Zuul zum Einsatz.
  • Datenbank Integration - Es gibt unterschiedliche Meinungen ob sich unterschiedliche Microservices eine gemeinsame Datenbank teilen dürfen, oder ob auf jede Datenbank jeweils nur ein Microservice exklusiv Zugriff hat.

Betrachten wir mal 2 mögliche Microservice Architekturen, und zwar ganz bewusst 2 völlig unterschiedliche Ansätze:

Microservices Variante 1

Abbildung 4.3

In Abbildung 4.3 dürfen sich Microservices eine Datenbank teilen, während das UI von den Services getrennt wurde und über ein API Gateway darauf zugreift. Bei diesem Ansatz besteht für mich ganz besonders die Gefahr, einen "Distributed Big Ball Of Mud" zu erzeugen. Ich empfehle diesen Ansatz nur bis zu einer gewissen Obergrenze an Komplexität.

In der folgenden Abbildung 4.4 wird jede Sub-Domäne bzw. Business Capability von einer Implementierung vollständig gekapselt. Also inklusive UI und Datenbank. Ich persönlich finde diesen 2. Ansatz empfehlenswerter, wenn er für den jeweiligen Anwendungsfall angemessen ist!

Microservices Variante 2

Abbildung 4.4

Egal für welche Ausprägung Sie sich entscheiden, so müssen Sie sich immer die Frage stellen, ob es auch wirklich Sinn macht auf Microservices als Architekturansatz zu setzen. Starke Aufteilung in kleine Module in einem Verteilten System bringen nämlich automatisch auch viel Integrationsarbeit mit sich, und diese ist teuer und unter Umständen im Betrieb prolbematisch. Es ist also eine Frage nach Pro und Contra ob Sie diese Nachteile für die folgenden Vorteile in Kauf nehmen wollen, die Microservices dafür zweifelsohne bieten:

  • Individuelle Technologieauswahl pro Feature möglich - Für ein Feature bietet sich eine NoSQL Datenbank an, und für ein anderes ein RDBMS? An einer Stelle brauchen Sie Ausfallsicherheit, an einer anderen besonders gute Skalierungsfähikeit? Kein Problem wenn Sie auf Microservices setzen!
  • Start UP Culture - Microservices fördern eine gewisse Stimmung im Unternehmen wie es sie sonst nur in Start-Ups gibt. Entwickler werden nicht so bevormundet wie in einer typischen SOA vom zentralen Enterprise Team und bleiben daher motiviert.
  • Experimente leichter möglich - Sie können einfacher Risiken eingehen, weil im Falle eines Fehlschlages jede Komponente einzeln einfach ersetzbar ist.

Bitte vergessen Sie eben aber auch niemals die Nachteile, wie z.B.:

  • Integrationsarbeit ist teuer, komplex und das Ergebnis ist schwieriger zu monitoren.
  • Refacotring ist schwieriger als beispielsweise bei einem Monolithen, wo das einfach über die rechte Maustaste in der IDE möglich ist.
  • Es besteht die Gefahr, dass einzelnen Requests so viele Microservices beteiligt sind, dass diese tiefen Aufrufkaskaden im Betrieb zu Instabilität führen.

Zwei Bücher zum Thema Microservices möchte ich Ihnen noch empfehlen, und zwar einmal das von Eberhard Wolff von innoq[5] und dann noch das von Sam Newman von Thoughtworks[6].

4.6.3 Right Sized Services

Was aber, wenn Sie nun die Vorteile des Microservice Ansatzes nicht benötigen, oder die Nachteile nicht in Kauf nehmen möchten? Und dass mit alten SOA Mustern heute kein Blumentopf mehr zu gewinnen ist steht ja außer Frage. Was haben Sie dann noch für Alternativen? In letzter Zeit kristalliert sich der Begriff "Right Sized Services" immer mehr heraus. Dabei geht es darum, moderne Paradigmen anzuwenden, wie sie hier auf der Website beschrieben sind und üblicherweise bei Microservice-Architekturen zur Anwendung kommen, ohne dabei diese feine Granularität der Services anzustreben. Ein Service ist dabei eben so groß wie es sein muss, um eine Sub-Domäne oder Business Capability vollständig abzubilden. Das, unter Umständen noch kombiniert mit dem folgenden SCS Ansatz, ist momentan sicher einer der vielversprechendsten Architekturansätze.

4.6.4 Self Contained Systems

Die Self Contained Systems[7] sind ein Ansatz, der ähnliche Philosophien umsetzt wie sie hinter den Microservices stehen. Es wird die Systemlandschaft dabei aber zu allererst in einige grobe Strukturen aufgeteilt, in die Systeme. Dabei kann die Architekturen wie die einzelnen Systeme umgesetzt sind, sich bewusst voneinander unterscheiden. So kann ein System aus Microservices bestehen, während ein anderes als Monolith implementiert wird.

Bei der Integration der Systeme sollte man dabei darauf achten, wenn möglich immer über das UI zu integrieren (ROCA Style), weil das die einfachste Form der Kopplung darstellt. Sollte das nicht ausreichend sein, so sollte wenn möglich asynchrones Messaging verwendet werden. SOAP ist eher verpönt, während auch statt synchronen REST Aufrufen eher auf Datenreplikation gesetzt werden sollte, sodass es zwischen den Systemen möglichst zu keiner zeitliche Abhängigkeit kommt. Dadurch wird die Gefahr der tiefen Aufrufkaskaden, unter der manche Microservice Architekturen leiden, so gut es geht vermieden.

Self Contained Systems

Abbildung 4.5

Ich persönlich bin ein großer Fan des SCS Ansatzes, und zwar aus folgenden Gründen:

  • Es ist einfach Generic Sub-Domains auch wirklich durch COTS (kommerzielle) Systeme abzubilden.
  • Es gibt dem reinen Microservice Ansatz etwas mehr Struktur auf einer höheren Abstraktionsebene.

Ob Sie wirklich auf Datenreplikation statt auf synchrone REST Calls setzen bleibt natürlich Ihnen überlassen. Allzu tiefe Aufrufkaskaden sollte man tatsächlich vermeiden. Andererseits ist Datenreplikation unter Umständen teuer. Ein anderer Nachteil der Datenreplikation wäre auch noch, dass es schwierig ist zu monitoren.

4.7 Fazit

Was kann man nun also empfehlen? Pauschal kann man das natürlich nicht beantworten, da es immer auf die konkreten Anforderungen ankommt. Als grobe Richtlinie möchte ich Ihnen aber noch folgendes mitgeben, abhängig von der Anzahl LOC von z.B. JAVA:

Bis 100.000 LOC

Hier ist meiner Meinung nach noch alles erlaubt. Hauptsache Sie vergessen nicht darauf, die Lösung in einzelnen Komponenten zu teilen. Egal ob Microservices oder ein Monolith würde ich sagen: Hauptsache strukturiert. SCS wäre bei dieser Größe wohl noch etwas übertrieben.

Darüber, bis 1.000.000 LOC

Hier sollte man bereits anfangen zu überlegen, ob man nicht noch eine weitere Strukturierungs-Ebene über der ersten definiert. Microservices sollten sich keine Datenbanken mehr teilen, und bei einem Monolithen würde ich bereits in Richtung Modular-Monolith tendieren.

Über 1.000.000 LOC

Hier empfehle ich auf jeden Fall mindestens (!) 2 Strukturierungs-Ebenen zu haben, wie es bei SCS der Fall ist. Wenn Sie die Datenreplikation nicht mögen, so sind synchrone REST Calls sicherlich auch ok, aber hüten Sie sich vor zu tiefen Aufrufkaskaden.

Wenn Ihnen keine der Varianten so wie sie hier vorgestellt wurde gefällt, so steht es Ihnen natürlich frei sich aus den auf dieser Website beschriebenen Prinzipien und Mustern sich Ihre ganz eigene Philosophie zu basteln. Wichtig scheint mir dabei folgendes: Je größer das System wird welches Sie bauen, desto mehr Struktur-/Abstraktionsebenen sollten Sie definieren. Und je weiter oben diese sich n dieser Hierarchie befinden, desto wichtiger wird lose Kopplung werden! Bei SCS würde das bedeuten: Innerhalb eines Systems ist Code-Reuse wie bei einem Monolithen noch in Ordnung, zwischen den Systemen aber nicht mehr.