Event-Based Components (EBC) werden vor allem mit C#-Events in Zusammenhang gebracht. Nicht zu unrecht natürlich. Darum ging es ja auch am Anfang: Funktionseinheiten über Events verbinden. So können sie “im Außen” zusammengesteckt werden; “im Innen” haben sie keine Abhängigkeiten mehr.
Inzwischen hat sich die Welt aber gedreht. Aus den EBC sind die Komponenten verschwunden. Und nun verschwinden auch die Events. Oder besser: sie bekommen einen anderen Platz zugewiesen.
Flow Design
In den letzten Monaten habe ich mit vielen Entwicklern über EBC diskutiert und in vielen Zusammenhängen damit gearbeitet. “EBC-Denke” hat auch die Clean-Code-Developer-Trainings, die ich mit Stefan Lieser veranstalte, stark beeinflusst. Sie macht es schlicht soviel leichter, Anforderungen in Lösungen zu transformieren.
Bei all dieser Beschäftigung mit dem Konzept bleibt es nicht aus, dass es sich verändert. Der Blick darauf wird differenzierter. Und so ist mein aktueller Standpunkt, dass EBC nur eine bestimmte Ausprägung eines Paradigmas ist.
Objektorientierung ist ein Paradigma, bei dem es um polymorphe “Dinger” geht, die Daten und Funktionen vereinen und voneinander abhängig sind.
Das hinter EBC stehende Paradigma nenne ich mal “Flussorientierung”. Bei dem geht es um “Dinger”, die Eingaben in Ausgaben transformieren, dabei auf Ressourcen zurückgreifen – und zu Sequenzen verbunden werden. Solche Sequenzen können wieder als “Dinger” betrachtet werden, weil sie als Ganzes Eingaben in Ausgaben transformieren. Im Fluss durch die Sequenzen sind also Daten; die Ausgaben eines “Dings” sind die Eingaben anderer “Dinger”.
Diese Flussorientierung sehe ich inzwischen als so wichtig weil grundlegend an, dass ich sie von den EBC getrennt nutzen möchte. Sie ist für mich sogar so universell, dass ich sie nicht auf die Modellierung, d.h. den Entwurf funktionaler Strukturen beschränken möchte. Flussorientierung ist für mich vielmehr das Ausgangsparadigma für Architektur und Modell. Denn nur mit einer Flussorientierung können wir komplizierte Systeme agil evolvierbar entwerfen, d.h. in dünnen Längsschnitten. Das behaupte ich mal keck :-)
Meine Begründung: Flussorientierung ist der Objektorientierung im Entwurf in mehrerlei Hinsicht überlegen.
- Erstens entstehen durch Flussorientierung keine Systeme mit funktionalen Abhängigkeiten;
- zweitens sind flussorientierte Systeme intuitiv auf beliebig vielen Abstraktionsebenen denkbar und darstellbar;
- drittens geht es bei Anforderungen immer um Verarbeitungsflüsse, so dass flussorientierter Entwurf näher an den Anforderungen verläuft.1)
Aller Softwareentwurf sollte daher, so meine ich derzeit, mit Flussorientierung beginnen.
Flow Architecture
Die nicht-funktionalen Anforderungen sollten konsequent mit einer flussorientierten Architektur erfüllt werden.2) Was da in Flüssen steckt, ist architekturspezifisch. Daten fließen in der Architektur (zumindest) zwischen Bounded Contexts, Apps, Maschinenen und BS-Prozessen.
Der Architekt definiert und bindet physische Funktionseinheiten zu Sequenzen zusammen. Einen Betriebssystemprozess kann man “anfassen” (s. Taskmanager), eine App kann man “anfassen” (s. die BS-Prozesse auf den zugehörigen Maschinen) usw. Mit diesen Funktionseinheiten kann am Ende auch ein IT-Admin etwas anfangen oder eine Einkaufsabteilung, weil hard- oder softwaretechnische Server gekauft werden müssen.
Flow Model
Die funktionalen Anforderungen sollten vor dem Hintergrund der flussorientierten Architektur dann ebenfalls flussorientiert angegangen werden. Das geschieht im Rahmen der Modellierung, die logische Funktionseinheiten definiert und zu Flüssen verbindet. Wo Architektur immer aus einer begrenzten physischen Hierarchie besteht, sind Modelle freier. Ihre Strukturelemente haben deshalb allgemeinere Namen: Platine und Bauteil. Bauteile sind atomare Funktionseinheiten, Platinen zusammengesetzte bzw. zusammensetzende Funktionseinheiten. Die Hierarchien der Funktionseinheiten von Modellen können beliebig tief geschachtelt sein.
Zum Trost der Freunde der Objektorientierung: Die Bauteile des Modells wie auch die in ihm fließenden Daten sind die “Taschen”, in denen für mich Objektorientierung weiterhin ihren Wert hat. Sie sind überschaubar und klar abgegrenzt, so dass Objektorientierung die Harmonie des Großen Ganzen des Entwurfs nicht zerstören kann. “Taschen voller Matsch” sind zwar misslich, aber immer noch viel besser als ganze Anwendungen als Brownfield.
Es obliegt Architektur und Modell, diese “Taschen” so klein zu halten, dass die Nachteile der Objektorientierung ihre Vorteile nicht überwiegen. Das halte ich aber für keine allzu schwierige Aufgabe.
Flow Implementation
Flüsse fließen also überall im Entwurf, in der Architektur und im Modell. Das bedeutet, es kann nicht nur eine Übersetzung für sie geben. Das hatten EBC suggeriert. Aber das ist eine unnötig einschränkende Sichtweise.
Einen Fluss wie
in Interfaces mit Events zu übersetzen wie hier
interface IA
{
event Action<string> Output;
}
interface IB
{
void Process(string input);
}
ist nur eine Möglichkeit. Sie ist einfach und naheliegend – doch letztlich ist es eben nur eine Möglichkeit.
Genauso gut können die Funktionseinheiten in Methoden übersetzt werden:
string A() {…}
void B(string input) {…}
Das tun Stefan und ich zum Beispiel gern in TDD-Seminaren, wenn wir kleine Aufgaben modelliert haben. Für die meisten Code Katas wäre die Übersetzung in Interfaces/Events ein zu großes Geschütz. Mit der Übersetzung in Methoden muss man nun aber nicht auf die Segnungen der Modellierung auch bei kleinen Aufgaben verzichten.
Und sonst? Es gibt weitere Übersetzungen für Modelle. In einem Blogartikel habe ich zum Beispiel mal mit expliziten Kanälen gearbeitet. Oder hier eine Übersetzung für die Java-Welt – auch ohne Events.
Auf Architekturebene sieht die Übersetzung dann noch wieder anders aus. Wie könnte ein Datenfluss zwischen zwei Bounded Contexts übersetzt werden?
Aus dem schlichten Pfeil im Architekturmodell auf hoher Abstraktionsebene kann z.B. ein ETL-Betriebssystemprozess3) auf niedrigerer Ebene werden:
Tendenziell werden allerdings flussorientierte Modelle direkt in recht simple programmiersprachliche Konstrukte übersetzt und flussorientierte Architekturen mit flussorientierten Modellen verfeinert.
Aus jeder Entwurfsperspektive gilt jedoch: Für “Kästchen” (oder “Kuller”) und Fluss-Pfeile sind je geeignete Übersetzungen zu finden. Welche das sind, hängt vom Abstraktionsgrad, zur Verfügung stehenden Technologien und nicht-funktionalen Anforderungen ab.
Fazit
Was mit EBC begonnen hat ist nun viel größer geworden. Auf Flussorientierung kann und will ich nicht mehr verzichten. Funktionale und nicht-funktionale Anforderungen möchte ich mit dem Paradigma angehen. Große und kleine Probleme möchte ich damit lösen. Synchron und asynchron möchte ich dabei denken können.
Die Trennung von Paradigma und Implementation, die in EBC ursprünglich nicht so direkt zu sehen war, hilft mir dabei. Ich denke auch, dass sie Flussorientierung attraktiver macht, weil sie keine bestimmte Implementation aufzwingt. Jeder kann einen flussorientierten Entwurf in objektorientierten oder prozeduralen oder funktionalen Code gießen, wie er mag. Je systematischer das geschieht, desto größer der Gewinn. Interfaces und Events sind nur eine Variante von vielen.
In diesem Sinne: Happy Flow-Designing!
Anmerkungen
1) Dass die Objektorientierung meint, bei Anforderungen ginge es vor allem um Daten, halte ich inzwischen für eine sehr tragische Verirrung. Sie ist tragisch, weil Objektorientierung dazu gedacht war “alles besser zu machen”. Endlich Software so strukturieren, wie die Welt uns entgegentritt: als Gewebe von Dingen, die Zustände haben und tätig sind und unterschiedlichen Kategorien angehören.
Klingt ja auch total cool. Klingt sogar philosophisch-richtig, weil hier These (Platon als Proponent einer Ideenwelt unabhängig von den Dingen –> Klassen) und Antithese (Descartes als Stellvertreter des modernen diesseitigen, mechanistischen Denkens –> Objekte) synthetisch vereint scheinen.
Leider ging der Schuss nach hinten los. Ich sehe nicht, dass die Objektorientierung die Softwareentwicklung ihrem Anspruch entsprechend leichter gemacht hat. Unsere Softwaresysteme sind heute größer als früher. Aber nicht wegen der Objektorientierung, sondern weil Rechner heute mehr leisten. Selbst wenn wir heute nur mit C oder Modula arbeiten würden, wären unsere Softwaresysteme größer als die in den 1970ern.
Schuldig geblieben ist die Objektorientierung, dass Softwaresysteme wirklich deutlich leichter entworfen werden können. Und dass Softwaresysteme leichter evolvierbar gemacht und gehalten werden können als zu zeiten von C oder Modula.
Wie an anderer Stelle schon gesagt: Objektorientierung hat ihren Wert. Ich möchte sie nicht missen. Aber sie ist nicht der Heilsbringer, den die Branche in ihr gesehen hat und sieht. Und erkennen lässt sich das an der kontraproduktiven Gewichtung von Daten bei der Analyse von Anforderungen, um die dann herum Funktionalität gruppiert wird. Das Ergebnis sind oft/meist ideal-naive oder schlicht falsche Repräsentationen der Welt.
2) Flussorientierte Architektur ist kein neuer Gedanke. Event-Driven Architecture (EDA) ist ein etabliertes Architekturmuster in diesem Sinne. Ich möchte jedoch darüber hinaus gehen. EDA ist heute vor allem in “großen Anwendungen” in Gebrauch. Das empfinde ich als unnötige Beschränkung, die vor allem einem Denken entspringt, EDA hätte mit spezieller oder gar teurer Infrastruktur zu tun.
Jede Software kann davon profitieren, wenn ihre Architektur flussorientiert ist. Höhere Evolvierbarkeit sollte kein Privileg großer Systeme sein.
3) Ein ETL-Prozess nimmt in der Architektur insofern eine besondere Bedeutung ein, als dass er nicht nur einem Bounded Context angehört. Eine eigene Ebene in der Strukturhierarchie der Architektur verdient er deshalb jedoch nicht. Er kann als App oder als BS-Prozess angesehen werden.