Über unsMediaKontaktImpressum
Sven-Torben Janus 01. Juni 2021

Macroservices – Nicht kleine Teile, sondern das große Ganze

Microservices waren der große Architekturtrend der letzten Jahre. Vielen Versprechungen wurden sie jedoch nicht gerecht. Kein Wunder, wenn daher zuletzt in Social Media der Begriff Macroservices die Runde machte [1]. Gleichzeitig muss man feststellen, dass auch Microapps beliebter sind denn je. Geht es um Softwarearchitektur, scheint eines klar: "Kleiner ist besser". Allerdings scheint das Denken in größeren Einheiten auch wieder auf dem Vormarsch, wie Macroservices zeigen.

Kleiner ist besser! Wirklich?

Microservices, Nanoservices, Serverless, Bigball of mud – oder dann doch Macroservices? Nun, was denn jetzt? Groß oder klein? Zumindest scheint eines sicher: Größe ist ein Entscheidungskriterium. Und es betrifft nicht nur Softwareartefakte, sondern auch organisatorische Aspekte. Man denke nur an Amazons "Two Pizza Teams" [2] oder Strukturen von Communities of Practices (CoP) [3].

Nehmen wir etwas Abstand und zoomen aus dem Bild ein wenig heraus. Egal wie groß ein Service, ein Team oder eine CoP auch sein mag, sie ist immer Teil eines Ganzen – Teil eines soziotechnischen Systems.

Services, Teams, ganze Organisationen interagieren miteinander, erfüllen Aufgaben in einem Gesamten. Dafür brauchen sie Verbindungen zueinander. Sie bilden ein System. Darum war Architekturdesign schon immer auch Systemdesign und ist es heute umso mehr. In Systemen geht es inhärent immer um Abgrenzung: was ist drinnen, was ist draußen? Welche Teile spielen zusammen und welche nicht? Was spielt sich dazwischen ab? Die Größe eines Service oder Teams ergibt sich durch diese Abgrenzung. Entscheidend ist die Frage, was sich innerhalb der Grenzen bewegt, was dazwischen und was außerhalb.

Genauso war und ist die Antwort auf diese Frage immer ein Kompromiss. Je größer die Services, desto weniger Beziehungen oder Verbindungen braucht es zwischen ihnen. Und je kleiner die Services, desto mehr Verbindungen werden benötigt.

Reliabilität und Komplexität

Wenige große Services resultieren in weniger Verbindungen. Viele kleinere Services resultieren hingegen in mehreren Verbindungen. Diese Verbindungen erzeugen potenzielle Problemstellen.

Stellen wir uns Services für einen Moment als eine feste Menge miteinander interagierender und voneinander abhängiger Features vor. Interaktionen und Abhängigkeiten spiegeln sich in Verbindungen wider. Ob wir diese Menge an Features nun in wenige große Services oder mehrere kleine Services zerlegen, hat auf die Anzahl der Verbindungen zwischen den Features keine Auswirkung. Die Anzahl der Verbindungen steht fest solange wir das System nicht verändern.

Teilt man Services nun in kleinere Teile, tendiert man dazu, verlässliche Verbindungen durch weniger verlässliche Verbindungen zu ersetzen. Während "In-Memory"-Kommunikation als ziemlich verlässlich angesehen werden kann, gilt das für Kommunikation über Netzwerke hinweg nicht. Denkt man allein an Paketverluste auf der Leitung oder Kommunikationsabbrüche, wird ziemlich schnell deutlich, wie unzuverlässig diese Verbindungen sind. Zusätzlich haben wir nun unabhängige Laufzeiten, Deployment- und Entwicklungszyklen. Zugegebenermaßen kann das viele Vorteile haben. Polyglotte Entwicklung, unabhängigere Releases, unabhängige Skalierung oder Austauschbarkeit einzelner Teile sind hier nur einige Beispiele. Leider geht dies aber auch mit deutlich erhöhter Komplexität einher. So sind Services zur Laufzeit plötzlich nicht mehr verfügbar oder Schnittstellen werden inkompatibel zueinander.

