DDD案例:网上书店

13-01-07 thinkjava
              

网上书店是采用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和各个领域对象中,服务和对象都与现实世界的业务息息相关,无论是对领域专家、开发人员和后期维护人员,都能这种方式中获得自己需要的内容。

              

23
gameboyLV
2013-01-07 21:04

2013-01-07 18:19 "@cloudstack"的内容
首先需要根据订单信息获取账户,根据账户信息确定折扣率,同时进行余额校验,如果校验通过,就会调用订单对象的dispose方法处理订单,这个场景会涉及到Order、Account、Discount等对象,这样的业务逻辑,应该由BS实现 ...

我觉得Cart和Account必须位于User领域之内,一个购物车必须属于某个用户,即使这个用户是“游客_123”。Account也必须要有一个唯一的所有者。

如果因为有了充血模型就完全放弃BusinessObject也不太好,服务接口的作用是调用仓储或切面,并对外部提供API。领域之外的业务完全可以放在切面或者BO里实现,不必硬编码到接口里。余额校验的逻辑可能多个接口都会用到,也可能随时变化。

现在接口的作用就仅剩下将SPI转换为API一个用途了,自然也不必调用DAO了。

banq
2013-01-08 09:37

问题有两个:

1.没有看到聚合根,这是DDD分析的核心。这里Book才是聚合根

2.Order和购物车都是Book参与销售活动产生的结果。不是领域的本质。

根本原因:注重了细节忘记了方向。

推荐你使用四色原型来对这个系统继续宏观把握,或者用故事的方式来简单描述一下这个系统。

建议你使用我总结Jdon分析法来分析这个系统,我引导如下:

这张图代表横向和纵向两个方向,分析一个系统要从这两个方向入手:

首先纵向,用DDD找出聚合根实体,代表这个系统的结构本质,很显然这里是BOOK。

再从横向:用户发出操作命令产生事件,在这个系统中,用户发出挑选或购买书籍BOOK的命令,也就是购书活动,改变了聚合根BOOK的状态。

什么状态呢?

1. 挑选命令致使BOOK放入购物车,

2.购买书籍命令致使BOOK加入了订单。

我帮助你分析到这里,下面我相信你知道如何实现了。

thinkjava
2013-01-08 12:08

2013-01-08 09:37 "@banq"的内容
1.没有看到聚合根,这是DDD分析的核心。这里Book才是聚合根

2.Order和购物车都是Book参与销售活动产生的结果。不是领域的本质。 ...

个人感觉聚合和聚合根(Aggregate Root)的东西有问题复杂化的倾向,用传统的聚合、组合等概念去描述领域对象之间的关系更容易理解,所以这里就没有描述聚合根

banq
2013-01-08 13:51

2013-01-08 12:08 "@cloudstack"的内容
感觉聚合和聚合根(Aggregate Root)的东西有问题复杂化的倾向 ...

不是复杂化,是思路不同而已,横看成岭侧成峰,如果你不从DDD角度看,你当然看不到聚合根,也无法知道聚合根是DDD的根本,这我在以前帖子反复强调了。

如果你这个帖子不命名为DDD案例,而是单纯建模案例,也许就无可挑剔了。

如果我们确定BOOK为聚合根,关键是订单是聚合根实体还是值对象的问题,如果我们这个需求中有网上支付和货物跟踪,无疑订单在这些流程中变成实体甚至是聚合根。

在一个简单的网上书店中,也就是没有那么多子领域系统,那么单纯以BOOK为聚合根的这个子领域边界中,我们可以把订单看成是值对象,用户买书的一种关系凭证,是一种活动的结果。

[该贴被banq于2013-01-08 16:02修改过]

6Go 1 2 3 4 ... 6 下一页