层次架构的困惑

介绍一下项目的现状先:

大体介绍:
表示层使用JSP,控制层使用STRUTS2,业务逻辑层使用自己封装的service类,持久层使用SPRING的JDBCTemplate封装SQL。

我现在的调用流程是这样的:
JSP(表单参数[参数形式:model.propertyName])--->ACTION(引用一个实体对象作为成员属性,struts2框架自动封装请求参数到实体对象)
--->调用service层的业务逻辑方法(层之间的参数传递依然是ACTION中封装的实体对象)---->service方法处理实体对象(实体对象可能嵌套)并调用多个DAO操作,且封装事务
--->DAO操作(从service层传过来的参数依然是ACTION中的实体对象,只是由service处理过了)

也就是说,层与层之间的接口的参数,我都使用了一个实体对象(当然实体对象会有嵌套关系,这样就可以操作多个实体),
或者在明确了参数仅需要ID的时候,参数为int型的ID(如:根据ID查询某条记录的详细信息时)

有几个困惑如下:
1. 表示层的表单参数的name感觉依赖于实体对象,尤其是实体嵌套的情况,比如某个表单项可能要这样写:name=user.department.deptName,甚至更长.
想问一下,这样好吗?表单参数和实体类属性纠缠在一起
2. 查询的时候,我喜欢直接使用spring的jdbcTemplate封装,使用queryForList等方法,直接返回LIST或MAP,从DAO方法返到SERVICE方法一直到ACTION中,
再将LIST或MAP添加到request中,在JSP页面上,直接使用EL表达式${list.数据库字段名}来显示
这样有个坏处,表示层直接和数据库字段扯在一起了...但是确实方便,想请教一下,老师们是怎么做的?
是不是这样:DAO负责从数据库中select数据,并封装成相应的实体对象或实体对象集合,然后返回到页面去展示吗?
3. 接着问题2,想问一下,查询得到的数据,有没有一种办法,可以灵活动态的定制字段列表,比如同一个查询,但是我需要的显示字段不同,
类似于[粗略展示记录列表]和[某记录的详细信息]这两种情况
我目前是将所有的字段全查出,封装成list或map,一并送去表示层,需要的显示,不需要的就不显示,这样应该是浪费传输资源的吧...
4. 是不是我的这种做法,实体对象就是传说中的DTO ? 我这种情况是否属于DTO满天飞的情况....老师指点啊
5. 看了不少相关文章,记得文章中说过,使用Hibernate的情况,实体对象的持久性质(如ID),不应该带到表示层去,应该剥离持久性质再传到表示层去显示...
这里有个疑问:比如我选中某条记录进行修改,步骤是:
1.根据选中记录的ID取出记录的详细信息并显示--->2.修改详细信息--->3.请求ACTION-->4.业务逻辑-->5.DAO做出修改
其中步骤2修改的页面中,肯定要保存一个hidden表单项---即该记录的ID,以便请求修改时知道所修改的是哪个记录?

老师们,耽误时间指点一下了...多谢!
[该贴被edison87915于2011-12-12 20:04修改过]

同志, 关于您的困惑, 我有以下几点建议, 纯属个人意见.
从您描述的这个情况来看, 其实你的问题只出现在2个地方, 1是架构上的数据传输. 纠正您的一个错误. DTO 是 Data Transfer Object, 数据传输对象, 并不是您说的实体对象. 2. Struts2 View层没有理解透和学习透.

以下对应您提出的困惑.
1. 对于Struts2的View层, 您的这个问题早意见在Struts2中解决, 只是您还不完全熟悉它的标签. OGNL可以处理这种常见的情况. 配合Struts2标签可以直接将"深对象"(引用比较深层次)放在值栈上. 方便读取和赋值. 在您的JSP上也很方便的运用. 具体您可以去查阅Struts2 文档. 在下不便说透.
2. 3.4.5这几个问题. 我想给出处于同一个根源的答案. 现在基于3层的开发模式很是流行. 但是有些老师只是教授了大家如何实现3层, 并没有结合整个项目和对象生命周期来探讨在层之间传输对象的最佳实践. 这里还是给出一个指引, 您去熟悉了DTO在回想您的问题. 答案迎刃而解.

2011年12月12日 20:02 "@edison87915"的内容
表单参数和实体类属性纠缠在一起 ...

楼上回答挺好,我补充一下,在复杂系统下,表单参数确实不应该和实体类纠缠在一起,可以专门建立一个表单参数。

查询时是否让数据表字段直接和界面耦合的问题,如果采取CQRS,也就是读写分离架构,那么为了报表输出或各种查询灵活性,读操作直接和数据表字段耦合也是正常的;

但是写操作需要在界面和数据库持久层之间有一个领域层,实体对象属于领域层的,写事件专门针对领域层的实体状态操作。

这样架构为:
Jsp --> Action ---> Service接口 -- >调用领域层中业务逻辑方法 --->仓储。

这个架构和你目前的区别是Service层,在Service层不应该直接加入业务逻辑,而是调用领域层,实体属于领域层,持久化DAO操作属于实体对象的一个行为职责,实体对象可能还有其他职责比如规则约束等其他业务逻辑行为实现。

相关帖:OOD vs SOA

也可参加这个重构架构帖子,亮点是:原来service只是作为提供业务的服务对象,而不是在里面实现业务的地方:http://www.jdon.com/jivejdon/thread/43502#23137901
[该贴被banq于2011-12-13 09:25修改过]

