关于Transaction Script、Domain Model模式讨论
这是 domain driven model design该如何设计的几个连接 :
http://gigix.blogdriver.com/gigix/166013.html 、http://gigix.blogdriver.com/gigix/168486.html
http://gigix.blogdriver.com/gigix/168634.html 、http://gigix.blogdriver.com/gigix/169972.html
现将其精华谪录如下:
评论人:charon
2004-05-23 15:08:51
hehe,tell和ask有区别吗?
在agent 交互的讨论中,一个一致的结论是实际上只需要tell就能把所有东西表达出来(虽然每个基于speech-act的语言不仅仅有tell原语)。而实际上,tell也好,ask也好,或者别的什么东西,一个动作或者交互的目的都是对接收方产生影响,所谓的原语是从影响的角度来划分的。
tell或者ask和是不是oo一点关系都没有。
。。。。。。
评论人:potian
2004-05-22 13:49:04
我说了一万遍了,service层的目的两个:
1 一般是客户代码,随客户而变
2 一般是服务代码,封装内部模型的复杂性
在分布式程序中的一个副作用是提高效率(通过DTO)
- 评论人:partech
2004-05-22 11:53:35
补充一下,关于Transaction Script,Table Module,Domain Model 的选择从martin Fowler的那张图可以看出他的选择。我没有系统的研究过三者的差异,我感觉这对的。按照图上的含义,对于小型项目首选应该是Table Module,随着业务逻辑的复杂程度的增加Domain Model 将是首选。
在.NET环境中因为有ADO.NET类似“内存数据库”的支持使得使用Table Module很自然。Java环境中也可以通过组合纪录集的形式实现Table Module。对于批量类型的对象操作可以考虑Transaction Script,因为单个记录或者单个对象操作效率可能太低。
服务层本质上是facade,确实不应该包含业务逻辑。
- 评论人:partech
2004-05-22 11:24:22
按照你们现在的做法,业务层还不能说是面向对象,因为对象的数据和操作这些数据的行为不是封装的而是分离的。
要发挥面向对象的所有威力需要将整个Domain层对象化,行为和数据必须封装,这是最基本的,然后还要考虑分层,对业务层进行抽象等等。
你们的做法我说一个缺点,不知道你们的应用同参与者是否只有一种交互形式?比如只通过WEB。然而参与者进行业务可能有很多种方式,拿银行来说吧,可以通过柜台,也可以通过ATM,还有电话,和网上银行。很明显这些应用后台都需要服务,而且这些服务的交互方式是不同的,也就是用例不同。所以Service层会不同,DTO会不同。但是如果我在这些应用中都进行转账业务,那么不管我使用的是哪一种途径,负责转账的代码都该只有一份。Service层的代码没多少复用价值,真正需要考虑的是Domain层的复用。
领域对象不单包含实体对象,还应该包含控制对象。很明显转账的整个操作没法划给单个对象,但是分解出的转出和转入操作还是可以作为帐户实体的方法的。
不过我感觉有人常常将领域层的控制对象同应用层的服务对象混淆。区分两者很简单,一个是不针对特定应用的,一个是针对特定应用的,也就是系统中烟囱的部分。
服务层充当的是系统中烟囱的部分,所以在不同应用中不考虑复用。或者说尽量避免进行复用,这样各应用就可以独立演化了。
可以看看下面的网站,作者的书里提到了很多如何进行领域层开发的方法。
http://domaindrivendesign.org
。。。。。。
下午Jacques Lebrun帮忙找到了Domain Driven Design,还没来得及看。Martin Fowler说应该由Domain Model负责访问持久化逻辑,并借助Data Mapper封装持久化的实现细节。但是,我竟然找不到这样的一个例子。Spring JPetstore细看之下原来也是Transaction Script,只不过service的方法都非常非常简单,前天乍一看还以为和我们做的Transaction Script有什么不同呢。看JPetstore唯一的收获是:不要使用DTO,用轻量级持久化框架(例如Hibernate或者iBatis),直接把实体对象到处传。
评论人:Windy
2004-05-24 20:09:22
领域对象(etc.)调用finder调用mapper调用领域对象的构造函数创建原来已经存储在数据库里的领域对象,看起来好像是绕口令,实际上可以这么做,如果你真的想用DomainModal+DataMapper的话 :P
- 评论人:partech
2004-05-24 13:31:58
领域对象(或者说,实体)的创建由业务控制对象,其他领域对象创建或者在查找时自动创建。
领域对象的查找同样可以由业务控制对象,其他领域对象或者其他查找方法调用。
服务层最好调用业务控制对象,而不是直接调用实体对象。
DDD里面的Repository可以回答你余下的问题。
。。。。。。
评论人:potian
2004-05-23 20:14:27
模型不复杂的情况这样做就足够了,典型的CRUDSevice层在WEB应用中的作用特别明显,我以前很多桌面程序没有这样一个明显的service层,而如果只需要CRUD,DTO-》Service-》DTOFactory-》DAO就足够了,并且确实是最佳的方安。
但当模型复杂的时候,Service就需要通过模型来完成工作,或者从Service得到合适的领域对象,直接利用这些对象的行为进行操作 我在绍兴、兰亭玩
- 评论人:gigix
2004-05-23 20:05:35
但是OSUser和Delphi程序和web应用,我觉得有点不同,也说不上来究竟是哪里不同。恰恰是web应用,我发现好多人都是照我们现在这个做法来做的,比如spring jpetstore和appfuse。
今天下午本来想到你家去问你这个问题顺便蹭饭的,可惜你不在。
- 评论人:potian
2004-05-23 19:59:25
gigix,不要这样大惊小怪好不好,这样的例子到处都是,譬如OSUser。或者我以前写过的任何一个delphi程序,或者我写过的任何复杂一点的程序
而事实上没有一个纯粹的什么Domain或者纯粹的Transaction Script,(我对这些名词越来越讨厌了),很多时候需要混合, 譬如service-》model-》〔Manager〕DAO,或者是直接的service-》manager-》DAO
。。。。。。
评论人:potian
2004-05-24 04:58:27
模型的复杂性是问题域本身决定的,而不应该有采用何种体系结构来决定。
至于模型的持久有谁来负责还是由模型的复杂性来决定的,一个简单的没有多少行为的模型实际上就是一个实体模型(关系模型),它们的持久可以通过 O/Rmapping(或者JDBC)完全解决,因此我们可以通过DAO完全解决掉,这样就成了Service->Manager->DAO ->Entity(这时候领域对象退化为简单的实体对象),但是如果简单的关系模型不足以反映领域内部对象的复杂性,那么这些领域对象还需要依赖于多个管理其他领域对象的Manager来处理。在这种情况下,出于对方便利用IOC考虑,一般并不是在领域对象内部持有这个Manager属性,而是领域对象的某些方法里面可能会有Manager这样的参数,它们来自IOC到Service的注入,或者来自客户端通过IOC容器得到,然后显式地在调用该方法的时候传入。
- 评论人:potian
2004-05-24 04:34:04
有了这些基本的前提,就可以看到,如果领域模型并不复杂,基本的操作都是CRUD,那么我们可能会选择第一种方式,因为这个时候Service不会复杂到无法起到Service本身作用的程度,这个时候也没有多少东西可以复用。复用是以行为为核心,而不是状态。
如果是领域模型比较复杂,那么完全通过Service就可能导致Service非常复杂,这个时候Service层就作用不大了。我们会提供一些简洁的Service方法来获取不同的领域对象,然后把领域对象直接暴露给客户端使用。
还有一个影响使用何种Service的情况就是客户端的状态问题,假设我们的领域对象比较复杂,但客户端和领域对象处于不同的JVM,那么一种方式利用 Remote对象存取领域对象,例如EJB里面的远程对象,这种方式可以直接操作领域对象,但是却会造成性能上的问题,所以就会产生DTO这样的模式。
- 评论人:potian
2004-05-24 04:09:02
这两种情况下,service的作用和范围就大大的不同了
第一种方式下,所有的工作必须经过Service,好处是接口简单,很少需要直接和背后的对象打交道,但坏处则是Service代码经常需要随着客户界面或行为的变化而变化,去实现新的接口方法,新的对象交互方式
第二种方式,service的主要作用是能够新建和获取(包括查询方法,通常用来统计和查询跨领域对象的数据和对象
)到领域对象,绝大部分的操作度直接在领域对象上进行,坏处是需要知道很多后面对象的接口和行为,但好处是客户端的变化通常不影响到服务端的变化。
- 评论人:potian
2004-05-24 03:24:32
没有例外,因为Session一直还在
在Web 应用程序中出现很多疑惑的原因是HTTP的无状态,在一个桌面应用程序中,我们可以一直在客户代码中持有这个领域对象,不断地对这个对象进行操作,但在 Web应用程序中,这个就很难,因此通常必须依赖service来进行“一记头“的操作。传递的数据通常是简单的数据类型或者是简单的数据类型组装起来的 Bean对象。
而WebWork"官方“宣称有两种方式,一种把Action作为简单的控制器,这是“传统“的方式,这种方式就可能直接导致必须使用service,而存取的数据通常是原始类型的数据。而另外一种方式则是所谓的领域丰富的Action,因为Xwork的ognlstack和 Xwork的action不需要象struts一样依赖于任何其它(例如serlvet),因此可以(在Action方法或者在Interceptor) 中通过Service得到领域对象,然后直接在界面上存取领域对象,这种方式比较象传统的桌面应用程序。另外Xwork可以直接把界面上的简单数据类型转换为对象。
。。。。。。
用Domain Model替代Transaction Script
你的业务层采用Transaction Script模式实现
你希望将实现方式改为Domain Model模式
- Long Method / Large Class——由于放置Transaction Script的service组件涉及全部业务逻辑的实现细节,其方法很容易变得冗长;如果对其使用Extract Method重构,则会使service实现类变得庞大。
- Duplicate Code——不容易从现有的Transaction Script中找出可复用的代码,因此常常重复实现类似的逻辑。
- Switch Statements——在领域对象有继承关系时,Transaction Script需要对不同类型的领域对象做不同的操作,此时容易造成基于类型的switch语句。
- Data Class——领域对象只携带了数据,作为对象的职责不够。
- 使用Extract Method,消除Transaction Script中的重复代码。
- 使用Move Method,将领域对象固有的业务逻辑移到领域对象类中。
- 如果该方法需要使用DAO,则将DAO作为参数传入。
- 如果该方法需要使用其他service,说明该方法并不适合存在于特定领域对象类,而应该仍然存在于service组件中。
- 如果领域对象是一个继承体系,将业务逻辑移到其基类中。
- 如果第2步搬移的方法中有switch语句,使用Replace Conditional with Polymorphism消除之,将业务逻辑分配到领域对象的各个子类。
。。。。。。
评论人:gmark
2004-05-26 11:26:16
Domain应该关心持久化吗?我觉得应该把持久化的工作放在Service里做,XPlanner可以参考,不过它的所谓domain也挺单薄的。它没有做DAO,也没有分DTO和PO,直接在Action里开Session处理就完了。非常简单和清楚。
- 评论人:UltraFool
2004-05-25 20:54:52
我觉得把DAO作为传参数不好,至少
1)这样似乎违反了OO封装原则?客户端还要在调用方法的时候还要准备你依赖的东西,一一了解你的实现细节。如果DAO能传,service为何就不能传。 IoC整个都是把依赖关系都建立在类上,而这样却把它提到了方法上。何况domain不但是暴露给service,如果没有DTO它还会直接暴露给客户端。
2)如果Domain有N个方法都要DAO怎么办?多麻烦啊,而且这个数量多少取决于你的O/R Mapping的能力,hibernate也许直接能映射,不需要传DAO,但JDBC就不行,你的应用就没有移植性
。。。。。。
评论人:Ronald Matt
2004-05-25 11:02:25
http://timeandmoney.sourceforge.net/
这么Cool的代码是重构不出来的,因为重构只有Code Smell作为输入。
那究竟漏掉了什么?呵呵。
- 评论人:iseeisee
2004-05-25 10:48:19
这样:
service -----dao
| |
| |
domain-------
or:
service - domain - dao
- 评论人:iseeisee
2004-05-25 10:12:27
哦,这样也可以,我可能有点纯化了,
不过我想这样会不会让很多接口都包含dao的传递,而不是清洁的逻辑参数呢???
而且如果你的domain 对象包含其他domain对象,
这样2,3层的就更麻烦了。
- 评论人:透明
2004-05-25 09:52:08
DAO不是已经注入到service里了吗?service再统一调度domain object和DAO,把DAO作为参数传入。我觉得这个依赖关系挺好的。
- 评论人:iseeisee
2004-05-25 09:44:07
传入dao?
这样是否有违分层的原则:只与下一层通信。
我觉得dao也没必要注射了,直接全局访问
ServiceLocator.getXXXDao()
>>如果该方法需要使用其他service???
假设
1.
UserChanagePasswordService...
changePassword(String username,String password){
User u = UserManager().getUser(username);
u.changePassword(password);
}
User...
void changePassword(String password){
_password = password;
ServiceLocator.getUserDao().save(this);
ServiceLocator.getMessageService().send("成功修改密码:"+password);
}
2.
UserChanagePasswordService...
changePassword(String username,String password){
User u = UserManager().getUser(username);
u.changePassword(password)
//_messageService可以注射进来,
_messageService.send("成功修改密码:"+password);
}
User...
void changePassword(String password){
_password = password;
ServiceLocator.getUserDao().save(this);
}
呵呵~~~~,也分不出哪个更好些,
甚至可以:
user.addPasswordChangeListener(xxxxxxx);
不过这太难了,不好吧??
- 评论人:moxie
2004-05-25 09:11:19
看了gigix的blog,我意识到我们以前忽略的一个问题:为了项目层次的清晰(完全独立的Domain Model层、DAO层、Service层),而把Domain Model的功能剥离到Service层了,Domain Model只是一个简单的实体(实体最好不要涉及到业务操作)。层次分清晰了,却与面向对象的原则相斥了,也给Service层带来了不必要的负责。所以:属于Domain Model的功能必须按照面向对象的法则给其归属回去,Domain Model不能实现的业务再有Service来实现。
2004年10月18日 17:21







