POCO's, DTO's, DLL's and Anaemic Domain Models

后端 未结 3 2001
执念已碎
执念已碎 2021-01-30 14:52

I was looking at the differences between POCO and DTO (It appears that POCO\'s are dto\'s with behaviour (methods?))and came across this article by Martin Fowler on the anaemic

相关标签:
3条回答
  • 2021-01-30 15:18

    Typically, you don't want to introduce persistence into your domain objects, since it is not part of that business model (an airplane does not construct itself, it flies passengers/cargo from one location to another). You should use the repository pattern, an ORM framework, or some other data access pattern to manage the persistent storage and retreival of an object's state.

    Where the anemic domain model comes in to play is when you're doing things like this:

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

    In this case, the management of the airplane's state (whether it's flying, where it's at, what's the departure time/airport, what's the arrival time/airport, what's the flight plan, etc) is delegated to something external to the plane... the AirplaneService instance.

    A POCO way of implementing this would be to design your interface this way:

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

    This is more discoverable, since developers know where to look to make an airplane fly (just tell the airplane to do it). It also allows you to ensure that state is only managed internally. You can then make things like current location read-only, and ensure that it's only changed in one place. With an anemic domain object, since state is set externally, discovering where state is changed becomes increasingly difficult as the scale of your domain increases.

    0 讨论(0)
  • 2021-01-30 15:18

    Personally I don't find those Anaemic Domain Models so bad; I really like the idea of having domain objects that represent only data, not behaviour. I think the major downside with this approach is discoverability of the code; you need to know which actions that are available to use them. One way to get around that and still keep the behaviour code decoupled from the model is to introduce interfaces for the behaviour:

    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, ...
    
    }
    

    That way you can keep the behaviour implementation separated from the model. The use of interface implementations that are injected into the model also makes the code rather easy to test, since you can easily mock the behaviour.

    0 讨论(0)
  • 2021-01-30 15:21

    I think the best way to clarify this is by definition:

    DTO: Data Transfer Objects:

    They only serve for data transportation typically between presentation layer and service layer. Nothing less or more. Generally it is implemented as class with gets and sets.

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

    BO: Business Objects:

    Business objects represents the business elements and naturally the best practice says they should contain business logic also. As said by Michael Meadows, it is also good practice to isolate data access from this objects.

    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 or Application Class These classes represent the interaction between User and the System and they will make use of both ClientDTO and 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
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题