Follow my new blog

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.

Kommentare:

Jander.IT hat gesagt…

Diesem Plädoyer kann ich nur zustimmen, genau so isses ;)

Frank Becker hat gesagt…

Hm, ist der event-store in dem Sinne nicht auch eine Datenstruktur? In diesem Falle hast Du Dich ja doch zumindest für eine "Datenstruktur" für die Züge entschieden!?

Ralf Westphal - One Man Think Tank hat gesagt…

@Frank: Es geht um Datenstruktur in Bezug auf die Domäne. Und da ist ein Eventstore als Liste von allen möglichen Events keine spezifische Datenstruktur.

Deshalb kann man auch einen generischen Eventstore mit so einem Interface bauen:

interface IBlackBox {
..void Record(IEvent event);
..IEnumerable Replay();
..event Action Recorded;
}

Der Eventstore sieht halt in jeder Anwendung gleich aus. Das kann man von einem Domänenobjektmodell wahrlich nicht sagen.

Jan Fellien hat gesagt…

Ich bin so froh, dass wir uns die zwei Stunden auf der DWX genommen haben.

:)

Mike Bild hat gesagt…

Hey - sehr cool!

hier mal meine JS + ES Tic Tac Toe Variante

https://gist.github.com/MikeBild/5926056

Viele Grüße, Mike

Mike Bild hat gesagt…

... so nochmals ein kleines Update meines "Quick Tic Tac Toe Hacks" unter

https://gist.github.com/MikeBild/5926056

gemacht.

So wird der Einsatz der Events und des Zustandsautomaten (Aggregate) hoffentlich etwas klarer.

Ein bisschen Optimierung geht sicher immer noch. Feedback erwünscht ;)

Greetz, Mike

Mike Bild hat gesagt…

... vielleicht auch hier noch ein paar Kommentare zur JS Lösung.

Der Gedanke ist, einen "Endzustand" aus den entsprechenden Events über einen "Event zu Funktion" Pattern Match und einer Aggregation (Fold) als ein einziges Zustandsobjekt zu projizieren. Das geschieht mittels eines Loop über alle Events und Invoke der "passenden" Methode plus eines Zustandsobjektes. Das Zustandsobjekt kann natürlich über die $init einen Initialzustand erhalten. Danach wird schrittweise ein neuer Zustand (Aggregate bzw. Fold Left) erzeugt und zurückgegeben.

"Gewinner finden" habe ich über ein "Intersect" von "Eingabe-Menge" eines Spieles und "Lösungs-Mengen" gelöst.

Bei mir wird der nächste Spieler über einen Toggle auf Zustand ermittelt.

Fehleingaben, wie bsw. "belegte Position" werden in der Zustandsprojektion (Zustandsautomaten) einfach ignoriert. Durch Toggle des nächsten Spielers sehr einfach möglich.

Dadurch kann ich eben alle Events speichern und erst in der Projektion "herauspicken" was toll ist und was nicht. -> "Keine Events werden abgelehnt!"

Die Projektionen sind letztlich sehr ähnlich zu Ralfs Implementierung (ReadModel_generieren) jedoch Teil des Interfaces "IBlackBox".

IMHO ist mir die "IBlackBox" etwas zu "einfach", aber muss vielleicht auch so sein, da .NET eine nette Projektionssprache nämlich LINQ besitzt. In meinem Beispiel ist die Projektion hin zum (End)Zustand eines Event-Stream eben integraler Bestandteil. Finde ich persönlich auch ganz gut so.

Mehrere Event-Streams zu einem Endzustand (joining) zu projizieren ist natürlich möglich. Das aber auch in einem anderen Beispiel ;).

Greetz, Mike

Ralf Westphal - One Man Think Tank hat gesagt…

@Mike: Interessante Lösung - leider verstehe ich sie nicht ;-)

Ich sehe keinen Eventstore. Ich sehe nicht recht, wie das Verhalten erzielt wird. Und ich krieg nix zum Laufen. Was muss man eingeben?

Insofern: Ideal für den heutigen "Clean Code" Kurs in Köln, wo wir immer auf der Suche nach einem Brownfield sind, das wir refaktorisieren können ;-)

Aber vielleicht magst du ja selbst noch etwas säubern?

Mike Bild hat gesagt…

@ralfw: copy & paste in ein bla.html und im WebBrowser laden. Fertig.

In "Aggregate" liegt der Event-Store - Zeile 46 - mehr ist es nicht.

Boah - "Brownfield" - LOL - immer sofort noch Release - gern nehmt es ruhig auseinander - dafür ist es von mir gemacht worden ;).

Die Lösung ist etwas "breiter" ausgestellt, da ich hier sozusagen eine App-Kata umgesetzt habe. Benutzer Input und Output habe ich bereits eingebaut. Insofern etwas mehr Funktionalität und IMHO Vollständigkeit als in Deinem Post. Etwas "Handfestes" um das Prinzip zu klären.

