请教开发中的分层问题,急!!!

现开发的项目,
持久层:hibernate
业务层:spring
表现层:没有使用什么struts框架,使用了本公司的一个框架,并且JSP中使用的C标签。

现在拿页面数据的保存和显示,来说一下我所要请教的问题。
一、背景环境:
(一)保存
1> 利用公司的框架,会自动把页面数据绑定到formbean(formbean中的数据对应于多个表中的数据)
2> 利用公司的框架,会执行相应的业务类,把formbean中的值分到所对应的各个PO中,并把这些PO存放到list列表中。
3> 然后调用业务服务类,把list列表中的各个PO,调用DAO的相应方法持久化。

(二)显示
1> 利用公司框架,执行相应的业务类调用DAO中相应的方法,进行数据查询。假如是通过userid查询获得用户信息,方法返回为User对象(即PO),并把此对象存在request的attribute中(以便通过页面的C标签获得)。
2> 通过C标签获得User对象的各个属性值显示在JSP页面上。

二、请教问题
公司前一个小项目的工作形式,如上面所说的保存和显示。
现感觉有点问题,请指点:
A> 数据显示:在页面显示时通过C标签直接获得PO对象,并显示其各个属性值是否合适?是否应在业务类中把查询得到的PO对象进行封装,把PO对象的数据对应封装到Formbean中,然后把此Formbean对象存在request的attribute中,最后通过C标签显示在页面上。
B> 数据保存:我现在采用的这种方式是否有问题?

现在想,无论是保存还是显示,是否都不应直接操作对应于数据库的PO?如果不应该,我是否应在业务层加入BO,查看了一些资料,感觉BO和PO没有什么区别,那我要怎么做呢?
为了使项目针对需求的改动(如表中加入一字段、页面显示格式变化等),能有更大的适应性,也就是最少的代码改动,并且只在一层做改动即可。
由于项目经验不足,所以不知如何做,请banq指点。
十分感谢!!

你这个框架可以说比数据库驱动框架要前进一步,应该说处于数据库和对象模型之间的中间道路,你的PO实际是数据库的影子,只不过以对象形式存在,必须先有数据库设计才会有PO,这实际还是数据库驱动设计思路,比较符合那些传统过程思维的人使用。对象PO还是服从于数据库,数据库设计是父母,PO对象是儿子,是这样先后关系。现在真正的OO系统应该是PO是父母,数据库才是儿子,应该倒过来。

那么问题来了,数据库做父母时,我们知道如何先创造数据库,这可以凭我们所谓的直觉和经验(还有一些号称理论的数据库建模);现在改为必须先设计对象,如何从需求中将对象找出来,这方面就需要对象建模,如本站介绍的四色图 Evans DDD

纲举目张,当你将PO作为主要纲抓起来以后,它在各层实现以及关系就自然解决,这方面实际案例可以看看基于jdonFramework的Jivejdon3
[该贴被banq于2007年06月12日 09:53修改过]

首先谢谢banq的回复,最近也一直在看你的关于领域驱动设计的文章。
现在,还是有一些不太明的的地方,请赐教:
1> 假如我现在是以对象建模的方式工作,那么我在前台是否可以获得查询所得到的Model对象?这种方式是否合适?
2> 你所提供的案例Jivejdon3 中Model会实现或是继承com.jdon.controller.model包中的接中和类,而com.jdon.controller.model下的内容是保密的,所以我不能完全理解这个思想。你能发一个小的案例给我吗,利用此思想从设计建模的图到最后的代码,不知我的要求是否过分,十分感谢。

请发到我的邮箱:huangzihua1219@163.com ,对于你的指点我不胜感激。

建议增加BO层,BO和PO是具有本质区别的,前者是面向对象分析的结果产物,后者是基于数据库分析结果的产物,虽然他们的表现形式可能类似。

如果你的系统达到一定复杂程度,你就会发现BO和PO开始不一样了,BO更接近抽象层次,PO只是具体存储实现的载体,比如一个BO(抽象业务概念)可能有多种存储的设计方式,不同的方式可能导致PO设计不同,如果只是单表,那么BO和PO样子确实很类似,但是最好预留这层,否则后面系统的模型复杂时,到后面PO就和真正的抽象概念越来越远,一个相同的抽象概念,可能客户不同,需求不同造成Po设计不同,造成系统复用性减低。

