论坛首页 Java企业应用论坛

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

浏览 8634 次
精华帖 (0) :: 良好帖 (14) :: 新手帖 (1) :: 隐藏帖 (3)
作者 正文
   发表时间:2010-01-26   最后修改:2010-01-26
经过半个多月的开发,我的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);
            }
        }
    }
}

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

   发表时间:2010-01-27   最后修改:2010-01-27
简单问题复杂化,而且这样Class也没什么东西呀,还不是调用别的类完成,只是Service、DAO还是DomainEvent名字的区别而已。

我见过一个,在Class中调用DAO,就是这样
Service->Class->DAO
0 请登录后投票
   发表时间:2010-01-27   最后修改:2010-01-27
mycybyb 写道
简单问题复杂化,而且这样Class也没什么东西呀,还不是调用别的类完成,只是Service、DAO还是DomainEvent名字的区别而已。

我见过一个,在Class中调用DAO,就是这样
Service->Class->DAO

Class里面怎么没东西呢?getStudentSize这个方法在领域模型中是属于Class的。所有对此的访问应该是基于对Class的访问。如果你觉得我简单问题复杂化,我不太明白。可能我举的例子不太合适吧。有些业务Class自己能判断,有些业务需要调用其他Service。DomainEvent解决的就是如何调用的问题。
Class中调用DAO很明显是一个错误的结构,二者紧耦合了。而且Dao要在什么时候注入Class?这些都会增加复杂性。
我也承认我这里的代码还比较初级。有些DomainEvent框架已经做到了通过注解来调用监听器了。
0 请登录后投票
   发表时间:2010-01-27  
魔力猫咪 写道

Class里面怎么没东西呢?

不好意思,这个我理解错了
0 请登录后投票
   发表时间:2010-01-27  
魔力猫咪 写道

Class中调用DAO很明显是一个错误的结构,二者紧耦合了。如果Class被分布处理、WebService调用、串行化等等会如何呢?而且Dao要在什么时候注入Class?这些都会增加复杂性。
我也承认我这里的代码还比较初级。有些DomainEvent框架已经做到了通过注解来调用监听器了。


我倒认为是个很好的方法。而且这个DAO也不一定要注入。我对领域模型不是很熟,好像有些什么工厂和仓库之类的东西负责类的创建和数据访问。
0 请登录后投票
   发表时间:2010-01-27  
Domain Event算是我框架里一个亮点,不过这个现在还更多处于理论论证。0.2版除了这个,别的组件也希望大家多多试用多多批评。特别是部分组件经过测试,我认为已经可以在日常开发中使用了。
0 请登录后投票
   发表时间:2010-01-27  
实体和服务解耦了, 不过都和你的Event紧耦合了。
相当于A耦合B变成了A耦合C, C耦合B。
我不确定我说的对不对,不过感觉这样的方式是简单问题复杂化了。
解耦和事件的应用在你这个例子中看不到好处,需要放到复杂业务的背景下, 而且不是你这种实体和服务的方式。 我想查询student的数量, 无论是list.size还是直接sql count一下是最直接的方式, 你解的藕没有太大意义。
在复杂系统中, 如果用Event, 最好用Annotation放到方法外, 而不是方法内显示raise一个event, 而具体的listner也是如此, listen的方法最多就用annotation修饰下就行, 如果单纯为了listen这个写一个代码似乎没有必要。
个人感觉interceptor处理annotation的方式会比你目前这种解耦方式好一些。
说的不对不用理会, 交流交流。
0 请登录后投票
   发表时间:2010-01-27  
提醒一下楼主,使用https即可访问group,


https://groups.google.com/group/bac_china/
0 请登录后投票
   发表时间:2010-01-27  
DomainListener这个东东在哪里注册的

个人感觉领域模型不一定要写服务方法吧,完全可以分成两个类来处理,一个实体,一个服务,大家觉得呢
0 请登录后投票
   发表时间:2010-01-27  
还有一个就是这种情况下,事务怎么处理?
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics