Category

Allgemein

CONSTRUCT: Aufbauen (5C Design, Teil 6)

By | Allgemein, Inhaltliches | No Comments

Eine Bausteinstruktur kann auf einer Ebene selbst wieder unübersichtlich werden. Baue Systeme höherer Komplexität durch Zusammenfassen von Bausteinen einer Ebene zu einem neuen Baustein einer nächsthöheren Ebene. Dabei sind weiterhin dieselben Handlungsmaximen anzuwenden.

  1. Software-Entwurf: Ein Blick zurück und nach vorn
  2. CUT: Richtig schneiden
  3. CONCEAL: Verbergen
  4. CONTRACT: Schnittstelle festlegen
  5. CONNECT: Verbinden
  6. CONSTRUCT: Aufbauen
  7. Its a Wrap: Zusammenfassung

In den bisherigen Artikeln ging es darum, wie man ein System auf einer Ebene effizient in einzelne Module zerlegt. Bei entsprechend großem Umfang der Lösung kann dabei nach wie vor Folgendes passieren: Die schiere Anzahl der Module wächst Ihnen über den Kopf und das gesamte System ist im Endeffekt wieder unübersichtlich. Was kann man in so einem Fall tun? Eine schlechte Idee wäre, mehr Logik in die einzelnen Module zu geben, sodass die Gesamtzahl an Modulen wieder überschaubar wird. Dadurch würde nur Komplexität in die Module hinein verlagert. Die Alternative ist Strukturen auf der nächst höheren Abstraktionsebene zu bilden. Wir zoomen aus unserer Architektur heraus und bauen dann modulare Strukturen nach denselben Regeln, nach denen wir schon die Module auf der unteren Abstraktionsebene entworfen hatten. Während dies den Vorgang eher bottom-up beschreibt, so ist dasselbe Ergebnis natürlich auch top-down zu erreichen. 

Whole-Part Pattern

Ein fast schon in Vergessenheit geratenes Muster, das dabei hilfreich sein kann, ist das Whole-Part Pattern. Vorgestellt wurde es in einem zeitlosen Klassiker der Software-Architektur namens “Pattern-Oriented Software Architecture” aus dem Jahr 1996. Als Beispiel dient uns der Motor eines Autos. Er besteht aus vielen Einzelteilen, wie dem Kolben, der Kurbelwelle und den Zündkerzen. Der Motor selbst ist aber wiederum nur ein Teil des Autos, das außerdem noch aus Dingen wie dem Lenkrad, der Karosserie, den Rädern und dem Auspuff besteht. Vor dem Fahrer wird diese Komplexität weitestgehend verborgen. Er dreht den Schlüssel um, beschleunigt mit dem Gaspedal und beeinflusst mit dem Lenkrad die Fahrtrichtung. Einmal im Jahr kommt das Auto in die Werkstätte zur Wartung. Dass dort der Filter der Klimaanlage genauso gewechselt wird wie das Motoröl kann dem Fahrer egal sein. Die Abstraktion “Auto” verbirgt all diese Komplexität vor seinem Nutzer so gut es geht.

Das Beispiel mit dem Auto könnte man sogar noch weiterdenken. Ein Robotertaxi-Service könnte eine weitere Abstraktionsebene darstellen. Eine Serversoftware weiß wo sich die einzelnen selbstfahrenden Taxis gerade befinden. Wenn ein Kunde per App eines der Taxis anfordert wird die Software des nächstbesten Robotertaxis dieses zum Kunden bewegen und ihn in weiterer Folge an sein gewünschtes Ziel bringen. Das Auto, die Software darin, die Software am Server und die App am Handy des Kunden bilden dann in Summe eine weitere Abstraktionsebene, die die Mobilität für den Kunden noch weiter vereinfacht.

In einer komplexen Enterprise Architektur können Sie sich dieses Prinzip wie folgt zu Nutze machen: Die Unternehmensarchitekten legen fest, wie sich die Systemlandschaft in einzelne Subsysteme zerlegt. Sie kümmern sich außerdem um Themen, die über die einzelnen Subsysteme hinaus gehen, wie die Wahl der Technologie zur Integration der einzelnen Systeme. Für die Implementierung der einzelnen Subsysteme selbst wird aber nur der grobe Rahmen definiert. Die Verantwortung für die korrekte Umsetzung wird in den einzelnen Teams belassen. Zu klären, wie sich ein konkretes Subsystem selbst dann weiter in Module zerlegt, ist also wiederum Aufgabe eines der Teams. Die Unternehmensarchitekten kümmern sich in einem solchen Modell um die strategischen Themen und überlassen die Taktik den einzelnen Teams.

Self-Contained Systems

Da die Gedanken bisher eher abstrakt waren, möchte ich noch einen konkreten Architekturstil vorstellen, mit dem man das Prinzip der hierarchischen Zerlegung prima umsetzen kann, nämlich Self-Contained Systems (oder kurz: SCS). Diese setzen explizit auf eine sehr lose Kopplung zwischen den einzelnen Teilsystemen. Bevorzugt wird eine Integration über das User-Interface. Ansonsten ist zeitliche Abhängigkeit zwischen den Systemen durch synchrone Kommunikation (wie über SOAP-RPC) eher verpönt und man sollte stattdessen auf asynchrone Integration (wie über Messaging) und Datenreplikation setzen.

Damit das klappt muss anfangs strategisch festgelegt werden, an welchen Grenzen sich das System gut in solch lose gekoppelte Subsysteme zerlegen lässt. Wenn das allerdings klappt, können die einzelnen Teams relativ isoliert voneinander an ihrem jeweiligen Subsystem arbeiten. Die Art der Umsetzung der einzelnen Teilsysteme kann sich dabei auch stark voneinander unterscheiden. So kann es sich bei einem der Subsysteme um einen Legacy-Deployment-Monolithen handeln, während ein anderes auf einer Microservice-Architektur aufbaut.

Im nächsten und bereits letzten Beitrag dieser Artikelserie fass ich das Thema noch einmal kurz zusammen. Außerdem zeige ich Möglichkeiten, wie man eine Architektur nach 5C umsetzt.

Kim Duggen über Organisationsentwicklung und mehr – im Podcast mit Lena Wittneben

By | Allgemein, Publikationen | No Comments

Kim Duggen über ihre persönliche Sicht auf Organisationsentwicklung im Podcast von und mit Lena Wittneben

Kim Nena Duggen im Gespräch mit Lena „There is a crack in everything – Wünsche, Ziele, Wendepunkte!“
Kim Nena Duggen und Lena Wittneben
Podcast über Organisationsentwicklung, Erfahrungswerte und vieles mehr..
September 2019
lena_wittneben

„Für mich ist Kim eine absolut positive „Sprechdenkerin“ (ihre Selbstbezeichnung), die mit maximal Herz, Hirn, Humor und Haltung intelligent-inspierende Ideen in die Welt bringt.“ (Zitat Lena Wittneben)

Was bedeutet Selbstorganisation für Kim und welche Erfahrungen hat sie in ihren beruflichen Umfeldern in den letzten Jahren gemacht? Wie haben die skandinavische und die chinesische Kultur sie beeinflusst und welche Rolle spielt ein wichtiger Sparringspartner schon seit Kindertagen in ihrem Leben? – nicht nur wenn es um Organisationsentwicklung geht.

Und wenn Sie auch noch wissen will, warum Kim beim Karaoke immer das gleiche Lied auswählt? Hören Sie selbst..

embarc echo – September 2019

By | Allgemein, echo | No Comments

Impulse aufgreifen – embarc echo

Lassen Sie sich von unseren aktuellen Konferenzbeiträgen und Publikationen im Web & in gebundener Form inspirieren. Wir haben Tipps für Ihren Einstieg in das Thema Machine Learning und Referenzwissen für ein modernes Web-Frontend direkt zum Download. Außerdem stellen wir Ihnen unsere neue Kollegin vor und werfen einen Blick auf künftige Termine & Veranstaltungen. Finden Sie in gewohnt kurzer Form frische Anregungen für Ihre Projekte!

Download embarc echo September 2019 (PDF, 8 Seiten)

follow us on Twitter – @embarced

CONNECT: Verbinden (5C Design, Teil 5)

By | Allgemein, Inhaltliches | No Comments

Durch Verwendung einer Schnittstelle eines anderen Bausteins kommt es immer zu Abhängigkeiten. Plane explizit zwischen welchen Bausteinen es welche Art von Abhängigkeit geben soll.

  1. Software-Entwurf: Ein Blick zurück und nach vorn
  2. CUT: Richtig schneiden
  3. CONCEAL: Verbergen
  4. CONTRACT: Schnittstelle festlegen
  5. CONNECT: Verbinden
  6. CONSTRUCT: Aufbauen
  7. Its a Wrap: Zusammenfassung

Damit ein System, das in seine Einzelteile zerlegt wurde, in Summe das gewünschte große Ganze ergibt, müssen seine Einzelteile miteinander interagieren. Diese Interaktionen erzeugen Verknüpfungen, die wiederum die einzelnen Teile in Abhängigkeiten zueinander bringen. Vereinfacht gesagt sind bei Änderungen, die nach außen wirksam sind, die angebundenen Module immer zu berücksichtigen. Diesen Aspekt gilt es möglichst klein zu halten. Die Abhängigkeiten können unterschiedliche Bereiche betreffen:

  • Daten und Formate: Ein Consumer muss das Format des Providers einer Schnittstelle verstehen. Bei Änderungen daran ist der Consumer ebenfalls betroffen.
  • Zeit: Wenn der Consumer seine Arbeit nur abschließen kann, wenn der Provider im selben Moment auch gerade verfügbar ist, so ist seine Verfügbarkeit auf Zeiten limitiert, in denen der Provider ebenfalls verfügbar ist. In dem Fall kommt es zu einer zeitlichen Abhängigkeit zwischen den Modulen.
  • Technologie: Ist der Consumer in der technologischen Auswahl seiner Implementierung auf irgendeine Weise eingeschränkt? Ein Netzwerkprotokoll wie Java RMI wirkt beispielsweise einschränkender als eine REST-Integration auf Basis des HTTP-Protokolls.
  • Ausführungsort: Ist es zur Interaktion nötig, dass die Module auf derselben (virtuellen) Maschine laufen, so sind sie was den Ausführungsort angeht eingeschränkt. Bei Kommunikation innerhalb der Grenzen des eigenen Prozesses ist dies beispielsweise so.

Diese technischen Abhängigkeiten ziehen in weiterer Folge Abhängigkeiten auf der organisatorischen Ebene nach sich. Ein Unternehmen, das zur Weiterentwicklung seiner Software überdurchschnittlich viele Meetings benötigt wäre ein Beispiel, wo sich diese Auswirkungen zeigen. Das und ein gezwungenermaßen hohes Maß an Bürokratie sind Indizien dafür, dass man die Abhängigkeiten innerhalb der Software nicht mehr im Griff hat.

Zur Vermeidung von Missverständnissen: Wenn Interaktion nötig ist, ist es nicht möglich, Abhängigkeiten völlig zu vermeiden. Und: Interaktion wird nötig sein. Es gilt die negativen Auswirkungen von Abhängigkeiten durch geschickten Architekturentwurf im Zaum zu halten. Ein bekanntes Entwurfsprinzip, dessen Befolgung dabei hilft, ist das Prinzip der azyklischen Abhängigkeiten. In einem Entwurf wo Module zyklisch voneinander abhängig sind, sind diese Module von jedem anderen in diesem Zyklus direkt oder indirekt abhängig. Diese Situation birgt hohe Risiken von unerwünschten Seiteneffekten bei Änderungen. Daher der Grundsatz Strukturzyklen möglichst zu vermeiden.

Kaskadierende Abhängigkeiten

Ebenfalls problematisch sind sogenannte Kaskadierende Abhängigkeiten. Dabei pflanzt sich ein und dieselbe Abhängigkeit, über die jeweiligen Modulgrenzen hinaus über weitere Abhängigkeiten zu weiteren Modulen fort. Mit anderen Worten: Was auch immer diese Abhängigkeit betrifft, sei es ein Datenformat oder eine Technologie, man wird sie nur ändern oder entfernen können indem man Änderungen am gesamten System vornimmt. Die folgende Grafik illustriert das:

Das Schnittstellenformat, das Baustein A anbietet, wird von den Bausteinen B und C selber wiederum intern benützt. Weiters kommen einzelne Aspekte dieser Schnittstelle in den externen Schnittstellen der Bausteine B und C wieder vor, wodurch sie sich auf deren Consumer (in diesem Fall D, E, F und G) ausbreiten. Eine Änderung der Schnittstelle des Bausteins A zieht dann eine Kaskade an Änderungen nach sich, die schließlich das gesamte System betreffen. Um so etwas zu verhindern bietet sich die Anwendung des Integrationgsmusters Anti-Corruption Layer aus dem Domain-Driven-Design an, das in der folgenden Grafik dargestellt ist. Dabei kommuniziert ein Consumer nur über einen solchen Layer mit seinem Provider, der das Modell des Providers in sein eigenes Format konvertiert.

Wenn Sie den Empfehlungen dieser Blog-Serie beim Entwurf Ihres Systems bis hierhin gefolgt sind kann es sein, dass Ihr Entwurf bereits aus einer sehr großen Zahl an Modulen besteht. Eine hohe Anzahl an Modulen kann, auch wenn diese jeweils wunderbar gekapselt sind, wiederum unübersichtlich werden. Sie tun dann gut daran, wiederum dieselben Regeln anzuwenden, allerdings eine Abstraktionsebene höher. Dies bringt uns zum nächsten Beitrag dieser Reihe: „CONSTRUCT: Aufbauen“.

