EntityId
Bei der Verwendung von Aggregaten werden (je nach Implementierungsansatz) Ids für die Verknüpfung von Aggregaten verwendet. Im Standardfall von CS 3.0 sind diese vom Typ System.Guid
. Um ein besseres Code-Verständnis und eine bessere Lesbarkeit, bessere Navigierbarkeit und Typisierung zu erhalten, wird im DDD mit ValueObjects gearbeitet. Dies bietet sich auch für Ids an. Diese Muster folgt somit den Vorschlägen von Vaughn Vernon: https://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_2.pdf (siehe auch https://buildplease.com/pages/vo-ids/, https://vaadin.com/learn/tutorials/ddd/tactical_domain_driven_design)
Desing
Für eine Entität, die eine typisierte Id verwenden soll, wird in der Domäne eine Klasse <EntityName>Id
erstellt, die eine Guid
kapselt. Die Entität selber implementiert die Schnittstelle IEntity
(ggf. unter Zuhilfenahme einer Basisklasse).
Zur Anbindung an die Persistenz muss das Repository von der Basisklasse Repository
abgeleitet werden, und zur Anbindung der EntityId
an die ClassMap<>
die Hilfsklasse EntityIdUserType
verwendet werden.
Implementierung
Zunächst muss die BuchId
durch Ableitung von EntityId
erstellt werden:
public sealed class BuchId : EntityId { public static BuchId Empty = new BuchId(Guid.Empty); public static BuchId New() => new BuchId(Guid.NewGuid()); public BuchId(Guid id) : base(id) { } }
Anschließend muss die Entität mit der Id versehen werden. Am einfachsten geht dies unter Verwendung der Basisklasse Entity
- alternativ kann auch die Schnittstelle IEntity
direkt implementiert werden.
[AggregateRoot] public partial class Buch : Entity<BuchId> { public Buch(BuchId id, string titel, string autor, DateTimeOffset erscheinungsdatum, Isbn isbn) : base(id) { } // ... }
Nun muss der Anschluss an die Persistenz erfolgen. Hierzu muss das Repository von Repository<TAggregateRoot, TKey>
abgeleitet werden!
public sealed class BuchRepository : Repository<Buch, BuchId>, IBuchRepository { // ... }
Der Anschluss an die Persistenz ist fertig, wenn die Id in der ClassMap<>
entsprechend gemappt wird.
Anschluss in Repositories
Manuell Anbindung
Hierzu muss der EntityIdUserType<>
verwendet werden.
public sealed class BuchMap : ClassMap<Buch> { public BuchMap() { // ... Id(x => x.Id).CustomType<EntityIdUserType<BuchId>>(); // Anschluss } }
Konventionsbasierte Anbindung (1st Edition)
public class BuchRepositoryConfigurator : NHibernateConfigurator, IBuchRepositoryConfigurator { // ... protected override IEnumerable<Type> ConventionTypes { get { List<Type> conventionTypes = new List<Type>(); conventionTypes.Add(typeof(EntityIdConvention)); // <-- bereitgestellt Konvention conventionTypes.AddRange(base.ConventionTypes); return conventionTypes; } } }
Konventionsbasierte Anbindung (2nd Edition)
public class BuchRepositoryMappingsConfigurator : INHibernateMappingsConfigurator { // ... public IEnumerable<Type> ConventionTypes { get { List<Type> conventionTypes = new List<Type>(); conventionTypes.Add(typeof(EntityIdConvention)); // <-- bereitgestellt Konvention return conventionTypes; } } }
Chronologischen Daten arbeiten intern mit Guid
's. Daher müssen die entsprechenden Typen die IEntity-Schnittstellen
implementieren. Dies macht man bestenfalls explizit:
public sealed class BuchId : EntityId, IEntity // IEntity wegen chronologischer Daten { // ... public BuchId(Guid id) : base(id) { } Guid IEntity.Id => base.Value; }
Analoges gilt für den ChangeLogAggregateEventBuilder
- auch hier ist einfach die Schnittstelle explizit zu implementieren.
Aggregatübergreifende Referenzierung
In Fall der Aggregat-übergreifenden Referenzierung erfolgt diese in der Domäne wie in 3.0: Aggregate (Aggregate, Linkage) beschrieben.
Für den Persistenzanschluss im Repository kann wiederum einfach der UserType EntityIdUserType<BuchId>
verwendet werden:
public class AusleiheMap : ClassMap<Ausleihe> { public AusleiheMap() { // ... Map(x => ((IAusleiheData)x).Buch).Column("Buch_Id").CustomType<EntityIdUserType<BuchId>>(); } }
Stärken und Schwächen
Stärken
- Starke Typisierung
- Lesbarkeit des Codes
Schwächen
- Implementierungsaufwand