Es kostet in der Regel eine Menge an Aufwand und Ressourcen, um diese unzuverlässigen Verbindungen wieder verlässlicher oder widerstandsfähiger zu machen. Plötzlich bedarf es Mitigations- oder Kompensationsstrategien. Wer kennt sie nicht, die scheinbar nie enden wollenden Diskussionen um Retries, Sicherstellung der Idempotenz von Aufrufen, (verteilte) Transaktionen oder Circuit Breakers. Service Meshes to the rescue! Ganz zu Schweigen von Versionierung von Schnittstellen bis hin zur manuellen, organisatorischen Kompensation für mögliche "Edge cases".

Zum Glück gibt es für jeden Aspekt mittlerweile das richtige Tool. Die Einführung solcher Tools wie Service Meshes oder die Implementierung von Strategien zur Minderung der Unzuverlässigkeit machen unsere Systeme jedoch technisch komplexer. Ganz zu schweigen von der damit verbundenen kognitiven (Über-)Belastung für Teams und Mitarbeiter. Man muss hier zu Recht manchmal die Frage aufwerfen, wie produktiv Teams eigentlich sein können, bei all dieser technischen Komplexität.

Bleibt festzuhalten: Je unzuverlässiger die Verbindungen zwischen den Services sind, desto komplexer ist das System in Summe. Daher ist ein ausgewogenes Verhältnis zwischen der Komplexität der einzelnen Teile und der des Gesamtsystems entscheidend. Aber wie stellt man diese Ausgewogenheit her?

Bounded Context und Context Maps

Ein entscheidender Faktor ist zunächst, das Zusammenspiel der Teile im Ganzen zu verstehen. Hierzu empfiehlt sich der Einsatz strategischer Methoden des Domain Driven Design (DDD). Ganz konkret Bounded Context für die Teile und Context Map für das Ganze.

Bounded Context

Beim Domain Driven Design wird Software auf Basis von Modellen der zugrundeliegenden Domäne erstellt. Die Modelle dienen dabei als eine Art allgegenwärtige Sprache (Ubiquitous Language). Das gilt im Sinne der Kommunikation zwischen den beteiligten Personen aber auch als konzeptionelle Basis für die Aufteilung der Software in ihre Teile. Jeder Teil wird als Bounded Context bezeichnet und muss sicherstellen, dass das ihm zugrundeliegende Modell in sich konsistent und widerspruchsfrei ist. Dabei wird beim DDD davon ausgegangen, dass die vollständige Vereinheitlichung eines Modells in einem größeren, komplexen System weder angemessen noch wirtschaftlich möglich ist. Daher nutzt DDD mehrere in sich konsistente Modelle zur Strukturierung, sogenannte Bounded Contexts.

Bei der Abgrenzung eines Bounded Context spielen neben dem Modell selbst auch die Geschäftsregeln und Richtlinien eine wichtige Rolle. Ganz im Sinne von "was ist drinnen und was ist draußen?". Zur Beantwortung dieser Frage hilft zumeist eine strategische Einordnung des Bounded Context anhand einiger Fragestellungen. So kennt DDD eine Klassifizierung in "Core Domain", "Supporting Domain" oder "Generic (Sub) Domain", zur Festlegung, wie wichtig ein Bounded Context für den Erfolg eines Unternehmens ist. Core Domains sind dabei die strategisch wichtigen Initiativen. Supporting Domains unterstützen diese, sind aber in sich selbst nicht differenzierend gegenüber anderen Kontexten oder gar anderen Unternehmen. Generic Domains hingegen kapseln häufig anzutreffende Funktionalitäten, die in vielen anderen Unternehmen oder Fachkontexten anzutreffen sind.

Daneben lässt sich die Frage stellen, welche Rolle ein Bounded Context für das Geschäftsmodell spielt. Handelt sich möglicherweise um einen Context, der direkte Einnahmen erzielt, oder eher um einen Context, der vielleicht dem Benutzerengagement oder der Benutzerbindung dient? Stellt er eventuell die Compliance sicher? Wie ausgereift ist ein Bounded Context bereits? Handelt es sich um ein noch recht unberührtes oder unerforschtes Geschäftsfeld? Ist es ein Produkt, das standardmäßig am Markt eingekauft werden könnte oder gar schon Standardsoftware? Hier lohnt sich beispielsweise in Blick in Wardley Maps[4].

