本人的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);
}
}
}
}

這樣實體和服務就分開了。不會造成實體和服務的緊耦合。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章