文档章节

论基于数据访问的集合类(Data Access Based Collection)和领域事件(Domain Event)模式

猪刚烈
 猪刚烈
发布于 2014/10/12 11:47
字数 2988
阅读 5
收藏 1
点赞 0
评论 0
        在正式展开之前,有一些概念要先做一个界定。首先:领域模型是指系统应对的领域中所有逻辑的一个抽象,本质上它是领域中各种对象和概念以及它们之间关系的集合。你可以用自然语言描述它,也可以用UML来描述,或者是代码去描述。特别地,当我们使用面向对象建模技术来实现这个领域模型时,我们可以把这个实现出来的模型称之为对象模型。我们可以认为领域模型是一个概念模型,是分析阶段的产物。

  让精心构建的对象模型高效地工作有很多底层的技术问题需要解决,其中如何满足领域对象的业务方法在计算过程中对数据的需求是一个普遍存在的问题(实际上,在实际应用中,我们会遇到更为复杂的情况,不只是有数据的需求,还可能出现对应用层面发生依赖)。对于这一问题,目前有两种模型可供借鉴,那就是基于数据访问的集合类和领域事件模式。


基于数据访问的集合类(Data Access Based Collection)


  基于数据访问的集合类是我在开发oobbs系统时设计的一种模式。这一模式通过一个抽象的接口来代表某一对象依赖的一组集合。当这一对象实例化时,一个基于数据访问的集合实现类会注入到这个对象中,所有通过这一集合进行的操作,比如遍历,增删元素等都是被实现类转化成数据访问操作。基于数据访问的集合类很像是一个缩水版的Repository。还是以Forum的public List<Thread> getThreads()方法为例,我们认为getThreads是Forum的一个典型的业务方法,但是由于一个Forum拥有众多的Thread,这使得我们根本不容许一次将这个集合全部加载出来。即使是在hibernate这类提供了lazy和extra lazy加载机制ORM工具里,也无法避免当我们直得去迭代这一集合时,它们会被一次性全部加载。而另一方面,实际的应用请求也不会一次请求所有的Thread,更常见的情况是以分页的形式,一小批次一小批次地请求。因此基于数据访问的集合模式使用一个集合接口做为一个占位符,并声明了一些基本的集合操作:比如返回某一区间内的子集(为分页而服务)和add,remove等操作,而实现类里,这些方法是以数据访问的方式实现的。下面是集合的接口定义。它看起来很像一个普通的集合。
package oobbs.domainmodel;

import java.io.Serializable;
import java.util.List;

/**
 * The collection interface represents a set of objects, it's like the
 * java.util.Collection, however, there no real objects in this collection, it
 * only looks like a collection, its method's implementation is database access
 * operation! see <code>oobbs.infrastructure.persistence.AbstractHibernateCollection</code> 
 * @author laurence.geng
 */
public interface Collection<Entity, PK extends Serializable, Owner> {
	void setOwner(Owner owner);

	void setOwnerName(String ownerName);

	/**
	 * Adds an object. This method will persist entity to database directly!
	 * @param e an entity instance. * @return the pK the generated primary key
	 * after insert into database.
	 */
	PK add(Entity e);

	void addAll(java.util.Collection<Entity> c);

	/**
	 * Removes the entity. This method will remove this entity from database
	 * directly.
	 */
	void remove(Entity e);

	void removeAll(java.util.Collection<Entity> c);

	boolean contains(Entity o);

	boolean isEmpty();

	int size();

	/**
	 * The most important method. It returns a subset of the whole collection.
	 * the returned subset is fetched from database by sql, hql or other data
	 * access way, The Collection itself never load all elements once time!
	 */
	List<Entity> toList(int startIndex, int offset);

	void flush();
}

  下面则是基本于hibernate的集合接口实现类。它实现了所有的基本的操作。在Forum类中就会这样一个字段以及相应的getter和setter:

@Transient
	private Collection<Thread, Long, Forum> forumThreads;

	@Autowired
	/**
	 * Sets ForumThreadCollection. 
	 * ForumThreadCollection is injected by this setter. When a collection instance injected, set this forum to its forum! 
	 */
	public void setForumThreads(@Qualifier("forumThreads") Collection<Thread, Long, Forum> forumThreads) {
		this.forumThreads = forumThreads;
		this.forumThreads.setOwner(this);
		this.forumThreads.setOwnerName("forum");
	}

	/**
	 *  Gets this forum's thread collection.
	 */
	public Collection<Thread, Long, Forum> getForumThreads() {
		return forumThreads;
	}

  其中注入的forumThreads对象是一个名为ForumThreadHibernateCollection的类,它继承了AbstractHibernateCollection类,因为没有特殊的需要,没有重写任何方法。而下面展示的是service中对这集合的一次使用:

List<Thread> threads = forum.getForumThreads().toList(startThreadIndex,threadTotal);

  我们来分析一下Domain Collection这一模式的优劣。我认为它最大的优点在于它能够以一个字段的形式存在于单端关联对象中,这使得单端对象的定义饱满,完成符合并体现了一对多双向关联中双方依赖关系。这一点是使用hiberate映射无法实现的,因为我们不能在Forum中映射@OneToMany(mappedBy="forum") private Set<Thread> threads;

  但是它的缺点也是非常明显并且似乎是无法克服的,那就是它只能用来表示直接关联的集合,如果单端对象想通过这一集合进一步遍历元素中更深层次的二级,三级集合时,domain collection就显得力不从心了。比方说:在论坛的首页上往往会罗列出各个Forum的一些基本信息,其中之一就是该Forum有多少帖子(Post),相应的,Forum对象会有这样一个方法public Long getPostCount();很显然,Post是Forum的二级集合,一个Forum需要先得到它的Thread集合,再从每个Thread中得到Post集合。我们可以为了这一方法再提供一个ForumPostHibernateCollcetion用来代表一个Forum的所有Post的集合,但是这个集合已经和模型的定义发生了偏离,因为并没有从Forum到Post的直接关联。而更加普遍的问题的是:我们会常常遇到某一个单端实体从它直接依赖的对象开始进行深度地导航(体现在SQL上就是对多个表的join操作),这时候我们不能为每一种导航而创建一个从出发点到结束点的domain collection。在这种情况下,获取数据必须通过另外一种方式进行了,那就是Domain Event模式。


领域事件(Domain Event)

  Domain Event模式最初由udi dahan提出,发表在自己的博客上:http://www.udidahan.com/2009/06/14/domain-events-salvation/这一模式得到广泛的认可。它所要应对的正是将领域对象从对repository或service的依赖中解脱出来,避免让领域对象对这些设施产生直接依赖。它的做法就是当领域对象的业务方法需要依赖到这些对象时就发出一个事件,这个事件会被相应的对象监听到并做出处理。在我的oobbs系统中,我对这一模式做了一些改进,主要是消除静态方法和规范事件模型。在我的方案中,这一机制由这样几个角色:DomainEventDispatcher,DomainEvent和DomainEventListener.

  DomainEventDispatcher会以字段的形式存在于领域对象中,负责在业务方法中dispatch领域事件。下面是所有Dispatcher的基类:

package oobbs.domainmodel;

import java.util.HashMap;
import java.util.Map;

/**
 * The DomainEventDispatcher take charge of listener registration and dispatch
 * domain event to corresponding listener to handle. Usually, one dispatch per
 * domain object.
 */
public class DomainEventDispatcher {
	/** The listener map. all registered listeners are stored in this map. */
	protected Map<String, DomainObejctListener> listeners = new HashMap<String, DomainObejctListener>();

	/** * Adds a listener. * * @param listener the listener */
	public void addListener(DomainObejctListener listener) {
		listeners.put(listener.getName(), listener);
	}

	/** * Removes all listeners. */
	public void removeAllListeners() {
		listeners.clear();
	}
}