banq大哥,小弟在此探讨下,领域层,相当于在数据层和服务层之间再架起一层。而这层中的业务逻辑可以放在内存中来处理。

2011年12月13日 09:20 "@louisni"的内容
,领域层,相当于在数据层和服务层之间再架起一层。而这层中的业务逻辑可以放在内存中来处理。 ...

可以这么理解,读操作就直接耦合数据库,充分利用关系数据库的数据相关性能力,然后在写操作中引入异步事件(根据领域对象的状态对一致性要求高低),过渡到CQRS或Event Sourcing。

谢谢您的回复,DTO和VO应该是差不多的概念吧,只是DTO担负了层之间传输的责任。我的出发点是根据业务实体,创建出所需的实体对象,只不过这个实体对象是没有行为的。因为项目比较简单,不需要另外扯出DTO、VO等对象,感觉实体对象也可以做为各层之间方法调用的参数,这样接口会更稳定。
OGNL是很强大,使用也很方便,但是,我想展示深层对象的属性值时,不还是要从OGNL的根一层一层引用到深层对象的属性么?感觉直接用一个MAP将对象封装成一个2维平面,直接在页面引用${map.xx}更方便么。呵呵,是不是不OO了啊。
提交页面的时候,使用OGNL可以给ACTION引用的参数对象赋值么,不太明白,一般表单不是这样吗:
<input type="text" name="xx.xx.xx" value="..">
真的很感谢

banq老师,我想问一下,领域层存在的是领域模型吗,领域模型就是包含属性、行为和关系的类吗?我所做的项目比较简单,service层中的很多方法已经充当了DAO的代理,真正需要事务封装的、同时操作多个DAO操作的方法占一小部分。我想在我这个场景下,应该不能再加领域层了,不然像是用原子弹炸兔子。

多次听您提及EventSourcing,我对这个词一头雾水,想问一下您,哪些层面的知识涉及到了这个东东。。

读写分离架构先学习下再来讨论,谢了BANQ

另外:贴一个我的service方法的代码,想问一下,简单点的应用程序,service层应该是这样的吗?


/**
* 移交合同[填写合同移交表]
* @[author]param[/author] contractTransfer
* @return
*/

public int transferContract(ContractTransfer contractTransfer){

//设置ID
contractTransfer.setTransferId(contractTransferDAO.newID());
//设置移交序号[年月日+三位日次]
contractTransfer.setSerial(contractTransferDAO.serial());
contractTransferDAO.insert(contractTransfer);
//更新相应备案记录的移交日期
contractTransfer.getContractRecord().setTransferDate(contractTransfer.getTransferDate());
contractRecordDAO.updateTransferDate(contractTransfer.getContractRecord());
return 0;
}

/**
* transferContract的包裹了事务的代理方法
*/

public void transferContractProxy(final ContractTransfer contractTransfer){
transactionTemplate.execute(new TransactionCallback(){
public Object doInTransaction(TransactionStatus transactionStatus) {
return transferContract(contractTransfer);
}
});
}

[该贴被admin于2011-12-13 10:31修改过]

2011年12月13日 10:17 "@edison87915"的内容
您提及EventSourcing,我对这个词一头雾水,想问一下您,哪些层面的知识涉及到了这个东东 ...

参考这个帖:http://www.jdon.com/jivejdon/thread/43486/5

如果你的DTO开始多起来,已经成为拓展障碍,建议采取CQRS架构,将DTO局限在读操作;写操作直接针对实体。项目简单时如果也这么做,更显得架构的清晰性,不一定要等到复杂时再重构,那是可能已经没有时间或精力,无力挽救了。

2011年12月13日 09:10 "@banq"的内容
service只是作为提供业务的服务对象,而不是在里面实现业务的地方 ...

不知道自己明白了这句话没有,当前我的做法:service层中所定义的,相当于每个用例对应的系统接口,即service中每一个(public的)方法都是要完成某个具体操作的,在这个方法中,组织了一些"我所谓的实体对象-贫血"和对应的一些DAO对象,进行业务逻辑的处理。
按照banq老师的理解,应该在service层与持久层之间再分出领域层,我的理解是:您所讲的领域层,应该是一个个的实体对象,这些对象拥有属性、行为,并且引用相关的DAO类,service层只负责调用领域层中各个实体对象的业务行为,并且实体对象的行为中负责调用DAO来实现持久化?

有些问题可能太2了,看的书还少,如果老师觉得我水平欠佳以至沟通困难,可以指明,或者推荐几本书籍参考,呵呵,谢谢banq

有新的理解了:
表单、以及控制层中的封装表单信息的对象,实质上相当于业务对象,一般对应一个业务用例,可以这样理解,即便没有软件系统,这些表单也是客观存在的,比如在没有软件系统时要手工填写的表格。
而领域对象,我的理解是实体对象,很有可能和业务对象不同,是对业务对象的一种抽象,这么说还有点乱。实质上,复杂点的情况,一个领域对象可能横跨多个业务用例,比如一个业务用例对应一个表单(边界),但这些表单信息并不能很好的描述一个对象模型,或者说不能很好的分析成一个便于理解的对象,这时,应该为这多个业务用例抽象出一些领域对象(实体对象)。
而对于一些简单的情况,所填写的表单本身就能很好的描述成一个对象模型,这种简单的情况下,我觉得领域对象实际上和"在控制层封装表单信息的对象"几乎没有区别