Auch das Verhalten eines Bounded Context lässt sich grundsätzlich charakterisieren [5]. Eine Betrachtung würde hier zu weit führen. Nur so viel sei gesagt: Die Identifikation der verschiedenen Rollen, die Bounded Contexts spielen, kann frühzeitig dazu beitragen, die Kopplung unterschiedlicher Verantwortlichkeiten zu vermeiden.

Kopplung – da sind sie wieder, die Verbindungen zwischen den Kontexten und Services.

Context Mapping

Zur Modellierung dieser Verbindungen bzw. Beziehungen zwischen Modellen bzw. Kontexten dient im DDD das sogenannte Context Mapping. Hier geht es tatsächlich darum, eine Karte der Kontexte zu erstellen: das "was ist dazwischen?".

DDD kennt drei grundlegende Pattern, um diese Beziehungen zu klassifizieren. Das Pattern "Mutually Dependant" beschreibt zwei Bounded Contexts, die zusammen ausgeliefert werden müssen, um zu funktionieren und ihre Funktionalität erfolgreich auszuführen. "Free" beschreibt eine Beziehung, die gewissermaßen nicht existiert. Änderungen in einem Bounded Context haben keinen Einfluss auf den anderen Bounded Context. Es existiert daher keine (organisatorische oder technische) Abhängigkeit zwischen den Bounded Contexts. Das letzte Pattern "Upstream/Downstream" beschreibt den Einfluss eines vorgelagerten Bounded Contexts (upstream) auf einen nachgelagerten Bounded Context (downstream) während die umgekehrte Beeinflussung nicht zwingend gegeben ist. Auch hier ist wichtig zu verstehen, dass es sich nicht nur um technische, sondern zumeist auch organisatorische Einflussnahme handelt.

Auf Basis dieses grundlegenden Patterns unterscheidet DDD vor allem für Upstream/Downstream-Beziehungen im Detail weiter in die folgenden Patterns.

PatternBeschreibung
Open HostStellt eine Menge an Diensten bereit, die Funktionalitäten für andere Dienste nutzbar machen.
ConformistDas Model im nachgelagerten Context entspricht und folgt dem Modell im vorgelagerten Context.
Anti-Corruption Layer (ACL)Ein ACL isoliert das Domänenmodell eines Context von einem anderen, indem es Konzepte der Modelle übersetzt. Kopplung erfolgt nur im Integrationslayer, das in diesem Fall dem ACL entspricht.
Shared KernelZwei Kontexte teilen sich einen Teil des Domänenmodells, möglicherweise durch Code-Artefakte, Bibliotheken oder Datenbanken.
Customer / SupplierDer nachgelagerte Context ist ein Kunde gegenüber dem vorgelagerten Context. Anforderungen des nachgelagerten Context beeinflussen die Planung und Umsetzung im vorgelagerten Context.
PartnershipEine kooperative Beziehung zwischen zwei Kontexten, die auf gemeinsamer Planung, Entwicklung, Integration und Auslieferung der Software beruht.
Published LanguageEine dokumentierte Sprache (oder auch Modell), die von zwei Kontexten genutzt wird. Die Kontexte übersetzen dabei in diese oder aus dieser Sprache.
Separate WaysEs gibt keine Beziehung zwischen zwei Kontexten, z. B. weil die Implementierung zu aufwändig oder zu teuer wäre. Zumeist handelt es sich um eine bewusste Entscheidung im Lösungsraum, während eine Abhängigkeit im Problemraum durchaus gegeben sein kann.
Big Ball of MudEin Context oder vielmehr ein Teil des Systems mit inkonsistenten oder nicht vorhandenen Grenzen und einer Vielfalt oder einem Durcheinander an Modellen.

Beim Einsatz dieser Pattern ist es wichtig, dass es sich um bewusste Entscheidungen handeln sollte. Denn die Patterns selbst verursachen unterschiedliche Kosten, die sich vor allem in Form von Kommunikationsbedarf widerspiegeln. Während Partnership oder Conformist in der Implementierung mit deutlich weniger Code auskommen als Anti-Corruption Layer, ist mindestens für Partnership ein höherer Kommunikationsbedarf notwendig. Conformist bedarf eventuell beidem nicht, geht aber in der Regel mit geringerer Qualität einher.

Und spätestens beim Thema Kommunikation ist man zwangsweise auch beim Thema Organisationsdesign.