一般来说一个领域对象会有一个对应的event dispatcher,这个dispatcher会有一组重载的dispatch方法,用于分发不同的领域事件。下面是oobbs中Forum对象的event dispatcher.

package oobbs.domainmodel.forum;

import oobbs.Constants;
import oobbs.domainmodel.DomainEventDispatcher;
import oobbs.domainmodel.ResultCollector;

/**
 * * The ForumEventDispatcher dispatch all events which about Forum object. * @author
 * laurence.geng
 */
public class ForumEventDispatcher extends DomainEventDispatcher {
	public void dispatch(GetForumThreadEvent event, ResultCollector result) {
		ForumListener forumListener = (ForumListener) listeners.get(Constants.FORUM_REPO_AS_FORUM_LISTENER);
		forumListener.handleGetForumThreadEvent(event, result);
	}

	public void dispatch(GetForumPostCountEvent event, ResultCollector result) {
		ForumListener forumListener = (ForumListener) listeners.get(Constants.FORUM_REPO_AS_FORUM_LISTENER);
		forumListener.handleGetFroumPostCountEvent(event, result);
	}

	public void dispatch(GetForumThreadCountEvent event, ResultCollector result) {
		ForumListener forumListener = (ForumListener) listeners.get(Constants.FORUM_REPO_AS_FORUM_LISTENER);
		forumListener.handleGetForumThreadCountEvent(event, result);
	}
}

系统中会有很多的domain event,下面是所有领域事件的基类:

package oobbs.domainmodel;

/**
 * The supper class of all domain events. all events should provide event
 * source, the domain object which fired this event.
 */
public class DomainEvent {
	/** The event source, the domain object which fired this event. */
	protected Object source;

	public DomainEvent(Object source) {
		super();
		this.source = source;
	}

	/** * Gets the event source. * * @return the event source */
	public Object getSource() {
		return source;
	}
}

下面就是刚才提到的例子中返回Forum某一部分(分页)Thread的事件,在这个事件中我们看到一个事件可以携带一些参数,供listener使用:

package oobbs.domainmodel.forum;

import oobbs.domainmodel.DomainEvent;

/** 
 * The Event that forum request to get its threads.
 * @author laurence.geng 
 */
public class GetForumThreadEvent extends DomainEvent {
	/** The start index of request thread. */
	private int startIndex;
	/** The count of request thread. */
	private int count;

	public GetForumThreadEvent(Forum source, int startIndex, int count) {
		super(source);
		this.startIndex = startIndex;
		this.count = count;
	}

	public int getStartIndex() {
		return startIndex;
	}

	public int getCount() {
		return count;
	}
}

而下面就是我们所有listener的基类:

package oobbs.domainmodel;

/**
 * The super class of all domain object listeners. it handles events from
 * domain objects. Each listener has to provide a name as key for registering
 * itself to dispatcher. 
 * @see DomainObejctEvent 
 * @author laurence.geng
 */
public interface DomainObejctListener {
	/** * Gets the name. * * @return the name */
	public String getName();
}

下面是Forum的listenerr接口,这个接口会有很多handle方法,代码只展示了一个。

package oobbs.domainmodel.forum;

import oobbs.domainmodel.DomainObejctListener;
import oobbs.domainmodel.ResultCollector;

/**
 * * The forum listener. It handles all events from Forum object. * * @see
 * ForumEvent * @author laurence.geng
 */
public interface ForumListener extends DomainObejctListener {
	/**
	 * * Handle the event that a forum requests to get its threads. * * @param
	 * event the event * @param result the result
	 */
	public void handleGetForumThreadEvent(GetForumThreadEvent event,ResultCollector result);
}

然后 是这个接口一个实现类,在oobbs中,做为forum的repository的实现类:ForumHibernateRepository,自然成为实现这一接口的最佳选择:

/**
 * * The Forum's repository with hibernate implementation, besides, it's a forum
 * listener which handle * all events come from forum object.
 */
