领域驱动设计-软件核心复杂性应对之道:第七章

发布时间 2023-05-22 18:42:57作者: LHX2018

第七章 使用语言:一个扩展的实例

7.1 货物运输系统简介

1)跟踪客户货物的主要处理部署

2)事先预约货物

3)当货物到达其处理过程中的某个位置时,自动向客户寄送发票

一个货物从货主手上通过托运公司运输货物,从起始点到目的地,托运公司(可能只负责一段路途,再由合作伙伴/外包/私人等接力)负责计划路线,可能由多种运输方式组成,还要区分比如:省内,省外,跨国运输等。空运+水运+陆路运输(大卡车/火车)等,每种运输都有中转点(换人不换运输方式,换人换方式,交接手续),最终到达目的地

  • cargo(货物 大宗货物 比如大米,有几十个集装箱。石油,多少罐)
  • customer(客户:货主,收货人,货运公司,货轮,航空公司,货车司机,火车货运公司,海关,港口码头,仓库管理公司),客户角色
  • specification(规格)
  • carrier(运输载具):车/船/飞机/火车/人力
  • carrier movement(运输动作):某个carrier(车/船)执行的一个从一个location(地点)到另一个location的旅程,cargo被装上carrier后,通过carrier的一个或多个carrier movement,就可以在不同地点之间转移
  • handing event(处理事件:装货,卸货,收货人提货)
  • delevery history(运输历史):反映了cargo实际上都发生了什么事情

成功的运输将会是一个满足delevery specification目标的delevery history对象

7.2 隔离领域:应用程序的引入

1)tracking query,跟踪查询,访问某个cargo过去和现在的处理情况

2)booking application(预定应用),允许注册一个新的cargo,并使系统准备好处理它

3)incident(事件) logging application(事件日志应用),它记录对cargo的每次处理(提供通过tracking query找到的信息)

7.3 将entity和value object区别开

  • customer ENTITY

  • cargo ENTITY

  • handing event ENTITY

  • carrier movement ENTITY

  • location 名称相同的两个地点不是同一个位置,经纬度可以提供一个唯一键,但这并不是一个非常可行的方案,location更可能是某种地理模型的一部分,这个模型根据运输航线和其他特定于领域的关注点将地点关联起来。因此使用任何一种自动生成的内部标识符就足够了。

  • delivery history ENTITY (流转历史)

  • delevery specification VALUE OBJECT (路线规划)

  • role VALUE OBJECT (客户角色)

7.4 设计运输系统中的关联

image

7.5 aggregate边界

customer、location、carrier movement、cargo(delivery history、delivery specification、handing event)

7.6 选择repository

有5个entity是aggregate的根,因此在选择存储库时只需考虑这5个实体,因为其他对象都不能有repository
image

7.7 场景走查

7.7.1 应用程序特性举例:更改cargo的目的地

delevery specification删除,创建新的

7.7.2 应用程序特性举例:重复业务

相同customer的重复预订往往是类似的,因为他们想要将旧cargo作为新cargo的原型。原型模式

  • delevery history:创建新的
  • customer roles:复制
  • tracking id:提供新的tracking id,它应该来自创建新cargo时的同一个来源

7.8 对象的创建

7.8.1 cargo的factory和构造函数

//cargo是aggregate的根
public Cargo(String id){
  trackingID = id;
  deleveryHistory = new DeleveryHistory(this);
  customerRoles = new HashMap();
}

7.8.2 添加一个handing event

货物在真实世界中每次处理,都会有人输入一条handing event记录

//CARGO ID,完成时间和事件类型
public handingEvent(Cargo c,String eventType,Date timeStamp){
  handler = c;
  type = eventType;
  completionTime = timeStamp;
}

//装货事件
public static HandingEvent newLoading(Cargo c,CarrierMovement loadedOnto,Date timeStamp){
  HandingEvent result = new HandlingeVENT(C,loading_event,timeStamp);
  result.setCarrierMovent(loadedOnto);
  return result;
}

模型中的Handing Event是一个抽象,它可以把各种专用的Handing Event类封装起来,包括装货、卸货、密封、存放以及其他与Carrier无关的活动。它们可以被实现为多个子类,或者通过复杂的初始化过程来实现,也可以将这两种方法结合起来使用。通过在基类(handing event)中为每个类型添加factory method,可以将实例创建的工作抽象出来,这样客户就不必知道实现的知识。factory必须知道哪个类需要被实例化,以及应该如何对它初始化。

image

7.9 停下来重构:cargo aggregate的另一种设计

image

7.10 运输模型中的modele

image

7.11 引入新特性:配额检查

公司可以根据货物类型、出发地和目的地或任何可作为分类名输入的其他因素来制定不同类型货物的运输配额。这些配额构成了各类货物的运输量目标,这样利润较低的货物就不会占满 配额而导致无法努书利润较高的货物,同时避免预定量不足(没有充分利用运输能力)或过量预订(导致因频繁地运输超出运输能力而取消客户预订,最终损害客户关系)

现在,他们希望把这个功能集成到预订系统中。这样,当客户进行一个预订时,可以根据这些配额来检查是否应该接受预订。
image
连接两个系统

销售管理系统和运输系统,创建一个类,做模型和销售管理系统语言之间的翻译。

划分业务

这种类型的货物可以接受多少预订,cargo的类型是什么

企业部门单元(enterprise segment),value object,每个cargo都必须获得一个enterprise segment类

image
allocation checker将充当enterprise segment与外部系统的类别名称之间的翻译。cargo repository还必须提供一种基于enterprise segment的查询。在这两种情况下,我们可以利用与enterprise segment对象之间的协作来进行操作,而不破坏segment的封装。

问题:

  1. 给预订系统分配了一个不该由它执行的公众,执行业务规则属于领域层的职责,而不该在应用层执行
  2. 没有清楚地表名预订系统是如何得出企业部门单元(enterprise segment)的
    image
    1.获得货物的enterprise segment

2.根据enterprise segment获得预订的数量

3.传入cargo和已预订的数量,查询是否在范围内?

enterprise segment是一组维度,他们定义了一种对业务进行划分的方式。这些维度可能包括我们在运输业务中已经提到的所有划分方法,包括时间维度