访问数据以进行验证并使用DDD确定默认值

时间:2011-03-28 03:58:20

标签: validation domain-driven-design repository

让我们采取一个假设的项目,如音乐学校的学生注册系统。

  • 在系统内,需要有一种方法可以为有兴趣在该学校接受教学的新学生输入学生数据,我们可以假设学生可以通过网站访问此功能。
  • 部分报名流程询问学生她希望接受指导的工具类型。
  • 由于输入了有关其首选乐器的信息,行为将是指派一名默认讲师分配给该组乐器。
  • 在完成注册申请之前,学生还可以将指定的教师更改为不同的教师,该教师也被列为能够为其所选择的教学提供指导。

鉴于此描述,我遇到一点麻烦的部分是如何管理可能的教师列表,这些教师可以在为新学生选择默认教师方面为特定乐器提供指导,第二,如何在提交注册之前验证选定的讲师时使用相同的数据。 其他技术限制是到目前为止我一直在使用一种非常类似于Jimmy Nilsson的书Applying Domain-Driven Design and Patterns中提到的自我验证技术,所以我不清楚继续跟踪自我验证的最佳方法当需要访问外部数据时,我通常会将其视为被测试实体范围之外的技术。

我知道的选项:

  1. 将验证移出实体本身。也许验证被转移到一组服务或每个实体的单个服务,它分析整个实体的当前状态并认为它是否有效,并提供要发出的域事件或其他值对象,以便更好地了解验证规则具有哪些被打破了。在这种情况下,我仍然对服务如何获取有关教师的必要信息感到有些不安
  2. 允许从试图执行此验证的必要实体访问讲师存储库。
  3. 创建一项服务,允许按工具类别访问教师列表。或者创建两个单独的服务,一个返回给定教师是否在给定类别的教师列表中,另一个返回给定类别的默认教师。
  4. 在我的聚合根目录(可能是学生或学生注册请求)中加载一个教师值对象列表,这些对象可以由根目录中包含的聚合根或实体进行验证。
  5. 在上面的前两种情况中的任何一种情况下,似乎使用教师存储库都是过度的,因为我不需要访问代表教师的聚合根,但在我的情况下,我会将教师看作是描述学生注册请求并使存储库吐回值对象的值对象似乎模糊了存储库应该执行的操作。对于最后两个选项,似乎错误两个允许在选项4的情况下从服务或工厂访问数据,因为不是应该负责数据访问的存储库,例如这个?如果存储库是这个逻辑的正确位置,那么访问或存储对存储库的引用的适当位置在哪里?我一直坚信有理由不直接在组成模型的任何实体或价值对象中访问存储库,所以我想知道这是否是我可能不得不屈服于这个假设的情况。我还应该提一下,我对DDD很陌生,而且我现在正遇到一些令人头疼的问题,并试图不把自己包装好,所以对这个主题的任何知识渊博的输入都是有价值的。

2 个答案:

答案 0 :(得分:3)

  

将验证移出实体本身。

一位教练不应该了解所有其他教练。对一组教师的验证不是一位特定教师的责任。

  

允许从试图执行此验证的必要实体访问讲师存储库。

域模型应该是持久性无知的。需要打破,这表明你的模型存在缺陷。

  

创建一项服务,允许按工具类别访问教师列表。

这一点信息显示缺少聚合根 - 我称之为InstrumentClass

将其引入您的模型可以解决您的一些问题。 InstrumentClass会聘请教授特定乐器的教师。

接下来你需要弄清楚的是如何恰当地描述分配给班级的学生。不幸的是我暂时无法命名(也许是Participation?)。但该实体将用于InstrumentClass以确定哪些教师太忙。


这是我的“自由风格”(只是为了显示我所看到的)建模您的域名:

using System;

