Working with Polymorphic children

218 views
Skip to first unread message

Ricardo da Silva

unread,
May 22, 2015, 2:02:56 AM5/22/15
to ddd...@googlegroups.com
 have been thinking of the best approach to deal with Aggregate Entities having polymorphic children and wonder if anyone has comments on whether this is correct or whether 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 Description { get; private set; }
    public StatusEnum Status { get private set; }
    public Schedule { get; private set; }
    public Delivery(DeliveryDto dto){
        Id = dto.Id;
        Description = dto.Description;
        Status = StatusEnum.New;
        Schedule = ScheduleFactories[dto.Schedule.GetType()](dto.Schedule);
    }
    public void Update(DeliveryDto dto){
        Description = dto.Description;
        Schedule.Update(dto.Schedule);
    }
    public DateTime GetNextDeliverySchedule(DateTime after){
        return Schedule.GetNextDeliverySchedule(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 GetNextDeliverySchedule(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 GetNextDeliverySchedule(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){
        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 GetNextDeliverySchedule(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){
        Days = ((HoulryScheduleDto)dto).Interval;
        base.Update(dto);
    }
}
class DeliveryService : IDeliveryService {
     public void Add(DeliveryDto dto){
         var delivery = new Delivery(dto);
         unitOfWork.Add<Delivery>(delivery);
         unitOfWork.SaveChanges();
     }
     public void Update(DeliveryDto dto){
          var delivery = unitOfWork.Get<Delivery>(dto.Id);
          delivery.Update(dto);
          unitOfWork.SaveChanges();
     }
}
 
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

Alexey Raga

unread,
May 22, 2015, 3:51:18 AM5/22/15
to ddd...@googlegroups.com
Just my 2 cents: introducing inheritance is particularly bad when you want to maintain backwards compatibility. Because hard things happen when in time exactly the logic you put in a base class has to diverge.
Message has been deleted

Ricardo da Silva

unread,
May 22, 2015, 4:39:32 AM5/22/15
to ddd...@googlegroups.com
@Alexey

Thanks for your comments, but could you elaborate a little please. If base logic has to diverge than I think that woiuld signal that the logic is no longer common to all derived types, meaning logic can be moved to the override in the derived types. If logic is still common among some derived types and not others; the common logic can be centralized in a helper. So I don't see that this would break compatibility. 

Ben Kloosterman

unread,
May 22, 2015, 4:46:32 AM5/22/15
to ddd...@googlegroups.com
Nothing to do with CQRS but personally i avoid all inheritance these days ( bar a few non premature optimizations) . Extension by composition  not inheritance to avoid the fragile base class problem . Use interfaces for istype and inject common behaviour. 

Regards, 

Ben

On Fri, May 22, 2015 at 6:39 PM, Ricardo da Silva <ricard...@gmail.com> wrote:
@Alexey

Thanks for your comments, but could you elaborate a little please. If base logic has to diverge than I think that woiuld signal that the logic is no longer common to all derived types, meaning logic can be moved to the override in the derived types. If logic is still common among some derived types and not others; the common logic can be centralized in a helper. So I don't see that this would break compatibility. 

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Ricardo da Silva

unread,
May 22, 2015, 5:24:02 AM5/22/15
to ddd...@googlegroups.com
@Bennie 

Yes agreed; but I thought the forum name implied DDD 'or' CQRS.apologies.
Could you possibly flesh out a small example based on my model to demonstrate how I would use interfaces for istype and inject common functionality please. In particular how would this work to ensure invoking the polymoric behaviour on the correct derived type without the need for prior type checking.

Tom Janssens

unread,
May 22, 2015, 12:46:25 PM5/22/15
to ddd...@googlegroups.com
Not sure what your question was, but I decided to implement something in .Net, as it's been a while for me; does this answer your question? (gist here: https://gist.github.com/ToJans/750afe22dda0c98a457e ):

namespace RicardoDeSilva
{
namespace Contracts
{
public enum ScheduleGeneratorType { Daily, Hourly /*, Add others */ };
 
public class DeliveryDetails
{
public string Id { get; protected set; }
public string Description { get; protected set; }
public DateTime? DeliverStartingFrom { get; protected set; }
public ScheduleGeneratorType ScheduleGenerator { get; protected set; }
}
}
 
namespace Modules
{
using Contracts;
 
public interface IGenerateSchedules
{
IEnumerable<DateTime> StartingFrom(DateTime startDate);
}
 
public static class ScheduleModule
{
 
public static IGenerateSchedules GetGenerator(ScheduleGeneratorType @type)
{
switch (@type)
{
case (ScheduleGeneratorType.Daily): return new DailyScheduleGenerator();
case (ScheduleGeneratorType.Hourly): return new HourlyScheduleGenerator();
default: return UnknownScheduleGenerator;
}
}
 
class HourlyScheduleGenerator : IGenerateSchedules
{
IEnumerable<DateTime> IGenerateSchedules.StartingFrom(DateTime startDate)
{
var currentDate = startDate;
while (true)
{
// this will probably be way more complicated
if (currentDate.TimeOfDay.TotalHours >= 8.5 &&
currentDate.TimeOfDay.TotalHours < 17)
{
yield return currentDate;
}
currentDate += TimeSpan.FromHours(1);
}
}
}
 
class DailyScheduleGenerator : IGenerateSchedules
{
IEnumerable<DateTime> IGenerateSchedules.StartingFrom(DateTime startDate)
{
var currentDate = startDate;
while (true)
{
// this will probably be way more complicated
if (currentDate.DayOfWeek != DayOfWeek.Saturday &&
currentDate.DayOfWeek != DayOfWeek.Sunday)
{
yield return currentDate;
}
currentDate += TimeSpan.FromDays(1);
}
}
}
 
class NopScheduleGeneratorClass : IGenerateSchedules
{
IEnumerable<DateTime> IGenerateSchedules.StartingFrom(DateTime startDate)
{
throw new NotImplementedException();
}
}
 
public static IGenerateSchedules UnknownScheduleGenerator = new NopScheduleGeneratorClass();
}
}
 
namespace Domain
{
using Contracts;
using Modules;
 
public class Delivery
{
void ProvisionDelivery(DeliveryDetails deliveryDetails)
{
var generateSchedule = ScheduleModule.GetGenerator(deliveryDetails.ScheduleGenerator);
 
Guard.Against(generateSchedule == ScheduleModule.UnknownScheduleGenerator, "Unknown schedule generator");
 
// a later starting date might be picked for whatever reason (f.e. not in stock etc)
var earliestDate = deliveryDetails.DeliverStartingFrom.HasValue ? deliveryDetails.DeliverStartingFrom.Value : DateTime.Now;
 
// here other dates might be picked (f.e. national holiday)
var scheduledDate = generateSchedule.StartingFrom(earliestDate).First();
 
// everything is ok, persist
this.DeliveryWasProvisioned(deliveryDetails.Id, deliveryDetails.Description, scheduledDate);
}
 
private void DeliveryWasProvisioned(string Id, string Description, DateTime scheduledDate)
{
// TODO
throw new NotImplementedException();
}
}
}
 
 
 
public static class Guard
{
public static void Against(bool condition, string message)
{
if (condition)
{
throw new InvalidOperationException(message);
}
}
 
public static void That(bool condition, string message)
{
Guard.Against(!condition, message);
}
}
}



Op vrijdag 22 mei 2015 08:02:56 UTC+2 schreef Ricardo da Silva:

Tom Janssens

unread,
May 22, 2015, 3:54:51 PM5/22/15
to ddd...@googlegroups.com
For your amusement only: https://github.com/ToJans/WeComeInPeaceShootToKill

Op vrijdag 22 mei 2015 18:46:25 UTC+2 schreef Tom Janssens:

Kijana Woodard

unread,
May 22, 2015, 5:50:37 PM5/22/15
to ddd...@googlegroups.com
Must of been one heck of a BBQ.

--

Bennie Kloosteman

unread,
May 23, 2015, 12:36:19 AM5/23/15
to Ricardo da Silva, ddd...@googlegroups.com
   my comments were more general than just cqrs but does cover it. Ie polymorphism is an anti pattern encapsulation and composition are what is still good about oo . Interfaces allow us to remove istype eg if myobj is icar.
re example a bit confused here if your being facetious. Injecting common behaviour ,interfaces and extension by composition can do everything inheritance can in a less dirty more maintainable way. That's how runtimes do it via vtable
. If you need an example let me know .

Ben

From: Ricardo da Silva
Sent: ‎22/‎05/‎2015 19:24
To: ddd...@googlegroups.com
Subject: [DDD/CQRS] Re: Working with Polymorphic children

@Bennie 

Yes agreed; but I thought the forum name implied DDD 'or' CQRS.apologies.
Could you possibly flesh out a small example based on my model to demonstrate how I would use interfaces for istype and inject common functionality please. In particular how would this work to ensure invoking the polymoric behaviour on the correct derived type without the need for prior type checking.

Tom Janssens

unread,
May 23, 2015, 1:09:10 AM5/23/15
to ddd...@googlegroups.com
It was ;)

Alexey Raga

unread,
May 23, 2015, 3:42:31 PM5/23/15
to ddd...@googlegroups.com, ricard...@gmail.com
@Bennie, I wouldn't say that polymorphism is an anti-pattern. Inheritance, however, often is highly overrated.

@Ricardo, It seems that you only need inheritance/interfaces to be able to get some object and then to be able to call its method. For the same reason @Tom uses interfaces in his "enterprisy" solution.

But what if instead of getting, say, a scheduler to act on it we just call it?
Here is the example, and since I almost forgot how to write C# treat it as a pseudocode (or semantically correct Scala):

class Scheduler {
    def register[A](filter: A => Boolean)(handler: A => Unit) = ???
    def execute[A](payload: A) = ???
}

Now you can simply register _any_ scheduler without requiring it to implement any specific interface. Actually, any function :)
And if in the unfortunate situation you are unlucky and have the same type for multiple meanings, you still can distinguish between them at the registration time using the "filter" parameter.

//register a specific scheduler type, preferred
scheduler.register[DailySchedule](_ => true) { ds =>
   val dates = Iterator.iterate(ds.start)(_.plus(ds.step)).filterNot(x => isWeekend(x))
   //run the schedule
}

//register a "common" scheduler type, smells
scheduler.register[Schedule](x => x.kind ==ScheduleType.Daily) {ds => 
  //run the schedule
}

Now, everywhere you want to run a scheduler you just need to call its "execute" method without even knowing who and how is going to handle it.

Cheers,
Alexey.

Ben Kloosterman

unread,
May 24, 2015, 10:10:27 AM5/24/15
to ddd...@googlegroups.com
yes i meant inheritance not polymorphism (its ok with interfaces). And for me inheritance is an antipattern  (though like goto not always) ,i have seen a lot of hard to maintain /fragile code  because of it. Interfaces with common behavior injected should nearly always be preferred.  
Reply all
Reply to author
Forward
0 new messages