Monthly Archives: June 2009

[Enterprise SOA] OO与SOA的阻抗

以前一直有就这种想法,今天发现“Enterprise SOA”这本书中明确地把它提出来了:

“面向对象强制性封装数据和功能,而面向服务通常认为数据和功能是分开的”

这让人联想起 贫血模型与充血模型旷日持久的争论……

还有,

"对象的粒度一般比较小,这会在分布式系统中导致很多远程调用"

Portlet V.S. Servlet

Portlets are similar to servlets, in that:

   1. Portlets are managed by a specialized container.

   2. Portlets generate dynamic content.

   3. A portlet’s life cycle is managed by the container.

   4. Portlets interact with web client via a request/response paradigm.


Portlets are different from servlets,
in that:

   1.
Portlets only generate markup fragments, not complete documents.

   2.
Portlets are not directly URL addressable. You cant send somebody URL of a portlet. You can send him the URL of the page containing a portlet.

   3.
Portlets cannot generate arbitrary content, since the content generated by a portlet is going to be part of portal page. If a portal server is asking for html/text, then all portlets should generate text/html content. On the other hand, if the portal server is asking for WML, then each portlet should generate WML content.

[O’reilly]An example of Portlet

This page is made up of different "windows." There is one window for the weather update, another window for news, a third for a stock price update, and so on.

Each of these windows represents a portlet.

Under the covers, these windows are different applications, developed independently of each other.

The developer of the news portlet will create an application and pack it into a .war file. Then the administrator of the portal server will install this .war file on the server and create a page. In the next stage,
every user will choose which applications he wants on his page. For example, if the user is not interested in stock updates but is interested in the sports update, he can replace his "Stocks Update" window with a "Sports Update" window.

从单元测试的角度看,抽象类与接口有很大区别

从单元测试的角度看,抽象类与接口有很大区别;下面将会提到,即使有了抽象类,也应该做一个接口。

为什么这么说呢?我们都知道单元测试时往往需要Mock一个被依赖的接口,并且要实现这个接口中的相关方法。

举例来说,如果我们要测A类,而A调用了B.method1(),那我们的Mock类就要实现B接口,并实现method1()方法

但如果被B不是一个接口名,而是一个无接口的具体类(也就是说没有按接口编程),那也不难办,我们就继承B,并覆盖method1()方法,可能还要覆盖构造方法,如果B中的构造方法依赖了别的资源。 这样做已经有点别扭了。

但如果B是一个抽象类,那就更别扭了。因为你不但要覆盖B的method1()方法和B的构造方法,还要实现B的抽象方法!不过,如果B的抽象方法就是method1(),那就不必了。

那么从中可以看出抽象类与接口的什么区别呢? 区别就是
接口的抽象方法代表真正的契约,而抽象类中的就未必。接口中的抽象方法总是为他人服务的public方式,而抽象类中的抽象方法可能只是protected的,交给子类实现而已。

再进一步说,
接口和抽象类都体现了一种抽象,一种等待子孙来实现的抽象;但接口还代表了一项服务。

而单元测试时的Mock是对服务的一种Mock,显然,这时候我们更偏好接口。

关于Domain Model的对谈录之四: 充血与贫血的折衷

A:持久化的问题搞得充血模型很头痛。于是有人提出了一个折衷的模型,即
与持久化无关的业务逻辑放到Domain Object中,有关的逻辑则放到Transaction Script中。robbin对种模型有非常好的阐述,这种模型的可操作性也的确比较强。

B:嗯,其实以前我也一直是这样做的。不过,这种模型还可以再精化一下,就是
尽量在Domain Object中做好所有的业务逻辑,然后Transaction Script只处理持久化。具体来说,就是先通过Transaction Script取出所有相关Domain Object,然后让这些Object在内存中互相交互,互相改变状态,以及产生新对象,最后再通过Transaction Script对这些新旧对象进行持久化。

A:听起来不错哦!职责分离的比较清楚,Domain Object和Transaction Script各司所长。你实践起来怎么样?

B:
折衷模型在大多数情况下都很好,因为大部分业务逻辑的OLTP处理都只牵涉到少数的Domain Object,因此在Transaction Script中对它们进行CRUD都不会很占用过多篇幅,代码整体上还是很简洁的。

A:那在少数情况下呢?

B:很讨厌。目前按我的经验有两种情形让我很烦:

   1.
如果参与处理Domain Object的类型有很多种,折衷模型就会很可笑。如果你的Script中要一次性取出很多种对象,那么其中很多种对象对你来说可能都是远亲,你的Script本不应该去找它们的。但你却找它们了,这意味着IOC被严重破坏,代码的质量可想而知。

   2.
如果参与处理的Domain Object实例数很多,折衷模型还会遭遇性能问题。比如一个批量操作,正常流程是取一个做一个,然后立即更新到数据库,对象从内存中清除;但现在你要取一整批,然后做一整批,最后再批量更新到数据库,这不是内存老虎是什么?很可怕的!