Architektur ist Organisationsdesign

Wie eingangs erwähnt, ist die technische Komplexität nur ein Teil der Medaille. Problematisch wird sie in der Regel dann, wenn sie durch die beteiligten Akteure und Teams nicht mehr wirtschaftlich sinnvoll handhabbar ist. In modernen Organisationen verbleibt diese Verantwortlichkeit meist in weitgehend autonomen Teams. Leider wird diese Teamautonomie oftmals dahingehend missinterpretiert, dass Teams in ihrer Entscheidungs- und Lösungsfindung zu isoliert sind. Conways Law hat uns gelehrt, dass unsere Softwarearchitektur in Teilen nur ein Abbild der Kommunikationsstruktur der Organisation ist. Damit sind die Verbindungen von Services auch nur ein Abbild der Kommunikationsstruktur zwischen den Teams, die mit diesen Services betraut sind. Sicherlich ist Teamautonomie ein entscheidender Faktor, wenn von Teams erwartet wird, effektiv und effizient zu arbeiten. Nichtsdestotrotz bauen wir Systeme. Es ist daher zwingend notwendig, nicht nur auf die Effektivität und Effizienz einzelner Teams zu schauen, sondern auch auf die des Gesamtsystems – des soziotechnischen Systems!

Das Ziel muss sein, Abhängigkeiten zu reduzieren und verbleibende durch weniger komplexe zu ersetzen.

Per Definition ist ein Service oder ein Team nicht Teil eines Systems, wenn es keine Abhängigkeiten oder Verbindungen zu anderen Teams und Services hat. Das gilt auch und insbesondere für autonome Teams. Daher kann das Ziel gar nicht sein, vollständig autonome Services zu bauen oder Teams zu bilden. Das Ziel muss vielmehr sein, ihre Abhängigkeiten untereinander zu reduzieren und verbleibende notwendige Abhängigkeiten durch weniger komplexe, möglichst nicht blockierende Abhängigkeiten zu ersetzen.

Daher bedeutet Architektur-Arbeit auch immer Organisationdesign und -entwicklung. Wie das Sprichwort sagt: Wer die Organisation nicht entworfen hat, hat auch die Architektur nicht entworfen.

Team Topologies

In den meisten Unternehmen kommt es aber gerade hier zu einer Menge Reibungsverlust. Man denke nur an die ständigen Schnittstellenabsprachen, -versionierungen oder -dokumentation. Kommunikation und Kollaboration zwischen Teams ist mit das wichtigste Merkmal erfolgreicher Unternehmen. Wäre es daher nicht hilfreich, zumindest eine Sprache oder Modellierungsmethodik ähnlich dem Context Mapping zu haben, um dies abzubilden.

Glücklicherweise gibt es mit Team Topologies eine Methodik, die eine gewisse Kategorisierung von Teams erlaubt [6]. Team Topologies unterscheidet dabei vier Arten von Teams:

Art des TeamsBeschreibung
Stream Aligned TeamEin Team, ausgerichtet auf einen bestimmten Arbeitsfluss, normalerweise in einem Segment der Geschäftsdomäne.
Complicated Subsystem TeamEin Team, in dem fundierte mathematische oder technische Fachkenntnisse erforderlich sind.
Enabling TeamEin Team, das vor allem den Stream Aligned Teams hilft, Hindernisse zu überwinden und fehlende Funktionalitäten aufzudecken.
Platform TeamEine Gruppe der anderen Team-Typen, die ein (internes) Produkt anbieten, um die Arbeit der Stream Aligned Teams zu beschleunigen.

Diese Teamtypen geben ergänzend zu Bounded Contexts ein Vokabular an die Hand, das es ermöglicht, nicht nur auf technischer, sondern auch auf organisatorischer Ebene die Teile eines soziotechnischen Systems zu beschreiben. Stream Aligned Teams und Complicated Subsystem Teams lassen sich in der Regel einem oder mehreren Bounded Contexts zuordnen – zumindest für Core Domains. Dagegen sind Supporting Domains oder Generic Domains eher in Platform Teams angesiedelt. Ergänzend kommen Enabling Teams hinzu, die in der Regel auf einer technischen bzw. nach Softwareartefakten strukturierten Context Map eher nicht zu finden sind. Dennoch sind diese Teams in einer Gesamtbetrachtung wichtig, denn sie stehen in Beziehungen zu anderen Teams und sind nur hilfreich, wenn sie diese wirklich befähigen, Hindernisse zu überwinden.

