识别业务服务规约的名词,可以获得领域概念:候选人(Candidate)、协调者(Coordinator)、培训(Training)、课程(Course)、票(Ticket)、候选名单(CandidateList)、票状态(TicketStatus)、邮件(Mail)。
识别业务服务规约的动词,然后逐一检查该动词代表的领域行为是否需要产生过程数据。发现“提名候选人”,除了候选人获得培训票,还要记录票的变更历史,因而获得票历史(TicketHistory)领域概念;发现“员工学习过课程”,需要记录该员工的学习记录,因而获得学习记录(Learning)领域概念。
对识别出来的领域概念进行归纳和抽象,发现CandidateList实际上是Candidate的集合,用List<Candidate>即可表达,没有必要单独引入;邮件通知由专门的OA集成上下文发送邮件,在培训上下文中没有必要列出,故而可以删去Mail概念。
对于其他业务服务的领域分析建模,也如法炮制。由于培训上下文的业务服务皆位于同一个限界上下文,因而只需要考虑该上下文内部领域模型之间的关系。由此可获得图20-51所示的领域分析模型。
2.领域设计建模
领域设计建模牵涉两个重要的设计阶段:识别聚合和服务驱动设计。
(1)识别聚合
首先梳理对象图。确定领域模型对象到底是实体还是值对象,并分别用不同的颜色表示。一些较容易识别的值对象可以最先标记出来,例如体现了单位、枚举、类型的内聚概念等,如图20-52所示。
一些容易识别的实体类也可以提前标记出来。这些实体类往往是业务服务中扮演主要作用的领域概念,体现了非常清晰的生命周期特征。
ProgramOwner、Coordinator、Nominee和Trainee都是参与培训上下文的角色,都拥有员工上下文的员工ID,如此即可建立这些角色与Training和Ticket等实体类之间的关联。它们对应的角色(role)来自认证上下文,用于安全认证和权限控制。角色具有的基本信息,如姓名、电子邮件等,又来自员工上下文。因此,这些领域模型类虽然定义了ID,但在培训上下文中不过是其主实体的一个属性值而已,并不需要管理它们的生命周期,应该定义为值对象。由于培训上下文并未要求为培训维护一个单独的教师信息,故而与Training相关的Teacher应定义为值对象。
Filter和ValidDate都与Training关联。它们看似具有值对象的特征。对过滤器而言,只要TrainingId的值以及类型与规则相同,就应视为同一个Filter对象;有效日期也是如此,只要公式、日期和时间相同,就是同一个ValidDate对象。但是,由于它们的生命周期需要单独管理,将它们定义为实体更加适合。同理,ValidDateAction与CancellingAction也需要单独管理生命周期,应定义为实体。TicketAction却不同,它的差异仅在于具体的活动内容,而它又不需要管理生命周期,应定义为值对象。于是,获得图20-53所示的领域设计类图。
在确定了值对象与实体后,可以简化对领域模型对象关系的确认,即只需梳理实体之间的关系。一个Course聚合了多个Training,一个Training聚合了多个Ticket,这三者之间的组合关系非常清晰。一个Training可以配置多个Filter与ValidDate,但它们之间并非必须有的关系,故而定义为OO聚合关系。同理,一个ValidDate聚合多个ValidDateAction、一个Ticket聚合多个CancellingAction和多个TicketHistory、一个Training聚合多个Candidate和多个Attendance,而BlackList则是完全独立的。确定了实体关系的领域设计类图如图20-54所示。