`
taowen
  • 浏览: 193890 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

贫血的Domain Model

阅读更多
好老的话题啦。拿出来炒炒冷饭。各位见谅。
——————————————————————
Domain Model贫血是说属于Domain Model的逻辑没有放在Domain Model中。那是哪些逻辑没有放到Domain Model中,从而导致贫血一说呢?原因有很多,但是我认为最主要是Service中的那些逻辑。而这些逻辑又有一个共同的特点就是依赖于DAO,或者说需要查询数据库。Robbin的帖子:http://www.iteye.com/topic/57075,举了一个很好的例子。我取其中的一个部分在这里做演示用。

public class Employee {
    private Set<Task> tasks = new HashSet<Task>();
}


public class Task {
    private String name;
    private Employee owner;
    private Date startTime;
    private Date endTime;
}


这是一个很简单的一对多的关系。现在要查找指定员工的处理中的任务。如果忽略数据库的存在,我想大部分的同志都会这么实现:

public class Employee {
    private Set<Task> tasks = new HashSet<Task>();
    public Set<Task> getProcessingTask() {
       ...
    }
}


这也符合OO数据隐藏的基本原则。但是如果有数据库存在,怎么写就不那么容易决定了。如果没有Hibernate这样的ORM。那肯定是:

public class TaskDAO {
   public Set<Task> getProcessingTasks(Employee employee) {
      ...//sql
   }
}


那我觉得,这就导致了Domain Model的失血。因为没有数据库的时候,这这个方法本来应该在Employee上的,而不是在DAO上的。
如果有Hibernate呢?是不是我就可以把这段代码写到Employee里面去呢?

@Entity
public class Employee {
    @OneToMany
    private Set<Task> tasks = new HashSet<Task>();
    public Set<Task> getProcessingTask() {
       ...
    }
}


还是有问题。因为访问tasks的时候,Hibernate会去加载数据。getProcessingTask会便利所有的task。如果task的数量很多,这降极大的影响性能。所以为了能够享受到关系数据库查询速度的好处,我们要还要利用SQL。于是DAO又再次地找到了自己的位置。那么怎么解决这个问题呢?在http://www.iteye.com/topic/57075的回帖中nihongye同学提出了一个解决方案。本质来说就是不让hibernate来映射tasks,改由查询来获得。加上Spring支持的@Configurable标记,我们可以把代码写成这样

@Entity
@Configurable
public class Employee {
    private TaskDao dao;
    public Set<Task> getProcessingTask() {
        return dao.getProcessingTask(this);
    }
    public void setTaskDao(TaskDao dao) {
        this.dao = dao;
    }
}


我们当然还可以把TaskDao替换成变的形式。比如http://www.iteye.com/topic/65406里firebody提到的那样。但是本质上来说,都是让Employee能够直接去使用Hibernate做查询。但是坏处是给Domain纯净分子的口实。虽然,我认为和ActiveRecord类似,entity绑定在数据库上没啥不好。另外一个缺点就是,要么仍然有一个Dao来封装查询逻辑的实现,要么Employee的实现中出现太多的hibernate api,而且写法复杂。这也就是Robbin一再强调,ActiveRecord那样的api在Java世界中不是不可以,而是实现复杂难度高的原因。注入可以解决问题,但是对Hibernate的依赖强而且写法丑陋。
那么有没有更优美的方案呢?有:

public class Employee {
    private RichSet<Task> tasks = new DefaultRichSet<Task>();
    public RichSet<Task> getProcessingTasks() {
        return tasks.find("startTime").le(new Date()).find("endTime").isNull();
    }
...
}



RichSet是我自己编造的一个名字。它是一个”rich“的set。其实就是附加了一些find,sort,sum之类的操作。

public interface RichSet<T> extends Set<T> {
    Finder<RichSet<T>> find(String expression);
    int sum(String expression);
}


DefaultRichSet是这些附加操作的内存版本的实现。这个能解决问题么?还是不能,这时候getProcessingTasks的时候,richSet还是去遍历内部的_tasks,然后把结果过滤出来。而且,hibernate还拒绝接受这样set。为了让hibernate能够接受RichSet,我们需要这么写配置文件。

<hibernate-mapping default-access="field" package="net.sf.ferrum.example.domain">
    <class name="Employee">
        <tuplizer entity-mode="pojo" class="net.sf.ferrum.RichEntityTuplizer"/>
        <id name="id">
            <generator class="native"/>
        </id>
        <property name="name"/>
        <property name="salary"/>
        <many-to-one name="department"/>
        <set name="tasks" cascade="all" inverse="true" lazy="true">
            <key/>
            <one-to-many class="Task" />
        </set>
    </class>
</hibernate-mapping>


通过指定RichEntityTuplizer,我们可以控制Hibernate的动态增强过程。

public class RichEntityTuplizer extends PojoEntityTuplizer {
    public RichEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) {
        super(entityMetamodel, mappedEntity);
    }

    protected Setter buildPropertySetter(final Property mappedProperty, PersistentClass mappedEntity) {
        final Setter setter = super.buildPropertySetter(mappedProperty, mappedEntity);
        if (!(mappedProperty.getValue() instanceof org.hibernate.mapping.Set)) {
            return setter;
        }
        return new Setter() {
            public void set(Object target, Object value, SessionFactoryImplementor factory) throws HibernateException {
                Object wrappedValue = value;
                if (value instanceof Set) {
                    HibernateRepository repository = new HibernateRepository();
                    repository.setSessionFactory(factory);
                    wrappedValue = new HibernateRichSet((Set) value, repository, getCriteria(mappedProperty, target));
                }
                setter.set(target, wrappedValue, factory);
            }

            public String getMethodName() {
                return setter.getMethodName();
            }

            public Method getMethod() {
                return setter.getMethod();
            }
        };
    }
}


这样,tasks就不再是DefaultRichSet了。Hibernate会尝试去增强为PersisentSet,但是被RichEntityTuplizer改写为增强HibernateRichSet了。这样就形成了HibernateRichSet -> PersisentSet -> DefaultRichSet -> HashSet 的包含关系。

当用户尝试在tasks上做find的时候,就不再是DefaultRichSet来做collection遍历了,而是HibernateRichSet去拼装一个DetachedCriteria。最后当用户在查询的结果上取size()或者取具体元素的时候,这个criteria被拿去求值。

通过使用RichSet,domain model具有了对自身进行查询的能力。更重要的是,这种能力的获得,不是通过把Hibernate session注入到domain model中。domain仍然是纯净的,没有依赖于数据库的东西。而且domain是可以脱离容器使用的。new Employee出来就可以直接使用,测试。区别只是经过repository增强的entity会使用sql,而transient的entity所有的查询都是通过遍历实现的。

没有了DAO之后,Domain Model是不是能够摆脱贫血的困扰呢?这个还需要观察。不过我认为至少是向前迈了一步了。
  • ferrum.zip (90.6 KB)
  • 描述: 新版代码,废除了手工的增强,改由Tuplizer来搭Hibernate的顺风车。
  • 下载次数: 302
分享到:
评论
77 楼 fxwdl 2009-01-21  
robbin 写道
taowen 写道
Robbin能否举一个例子如何像ActiveRecord那样用静态方法调用领域逻辑。



public class Employee {  
     @OneToMany  
     private Set<Task> tasks = new HashSet<Task>();  
     public Set<Task> getProcessingTask() {  
        ......
     }  
     public static getAllEmployee() {
        ......
     }
} 


比方说类似这样的,如果不依赖特定对象实例的方法,直接用静态方法如何?如果getProcessingTask用到其他对象的方法,如果是依赖对象状态,可能就是方法参数传递进去,如果不依赖,直接调用该对象静态方法。

不知道这样去用,会不会有什么问题。自从IoC兴起以后,貌似就没有人这样去用了,大家都极力避免静态方法的出现。




我昨天也在思考这个问题,如果是静态方法,可以很大程度上减轻对象的行为,因为像GetXX这些方法我认为与具体的实例相关增加了领域对象的复杂程度,领域对应该最好只是专注与自身的状态.但是Ioc的动态注入实在是个好东西,真是不知道如何取舍好.
76 楼 fxwdl 2009-01-21  
coolnight 写道
robbin 写道
taowen 写道
Robbin能否举一个例子如何像ActiveRecord那样用静态方法调用领域逻辑。



public class Employee {  
     @OneToMany  
     private Set<Task> tasks = new HashSet<Task>();  
     public Set<Task> getProcessingTask() {  
        ......
     }  
     public static getAllEmployee() {
        ......
     }
} 


比方说类似这样的,如果不依赖特定对象实例的方法,直接用静态方法如何?如果getProcessingTask用到其他对象的方法,如果是依赖对象状态,可能就是方法参数传递进去,如果不依赖,直接调用该对象静态方法。

不知道这样去用,会不会有什么问题。自从IoC兴起以后,貌似就没有人这样去用了,大家都极力避免静态方法的出现。




java里面使用静态方法的不便之处很多, 比如说不容易扩展, 不容易被替换等等,而ruby的特性决定了这些不是很大的问题。java里面这种形式的rich domain model的事务也不容易被管理
在java里面用ioc来管理依赖是比较好的做法, 很难扔掉;而且 ioc也确实带来很多的好处

一个复杂的系统里面,真正最稳定最一致的东西是什么呢, 其实正是那些 贫血的domain model!


除非需求非常明确, 系统在设计期就能定下rich domain model,最好对于独立的model很少有团队协作,这种
情况下rich domain model才有优势, 至少少了一个dao的依赖。 但是这样严格的场景太罕见了,
大部分情况下需求经常在变, 经常要加上新的功能, 这个时候就会发现,如果是rich domain model的设计,
对domain model的不断的修改就成了一个梦魇! 如果有多人协作完成系统的不同部分,互相之间要share domain
model的时候, 那rich domain model就更加难以运用了! 如果还要用Service层进行事务管理的话,那很多时候
就必须把rich domain model里面的功能在Service层全都包装一遍, 那么为什么不直接在Service层直接实现
rich domain model的功能呢?

我们公司现在的产品就是这样, 开始的时候就是贫血domain model和rich domain model都有, 越用就越发现贫血模型的好处。现在 rich domain model的功能大部分处于废弃状态, 真正有用的,基本就是 贫血 domain model, 或者说po,
加上一个薄薄的dao层, 然后系统的各个部分各自使用各自的service层,很清晰很简单很有效!!




但是我认为这里存在一个问题,就是如果系统中某个逻辑要求处理一个抽象的类的实例,即要求处理的对象具有多态的特征(指的是由Manager实现的行为),此时,难道要直接去处理因此得来的Manager的继承层次?
不知道我的意思你是否能够明白,就像你上面讲的那个Employee,如果这个人员分为普通人员,特殊人员,他们的addFriend 行为是不一样的,这里你的Manager会不会按照两种不同的人员来分别处理?换作另一个地方,某个逻辑要求无分别的处理不同类型的Employee.AddFriend,那这里我要传过来什么?Manager,还是Employee?如果是Manager,这种层次会让我有非常不好的感觉,如果是Emplyee,那么我就要在这个新的地方重写AddFirend的逻辑.请替我解解惑....在这里,我认为最好是无差别的应用Employee的对象,不过我确实是不知道你的Manager是如何写的.
75 楼 狂放不羁 2008-11-26  
我觉得也可以专门抽象个查询组件来实现类似的功能。数据量大的情况下,我一般采用查询的方式。DDD中也说了,同一种模型有不同的实现,查询也可以算一种实现。
74 楼 coolnight 2008-05-26  
taowen 写道
to coolnight:能不能举一个你们公司的Rich Domain Model的例子,以及它们是如何被大家废弃的。


sorry  看到这个较晚,
大致回复下:

我们的系统有很多模块组成, 各模块基本上通过数据库来共享信息。
主要的模块大致有: 核心系统(非web), 网站、 bbs、 网站后台,核心系统后台,BI, 推广员等等

原来的打算是写个rich domain model供各模块来使用,以便代码重用减轻个模块开发的工作量

一个简单的例子, User 原有 changePassword, getFriends, addFriend ... 等等方法
撇开配置以及获取User对象的复杂性不谈, 实际开发中发现, 这些东西重用的地方太少了,
对网站来说很好的rich domain model, 在网站后台里面就麻烦,很多方法在那里根本就是对后台
开发人员的干扰,而很多方法对核心系统、BI等等根本就毫无用处。
而且由于网站之类的web系统, 其功能变化很快, 今天一个样子明天完全有可能是另外一个样子了,
新招个策划都可能提出一堆的改动来, 还有很多临时性的功能, 最后真正在各模块之间共享
的就是最贫血的domain model, 基本上就是表的映射, 连关联都给废了各处自行维护了

因为系统数据量比较大,所以性能问题也受到比较多的关注, 在系统的关键部分, DBA说了算,OO靠边站

实际上我在几个公司呆过 也曾试图写出比较rich的domain model, 但是只要项目的需求变更的比较频繁,
那么rich domain model维护的成本就会很高, 而且想重用的想优雅的地方最终大部分变成了摆设,
而需求变更不频繁的项目, 太少太少了
73 楼 pig345 2008-05-23  
kabbesy 写道
taowen 写道
我想robbin的意思其实是如果只有一个client,也就是在只有web层用它的情况下,service层除了啰嗦没啥价值,不如直接合并进web层。我对这个问题的看法是,要看service层中的具体服务到底有几个用户。如果到处都是只有一个用户的service,我们还要抽象出好多接口和对象来提供这些服务,不如直接在用的地方写就好了。在实际的,简单的web项目中,好像取消service层,让service层的用户也就是web层来实现事务控制安全控制好像更符合务实的精神。其实有三个选项:1、分层,2、分对象,3、分方法。简单情况下,取消service层,把起职责散布成web层的一些对象乃至成为action的一个方法,也未尝不可。不过没有具体实践,还要观察观察。


domain是不应该太贫血,但也没必要单纯为了rich domain而取消service层。

假设取消掉service层:
首要问题就是domain之间的交叉依赖,而在传统service层中,domain之间的关系从来都是交给具体service去完成的,service搞的再烂,也不会把domain搞烂;

然后是事务边界带来的bad smell(虽然将持久化设备注入给domain也不见得是坏事,但习惯上总觉得不那么好)

然后是如何为prototype bean进行注入;

最后还有诸如团队协作这样的麻烦,逻辑过于集中后,协同开发的确成为了麻烦事情。


所有这一切,只换来一个“逻辑集中”的优势,不用再考虑哪些方法该调用domain的,哪些是service的。而这些完全可以被合理的方法命名和编程规范所解决。

取消service层追求完全的rich domain,实在是弊大于利。


似乎你说的service和我说的还不大一样,呵呵。

DomainObject之间的交互是不需要Service的。
72 楼 kabbesy 2008-05-21  
taowen 写道
我想robbin的意思其实是如果只有一个client,也就是在只有web层用它的情况下,service层除了啰嗦没啥价值,不如直接合并进web层。我对这个问题的看法是,要看service层中的具体服务到底有几个用户。如果到处都是只有一个用户的service,我们还要抽象出好多接口和对象来提供这些服务,不如直接在用的地方写就好了。在实际的,简单的web项目中,好像取消service层,让service层的用户也就是web层来实现事务控制安全控制好像更符合务实的精神。其实有三个选项:1、分层,2、分对象,3、分方法。简单情况下,取消service层,把起职责散布成web层的一些对象乃至成为action的一个方法,也未尝不可。不过没有具体实践,还要观察观察。


domain是不应该太贫血,但也没必要单纯为了rich domain而取消service层。

假设取消掉service层:
首要问题就是domain之间的交叉依赖,而在传统service层中,domain之间的关系从来都是交给具体service去完成的,service搞的再烂,也不会把domain搞烂;

然后是事务边界带来的bad smell(虽然将持久化设备注入给domain也不见得是坏事,但习惯上总觉得不那么好)

然后是如何为prototype bean进行注入;

最后还有诸如团队协作这样的麻烦,逻辑过于集中后,协同开发的确成为了麻烦事情。


所有这一切,只换来一个“逻辑集中”的优势,不用再考虑哪些方法该调用domain的,哪些是service的。而这些完全可以被合理的方法命名和编程规范所解决。

取消service层追求完全的rich domain,实在是弊大于利。
71 楼 naive_1010 2008-05-17  
taowen 写道
和Linq还是不一样。Linq就是一种查询语言。你可以在colllection上做linq查询,也可以在数据库上做linq查询。但是以现在的实现而言。如果你认为tasks是collection,在Employee的task上做linq查询,那就是对collection的过滤。如果你认为tasks是数据库的表,在Employee上做linq查询,那就是sql查询。但是无法做到连接到数据库的时候做sql,不连接数据库的时候做过滤。也就是说,linq也会造成你的domain model和数据库绑定。


Linq 不是也有Linq to Objects嘛! 这个应该和DLinq有异曲同工之效
70 楼 dendai1 2008-05-15  
@Entity  
@Configurable  
public class Employee {   
    private TaskDao dao;   
    public Set<Task> getProcessingTask() {   
        return dao.getProcessingTask(this);   
    }   
    public void setTaskDao(TaskDao dao) {   
        this.dao = dao;   
    }   
} 

saf
69 楼 pig345 2008-05-14  
robbin 写道
pig345 写道
taowen 写道

连接的获取和释放,和事务的管理是Service层的职责,无论Domain Model是贫血还是不贫血,都不会牵涉到连接和事务的管理。

大家的做法不某而和,呵呵。

如果做过多Client的WebServer项目的话就不会有太多疑问了。
这种项目中,web层本身只不过是众多client中的一个而已,另外还有GUI的 或 独立进程(用于系统间通信)的client,这些client并不能直接使用webserver里面的DomainObject,必须要一个Service/action层作agent,其实service层也可以发展成应用/界面逻辑(顺便控制事务边界),而Domain中是真正的领域相关的业务逻辑。


先不说你多client的项目是多么的罕见,就算你有GUI的独立client,你用什么方式和Server通信?说到底无非就是两种:

1、RMI方式
EJB就是基于RMI方式,但我相信你不会采用RMI方式,RMI不但复杂而且有太多限制,现在这种方式已经基本不被采用了

2、Web Service方式
好吧,Web Service方式你是不是还是需要Server端有一个Web层?

所以不管你是不是多个client? Server端必然都是通过Web层提供服务的,所不同的只不过是client是浏览器、手机、或者应用程序而已。所以Service层真的是必要的吗?恐怕未必吧。

你就看Rails的REST,人家只有Web层,那有怎样? 看看twitter吧,浏览器访问、手机访问、桌面软件访问、插件访问,其他网站API访问,消息访问,这client够多了吧,可是Rails需要什么Service层,什么agent层吗?




您还真说对了,实际中就是用的RMI,还就因为这个使用了SLSB(当然web可以走近道local)。
04年的项目了,现在要赶时髦当然可以换成HttpInvoker、Hessian、Buriap、甚至JSON......
不过慢着, 如果当时将应用逻辑混合到了SLSB里面(而没有将其分离到独立的service层,这个就和你认同的将逻辑写到web的action里面一样),哦,我的天,恐怕这事儿还真麻烦了......

另:
我相信没人真的喜欢webservice,
不过即使是使用webservice,webservice也只是一个adapter而已(不含任何业务),同样SLSB也只是提供RMI的adaper,这些adapter都只是技术相关的,他们本身不能算作一个层,但他们却都需要使用service层来完成动作。

从ROR看REST的神奇之处在于,他将REST(系统间的接口)和正常的Web应用(人机接口)较完美的和谐成一套代码了。再加上ROR本来就没有分层,只有MVC。而JEE是强调分层,然后再在Web层使用MVC。在Rails里面别说找什么Service层,agent层,你连表现层,逻辑层,持久层都找不到。。。差异太大,先别参考了。

twitter我是实在不了解,抱歉了。



68 楼 taowen 2008-05-13  
从简单设计和重构的角度来看。最开始,应该是直接在action中调用domain。后来发现,需要抽出一个方法来调用domain,然后这个方法提供一个服务,支持action。后来发现,这个方法还需要一些额外的依赖和更多的实现内容,于是抽取出一个接口,注入一个对象到action中。后来发现,很多地方都需要这么做,那么就强制要求action必须依赖于service的接口,不能直接访问domain对象。这么一组抽象的接口,就构成了一个service层。
67 楼 robbin 2008-05-13  
pig345 写道
taowen 写道

连接的获取和释放,和事务的管理是Service层的职责,无论Domain Model是贫血还是不贫血,都不会牵涉到连接和事务的管理。

大家的做法不某而和,呵呵。

如果做过多Client的WebServer项目的话就不会有太多疑问了。
这种项目中,web层本身只不过是众多client中的一个而已,另外还有GUI的 或 独立进程(用于系统间通信)的client,这些client并不能直接使用webserver里面的DomainObject,必须要一个Service/action层作agent,其实service层也可以发展成应用/界面逻辑(顺便控制事务边界),而Domain中是真正的领域相关的业务逻辑。


先不说你多client的项目是多么的罕见,就算你有GUI的独立client,你用什么方式和Server通信?说到底无非就是两种:

1、RMI方式
EJB就是基于RMI方式,但我相信你不会采用RMI方式,RMI不但复杂而且有太多限制,现在这种方式已经基本不被采用了

2、Web Service方式
好吧,Web Service方式你是不是还是需要Server端有一个Web层?

所以不管你是不是多个client? Server端必然都是通过Web层提供服务的,所不同的只不过是client是浏览器、手机、或者应用程序而已。所以Service层真的是必要的吗?恐怕未必吧。

你就看Rails的REST,人家只有Web层,那有怎样? 看看twitter吧,浏览器访问、手机访问、桌面软件访问、插件访问,其他网站API访问,消息访问,这client够多了吧,可是Rails需要什么Service层,什么agent层吗?


66 楼 taowen 2008-05-13  
我想robbin的意思其实是如果只有一个client,也就是在只有web层用它的情况下,service层除了啰嗦没啥价值,不如直接合并进web层。我对这个问题的看法是,要看service层中的具体服务到底有几个用户。如果到处都是只有一个用户的service,我们还要抽象出好多接口和对象来提供这些服务,不如直接在用的地方写就好了。在实际的,简单的web项目中,好像取消service层,让service层的用户也就是web层来实现事务控制安全控制好像更符合务实的精神。其实有三个选项:1、分层,2、分对象,3、分方法。简单情况下,取消service层,把起职责散布成web层的一些对象乃至成为action的一个方法,也未尝不可。不过没有具体实践,还要观察观察。
65 楼 pig345 2008-05-13  
taowen 写道

连接的获取和释放,和事务的管理是Service层的职责,无论Domain Model是贫血还是不贫血,都不会牵涉到连接和事务的管理。

大家的做法不某而和,呵呵。

如果做过多Client的WebServer项目的话就不会有太多疑问了。
这种项目中,web层本身只不过是众多client中的一个而已,另外还有GUI的 或 独立进程(用于系统间通信)的client,这些client并不能直接使用webserver里面的DomainObject,必须要一个Service/action层作agent,其实service层也可以发展成应用/界面逻辑(顺便控制事务边界),而Domain中是真正的领域相关的业务逻辑。
64 楼 Ashela 2008-05-13  
coolnight 写道
robbin 写道
taowen 写道
Robbin能否举一个例子如何像ActiveRecord那样用静态方法调用领域逻辑。



public class Employee {  
     @OneToMany  
     private Set<Task> tasks = new HashSet<Task>();  
     public Set<Task> getProcessingTask() {  
        ......
     }  
     public static getAllEmployee() {
        ......
     }
} 


比方说类似这样的,如果不依赖特定对象实例的方法,直接用静态方法如何?如果getProcessingTask用到其他对象的方法,如果是依赖对象状态,可能就是方法参数传递进去,如果不依赖,直接调用该对象静态方法。

不知道这样去用,会不会有什么问题。自从IoC兴起以后,貌似就没有人这样去用了,大家都极力避免静态方法的出现。




java里面使用静态方法的不便之处很多, 比如说不容易扩展, 不容易被替换等等,而ruby的特性决定了这些不是很大的问题。java里面这种形式的rich domain model的事务也不容易被管理
在java里面用ioc来管理依赖是比较好的做法, 很难扔掉;而且 ioc也确实带来很多的好处

一个复杂的系统里面,真正最稳定最一致的东西是什么呢, 其实正是那些 贫血的domain model!


除非需求非常明确, 系统在设计期就能定下rich domain model,最好对于独立的model很少有团队协作,这种
情况下rich domain model才有优势, 至少少了一个dao的依赖。 但是这样严格的场景太罕见了,
大部分情况下需求经常在变, 经常要加上新的功能, 这个时候就会发现,如果是rich domain model的设计,
对domain model的不断的修改就成了一个梦魇! 如果有多人协作完成系统的不同部分,互相之间要share domain
model的时候, 那rich domain model就更加难以运用了! 如果还要用Service层进行事务管理的话,那很多时候
就必须把rich domain model里面的功能在Service层全都包装一遍, 那么为什么不直接在Service层直接实现
rich domain model的功能呢?

我们公司现在的产品就是这样, 开始的时候就是贫血domain model和rich domain model都有, 越用就越发现贫血模型的好处。现在 rich domain model的功能大部分处于废弃状态, 真正有用的,基本就是 贫血 domain model, 或者说po,
加上一个薄薄的dao层, 然后系统的各个部分各自使用各自的service层,很清晰很简单很有效!!



对于模型不断的演化的问题目前最有实际意义的解决办法是测试驱动开发
63 楼 Ashela 2008-05-13  
robbin 写道
taowen 写道
To Robbin:
连接的获取和释放,和事务的管理是Service层的职责,无论Domain Model是贫血还是不贫血,都不会牵涉到连接和事务的管理。Service在使用Domain Model的时候,就要保证连接已经获得。事务可以用在Service层的方法上加Annotation,用AOP来织入实现的代码。
对于连接和事务的存在,使得在Java中必须存在Service层。这个是架构原因造成的,和Rich Domain Model无关。当然,由于Service层的存在,Java写的程序自然无法如RoR那么简洁了。所以我认同你的这个观点。确实,很多时候,Service层的代码写起来很无聊。但是为了管理事务和安全,又不得不有这么一层。

在我的方案中,Domain对象的注入不是由IoC容器来完成的。Domain对象的注入,其实是POJO->PO的过程。这个过程是由Hibernate完成的。Java的class虽然不是open的,但是也非完全closed。Hibernate用反射同样实现了Collection的增强。通过改写Hibernate增强POJO的过程,让Domain对象具有了查询自身Collection的能力。我认为具有了查询能力,Domain Model可以更加Rich。更重要的是,人们所担心的Domain对象变得复杂无比,与持久化代码发生纠缠的局面并没有出现。因为实现细节被RichSet,RichList这样的Collection API隔离了。甚至RoR更好,因为Domain对象可以脱离数据库构造出来做单元测试,完全不牵涉容器和数据库。


我原意是指抛弃Service层,不过下载你的代码看了单元测试以后,明白了。其实我希望看到的是消除Service层,否则Rich Domain Model的意义就被削弱很多了。因为最终你不得不在Service层针对domain logic再封装一层transaction script,这样Service层就显得颇为罗嗦。

而且更麻烦的是,会造成Web层的程序员的困扰。他明明看到domain model的逻辑是自洽的,却偏偏要搞清楚哪些logic不能直接调用,必须通过Service;哪些logic可以直接调用,不能通过Service,否则就会出错。与其这样,倒不如干脆把所有的逻辑剥离到Service层更直观了。

所以我的想法就是:如果不能够消除Service层的话,使用rich domain model没什么意义。





我觉得抛弃service反而使得系统和真实的用户需要衔接不上,让service成为模型和真实需求的接口岂不是更好?
至于模型应该为开发和维护这个系统的人员和领域专家谋福利,分析的好的service会带来很多系统集成上的好处
62 楼 fantasybei 2008-05-12  
服务是一些行为功能,有人指出没有行为的模型只有getter/setter
,是不是贫血模型,或者叫失血模型,DDD专家Eric Evans认为:将领域需要的功能强加给实体和值对象,不仅会破坏模型中对象定义,而且会认为地添加毫无意义的对象           呵呵,引用别人的,http://www.jdon.com/jivejdon/forum/messageList.shtml?thread=27452&count=15&start=15
61 楼 taowen 2008-05-12  
to coolnight:能不能举一个你们公司的Rich Domain Model的例子,以及它们是如何被大家废弃的。
60 楼 taowen 2008-05-12  
事务控制貌似向前推到WEB层就可以了。同样可以在方法上加Annotation。反正spring可以在WEB框架之间插一脚,把我的代码给增强了。同样,dependency也可以让spring给注入到WEB框架的Action/Controller/Managed Bean(whatever)中来。service层可以被融合到WEB层之中,变成一些特殊的方法,让容器去重载。
59 楼 coolnight 2008-05-12  
robbin 写道
taowen 写道
Robbin能否举一个例子如何像ActiveRecord那样用静态方法调用领域逻辑。



public class Employee {  
     @OneToMany  
     private Set<Task> tasks = new HashSet<Task>();  
     public Set<Task> getProcessingTask() {  
        ......
     }  
     public static getAllEmployee() {
        ......
     }
} 


比方说类似这样的,如果不依赖特定对象实例的方法,直接用静态方法如何?如果getProcessingTask用到其他对象的方法,如果是依赖对象状态,可能就是方法参数传递进去,如果不依赖,直接调用该对象静态方法。

不知道这样去用,会不会有什么问题。自从IoC兴起以后,貌似就没有人这样去用了,大家都极力避免静态方法的出现。




java里面使用静态方法的不便之处很多, 比如说不容易扩展, 不容易被替换等等,而ruby的特性决定了这些不是很大的问题。java里面这种形式的rich domain model的事务也不容易被管理
在java里面用ioc来管理依赖是比较好的做法, 很难扔掉;而且 ioc也确实带来很多的好处

一个复杂的系统里面,真正最稳定最一致的东西是什么呢, 其实正是那些 贫血的domain model!


除非需求非常明确, 系统在设计期就能定下rich domain model,最好对于独立的model很少有团队协作,这种
情况下rich domain model才有优势, 至少少了一个dao的依赖。 但是这样严格的场景太罕见了,
大部分情况下需求经常在变, 经常要加上新的功能, 这个时候就会发现,如果是rich domain model的设计,
对domain model的不断的修改就成了一个梦魇! 如果有多人协作完成系统的不同部分,互相之间要share domain
model的时候, 那rich domain model就更加难以运用了! 如果还要用Service层进行事务管理的话,那很多时候
就必须把rich domain model里面的功能在Service层全都包装一遍, 那么为什么不直接在Service层直接实现
rich domain model的功能呢?

我们公司现在的产品就是这样, 开始的时候就是贫血domain model和rich domain model都有, 越用就越发现贫血模型的好处。现在 rich domain model的功能大部分处于废弃状态, 真正有用的,基本就是 贫血 domain model, 或者说po,
加上一个薄薄的dao层, 然后系统的各个部分各自使用各自的service层,很清晰很简单很有效!!


58 楼 taowen 2008-05-12  
jjx 写道
可以用service locator,从ioc中获取hibernateTemplate或类似的东西
public static IList findAll(){
    return getHibernateTemplate().Find("from Employee e").....
}
 
static HibernateTemplate getHibernateTemplate(){
return (HibernateTemplate)ContextRegistry.GetContext()["hiberanteTemplate"];
}


不一定一定要以注入的方式


一般的攻讦方式:
serviceLocator的问题是我测试Employee的时候,不能确定它对外的依赖有多少。我只看到ContextRegistry是它的依赖。具体它要lookup多少对象,不读源代码就很难确定。这使测试的时候就拿不准到底要构造一个多大的环境。

当然,这个就是trade-off,Ruby的对象就从来不要求脱离容器(环境)存在。因为RoR的环境实在考虑得太周到了,以至于你不想脱离它,或者没什么可脱离的(融为一体了)。直接从ActiveRecord::Base集成就什么都解决了。

相关推荐

    对贫血和充血模型的理解

    特别是在面向对象编程中,贫血模型(Anemic Domain Model)和充血模型(Rich Domain Model)两种设计策略,一直被广泛讨论和应用。它们代表了不同的业务逻辑和数据关系处理方式,对系统的架构和后续维护工作有着深远...

    贫血

    为了解决贫血模型的问题,开发者可能会转向富模型(Rich Domain Model)或领域驱动设计(Domain-Driven Design, DDD)。在DDD中,业务规则和逻辑被封装到领域对象(Domain Objects)中,这些对象不仅包含数据,还...

    领域模型说明及范例代码.zip

    领域模型(Domain Model)和贫血模型(Anemic Domain Model)是两种常见的模型设计模式,它们各有特点,适用于不同的场景。本资料包旨在通过实例对比,帮助初学者理解这两种模型的区别和概念,并提供实际的Java代码...

    领域模型设计技术实战篇.docx

    - 采用领域驱动设计的实践,比如贫血模型(Anemic Domain Model)和富领域模型(Rich Domain Model),以及Repository模式来管理领域对象。 通过上述分析,我们可以看到领域模型设计的关键在于理解业务逻辑,将其...

    JAVA面试题(下).pdf

    16. 领域模型(Domain Model)和贫血模型(Anaemic Domain Model)与充血模型(Rich Domain Model)的区别在于,贫血模型将业务逻辑放在服务层,而充血模型则将业务逻辑放在领域模型的实体中。 17. 测试驱动开发...

    基于struts2 spring2.5 hibernate3的人事管理系统 源码完整包

    这个系统利用了贫血模型(Anemic Domain Model)来实现DAO层,旨在提供高效、稳定且易于维护的企业级解决方案。 首先,Struts2是一个强大的MVC(Model-View-Controller)框架,它负责处理HTTP请求,并将这些请求...

    强悍的自动生成java代码工具.rar

    例如,开发者可以决定是否生成贫血模型(Anemic Domain Model)或富模型(Rich Domain Model),是否使用MyBatis或Hibernate作为持久层框架,以及选择RESTful风格的Controller还是传统的Action风格。 此外,自动...

    多表联合分页查询(Mybatis注解).zip_9AB_mybatis_skillwoc_全注解_多表关联分页查询

    例如,使用贫血模型(Anemic Domain Model)或富领域模型(Rich Domain Model),根据项目需求选择合适的数据访问模式,以及合理地组织Mapper接口和实体类,使代码结构清晰。 总的来说,这个案例涉及了Mybatis注解...

    HCPC管理系统EJB源码

    - EJB2.0的设计通常遵循一些模式,如贫血模型(Anemic Domain Model)和富模型(Rich Domain Model)。理解这些模式可以帮助我们更好地分析HCPC管理系统中的设计决策。 8. 源码学习: - 对于"jkjkljk解决好家伙...

    EJB设计模式

    12. **贫血模型和富模型(Anemic Domain Model and Rich Domain Model)** 贫血模型将业务逻辑放在服务层,而模型对象仅包含数据。富模型则将业务逻辑内聚到域对象中,提高代码的封装性和可维护性。 通过理解和...

    struts孙卫琴.rar

    学习Struts不仅要知道如何使用,还要理解如何遵循最佳实践,如分离表现层和业务层,使用贫血模型(Anemic Domain Model),以及如何有效地组织和管理ActionForm对象。 10. **实战案例** 书中可能包含多个实战案例...

    springMVC整合mybatis时的jar

    8. **最佳实践**:在实际开发中,应遵循一定的设计原则,如贫血模型(Anemic Domain Model)和富领域模型(Rich Domain Model),以及合理的分层架构,以保持代码的可维护性和扩展性。 综上所述,"springMVC整合...

    WPF中MVVM模式原理分析与实践

    - **解决现实世界的问题**:在开发过程中,开发者需要将现实世界中的实体抽象成模型,即Domain Object。这些模型可以是简单的数据载体(贫血模型),也可以包含业务逻辑(富血模型)。无论哪种形式,模型都无法直接...

    struts例子

    使用Struts开发时,应遵循一些最佳实践,例如:明确分离业务逻辑和表现层,使用贫血模型(Anemic Domain Model),合理组织Action和ActionForm,以及充分利用拦截器来增强功能。 综上所述,Struts框架通过其强大的...

    DDD领域驱动设计day01.pdf

    1. **领域模型**(Domain Model):领域模型是业务逻辑的抽象,它包含了业务实体、值对象、领域事件、聚合、工厂、仓储等元素。这些元素共同构成了一个反映业务规则的模型。 2. **统一语言**(Ubiquitous Language...

    JavaEye论坛热点月报 总第7期

    8. **贫血模型与领域模型** - 讨论了一个简单的示例,比较了贫血模型(Anemic Domain Model)和领域模型(Domain-Driven Design)在Java应用中的应用和优缺点。 9. **Spring中的DAO与Service** - 在Spring框架中,...

    java代码生成器

    3. **贫血模型(Anemic Domain Model)**:在生成的代码中,Entity类通常只包含属性,没有业务逻辑方法,这种设计被称为贫血模型,便于保持模型的简单性和纯粹性,但可能需要额外的服务层来处理业务逻辑。...

    wcl-cqrs-example:一个简单的CQRS示例

    查询模型通常使用贫血模型(Anemic Domain Model),其中对象主要包含数据字段,不包含业务逻辑。这些模型可以映射到视图层,用于展示数据。例如,我们可能会有一个`UserReadModel`,只包含用户的基本信息,用于检索...

    struts2项目

    11. **最佳实践**:在实际项目中,应遵循一些最佳实践,例如避免在Action类中使用静态变量,使用贫血模型(Anemic Domain Model)来分离业务逻辑,以及充分利用拦截器来封装共性功能。 12. **安全性**:Struts2的...

Global site tag (gtag.js) - Google Analytics