当然,系统还不是特别复杂的情况下,可以考虑把PO层和BO层结合,放到一个层次,复杂的BO通过组合多个PO实现,但是概念上一定要清楚,他们有本质区别。

to leadyu :
谢谢你的答复,还有一些不太明白的地方,请指点。
1> 我们系统中的PO是利用MyEclipse针对数据库中的表自动生成的。
你所说的“比如一个BO(抽象业务概念)可能有多种存储的设计方式,不同的方式可能导致PO设计不同”具体是什么意思,PO难道不是和库表对应的吗?能举个更具体的例子吗?
2> “复杂的BO通过组合多个PO实现”,你能举个复杂BO的例子的源码吗?

非常感谢。

不敢说指教了,呵呵,我只是谈谈自己的理解。

比如说我设计一个电子商务系统:其中有个很重要的概念---产品。好,那么我们看看产品模型。

需求简单的时候,产品可能我只要用一张表存放就完了,比如Product表,那么我设计了ProductPO,ProductService,ProductDAO,三个类。

这没问题,好,那么现在需求变了,更复杂一点点了,产品需要对应多个产品图片,那么就有了Pic表存放图片路径,那么就又多了PicPO,PicService,PicDAO。这时候有些业务和图片有关,有些业务和产品主表有关,和图片有关的程序就调用PicService.XXX等方法,那么从此他依赖了Pic表,PicPO。好,这个时候问题还不大,那么接下来业务又更复杂了。

不同类别的产品需要不同的参数和属性,冰箱有冰箱的参数,电视有电视的参数。那么我们又通过一种更通用的数据库设计来解决这个问题,比如属性的纵表扩展,通过把横向字段变成竖向记录来存放,达到动态配属性,那么这时候又设计了AttributeDefine表,AttributeValue表,后面发现产品图片也可以用属性的方式来实现,那么Pic表被废除,产品的存储方式已经不同了。好,下面我们看看现状。

由于PO紧密结合表设计,那么原来的其他模块已经依赖了这些PO,比如PicPO,那么现在存储结构变了,怎么办,不得已,改!变成依赖AttributeValuePO。这只是第一个问题。

好,现在一个同事做的模块需要和产品打交道,他想了解一下产品这块的业务,好,我拿出数据库模型给他,天,产品的不同字段的值还放在不同地方,Attribute表是干嘛的阿?我只要Product,能够从里面取得参数就行,你的模型里面几十张表,都干嘛的阿?那么如果是新同事来了怎么办呢?一个系统中核心业务模型其实不会太多,但是当我们拿出数百张表的数据库模型给业务分析人员时,他们怎么看。

不好理解还只是个次要问题,系统之间由于这样现状,很多模块之间不得不依赖于其他模块的一些表设计,这种表设计其实是很不稳定的,这意味什么,反而,业务模型是很稳定的,比如我做个CRM产品,到北京做项目和到广州做项目,核心模型几乎一样,但是业务流程确千变万化。

所以,系统简单的时候BO不妨就是PO,VO,但是一定要记住,BO表达的是业务概念,不是表存储,当表设计不能表达业务概念时,一定要把他们区分出来,比如我不管怎么变,产品都只有一个概念Product,里面组合了一些PO,比如AttributePO,存储时,ProductService只需要调用组合PO的DAO就可以了。

所有其他模块和产品产生依赖,我只暴露业务模型相应对象给它,他不需要了解我怎么存储实现,其实大部分时候他是不关心的,当然,除了做一些很复杂的查询时,他才关心数据库实现,ORM就在这时候体现作用了,它连查询都按照对象思路封装好了,当然,有些统计逻辑,ORM还没解决的很好。

这样还带来另一个好处就是,复用性,由于整个系统是根据业务模型设计出来的,贴近业务,所以在有些新的业务出来时,你就会发现,完全可以组合现有BO,和Service实现新的业务流程,有点类似BPM的概念。就是因为BO设计贴近业务才有这种组合的可能,比如说现在,根据不同的定价策略+产品=衍生出商品的概念,那么很多我完全可以组合产品BO,和定价BO,完成一些业务,只要Service的接口定义的粒度够细。