Was musst du eingeben? Die Koordinaten Deines Spielsteins. - Wie in deinem Beispiel - 11 für links unten - 33 für rechts oben.

31 32 33
21 22 23
11 12 13

Weitere Fragen? ;)

Greetz, Mike

Mike Bild hat gesagt…

@ralfw:

Ein zwei dinge sind mir in Deinem Beispiel aufgefallen:

Algo: leider keine Gewinnermittlung für Diagonalen

"validieren" vor "ausführen" - machbar ja - halte ich für keine so gute Idee. Folgt für mich - vorzeitige Logik die mir eventuelle spätere Logiken, Zustände, Auswertungen (Data Mining) versperren könnten.

Der Witz ist ja gerade, das ich alles annehmen sollte und erst später bei "Projektion" entscheide, was für den Use-Case wichtig ist.

Greetz, Mike

Ralf Westphal - One Man Think Tank hat gesagt…

@Mike: Gewinnermittlung findet für Diagonalen statt. Ist nur hier im Listingausschnitt abgeschnitten. Und es ist ein kleiner Fehler drin, der aber im Code korrigiert ist.

Validation vor Ausführung finde ich zwingend - wenn man denn validieren will. Und ich verstehe nicht, was dein Argument dagegen ist.

Und welche späteren Logiken? Es wird nichts ins Log eingetragen, was nicht korrekt ist. Das finde ich völlig legitim.

Wenn irgendwann einer kommt und sagt, er will auch Fehleingaben sehen... na, dann kann man das umdrehen. Erstmal ist es für mich aber naheliegend, grobe Fehler aus dem Eventlog rauszuhalten.

Ralf Westphal - One Man Think Tank hat gesagt…

@Mike: In meinem Beispiel sind die Feldkoordinaten

00 01 02
10 11 12
20 21 22

"In Aggregate liegt der EventStore" - genau das meine ich :-) "Verständlich" finde ich das leider nicht. Da bin ich doch mehr als "least astonished".

Dass "Handfeste" find ich gut. Eine weitere Diskussionsgrundlage. Andere Plattform. Super Sache. Nur eben... sorry to say... für mich nicht gut nachvollziehbar.

Mit deiner Erläuterung der Eingabe konnte ich es dann auch bedienen.

Mike Bild hat gesagt…

@Ralf: Diagonalen - okay, war nicht sichtbar.

"Validation vor Ausführung finde ich zwingend"
" Es wird nichts ins Log eingetragen, was nicht korrekt ist."

Wer weiß das alles im Vorfeld? Was ist korrekt, was nicht? Damit muss eine Lösung "durchdachter" sein. Klaro - völlig legitim. War das so machen kann?!? Ich sehe hier nicht nur das Datenmodell "flexibel", sondern auch "Logik". Was ich später machen kann, mach ich später. Deine Lösung war sicher gut durchdacht ;-).

"Fehleingaben sehen... na, dann kann man das umdrehen" - Und welche späteren Logiken?

Umdrehen? Ja klaro geht, aber welcher Aufwand entsteht?

Ich kann z.B. mit 2 weiteren Zeile + Klammern z.B.
state.errors++; in 101 und 111
gewünschte Funktion herstellen.

Was ist damit gemeint? Information nur im äußersten Notfall vernichten, denn es könnte eine Relevanz in einem nicht (durch)dachten Use-Case geben. Auch hier vorzeitige "Logik" vermeiden.

"In Aggregate liegt der EventStore" - "Verständlich" finde ich das leider nicht.

Ist okay - geht besser, aber ich wollte gerade kein Super-Duper sauberes Framework bauen, sondern das Prinzip in ein paar knappen Zeilen zeigen. Das geht sicher auch besser.

Jepp, deine Koordinaten sehen etwas anders aus. Ich war etwas Kreativ :) und habe mir in meinem kleinen "Hack" bei dir nichts angesehen. Kann sicher einfach umgebaut werden, da auch hier die "sortierbare" (aufsteigend) Koordinaten verwendet werden. Der Algo zur Lösung ist somit relativ stabil.

Der Rest orientiert sich weitestgehend an eurem - im übrigen echt coolen - Beispiel der TTT Kata. ;)

Was ich ebenfalls etwas kritisch finde ist, dass du dich etwas auf die Reihenfolge der Events in der Collection stützt. Hab ich noch nicht ganz durchdacht, aber macht mir (ES als Verallgemeinerung) etwas Bauchschmerzen. Kann aber auch egal sein... weiß ich gerade noch nicht ;).

Vielleicht versuche ich mich heute Abend an einer weiteren Iteration AI ;)

Greetz, Mike

Mike Bild hat gesagt…

