域驱动设计 – 在域驱动设计中正确使用规范类

我正在使用DDD构建一个应用程序,但是我很难理解你应该在哪里实例化规范类或使用它们.

我的应用程序大量使用预订窗口,所以我有一个规范,确保即将添加到聚合的预订窗口不会与当前聚合的另一个窗口重叠.如下所示.

    /// <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));
    }
}

然后我在聚合上有一个方法,允许使用规范添加一个新窗口.已经持久化的聚合窗口被传递到规范构造函数中.

        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);
        }
    }

我正在努力的是,我的一部分是认为我应该将这个规范注入到类中而不是直接实例化(作为我的应用程序的一般规则,而不仅仅是在这种情况下),因为可能需要这种类型的specfication根据实体的状态进行更改.但是从MVC层注入规范感觉很脏,好像我有一个像REST API这样的另一个应用程序接口,然后关于使用哪个规范的逻辑将被复​​制.

如何确保所使用的规范保持灵活性,同时确保使用哪个规范的逻辑不会在另一个应用程序界面中重复.

这是一种情况,您希望将工厂注入实体并从那里返回规范,从而不允许域逻辑溢出到更高层?或者有更好/更清洁/更简单的方法吗?

最佳答案 将域服务注入实体是完全可以接受的.最好将服务的依赖关系显式化为聚合中相应方法中的参数.例如,AddWindow方法可能如下所示:

  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);
        }
    }

在这种情况下,规范充当域服务.现在,周围的基础设施需要通过适当的规范.这就是应用程序服务的用武之地.应用程序服务在您的域层上建立了一个外观,并包含特定用例的方法.反过来,该应用程序服务将由控制器引用.控制器也可以传递所需的依赖关系,但是应用程序服务提供的封装可能是有益的.

示例应用服务代码:

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();
}

这是典型的应用程序服务代码:获取适当的聚合,实例化所需的依赖项(如果有),并在聚合上调用行为.

点赞