老妖的博客
现实的中没有几个人能够真为对方去死,甚至山盟海誓很快就会在金钱面前变的微不足道,这才是生活。没有永远的爱,除了你的父母对你,当然也就没有永远的恨,更没有永远的痛,时间是最好的治疗大师,它会很快抚平你心灵上累累的伤痕。很多年以后你想起来时,那些在你生命中汹涌来往的人群至多是个模糊的影子或者毫无意义的名字
posts - 105,  comments - 171,  trackbacks - 0
1public interface CompositeAct extends Act 
2
3Act[] getActs(); 
4}
 
一直想系统的整理一下自己有关Domain Model实践的尝试。但总觉得自己的想法还不够系统而作罢。
然而从另一方面看“系统的东西”也许永远做不到,失去了目标的生活该会多乏味。
因此我决定将自己有关Domain Model设计的有关实践和思考和盘托出,也算是抛砖引玉。欢迎大家
参与讨论,遇到同你的观点相左的地方,希望能以包容的态度来面对,我们是朝同一方向走的伙伴而不是
相互对视的敌人。:)

在深入讨论之前我先抛出一些原则和概念,最后你会看到这些概念和原则的威力。
1.按照概念依赖的原则来组织业务层。
2.将业务活动(业务流程)建模成类。
3.用业务活动(业务流程)作为关联整个业务层各种对象的骨架。
4.在业务活动中凿出扩展点,使用不同接口分离不同性质业务对象。
5.将对象的存储理解为业务层概念。
......

概念依赖

这是我认为能否得到良好业务层最重要的概念。
在我系统框架设计将要完成,开始涉及业务层设计时,我脑袋一片空白,书上,大家讨论的大多是整个系统的结构从UI层
到服务层到数据访问层到数据库。到底业务层该如何组织?Martin Fowler的POEAA的书中没有回答。找到的相关
书籍也都过于空泛。Martin Fowler的分析模式有些用处,但不够系统。透过Martin fowler网站,我拿到了
Domain Driven Design的发行前版本。该书给了我很大的启示。其中的要点有:
关于关联:
1.Imposing a traversal direction (强制一个关联的导航方向)
......
关于Responsibility Layers(业务职责层)的划分:
作者给出了三个指导原则:Conceptual dependency.(概念依赖)为其中一项。
书中给出的描述的是业务职责层上层的对象需要通过下层对象才能在概念上完整,
相反下层对象则可独立于上层对象存在含义。这样天然的下层对象相对于上层对象
会更稳定。并且在今后演变的过程中,使同扩展的方式来完善系统,而不是改变对象
的方式。
通过实践,我觉得这条原则可以应用在任何两个有关联的业务对象上。通常可以通过
概念依赖先建立一个导航方向。这能够满足大多数的需求。当确实需要反向导航时,
只要理由充分可以随时加上,并且如果先前将这两个对象放入不同包中,这时需要
将他们合并到同一个包中。
我见过一个不好的设计。Customer具有很多Flag分别标记该客户是否挂失,冻结,注销等等。
通常叫做客户状态,然而这是不对的,这违背了单一职责原则。事实上除了注销外
挂失和冻结都不应该算作Customer的本质属性。相反我把他们看作某种约束,进而把挂失看作
一种协议.....因为Customer的概念可以不依赖于挂失和冻结的概念,相反挂失和冻结却要依赖
Customer的概念,应为这是他们动作的主体。
同样的一开始就让Customer有GetAccount的方法同样不好。因为Customer的概念确实不依赖Account
XXXAccount却可以有Customer的属性,Account在概念上依赖Customer。
按照概念依赖原则我们能更好的理解业务职责层的划分。DDD中建议了如下的职责层。
按从高到低分别为:

依赖方向
| Decision
| Policy
| Commitment
| Operation
V Potential

Potential中包括类似Customer,Employee等Party方面的类。对应支持业务。
Operation中包括了核心业务如存取款,买卖以及同这些业务关联的Account,Product等等。
Commmitment对于客户包括同客户签订的协议等。对于员工来说包括授权等。
Policy包括计算某种收费的策略,比如电信收费的算法。对应支持业务。
Decision包括主要对应于管理业务。监控系统多在这层。
从上到下观察,很好的遵循了概念依赖的原则。
从另一方面来看,可以根据概念随着时间发展的顺序来建立对象之间的关系。这样会天然的满足概念依赖原则。
后面发展起来的概念可以依赖前面的已经存在的概念,而反过来这不可。这是系统稳定的关键。
同客户签订的各种协议可以不断的发展,但是客户对象是稳定的。
同理收费的策略可以变化但是最终反映到帐户上的都只是对Balance的变更。所以收费策略比
帐户更不稳定。
客户对象也要比帐户对象稳定。

