Work in Progress
Diese Seite ist aktuell im Review! Die Seite wurde noch nicht qualitätsgesichert und kann Fehler enthalten.
Die verlinkten Seiten sind ggf. nur für Schleupen-Mitarbeiter sichtbar.

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
Cookie Consent mit Real Cookie Banner