I’m building an app using DDD, however I’m struggling to understand where you should be instantiating specification classes or making use of them.
My app makes heavy use of booking windows so I have a specification that ensures that a booking window that is about to be added to an aggregate doesn’t overlap with another window that’s currently in the aggregate. Like the following.
/// <summary>
/// A specification that determines if the window passed in collides with other windows.
/// </summary>
public class BookingTemplateWindowDoesNotCollideSpecification : ISpecification<BookingScheduleTemplateWindow>
{
/// <summary>
/// The other windows to check the passed in window against.
/// </summary>
private readonly IEnumerable<BookingScheduleTemplateWindow> otherWindows;
/// <summary>
/// Initializes a new instance of the <see cref="BookingTemplateWindowDoesNotCollideSpecification" /> class.
/// </summary>
/// <param name="otherWindows">The other windows.</param>
public BookingTemplateWindowDoesNotCollideSpecification(IEnumerable<BookingScheduleTemplateWindow> otherWindows)
{
this.otherWindows = otherWindows;
}
/// <summary>
/// Determines whether the window passed in collides with other windows held inside this class.
/// </summary>
/// <param name="obj">The obj.</param>
/// <returns>
/// <c>true</c> if [is satisfied by] [the specified obj]; otherwise, <c>false</c>.
/// </returns>
public bool IsSatisfiedBy(BookingScheduleTemplateWindow obj)
{
return !this.otherWindows.Any(w => obj.DayOfWeek == w.DayOfWeek && w.WindowPeriod.IsOverlap(obj.WindowPeriod));
}
}
And then i have a method on the aggregate that allows a new window to be added using the specification. The aggregates already persisted windows are passed into the specification constructor.
public virtual void AddWindow(DayOfWeek dayOfWeek, int startTime, int endTime)
{
var nonCollidingWindowSpecification = new BookingTemplateWindowDoesNotCollideSpecification(this.Windows);
var bookingWindow = new BookingScheduleTemplateWindow(this){
DayOfWeek = dayOfWeek,
WindowPeriod = new Range<int>(startTime, endTime)
};
if (nonCollidingWindowSpecification.IsSatisfiedBy(bookingWindow))
{
this.Windows.Add(bookingWindow);
}
}
What I’m struggling with is that a part of me is thinking that I should be injecting this specification into the class rather than instantiating directly (as a general rule across my app and not just in this case) as the type of specfication may need to change depending on the state of the entity. But it feels dirty injecting the specification from the MVC layer as if I have another application interface like a REST API later then logic about which specification to use would be duplicated.
How do you make sure that the Specification being used remains flexible while ensuring that the logic about which specification to use doesn’t get duplicated in another application interface.
Is this a case where you would want to inject a factory into an entity and return the specification from there, thus not allowing the domain logic to spill out into a higher layer? Or is there a better/cleaner/simpler way to do this?
It is perfectly acceptable to inject domain services into entities. It is best to make dependencies on services explicit as parameters in the respective method on the aggregate. For example, the
AddWindowmethod could look like this:In this case, the specification acts as a domain service. Now it is up to surrounding infrastructure to pass the appropriate specification. This is where application services come in. An application service establishes a facade over your domain layer and contains methods for specific use cases. This application service, in turn, would be referenced by a controller. It is also possible to have the controller pass the required dependencies, however the encapsulation provided by an application service may be beneficial.
Sample application service code:
This is typical application service code: gets the appropriate aggregate, instantiates required dependencies if any, and invokes behavior on the aggregate.