A:很好! 那遇到这两种情况你怎么办?

B:能怎么办?只能干脆把业务逻辑全部从Domain Object里转移到Transaction Script中来,这样首先可以避免性能问题(做一个存一个),而且还可以通过多个Script对象之间的协作来完成复杂的业务逻辑。

A:但这就完全退化到了贫血模型了。

B:是啊。所以,还是干脆点,就用充血模型好了。

A:呵呵。不管怎么样,从你上面举的例子中可以看出,
业务逻辑越复杂,Domain Model的性价比就越高

B:为什么?

A:因为业务逻辑越复杂,一次业务计算牵涉到的业务对象的种类和实例数就会越多,Transaction Script就会变得越来越全能,也越来越臃肿(反IOC),也就越来越反OO,也就越来越难于适应需求的变化。

关于Domain Model的对谈录之三: 充血模型与Domain Object的持久化

A:
充血模型中,Domain Object的持久化远远不如贫血模型来得清爽

B:你这里的“清爽”是上海话吧? 详细说说,为什么不够清爽?你不是说充血模型“老清爽”吗?

A:从业务逻辑的OO化来说,充血模型的确更清楚简洁;但在持久化问题上,可以说“很别扭”。第一个问题是循环调用问题:Domain Object要调用持久化的DAO接口,来把自己持久化;而DAO接口又要把Domain Object作为参数来做CRUD操作。

B:嗯, Stutent.save()中要执行studentDAO.save(this),而StudentDAO.save(Student)的参数就是调用它的Student.
这里存在Domain Object与其DAO之间的调用环路了,的确让人很不舒服。其实,
充血模型的这类问题不仅存在于持久化的场景中,任何一个以JAVABEAN为参数的接口都可能会与Domain Object循环调用

A:这还只是第一个问题。还有第二个问题。
“事务”不方便再以AOP方式来控制了。在贫血模型中,所有业务逻辑都集中在Transaction Script中,我们一般会用XxxService来命名这些事务,然后以AOP的方式在所有名为XxxService.doSth()的操作中强行织入事务。

B:嗯。
Domain Object的名字一般不会遵循某种命名规范,很难确定合适的AOP织入点

关于Domain Model的对谈录之二: 充血模型中的Service Facade

A:
充血模型中的Service Facade地位会有点尴尬

B: 讲什么东西啊?说清楚点!

A: 别急嘛,比如说 在Transaction Script模式下,表现层一般通过 类似于“XXXService” 的Facade来调用业务层的服务,是不是?

B: 是啊。不过在充血模型中,已经没有Transaction Script了,也就是说表现层就要直接通过调用Domain Object来获取服务了。

A: 对。

B: 这有什么不好?

A: 那还用说吗,
层和层之间的接口应该尽量单一和集中嘛,这样才能体现封装性嘛。

B: 哦,是。不过我们可以仍然保留一些很薄的Transaction Script作为Service Facade,这些Facade自己不实现业务逻辑,而只是给Domain Object做二传手。

A: 是。但
Facade一般怎么做二传呢?基本上都是在复制Domain Object的方法签名!,简直无聊死。

B: 这类Facade不要不行,要了又无聊,怪不得你说它们地位尴尬。

关于Domain Model的对谈录之一: 充血模型与OO

一篇文章只写一个小主题,这样才能让读者集中注意力。

不过此系列文章都是给自己看的,因为它们没什么新意,都是在别人牙慧的基础上总结出来的

===================================================================================

结论:

 
充血模型比贫血模型更OO

==========================================

A:
充血模型比贫血模型更OO.因为一个领域对象中既有数据又有行为,符合对象的定义

B: 这太牵强了!你还是说点有实际意义的吧。

A: 好吧。比如说,我们可以在充血模型中
通过多态消除IF/ELSE,这算是典型的OO吧。

B: 举个实际的例子吧。

A: 好。比如你还在上大学的时候,天天要去食堂。一般情况下,你还没开口,食堂师傅就给你打好了5毛钱的饭,为什么?

B: 因为我是男生。如果是女生,默认打3毛钱的饭。

A: 对。那请你用Transaction Script模式,也就是贫血模型,实现“默认打饭”逻辑。

B:

      void 打饭(student){
          if(student 是 “男生”){
               打五毛;
          }else{
               打三毛;
          }
      }
   

A: 好。看我这个

  

      void 打饭(student){
        打(student.默认饭量());
      }

      class MaleStudent extends Student(){
         int 默认饭量(){
             return “五毛”;
         }
      } 

      class FemaleStudent extends Student(){
         int 默认饭量(){
             return “三毛”;
         }
      } 
   

B:嗯,的确!由于Student变成了充血模型,

   1.
充血模型可以自己实现业务逻辑,业务逻辑在过程性的Transaction Script中无法多态,而在充分OO的领域对象就可以方便地实现。

   2.
另外,由于职责的合理分配,使得这里实现了控制反转,从而使代码更优雅简洁。