Will sich irgendwer eine Pizza teilen oder dann doch lieber Salat bestellen?

Bezüglich der Beziehungen unterscheidet Team Topologies zwischen drei Interaktionsmodi.

  • Collaboration: zwei Teams arbeiten eng zusammen, um ein gemeinsames Ziel zu erreichen.
  • X as a Service: zwei Teams kollaborieren nicht eng miteinander, aber ein Team nutzt die Arbeit (oder Dienste) des anderen Teams.
  • Facilitation: ein Team hilft einem anderen, ein Hindernis zu überwinden oder aus dem Weg zu räumen.

Im Gegensatz zu den Pattern im DDD beschränkt sich Team Topologies bewusst auf drei Interaktionsmodi. Allerdings gibt es sicherlich Überschneidungen. So liegen X as a Service und Open Host nahe beieinander. Bei Upstream/Downstream, Partnership oder Shared Kernel handelt es sich zumeist um Collaboration. Letztlich wird die Kommunikationsstruktur der Software der Kommunikationsstruktur der Teams folgen. Daher ist es sicherlich sinnvoll, diese zu definieren, anstatt dem Zufall zu überlassen.

Zusammenfassung

Wie wir gesehen haben, ist Servicedesign mehr als nur größenorientiert. Vielmehr bedarf es einer soziotechnischen Betrachtung des Gesamtsystems. Um das Amazon-Beispiel nochmals aufzugreifen: Wie groß sind die beiden Pizzen? Mini-Pizzen, Maxi-Pizzen oder Familien-Pizzen? Wie viele Teams haben Pizza bestellt? Muss die Lieferung koordiniert werden? Will sich irgendwer eine Pizza teilen oder dann doch lieber Salat bestellen?

Auch Teams sind in Bezug auf die inhärente, geschäftliche, technische und organisatorische Komplexität, mit der sie umgehen können, begrenzt. Die kognitive Belastung des Teams ist ein begrenzender Faktor, der nicht unterschätzt werden darf.

Hier können Muster und Heuristiken wie Bounded Context, Context Maps oder Team Topologies helfen. Letztlich ist und bleibt das Schneiden von Diensten jedoch ein individueller Ansatz für die betroffenen Domänen oder das Unternehmen. Ein guter Serviceschnitt ist nicht einfach zu gestalten – ganz im Gegenteil: es ist langfristige, grundlegende Arbeit. Deshalb ist es oft verlockend, einfache Heuristiken wie "Größe" zu bevorzugen, aber am Ende führen sie nicht zum gewünschten Ergebnis. Bei gutem Servicedesign geht es nicht um Größe, sondern vielmehr um Abgrenzung zur Schaffung eines Komplexitätsgleichgewichts – auf unternehmerischer, organisatorischer und technischer Ebene. Es geht um Schaffung eines Gleichgewichts zwischen Komplexität innerhalb einzelner Dienste und der Komplexität im Gesamtsystem. Die Ausrichtung der Services nicht nur auf technische Aspekte, sondern vor allem auf geschäftliche Fähigkeiten, User Journeys und organisatorische Prozesse ist der Schlüssel – kombiniert mit starker, gemeinschaftlicher Zielausrichtung.

Quellen
  1. One Team at Uber is Moving from Microservices to Macroservices
  2. Amazons "two pizza teams": The culture of microservices: Conway's law and two pizza boxes
  3. Strukturen von Communities of Practices (CoP): The fractal structure of communities of practice: Implications for business organization
  4. Wardley Maps: Finding a new purpose
  5. R. Wirfs-Brock, A. McKean; 2003: Object Design: Roles, Responsibilities and Collaborations
  6. Team Topologies

Autor

Sven-Torben Janus

Sven-Torben ist Partner der Conciso GmbH, wo er als praktizierender Softwarearchitekt arbeitet. Er befürwortet er einen agilen und praktikablen Entwurf von Softwarearchitekturen.
>> Weiterlesen
Das könnte Sie auch interessieren
Kommentare (0)

Neuen Kommentar schreiben