@Ralf: "Reihenfolge der Events in der Collection" war falsch gesagt - Folge (streng monoton steigend) von Events ist ja richtig wichtig ;) - ich meine den unbedingten "Wechsel" von SpielerX und SpielerO Koordinate.

Egal, was gemeint war - ES ist nicht nur für "großes" CQRS/DDD gut geeignet, sondern auch für "kleines" feines Casual-Game ala TTT. ;)

Greetz, Mike

Ralf Westphal - One Man Think Tank hat gesagt…

Warum soll ich mich nicht auf die Reihenfolge der Events verlassen?

Es geht hier doch nicht um "the next enterprise software". ES wie jedes andere Mittel soll angemessen eingesetzt werden. Auch mit ES kann man übers Ziel hinausschießen, wenn man mehr Flexibilität als nötig einbaut.

Das zentrale Prinzip ist, Zustandsdifferenzen in zeitlicher Abfolge zu speichern, statt Zustand selbst.

Das tue ich. Mehr ist hier nicht nötig. Da darf ich alles mögliche auch als Randbedingung annehmen.

Mike Bild hat gesagt…

@Ralf:
"ES kann man übers Ziel hinausschießen, wenn man mehr Flexibilität als nötig einbaut".

Das von mir zur Anschauung implementierte ES ist wie es ist. Mehr ist da nicht. Keine Erweiterungen und sonstige Framework-Tricks verwendet. Pure JavaScript - sehr einfach. Nur eben etwas anders eingesetzt.

Von "Enterprise" kann bei 15 Zeilen Infrastruktur-Code (wohlgemerkt kein LINQ und so ein tolles Framework-Zeugs) sicher nicht die Rede sein. Und gerade das wollte ich durch vorerst fehlenden Bezug zu CQRS und DDD auch zeigen. Nix Enterprise, next Super-Duper Framework, verteilte Systeme und CAP Theorem. 15 Zeilen Pure JavaScript - mehr nicht.

Wenn es um Randbedingungen im stillen Kämmerchen geht, dann ist jede noch so naive sogenannte "Brownfield"-Lösung mit der Begründung "Randbedingungen" gut genug.

"Das zentrale Prinzip ist, Zustandsdifferenzen in zeitlicher Abfolge zu speichern, statt Zustand selbst."

Ja richtig, aber IMHO unvollständig. Ala - hier hast Du eine Idee. Wie das genau gehen kann, kannst du dir mal selbst überlegen.

Du hast in deinem TTT Beispiel mehr gezeigt - Super - selbiges hab ich mit meinem Beispiel und im Vergleich versucht.

Nun gut, so ist das fürs erste besprochen.

Grüße, Mike




Tilman hat gesagt…

Sehr interessant und bekommt meine gewogene Zustimmung. :-)
Taucht der Event Store nicht auch auf bei einem Taschenrechner? Der Baum einer Rechnung bestehend aus Operatoren und Operanden mit der Berücksichtigung der Präzedenz ist doch eine Art Event Store (Record(3), Record('+')). Wobei die Events hier tatsächlich mit denen der Button_Clicked Events zusammen fallen. Zumindest ließe sich so ein Taschenrechner über einen Event Store wahrscheinlich ganz gut bauen.

Mike Bild hat gesagt…

@ralf: Wenn du möchtest hier weitere Iteration

https://gist.github.com/MikeBild/e10f1c3e90ce4d17022a

Game-Logic und ES aus "only Client-Side" Single Page App wandert per Copy & Paste auf die Node.JS Server-Side. Kleine handgemachte WebAPI als Interface dazwischen - fertig.

@Tilman: Ja auch eine coole Idee. Events werden zu einem Taschenrechner-AST (Graphen) projiziert und dann das Ergebnis berechnet. Vielleicht gleich als nächstes Demo Projekt ;).

Greetz, Mike

Ralf Westphal - One Man Think Tank hat gesagt…

@Tilman: Klar, einen Taschenrechner kann man auch mit ES realisieren.

Und dann kann man überlegen, ob ein AST gebaut werden muss oder nicht. Eben nur bei Bedarf und JIT.

Stellt sich halt die Frage, was Events sind. Ist jede Ziffer ein Event, wird ein Event nur generiert, wenn ein Operator geklickt wird...?

Christof Konstantinopoulos hat gesagt…

Ein Vorteil wurde noch nicht angesprochen.

Durch einen Event Store bekommt man quasi automatisch eine unbegrenzte Undo Funktion geschenkt!

Genau auf solch einer Struktur bauen wir gerade eine größere Anwendung. Alle(!) Daten werden durch Events erstellt oder geändert. Statt der Daten werden diese Events persistiert. Die Daten liegen nur als Cache im Speicher.

Wenn man nun eine Aktion (eine Datenänderung) zurück nehmen möchte, muss nur der letzte Event aus der Liste gelöscht werden. Dann baut man den Zustand (den Cache)von vorne wieder zusammen, in dem man einfach alle Events der Liste erneut ausführt.

