Java EE/J2EE面向对象编程之道

一个真正面向对象的JavaEE/J2EE系统,应该是围绕领域模型的多层架构,以OO思维进行模型提炼和重构,继续以OO思维进行表现层和持久层配置实现,这才是化复杂为简单的软件解决之道。
http://www.jdon.com/artichect/javaee.html

Eric特别指出:那种将业务逻辑交由业务界面处理的快速UI方式是旁门左道。希望象C/S结构那样可视化拖拖图形就完成的软件开发是一种错误的方向,开发时快速,难于维护和扩展,虽然使用J2EE技术,其实是一种伪多层技术。可惜,有很多国人在疯狂开发这类工具,大有不撞南墙不低头之势,并且疯狂误导很多非专业人士,可悲可叹!如果对这段言论持不同意见,建议你购买"领域驱动设计"这本译书,见P53页

请问:像Tapestry 这样的框架是否算是以上所说的旁门左道那?这种框架几乎所有的逻辑都是与页面有关的。

>Tapestry 这样的框架是否算是以上所说的旁门左道
Tapestry 是和struts一样的表现出框架见:
表现层框架Struts/Tapestry/JSF架构比较 :
http://www.jdon.com/artichect/sjt.htm

不过Tapestry性能上好像有些微词,TheServerside就是用Tapestry+JDO做的,很慢,Tapestry作者认为是后台JDO的问题,反正TSS打开页面很慢。

有个问题:
现有3个实体A,B,C
A{
Aid
Aname
B集合
}
B{
Bid
Bname
C集合
}
C{
Cid
Cname
C一些相关属性
}

在Hibernate当中肯定要设one to many关联查询,
但是如果我有某个页面只是想得到A的当中的name和id就可以了,但每次通过hibernate查询都会相应的把B和C的集合都包含进来(数据少的话那倒是不要紧,但如果多的话那就要命了)。
请问如果遇到这种情况该怎么设置呢?

>每次通过hibernate查询都会相应的把B和C的集合都包含进来
Hibernate3 中lazy为true,所以不会将B和C集合包括出来。

另外,如果根据Evans DDD,持久层提供完整的A对象,这时需要将B和C集合包括进来,出现这种情况有两种方式处理:1.检查模型A B C建模正确与否?按照高聚合 低关联设计原则,A是否真的包含多个B?B又包含多个C?
2.使用批量查询等lazy方式来对集合进行查询,就象Hibernate3这样处理一样

->Open session in view,也就是在表现层一直打开持久层的session,这不但违背分层不干扰原则,而且造成数据库连接一直打开,一旦出错,有可能造成内存泄漏死机等问题

Banq表误导俺们呀,谁说Open session in view是在表现层“一直”打开session的?对于缺省的配置,OpenSessionInViewFilter会在一个请求(request)的开始的时候,打开一个session。Spring绝的地方在于,该请求之后的所有用Spring方式打开的session都是这个session,因为这个session是与Thread绑定的。当请求结束,OpenSessionInViewFilter会关闭这个session的。如果将singleSession属性设置为false,那么OpenSessionInViewFilter不会打开session,请求处理过程中所使用的session就是原来的那个(就像没有用OpenSessionInViewFilter)。但是,这个session会延迟关闭。
看看spring的代码就知道了。
使用OpenSessionInViewFilter似乎是破坏了分层,但是却大大简化了开发,比使用Hibernate的强制即时加载和直接用JDBC破坏延迟加载的方式不知要简单多少。而且,这种对分层的“破坏”,是隐藏在框架中的。开发者只需要配置一个OpenSessionInViewFilter即可,代码中看不到破坏分层的坏味道。
从性能上来说,对于单个请求而言session的关闭被延迟了,的确会降低性能。但是,这种性能的降低是和延迟加载所提高的性能是可以抵消的。如果通过性能测试,发现OpenSessionInViewFilter“的确”降低了性能,那么可以通过URL-Pattern的配置绕过不需要OpenSessionInViewFilter的请求。例如,带有query或list的URL使用OpenSessionInViewFilter,而带有save或update的则不使用OpenSessionInViewFilter。

>该请求之后的所有用Spring方式打开的session都是这个session,因为这个session是与Thread绑定的