public class ForumHibernateRepository extends AbstractHibernateRepository<Forum, Long> implements ForumRepository {
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * oobbs.domainmodel.forum.ForumListener#handleGetForumThreadEvent(oobbs
	 * .domainmodel.forum.GetForumThreadEvent,
	 * oobbs.domainmodel.ResultCollector)
	 */
	@Overridepublic
	void handleGetForumThreadEvent(final GetForumThreadEvent event, ResultCollector result) {
		List<Thread> threads = (List<Thread>) getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
					@SuppressWarnings("unchecked")
					public Object doInHibernate(Session session) throws HibernateException, SQLException {
						logger.info("Start to load threads of forum.");
						// Get forum.
						String getForuumThreadHql = "from Thread as thread where thread.forum=:forum";
						List<Thread> threads = (List<Thread>) session
								.createQuery(getForuumThreadHql)
								.setCacheable(true)
								.setParameter("forum", event.getSource())
								.setFirstResult(event.getStartIndex())
								.setMaxResults(event.getCount()).list();
						return threads;
					}
				});
		result.add(threads);
	}
}

最后我们看一看这一切是如被触发的。我们来看forum的这个方法:oobbs.domainmodel.forum.Forum.getThreads(int, int):

public List<Thread> getThreads(int startIndex, int count) {
		GetForumThreadEvent event = new GetForumThreadEvent(this, startIndex, count);
		ResultCollector result = new ResultCollector();
		forumEventDispatcher.dispatch(event, result);
		return (List<Thread>) result.getUniqueResult();
	}

  很简洁的四行代码:分别new一个event和result collector,然后用ForumEventDispatcher来dispatch这个事件,然后收集返回的处理结果。上述所有组件都是围绕这里而设计的,我们希望在领域对象的业务方法里不会出现任何repository或service,DomainEvent模式很好的满足了我们的需求,并且是以一种非常优雅而简洁的方式。

       当然,在上面讲述的整个机制中,我们漏掉了一环没有讲,那就是dispatcher是如何被实例化并完成注册listener工作的。由于oobbs使用了spring的IOC管理对象, dispatcher是由IOC创建并完成注册listener等初始化工作的。下面的类完成了这一系列工作:

package oobbs.infrastructure.appcontext;

import oobbs.domainmodel.DomainObejctListener;
import oobbs.domainmodel.forum.ForumEventDispatcher;
import oobbs.domainmodel.forum.ThreadEventDispatcher;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * * The ApplicationBeanPostProcessor will do some initialization work when bean
 * is created by spring ioc container, * such as: Adding listeners for domain
 * event dispatchers and so on. * @author laurence.geng
 */
public class ApplicationBeanPostProcessor implements BeanPostProcessor,
		ApplicationContextAware {
	/** The Constant logger. */
	private static final Logger logger = Logger
			.getLogger(ApplicationBeanPostProcessor.class);
	/** The application context. */
	private ApplicationContext applicationContext;

	/*
	 * (non-Javadoc) * @see org.springframework .beans.factory
	 * .config.BeanPostProcessor #postProcessBeforeInitialization
	 * (java.lang.Object, java.lang.String)
	 */

	public Object postProcessBeforeInitialization(Object bean, String beanName)	throws BeansException {
		return bean; // we could potentially return any object reference here...
	}

	/*
	 * (non-Javadoc) * @see
	 * org.springframework.beans.factory.config.BeanPostProcessor
	 * #postProcessAfterInitialization(java.lang.Object, java.lang.String)
	 */
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		logger.debug("Bean '" + beanName + "' created : " + bean.toString());
		// Adding listeners for domain event dispatchers.
		if ("forumEventDispatcher".equals(beanName)) {
			ForumEventDispatcher forumEventDispatcher = (ForumEventDispatcher) bean;
			forumEventDispatcher.addListener((DomainObejctListener) applicationContext.getBean("forumRepository"));
		}
		if ("threadEventDispatcher".equals(beanName)) {
			ThreadEventDispatcher threadEventDispatcher = (ThreadEventDispatcher) bean;
			threadEventDispatcher.addListener((DomainObejctListener) applicationContext.getBean("threadRepository"));
		}
		return bean;
	}

	/*
	 * (non-Javadoc) * @see
	 * org.springframework.context.ApplicationContextAware#setApplicationContext
	 * (org.springframework.context.ApplicationContext)
	 */
	@Override
	public void setApplicationContext(ApplicationContext arg0) throws BeansException {
		this.applicationContext = arg0;
	}
}