Auch mehrere Schritte zurück sind so kein Problem.

Viele Grüße,
Christof Konstantinopoulos

Marco Heimeshoff hat gesagt…

Ich bin begeistert.
Hätte nicht Gedacht, das ein weiterer Artikel über Event Sourcing die Welt noch hätte bereichern können, aber du bringst es, toll zu lesen und sehr verständlich, auf den Punkt.
Ich hoffe, dass dieser Blogpost die Skeptiker erneut zum Nachdenken und vielleicht sogar zum Umschwenken motiviert.

Tilman hat gesagt…

Wo liegen dann eigentlich die Vorbehalte gegen Event Stores?

Einer könnte sein, dass die Daten sehr feingranular erodieren können. Heißt: Bei einer klassischen Datenbank wird der Zustand gespeichert. Wird ein Datensatz aus Versehen gelöscht, ist der ganze Zustand weg. Unter Umständen gibt es dann natürlich auch relationale Diskrepanzen. Aber jeder Zustand, der gespeichert ist, ist auch korrekt.

Wird bei einem Event Store ein Datensatz (=Event) gelöscht, wird in der Aggregation der Zustand minimal verändert. Das mag weniger Auswirkungen haben als in dem Fall mit der relationalen Datenbank. Trotzdem hinterlässt das ein unangenehmes Gefühl.

Trotzdem sind die Möglichkeiten, die ein solches Pattern mit sich bringt, gigantisch. Die Undo-/Redo-Funktion wurde schon genannt. Die gesamte Historie eines "Zustands" gibt es für lau.

Mike Bild hat gesagt…

@Christof:

Löschen!!! ist wie gesagt !!!keine gute Idee!!!. Das führt zur Informationsvernichtung. Ich kann aber nur einen bestimmten Teil des Event-Streams z.B. n-1 durch zur Projektion verwenden. Dann hätte ich einen Zustand ohne die letzt Aktion.

@Tilman @Ralf: Ein ganz einfacher Taschenrechner ohne operator precedence geht natürlich sehr sehr einfach. z.B.

div(5,sub(5,mul(8, add(6,init(0))))) ->

init0
add6
mul8
sub5
div5

Immer schon "ausrechnen" nach jedem "Schritt" f(state, event) -> state - fertig.

Mike Bild hat gesagt…

@Tilman:

Achso ... IMHO macht ES bei bereits gut strukturierten Daten (z.B. Stammdaten / hoch verdichteten Daten) oder Nutzen ohne Historie keinen so großen Sinn. Hier können die Argumente nicht so gut greifen. Zumal Struktur (RDB, OO, usw.) ja auch Ihre großen Vorteile hat. "Alles Einheitlich" ist auch hier wieder... Wer einen Hammer hat, sieht in allem einen Nagel. ;-)



Christof Konstantinopoulos hat gesagt…

@Mike: Löschen ist nicht per se böse. Es kommt auf den Anwendungsfall an. Bei uns sollen die Daten wirklich gelöscht werden.
Die Informationsvernichtung ist dabei gewollt.

Wie würdest Du die Projektion der Liste machen, wenn nach dem Undo neue Events dazu kommen?

Die Liste von 0 bis n-1 ist ja nur der Bereich, der nach einem Undo gültig ist.

Wenn nun zwei neue Events dazu kommen, müsste der Bereich aus zwei getrennten Listen [0..n-3; n-1..n] zusammen gesetzt werden. [n-2] wäre dabei das durch Undo entfernte, nicht mehr sichtbare Event.

Das würde bei jedem Undo immer unübersichtlicher. Und Du müsstest diese Meta Information zur Liste zusätzlich speichern. Der Event Store wäre dann in sich selbst nicht mehr verständlich.

Ich denke, dass durch das "nicht löschen" zu viel Datenrauschen entstehen kann. Ich glaube nicht, dass es sinnvoll ist Daten zu sammeln, weil man sie irgendwann mal brauchen könnte.

Aber wie gesagt, das hängt vom Anwendungsfall ab.

Viele Grüße,
Christof

Mike Bild hat gesagt…

@Christof: ohne zu sehr ins Detail zu gehen. Wenn kein Append-only-Modell für dich in Frage kommt - warum nicht einfach ne Liste ;-) ... Hammer-Nagel Problem ... Die Projektion darauf kannste ja auch machen. Bei ES geht es eben genau darum, nix zu ändern. Der Grund ist, wo nichts geändert wird (Entität) gibt's keine Kollisionen.

Greetz, Mike

Jürgen hat gesagt…

@Cristof:
Das löschen eines Events ist doch auch nur ein Event, das entsprechend behandelt werden muss.

So kommt es zu keinerlei Daten bzw. Event verlust.