Das Rollenmuster ist ein recht interessantes Muster über das man Bereiche sinnvoll verknüpfen kann, bzw. erweitern kann. Wenn man ein Unternehmen abbilden möchte, wäre es z.B. sinnvoll, dass man eine Person zu einem Mitarbeiter machen kann. Eine Person hat einen Namen und eine Anschrift, ein Mitarbeiter eine Personalnummer und einen Angestelltenzeitraum, sowie eine Jobbeschreibung, usw. Dieses Dokument basiert auf einem Artikel von Martin Fowler aus 1997.
Eine Möglichkeit in einer einfachen Domäne, die ausschließlich mit Mitarbeitern arbeitet, wäre es, der Person einfach die Merkmale1) eines Mitarbeiters zu geben. Eine Person ist in dem Fall immer gleichzeitig ein Mitarbeiter.
Wenn das aber nicht der Fall sein soll, und eine Person ein Mitarbeiter sein kann, wäre es eventuell sinnvoll, den Mitarbeiter getrennt von der Person zu haben, und eine Referenz zwischen einer Person und einem Mitarbeiter herzustellen, um zu verdeutlichen, dass ein bestimmter Mitarbeiter eine bestimmte Person ist und anders herum.
Man müsste sicherstellen, dass die Referenz eindeutig ist. Trotzdem ist eine Referenz ein recht schwache Aussage über die Beziehung von der Person zum Mitarbeiter. Eine Referenz sagt aus, dass die Person einen Mitarbeiter hat/besitzt, oder anders herum, einem Mitarbeiter gehört. Nicht aber, dass der Mitarbeiter die Person ist2).
Eine Möglichkeit wäre es somit, den Mitarbeiter von der Person abzuleiten, in der Form
public class Person { public Guid Id {get; set;} public string Name {get; set;} } public class Employe: Person { public string EmployeeKey {get; set;} } public class Contact: Person { public Debtor Customer {get; set;} }
In Sprachen mit Einfachvererbung könnte eine Person auf diesem Weg aber nicht gleichzeitig ein Mitarbeiter und ein Kundenkontakt sein. Das ist in vielen Fällen zu einschränkend, da eine Person durchaus mehrere Rollen einnehmen kann.
Das ist der springende Punkt. Eine Person kann Rollen einnehmen. Daher soll das auch so modelliert werden:
public abstract class PersonRole { public Person Person {get; set;} } public class Person { public Guid Id {get; set;} public string Name {get; set;} private readonly IDictionary<Type, PersonRole> Roles = new Dictionary<Type, PersonRole>(); public PersonRole GetRole(Type roleType) { if (Roles.ContainsKey(roleType)) return Roles[roleType]; throw new PersonRoleInterfaceNotImplementedException(this.Id, roleType.ToString()); } } public class Employe: PersonRole { public string EmployeeKey {get; set;} } public class Contact: PersonRole { public Debtor Customer {get; set;} }
So kann eine Person mehrere Rollen erhalten, sie kann gleichzeitig die Rolle eines Mitarbeiters einnehmen, einer Kontaktperson, oder eine beliebige andere Rolle. Referenziert wird immer die Person, und über Person.GetRole(typeof(Employee)) wird die Person in der Rolle als Mitarbeiter angesprochen. Nochmal, weil es so wichtig ist: Referenziert wird immer die Person. Das ist wichtig, denn das spiegelt wieder, was modelliert werden soll. Die Person ist ja der Mitarbeiter, und wenn ich ihn in als Mitarbeiter brauche, und nicht als Kumpel, dann spreche ich ihn in seiner Rolle als Mitarbeiter an. Wenn ich einen Mitarbeiter einem Projekt zuordne, dann wird die Person zum Projektmitarbeiter, d.h. die Person wird auch referenziert. Alle Eigenschaften und Verhaltensweisen der Person die das Projekt benötigt erhält man, indem man die Person in der Rolle Mitarbeiter verwendet.
In DDD ist eine Hauptelement das Aggregate. Eine Strukturierungsmethode sind Bounded Contexts. Es wäre in DDD sinnvoll, die Person und den Mitarbeiter in unterschieliche Bounded Contexts zu packen, denn wenn man von jemanden als Person spricht, meint man oft was anderes, als wenn man im Kontext eines Mitarbeiterdaseins von der Person spricht.
Das ist eine sehr strikte Trennung. Man könnte keine Einschränkungen definieren, die für eine Person aus Sicht des Mitarbeiters gelten, d.h. die Regeln die Person betreffend und den Mitarbeiter betreffend sind völlig getrennt von einander. Man müßte die Regeln mit eventual consistency umsetzen, wobei die Applikationsschicht für die Einhaltung und Ausgleichssteuerung verantwortlich wäre. Zudem wäre ein Mitarbeiter ein komplett eigenständiges Object, ein eigenes Aggregate neben der Person3).
Der Vorteil dieser Trennung wäre vor allem, dass die Person sich nicht ändern müsste, wenn man entscheidet, dass sie auch als Mitarbeiter fungieren kann. Das macht das System wartbar, weil der Mitarbeiterkontext unabhängig vom Personenkontext von einem selbst oder sogar von getrennt voneinander arbeitenden Teams entwickelt werden kann. Das System ist ebenfalls erweiterbar, und zwar in Richtungen, die man Anfangs vielleicht gar nicht bedacht hat.
Die Beziehung von der Person zum Mitarbeiter ist down-stream. Die Person weiß nichts vom Mitarbeiter und ist auch nicht vom Verhalten des Mitarbeiters abhängig. Der Mitarbeiter kann aber durchaus von der Person abhängen. Wenn ein Mitarbeiter beauftragt wird, etwas zu tun, benötigt er eventuell Eigenschaften der Person, um diesen Auftrag auszuführen. Es gibt diverse Wege, die Personeneigenschaften in den Mitarbeiter einfließen zu lassen
Punkt 4. ist im Grunde die Zusammenfassung der Punkte 1. bis 3., denn eine Schnittstelle ist am flexibelsten. Wenn die Person und der Mitarbeiter nicht auf demselben Server existieren, kann 1. nicht verwendet werden, wenn sie es doch tun, wäre 1. die effektivste Methode. Für Punkt 2. gilt ähnliches, nur dass bei Verwendung von CQRS das Personen-Query-Object auch verteilt werden könnte. Wenn man aber die Schnittstelle aus Punkt 4. umsetzt, kann die Erfüllung der Schnittstelle Systemarchitekturabhängig optimiert werden, und mal direkt, mal indirekt über einen Remote-Dienstaufruf implementiert werden.
Das klingt alles sehr nach einer schwachen Verknüpfung vom Mitarbeiter zu der Person. Ist es im Grunde auch! Um daraus eine explizite ist-Beziehung zu machen, und auch um diese Beziehung eindeutig zu machen, wird definiert, dass der Mitarbeiter die Identität5) der Person verwendet. Bei einer hat/kennt-Beziehung würde der Mitarbeiter eine eigene ID haben und die ID der Person referenzieren. Bei einer ist-Beziehung6) wird die ID übernommen.
Zugegeben, das ist keine so starke Beziehung wie eine tatsächliche Rolle, und mann kann nicht über Employee.Person die Person erhalten. In DDD ist aber ein wichtiger Aspekt die sinnvolle Aufteilung von Objekten in bestimmte Aufgabenbereiche, um die einzelnen Bereiche überschaubar zu halten. Wenn man nun weiß, dass der Mitarbeiter eine Rolle einer Person darstellt, kann man die Mitarbeiter-ID verwenden, um Methodenaufrufe direkt an die Person zu senden. Somit ist die geteilte Identität als explizite Definition einer Objekt-Rollenbeziehung eine flexible und starke Beziehung, die das Rollenmuster sinnvoll wiedergibt.
Ein weiterer Punkt ist der, dass ein Mitarbeiter ohne eine Person wenig Sinn ergibt. Bei einer echten Rollenbeziehung mit dem Mitarbeiter als Rolle einer Person ist die korrekte Beziehung sichergestellt. Bei der losen Beziehung nicht. Die Applikationsschicht muss an dieser Stelle die Beziehung sicher stellen. Wenn sie einen Befehl erhält, einen Mitarbeiter anzulegen, muss sie erst prüfen, ob es die Person bereits gibt. Erst dann legt sie den Mitarbeiter an, ansonsten wirft sie einen Fehler.
Es gibt sicherlich Wege und Methoden, eine stärkere Beziehung herzustellen, die auch in der Domäne verankert ist und nicht in der Applikationsschicht. Der Konstruktor eines Mitarbeiters könnte z.B. eine Person verlangen. So wird die Applikationsschicht dazu gezwungen, die Person vorher zu laden. Dafür müssen die beiden Domänen aber auch einen gemeinsamen Prozessraum haben, was nicht immer der Fall sein muss.
Man darf aber nicht vergessen, dass alle Modelle nur Abstraktionen sind. Zudem ist die Applikationsschicht nicht unwichtiger als die Domänenschicht. Sie nimmt die Bausteine der Domänen und macht daraus eine Anwendung. Objekte und deren Rollen zusammen zu führen, z.B. über Prüfungen, ob ein Objekt für eine Rolle überhaupt vorhanden ist, kann durchaus eine Aufgabe der Applikationsschicht sein.