Frage POCOs, DTOs, DLLs und anämische Domänenmodelle


Ich schaute auf die Unterschiede zwischen POCO und DTO (Es scheint, dass POCO's sind dto's mit Verhalten (Methoden?)) Und rüber kam Dieser Artikel von Martin Fowler über das anämische Domänenmodell.

Durch Unverständnis habe ich eines dieser anämischen Domänenmodelle geschaffen.

In einer meiner Anwendungen habe ich meine Geschäftsdomäneneinheiten in einer "dto" -Dll definiert. Sie haben viele Eigenschaften mit Getter und Setter und nicht viel mehr. Mein Geschäftslogik-Code (ausfüllen, berechnen) befindet sich in einer anderen DLL-Datei, und mein Datenzugriffscode befindet sich in einer DAL-DLL. "Best Practice", dachte ich.

Also erstelle ich normalerweise ein dto so:

dto.BusinessObject bo = new dto.BusinessObject(...)

und es wie folgt an die bll-Ebene übergeben:

bll.BusinessObject.Populate(bo);

welches wiederum eine gewisse Logik ausführt und es wie folgt an die Dal-Ebene übergibt:

dal.BusinessObject.Populate(bo);

Aus meinem Verständnis, um meine dto in POCO's zu machen, muss ich die Geschäftslogik und das Verhalten (Methoden) Teil des Objekts machen. Anstatt des obigen Codes ist es eher wie folgt:

poco.BusinessObject bo = new poco.BusinessObject(...)
bo.Populate();

dh. Ich rufe die Methode für das Objekt auf, anstatt das Objekt an die Methode zu übergeben.

Meine Frage ist - wie kann ich das tun und immer noch die "Best Practice" Schichtung von Bedenken (separate DLLs etc ...) beibehalten. Ruft die Methode für das Objekt nicht auf, bedeutet das, dass die Methode im Objekt definiert werden muss?

Bitte hilf meiner Verwirrung.


20
2018-05-22 05:22


Ursprung


Antworten:


Normalerweise möchten Sie keine Persistenz in Ihre Domänenobjekte einführen, da sie nicht Teil dieses Geschäftsmodells ist (ein Flugzeug konstruiert sich nicht selbst, es befördert Passagiere / Fracht von einem Ort zum anderen). Du solltest das benutzen Repository-Muster, ein ORM-Rahmenoder ein anderes Datenzugriffsmuster, um die dauerhafte Speicherung und das erneute Einreichen eines Objekts zu verwalten Zustand.

Wo das anämische Domänenmodell zum Einsatz kommt, ist Folgendes:

IAirplaneService service = ...;
Airplane plane = ...;
service.FlyAirplaneToAirport(plane, "IAD");

In diesem Fall wird die Verwaltung des Flugzeugzustands (ob es fliegt, wo es ist, wie ist die Abflugzeit / Flughafen, wie ist die Ankunftszeit / Flughafen, wie ist der Flugplan, etc.) an etwas außerhalb des Flugzeugs delegiert. Die AirplaneService-Instanz

Ein POCO Weg dies zu implementieren wäre, Ihre Schnittstelle so zu gestalten:

Airplane plane = ...;
plane.FlyToAirport("IAD");

Dies ist besser erkennbar, da die Entwickler wissen, wo sie suchen müssen, um ein Flugzeug zum Fliegen zu bringen (sagen Sie einfach dem Flugzeug, es zu tun). Es ermöglicht Ihnen auch sicherzustellen, dass der Status ist nur intern verwaltet. Sie können Dinge wie den aktuellen Standort schreibgeschützt machen und sicherstellen, dass er nur an einer Stelle geändert wird. Da ein Zustand mit einem anämischen Domänenobjekt extern festgelegt wird, wird es immer schwieriger, zu erkennen, wo sich der Status ändert, da die Größe Ihrer Domäne zunimmt.


22
2018-05-26 17:44



Ich denke, der beste Weg, dies zu klären, ist per definitionem:

DTO: Datenübertragungsobjekte: 

Sie dienen nur zum Datentransport zwischen Präsentations- und Serviceschicht. Nichts weniger oder mehr. Im Allgemeinen wird es als Klasse mit Gets und Sets implementiert.

public class ClientDTO
{
    public long Id {get;set;}
    public string Name {get;set;}
}

BO: Geschäftsobjekte:

Business-Objekte repräsentieren die Business-Elemente und natürlich empfehlen die Best Practices auch Business-Logik. Wie von Michael Meadows gesagt, ist es auch eine gute Praxis, den Datenzugriff von diesen Objekten zu isolieren.

public class Client
{
    private long _id;
    public long Id 
    { 
        get { return _id; }
        protected set { _id = value; } 
    }
    protected Client() { }
    public Client(string name)
    {
        this.Name = name;    
    }
    private string _name;
    public string Name
    {
        get { return _name; }
        set 
        {   // Notice that there is business logic inside (name existence checking)
            // Persistence is isolated through the IClientDAO interface and a factory
            IClientDAO clientDAO = DAOFactory.Instance.Get<IClientDAO>();
            if (clientDAO.ExistsClientByName(value))
            {
                throw new ApplicationException("Another client with same name exists.");
            }
            _name = value;
        }
    }
    public void CheckIfCanBeRemoved()
    {
        // Check if there are sales associated to client
        if ( DAOFactory.Instance.GetDAO<ISaleDAO>().ExistsSalesFor(this) )
        {
            string msg = "Client can not be removed, there are sales associated to him/her.";
            throw new ApplicationException(msg);
        }
    }
}

Service- oder Anwendungsklasse Diese Klassen stellen die Interaktion zwischen dem Benutzer und dem System dar und sie verwenden sowohl ClientDTO als auch Client.

public class ClientRegistration
{
    public void Insert(ClientDTO dto)
    {
        Client client = new Client(dto.Id,dto.Name); /// <-- Business logic inside the constructor
        DAOFactory.Instance.Save(client);        
    }
    public void Modify(ClientDTO dto)
    {
        Client client = DAOFactory.Instance.Get<Client>(dto.Id);
        client.Name = dto.Name;  // <--- Business logic inside the Name property
        DAOFactory.Instance.Save(client);
    }
    public void Remove(ClientDTO dto)
    {
        Client client = DAOFactory.Instance.Get<Client>(dto.Id);
        client.CheckIfCanBeRemoved() // <--- Business logic here
        DAOFactory.Instance.Remove(client);
    }
    public ClientDTO Retrieve(string name)
    {
        Client client = DAOFactory.Instance.Get<IClientDAO>().FindByName(name);
        if (client == null) { throw new ApplicationException("Client not found."); }
        ClientDTO dto = new ClientDTO()
        {
            Id = client.Id,
            Name = client.Name
        }
    }
}

10
2017-12-02 11:24



Persönlich finde ich diese anämischen Domain-Modelle nicht so schlecht; Ich mag die Idee, Domänenobjekte zu haben, die nur Daten repräsentieren, nicht das Verhalten. Ich denke, der größte Nachteil bei diesem Ansatz ist die Auffindbarkeit des Codes. Sie müssen wissen, welche Aktionen verfügbar sind, um sie zu verwenden. Eine Möglichkeit, dies zu umgehen und dennoch den Verhaltenscode vom Modell abgekoppelt zu halten, ist die Einführung von Schnittstellen für das Verhalten:

interface ISomeDomainObjectBehaviour
{
    SomeDomainObject Get(int Id);
    void Save(SomeDomainObject data);
    void Delete(int Id);
}

class SomeDomainObjectSqlBehaviour : ISomeDomainObjectBehaviour
{
    SomeDomainObject ISomeDomainObjectBehaviour.Get(int Id)
    {
        // code to get object from database
    }

    void ISomeDomainObjectBehaviour.Save(SomeDomainObject data)
    {
        // code to store object in database
    }

    void ISomeDomainObjectBehaviour.Delete(int Id)
    {
        // code to remove object from database
    }
}
class SomeDomainObject
{
    private ISomeDomainObjectBehaviour _behaviour = null;
    public SomeDomainObject(ISomeDomainObjectBehaviour behaviour)
    {

    }

    public int Id { get; set; }
    public string Name { get; set; }
    public int Size { get; set; }


    public void Save()
    {
        if (_behaviour != null)
        {
            _behaviour.Save(this);
        }
    }

    // add methods for getting, deleting, ...

}

Auf diese Weise können Sie die Verhaltensimplementierung vom Modell getrennt halten. Die Verwendung von Schnittstellenimplementierungen, die in das Modell eingefügt werden, macht den Code auch ziemlich einfach zu testen, da Sie das Verhalten leicht verspotten können.


6
2018-05-22 05:54