DDD (Domain Driven Design), how to handle entity state changes, and encapsulate business rules that requires large amount of data to be processed

后端 未结 4 920
萌比男神i
萌比男神i 2021-01-31 04:47
public class Person
{
    public IList SpecialBirthPlaces;
    public static readonly DateTime ImportantDate;
    public String BirthPlace {get;set;}

             


        
4条回答
  •  我在风中等你
    2021-01-31 04:54

    The following is a sample implementation. This implementation consists of several layers: the domain layer, the service layer and the presentation layer. This purpose of the service layer is to expose the functionality of your domain layer to other layers, such as the presentation layer or a web service. To that end, its methods correspond to specific commands that can be processed by the domain layer. In particular we have the command to change the birthday. Furthermore, this implementation uses Udi Dahan's version of a domain event framework. This is done to decouple the domain entity from the business logic associated with changing the birthday. This can be regarded as both a benefit and a drawback. The drawback is that your overall business logic is spread across multiple classes. The benefit is that you gain a lot of flexibility in how you handle domain events. Additionally, this approach is more extensible, since you can add subscribers to the BirthDateChangedEvent which perform auxiliary functions. Yet another benefit, (which contributed to the reasoning behind Udi's implementation) is that your Person entity no longer needs to be aware of any repositories, which seem outside the scope of the domain entity. Overall, this implementation calls for quite a bit of infrastructure, however if you envision investing greatly into your domain then it is worth the initial trouble. Also note, that this implementation assumed an ASP.NET MVC based presentation layer. In a stateful UI, the presentation logic would need to change and the ViewModel would need to provide change notifications.

    /// 
    /// This is your main entity, while it may seem anemic, it is only because 
    /// it is simplistic.
    /// 
    class Person
    {
        public string Id { get; set; }
        public string BirthPlace { get; set; }
    
        DateTime birthDate;
    
        public DateTime BirthDate
        {
            get { return this.birthDate; }
            set
            {
                if (this.birthDate != value)
                {
                    this.birthDate = value;
                    DomainEvents.Raise(new BirthDateChangedEvent(this.Id));
                }
            }
        }
    }
    
    /// 
    /// Udi Dahan's implementation.
    /// 
    static class DomainEvents
    {
        public static void Raise(TEvent e) where TEvent : IDomainEvent
        {
        }
    }
    
    interface IDomainEvent { }
    
    /// 
    /// This is the interesting domain event which interested parties subscribe to 
    /// and handle in special ways.
    /// 
    class BirthDateChangedEvent : IDomainEvent
    {
        public BirthDateChangedEvent(string personId)
        {
            this.PersonId = personId;
        }
    
        public string PersonId { get; private set; }
    }
    
    /// 
    /// This can be associated to a Unit of Work.
    /// 
    interface IPersonRepository
    {
        Person Get(string id);
        void Save(Person person);
    }
    
    /// 
    /// This can implement caching for performance.
    /// 
    interface IBirthPlaceRepository
    {
        bool IsSpecial(string brithPlace);
        string GetBirthPlaceFor(string birthPlace, DateTime birthDate);
    }
    
    interface IUnitOfWork : IDisposable
    {
        void Commit();
    }
    
    static class UnitOfWork
    {
        public static IUnitOfWork Start()
        {
            return null;
        }
    }
    
    class ChangeBirthDateCommand
    {
        public string PersonId { get; set; }
        public DateTime BirthDate { get; set; }
    }
    
    /// 
    /// This is the application layer service which exposes the functionality of the domain 
    /// to the presentation layer.
    /// 
    class PersonService
    {
        readonly IPersonRepository personDb;
    
        public void ChangeBirthDate(ChangeBirthDateCommand command)
        {
            // The service is a good place to initiate transactions, security checks, etc.
            using (var uow = UnitOfWork.Start())
            {
                var person = this.personDb.Get(command.PersonId);
                if (person == null)
                    throw new Exception();
    
                person.BirthDate = command.BirthDate;
    
                // or business logic can be handled here instead of having a handler.
    
                uow.Commit();
            }
        }
    }
    
    /// 
    /// This view model is part of the presentation layer.
    /// 
    class PersonViewModel
    {
        public PersonViewModel() { }
    
        public PersonViewModel(Person person)
        {
            this.BirthPlace = person.BirthPlace;
            this.BirthDate = person.BirthDate;
        }
    
        public string BirthPlace { get; set; }
        public DateTime BirthDate { get; set; }
    }
    
    /// 
    /// This is part of the presentation layer.
    /// 
    class PersonController
    {
        readonly PersonService personService;
        readonly IPersonRepository personDb;
    
        public void Show(string personId)
        {
            var person = this.personDb.Get(personId);
            var viewModel = new PersonViewModel(person);
            // UI framework code here.
        }
    
        public void HandleChangeBirthDate(string personId, DateTime birthDate)
        {
            this.personService.ChangeBirthDate(new ChangeBirthDateCommand { PersonId = personId, BirthDate = birthDate });
            Show(personId);
        }
    }
    
    interface IHandle where TEvent : IDomainEvent
    {
        void Handle(TEvent e);
    }
    
    /// 
    /// This handler contains the business logic associated with changing birthdates. This logic may change
    /// and may depend on other factors.
    /// 
    class BirthDateChangedBirthPlaceHandler : IHandle
    {
        readonly IPersonRepository personDb;
        readonly IBirthPlaceRepository birthPlaceDb;
        readonly DateTime importantDate;
    
        public void Handle(BirthDateChangedEvent e)
        {
            var person = this.personDb.Get(e.PersonId);
            if (person == null)
                throw new Exception();
    
            if (person.BirthPlace != null && person.BirthDate < this.importantDate)
            {
                if (this.birthPlaceDb.IsSpecial(person.BirthPlace))
                {
                    person.BirthPlace = this.birthPlaceDb.GetBirthPlaceFor(person.BirthPlace, person.BirthDate);
                    this.personDb.Save(person);
                }
            }
        }
    }
    

提交回复
热议问题