从按照稳定的方向依赖的原则出发,我们可以得到对象间的单向依赖。当然也会存在双向关联
的对象,然而这种情况在我的实践中不是很多。而且一旦你懂得了单向关联的好处后,你就会
谨慎的使用双向关联。滥用关联会使得整个业务层象DDD中说的,变成一大块“果冻”,你随便触动
果冻某一块,整个果冻都会颤动。
同样为了简化设计,对象的关系中多对多的关系尽量避免。如果可以
则通过限定角色转化为一对多或一对一的关系。
以上是关于概念依赖的观念,下面让我们看看如何建模业务中的活动。
有一种做法是使用分析模型中的控制类直接映射到设计中类中。我看来这不是好的做法。
这里谈谈分析与设计的区别。
从分析的角度来看,业务实体总是被动的。业务是通过控制对象操作业务实体来完成的。
分析时我们是关注是什么问题。这要求我们客观的来描述现实。
进入设计阶段我们关注的是如何解决的问题。控制对象施加与业务实体的操作加入不涉及
第三者,则这个操作可以并入被操作的实体类中。然而分析中控制对象的概念是如此的
深刻,以至于只涉及Customer的ChangePassword方法该放到哪里都成了问题。类不是
“某概念 + 所关心该概念的属性 + 最终施加与这些属性上的操作” 的封装,又是什么呢?
下面的问题是如何建模跨越多个业务实体的操作?
举个例子:银行开户。
现在假设开户时涉及到下面的一些操作对象。
创建一个Customer对象。
创建一个CapitalAccount对象。
存入一定数额的现金。
记录一笔开户流水。
整个业务活动,我可以建模为OpenCustomerAct对象。伪码如下:
public class OpenCustomerAct extends CustomerAct 

 
public void override doRun() 

Customer customer 
= Customer.create(); 
CapitalAccount capitalAccount 
= CapitalAccount.create(customer,); 
capitalAccount.deposit(); 
OpenCustomerLog.create(
this); 
}
 
 
}
 

所需的参数通过构造函数得到。
将所有的业务活动都建模成一个Act,这非常重要。甚至你可以在Session中放入一个Act来
表示当前正在进行的业务。所有的扩展都是从Act开始的。
假如你要对Act施加某种检查,那么对doRun方法进行拦截可以达到该目的。
用例能够简化到只剩下流程,同样道理Act也可以做到这点。
对于象RichClient的交互模式,通常只在最后才提交业务,中间的交互都是在准备提交的数据。
那么在中间调用的方法中可以只new XXXAct而不执行doRun操作。这样做是因为中间的调用
可能会用到XXXAct来作为上下文。现在我还没有想好在这样的中间过程中,如何能够触发
植入到donRun前的检查?或许可以创建一个空doRun的子类覆盖掉父类实际的操作?
Act
1public interface Act 
2
3Operator getOperator();//谁 
4Date getOccurDate();//在什么时间 
5String getOccurPlace();//什么地点 
6BusinessType getBusinessType();//做什么业务 
7ActState getActState();//业务运行的当前状态 
8}
 
9

“谁在什么时间什么地点做了什么业务。”
这描述了任何业务的基本方面。从哲学的角度来看,“我们得到了Act,我们就得到了事物的基础”。
当我们具体的描述某项业务时,假如需要向调用方暴露特定的属性。
我们可以随时添加到Act的子接口中。
例如同Customer相关的Act可定义为:

 

1public interface CustomerAct extends Act 
2
3Cutomer getCustomer();//针对哪个客户 
4}
 
在复杂一点的情况下,如业务需要多人协作完成,可以通过组合模式达到目的。
涉及到一段时间有中间状态的工作流也应该可以作为Act的子接口进行扩展。
不过我没有做过这方面的尝试。

将Act放入Session

将Act放入Session使得可以方便得到业务运行的上下文。而且通过扩展Act。
可以从Act或其子接口中得到想得到的任何东西,这使得任何扩展都成为可能。

这里说明一下Act类的位置应当放入Potential层中,并且与Operator在一起。
因为Potential层的业务对象也需要业务活动来维护。
如果你的框架中Sesion在更基础的包中,则可以给Act提供一个空内容的父接口,放入Session所在的包中。
 1public interface AbstractAct 
 2
 3}
 
 4
 5public interface Act extends AbstractAct 
 6
 7 
 8}
 
 9Session提供得到AbstractAct的入口。 
10public class Session 
11
12 
13static public AbstractAct getAbstractAct() 
14
15return Instance().abstractAct; 
16}
 
17 
18}
 
posted on 2005-09-29 00:35 老妖 阅读(526) 评论(1)  编辑  收藏

FeedBack:
# re: Domain Model 探索(一)--摘自:javaeye
2013-08-14 10:03 | GoDaddy域名
博主的文章技术性都那么强啊  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航:
 

<2005年9月>
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

常用链接

随笔分类(48)

随笔档案(104)

好友链接

我的豆瓣

积分与排名

  • 积分 - 218777
  • 排名 - 256

最新评论

阅读排行榜