User Tools

Site Tools


technology:es

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

technology:es [2013/01/09 21:00]
rtavassoli created
technology:es [2013/01/15 15:09] (current)
rtavassoli
Line 18: Line 18:
 Die Denormalisierer, die die Projektionen bauen, greifen veröffentlichte und gespeicherte Ereignisse ab, und erstellen daraus Listen. Es kann beliebig viele solcher Denormalisierer geben. Manch komplexe Liste kann aufwendig zu bauen sein. Damit das Speichern von Ereignissen nicht durch die Denormalisierer verlangsamt wird, werden erst die Ereignisse gespeichert, und dann in einem anderen Prozess((oder Thread)) von den Event Handlern denormalisiert. Die Denormalisierer, die die Projektionen bauen, greifen veröffentlichte und gespeicherte Ereignisse ab, und erstellen daraus Listen. Es kann beliebig viele solcher Denormalisierer geben. Manch komplexe Liste kann aufwendig zu bauen sein. Damit das Speichern von Ereignissen nicht durch die Denormalisierer verlangsamt wird, werden erst die Ereignisse gespeichert, und dann in einem anderen Prozess((oder Thread)) von den Event Handlern denormalisiert.
 \\ \\ \\ \\
-Das bedeutet nun, dass die denormalisierten Listen nicht transaktional mit den Ereignissen gespeichert werden. Wenn ich die Adresse einer Person ändere, und die Aktion erfolgreich durchgeführt wurde, kann es sein, dass die Adressliste, die ich gleich danach aufrufe, noch die alte Adresse anzeigt, weil die neue noch nicht vom Event Handler behandelt wurde. Die Änderung wird schlussendlich in der Liste landen, weil sie ja passiert ist, nur ist nicht klar, wie lange das dauert((i.d.R. ein paar Millisekunden, wenn der Denormalisierer aber gerade nicht am Start ist, vielleicht deutlich länger)). Das ganze läuft unter dem Namen //eventual consistency//((schlussendliche Konsistenz)).+Das bedeutet nun, dass die denormalisierten Listen nicht transaktional mit den Ereignissen gespeichert werden. Wenn ich die Adresse einer Person ändere, und die Aktion erfolgreich durchgeführt wurde, kann es sein, dass die Adressliste, die ich gleich danach aufrufe, noch die alte Adresse anzeigt, weil die neue noch nicht vom Event Handler behandelt wurde. Die Änderung wird schlussendlich in der Liste landen, weil sie ja passiert ist, nur ist nicht klar, wie lange das dauert((i.d.R. ein paar Millisekunden, wenn der Denormalisierer aber gerade nicht am Start ist, vielleicht deutlich länger)). Das ganze läuft unter dem Namen //[[technology:eventualconsistency|eventual consistency]]//((schlussendliche Konsistenz))
 + 
 +===== Verantwortungen beim Event Sourcing ===== 
 +Nach dem SRP((Single Responsibility Principal)) sollte eine Klasse genau eine Aufgabe haben. Abgeleitet davon wird es oft so definiert, dass es nur genau einen Grund geben sollte, warum man die Klasse ändern müsste.
 \\ \\ \\ \\
-Das ist ziemlich übelvor allem wenn der Anwender das anders gewohnt ist. Es kann dazu führen, dass der Anwender dem System nicht vertraut, die Änderung wieder und wieder angibtusw. Die am häufigsten genannte Lösung ist die, dass man den Anwender erziehen mussDas sehe ich nur bedingt soEine andere ist es, nach dem Ergebnis der letzten Aktion zu pollen((man gibt dem Befehl eine PollingId mitnach der man dann pollen kann))bis es verfügbar ist((denormalisiert wurde)). Ich löse es wie folgt: +Um dem SRP nahe zu kommen, sollten Aggregates klein gehalten werden, zumindest die darin enthaltenen Klassen. Welche Aufgaben hat ein Aggregate? U.a. die folgenden: 
-  * Da wo es gehtdenormalisiere ich die Ereignisse transaktional in einfache Listen. Die meisten UI-Sichten können aus diesen einfachen Listen traditionell über JOINs gebaut werden und sind somit immer auf dem aktuellsten Stand, +  * Businesslogik einhalten und Domänenereignisse erzeugen 
-  * Dawo das nicht möglich ist, vor allem in komplexen Reportswird nach dem Ergebnis des Befehls gepollt bevor das UI die Sanduhr abschaltet und die Datenaktion beendet. Wenn nun in eine Listenansicht gewechselt wird, beinhaltet die Liste die gerade gespeicherten ÄnderungenBricht das Pollen mit einem Timeout abwird das dem User mitgeteilt oder aber nicht, siehe nächsten Punkt, +Wenn ein Aggregate von einem Repository wiederhergestellt wird, hat es zudem noch einige Infrastruktur bezogenen Aufgaben. Was aber ist mit den Ereignissendie es erzeugt? Muss das Aggregate darauf achten, dass 
-  * Der Denormalisierer speichert zu jeder Liste das Datum und die Uhrzeit der letzten DenormalisierungDieses kann in der Liste angezeigt werden, z.B"Diese Liste enthält alle Änderungen die bis zum 09.01.2013 20:46:45 eingetragen wurden". Wenn der Anwender weißdass er um 20:50 eine Änderung eingetragen hatkann er die Liste so lange aktualisieren, bis die Listenaktualität mit dem letzten Änderungszeitpunkt übereinstimmtIn Reports sollte das genauso gemacht werden.+  * die Ereignisse serialisierbar sind? 
 +  * wichtige Eigenschaften in den Ereignissen sicher((verschlüsselt)) gespeichert werden sollen? 
 +Als Aggregate möchte ich z.B. das Ereignis PasswordChanged("meinpasswort") erzeugen. Es ist nicht meine Aufgabe, dass Passwort zu verschlüsseln. Oder ich möchte mitteilendass meine Authentifizierungsstrategie über AuthenticationStrategySet(new KerberosAuthentication("rta", "meinpasswort")) gesetzt wurde. Wie die KerberosAuthentication Strategie gespeichert wird, und dass das Kennwort dazu verschlüsselt wird, ist nicht die Aufgabe des Aggregatessondern des Speichermechanismus. 
 +\\ \\ 
 +Ich sehe hier zwei unterschiedliche VerantwortlichkeitenWenn der Ereignisstrom((Event Stream)) verschlüsselt werden soll, dann kann das vor dem speichern des JSON serialisierten Ereignisses werden sollenund nach dem laden können die Anwendungen mit dem entsprechenden Schlüssel die Ereignisse wieder entschlüsseln. So kann alles verschlüsselt gespeichert werden((auch denormalisierte Sichten könnten verschlüsselt werden, wobei die Abfragen darauf schwierig werden)). 
 +\\ \\ 
 +Zudem müssen die Daten in den Ereignissen, die vom Aggregate erzeugt werden, serialisierbar sein, und einige sind schützenswert, unabhängig davon, ob die Ereignisse komplett verschlüsselt werden oder nicht. Korrekt wäre es alsodass die Domäne Ereignisse erzeugt, diese dann von einem Adapter in andere, serialisierbare Ereignisse gewandelt werden, die dann gespeichert werden. Ein weiterer Adapter ändert die Ereignisse in solche, die auch serialisierbar und veröffentlichbar sind. 
 +\\ \\ 
 +Es wurde von einigen berichtetdass eine Trennung in Domain Events und Published Events versucht wurde, das Ganze aber ziemlich gegen die Wand gelaufen ist. In der Klassischen Datenbankanwendung gibt es diese Trennung ja auch nicht. Trotzdem sehe ich keinen anderen Weg als eine Trennung in DomainEvents und, nennen wir sie mal für jetzt //AggregateEvents//. Die Domäne erzeugt und konsumiert AggregateEvents. Nach Außen((Speicherung und veröffentlichung an die Handler)) werden diese aber in DomainEvents gewandelt. Wenn das Aggregate nach seinen Ereignissen gefragt wird, werden diese in DomainEvents gemappt((Hierfür ist nun AutoMapper super, weil in 99,9% der Fälle das eine 1-zu-1 Zuordnung sein wird)). Dabei können Passworte verschlüsselt werdenStrategien in //StrategyDTO// Objekte((besser //StrategyRepresentation//)) gewandelt werden, usw. Alle Handler dieser Ereignisse können damit was anfangen, sie sind ja extra dafür gebaut. 
 +\\ \\ 
 +Und wenn das Repository die DomainEvents lädt und zurück an das Aggregate übergibt, werden diese vorher zurück in AggregateEvents gewandeltKlar ist das mehr Arbeit, aber mit AutoMapper und copy/paste ist der zusätzliche Tippaufwand vernachlässigbar im Vergleich zu der Zeit, die man für ein vernünftiges Design und den Bau der Geschäftslogik aufwendet. Zudem kann die Domäne nun völlig befreit von irgendwelchen Speicheraufgaben bleiben, muss nicht darauf achten, serialisierbare Ereignisse zu erzeugen, kann völlig Objektorientiert Strategien als Eigenschaften von Ereignissen setzen, usw. Vor allem sind die Strategien((und auch Rollen, Status, und andere erweiterbare Aspekte)) über diesen Weg erweiterbar, das Open-Closed Prinzip wird dadurch untermauert. 
 +\\ \\ 
 +Teilt man die Ereignisse nicht derartwird man an diversen Stellen Hacks bauen müssendamit es klappt. Das mag anfangs pragmatisch wirken, wird aber schnell dazu führen, dass die Summe der Hacks aufwendiger wird, als einfach Ereignisse in zwei Formen zu bauen. Zudem wird die Nachvollziehbarkeit erschwert, weil man jeden Hack verstehen muss. Die Zweiteilung der Ereignisse muss man nur einmal verstehen. 
 +===== Aggregate Events ===== 
 +Angenommen es gibt für ein Konto eine Authentifizierungsstrategie. Diese ist erweiterbar. Für die DomainEvents braucht man für die Strategie eine Repräsentation 
 +<code csharp> 
 +[DataContract] 
 +public abstract class AuthenticationStrategyRepresentation { } 
 +</code> 
 +Eine Erweiterung kann nun eine konkrete Repräsentation hinzufügen((Ursprünglich dachte ich an folgendes - aber das geht nicht, weil die Strategie erst nach allen Aktionen in die Repräsentation gewandelt wird, und der UserName und das Passwort inzwischen verändert werden könnenD.h., dass Objekt Repräsentationen Parameterlos gespeichert werden müssen, also auch Parameterlose Konstruktore haben müssenFolgendes geht also nicht: 
 +&lt;code csharp> 
 +[DataContract] 
 +public abstract class UserNameAuthenticationStrategyRepresentation: AuthenticationStrategyRepresentation 
 +
 +   [DataMember] 
 +    public string UserName { get; private set; } 
 +    [DataMember] 
 +    public string CryptedPassword { get; private set; } 
 +    [DataMember] 
 +    public string IV { get; private set; } 
 +     
 +    public UserNameAuthenticationStrategyRepresentation(string userNamestring cryptedPassword, string iv) 
 +    { 
 +        UserName = userName; 
 +        CryptedPassword = cryptedPassword; 
 +        IV = iv; 
 +    } 
 +
 +</code>)) 
 +<code csharp> 
 +[DataContract] 
 +public abstract class UserNameAuthenticationStrategyRepresentationAuthenticationStrategyRepresentation { } 
 +</code> 
 +Das entsprechende DomainEvent, über das eine Änderung der Strategie mitgeteilt wirdsieht so aus 
 +<code csharp> 
 +[DataContract] 
 +public class AuthenticationStrategySet: DomainEvent 
 +
 +    [DataMember] 
 +    public AuthenticationStrategyRepresentation Strategy { get; private set; } 
 +     
 +    public AuthenticationStrategySet(AuthenticationStrategyRepresentation strategy) 
 +    { 
 +        Strategy = strategy; 
 +    } 
 +
 +</code> 
 +In der Domäne sieht die Entsprechnung wie folgt aus 
 +<code csharp> 
 +public abstract class AuthenticationStrategy 
 +
 +    public Boolean IsAuthenticated { get; private set; } 
 +    public abstract void Authenticate(Credentials credentials); // hier wird u.a. IsAuthenticated gesetzt 
 +     
 +    public AuthenticationStrategy() 
 +    { 
 +      IsAuthenticated = false; 
 +    } 
 +
 +</code> 
 +Eine Erweiterung kann nun eine konkrete Repräsentation hinzufügen 
 +===== Ahhh, klappt so nicht. wie soll die Strategy vom Command Handler erzeugt werden und an das Konto übergeben werden??????? So ein Mist ===== 
 + 
 +<code csharp> 
 +public abstract class UserNameAuthenticationStrategy: AuthenticationStrategy 
 +
 +    public Account Owner { get; private set; } 
 +    public string UserName { get; private set; } 
 +    public string Password { get; private set; } 
 +     
 +    public UserNameAuthenticationStrategy(Account owner, string userName, string password) 
 +      : base() 
 +    { 
 +        Owner = owner; // hierüber kann z.B. Owner.ApplyChange(AggregateEvent @event) aufgerufen werden, um Ereignisse zum Aggregate zu erzeugen 
 +        Owner.ApplyChange(new UserNameAuthenticationStrategyCredentialsSet(userName, password); // immer daran denken, dass jede Änderung des Zustands über Ereignisse gemacht werden muss! 
 +    } 
 +    public override Authenticate(Credentials credentials) 
 +    {         
 +        var c = credentials as UserNameCredentials; 
 +        // prüfen ob gesperrt, inaktiv, usw. 
 +        if (c == null) throw new IllegalArgumentException(); 
 +        IsAuthenticated = ((c.UserName == UserName) && (c.Password == Password)); 
 +        if (!IsAuthenticated) Owner.ApplyChange(new AccountCredentialsRejected()); // + Zähler hochsetzen und Konto eventuell sperren 
 +        else (Owner.ApplyChange(new AccountCredentialsAccepted()); // + Zähler auf 0 setzen 
 +    } 
 +
 +</code> 
 +Das entsprechende AggregateEvent, über das eine Änderung der Strategie mitgeteilt wird, sieht so aus 
 +<code csharp> 
 +public class AuthenticationStrategySet: AggregateEvent 
 +
 +    public AuthenticationStrategy Strategy { get; private set; } 
 +     
 +    public AuthenticationStrategySet(AuthenticationStrategy strategy) 
 +    { 
 +        Strategy = strategy; 
 +    } 
 +
 +</code> 
 +Im Konto passiert nun beim Setzen der Strategie folgendes 
 +<code csharp> 
 +public abstract class Account: AggregateRoot 
 +
 +    public AuthenticationStrategy Strategy { get; private set; } 
 +     
 +    public void SetAuthenticationStrategy(AuthenticationStrategy strategy) 
 +    { 
 +        ApplyChange(new AuthenticationStrategySet(strategy)); 
 +    } 
 +
 +</code>
technology/es.1357761614.txt.gz · Last modified: 2013/01/09 21:00 by rtavassoli