Proper use of specification classes in a domain-driven project

I am building an application using DDD, however I am trying to figure out where you should create specification classes or use them.

My application makes heavy use of booking windows, so I have a specification that ensures that the booking window to be added to the unit does not overlap with the other window that is currently in the aggregate. As shown below.

/// <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 in combination that allows me to add a new window using the specification. Aggregates that have already been saved, windows are passed to the specification designer.

  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 part of me thinks that I should introduce this specification into the class, and not create it directly (as a general rule for my application, and not just in this case) as a type, a specification may be required change depending on the state of the object. But it feels dirty introducing the specification from the MVC level, as if I had a different application interface such as the REST API, later the logic that the duplicate specification would be used about.

How do you make sure that the specification used remains flexible, ensuring that the logic about which specification will be used is not duplicated in another application interface.

Is this the case when you want to insert a factory into an object and return the specification there, not allowing the domain logic to go to a higher level? Or is there a better / cleaner / easier way to do this?

+4
source share
1 answer

It is completely acceptable to introduce domain services in essence. It is best to make service dependencies explicit as parameters in the corresponding method in aggregate. For example, the AddWindow method might look like this:

  public virtual void AddWindow(ISpecification<BookingScheduleTemplateWindow> nonCollidingWindowSpecification, DayOfWeek dayOfWeek, int startTime, int endTime) { var bookingWindow = new BookingScheduleTemplateWindow(this){ DayOfWeek = dayOfWeek, WindowPeriod = new Range<int>(startTime, endTime) }; if (nonCollidingWindowSpecification.IsSatisfiedBy(bookingWindow)) { this.Windows.Add(bookingWindow); } } 

In this case, the specification acts as a domain service. Surrounding infrastructure is now required to meet relevant specifications. Here are the applications. The application service sets the faΓ§ade over your domain level and contains methods for specific use cases. This application service, in turn, will refer to the controller. It is also possible that the controller passes the necessary dependencies, however, the encapsulation provided by the application service may be useful.

Example application service code:

 public class AddWindow(string aggregateId, DayOfWeek dayOfWeek, int startTime, int endTime) { var aggregate = this.repository.Get(aggregateId); var specification = // instantiate specification aggregate.AddWindow(specification, dayOfWeek, startTime, endTime); this.repository.Commit(); } 

This is a typical application service code: it gets the appropriate aggregate, creates the necessary dependencies, if any, and causes the behavior in aggregate.

+2
source

All Articles