Moin Moin!

By | Allgemein | No Comments

Ich bin als Organisationsentwicklerin im Bereich neue Arbeitswelten, Selbstorganisation und (IT-)Strategie unterwegs. Und seit Mitte August bei embarc mit an Bord! Wenn ich mit Menschen arbeite, die selbst etwas bewegen wollen..

Read More

CONTRACT: Schnittstelle festlegen (5C Design, Teil 4)

By | Allgemein, Inhaltliches | No Comments

Entwerfe Schnittstellen so, dass eine möglichst reibungslose Interaktion zwischen dem Baustein und seinen Consumern möglich ist.

  1. Software-Entwurf: Ein Blick zurück und nach vorn
  2. CUT: Richtig schneiden
  3. CONCEAL: Verbergen
  4. CONTRACT: Schnittstelle festlegen
  5. CONNECT: Verbinden
  6. CONSTRUCT: Aufbauen
  7. Its a Wrap: Zusammenfassung

Wie legt man eine Schnittstelle fest, die eine möglichst reibungslose Interaktion ermöglicht? Tatsächlich ist das Thema bereits in den SOLID Prinzipien sehr prominent vertreten. So steht das L darin für das Liskovsche Substitutionsprinzip von Barbara Liskov (nähere Infos hier). Noch interessanter finde ich aber das I aus SOLID, das für das Interface-Segregation Prinzip steht. Dies ist die erste von 3 Techniken für sauberes Schnittstellendesign, die ich ihnen hier vorstelle. Es besagt, dass die Schnittstelle eines Moduls immer nach ihren einzelnen Verantwortlichkeiten aufgeteilt sein sollte. Es darf demnach nicht eine einzige generische Schnittstelle geben, sondern für jeden Anwendungsfall der Benutzung einer Komponente jeweils eine eigene. Ich möchte das anhand eines Beispiels erläutern.

Interface Segregation und REST-APIs

Die Web Service Description Language (oder kurz WSDL) ist eine Beschreibungssprache für Schnittstellen und ein offizieller Standard des W3C Konsortiums. Sie wird meist in Kombination mit dem Simple Object Access Protocol (oder kurz SOAP) verwendet. Bei meinem Grundlagen-Seminaren zum Thema Software-Architektur geht es u.a. immer auch um das Interface-Segregation Prinzip. Ich frage die Teilnehmer dann gerne, ob jemand unter ihnen schon mal eine WSDL-Datei bekommen hat, die mehrere Megabyte groß war, und von der er (oder sie) sich zunächst einmal wie erschlagen fühlte. Eigentlich ist immer jemand dabei, der so eine Situation schon mal erlebt hatte. Solch komplexe Schnittstellen haben 2 potentielle Probleme:

  • Der Einarbeitungsaufwand für den Entwickler, der diese Schnittstelle verwenden möchte, ist ausgesprochen hoch. In der WSDL Spezifikation ist nicht vorgesehen, die Schnittstelle zu strukturieren.
  • Es besteht die Gefahr, dass der Code, der die Schnittstelle konsumiert, Abhängigkeiten zu Teilen der Schnittstelle entwickelt, die von diesem gar nicht benötigt werden. Sobald das Modell zur Schnittstelle vom Client generiert wurde ist es möglich, auch zu nicht relevanten Teilen einer nur teilweise verwendeten Schnittstelle Abhängigkeiten zu entwickeln.

Als Standard für Schnittstellen im Web hat sich inzwischen der Representational State Transfer als Alternative etabliert. Dabei wird eine komplexe Schnittstelle in ihre einzelnen „Subjekte“ bzw. Ressourcen aufgeteilt. Dieser Schritt entspricht einer Umsetzung des Interface-Segregation-Prinzips. REST hat also das I aus SOLID quasi „mit eingebaut“.

Die Kommunikation mit diesen einzelnen Ressourcen erfolgt mit den „Verben“ des HTTP-Protokolls, also beispielsweise mittels GET, PUT, POST und DELETE. Dabei gibt es auch Weiterleitungen von einer Ressource zur nächsten, die damit in Verbindung steht. Diese Weiterleitung (genannt Hypermedia As the Engine of Application State oder kurz: HATEOAS) bietet eine gewisse Flexibilität was die Änderung der einzelnen Endpoints angeht. Außerdem ist sie noch eine Form der Selbstdokumentation für Nutzer der Schnittstelle. Das Gefühl der Frustration, weil man so etwas wie eine monströse WSDL erhalten hat, sollte es bei REST-APIs also gar nicht erst geben.