to leadyu :
谢谢你这么详细的讲解。
还有一点不太清楚,请你再指点一下。
假如,有两个PO:PO1和PO2,并且这两个PO各包括两个属性name和type,并且类型都为string。
现在有BO层,并且BO中是通过上面的两个PO组合成的。
1> 那么BO是直接包含这两个PO对象吗?像下面这段代码:
public class SampleBO {
private PO1 po1;
private PO2 po2;
.................
}
2> 那在 SampleBO这个BO类中,是否只应该包含属性的set,get方法。不应该包含其它的业务方法?
3> 你所说的:“Product,里面组合了一些PO,比如AttributePO,存储时,ProductService只需要调用组合PO的DAO就可以了”。
这句话,有点不太懂。请看我下面的理解是否正确:
假如,要通过ProductService 保存ProductBO中的各数据。
在业务层调用业务服务类ProductService 来完成保存,假设在ProductService 中实现保存功能的方法叫saveProductBO(ProductBO bo),(请注意方法中的参数类型),并在此方法实现中得到AttributePO,调用DAO方法,从而实现AttributePO对象的持久化。
是这样的吗?
4> 如果3> 上所说的是正确的话,那么 saveProductBO(ProductBO bo)方法中的参数是否不应该设为ProductBO 类型,而是更是更通用的类型,如在BO层建一个BaseBO,做为所有BO的父类。saveProductBO方法中也应传BaseBo类型的参数。
不知我这样考虑是否正确?


1)关于复杂的BO组合PO这点,是我表达不清楚,其实PO只是ORM才有的概念,他的生命周期也应该交给ORM框架去管理,我个人认为真的没什么必要把PO这个概念暴露给DAO层以上的层次,也不应该暴露,PO对应的是数据记录,如果在视图上修改了,等于修改数据,而且只在连接有效时才有效.业务真没必要知道有PO这个概念,DAO层返回的就是值对象,服务的就是值对象,在DAO层完成PO和VO的转换,这种转换可以通过反射自动完成,复杂的BO再通过组合值对象来实现.说实在,我设计系统的时候还真没有PO这个概念,完全是ORM做关系映射没办法下的东东。个人觉得ORM就不应该把这个概念暴露出来。


2)关于BO里面有没有业务方法,呵呵,我想jdon里面有很多比我高手多了的哥们发表过文章了,你可以看看,我个人认为,BO里面可以放那些和领域模型有关的业务方法,对于大多数业务方法还是放在Service里面,什么叫领域有关,比如体现领域对象之间关系的get方法,以及一些跟业务流程关系不大,跟领域模型关系密切,可重用的业务方法。哪怕剥离了Service单独看模型也是有意义的那些业务方法。

这并不违反OO设计,服务本身就是一种对象抽象概念,就好像,人,银行两个概念,银行为人提供存款服务,跟人有关,但人才是核心模型,不管在原始社会还是现代社会,人都是有意义的,而银行,只是现代社会环境下,对人提供服务,这就像开发系统一样,对于不同客户,有不同的业务服务,核心模型却类似,难道我们还能把存款这样的方法放到人的类里面吗?人的类里面挺多就是getHead,getBody,speak之类的和人密切相关的业务。

>并不违反OO设计,服务本身就是一种对象抽象概念,....难道我们还能把存款这样的方法放到人的类里面吗?人的类里面挺多就是>getHead,getBody,speak之类的和人密切相关的业务。

非常赞同楼上这段观点。服务和领域对象分离并不违反OO设计,关于MF所提的贫血模型等概念只是一种善意提醒,不能走两个极端:一是将很多非关领域对象紧密行为加入领域对象;也不能将领域对象紧密的行为完全从该对象中分离出去(使该对象变成贫血对象)。

相关话题:贫血和充血模型的比较之我见:
http://www.jdon.com/jivejdon/thread/31369.html


这其实是一个尺度拿捏问题,也就是回到对象最原始的设计问题:哪些特征是属于该对象固有基本的;哪些则不是,哪些和应用特征相关的服务性质,比如楼上举例的人存款这个动作特征,其实和具体应用有关,而不是人对象固有基本的。

