DDD案例:网上书店
网上书店是采用DDD设计思想构建的一个应用系统示例,实现网上书店的常用功能:包括浏览书籍、挑选书籍、提交订单、查看订单、自动折扣、处理订单、取消订单等。未登录用户可以浏览和挑选书籍;已登录用户可以提交和查看自己相关的订单;管理员可以处理订单。
经过业务抽象,即使是这样一个简单的业务场景也包含了很多领域对象,例如订单、账户、书籍、购物车、购物项、折扣等,通过分析和设计,可以得到如下的设计图:
BookStoreAction负责处理展现层的请求,并把请求转发给业务服务IBookStoreBS,业务服务负责调度上图中显示的领域对象,处理该场景的所有业务。
其中领域对象和现实业务的对应关系为:
Account——账户
Order——订单
Book——书籍
Cart——购物车
Item——订单项
Discount——折扣
与事务脚本的编程模式不同,领域驱动设计不是把业务逻辑放在BS(BusinessService)中,而是由具备属性、行为和状态的领域对象处理。例如Order类,它是一个相对独立的、能够处理自身关联业务的领域对象。在本系统中,我们对Order的描述如下:
订单的实现类是com.ddd.bookstore.model.Order,类中除了联系方式、邮寄地址等基本属性外,还有以下领域相关的行为:
init(...),结算时调用方法,根据当前用户与购物车中的Items初始化订单,供用户修改。
submit(...),提交订单时调用的方法,保存订单。
cancel(...),取消订单,把订单和相关item的状态设置为“已取消”,然后委托Dao进行持久化。
dispose(...),处理订单,首先更新订单项的状态,然后委托Dao持久化订单数据。
reSubmit、setItemsStatus......
通过以上的描述,我们可以看到,Order类基本上覆盖了现实世界中订单这个业务的所有行为和状态,是相对内聚的,这样的特性使其复用性大大增加,即使未来开发新的模块,涉及到订单业务的,可以直接复用Order类。同时在后期维护中,如果我想了解订单的业务,直接读Order的代码就可以了。
从上图中我们还可以清晰的看到各个领域对象之间的关系。Order和Cart都聚合了Item,对应都是1...n,Item聚合了Book,对应关系1...1。Order分别与折扣、账户发生关联和调用等等,整个网上书店的场景就这样描述出来了。
另外,不要忘了BS,除了起到基础设施的作用外(事务管理和服务共享),它还要负责调度和维护领域对象之间的关系。因为总会有些业务逻辑,既不属于这个领域对象,也不属于那个,那这部分业务由谁来处理呢?由BS来处理。例如在管理员处理订单这个场景中,首先需要根据订单信息获取账户,根据账户信息确定折扣率,同时进行余额校验,如果校验通过,就会调用订单对象的dispose方法处理订单,这个场景会涉及到Order、Account、Discount等对象,这样的业务逻辑,应该由BS实现。
IBookStoreDao是数据访问对象,可以被BS调用,用来持久化对象,也可以被领域对象引用,用来持久化自身。
通过以上的描述,我们可以看到,整个设计和实现是优雅、清晰的。业务逻辑没有堆积在BS中,而是分散在BS和各个领域对象中,服务和对象都与现实世界的业务息息相关,无论是对领域专家、开发人员和后期维护人员,都能这种方式中获得自己需要的内容。