public class Main{
  public Main(){
    var instructor = new Instructor();
    var instrument = new Instrument("saxaphone");
    var saxaphoneClass = new InstrumentClass(saxaphone,teacher);
    var me=new Person("Arnis");
    //here, from UI, I can see available classes, choose one
    //and choose according instructor who's assigned to it
    var request=me.RequestEnrollment(saxaphoneClass, instructor);
    saxaphoneClass.EnrollStudent(request);
  }
}
public class Person{
  public IList<EnrollmentRequest> EnrollmentRequests { get; private set; }
  public EnrollmentRequest RequestEnrollment
   (InstrumentClass instrumentClass,Instructor instructor){
    if (!instrumentClass.IsTeachedByInstructor(instructor))
      throw new Exception("Instructor does not teach this music instrument");
    var request=new EnrollmentRequest(this,instrumentClass,instructor);
    EnrollmentRequests.Add(request);
    return request;
  }
}
public class EnrollmentRequest{
  public Person Person{ get; private set; }
  public InstrumentClass InstrumentClass { get; private set; }
  public Instructor Instructor{ get; private set; }
}
public class InstrumentClass{
  public void EnrollStudent(EnrollmentRequest request){
    var instructor=request.Instructor;
    var student=new Student(request.Person);
    var studies=new Studies(this,student,instructor);
    //TODO: this directiveness isn't good
    //student/instructor should listen for class events themselves
    //and class should listen if by any reason instructor or student cannot
    //participate in studies
    student.EnrollInClass(studies);
    instructor.AssignStudent(studies);
    Studies.Add(studies);
  }
  public bool IsTeachedByInstructor(Instructor instructor){
    return Instructors.Contains(instructor);
  }
  public InstrumentClass
   (Instrument instrument, params Instructor[] instructors){
    Instrument=instrument; Instructors=instructors.ToList();
  }
  public IList<Instructor> Instructors{get;private set;}
  public IList<Studies> Studies { get; private set; }
  public Instrument Instrument { get; private set; }
}
public class Studies{
  public Student Student { get; private set; }
  public Instructor Instructor { get; private set; }
  public InstrumentClass InstrumentClass { get; private set; }
}
public class Student{
}
public class Instructor{
}
public class Instrument{
}

答案 1 :(得分:1)

我的回答并未涵盖详细的实施/代码,因为您似乎正在使用此作为练习来了解有关DDD的更多信息。

请记住,在大多数情况下,您无法在第一时间获得正确的模型,并且需要改进模型。当你“玩它”时,某些刚性部件将变得更加灵活。 (就像埃里克的比喻一样,园艺手套)。当您获得对域的新见解时,您会发现需要在模型中引入新概念。使用“简单示例”存在危险,例如它们可能缺乏深度。但是有时需要简单的例子才能掌握DDD,幸运的是我们也可以发展这个例子;)

我听过Eric Evans提到的一件事是,如果域名感觉不对,或者您在模型中表达某些内容有困难,那么您可能会错过一个概念。当然,如果您拥有域中的概念,您可以“感受”或找到一个可以进行验证的自然场所。

设置上下文后,我有一个命题如下:

Enterprise Patterns and MDA有一些复杂的模式,但您可以带走的想法是库存原型 CapacityManager 作为指导。不幸的是,pg 278上的模型不能在线获得,但可以查看ServiceInventory原型。

教师正在向学生出售他们的服务。 (上次我检查时教官得到了工资:)。如果我要将您的示例映射到Inventory原型,我会使用:

  • 库存 - 工具/课程清单
  • 服务类型 - 工具/课程详情,开始结束等
  • ServiceInventoryEntry - 工具+位置可用(使用容量管理器)
  • ServiceInstance - 注册 - 上课(预定,预订,取消,已完成)
  • CapacityManager (由ServiceInventoryEntry使用)
  • ReservationRequest - 注册请求

我认为添加的关键概念是CapacityManager:您可以调用ServiceInventoryEntry :: getCourses()方法,该方法将使用CapacaityManager(服务或类)向您显示/计算可用教师或返回默认教师。因此,根据上下文调用多次:默认或建议可用的位置/席位/教师列表。

使用此模型,您应该能够找到要验证的自然位置(地点和时间)。 Streamlined Object Modeling的指导是将验证放在数据所在的位置。不要把它当作一个硬性规则,但是对象自然倾向于将适当的关注点和数据组合在一起。例如,容量管理器知道注册和工具。 (来自MDA - CapacityManger:通过发布ServiceInstances管理容量利用率)

要让您的聚合看到您将执行的事务/更改,以便确保它们强制执行不变量(规则)。在您的示例中,我将使ServiceType(Course)成为值对象,ServiceInventoryEntry和ReservationRequests聚合根。 (取决于你想要遵守规则的复杂程度)。您还可以根据MDA书籍将学生和教师添加为​​参与方。我倾向于使用存储库来获取我的聚合,然后依赖于你引用的Jimmy的书中的控制反转。

我喜欢MDA模式的原因是它让我想到了我或业务无法想象的用例和模型概念。但是,要小心只模拟您需要的内容,因为MDA模式可能很大甚至很诱人。好处是它们被设计成模块化或“向下扩展”。

简而言之: - 您的聚合根应确保您的域处于有效状态(规则/不变量) - 将验证放在数据所在的位置。您的模型将指导您的。