Follow my new blog

Posts mit dem Label DDD werden angezeigt. Alle Posts anzeigen
Posts mit dem Label DDD werden angezeigt. Alle Posts anzeigen

Donnerstag, 1. August 2013

Was ist eine Entität?

Im Zuge meiner Beschäftigung mit CQRS und Event Sourcing habe ich mir wieder die Frage gestellt, was denn eigentlich eine DDD Entität sei?

Nun hab ich endlich für mich eine ganz einfache Definition gefunden: Eine Entität ist ein Dictionary<string,string> mit unwandelbarer Id [1]. Eine Entität ist ein Sammlung von Schlüssel-Wert-Paar als Zeichenfolgen mit unwandelbarer Id [1].

Jup. Das isses. Mehr nicht [2].

Den Inhalt einer Entität kann man wandeln, die Id aber nicht. Und der Inhalt einer Entität ist strukturiert. Gerade die Annahme, dass Keys Pfade sein können [2], macht mich zufrieden. Nicht nur “Vorname” oder “Mindestgebot” oder “Lagerplatz” sind also Keys, sondern ebenfalls “Wohnsitz/PLZ” oder “Telefonnummern/3” oder “Positionen/2/Menge” [3].

So ein Dictionary ist ein Dokument, d.h. es ist selbstgenügsam (self-contained). Alles, was dazugehört, steht drin. Was nicht drin steht, gehört zu einer anderen Entität. So einfach ist das.

Was Sie als Daten bei einem Key hinterlegen, ist Ihre Sache. Das kann so umfangreich und strukturiert sein, wie es will. Per definitionem ist das aber keine Entität, weil es eben ein Wert in einer Entität ist. Werte als string [4] und nicht als object zu definieren ist mir deshalb sehr wichtig. Dadurch ist ausgeschlossen, aus einer Entität Objekte zu referenzieren.

Entitäten dürfen allerdings Ids anderer enthalten. Solche Entitätsreferenzen sind ok, weil sie sich auf das wahrhaft Unwandelbare beziehen [5]. Beispiel:

var frau = new Dictionary<string,string>{{$$id, "10"}, {"Vorname", "Eva"}, ...};
var mann = new Dictionary<string,string>{{$$id, "0815"}, {"Vorname", "Adam"}, ...};

frau["partner"] = mann["$$id"];
mann["partner"] = frau["$$id"];

Mit der Definition “Eine Entität ist ein Dictionary<string,string>” ist es nun ganz einfach herauszufinden, ob ein zusammengesetzter Wert eine Entität ist oder nicht. Fragen Sie sich einfach, ob Sie den Wert einem oder mehreren Keys in einer anderen Entität zuweisen würden. Beispiel:

Ist der zusammengesetzte Wert

Person("Vorname": "Peter", "GebDat": "1967-05-12", "Geschlecht": "m")

eine Entität? In manchen Zusammenhängen mag das so sein:

var person = new Dictionary<string,string>{{"$$id", "7654}, {"Vorname", "Peter"}, ...};
var einwohner = new Dictionary<string,string>{{"$$id", "0192"}, {"Person", "7654"}};

In anderen wieder nicht. Dann ist er Teil einer umfassenden Entität, z.B.

var einwohner = new Dictionary<string,string>{...};
einwohner["Person"] = "{\"Vorname\": \"Peter\",
                        \"GebDat\": \"1967-05-12\",
                        \"Geschlecht\": \"m\"}";

oder

einwohner["Person/Vorname"] = "Peter";
einwohner["Person/GebDat"] = "1967-05-12";
einwohner["Person/Geschlecht"] = "m";

Ich denke, das macht die Entscheidung einfacher. Doch auch wenn die mal daneben geht, ist es nicht so schlimm. Entitäten in dieser Form kann man auch leicht refaktorisieren. “Inline entity” und “extract entity” scheinen mir genauso probat wie “inline method” und “extract method”. Also keine Angst vor BDUF!

Außerdem lassen sich Entitäten dieser Form sehr leicht verteilt über Ereignisse denken, z.B.

Zugezogen("$$id": "9384", "Nachname": "Müller", "Straße": "Isestr. 2", ...)
Umgezogen("$$id": "9384", "Straße": "Richterstr. 17", "PLZ": "22085", ...)
Geheiratet("$$id": "9384", "Ehepartner": "0815", "Nachname": "Schmied", ...)
Geheiratet("$$id": "0815", "Ehepartner": "9384", ...)

In jedem Ereignis wird die Entität angegeben und danach nur eine Liste von Keys mit neuen Werten. Der aktuelle Stand einer Entität ergibt sich dann aus dem Summe ihrer Ereignisse.

Endnoten

[1] Ob die Id ein Eintrag mit speziellem Key in so einem Dictionary ist oder separat gehalten wird, ist mir erstmal egal.

[2] Noch einfacher könnte man natürlich sagen, eine Entität sei ein strukturierter Text, z.B. ein Json- oder XML-Dokument. Wird letztlich bei NoSql auch so gemacht. Aber das ist mir grad zu wenig griffig, wenn ich an den Umgang damit im Code denke. Mit einem Dictionary fühle ich mich etwas wohler.

Ein geschachteltes Dokument wie

<Person id="1234">
  <Vorname>Peter</Vorname>
  <Anschrift>
    <PLZ>22085</PLZ>
  </Anschrift>
</Person>

sieht mir auch komplizierter aus als eine Liste von Key-Value-Paaren:

$$id:1234
Vorname:Peter
Anschrift/PLZ:22085

Das eine kann in das andere übersetzt werden. Das reicht mir.

[3] Wem da Metadaten fehlen, der kann sie sich gern dazudenken, z.B. “Vorname:string” oder “Positionen/2/Menge:int”. Am Ende ist jedenfalls jeder Wert auf eine Zeichenkette abbildbar.

Wie solche Pfade aussehen, ist hier ebenfalls nicht wichtig. Mit der Syntax, die ich hier benutze, sind Sie ja aber vertraut.

[4] Binärdarstellungen sind nur eine Optimierung. Mit textuellen Repräsentationen von Daten kommen wir weiter. Sie sind ausreichend, bis ein konkreter Use Case etwas anderes fordert.

[5] Allerdings hadere ich hier noch mit mir. Manchmal denke ich, dass Entitäten gar keine Referenzen enthalten sollten. Wenn sie zu anderen in Beziehung stehen, dann nur innerhalb eines Kontextes - und der wird dann von einem Aggregat aufgespannt.

Ein Aggregat ist dann eine Entität, deren Zweck es ist, andere zu verbinden. Es ist ein Beziehungsraum.

So ganz traue ich mich aber noch nicht, das konsequent zu denken. Ich habe es zwar schon einmal in der dotnetpro so beschrieben - doch im Moment bin ich wieder ein wenig im Zweifel… Hm… warum eigentlich? Dazu mach ich mir am besten ein andermal weitere Gedanken.

Mittwoch, 24. Juli 2013

Historische Interaktionen

Die erste Frage bei der Überführung von Anforderungen in Code sollte den Interaktionen gelten: Welche Interaktionen braucht ein Softwaresystem? Das bedeutet, welche technischen Ereignisse [1] sind domänenrelevant und müssen verarbeitet werden?

Im einfachsten Fall bedeutet das, hinter jedem Menüpunkt und jeder Schaltfläche steht eine Interaktion. Löst der Anwender sie aus, soll im Softwaresystem etwas passieren.

Für mich beginnt die Softwareentwicklung also mit einer Diskussion über die Schnittstellen des Softwaresystems zur Umwelt. Dort finden Interaktionen mit Benutzern statt - seien das Menschen, andere Software oder Maschinen. Von dort treibe ich den Softwareentwurf nach innen weiter: outside-in design. Denn alle Strukturen im Softwaresystem müssen einem Bedarf in der Umwelt dienen, der über eine Schnittstelle vermittelt wird.

Wie der Entwurf nach Fund einer Interaktion dann bis zum Code fortschreiten kann, will ich hier nicht thematisieren. Von mir aus kann man das streng mit Test-Driven Design versuchen.

Entscheidend ist vielmehr, dass Interaktionen einen idealen Ansatzpunkt für jegliches weiteres Vorgehen bieten. Jede Interaktion kann nämlich als Funktion codiert werden [2].

Beispiel Benutzeranmeldung: Die Benutzeranmeldung erfolgt über einen Dialog, in dem der Anwender seinen Benutzernamen und ein Passwort einträgt. Dann drückt er die Schaltfläche “Anmelden”, um die Authentifizierung und Autorisierung anzustoßen.

Die Schaltfläche steht für die Interaktion “Anmelden”. Und die Interaktion kann als Funktion bool Anmelden(string benutzername, string passwort) realisiert werden.

Alternativ könnte die Interaktion durch einen Menüpunkt oder einen Tastatur-Shortcut ausgelöst werden. Das technische Ereignis ist letztlich unwichtig. Wichtig ist, was daraufhin passiert, das Verhalten des Softwaresystems. Und das kann komplett in einer Funktion gekapselt werden. Immer.

Soweit der Blick mit unbewaffnetem Auge.

Interaktionen unterscheiden

Jetzt aber die Lupe zur Hand genommen. Da zeigen sich nämlich Unterschiede in den Interaktionen bzw. Interaktionsfunktionen.

image

Reinheitsgrad: Ich denke, es lohnt sich die Unterscheidung zwischen reinen und unreinen Interaktionen. Reine Interaktionen arbeiten nur auf Daten, die ihnen übergeben werden. Beispiel: Wenn ich in einem Dialog einen Text eingeben kann, der auf Knopfdruck umgekehrt wird, damit ich sehen kann, ob ein Palindrom vorliegt, dann steht dahinter eine reine Interaktionsfunktion. Der oben skizzierte Anmeldedialog hingegen stößt eine unreine Interaktionsfunktion an. Mit Benutzername und Passwort allein kann nicht authentifiziert werden; dazu ist mindestens noch ein Verzeichnis von Benutzern nötig.

Verhaftungsgrad: Unreine Interaktionen zerfallen für mich darüber hinaus noch in solche, die zeitlich verhaftet sind - und solche, die quasi nur im Moment leben.

Zeitlich verhaftete Interaktionen benötigen für ihre Arbeit ein Gedächtnis. Ihnen ist die Vergangenheit wichtig - zumindest in Form eines akkumulierten Zustands. Man könnte auch sagen, sie seien historisch determiniert. Denn ihr Verhalten hängt nicht nur vom aktuellen Input ab, sondern von vorherigem Verhalten des Softwaresystems.

Der Anmeldedialog ist dafür ein Beispiel. Ob er einen Benutzer erfolgreich authentifiziert oder nicht, hängt davon ab, welche Veränderungen vorher am Benutzerverzeichnis vorgenommen worden sind.

Anders ist das bei Interaktionen, die zwar auch nicht nur auf ihrem Input arbeiten, sondern weitere Ressourcen brauchen. Doch da ist deren Geschichte nicht wichtig. Sie wird nicht in die Verarbeitung mit einbezogen.

Das ist zum Beispiel der Fall, wenn eine Interaktion etwas druckt oder Input mit der aktuellen Systemzeit vergleicht.

Zugriffsart: Unreine Interaktionen, die nur im Moment leben, können auf ihre Ressourcen lesend oder schreibend zugreifen. Sie interessieren sich nicht für die Vergangenheit, also dürfen sie Zustand auch einfach verändern. An der Ressource ist anschließend nicht abzulesen, in welchem Zustand sie vorher war.

Aber wie ist das bei unreinen verhafteten Interaktionen?

Ich glaube, hier sollten wir umdenken. Bisher ist dort auch lesender und schreibender Zugriff erlaubt - ohne dass vorherige Zustände erhalten blieben.

Aus Effizienzgründen war das bisher so. Wir konnten nicht anders oder haben das zumindest geglaubt. Doch es hat immer schon der Natur dieser Interaktionen widersprochen.

Das haben wir schmerzvoll erfahren, wenn wir Mühe hatten undo/redo zu implementieren oder Daten mit Geltungsdauern zu verwalten oder historische Daten zu pflegen oder wir uns mit der Versionierung von Datenstrukturen herumschlagen mussten.

Wenn wir nun aber mal von eingefahrenen Mustern Abstand nehmen, dann können wir mit solchen Interaktionen natürlicher umgehen. Das bedeutet für mich, dass bei “historischen Interaktionen” nur lesender und anfügender Zugriff erlaubt sind. Es gibt keinen schreibenden im Sinne von verändernden Zugriff. Denn der würde ja die Geschichte umschreiben.

Die Geschichte, das ist die Liste der Veränderungen, die zum aktuellen Stand der Daten geführt hat. Ja, ich glaube, die sollte unangetastet bleiben. Wir können davon nur profitieren.

Fazit

Nicht jede Interaktion ist zeitlich verhaftet. Wir tun also gut daran, genauer hinzuschauen. Die Unterscheidung zwischen Interaktionsfunktionen mit und ohne Seiteneffekt finde ich jedoch zu einfach. Denn Seiteneffekte können sehr unterschiedlich sein.

Die Ausgabe auf einem Drucker ist etwas anderes, als die Fortschreibung von Unternehmensdaten in einer Datenbank. Event Sourcing scheint mir in dieser Hinsicht ein sehr probates Mittel, um Klarheit zu schaffen.

Interaktionen, die mit Zustand zu tun haben, der sich über die Lebenszeit eines Softwaresystems durch das Softwaresystem entwickelt, sollten sich dafür ein Gedächtnis bewahren. Das ist für mich der neue Default.

Und damit dieser Default nicht über Gebühr verstört, sollte klar sein, wo er gilt. Das sind nur die “historischen Interaktionen”. Alle anderen - auch wenn sie Seiteneffekte haben - können weiter wie bisher gehandhabt werden.

 

PS: Natürlich können unreine Interaktionen auch gemischter Art sein: Sie können aus Wiedergabe und Seiteneffekt oder aus Lookup und Aufzeichnung bestehen usw.

In jedem Fall sollten diese Aspekte aber in eigenen Funktionen gekapselt sein. Das Integration Operation Segregation Principle (IOSP) gilt auch hier.

Endnoten

[1] Technische Ereignisse sind Tastendrücke, Mausklicks/-bewegungen oder Notifikationen von Timern oder Geräten.

[2] Die Schnittstelle zwischen Umwelt und Domäne nehme ich von dieser Funktion aus. Die Interaktionsfunktion kümmert sich nicht darum, woher ihre Input-Daten kommen und was mit ihren Output-Daten gemacht wird. Sie kennt die Schnittstelle nicht, über die sie aufgerufen wird. Das kann ein WPF-Dialog oder ein Webservice sein.

Es ist also nicht Sache einer Interaktionsfunktion, sich Daten aus Eingabefeldern des UI zu beschaffen oder Ergebisse im UI, über das sie aufgerufen wurde, anzuzeigen.

Mittwoch, 3. Juli 2013

Event Sourcing: Vorzeitige Datenstrukturoptimierung vermeiden

Datenstrukturen wohin Sie schauen. Wenn Sie Anforderungen studieren, sucht Ihr Gehirn sofort nach Zusammenhängen und Mustern für Daten. So sind Sie einfach trainiert. Die Objektorientierung scheint das nahezulegen. Der Umgang mit Datenbanken erfordert das allemal.

Ich kenne diesen Reflex jedenfalls genau. Wenn ich vor der Aufgabe stehe, einen Stack zu implementieren, frage ich mich, wie dafür die Daten strukturiert sein sollen. Speichere ich die Einträge in einem Array? Oder benutze ich besser eine Liste? Sollte die Liste einfach oder doppelt verkettet sein?

Oder wenn ich vor der Aufgabe stehe, ein Tic Tac Toe Spiel zu implementieren, dann frage ich mich, wie das Spielbrett intern abgebildet werden sollte. Ist es besser, alle Spielfelder in einem eindimensionalen Array zu halten? Oder sollte ich ein zweidimensionales Array benutzen?

Und falls dazu noch die Anforderung kommt, die Daten zu persistieren, dann mache ich mir Gedanken über das Persistenzparadigma – relational oder dokumentenorientiert oder key-value store usw. – und ein dazugehöriges Schema.

Puh… ganz schön viele Gedanken, die sich um Datenstrukturen drehen. Aber das ist ja auch verständlich, weil es um die eine Datenstruktur geht, quasi das Herzstück der Codes. Denn alle Funktionalität muss damit leben. Da greift man besser nicht daneben, sonst knirscht das Software-Getriebe später.

Dieses Vorgehen scheint alternativlos. So haben wir es schon immer gemacht. So muss man Softwareentwicklung planen.

Oder?

Nein! Ich glaube daran nicht mehr. Historisch gesehen finde ich diese Herangehensweise zwar verständlich – nur müssen wir deshalb ja nicht so weitermachen.

Für mich scheint der “data structure first” Ansatz zunehmend kontraproduktiv. Er versucht gleich zu Beginn der Entwicklung etwas zu optimieren, das sich eigentlich erst über die Zeit ergeben muss. Beim Stack und für Tic Tac Toe mag das noch nicht so offensichtlich sein. Liegen da die Datenstrukturen nicht sehr klar auf der Hand? Aber bei Ihren Anwendungen ist das sicherlich anders. Beweis dafür ist die Bewegung, die über Jahre in Ihren Schemata stattgefunden hat. Die sehen sicherlich nicht mehr so aus wie am Anfang.

Datenstrukturen sind kein Selbstzweck. Wenn Sie eine Datenstruktur planen, müssen Sie vielmehr immer genau im Blick haben, wer deren Konsument ist. Was sind dessen Bedürfnisse? Wissen Sie das aber genau? Können Sie das wissen? Ist das nicht auch eine Form von Big Design Up-Front (BDUF) und ein Fall von Premature Optimization?

Klar, bei Tic Tac Toe ist da natürlich zunächst einmal der Anwender. Der will auf seinem Bildschirm ein zweidimensionales Spielbrett sehen.

Und dann ist da die Domänenlogik. Die muss auch mit dem Spielbrett umgehen. Aber ist für sie ebenfalls eine zweidimensionale Struktur die beste? Vielleicht. Vielleicht aber auch nicht. Herausfinden werden Sie das erst, wenn Sie die Domänenlogik implementieren.

Vielleicht ist es sogar so, dass verschiedene Aspekte der Domänenlogik unterschiedliche Datenstrukturen bevorzugen würden. Braucht die Bestimmung des nächsten Spielers eine zweidimensionale oder eindimensionale Datenstruktur oder gar etwas anderes? Wie steht es mit der Prüfung, ob ein Spieler gewonnen hat? Wie wird am leichtesten festgestellt, ob ein Zug überhaupt gültig ist?

Wenn Sie nach der einen Datenstruktur zur Erfüllung aller Anforderungen an den Entwurf herangehen, befinden Sie sich schnell im Kreuzfeuer vieler “Stakeholder”, d.h. Aspekte. Die Suche nach einem Optimum ist da vergeblich, würde ich sagen. Es kann immer nur ein Kompromiss herauskommen. Die Frage ist nur, wie sehr Sie sich dabei aufreiben.

Warum also nicht Zeit sparen? Schluss mit BDSDUF = Big Data Structure Design Up-Front. Seien Sie schnell statt gründlich – insbesondere wenn die Gründlichkeit ja ohnehin kein stabiles Ergebnis liefern kann. Statt in die Glaskugel schauen Sie auf die ohnehin stattfindende Entwicklung beim Umgang mit Daten und versuchen, Muster zu entdecken. Dann kommen Sie früher oder später schon zu stabile(re)n Datenstrukturen.

Den Plural meine ich hier ernst. Statt sich auf die eine Datenstruktur zu kaprizieren, versuchen Sie mal, mehrere zuzulassen.

Events als Datengranulat

Ich will versuchen, Ihnen konkreter zu beschreiben, was ich damit meine. Als Beispiel soll Tic Tac Toe dienen. Die Verarbeitung eines Zuges könnte so aussehen:

image

Klar ist dabei, womit die Verarbeitung angestoßen wird – durch Meldung der Koordinate des Spielfeldes, auf das ein Spieler einen Stein setzen will – und was die Verarbeitung am Ende als Ergebnis liefern soll: die aktuelle Konfiguration des Spielfeldes sowie den Spielstand (ob es weitergeht oder das Spiel zuende ist durch Gewinn oder Unentschieden).

Alle anderen Daten liegen im Dunkeln. Also könnte es losgehen mit der Spekulation, wie denn “das Spiel” über diese Kette von Verarbeitungsschritten am besten repräsentiert werden sollte, was die eine beste Repräsentation sein sollte.

Was immer Sie nun dazu denken… Ich möchte Ihnen etwas anderes vorschlagen. Wie wäre es, wenn es keine spielspezifische gemeinsame Datenstruktur gäbe? Wie wäre es, wenn es keine Datenstruktur gäbe, die den aktuellen Zustand darstellte?

Stattdessen schlage ich vor, eine Liste von Zustandsänderungen zu führen. Im Falle von Tic Tac Toe ist das ganz, ganz einfach. Diese Zustandsänderungen sind die Züge. Jeder Zug verändert die Konfiguration des Spielbretts und den Spielstand. Das kann als Ereignis (Event) angesehen werden. Und diese Events schlage ich vor zu speichern.

Statt Spielbrett mit Daten für Spielfeldbelegungen…

image

…gibt es eben nur eine Liste von Events:

image

Diese Events beziehen sich zwar auf die Vorstellung eines zweidimensionalen Spielbretts, doch das existiert eben nicht statisch.

Und wie sollen die Verarbeitungsschritte nun vorgehen?

Fangen wir mal einfach an: Für den Spielerwechsel muss nichts getan werden. Es ist kein spezieller Zustand zu führen. Welcher Spieler dran ist, ergibt sich aus der Anzahl der Züge:

image

Das ist auch nur relevant für das Rendering des Spielbretts.

Wie ist es mit der Zugausführung – vorausgesetzt, der Zug ist valide? Das ist ein Einzeiler:

image

Es muss ja nur memoriert werden, welcher Zug gemacht wurde. Kein Spielstein wird auf einem Spielbrett platziert.

Und wie sieht die Validation aus? Die beschränkt sich auf die Prüfung, ob die Koordinate des Zuges schon einmal vorgekommen ist:

image

Falls ja, liegt ein Fehler vor. Nur der Einfachheit halber bricht das Spiel dann mit einer Exception ab.

Jetzt die Spielstandprüfung. Die ist aufwändiger. Aber das wäre sie auch, wenn der Spielbrettzustand vorgehalten würde:

image

Ob ein Unentschieden vorliegt oder nicht, ist schnell entschieden. Wenn alle Felder belegt sind, also 9 Züge gemacht wurden, geht nichts mehr.
Die möglichen Gewinnpositionen werden einzeln geprüft. Züge auf horizontale, vertikale und diagonale Feldreihen werden selektiert. Falls in einer Reihe nur Züge desselben Spielers gemacht wurden, liegt ein Gewinn vor.
Züge von Spieler X werden mit 1 bewertet, die von O mit –1. Eine Gewinnreihe hat dann den Wert 3 bzw. –3.
Zum Schluss ist natürlich doch ein Spielbrett der herkömmlichen Art nötig. Das zeigt das obige Flow-Diagramm ja schon. Allerdings überlasse ich das nicht dem Spielerwechsel – das wäre ein Widerspruch zum SRP -, sondern verpacke es in eine eigene Routine:
image
Bei CQRS spricht man von ReadModels auf der Query-Seite. Und genau darum geht es ja auch hier: eine Datenstruktur, die nur für lesenden Gebrauch bestimmt ist. Dass die jedes Mal neu generiert wird, macht nichts. Auch ein Cache ist eine Optimierung, die hier vorzeitig wäre.
Zur Abrundung noch die Integration dieser Operationen in einer eigenen Methode:
image

Fazit

Ich habe mir die Entscheidung für die eine alleinseligmachende Datenstruktur gespart. Alle Aspekte des Tic Tac Toe Spiels haben für sich entscheiden können, was ihnen am besten taugt. Und wie sich herausstellt, konnten alle mit einer Event Source sehr gut leben.
Das ist insofern bemerkenswert, als dass eine Event Source eine generische Datenstruktur ist. Die sieht in allen Anwendungen gleich aus. Es ist nicht mehr als eine Liste von Events. Zugegeben, die sind bei TTT sehr, sehr simpel. Aber auch wenn Events eigene Klassen sind, ändert sich das Prinzip nicht. Eine Event Source ist und bleibt eine schlichte Liste. Und aus der kann bei Bedarf jede konkretere Datenstruktur generiert werden.
Deshalb bezeichne ich die Events auch als Datengranulat. Sie sind wie kleine Kunststoffkügelchen, aus denen man bei Bedarf allerlei Nützliches formen kann.
Mit einer Event Source bin ich also schneller am Start. Und wenn ich feststelle, dass ich aus den Events die eine oder andere Datenstruktur öfter generiere, also sich ein Muster herausschält, dann kann ich mir Gedanken darüber machen, ob ich dafür einen Cache einrichte. Denn nichts anderes sind die üblichen fein ziselierten Datenstrukturen in unseren Anwendungen.
Ihre Bevorratung dient der Effizienz – und kostet uns Zeit in der Entwicklung und Flexibilität während der Evolution der Software.
Deshalb: Versuchen Sie doch einmal, solche vorzeitige Optimierung zu vermeiden. Geben Sie solchem Event Sourcing eine Chance.

Montag, 1. Juli 2013

Eine Black Box für Software

CQRS hat mich jetzt gepackt. Auf der DWX Konferenz hatte ich Gelegenheit, mich darüber länger mit Jan Fellien auszutauschen. Den Moment, wo ich innerlich “Aha!” und “Wow!” ausrief war, als ich erkannte, was die Aufgabe einer Event Source ist.

Nicht nur ist Event Sourcing für mich die Antwort auf meine Frage nach einem Datengranulat. Denn aus den “Daten-Kügelchen” vieler kleiner Events kann man sich größere Strukturen in immer neuer Weise “gießen”. Es gibt für mich nicht mehr die Frage, ob “das eine Datenschema” für eine Anwendung relational oder dokumentenorientiert oder sonstwie sein sollte. Stattdessen gibt es soviele Schemata und Datenbanken wie man braucht. Und alle werden aus der einen Quelle gespeist: aus der Event Source.

Daten für einen Zweck in einem bestimmten Schema bereitzustellen, kann immer dynamisch aus der Event Source geschehen. On demand. In dem Augenblick, wenn sie benötigt werden. Wem das zu langsam ist, der muss halt die Daten in dem Schema cachen. Doch das ist dann eine bewusste Optimierung.

Mit einer Event Source kann man diese Optimierung dann vornehmen, wenn man sie braucht. Bis dahin ist man frei von lästigen Überlegungen, wie denn ein Schema am besten aussehen sollte. Welche Erleichterung!

Aber dieser Gedanke hatte mich nicht überfallen auf der DWX. Den hatte ich schon vorher. Im Gespräch mit Jan kam mir vielmehr ein sehr mächtiges Bild für die Event Source in den Sinn.

Die Event Source ist die Black Box einer Software.

Ich meine das im Sinne der Flugschreiber, die auch als Black Box bezeichnet werden. Die zeichnen Ereignisse während des Fluges auf. Wenn etwas schief geht, kann man durch Abspielen der Aufzeichnung versuchen, die Ursache zu finden.

Das leistet für mich nun auch eine Event Source bzw. ein Event Store für Software. Alle Domänenevents werden doch gespeichert (record). So kann man den Zustand eines Programms zu jeder Zeit rekonstruieren. Was an Zustand in-memory ist nur eine Optimierung; Zustand in einem Read-Model ist auch nur eine Optimierung. Maßgeblich ist einzig das, was in der Black Box steht.

image

Wenn also ein Programm oder auch nur ein Teil abstürzt, kann es neu gestartet und aus der Event Source auf den letzten Stand gebracht werden (replay). Die Speicherung auch kleinster Veränderungen des Zustands ist ja kein Problem, weil die Events als “Zustandsdifferenzen” ganz simpel und schnell persistiert werden können.

Events kommen aus der Domäne. Da spielt die Anwendungsmusik. Dafür wird Software gemacht.

Die Domäne spielt immer wieder aber auch Events ab, da sie ja nicht ihren kompletten Zustand in-memory halten will.

Andere Konsumenten von Events werden über sie per Notifikation informiert. Die sind also von der Eventquelle entkoppelt. Falls sie jedoch offline waren, können sie sich “verpasste” Events wieder vorspielen lassen, um sich auf den aktuellen Stand zu bringen.

Bei CQRS ist ein typischer Event-Konsument natürlich das Read Model. Es fertigt aus einzelnen Events fixe größere Strukturen, die auf unterschiedliche Abfragemuster zugeschnitten sind.

Ich finde das Bild der Event Source als Black Box sehr eingängig und motivierend. Damit werde ich mich jetzt mal intensiver beschäftigen…

Donnerstag, 30. Mai 2013

Daten als systemrelevante Größe behandeln

Systemrelevante Größen sind in der Softwareentwicklung wie auch sonst im Leben zu vermeiden. Dieser Meinung bin ich immer noch und sogar immer mehr.

Doch auch wenn wir sie vermeiden sollen, heißt das nicht, dass wir das immer können. Manches ist einfach systemrelevant, weil es die fundamentale Wahl für ein System repräsentiert. Es muss konstant bleiben - ansonsten handelt es sich um ein ganz anderes System. Für die westliche Welt sind das z.B. eine Demokratie oder Marktwirtschaft.

Für die Softwareentwicklung gibt es das auch. Da gibt es einen Aspekt, der ist so zentral, dass wir nicht um ihn herum kommen. Er ist bestimmend. Es ist unveränderlich.

Damit meine ich nicht, ein bestimmtes Betriebssystem oder eine Entwicklungsplattform. Im Gegenteil! Die müssen ständig disponibel.

Nein, ich meine die Daten. Die Daten einer Software bzw. eines Unternehmens sind aus meiner Sicht eindeutig systemrelevant. Sie sind förmlich das Fundament. Sie gilt es zu hegen, zu pflegen, zu erhalten. Nicht umsonst heißt es: data outlives application.

Anwendungen kommen und gehen über Jahre und gar Jahrzehnte. Doch die Daten bleiben. Sie wachsen nur. Allemal in Zeiten scheinbar unbegrenzten Speicherplatzes scheint es töricht, Daten zu löschen. Wer weiß, wann sie einmal nützlich werden könnten für eine Big Data Auswertung?

Wenn das aber nun so ist, dass Daten unvermeidbar systemrelevant sind, was bedeutet das für den Umgang mit ihnen?

Ich denke, systemrelevante Größen brauchen eine spezielle Behandlung. Sie stehen ja quasi außerhalb des Gesetzes. Wir sind von ihnen abhängig. Deshalb müssen wir darauf achten, dass sie uns keine Diktatur aufzwingen. Wenn eine Größe systemrelevant ist, dann ist sie ein Herrscher. Mit einem "guten König" lässt es sich da leben; aber einen unberechenbaren Nero wollen wir nicht über uns haben.

Letztlich sollte auch die systemrelevante Größe an einem guten Verhältnis mit ihren Untertanen interessiert sein. Denn die Abhängigkeit besteht letztlich in beide Richtungen. Ohne ein System, ist die Größe nichts.

Also, was bedeutet es für die Softwareentwicklung oder genauer: für die Softwarearchitektur, dass Daten eine unvermeidlich systemrelevante Größe sind?

Wie im richtigen Leben folgt daraus die Notwendigkeit zu Transparenz und Flüssigkeit, würde ich sagen.

Systemrelevante Größen müssen besonders offen und verständlich sein. Weil sie systemrelevant sind, können sie ja nicht ersetzt werden. Wenn man sie nicht mehr versteht, gibt es keine Alternative.

Systemrelevante Größen müssen besonders flüssig sein. Ihre Form muss sich verändern lassen. Sie dürfen sich nicht aus innerer Trägheit/Festigkeit dem Wandel widersetzen. Denn Wandel - soviel sollte gewiss sein - wird nötig sein. Systemrelevante Größen haben eine lange Lebensdauer, so dass Veränderungen in der Umwelt und deshalb Anpassung unvermeidbar sind.

Alles, was die Transparenz/Verständlichkeit und die Flüssigkeit beeinträchtigt, ist also zu vermeiden.

Was bedeutet das für Daten konkret?

Daten müssen in einer Weise gehalten werden, die transparent und verständlich ist. Klingt vielleicht einleuchtend - wird aber oft nicht beachtet. Einkaufspolitik, Tradition, Performance und andere Kräfte ziehen Daten oft in eine Form, die Transparent und Verständlichkeit reduzieren. So weit reduzieren, bis nur noch eine kleine Gruppe von Personen die Daten versteht. Die bestimmt dann über das Schicksal einer Organisation - ob sie will oder nicht.

Was Transparenz im konkreten Fall bedeutet, weiß ich nicht. Das ist von Fall zu Fall, von Datenbasis zu Datenbasis verschieden. Das eine große Unternehmensdatenmodell mit 1468 eng verwobenen Tabelle in einer Oracle Monsterserver ist aber sicherlich keine Lösung.

Eher sind es Daten in unterschiedlicher Organisationsform in verschiedenen Bounded Contexts, die transparent sind.

Daten müssen darüber hinaus auch in einer Weise gehalten werden, die es leicht macht, ihre Form immer wieder zu verändern. Formate und Technologien, die es schwer machen, zu exportieren und zu importieren, sind zu vermeiden. Lock-In jeder Art ist eine Gefahr für das System. Wer sagt, "Wir sind eine XYZ-Company!" ist schon auf dem falschen Weg. (Setzen Sie für XYZ ein Persistenzprodukt oder eine Technologie oder ein Paradigma Ihrer Wahl ein.)

ETL ist deshalb auch here to stay und wird weiter an Bedeutung gewinnen. Weil die Vielfalt der Optionen, der Anwendungen und Bounded Contexts wächst, mit/in denen Daten gesammelt und verarbeitet werden.

Event Sourcing halte ich aus diesem Grund auch für zeitgemäß. Denn damit werden Daten im Grunde von jedem Format befreit. Das macht sie maximal flüssig. Events sind quasi ein Granulat, das in immer neue Form je nach Bedarf gegossen werden kann.

Ja, ich denke, Transparenz und Flüssigkeit sind die beiden zentralen universellen nicht-funktionalen Anforderungen an die Datenhaltung. Sonst ist sie nicht nachhaltig. Sonst kommt es zu hemmendem Lock-In der einen oder anderen Art - und das kostet Geld.

Daten sind eben ein oder vielleicht sogar die einzige wirklich systemrelevante Größe in der Softwareentwicklung. Deshalb müssen wir mit ihnen in besonderer Weise umgehen.

Dienstag, 1. Juni 2010

Die Ubiquitous Language konsequent codieren [OOP 2010]

Das war mir doch ein Experiment wert: Wie würden es Entwickler aufnehmen, wenn quasi alle Parameter von Methoden (zumindest in den Kontrakten von Komponenten) nicht primitiv sein sollen? Auf dem Coding Dojo in München habe ich es ausprobiert.

Das Beispielszenario dort war eine Rechtschreibprüfungsanwendung. In der gibt es dann irgendwo eine Funktion WortKorrekt() o.ä. Deren Signatur würde üblicherweise so aussehen:

bool WortKorrekt(string wort)

Darin sind zwei primitive Typen zu finden: bool und string.

Ich bin nun der Meinung, dass wir uns anstrengen sollten, solche Typen (zumindest in den Kontrakten von Komponenten) zu vermeiden. Statt unspezifischer primitiver Typen sollten wir spezifische Typen passend zur Problemdomäne definieren.

Codegrundlage Ubiquitous Language

Über die Problemdomäne sprechen wir unter uns und mit dem Kunden in der Ubiquitous Language (UL).  Es ist deshalb wichtig, sie in unserem Code wiederzufinden. Sonst müssen wir ständig Aufwand treiben, um unsere Rede in Code (und wieder zurück) zu übersetzen. Code würde weniger verständlich. Und wir wären unsicherer, ob wir unseren Code geeignet strukturiert haben, denn die Begriffe der UL sollten darin repräsentiert werden.

Damit wir uns der UL bewusst werden, habe ich im Dojo mit den Teilnehmern eine Concept Map der UL für die Domäne “Rechtschreibprüfung” erarbeitet:

image

Die sieht ein bisschen wüst aus, vor allem, weil man meine Schrift schon nach wenigen Minuten unleserlich wird ;-) Doch mir gehts hier nicht um Einzelheiten, sondern einfach mal eine Impression.

Die Concept Map enthält für jeden Begriff der Domänensprache einen “Kuller”. Unterschieden wird dabei nicht zwischen Daten und Diensten, Verben und Substantiven. Was wichtig ist, wird in einen Kreis gesetzt. Und dann werden die Begriffe verbunden mit qualifizierten Beziehungen. Ein “Wörterbuch” (Begriff) ist z.B. “abgefasst in” (Beziehung) einer “Sprache” (Begriff).

Der Vorteil einer Concept Map (z.B. statt eines Klassendiagramms) in einem frühen Stadium der Planung ist seine Informalität. Es gibt nur wenige Regeln zu beachten. Sie können Ihren Gedanken freien Lauf lassen. Ziel ist Vollständigkeit in Bezug auf Begriffe/Konzepte und Beziehungen auf einem hohen Abstraktionsniveau. Concept Maps sind Landkarten des Begriffsterrains der Problemdomäne.

Worauf ich nun hinaus will: der Concept Map können Sie die grundlegenden Funktionseinheiten und Daten der Problemdomäne entnehmen. Sie dient mithin der Hinführung zur Implementierung. Die Konzepte “Prüfer” und “Parser” in der Concept Map der Rechtschreibkontrolle sollten also z.B. als Services implementiert werden. (Nein, ich meine nicht SOA-Services, sondern benutze den Begriff “Service” für dienstleistungsorientierte Funktionseinheiten im Gegensatz zu solchen, die eher Daten halten.)

“Wörterbuch” hingegen hört sich eher wie eine Datenstruktur oder zumindest eine Entität an.

Soweit so normal. Sie wären auf diese Konzepte und ihre spätere Implementierung als Klassen vielleicht auf anderem Weg gekommen; identifiziert hätten Sie sie jedoch allemal.

Jetzt aber zu Prüftext, Prüfwort und Fehlerwort. Das sind ebenfalls Begriffe der Domänensprache. Doch wie übersetzen Sie die in Code? Das sind keine Services, keine Entitäten, nicht mal “echte” Datenstrukturen. Der Prüftext ist eher nur ein Text in einer bestimmten Situation. Dito das Prüfwort und das Fehlerwort. Prüfwort und Fehlerwort mögen sogar dieselben Texte sein, einmal vor der Prüfung und einmal hinterher.

Da liegt es doch nahe, diese Begriffe in string-Parameter/Felder zu übersetzen, oder? Die obige Funktion tut das exemplarisch und sieht normal aus, oder? Immerhin haben diese Begriffe keine weiteren Eigenschaften, sind nur “im Fluss” zwischen Services zu finden und ihre Daten müssen auch nicht persistiert werden. Einer so simplen Übersetzung steht daher nichts im Wege.

Primitiven der Ubiquitous Language codieren

Die klassische OOA/OOD kennt natürlich die Repräsentation von Domänenbegriffen als Klassen. Aus einem “Kunden” wird die class Customer, aus einem “Termin” die class Appointment, aus einem Auftragsstatus ein enum OrderState usw. Begriffe, die offensichtliche Eigenschaften haben oder zusammengesetzt sind und Daten repräsentieren, werden in class oder struct übersetzt.

Eine Rechnung besteht aus einer Rechnungsnummer, einem Rechnungsdatum, einem Kunden und vielem mehr. Die Übersetzung sieht dafür meist so aus:

class Invoice
{
    public string InvoiceNumber { get; set; }
    public DateTime InvoiceDate { get; set; }
    public Customer Customer { get; set; }
    …
}

Rechnung und Kunde haben eigene Datentypen bekommen, alle anderen Informationen sind als primitiv eingestuft und mit Standarddatentypen definiert.

Dem möchte ich nun entgegenhalten, auch Primitiven der Domänensprache durch eigene Datentypen zu repräsentieren. Ja, genau: Auch wenn ein Begriff nur für eine ganze Zahl oder eine Zeichenkette steht, sollten Sie ihm eine Klasse (oder eine Struktur) spendieren. Die Implementierung der Rechnung würde dann z.B. so aussehen:

class Invoice
{
    public InvoiceNumber Number { get; set; }
    public InvoiceDate CreatedAt { get; set; }
    public Customer Customer { get; set; }
    …
}

Oder die Prüfroutine der Rechtschreibkontrolle würde so aussehen:

bool WortKorrekt(PrüfWort wort)

Warum das? Ist nicht bool WortKorrekt(string wort) genauso gut zu lesen, genauso aussagekräftig wie bool WortKorrekt(PrüfWort wort)?

Landläufig betrachtet liegen beide in puncto Verständlichkeit nahe beieinander. Doch ich behaupte, dass Code, der weniger mit primitiven oder allgemeinen Typen des .NET Framework arbeitet und stattdessen konkrete Typen der UL benutzt, typsicherer ist und semantisch weniger Zweifel lässt.

Am Ort der Definition mag das weniger sichtbar sein als am Ort der Nutzung:

if (prüfer.WortKorrekt(new PrüfWort(…)))

oder

var inv = new Invoice(InvoiceNumber.Create(), InvoiceDate.Today(), …);

lassen weniger Zweifel, ob zusammenpasst, was zusammenpassen soll.

Einer Zeichenkette “Daten” können Sie nicht ansehen, was sie bedeutet. Es ist einfach nur eine Zeichenkette. Aber new PrüfWort(“Daten”) ist ganz eindeutig etwas anderes als new Dateiname(“Daten”). Ebenso ist new InvoiceDate(“2010-06-01”) (der erste Tag im Monat Juni) etwas anderes als new InvoiceNumber(“2010-06-01”) (die erste Rechnung im Monat Juni).

Die übliche Objektorientierung richtet ihr Augenmerk auf Objekte, d.h. “Dinger mit Eigenschaften”. Ich möchte Sie motivieren, genauer hinzusehen. Zoomen Sie näher heran und sehen Sie die Eigenschaft als letztlich nichts anderes als die Objekte. Die Welt ist rekursiv. Sie besteht aus “Objekten”, die aus “Objekten” bestehen, die aus “Objekten” bestehen usw. Und auf jeder Ebene kann es Begriffe der Ubiquitous Language geben.

Im Beispiel besteht ein Prüftext aus Prüfworten. Das versteht ein Entwickler wie auch der Kunde. Wird das bei der Codierung nur in die Zerlegung einer Zeichenkette in Zeichenketten übersetzt, dann entsteht eine Diskrepanz zwischen Sprache und Code. Der Grundstein für Missverständnisse ist gelegt.

So sollte in der Rechtschreibkontrolle also die Service-Funktionseinheit Parser besser wie folgt definieren:

class Parser
{
    IEnumerable<Prüfwort> ZerlegeText(Prüftext text) { … }
}

mit

class Prüftext
{
    public string Text;
}

class Prüfwort
{
    public string Wort;
}

(Dass ich hier der Klasse Prüftext keine Methode gebe, über die man an ihre Worte herankommt, möchte ich undiskutiert lassen. Das ist ein Thema für einen anderen Blogartikel.)

Zusammenfassung

Seien Sie rigoros bei der Implementierung. Nehmen Sie die Ubiquitous Language Ihrer Problemdomäne ernst. Repräsentieren Sie alle Begriffe durch “Kontrakte” (Interface/Klasse, Struktur, Methodensignatur). Primitive Typen des .NET Framework sollten “im API” von Komponenten auf das Nötigste beschränkt sein. Stattdessen übersetzen Sie selbst primitive Konzepte der Domäne in eigene Typen.

Sie machen damit ihren Code leichter lesbar und auch leichter veränderbar. Denn wenn heute ein Prüfwort womöglich nur eine Zeichenkette ist, dann könnte es morgen eine Zeichenkette mit einer Position in einem Text sein.

class Prüfwort
{
    public string Wort;
    public Textposition Position;
}

Und übermorgen ist es eine Zeichenkette mit einer Position in einer Sprache.

class Prüfwort
{
    public string Wort;
    public Textposition Position;
    public Wörterbuchsprache Sprache;
}

Machen Sie Ihren Code an jeder Stelle so präzise und domänenorientiert wie möglich.