DDD: Domain Model Factory Design

8

Ich versuche zu verstehen, wie und wo Domänenmodellfabriken implementiert werden sollen. Ich habe mein CompanyAggregat als Demo aufgenommen, wie ich es gemacht habe.

Ich habe meine Entwurfsentscheidungen am Ende aufgenommen - ich würde mich über Kommentare, Vorschläge und Kritik zu diesen Punkten freuen.

Das CompanyDomain-Modell:

public class Company : DomainEntity, IAggregateRoot
{
    private string name;
    public string Name
    {
        get
        {
            return name;
        }
        private set
        {
            if (String.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentOutOfRangeException("Company name cannot be an empty value");
            }

            name = value;
        }
    }

    internal Company(int id, string name)
    {
        Name = name;
    }
}

Die CompanyFactoryDomain Factory:

Diese Klasse wird verwendet, um sicherzustellen, dass Geschäftsregeln und Invarianten beim Erstellen neuer Instanzen von Domänenmodellen nicht verletzt werden. Es würde sich in der Domänenschicht befinden.

public class CompanyFactory
{
    protected IIdentityFactory<int> IdentityFactory { get; set; }

    public CompanyFactory(IIdentityFactory<int> identityFactory)
    {
        IdentityFactory = identityFactory;
    }

    public Company CreateNew(string name)
    {
        var id = IdentityFactory.GenerateIdentity();

        return new Company(id, name);
    }

    public Company CreateExisting(int id, string name)
    {
        return new Company(id, name);
    }
}

Der CompanyMapperEntity Mapper:

Diese Klasse wird verwendet, um Rich-Domain-Modelle und Entity Framework-Datenentitäten zuzuordnen. Es würde sich in Infrastrukturschichten befinden.

public class CompanyMapper : IEntityMapper<Company, CompanyTable>
{
    private CompanyFactory factory;

    public CompanyMapper(CompanyFactory companyFactory)
    {
        factory = companyFactory;
    }

    public Company MapFrom(CompanyTable dataEntity)
    {
        return DomainEntityFactory.CreateExisting(dataEntity.Id, dataEntity.Name);
    }

    public CompanyTable MapFrom(Company domainEntity)
    {
        return new CompanyTable()
        {
            Id = domainEntity.Id,
            Name = domainEntity.Name
        };
    }
}
  1. Der CompanyKonstruktor wird als deklariert internal.
    Grund: Nur die Factory sollte diesen Konstruktor aufrufen. internalstellt sicher, dass keine anderen Ebenen es instanziieren können (Ebenen werden durch VS-Projekte getrennt).

  2. Die CompanyFactory.CreateNew(string name)Methode wird beim Erstellen einer neuen Firma im System verwendet.
    Grund: Da es noch nicht beibehalten worden wäre, muss eine neue eindeutige Identität dafür generiert werden (unter Verwendung der IIdentityFactory).

  3. Die CompanyFactory.CreateExisting(int id, string name)Methode wird CompanyRepositorybeim Abrufen von Elementen aus der Datenbank verwendet.
    Grund: Das Modell hätte bereits eine Identität, daher müsste diese an das Werk geliefert werden.

  4. Das CompanyMapper.MapFrom(CompanyTable dataEntity)wird von verwendet, CompanyRepositorywenn Daten aus der Persistenz abgerufen werden.
    Grund: Hier müssen Entity Framework-Datenentitäten Domänenmodellen zugeordnet werden. Das CompanyFactorywird verwendet, um das Domänenmodell zu erstellen, um sicherzustellen, dass die Geschäftsregeln erfüllt werden.

  5. Das CompanyMapper.MapFrom(Company domainEntity)wird von verwendet, CompanyRepositorywenn Modelle zur Persistenz hinzugefügt oder aktualisiert werden.
    Grund: Domänenmodelle müssen direkt auf Datenentitätseigenschaften abgebildet werden, damit Entity Framework erkennen kann, welche Änderungen in der Datenbank vorgenommen werden müssen.

Vielen Dank

Dave New
quelle

Antworten:

2

