1.关于Action中populate数据的问题:
Action的一个职责就是:创建领域对象的实例,并将从页面传来的数据populate到实例中。这个过程有一个问题:即,如果建新的对象需要的是另一个领域对象,这时候,页面传递过来的只是这个对象的id,那么这个propoerty将如何set呢?
比如在新建一个Thread的action中,要为新的thread设置forum,而页面传来的只是forum的id,那么,是在action中load这个forum,并set给thread还是在service中呢?
解决方案:使用DTO会解决这一系列的问题!同时使领域层具有更好的自封装性。也不再需要opensessioninview! DTO只是数据的载体,这与展现层的基本需求(只是读写数据而不做业务处理)的特点是相适应的。当DTO到达service后,再拆解数据,做一系列的业务操作是很好的。
引入DTO的好处在于:可以把领域对象封装在领域模型内部。本质上讲,视图层需要的仅仅是从领域层得来的数据,当然它自己也有逻辑,不过这些都是展示逻辑,与业务无关,所以说,我应该只传送数据就好。另外,如果使用领域对象做为数据载体在领域层和展现层之间进行传递,会有一些问题:如果领域对象的创建较为复杂,那么在Action里创建领域对象会变得很不方便,因为毕竟不是在领域模型里,简单例子是,如果我们需要的对象A是通过对象B的工厂方法创建的,那么这个在Action里写出来就会很怪异。另外,DTO并不是与领域对象一一对应的,它是粗粒度的,面向视图层的。这也是引入DTO的一个目的。
2.在应用层关于service和data的设置意图。
显然,在应用层,不存在面向对象那种数据和行为的封装,因此, service承担了“行为”,data承载的“数据”。在应用层内部,我们会创建领域对象,并把data的值populate到领域对象里,然后再通过领域对象的业务方法执行业务操作。这一模式是非常合理而有效的。
3.关于DTOAssembler装配DTO的问题:
一般来说,从领域对象抽取数据装配DTO没有问题,因为领域对象包含全部
可能的数据,装配DTO时只是一个选取的过程。但是反过来可能会存在问题
。比如:保存一个Thread需要设置它所属的Forum。从页面传递过来的Threa
dDTO中除了Thread的基本信息外,还提供了一个ForumId.对于Assembler来说,如果想要组装一个Thread领域对象,需要通过ForumId加载出一个Forum实例来(注:在hibernate里,要通过session.load方法加载一个代理,这样才不会有效率问题),但这需要repository的协助。
4.关于是否需要引入ServiceFacade的思考
目前工程已经引入了DTO。按照标准的j2ee模式,DTO总是配合ServiceFacade一起使用使用的(4月27日做出的修正:DTO并不只是配合ServiceFacade使用的,基本上我们需要引入DTO,那么ApplicationService也必将使用DTO。并不是说DTO只能出现在Facade的接口里。这一点在PoEAA和CoreJ2EEPatterns两本中的代码中都有所体现)。对于是否引入这一组件我的思考是:
传统意义上来看,DTO是面向Client(或者说是展现层)的数据结构,ServiceFacade是面向Client的UserAction,或者说是面向UseCase的。DTO和ServiceFacade联合起来构成一个Client(或者说是展现层)和业务层(领域模型)之间的适配器。典型的情况是:如果一个UserAction需要多个Service共同工作才能写成时,ServiceFacade的作用就突显出来了。它本身并没有业务逻辑,但是它会将Action的请求委派给合适的Service进行处理。
这个理念看上去很合理但是实际运用起来并不容易。首先这里有一个Facade和Service的粒度问题。如果说一个Facade的方法对应一种Action,那什么样的UserAction会需要委派给多个Service呢?具体又应该是几个呢?Service的粒度标准又是什么呢?有人说Service是面向业务的(在TransactionScript类型的工程里尤其如此),我看确切的说它的内部是业务处理,它的那些方法究竟是面向什么(或者说是以什么为依据)而梳理成一个方法的呢?我想这个标准并不好找,我们只有一个最上面的一致标准:用户请求,也就是Facade的粒度标准。问题就在这里,对于Facade与Service之间的粒度划分并没有一个很清晰明了的标准。严格来说,Facade与Service都是应用层产物,应用层的职责就是将外部应用请求(比如一个UserAction)委派给领域层进行处理。从这个角度上来看,应用层接口的粒度只有一层标准,我看Facade与Service之间真得找不出很好的粒度切分标准。绝大多数情况下一个Facade方法都是委派给一个Service方法也证明了这一点。于是我想不如将两者合二为一来得简洁利落。至于真得出现有两个以上的Action需要执行一组同样的业务操作时,我们就把这个组操作重构成一个独立的Service方法。
4月27日:通过对ServiceFacade和AppliationService的继续思考,我有一些新的认识:
之前提到,当一个用户请求需要多个Service来共同处理时,Facade的作用就会凸显出来,这确实是引入Facade的决定性原因。我想这种情况是确实存在的。当这种情况出现时,在Client的Controller方法里就会出现这样一种现象:这个方法调用2个以上的Service来委派用户的请求。这就是一个信号,说明应该把这些Service封装到一个统一的facade里面了。或者反向推论:如果在某种规范的约束下,要求我们只能以一个Service的一个接口来处理这个请求时,你可能会想到,是否需要让这个Service依赖那些用到的Service呢?显然,Service之间是不能用依赖的。这也从反向说明,我们确实需要一个更高层的封装来把这些用到Service放到一起了。
基本上ApplicationService是以实体特别是聚合根为单位组织的。我相信会有一些很heigh level的请求需要跨越多个实体或者聚合来实现的,这个时候我们就需要Facade的了。
这就是上面我反复思考的Service和Facade之间的粒度标准问题。简单总结一下就是:ApplicationService是以实体特别是聚合根为单位组织的,它的接口是直接面向用例(用户请求者是UserAction)的.而Facade的粒度没有明确的上限(或许你可以为一个模块设计一个Facade),但其下限是很明确的,即针对那些跨越两个以上实体或聚合的用例而服务。它的接口同样是面向用例的。
不过有个问题需要注意:即UserAction或许可以成为一个力度标准,但是一个UserAction可是有大有小,有时候差距是很大的。这个要引起注意。
PS:这些Service接口传入传出的数据还是DTO。
2010-4-26:今天,通过查阅《PoEAA》,我对这个问题有了一些更为深入的认识。
You don't see Remote Facade used with a Transaction Script (110) as a rule, since a Transaction Script (110) is inherently coarser grained.
第一,必须明确:RemoteFacade只存在于基于DomainModel的系统架构中。因为领域对象是细粒度的,它们需要Facade. 而TransactionScript的系统是不需要的,因为TransactionScript天生就是面向粗粒度的!
对于Service层的再认识:
我认为Service就是应用程序的API,同时也是事务和安全管理最佳层面。
在基于TransactionScript里系统里,Service非常厚重,包含了绝大多数的页务逻辑。
而在基于DomainModel的系统,Service只是一层很薄的接口,或者说是DomainModel的Facade,它通过委派领域对象完成业务处理。
Applications implement use cases that coordinate multiple Business Objects (374) and services. However, you shouldn't implement use case-coordinating behavior specifically within Business Objects (374), because it increases coupling and reduces cohesion between these Business Objects (374). Likewise, you don't want to add this business logic to a service facade, because the business logic potentially gets duplicated among different facades, reducing the reusability and maintainability of common code.
实现一个用例(或者简单地说一个UserAction),在DomainModel有两部分的逻辑,一部分是领域对象自身的行为逻辑(也就是方法),另一部分是协调各领域对象的逻辑,这也是非常重要的一部分。你不能把这部分逻辑写到领域对象里,这样领域对象就会耦合在一起。那这部分逻辑的在哪里呢?应用服务里!
ApplicationService是包含业务逻辑的,它的业务逻辑就是领域对象之间如何协作的逻辑!本身上讲它是领域对象的Facade!
In non-EJB applications, where you need to reduce coupling between the presentation-tier components and business-tier components such as Business Objects (374) and other services, Application Services provide that intermediary function between the two tiers. An Application Service exposes a finer-grained interface than a service facade and a coarser-grained interface than the underlying Business Objects (374) and other services.
应用服务的接口比ServiceFacade细,比领域对象粗,是面向用例的!
而ServiceFacade则是另外一种东西了,它是ApplicationService的Facade。那ServiceFacade到底是面向什么的呢?我们又回到了开头!
请参考一下Core J2EE Patterns一书ApplicationService一节的SampleCode吧,我想一个Facade对应一个模块会比较合适吧,但是又为什么非要设计出这个Facade呢?
但是至少有一点我们现在非常明确了:
那就是ApplicationService传入传出的数据是DTO!这一点在PoEAA和Core J2EE Patterns一书的代码中都有验证,而ApplicationService的职责也决定它就应该使用DTO!(当然是在需要DTO的系统里)
关于DTO与所依赖的领域对象:
当们需要persist/update一个领域对象时,页面数据以DTO形式传入service,当这个领域对象依赖其他领域对象时,页面上传来的多是这个对象的ID。那么我们如何通过ID加载出这些对象并设置它们之间的关系呢?
比如:当我们新建一个thread时,它会依赖到的实体有一个forum,和一个user.那么页面传到service的只是一个forumid和一个userid.我们有两个问题:
1.这些ID是放到DTO里还是单独作为参数传入。
2.在哪里load这些实体并将它们与thread关联起来。
先思考第2个问题:我目前有很明确的倾向,即load这些实体并将它们与thread关联起来的工作不应放到assembler里!因为将实体关联起来本身就是业务逻辑一部分,放到assembler里会使业务逻辑“外流”。另一个原因时,很多时候,关系实体可能并不只是一个简单的set就能搞定的,很多时候可能会有比较复杂的业务逻辑。所以我倾向在service里load实体并将实体关联起来,这样也可以保证assembler不会依赖任何repository!
对于第1个问题,并不是一个很重要的问题。放到dto里应该会使service的接口简洁些吧。
为什么去掉了Forum的createThread()和Thread的createPost()方法?
思前想后,我还把这些简单工厂方法给去掉了。现在看来,我觉得这其中的道理可以做为一种经验记录一下:
我认为工厂方法最好能生产完备的产品。如果生产出的是一个半成品,我们还要在其他地方为这个半成品再赋值和加工的话,这个工厂方法可能就不那么必须了吧?
应用服务k考虑一下:像Swiz那样dispatch领域事件,然后委派给repository进行数据访问的方案!
A
a
a
a