我的理解是这个Thread是每个客户端对应一个thread, Web是一个多线程环境,你能说这个session不是和客户端相关的?从设计高度来说:session必然由客户端决定的,我没有研究Spring这部分代码,但是我想它绝也不会绝到跳出设计常理。

>使用OpenSessionInViewFilter似乎是破坏了分层,但是却大大简化了开发
是的,如果我们使用Delphi/VB等两层系统,还更加简化的,我一直强调,简化的前提不能牺牲分层原则。

>从性能上来说,对于单个请求而言session的关闭被延迟了,的确会降低性能
更坏的性能是Hibernate自己都承认:当出现Exception出错抛错的时候,导致大量资源可能无法关闭,这是最危险的。

正因为有这些陷阱在,所以才出现不少Spring+Hibernate系统缓慢存在,一个好的架构不应该在基础功能:性能上留有陷阱,即使它的设计超前时代几十年...

总之,在找不到两全其美的情况下,我目前是用JDBC来实现,这也符合Evans DDD的仓储原理。


>从设计高度来说:session必然由客户端决定的。
我只是说明“在表现层始终打开Hibernate的Session”的说法是不对的,不要提高到“设计高度”。
>使用Delphi/VB等两层系统,还更加简化的,我一直强调,简化的前提不能牺牲分层原则.
我的观点是一切从实际出发,不要为了分层而分层。再说很多系统,用CS方式就是比BS方式合适,比如高速公路车辆监控。简单,高效的开发,一定程度上的扩展性就好。分层吗...看看ror吧。
>当出现Exception出错抛错的时候,导致大量资源可能无法关闭,这是最危险的.
JDBC方式是怎么释放资源的呢?Exception出现的时候资源会不会无法关闭呢?如果使用不当,什么都可能发生。
JDBC当然很好,如果没有工期催,我也可能用它。

>分层吗...看看ror吧
我的观点:ROR是可笑的倒退,MatinFowler一边推荐Evans DDD,一边玩ROR,而ROR将业务和表现层控制混淆在一起,相当于我们在struts的Action中写入业务代码,所以,RoR是与Evans DDD矛盾的。这也是理论和实际的一个现状吧。

http://www.jdon.com/jdonframework/rails.html

>当出现Exception出错抛错的时候,导致大量资源可能无法关闭,这是最危险的
这个我之前没有表达清楚,Open session in view模式在复杂系统中就不是在web.xml设置一句filter那么简单了,真的会导致系统性能下降缓慢。

因为后台Hibernate的session被前面表现层hold后,这个环节因为跨表现层和复杂的业务层以及持久层。

万一(注意,这是随时可能发生的,一个用户等不及浏览器打开页面就关闭等等),三个层中任何一个环节任何一个点出错,就会抛Exception,那么这个跨环节的过程就会崩溃。

关键问题是:崩溃后,我们如何做善后处理?如何将这个过程涉及的多个环节中打开的一些资源(不一定是数据库连接,有可能使用缓存资源保存了很多垃圾数据)进行清除。

如果不进行Exception捕获善后处理,那么就有可能引起内存泄漏,就象一个房间里举行过party,无论是否成功完成或中途结束,肯定要进行现场清扫,垃圾回收。

如果进行Exception捕获,又何其的难,跨多个层的执行循序我如何能进行逆转判断,然后逐个关闭一些资源?当然除非你是明显的过程化编程。

因此,这是一个很棘手的问题,尤其是系统复杂,也就是业务层庞大时。

这种Open session in view模式我个人一直是持反对态度,这是典型使用模式来弥补基础功能的缺失,是反模式的。见以前这个帖子:
http://www.jdon.com/jive/thread.jsp?forum=62&thread=22251&message=14293011

当然,喜欢使用Open session in view模式尽管使用,以上只是我个人想法。

为了佐证表达我对Hibernate的Open session in view意见,以Hibernate3使用说明中说明为例,注意:当初Hibernate 2出来后,我就对这个模式有意见,没有看到过hibernate3中这段说明:

In a web-based application, a servlet filter can be used to close the Session only at the very end of a user request, once the rendering of the view is complete (the Open Session in View pattern). Of course, this places heavy demands on the correctness of the exception handling of your application infrastructure. It is vitally important that the Session is closed and the transaction ended before returning to the user, even when an exception occurs during rendering of the view. See the Hibernate Wiki for examples of this "Open Session in View" pattern.

