User Tools

Site Tools


patterns:inheritancerolestragegyoverview:inheritance

Differences

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

Link to this comparison view

patterns:inheritancerolestragegyoverview:inheritance [2013/01/07 21:58]
rtavassoli
patterns:inheritancerolestragegyoverview:inheritance [2013/01/08 16:29] (current)
rtavassoli
Line 2: Line 2:
 {{ :patterns:inheritancerolestragegyoverview:inheritance.png |}} {{ :patterns:inheritancerolestragegyoverview:inheritance.png |}}
 ===== Implementierung ===== ===== Implementierung =====
 +==== Authentication BC ====
   * UserAccount und SystemAccount werden beide von Account abgeleitet,   * UserAccount und SystemAccount werden beide von Account abgeleitet,
   * In Account gibt es einen abstrakten Konstruktor der in den Kindklassen implementiert werden muss. Das Systemkonto wird z.B. eine Beschreibung erhalten((z.B. "Outlook-Schnittstellenkonto")), und das Personenkonto eine PersonID,   * In Account gibt es einen abstrakten Konstruktor der in den Kindklassen implementiert werden muss. Das Systemkonto wird z.B. eine Beschreibung erhalten((z.B. "Outlook-Schnittstellenkonto")), und das Personenkonto eine PersonID,
   * Es gibt das abstrakte Ereignis AccountCreated, welches nur die ID des Kontos beinhaltet. UserAccount wird im Konstruktor das UserAccountCreated:AccountCreated Ereignis melden, welches neben der ID des Kontos die PersonID beinhalten wird. Entsprechendes gilt für SystemAccount oder neue Kontenarten,   * Es gibt das abstrakte Ereignis AccountCreated, welches nur die ID des Kontos beinhaltet. UserAccount wird im Konstruktor das UserAccountCreated:AccountCreated Ereignis melden, welches neben der ID des Kontos die PersonID beinhalten wird. Entsprechendes gilt für SystemAccount oder neue Kontenarten,
   * Auf der Befehlsseite wird es den abstrakten Befehl CreateAccount geben. Für UserAccount wird es CreateUserAccount:CreateAccount geben, entsprechendes gilt für SystemAccount,   * Auf der Befehlsseite wird es den abstrakten Befehl CreateAccount geben. Für UserAccount wird es CreateUserAccount:CreateAccount geben, entsprechendes gilt für SystemAccount,
-  * Im Command Handler wird //Service Locator// verwendet, um die korrekte //Factory// für die entsprechenden Implementierungen von CreateAccount zu ermitteln, +  * Im Command Handler wird //Service Locator// verwendet, um die korrekte //Factory// für die entsprechenden Implementierungen von CreateAccount zu ermitteln 
-  * Die //Projektionsseite// ist nun tatsächlich ein wenig komplexerProjektionen werden dadurch erzeugtdass Ereignisse behandelt werden, und aus diesen Ereignissen Sichten erstellt werden, die abgefragt werden. Die Sichten werden dann in Data Transfer Objects((DTO)) gewandeltdie dann an andere Systeme((vorwiegend an das UI)) gesendet werden können. Die anderen Systeme müssen nun mit den DTOs arbeiten können. Die Komplikation liegt darin, dass jede neue Kontoart neue Ereignisse, DTOs und Anforderungen and die Sichten einführt. Das wird am besten hier besprochen: +==== Authentication Info BC ==== 
-===== Konsequenzen ===== +Der Authentication BC ist dafür da, die Domäne der Authentifizierung abzubildenEs gibt dazu fast immer noch den Aspekt der Darstellung. **Gemeinsame** Eigenschaften des Kontosdie mit der Geschäftslogik nichts zu tun haben, und nicht vom Anwender eingegeben werden, sondern ermittelt werden müssen((eventuell aus anderen BCs)) erhalten am besten einen eigenen Kontext. Es ist sinnvoll die Eigenschaften explizit zu machendenn wenn sie implizit bleiben, sind wird schnell wieder bei der dritten Normalform((3NF)), etwas wovon wir mit DDD/CQRS/ES weg wollten((Man müsste Eigenschaften referenzieren, eventuell sogar aus anderen BCs)).
-//Ich//((ein Event Handler)) bin an der Erstellung von Konten interessiert, will wissen, wann ein Konto erstellt wird. Ich abonniere somit das AccountCreated Ereignis. Da UserAccountCreated und SystemAccountCreated von AccountCreated abgeleitet sind, würden beide von mir behandelt werden. Wenn eine neue Kontoart mit einem neuen Erzeugungsereignis dazu kommt, wird mir das auch automatisch zum Behandeln übergeben.+
 \\ \\ \\ \\
-In diesem Fall sollte ich nicht fest auf die vorhandenen [User/Sytem]AccountCreated reagierensondern auf die Basisklasse AccountCreated. Ansonsten würde ich Ereignisse eines neu dazu gekommenen Kontos nicht behandeln, und die Systemerweiterbarkeit ist nicht gegebenAngenommen, ich möchte eine Liste aller Konten darstellenmit der Bezeichnung der Kontoart und der Kontobezeichnung. Für Systemkonten ist das "Systemkonto"((wenn die Liste in Deutsch dargestellt werden soll)) plus die Bezeichnung des Kontos, z.B. "Outlook-Schnittstellenkonto", für Personenkonten "Benutzerkontosamt der PersonenbezeichnungDas ist schon nicht ganz so einfachweil die Kontoart in die Sprache des Anwenders übersetzt werden sollteund weil UserAccount nur die PersonID hält, nicht aber die Personenbezeichnung kenntUnd was, wenn neue Kontenarten dazu kommen?+Es wurde also identifiziertdass die Bezeichnung des Subjektes des Kontos im Authentication Info BC wichtig ist. Für ein Systemkonto möchte man gerne sehen//was// das Konto repräsentiert, z.B. "Konto für die Outlook-Schnittstelle". Für ein Benutzerkonto möchte man gerne sehen//wen// das Konto repräsentiert, z.B. "Max Mustermann". 
 +=== Umsetzung === 
 +Es gibt das Aggregate AccountInfo im Authentication Info BC. Es gibt den Befehl SetAccountInfo { IDSubjectDescription }. Es gibt das Ereignis AccountInfoSet { IDSubjectDescription }.
 \\ \\ \\ \\
