`
魔力猫咪
  • 浏览: 105801 次
  • 来自: 北京
社区版块
存档分类
最新评论

本人的BAC框架发布0.2版,兼谈DomainEvent模式

阅读更多
经过半个多月的开发,我的BAC框架正式发布0.2版。本来预计是春节前发布,但是最近比较闲,用于开发的时间也增加了很多,框架提前完成。
0.2版在修正了0.1版错误的基础新增了验证码组件、DomainEvent框架和工具类。地址是:http://code.google.com/p/basicaidedcomponent/,欢迎大家前往下载试用。同时我在Google注册了一个论坛http://groups.google.com/group/bac_china/,不过访问总是时灵时不灵的。

说完了框架发布,我在这里谈一下0.2中比较特别的DomainEvent框架。这也是希望大家不要投新手帖。在这里发布自有框架,总会被贴上几个新手帖。一个弄不好就被丢到新手区了。

DomainEvent框架是为了解决实体与服务的交互而提出来的。在我们日常开发中,领域对象和Service的交互有时候需要领域对象调用Service。
比如我们有一个班级类,班级里有List保存学生对象。现在要求有一个统计学生的功能。如果完全按照领域驱动设计,这个功能应该是班级类自己的工作。如果所有的对象都在内存中,倒也没什么,直接统计一下List里学生对象数量就是。
public class Clazz {
    //学生
    List students;
    
    public int getStudentSize() {
        return students.size();
    }
}

但是实际上这些对象都是保存在数据库中的。我们需要使用ORM工具获取Clazz,而一般学生列表是延迟加载的。在读取students列表时,ORM再加载学生实例。如果我们需要学生列表数据也罢了,但是如果我们只是要一个学生数量,那么将所有的学生对象加载就是非常不合算的事情。也许有人说写查询语句直接让数据库统计一下就是,比如下面:
select count(s) from student s where s.clazz.id = ?

但是问题是如何调用呢?总不能写成下面这种代码
public class Clazz {
    //学生
    List students;
   //一个学生Service对象,用于处理学生相关业务
   StudentService service;
    
    public int getStudentSize() {
        return service.getStudentSize(this.id);
    }
}

很明显,实体和服务产生了紧耦合。先不说我们如何把StudentService注入Clazz的实例中,就是可以注入,产生的紧耦合也使得Clazz和StudentService绑到了一块。如果写成单独的Dao或者Service方法,那么就可能造成Clazz只是一个纯粹的持久化对象,没有了任何业务。
2008年Udi Dahan在其博客How to create fully encapsulated Domain Models一文中也提出这个问题。一个实体模型中的方法参数依赖服务或者Repository,那么就会造成实体和服务的紧耦合。
2009年6月14日作者在征询很多意见后,再次在其博客Domain Events – Salvation提出了Domain Event的解决方案。采用了事件/消息模型对实体和服务进行解耦。
定义一个事件接口
public interface DomainEvent<T> {
    /**
     * 获得发生Event的对象
     * @return Event的对象
     * @since 0.2
     */
    public T getSource();
    /**
     * 获得Event类型
     * @return Event类型
     * @since 0.2
     */
    public String getType();
}
然后定义监听器接口
public interface DomainListener {
    /**
     * 处理事件
     * @param event 事件
     * @since 0.2
     */
    public void handler(DomainEvent event);
}

现在我们可以实现一个Listenrer,把查询学生人数的代码放到监听器里。
public class StudentListener implements DomainListener {
   
    StudentService service;

    public void handler(DomainEvent event) {
          if (event.getType().equlas("getStudentSize") {
                 int size = service.getStudentSize(event.getSource().getId());
                 event.getSource().setStudentSize(size);
          }
    }


}



然后Clazz只要在需要查询人数的时候发出一个事件。
public class CLazz{
   
   int studentSize;

   public void getStudentSize() {
       DomainEventManager.disptcher(new DefaultDomainEvent(this, "getStudentSize"));
   }

}
//一个简单的DomainEvent实现
public class DefaultDomainEvent implements DomainEvent {
    private Object object;
    private String type;

    public DefaultDomainEvent(Object object, String type) {
        this.object = object;
        this.type = type;
        this.sync = sync;
    }

    public Object getSource() {
        return object;
    }

    public String getType() {
        return type;
    }
}


监听器在收到事件后就会自动调用代码进行查询,并把查询结果放到Clazz中。这里只进行一些简单描述。大家想看完整的DomainEvent代码,可以下载我的BAC框架。
disptcher会将事件交给事件管理器,事件管理器是一个Command模式,它调用Listener对事件进行处理。
public class DomainEventManager{

    /**
     * 已注册的监听器集合
     */
    private final static Map<Class, List<DomainListener>> listenerMap = new ConcurrentHashMap<Class, List<DomainListener>>();

    /**
     * 处理事件
     * @param event
     * @since 0.2
     */
    public static void dispatch(DomainEvent event) {
        //如果存在该类的监听器
        if (hasClassListener(event.getSource().getClass())) {
            List<DomainListener> listenerList = getClassListeners(event.getSource().getClass());
            //循环监听器,这是一个典型的Command模式
            for (DomainListener listener : listenerList) {
                listener.handler(event);
            }
        }
    }
}

这样实体和服务就分开了。不会造成实体和服务的紧耦合。

分享到:
评论
30 楼 unsid 2010-03-02  
这个例子不好啊,我实在没明白为什么domain object要调用services,这属于逆向调用了,在这个例子里既然获取所有学生的职责在class类里,为什么获取一个指定学生的职责却在services里?并且,就算在services里,那么services也是通过调用domain实现业务的,终归到底还是domain之间的调用。。

我相信肯定存在domain调用services的情况,但是这个例子实在让人费解阿
29 楼 apache.smack 2010-03-02  
<p>我觉得LZ这样做挺好的。一直认为领域模型应该脱离具体的技术实现。每次都依赖DAO总觉得有点别扭</p>
28 楼 魔力猫咪 2010-01-29  
donkey_lin 写道
楼主可以看一下最新Jdon framework,里面已经有Domain Event的实现,而且是异步的。

我知道。其实最早看到这个模式就是在那里。我文章里虽然只写了同步的,但是其实我框架本身同时支持同步和异步。
27 楼 donkey_lin 2010-01-28  
楼主可以看一下最新Jdon framework,里面已经有Domain Event的实现,而且是异步的。
26 楼 e3002 2010-01-28  
个人觉得,你这个框架好不好应该从方面去说:比如可维护性、解耦等等。这样大家能和自己使用现状有所比较,自认能体会到其好坏。
25 楼 myreligion 2010-01-28  
域对象分为贫血模型和富血模型,您的设计应该属于后者。对于贫血模型,对于getStudentSize()这类lazy load的东西,我在guzz中设计的模型是“自定义属性”。

class T{

public int getStudentSize() ;

}

然后在领域对象映射文件中配置字段加载属性自定义加载方式loader="xxxxx.xxxx"。基本上,域对象设计不变(还是简单的POJO)。由loader负责这类特殊内容的自定义读取,感觉比DomainEvent的方式清晰一些。您看是否有参考价值。



24 楼 魔力猫咪 2010-01-28  
jansel 写道
首先,LZ的想法是很不错的。在目前的WEB开发中(注意,我强调的是WEB),Model基本上都是贫血的,里面只有数据以及自身内部数据的处理。其他操作都交给Service来处理。

LZ提到的问题,我的理解是,本来应该属于Model自身的数据处理,但是要考虑到性能,所以需要懒加载。但是,懒加载也有解决不了的问题,比如返回班级里面学生的个数。如果使用Hibernate的懒加载,那么是需要把所有的学生读取出来然后取一个size,这里就存在一个性能问题了。当然,不知道我有没有理解LZ的意思啊。

其次,LZ的做法让人感觉耦合了。其实,耦合看你怎么看了,LZ的是API耦合。假设,我们使用Interceptor去实现,难道就没有耦合了吗?那只不过不是API耦合而已,是数据耦合,因为你同样要识别getStudentSize这个方法。

另外,Model里面耦合Service这个肯定不是上上策(当然,不能说是错误)。

LZ继续加油吧。

多谢。我这个DomainEvent本身还是处于一个试验性质,肯定有很多设计不合理的地方。大家对我文章批评了这么多(我觉得主要是举的例子不合适),我会仔细思考如何改进DomainEvent框架,看能否进一步解耦。大家还请多多对我的框架本身多多试用多多批评。BAC框架0.2除了DomainEvent之外,还有验证码等组件,希望大家对此多提意见。
23 楼 jansel 2010-01-28  
首先,LZ的想法是很不错的。在目前的WEB开发中(注意,我强调的是WEB),Model基本上都是贫血的,里面只有数据以及自身内部数据的处理。其他操作都交给Service来处理。

LZ提到的问题,我的理解是,本来应该属于Model自身的数据处理,但是要考虑到性能,所以需要懒加载。但是,懒加载也有解决不了的问题,比如返回班级里面学生的个数。如果使用Hibernate的懒加载,那么是需要把所有的学生读取出来然后取一个size,这里就存在一个性能问题了。当然,不知道我有没有理解LZ的意思啊。

其次,LZ的做法让人感觉耦合了。其实,耦合看你怎么看了,LZ的是API耦合。假设,我们使用Interceptor去实现,难道就没有耦合了吗?那只不过不是API耦合而已,是数据耦合,因为你同样要识别getStudentSize这个方法。

另外,Model里面耦合Service这个肯定不是上上策(当然,不能说是错误)。

LZ继续加油吧。
22 楼 shoushou2001 2010-01-28  
感觉有点类似适配器,在实体与服务之间加入了一个接口,达到解藕的目的。
楼主有没有DomainEvent更详细的说明。
21 楼 lonelybug 2010-01-27  
首先以LZ的例子说一下,班机有多少学生上课,本身,一个班机和学生,本身应该是两个domain model,因为很简单,学生和班级在学校整个这个context下都是拥有全局identity的。

而,一个班机拥有多少学生,本身可以把学生的统计情况作成Value Object,里面包含Student number还有其他统计情况即可。

再说回来你本身问题,一个Domain Aggregate Root本身就是第一次创建于Factory或者从Repository取得,也就是说Dao要在这两个里面进行注入,而一个Domain Aggregate Root对象应该是在通过Repository的Save之后才拥有Dao的访问权利。

而对于Repository的Dao注入,当然是在类的创建时候了--constructor。那个文章,我也看了,属于没有实际应用经验的杞人忧天罢了,记住了,不要太崇洋媚外了,不一定用英文写的文章就一定会使多高深的,就一定要去盲目追从!
20 楼 Arden 2010-01-27  
这种方式唯一的好处就是可以把CRUD中的CUD等数据库的操作异步执行~~
19 楼 Arden 2010-01-27  
另外给你推荐一下:
http://axonframework.googlecode.com

CQRS框架~~~
18 楼 Arden 2010-01-27  
我感觉真的没啥优势,反而越来越复杂~~
17 楼 topcode 2010-01-27  
单纯为了节偶的话代价是否有点大
16 楼 berlou 2010-01-27  
魔力猫咪 写道
berlou 写道
还有一个就是这种情况下,事务怎么处理?

实体本身不负责事务,事务由监听器处理。


如果同时有3个listener监听一个事件的时候呢?

你这个地方可以考虑一下不用observer这种模式。


15 楼 魔力猫咪 2010-01-27  
berlou 写道
还有一个就是这种情况下,事务怎么处理?

实体本身不负责事务,事务由监听器处理。
14 楼 魔力猫咪 2010-01-27  
可能是我写作水平太差,举的例子可能也不是很合适。我承认我目前的实现更多还处于试验性质,还很不成熟。而且因为只是进行说明,所以代码并不完整。完整代码可以下载我的框架看源代码。
对这个模式是否合适大家可以看一下我链接的最早提出该方案的作者的博客。然后再继续讨论这个模式是否合适。
13 楼 魔力猫咪 2010-01-27  
超级潜水员 写道
提醒一下楼主,使用https即可访问group,


https://groups.google.com/group/bac_china/

我知道使用https可以访问。不过即使是https也是时通时不通的。
12 楼 hatedance 2010-01-27  
别为了解耦而解耦。
把getStudentSize(Clazz clz)方法定义在ClassService上不也挺好理解?
一个班级的人数的确是可以自查,也可以让第三方机构来统计吧?
11 楼 kaipingk 2010-01-27  
什么飞机哦,越搞越复杂。。。还用到这么多模式。。。。

相关推荐

Global site tag (gtag.js) - Google Analytics