Working with Polymorphic Child Entities

"[email protected] [domaindrivendesign]" <[email protected]> 27 May 2015 14:26:11 -0700
Newsgroups gmane.comp.programming.domain-driven-design
Message-ID <[email protected]>
I have been thinking of the best approach to deal with Aggregate Entities having polymorphic children and wonder if anyone could tell me if implementation below is a correct implementation or if I may be overlooking some potential pitfalls.
 The following code demonstrates a system for sending some sort of communication on varying schedules.
 THE DOMAIN
 class Delivery { Dictionary<Type, Func<ScheduleDto, ScheduleEntity>> ScheduleFactories = new Dictionary<Type, Func<ScheduleDto, ScheduleEntity>> { { typeof(DailyScheduleDto), GetDailySchedule}, { typeof(HourlyScheduleDto), GetHourlySchedule} }; public Guid Id { get; private set; } public string Message { get; private set; } public StatusEnum Status { get private set; } public Schedule { get; private set; } public Delivery(DeliveryDto dto){ Id = dto.Id; Message = dto.Message; Status = StatusEnum.New; Schedule = ScheduleFactories[dto.Schedule.GetType()](dto.Schedule); } public void Update(DeliveryDto dto){ Message = dto.Message; Schedule.Update(dto.Schedule); } public DateTime GetNextExecution(DateTime after){ return Schedule.GetNextExecution(after); } public enum StatusEnum { New, InProgress, Complete } Schedule GetDailyScheduleEntity(ScheduleDto dto){ return new DailyScheduleEntity((DailyScheduleDto)dto); } Schedule GetHourlyScheduleEntity(ScheduleDto dto){ return new HourlyScheduleEntity((HourlyScheduleDto)dto); } } abstract class Schedule { public DateTime StartsOn { get; private set; } public abstract DateTime GetNextExecution(DateTime after); public virtual void Update(ScheduleDto dto){ StartsOn = dto.StartsOn; } public Schedule(ScheduleDto dto) { StartsOn = dto.StartsOn; } } class DailySchedule : Schedule { public DaysEnum Days { get; private set; } public override DateTime GetNextExecution(DateTime after){ return // logic for calculating the next day based on Days; } public DailySchedule(DailyScheduleDto dto) : base(dto) { Days = dto.Days.ToEnum<DaysEnum>(); base.Update(dto); } public override void Update(ScheduleDto dto){ if(!(dto is DialyScheduleDto)) throw new InvalidOperationException(); Days = ((DailyScheduleDto)dto).Days.ToEnum<DaysEnum>(); } [Flags] public enum DaysEnum{ Monday, Tuesday, Wednesday, Thursday, Friday } } class HourlySchedule : Schedule { public int Interval { get; private set } public override DateTime GetNextExecution(DateTime after){ return // logic for calculating the next hour based on interval; } public HourlySchedule(HourlyScheduleDto dto) : base(dto) { Interval = dto.Interval; } public override void Update(ScheduleDto dto){ if(!(dto is HourlyScheduleDto)) throw new InvalidOperationException(); Days = ((HoulryScheduleDto)dto).Interval; base.Update(dto); } } class DeliveryService : IDeliveryService { public void Add(DeliveryDto dto){ var delivery = new Delivery(dto); var scheduledTask = new ScheduledTask( delivery.Id, delivery.GetNextExecution(after: DateTime.Now) ); unitOfWork.Add<ScheduledTask>(scheduledTask); unitOfWork.Add<Delivery>(delivery); unitOfWork.SaveChanges(); } public void Update(DeliveryDto dto){ var delivery = unitOfWork.Get<Delivery>(dto.Id); delivery.Update(dto); unitOfWork.SaveChanges(); } public void SendAll(){ var scheduledTasks = unitOfWork.GetAll<ScheduledTask>(st => st.ExecutionDateTime < DateTime.Now && st.Status == ScheduledTaskStatus.New); scheduledTasks.ForEach(//Send to subsystem for execution); } } CONTRACTS
 DeliveryDto{ Guid Id {get; set;} string Description {get; set;} ScheduleDto Schedule {get; set;} } ScheduleDto{ DateTime StartsOn {get; set;} } HourlyScheduleDto : ScheduleDto{ int Interval {get; set;} } DailyScheduleDto : ScheduleDto{ String Days {get; set;} } I would typically have specific dtos for creating new instances and performing updates, but reuse the same types here for simplicity.
 
 Although there is an argument that passing Dtos directly to Entities would tightly couple clients to the domain, my intention is to provide a thin facade-type layer that would be responsible for 'upgrading' dtos to maintain compatibility with existing clients should the current dto's need to change to accommodate changing business needs