Das Schnittstellen-Problem des Mars-Climate Orbiter

Neben der Schnittstellentrennung gibt es weitere Techniken für eine reibungslose Interaktion. Im konkreten Beispiel hätte ihre Anwendung einen Millionenschaden vermieden. Vielleicht hatten Sie es 1999 in den Nachrichten mitbekommen: Die NASA Sonde Mars-Climate Orbiter war abgestürzt. Sie hätte in einen Orbit um den Planeten Mars eintreten sollen, ist aber stattdessen auf dessen Oberfläche zerschellt (für nähere Infos dazu klicken Sie bitte hier). Passiert war Folgendes: Die Software wurde in Teilen von der NASA und teilweise von Lockheed-Martin entwickelt. An einer Stelle gab es eine Schnittstelle zur Weitergabe des gemessenen Impulses an die Steuerungseinheit. Während das eine Team davon ausging, dass dieser auf dem metrischen System basiert ging das andere Team von einer Abbildung basierend auf dem imperialen System aus. Das Ergebnis war der oben erwähnte Verlust der Raumsonde und ein entsprechender Millionenschaden. Ich könnte mir vorstellen, dass diese Schnittstelle nicht viel spezifischer definiert war als in folgendem Listing:


public double getSpeed ();

Eine solche Definition lässt einfach zu vieles offen. Um welche Einheit handelt es sich bei der Geschwindigkeit? Außerdem ist nicht klar ob auch negative Werte möglich sind, oder nur positive Werte als Rückgabewerte in Frage kommen. Diese Art von Problem kann durch eine spezifischere Definition des Schnittstellenkontraktes behoben werden. Viel besser ist die folgende Definition, wo wir uns des JSR305 bedienen und mit der entsprechenden Annotation beschreiben, dass diese Methode keine negativen Werte zurückliefert. Außerdem wird durch die Methodensignatur klar, dass es sich um die SI Einheit für Geschwindigkeit handelt:


public @Nonnegative double getSpeedInMetersPerSecond();

Achtung, Gefahr!

Besonders problematisch sind Schnittstellen, die einem jeden Consumer die Möglichkeit bieten, den Provider selbst in Probleme zu bringen. Dies zu vermeiden wäre mein 3. Tipp zum Thema. Wo man dies häufig antrifft ist, wenn sich mehrere Services oder Subsysteme den Zugriff auf ein und dieselbe Datenbank mit demselben Datenmodell teilen. So wie dies in der folgenden Grafik dargestellt ist:

Die Schnittstelle, die eine solche geteilte Datenbank ihren Consumern bereitstellt ist nämlich das Datenmodell selbst, und im Falle einer relationalen Datenbank die Abfragesprache SQL. Das beinhaltet die Möglichkeit einzelne Tabelleneinträge zu sperren, oder auch die Datenbank mit umfangreichen Query-Abfragen inkl. Joins über viele Tabellen zu lähmen. Eine dermaßen missbräuchliche Verwendung wird die anderen Consumer der Datenbank ebenfalls in Probleme bringen. Genau das sollte aber über eine Schnittstelle gar nicht erst möglich sein. Ich rede dabei wohlgemerkt nicht von Denial-of-Service Attacken, sondern von Dingen, welche früher oder später im Zuge der üblichen Wartungstätigkeit passieren.

Wird eine Schnittstelle zwar angeboten, aber von niemandem benutzt, ist natürlich noch keine Interaktion passiert. Im nächsten Beitrag (“CONNECT: Verbinden”) betrachten wir die Folgen, welche es zwangsläufig haben wird, wenn Bausteine über Schnittstellen miteinander interagieren.

DevOpsDays London 2019: DevOps and Legacy Code

By | Allgemein, Publikationen | No Comments

DevOps and Legacy Code

Unfortunately we can’t all work in startup companies. Some of use have to deal with legacy code bases. These are often structured as a monolith and tightly coupled. They typically are hard to build and deploy. You might think all the new DevOps practices and toys are not suitable for your context. You are wrong!

We will discuss strategies to work with legacy code and still use DevOps practices and tools. These include breaking up of monolithic structures, migrating data and monitoring systems that aren’t built to be monitored. We will show real-world examples of successful applications of these strategies.

While dealing with legacy code will still be painful, you will have some applicable ideas to relieve your pain.

Devopsdays London 2019

follow us on Twitter – @embarced

CONCEAL: Verbergen (5C Design, Teil 3)

By | Allgemein, Inhaltliches | No Comments

