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

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

说完了框架发布,我在这里谈一下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在其博客[url=http://www.udidahan.com/2008/02/29/how-to-create-fully-encapsulated-domain-models/]How to create fully encapsulated Domain Models[/url]一文中也提出这个问题。一个实体模型中的方法参数依赖服务或者Repository,那么就会造成实体和服务的紧耦合。
2009年6月14日作者在征询很多意见后,再次在其博客[url=http://www.udidahan.com/2009/06/14/domain-events-salvation]Domain Events – Salvation[/url]提出了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);
}
}
}
}

这样实体和服务就分开了。不会造成实体和服务的紧耦合。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值