-Wir haben somit identifiziert, welche Gemeinsamkeiten die unterschiedlichen Kontoarten besitzen sollen. Die Bezeichnung der Kontoart plus die Bezeichnung des Kontoinhabers. Welche Ereignisse führen nun dazudass sich die Bezeichnung des Kontoinhabers ändert? SystemAccountDescriptionChanged wäre eins. Was ist mit der Personenbezeichnung? Die Person eines UserAccounts ändert sich nieaber die Bezeichnung kann sich ändernentweder weil sie korrigiert wurde, oder weil die Person ihren Namen geändert hat((z.Bdurch Heirat)). Muss ich also wissen, dass ich auch auf PersonNameChanged Ereignisse lauschen muss? Das ginge ja noch, aber bei neuen Konten wäre das nicht mehr möglich+Für jede Kontoart, die von Account abgeleitet wirdsollte ein Handler gebaut werdender die AccountInfo aktuell hältBeim SystemAccount ist das einfachEin handler lauscht auf SystemAccountAdded und SystemAccountRenamed und veröffentlicht einfach SetAccountInfo mit der ID des Kontos und der Beschreibung des Systemkontos.  
-==== Push==== +---- 
-//Zusammenfassende Ereignisse für Projektionen von abstrakten Klassen//. Als erstes muss identifiziert werden, welche Gemeinsamkeiten für abstrakte Klassen existieren. Dann werden Ereignisse eingeführt, die Änderungen an diesen Gemeinsamkeiten melden. Diese Ereignisse werden ausgelöst durch +Hinweis: Denormalisierer, die Informationen aus Account und AccountInfo kombinierenmüssen darauf achten, dass sie SystemAccountAdded und AccountInfoSet nicht zwingend in der erwarteten Reihenfolge erhaltenAccountInfoSet kann durchaus vor SystemAccountAdded behandelt werden. In dem Fall kann man entweder wartenbis SystemAccountAdded erhalten wurdeund erst dann das Konto denormalisieren, oder man schreibt schon mal einen Datensatz mit der Konto-ID und der Bezeichnungalles andere bleibt leerDie DTOs müssen nur darauf vorbereitet seinleere Felder zu habenoder aber die Abfragen liefern nur Konten mit allen Feldernso dass die DTOs sicher sein können, dass sie immer vollständig gefüllt sind
-  * andere Ereignisse. Z.B. wird ein handler von PersonNameChanged eingebaut, der den neuen Namen meldetDieser handler behandelt ebenfalls UserAccountCreatedum zu wissen, zu welchem Benutzerkonto die Person gehört. Beim behandeln von PersonNameChanged wird also der neue Personenname gespeichert, das Konto((bzw. die Konten)) der Person werden ermittelt, und für jedes wird ein AccountDescriptionChanged(Guid AccountIDstring Description) Ereignis ausgelöstKeine Ahnung wie dieses Muster heißtes ist aber einfach zu verstehen und genauso einfach zu implementieren, +---- 
-  * regelmäßige Abfragen. Wenn Namensänderungen z.B. nicht über Ereignisse gemeldet werdenwürde man die Personenbezeichnungen regelmäßig abfragen. Wenn sich eine ändert, wird wie im ersten Fall dafür ein AccountDescriptionChanged Ereignis ausgelöst. +Wie fülle ich die Personeninformation beim Benutzerkonto? Das ist etwas schwieriger. Es muss einen Handler für Ereignisse aus dem Personenkontext und dem Authentifizierungskontext gebenund dieser Handler muss Daten cachen((in einer Datenbank)), weil er nicht weißin welcher Reihenfolge die Ereignisse kommen. Der Handler muss folgende beiden Tabellen vorhalten((OKwir sind hier bei 3NF. An irgend einer Stelle muss ja die Beziehung vom Benutzerkonto zur Person gehalten werden. Getrieben wird das aber von Ereignissen)) 
-Es können beliebig tiefe Ereignisketten gebildet werden. AccountDescriptionChanged könnte z.B. von einem weitern handler in ein weiteres Ereignis gewandelt werdenusw+   PersonDescription    ^^ 
-=== Das Push-Problem === +^ PersonID      ^ Description          ^ 
-Das Problem bei solche einer Push Lösung ist die, dass man es nur //per Konvention// von der Implementierung einer weiteren Account Klasse verlangen kann. Das ist aber immer noch besser als die Notwendigkeit, vorhandene Klassen für einen neue Account Klasse ändern zu müssen. Es geht ja darumein System erweitern zu können ohne vorhandene Systeme zu (zer)stören. Und genau das wäre damit erreicht. Zudem kann man die Konvention per Pull ein wenig formalisieren. +| 3F2504E0-4F89-11D3-9A0C-0305E82C3301    | Max Mustermann | 
-==== Pull ==== + 
-Account hat eine abstrakte Methode //GetSubjectDescription()//. Die muss von der geerbten Kindklasse implementiert werden; wieist der Kindklasse überlassen. SystemAccount würde einfach den Wert des Feldes //Description// raus geben. UserAccount würde entweder eine Projektion von der Person fragenoder die Person direkt befragen, wenn sie im selben Prozess laufen((In dem Beisiel ist UserAccount das Query-Objektwas entweder gemeinsam mit dem Command-Objekt existiert, oder getrennt, wenn CQRS umgesetzt wird))+   UserAccountPersonMap    ^^ 
-<code csharp> +^ UserAccountID      ^ PersonID          ^ 
-public class UserAccount: Account +| 64RT31G0-34UI-186E-Z896-RH84F8D7 | 3F2504E0-4F89-11D3-9A0C-0305E82C3301    | 
-{ + 
-    ... +Der Handler verarbeitet die drei Ereignisse UserAccountAdded, PersonAdded und PersonRenamed((wenn der Person BC mit Event Sourcing arbeitetWenn nicht, läuft das ähnlich, nur dass der Handler regelmäßig die Personenbezeichungen abfragt)). Die Workflows sind nun folgende((dran denken, der Handler serialisiert die Ereignisse, weil er ein singleton ist)): 
-    private IPersonQuery PersonQuery { get; set; } +== Behandlung von UserAccountAdded == 
-    public Guid PersonId { get; private set; } +{{ :patterns:inheritancerolestragegyoverview:useraccountadded.png |}
-    ... +== Behandlung von PersonAdded/PersonRenamed == 
-    public UserAccount(IPersonQuery personQuery) +{{ :patterns:inheritancerolestragegyoverview:personaddedrenamed.png |}} 
-    { +=== Besprechung === 
-        PersonQuery = personQuery; +  * Die AccountInfoSet Befehle müssen zwingend erfolgreich sein. Dafür sollte man ein Systemkonto einrichten, das die entsprechenden Rechte hatund keine Logik in dem AccountInfo Aggregate habenBei Fehlern wird der Handler von oben nicht weiter machen, und der Administrator sollte informiert werden, damit der Fehler behoben wird, 
-    } +  * Wenn ein neues Konto dazu kommt, sollte es einen Handler mitbringen, der die AccountInfo füllt. Ansonsten wäre die Info im UI immer leer((oder der Datensatz wird dort nicht angezeigt))Man kann den Handler aber auch selbst dazu bauen, wenn man entsprechende Kenntnisse von den verwendeten BCs hat
-    ... +  * Das Open-Closed Prinzip wird eingehalten. Der Authentifizierungskontext ist geschlossen was Änderungen angehtund offen für ErweiterungenDurch den Authentication Info BC wird auch die Projektionsseite bedient, 
-    public string GetDescription(+  Wenn die AccountInfoSet Befehle falsch erzeugt wurdenmuss der fehlerhafte Handler korrigiert werden, und es wird kompensiert - in dem Fall werden einfach neue SetAccountInfo Befehle gesendet
-    { +  * Wenn es zu einer Person viele Konten geben kann((oder zu einem Produkt viele Bestellungen))dann ist der Prozess Speicher- und ZeitintensivFür eine Änderung an der Personenbezeichnung müssten N Befehle gesendet werden, diese werden alle gespeichert und es werden N Domänenereignisse generiertZudem werden alle Personen und Konten im Handler gecacht. Es kann auch dauernbis alle N Befehle durchgelaufen sind. Man muss abwägenob man in bestimmten Fällen die Info nicht direkt in die Haupt-Domäne schreibtund Sichten auf aktuell verknüpfte Daten dann tatsächlich über Referenzen ermittelt. Wenn die Domäne aber erweiterbar istist das mit den Referenzen nicht so einfachund Optimierungen mit z.B. geteilten Datenbank Views würden bedeutendass man sich an anderer Stelle einschränkt. Es ist somit eine Fall-zu-Fall Entscheidung, 
-        return personQuery.Get(PersonId).GetDescription(); +  * Man darf sich nicht darauf verlassen, dass man nach AccountAdded auch AccountInfoSet erhältDas sind Ereignisse aus 2 Domänen, die durch die Applikationsschicht verknüpft werden
-    } +  * AccountAdded((und alle erweiterbaren Typen)) sollte den Typ des Kontos beinhalten((das sollte im Grunde automatisch passieren - wird in das Framework eingebaut))So kann dann jedes DTO den Typ beinhalten, z.B. PROM.Authentication.UserAccount. Anhand dieses Typs kann nun überall über //Service Locator// entsprechend mit dem Konto umgegangen werden. Das UI kann zum Bearbeiten den entsprechenden Controller laden, ebenso kann ein Übersetzer gefunden werden, der aus der Typenbezeichnung eine Bezeichnung in der Sprache des angemeldeten Users macht
-    ...    +=== Doppelt gemoppelt? Gefahr widersprüchlicher Daten? === 
-+Ist das nicht alles doppelt gemoppelt? Ich habe doch die Personenbezeichnung im Personenkontext, wozu nochmal in den AccountInfo Kontext schreiben? Aus mehreren Gründen 
-</code> +  * Die Bounded Contexts sollen einigermaßen autark sein. Wenn ich was im Authentifizierungskontext mache, möchte ich dort alles haben, was ihn betrifft((eventuell gemeinsam mit den diversen Info-Kontexten...)). 
-IPersonQuery kann ein Repository<Person> dahinter liegen haben, eine Abfrage, etc+  * Die Koppelung der Kontexte soll auf ein Minimum reduziert sein. Wenn die Referenzen nicht explizit aufgelöst werden, sondern implizit gehalten und jedesmal ermittelt werden müssen, sind die Systeme stark aneinander gebunden, 
-=== Das Pull-Problem === +  * Man könnte eine Sicht inklusive der Bezeichnung des Subjektes((was oder wen das Konto repräsentiert)) auf alle Konten haben, die dann von Handlern wie oben aufgezeigt gefüllt werden. Man bräuchte dazu keinen zusätzlichen BC und kein AccountInfo Aggregate. In dem Fall würde die Projektion((Sicht)) von allen weiteren Projektionen, die sich für die Bezeichnung interessieren, geteilt werden. Wenn es keine weiteren Projektionen gibt, wäre das ein einfacher und sinnvoller Weg. Wenn es aber mehrere Projektionen gibt, die den Bezeichner benötigen, und wenn man davon ausgehen kann, das weitere dazu kommen werden, ist eine Basisprojektion nicht mehr sinnvoll. Die anderen müssten Zugriff auf sie erhalten, sie ständig abfragen, und Änderungen an der Basisprojektion müssten über //Projektionsereignisse-/Meldungen// mitgeteilt werden. In dem Fall sollte man sich dazu entscheiden, das Ganze explizit zu machen und einen eigenen Kontext mit einer echten Domäne dafür zu bauen. 
-Immer wenn die Kontobezeichnung benötigt wirdkann das Konto direkt befragt werden. Das Problem besteht darinwann danach gefragt werden sollVor jeder Abfrage auf Daten, die die Kontobezeichnung benötigen? Wenn es 1.000 Konten gibt, und man eine Abfrage auf sie startet, müßten dann erst 1.000 Pullaktionen durchgeführt werden? Sinnvoller wäre ein regelmäßiges Abfragenoder eine Push-Pull Kombilösung. +Was ist mit der single-source-of-truth? Die wahre Personenbezeichnung der Person eines Benutzerkontos steht doch in den Personenereignissen. Wenn wir ihn jetzt nochmal in die Ereignisse des Authentication Info BCs schreiben, haben wir zwei mögliche unterschiedliche Bezeichnungen für ein und dieselbe Person. Dazu folgendes: 
-=== Push-Pull Kombilösung mit Event Enrichment - Beispiel UserAccount === +  * wir haben nicht zwei unterschiedliche Bezeichnungen für eine Person. Wir haben die Bezeichnung der Person im Personenkontext, und wir haben eine //Repräsentation// des Kontoinhabers im Benutzerkonto. Wie diese Repräsentation aufgebaut sein soll entscheidet das Benutzerkonto, nicht der Personenkontext. Dort kann alles mögliche stehen, es bedeutet nicht, dass die Person eine falsche Bezeichnung hat, sondern dass die Sicht des Benutzerkontos auf die Person eine andere ist, 
-  - Es gibt einen Denormalisierer im Personen Kontext, der die Personenbezeichnung aktualisiert und dann eine PersonNameChanged(Guid id, string newName) //Nachricht//((kein Ereignis, und auch kein Befehl. Es ist lediglich eine ....AHHHHHHHHHHHHHHHHHHHH das sind ja gar keine Ereignisse. Und was wenn doch? AccountSubjectDescriptionChanged oder PersonNameChanged als Ereignisse einführen? Übel!!!   sendetDie Reihenfolge ist hierbei äußerst wichtig+  * die Applikation stellt sicher, dass die Bezeichnung im Konto schließlich mit der Personenbezeichnung übereinstimmt. Das ist die Eventual Consistency, von der gesprochen wird. Das gilt natürlich nur, wenn die Systeme fehlerfrei laufen, aber auch Fehlerbehebung ist ein Teil von Eventual Consistency. 
-  - Es gibt einen Dienst im Personen Kontextder nach der Personenbezeichnung für eine PersonID gefragt werden kannDieser Dienst bezieht sich auf die Denormalisierung aus 1. +Was ist mit //Event Enrichment//. Man könnte die Ereignisse bereichern((z.B. mit dem //Decorator Pattern//)) anstatt eine neue Domäne zu bauen mit eigenen Ereignissen. Ich finde eine zweite Domäne wesentlich einfacher als Ereignisse zu bereichern. Das Abfragen der bereicherten/dekorierten Ereignisse nach ihrem Typ und den internen Eigenschaften der Basisklassen ist komplexer als eigene, zusätzliche Ereignisse. Zudem ist eine nachträgliche Bereicherung bereits vorhandener Ereignisse unmöglich. Info-Kontexte nachträglich dazu zu bauen und mit historischen Ereignissen zu befüllen ist aber möglich - und sehr einfach.
-  - Es gibt im UserAccount Kontext einen Handler, der auf PersonNameChanged Ereignisse lauscht, sowie auf UserAccountCreated. Wenn UserAccountCreated behandelt wirdzieht sich der Handler die Personenbezeichnung aus dem Dienst aus Punkt 2((es kann sein, dass die Person in der Projektion noch gar nicht existiertalso muss auch ein null-Bezeichner möglihch sein)). Darüber wird dann ein AccountSubjectDescriptionChanged Ereignis  +
-  - Es gibt im UserAccount Kontext einen Handler, der eine SAGA startet sobald ein UserAccount erzeugt wurdeDie Saga lauscht auf AccountSubjectDescriptionChanged und auf PersonNameChangedDerselbe Handler lädt dann aus dem Abfragedienst aus Punkt 2die Personenbezeichung und meldet UserAccount +
-  - Es gibt im UserAccount Kontext einen Denormalisiererder auf UserAccountCreated Ereignisse lauschtsowie auf die PersonNameChanged Ereignisse aus 1.  +
-   +
-Es muss **einen** Denormalisierer gebender auf Ereignisse die Personenbezeichnung betreffend wartetsowie auf Ereignissedie ein UserAccount anlegenWird ein UserAccount angelegtwird die PersonID gespeichertdamit der Denormalisierer weiß, dass die Bezeichnung dieser Person benötigt wirdDann wird gegucktob die Personenbezeichnung bereits vorhanden ist. Wenn jawird gleich hinterher ein AccountSubjectRenamed mit der Bezeichnung hinterher geschickt.  +
-\\ \\ +
-Der Handler für UserAccountCreated kann jetzt  +
-==== Service Locator ==== +
-Die Kontoartbezeichnung sollte einen Default haben, z.B. in DeutschJedes DTO für Account wird zudem den Typ beinhalten, also z.B. PROM.Authentication.UserAccount. Anhand dieses Typs kann nun überall über //Service Locator// entsprechend mit dem Konto umgegangen werden. Das UI kann zum Bearbeiten den entsprechenden Controller laden, ebenso kann ein Übersetzer gefunden werden, der aus der Typenbezeichnung eine Bezeichnung in der Sprache des angemeldeten Users macht.+
patterns/inheritancerolestragegyoverview/inheritance.1357592318.txt.gz · Last modified: 2013/01/07 21:58 by rtavassoli