Verbirg so viel der internen Struktur eines Bausteins und der Art der Umsetzung vor der Außenwelt wie möglich.

  1. Software-Entwurf: Ein Blick zurück und nach vorn
  2. CUT: Richtig schneiden
  3. CONCEAL: Verbergen
  4. CONTRACT: Schnittstelle festlegen
  5. CONNECT: Verbinden
  6. CONSTRUCT: Aufbauen
  7. Its a Wrap: Zusammenfassung

Es war 1972 als David Parnas sein visionäres Papier mit dem Titel “On the Criteria To Be Used in Decomposing Systems into Modules” veröffentlichte. Während Parnas hier viele Aspekte der Modularisierung angesprochen hat, ist es inzwischen hauptsächlich noch für eines bekannt, und zwar für die Formulierung des Information-Hiding Prinzips. Alles, von dem man annahm, dass es sich später ändern könnte, sei vor der Außenwelt zu verbergen. So hat man die Garantie, dass es von außerhalb eines Bausteins niemals Abhängigkeiten dazu geben wird. Dadurch kann dies später jederzeit isoliert geändert werden. Ich unterstreiche das und gehe sogar noch weiter: Verbergen Sie am besten einfach alles, was sich verbergen lässt. Standardmäßig sollte einmal alles zunächst einmal verborgen sein. Nur wenn es außerhalb wirklich benötigt wird, sollte es auch veröffentlicht werden. Schließlich ist es schwierig vorherzusehen, was tatsächlich später einmal geändert werden muß.

Einer der Vorzüge der aktuell so beliebten Microservices ist, dass dabei Interna der einzelnen Module (dort eben Microservices genannt) automatisch hinter einer Remote-Schnittstelle (hier oft REST) verborgen werden. Durch den Verteilungsaspekt eines Service sind das nicht nur die konkreten Komponenten, sondern auch deren Laufzeitumgebung und die jeweils eingesetzte Technologie zur Implementierung.

Die verschiedenen Technologien bringen allerdings selbst bereits Dinge mit, um Subbausteine zu verbergen. In Java ist dies wie folgt möglich, wobei wir auf der niedrigen Abstraktionsebene beginnen und danach “nach außen zoomen”:

  • Innerhalb einer Klasse lassen sich einzelne Member (wie Methoden und Variable) verbergen, beispielsweise mit dem private-Keyword. In Java hat sich eingebürgert Instanzvariable einer Klasse prinzipiell zu verbergen, und wenn, dann nur über sogenannte getter und setter zu veröffentlichen.
  • Klassen lassen sich innerhalb eines Java-Packages verbergen, indem man diese als package-protected definiert. Die Angabe erfolgt dann ohne visibility Modifier in der Klassen-Deklaration.
  • Ab Java 9 können Packages mittels JigSaw (nähere Infos dazu hier) zu Modulen zusammengefasst werden. Dabei kann man gezielt Packages exportieren oder vor der Außenwelt verbergen.

Facade Pattern

Eine Möglichkeit um das Information-Hiding Prinzip umzusetzen stellt das Facade Pattern dar (nähere Infos dazu hier). Bei einer Fassade handelt es sich um einen dezidierten Einstiegspunkt in ein Modul oder Subsystem. Die einzelnen Bestandteile werden gezielt verborgen und jede Interaktion mit ihnen von außen erfolgt über die Fassade. Bei einer REST-API eines (Micro-)Service beispielsweise handelt es sich im Grunde ebenfalls auch immer um eine Fassade.

Law-of-Demeter / Principle of least Knowledge

Beim Law-of-Demeter (zum Ursprung und Namen des Prinzips siehe hier) handelt es sich um einen Spezialfall des Information-Hiding Prinzips. Für den Consumer einer Schnittstelle soll das Zusammenspiel des Providers mit anderen Komponenten möglichst verborgen sein. Der Consumer vemeidet dadurch Abhängigkeiten zu den vom Provider verwendeten weiteren Komponenten. Im folgenden Listing muss der Fahrer wissen, aus welchen einzelnen Bauteilen das Auto besteht, um es zu verwenden. Für die Hersteller des Autos hat dieses Design den Nachteil, bei Änderungen die Fahrer informieren zu müssen, damit diese sich darauf einstellen können.


Car wartburg = new Car();
wartburg.getSeatbelt().fasten();
wartburg.getEngine().start();

Im folgenden Listing sind diese Probleme behoben. Nun kann ich den Verbrennungsmotor durch Elektromotoren auswechseln, die an den Radnaben montiert sind, und außerdem den Start des Autos verweigern, sollte sich der Fahrer nicht angeschnallt haben. Den Fahrer muss ich über diese Änderungen gar nicht informieren.


Car tesla = new Car();
tesla.secureDriver();
tesla.activate();

Wenn Sie konsequent Dinge verbergen stellt sich die Frage, wie den möglichen Consumern der Zugang zur Funktionalität des eigenen Bausteins gewährt werden soll. Hier kommen dann Schnittstellen ins Spiel, was uns auch zu unserem nächsten Beitrag dieser Serie bringt, nämlich: “CONTRACT: Schnittstelle festlegen“. (Coming soon, stay tuned…)

CUT: Richtig schneiden (5C-Design, Teil 2)

By | Allgemein, Inhaltliches | No Comments

Je unabhängiger ein Baustein ist, desto einfacher wird er zu handhaben sein. Schneide Bausteine so, dass sie sich möglichst gut voneinander abgrenzen.

  1. Software-Entwurf: Ein Blick zurück und nach vorn
  2. CUT: Richtig schneiden
  3. CONCEAL: Verbergen
  4. CONTRACT: Schnittstelle festlegen
  5. CONNECT: Verbinden
  6. CONSTRUCT: Aufbauen
  7. It´s a Wrap: Zusammenfassung

Um Komplexität dauerhaft beherrschbar zu machen, gibt es kaum eine Alternative dazu, diese in einzelne kleinere Teile zu zerlegen. Dafür muss man sich zunächst einmal die Frage stellen, an welchen Grenzen diese Abgrenzung denn am besten funktioniert. Wie findet man diese? Im Endeffekt wird der Programmcode der Lösung ein Abbild der fachlichen Anforderungen sein. Die möglichen Grenzen müssten demnach bereits in den Anforderungen auffindbar sein. Warum eine solche “fachliche Grenzziehung” (auch Vertikalität genannt) zu mehr Eigenständigkeit der Bausteine führt möchte ich anhand eines Beispiels verdeutlichen.

Schichten vs. Vertikale Architektur

Abbildung 1 zeigt ein nach klassischer Manier in technische Strukturen zerlegtes System. Daraus ergeben sich die dort abgebildeten Schichten, manchmal auch Layer genannt. Damit werden vorrangig die technischen Aspekte der Lösung betont. Dies hat den Vorteil, dass Entwickler passend zu ihren Stärken Strukturen im Code wiederfinden. Es werden außerdem querschnittliche Themen wie Persistenz gleichartig behandelt, was meist auch gewünscht ist. Auch das User-Interface wird für den Benutzer hier wohl ein durchgängig flüssiges Erlebnis darstellen. Aber: Sobald der Wunsch auftaucht, in irgendeinem der bestehenden Features ein weiteres Eingabe-Feld hinzuzufügen, zieht dies eine Kaskade an Änderungen in allen Layern nach sich. Man beginnt in der DB, zieht dies dann im DB-Access-Layer nach, danach in der Service-Schicht und zum Schluss im UI. Auch wird ein Ausfall einer niedrigeren Schicht sich immer auch auf alle Schichten darüber auswirken. Solch technische Strukturen sind demnach nicht wirklich eigenständig.

Abb. 1: Schichtenarchitektur

Die Alternative zu technischen Strukturen sind fachliche bzw. vertikale Strukturen (siehe Abb. 2). Hier wird besonders die Fachlichkeit betont. Das heißt übrigens nicht, dass man bei näherem Blick in eine dieser fachlichen Strukturen nicht wieder so etwas wie Layer finden wird. Trotzdem: Der Aspekt, der der obersten Abstraktionsebene Form gibt, wird immer etwas stärker betont werden als der sekundäre Aspekt innerhalb dieser primären Struktur.

Abb. 2: Vertikale / Fachliche Architektur

Die Grenzen der fachlichen Zerlegung

Tatsache ist allerdings, dass vertikale Abtrennung in manchen Fällen etwas besser funktionieren wird, und in anderen Fällen wiederum nicht so gut. Die Anforderungen eignen sich nämlich nicht immer in gleichem Maß für eine solche Aufteilung. Manchmal sind bereits die Business-Regeln, die der Fachexperte formuliert, so dermaßen miteinander vernetzt, dass eine effiziente Zerlegung schwierig wird. Werfen Sie dazu bitte einen Blick auf die Architektur, die in Abb. 3 dargestellt ist. Es handelt sich dabei um einen Web-Shop, welcher nach allen aktuellen Regeln der Kunst in fachliche Microservices zerlegt wurde, während die User-Interfaces nach wie vor als monolithische Layer implementiert sind.

Zunächst ist bemerkenswert, dass man ein solch monolithische Design des UI in Microservice-Architekturen häufig antrifft. Hier möchte ich kurz innehalten und die Frage stellen, warum dem eigentlich so ist. Warum klappt hier die fachliche Zerlegung oft nicht so gut wie im Backend? Die Ursache ist bei einer nicht-funktionalen (oder qualitativen) Anforderung zu finden. Wenn ich dem User ein durchgängiges Erleben des UIs ermöglichen möchte, so ist ein vertikaler Architekturstil an dieser Stelle nicht besonders gut geeignet. Daran sieht man, dass qualitative Anforderungen ebenfalls Einfluss auf die optimal mögliche Zerlegung eines Systems haben können.

Im Backend wiederum scheint die vertikale Modularisierung prima geklappt zu haben. Im Service “Kundenkonto” werden die Daten gespeichert, die ich vom Kunden benötige um ihn in der Plattform zu registrieren. Dabei wird die Validität seiner Angaben entweder über eine EMail oder eine SMS geprüft. Aus diesem Grund haben wir das Thema “Kommunikation” hier mit rein gepackt. Weiters gibt es Services für die “Bestellung”, das “Lager”, die “Bezahlung” und die “Lieferung”. Querschnittliche fachliche Themen wie die Daten des Kunden (Zahlungsweise, Lieferadresse, Rechnungsadresse etc.) haben wir ebenfalls auf diese Services aufgeteilt wie das Produkt an sich (Preis, Lagerstand, Beschreibung etc.).

Das Ziel war Logik und Daten jeweils nahe beieinander zu haben um eine Architektur mit hoher Kohäsion und niedriger Kopplung zu erreichen. Dies ist hier auch ganz gut gelungen, wie ich finde. Würde mir das jemand als Architektur zum Review vorlegen mit der Fragestellubg, ob dies gängigen Best-Practices folgt, hätte ich zumindest auf den ersten Blick nichts zu beanstanden.

Abb. 3: Die einwandfreie Microservice-Architektur unseres Web-Shops

Eine neue Anforderung

Ab sofort soll es in gewissen Fällen möglich sein, frische Schnittblumen bei jeder Bestellung mit auszuliefern. Dies kann entweder über Bezahlung möglich sein, oder ab jeweils einem Umsatz von 1000 Euro einmalig als Gratis-Angebot. Dies ist allerdings nur möglich bei Lieferdiensten, die dabei auch mitmachen. Diese müssen nämlich die Blumen immer frisch bei örtlichen Blumenhändlern abholen. Außerdem ist dies nur möglich, wenn es in der jeweiligen Gegend auch einen Blumenhändler gibt, der mit uns kooperiert. Angeboten wird dies außerdem nur weiblichen Kunden. Für männliche Kunden wird überlegt evtl. später ein After-Shave als Bonus den Paketen beizulegen. Allerdings nur solange auch welche auf Lager sind. In welchem der Bausteine würden Sie diese Änderung verorten? Es gäbe dafür diverse Strategien:

  • Man schreibt einen neuen Service “Geschenke”, der genau dieses Feature abbildet.
  • Man gibt das neue Feature irgendwo dazu, z.B. bei Payment, Bestellung oder Lieferung.
  • Man extrahiert aus den bestehenden Services einen neuen namens “Produkt”, der Daten und Logik der Produkte beinhaltet und dabei die Geschenke als Sonderfall behandelt.

Jede dieser Lösungen führt dazu, dass sich die Kopplung zwischen den Services im Vergleich zur Situation davor vergrößert. Dies allerdings nicht, weil keine gute Arbeit geleistet wurde, sondern weil dies die Anforderungen selbst nötig machten.

Fazit

Es gibt nicht nur qualitative Anforderungen, die eigentlich immer querschnittlicher Natur sind. Es gibt ebenfalls funktionale Anforderungen, welche auf gewisse Weise übergreifend sind. Sie vernetzen bestehende Anforderungen und sind nicht so gut als Modul isolierbar. Sie führen dazu, dass sich die Software in Summe nicht so gut modularisieren lässt. Die Aufgabe ist, eine Struktur zu finden, welche möglichst gut zu den fachlichen Anforderungen passt. Außerdem haben Sie idealerweise noch die Möglichkeit, auf Änderungen der Fachlichkeit zu reagieren, indem Sie im Zuge eines Refactorings eine Restrukturierung vornehmen. Abhängig von der Komplexität der Anforderungen selbst wird Ihnen das manchmal besser gelingen, und manchmal sind dem gewisse Grenzen gesetzt. Wenn Ihnen das gelungen ist, geht es weiter mit dem nächsten der 5 C´s, nämlich: “CONCEAL: Verbergen”.