对象/类定义的尺度拿捏相关话题:
http://www.jdon.com/article/31041.html


其实Evans DDD将模型对象分为实体对象 值对象和服务,已经为我们进行具体尺度拿捏提供了很多的方法依据,可以说实际做起来就比较简单了:

BO是一个实体对象,还是一个主要实体对象,比如Product应该是一个BO,而ProductPic就不能算一个BO,但是如果有一个数据表专门保持ProductPic,那么ProductPic可以认为是一个PO,但是正如楼上所说:PO这个叫法其实不是完全OO的称呼,是一个局限于持久层内部的叫法。

楼上建议PO不要暴露给业务层,这是对的,实际上,我的观点是:正因为PO是数据表驱动设计的产物,我们采取Evans DDD等对象建模,那么就没有PO这个概念了,有的都是实体对象,ProductPic可以算一个实体对象,但是它是从属于Product的,是Product的关联子对象,被包含在product中,他们之间关系是对象的1:N关联关系。


以上稍微总结了一下在Evans DDD新体系下和以前数据表驱动半对象体系下的同一个对象不同的称呼,也是综合了楼上leadyu的观点,欢迎补充更正。


区分了以上新旧概念以后,对于楼主的问题:"为了使项目针对需求的改动(如表中加入一字段、页面显示格式变化等),能有更大的适应性,也就是最少的代码改动,并且只在一层做改动即可。"

试图通过更改一层的模型对象改动达到全部改动,这一目标目前可实现,但是要借助一些MDD自动生成代码工具MDSD( Model Driven Software Development ).原理也就是更改模型对象BO后,其他各层代码自动生成,TSS最近介绍了这方面一个工具Sculptor,可生成spring+Hibernate多层架构代码:

http://www.theserverside.com/tt/articles/article.tss?l=ProductivityWithSculptor
[该贴被banq于2007年06月15日 15:28修改过]

同意banq老师的观点,我一直觉得,BO应该有两个层次,不过好像没有什么观点支持:

一种是实体型的,他包含的业务方法,应该是和自身关系紧密的,同时细粒度的,应该是越细越好。

第二层是应用层的,也就是我们普遍叫的Service,他也是业务对象,只不过,它不是实体模型,他针对应用,由他对外提供服务,在整个架构中,由他实现业务

在整个架构中,整合的是应用层,实体模型属于核心

你的思想我认为和Evans DDD的模型思想是相近的,只不过名称有些不同。关键是注意到业务模型的区别,这点很重要。

那把所谓的poZ转化到所谓的bo如果设计深度关联 怎么 办

比如 Product 类 里有 Category类 属性
如果 返回是 List类型 如何 深度复制 ?
效率如何

深度关联的复制可能是具体技术细节问题,比如可以借助lazyload 和动态代理,这方面Hibernate做得不错,特别是主对象修改save后,它支持对子对象集合和原来旧数据进行深度比较更新,这些都是Hibernate值得使用得地方

持久层:hibernate
业务层:spring

你把Spring归于业务层让我汗颜.Spring应该是用来让你把各个层在一个统一的框架下管理起来,他是一个框架,不是一个层

看了上面的几篇文章,感觉受益非浅,这里我也有几个问题想咨询一下:
1> 利用公司的框架,会自动把页面数据绑定到formbean(formbean中的数据对应于多个表中的数据)
这个是如何实现的,是通过formbean中的字段名和页面传过来的字段名的一个默认的规则,然后利用java反射来做的吗?
比如:formbean中有一字段为name, 我们默认request中有一个参数Name
通过遍历formbean中的字段进行依次赋值吗?
那对于checkbox这样的字段如何处理?
如果方便的话能不能再介绍一下struts中的formbean是如何实现的?

2> 对于formbean中的方法,除了提供get和set的方法,还能提供一些别的方法吗?
比如说:页面上有一个日期输入,但是从页面上传过来的是一个字符型,那在form 中我们是不是应该设置为String型,然后再提供一个另外的方法来由字符型转换为日期型,这个方法放在formbean中合适吗?谢谢!