小结

  最后总结一下:其实基于数据访问的集合类(Data Access Based Collection)和领域事件(Domain Event)两者各有优劣。在实际中可以结合使用。如果只使用数据访问的集合类,则很难支持二、三级关联,而一味地使用领域事件则有会导致引入过多的事件类,引起类型爆炸。

本文转载自:http://blog.csdn.net/bluishglc/article/details/6616914

共有 人打赏支持
猪刚烈
粉丝 22
博文 708
码字总数 110
作品 1
海淀
程序员
从MVC框架看MVC架构的设计

从MVC框架看MVC架构的设计 尽管MVC早已不是什么新鲜话题了,但是从近些年一些优秀MVC框架的设计上,我们还是会发现MVC在架构设计上的一些新亮点。本文将对传统MVC架构中的一些弊病进行解读,...

bluishglc ⋅ 2011/08/16 ⋅ 0

从Prism中学习设计模式之Event Aggregator 模式

Event Aggregator 模式定义:渠道事件从多个对象通过一个单一的对象来简化clients的注册。 结构图: Prism的Event Aggregator 模式:Event Aggregator允许多对象定位和发布、订阅事件。 我们...

andrewniu ⋅ 05/29 ⋅ 0

C#中DataGridView控件的使用

DataGridView控件 DataGridView是用于Windows Froms 2.0的新网格控件。它可以取代先前版本中DataGrid控件,它易于使用并高度可定制,支持很多我们的用户需要的特性。 关于本文档: 本文档不准...

DDwang ⋅ 2012/10/27 ⋅ 0

什么是ActiveRecord

ActiveRecord是什么: 每一个数据库表对应创建一个类.类的每一个对象实例对应于数据库中表的一行记录; 通常表的每个字段在类中都有相应的Field; ActiveRecord同时负责把自己持久化. 在ActiveR...

银月光海 ⋅ 2015/12/03 ⋅ 0

浅谈命令查询职责分离(CQRS)模式

浅谈命令查询职责分离(CQRS)模式 在常用的三层架构中,通常都是通过数据访问层来修改或者查询数据,一般修改和查询 使用的是相同的实体。在一些业务逻辑简单的系统中可能没有什么问题,但是随...

AntMoon ⋅ 2016/03/22 ⋅ 0

前端框架开发指南

Dom是一款专门针对移动端的JS库,集成了大部分常用DOM操作API,你不需要学习任何新的东西,其用法和jQuery几乎是一样的。 创建一个Dom对象很简单只需通过 $ 对象即可 $(selector, [context])...

369yun ⋅ 2016/03/24 ⋅ 0

vert.x core vert.x的核心是一个java api的集合

At the heart of Vert.x is a set of Java APIs that we call Vert.x Core vert.x的核心是一个java api的集合 Repository. Vert.x core provides functionality for things like: 核心提供了......

天舒 ⋅ 2016/04/19 ⋅ 0

云计算的24个设计模式

注:译自微软出版的《Cloud Design Patterns Book》。下载地址:https://download.microsoft.com/download/B/B/6/BB69622C-AB5D-4D5F-9A12-B81B952C1169/CloudDesignPatternsBook-PDF.pdf 注......

开源中国驻成都办事处 ⋅ 2016/10/15 ⋅ 0

第一篇:Asp.net MVP模式介绍