大意翻译:在基于Web应用中,我们可以为每个用户请求一直打开session,直至这个请求输出结果页面结束。这就会严格要求你有正确的exception捕获上(这就是难度,当要求你小心行路时,说明脚下有陷阱了,这就是性能陷阱)。下面就非常注意:即使在一个请求结束后输出页面时出错,也必须在结果返回给用户客户端之前,关闭Session和事务,(注意,这里是指session和事务,这些Spring可能能够帮助你完成,但是如果是你自己打开的一些资源占用,如打开文件资源准备写等)。

最近 Arjen Poutsma在他的博客The Ancient Art of Programming的一文Domain Drivel中谈到和本文提到的OO系统相关的意见:


Arjen Poutsma说:
计算科学告诉我们有三种方式来表达数据:

1. Object graphs (i.e. Java or C#) 对象
2. Relational data (RBMS) 关系数据库
3. Hierarchal data (XML or HTML) 树形层次数据

Data is generally stored in relational databases, and converted to Java or .NET objects using some kind of ORM tool. Next, we use the objects to invoke business logic, and finally, we display them on an HTML page. I find this very amusing, it makes me feel we are doing something wrong here.

译文大意:(现在J2EE OO系统大部分是这样做的,如本文提到的:)数据通常保存在关系数据库中,然后通过一些ORM工具转换为Java或.NET对象,我们可以使用这些对象进行业务逻辑处理,最后将他们显示在HTML页面上,这给我的感觉好像有些问题。

Recently, though the influence of books like Domain Driven Design, it has become fashionable to think that a rich Domain Model is one of the most important things there is.
最近,因为Domain Driven Design书籍的流行,一个丰富的Domain Model概念被提出来了。

Well, that might be the case for you as an OO developer, but for the Enterprise the database is much more important.
不错,也许这样你就成为了一个OO开发者,但是企业数据库也许更重要!

It is not without reason that a large number of databases outlive the applications built around them.很显然很多数据库比他们的应用系统活得更长。

And for the user of your Web application the most important part is the HTML UI he or she is looking at.
对于Web应用系统,最重要的是人们看到的Html界面(这也是某些坚持AJAX比DDD更重要的原因)

Finally, in this SOA day and age, the OO business logic is probably not the only business logic that is there. Your application is part of a team of services, the business logic on the mainframe is just as important!
最后,在SOA年代,OO业务逻辑大概不只是就是某个地方一点点业务逻辑,你的应用是一系列services服务的一部分,大型主机中业务逻辑也重要。

Now, I’m not saying that a rich domain model is not important. I’m just saying that ― as with any solution ― it is not a silver bullet. Use it when it gives an advantage, but don’t swear by it. In the grand scheme of things, your precious OO model is not as important as you think it is.

当然我并不是说胖Domain Model(rich domain model )不重要,我想表达的是:正如其他解决方案:它不是银弹,使用其优点。

这一点引发了更多的关于数据库和对象的争论,见TSS帖子:
http://www.theserverside.com/news/thread.tss?thread_id=42602

关于OSIV (Open Session in View)以及OSIV / LazyInitEx 在老外六月份这篇文章后也进行了激烈的讨论,作者认为Avoid LazyInitialisationException (OSIV is not the answer C alternatives include AJAX*, use fetch joins; use eager loading)

当然,文中提到了RDM(Rich Domain Model)和贫血模型、失血模型(anemic model )比较,注意这里RDM不是指在模型对象里带有数据库操作save;get等行为的RDM,Evans DDD是坚决反对将持久层行为耦合到领域层的,这里的RDM不是传统意义的RDM,是Evans DDD中的RDM

您在文中说道:"...
下面再以用户User这个对象为例,用户User可能拥有很多动态属性,一些属性需要运行时动态确定,用户和动态属性是一个整体和部分的聚合关系;每个用户都必然属于某个部门,因此,用户和部门属性对象之间也是一个整体和部分的聚合关系,这两种聚合关系不同之处在于:前者一个用户可能有多个动态属性,是1:N关系;部门Dept和用户User之间是1:N关系,一个部门中可能有多个用户,反过来说,对于用户User来说:它和部门Dept之间是N:1关系。"
我不太明白它们之间的不同之处到底在哪里?