Eines gefällt mir an Ihrem Design nicht. Und das ist eine Tatsache, dass Sie 3 zusätzliche Klassen für jeden aggregierten Stamm (Factory, Mapper, Repository) mit halbdupliziertem Code in Form von Lese- und Festlegen von Eigenschaften überall haben werden. Dies ist beim Hinzufügen und Entfernen von Eigenschaften problematisch, da das Vergessen, eine einzelne Methode zu ändern, zu einem Fehler führen kann. Und je komplexer die Domänenentität ist, desto mehr von diesem doppelten Code ist. Ich möchte nicht sehen, CompanyFactory.CreateExistingwann das Unternehmen insgesamt 10 Immobilien und 2 Unternehmen hat.

Das zweite, worüber ich mich beschweren könnte, ist das IdentityFactory. In DDD sollte die Identität entweder mit der Domäne verknüpft sein. In diesem Fall setzen Sie sie auf einen berechneten Wert. Oder es ist transparent. In diesem Fall kann sich die DB darum kümmern. Das Hinzufügen einer Art Fabrik für Identität ist IMO-Überentwicklung.

Das Mapping gefällt mir auch nicht. Ich bin damit einverstanden, dass EntityFramework möglicherweise nicht direkt verwendet werden kann, aber Sie könnten es zumindest versuchen. EF wird mit POCO in letzter Zeit immer besser und Sie werden überrascht sein, wie leistungsfähig es ist. Aber es ist immer noch nicht NHibernate. Wenn Sie wirklich separate Modelle erstellen möchten, sollten Sie über die Verwendung einer Auto-Mapping-Bibliothek nachdenken.

Es ist schön, dass Sie das Design in einer einfachen Klasse testen, aber versuchen Sie auch, das Design zu testen oder sich zumindest vorzustellen, wie das Design mit Dutzenden von Aggregaten mit vielen Eigenschaften und Entitäten funktioniert. Schreiben Sie auch einen Code, der dieses Design verwendet. Möglicherweise stellen Sie fest, dass das Design von innen zwar gut aussieht, von außen jedoch umständlich zu verwenden ist. Außerdem wird Ihnen hier niemand sagen, ob das Design für Ihr Team und Ihre Domain geeignet ist.

Euphorisch
quelle
Danke für deine Antwort. Wir haben EF POCO als Domänenmodelle ausgeschlossen, da dies Einschränkungen bei der Gestaltung unserer Modelle mit sich bringt. Wir könnten eine Automapping-Bibliothek verwenden, dies wäre jedoch nur bei der Zuordnung zu EF-Datenentitäten und nicht zu Domänenmodellen anwendbar. Grund dafür ist: Die Zuordnung zu einem Domänenmodell sollte eine Factory verwenden, nicht wahr? Andernfalls besteht die Gefahr, dass ungültige Domänenmodelle auf Ihrem System schweben. Ich bin damit einverstanden, dass es viel "Piping Code" gibt. Das Hinzufügen neuer Aggregate oder sogar das Hinzufügen von Eigenschaften zu vorhandenen Entitäten erfordert eine Menge Gerüstcode.
Dave New
1
+1. Ich stimme insbesondere dem "Mapper" zu - nach meinem Verständnis sollte ein guter ORM die Unterscheidung zwischen "Domänenklassen" und "Entitätsklassen" unnötig machen. Haben Sie einen Vorschlag für eine Alternative zum CompanyFramework.CreateExistingKonstrukt? Eine Sache, die ich nicht als Schwäche sehe, ist die IIdentityFactory. Wenn die IDs aus einer Datenbank generiert werden, sollten Sie über eine Mechanik verfügen, die dies verspottet, um Unit-Tests mit einer Datenbank zu ermöglichen.
Doc Brown
DocBrown ist genau richtig IIdentityFactory. In DDD müssen Entitäten ab dem Zeitpunkt der Erstellung eine Identität haben. Leider haben wir nicht den Luxus, GuidTypen verwenden zu können. Wir müssen int Idsaus der Datenbank generieren, bevor Persistenz auftritt. Ein weiterer Grund für eine Fabrik.
Dave New
1
@DocBrown Die Alternative zu CompanyFramework.CreateExisting ist ein gutes ORM. Die Sache mit IDs ist, dass es keine IDs geben sollte, es sei denn, die ID ist ein Domänenproblem. In diesem Fall generieren Sie sie auf irgendeine Weise. IDs dienen dazu, dass die DB Informationen über Beziehungen speichert. In DDD sollten diese jedoch von Objektreferenzen verarbeitet werden. In diesem Fall muss es nicht generiert werden, bevor es in der Datenbank gespeichert wird.
Euphoric