作者:BirchLee 2011年8月20日 22时10分【学而不思则罔,思而不学则殆】 本文内容: 1.什么是MVP模式 2.Asp.net MVP模式简单示例 1.什么是MVP模式 任何事物的出现,必有起因。 先看起源:软件...

birchlee ⋅ 2011/08/21 ⋅ 1

重构-改善既有代码的设计-重新组织数据

重新组织数据相关重构手法 1.Self Encapsulate Field(自封装字段)(你直接访问一个字段,但是字段之间的耦合变得笨拙,为这个字段建立setter getter 并只用函数来访问字段) 2.Replace Da...

梦想游戏人 ⋅ 2016/05/16 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

来自一个优秀Java工程师的简历

写在前面: 鉴于前几天的一份前端简历,虽然带着很多不看好的声音,但却帮助了很多正在求职路上的人,不管评论怎么说,我还是决定要贴出一份后端的简历。 XXX ID:357912485 目前正在找工作 ...

颖伙虫 ⋅ 11分钟前 ⋅ 0

Confluence 6 恢复一个站点有关使用站点导出为备份的说明

推荐使用生产备份策略。我们推荐你针对你的生产环境中使用的 Confluence 参考 Production Backup Strategy 页面中的内容进行备份和恢复(这个需要你备份你的数据库和 home 目录)。XML 导出备...

honeymose ⋅ 今天 ⋅ 0

JavaScript零基础入门——(九)JavaScript的函数

JavaScript零基础入门——(九)JavaScript的函数 欢迎回到我们的JavaScript零基础入门,上一节课我们了解了有关JS中数组的相关知识点,不知道大家有没有自己去敲一敲,消化一下?这一节课,...

JandenMa ⋅ 今天 ⋅ 0

火狐浏览器各版本下载及插件httprequest

各版本下载地址:http://ftp.mozilla.org/pub/mozilla.org//firefox/releases/ httprequest插件截至57版本可用

xiaoge2016 ⋅ 今天 ⋅ 0

Docker系列教程28-实战:使用Docker Compose运行ELK

原文:http://www.itmuch.com/docker/28-docker-compose-in-action-elk/,转载请说明出处。 ElasticSearch【存储】 Logtash【日志聚合器】 Kibana【界面】 答案: version: '2'services: ...

周立_ITMuch ⋅ 今天 ⋅ 0

使用快嘉sdkg极速搭建接口模拟系统

在具体项目研发过程中,一旦前后端双方约定好接口,前端和app同事就会希望后台同事可以尽快提供可供对接的接口方便调试,而对后台同事来说定好接口还仅是个开始、设计流程,实现业务逻辑,编...

fastjrun ⋅ 今天 ⋅ 0

PXE/KickStart 无人值守安装

导言 作为中小公司的运维,经常会遇到一些机械式的重复工作,例如:有时公司同时上线几十甚至上百台服务器,而且需要我们在短时间内完成系统安装。 常规的办法有什么? 光盘安装系统 ===> 一...

kangvcar ⋅ 昨天 ⋅ 0

使用Puppeteer撸一个爬虫

Puppeteer是什么 puppeteer是谷歌chrome团队官方开发的一个无界面(Headless)chrome工具。Chrome Headless将成为web应用自动化测试的行业标杆。所以我们很有必要来了解一下它。所谓的无头浏...

小草先森 ⋅ 昨天 ⋅ 0

Java Done Right

* 表示难度较大或理论性较强。 ** 表示难度更大或理论性更强。 【Java语言本身】 基础语法,面向对象,顺序编程,并发编程,网络编程,泛型,注解,lambda(Java8),module(Java9),var(...

风华神使 ⋅ 昨天 ⋅ 0

Linux系统日志

linux 系统日志 /var/log/messages /etc/logrotate.conf 日志切割配置文件 https://my.oschina.net/u/2000675/blog/908189 logrotate 使用详解 dmesg 命令 /var/log/dmesg 日志 last命令,调......

Linux学习笔记 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部