论坛首页 Java企业应用论坛

Rich Domain Model In Java ORM

浏览 39817 次
该帖已经被评为精华帖
作者 正文
   发表时间:2007-03-25  
上次展开了一次热烈的 domain model 的讨论 (  http://www.iteye.com/topic/57075 ) ,其中robbin采取了一种非常好的讨论方式:

拿出ruby 的model , 然后打擂台,看谁能够用java 写出 简洁的,和ruby差不了多少的model.

nihongye给出的 基于JPA的 domain model赢得了一片掌声,我接着nihongye的思路,在最近的一个小项目作了一次尝试,就着他的思路作了一些改动。 具体的改动如下:

*   在Context上增加一个 EntityManagerLookUp 接口,只有一个方法:getEntityManager() ; 用于返回 绑定于当前线程的EntityManager .
       原先的设计想采用ThreadLocal, 直接 register entityManger  。但是需要保证  “register entityManager的动作总是发生在 Context.getEntityManager() 之前“,这个面总是不能够很好的得到保证,比如在需要 Quartz 的时候。所以后来改为使用一个lookup接口,直接从Spring的ThreadLocal返回。


* 类似ActiveRecord . 增加一个实体的超类: JPAEntity 。  JPAEntity类似一个抽象的DAO,提供了尽可能通用的数据访问接口:
          # save ,update,delete
          # 一些通用的Query接口。
         # 结合了一个小工具DynamicQuery .提供动态查询的功能。

*  为了 实现“在对多映射上,进行合理的行为构造",nihongye提出了一个思路,具体看代码:
<kind><kind><task><task>http://nihongye.iteye.com/blog/58838

 其中的Tasks 本来是对应一个 List<task> tasks 来映射 User和Task的一对多关系。但是为了类似ruby那样的丰富的语义:

    user.tasks.find_by_name("name")

做了这样的映射。

我做了一下扩展,抽象出一个抽象类: ToMany 。 可以类似ruby在某个一对多关系上作更多的处理。
ToMany提供了如下一些通用的功能:
    # 在这个关系上作具体的查询
    # 删除 (符合条件的纪录)


还可能存在问题的地方:
    * 在复杂的继承关系上,抽象实体类可能需要加多一些特殊的声明,比如:@MappedSupperClass
    * 因为不再提倡用集合来映射一对多关系,从而在使用上带来一些问题,比如失去了批量初始化集合的特点,
        在显示多条数据的集合时,会是一个n+1次查询,这个隐患虽然问题不大,但是在一些应用中会可能成为
        一个性能隐患。
    * 需要在应用的Bootrap 点加多一个步骤; 注册合适的Lookup到ModelContext.
  
   *  需要IOC吗? 我认为在实体Model中,不应该需要IOC。 因为一个合理的层次依赖应该是这样:

        Service(Manager)--->Entity 

    在实体中 ,自然不应该注入类似Service这样的Component 。



具体的代码如下:

实体抽象类:
<pair> <pair> <pair>
</pair> </pair> </pair> </task></task></task></kind></kind>
java 代码
 
  1. package org.w2fun.jpamodel;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import javax.persistence.EntityManager;  
  7. import javax.persistence.Query;  
  8.   
  9. import org.apache.commons.logging.Log;  
  10. import org.apache.commons.logging.LogFactory;  
  11.   
  12. import net.sf.querytool.DynamicQResult;  
  13. import net.sf.querytool.FilterTag;  
  14. import net.sf.querytool.provider.QueryTool;  
  15.   
  16. /** 
  17.  *  
  18.  * @author wyx 
  19.  * @date 2007-3-23--下午02:58:16 
  20.  */  
  21.   
  22. public abstract class JPAEntity {  
  23.     protected Log LOG = LogFactory.getLog(this.getClass());  
  24.       
  25.     public static final QueryTool QT = new QueryTool(new FilterTag("ignore",  
  26.             "<"">""<!---->">"));  
  27.       
  28.     public static final Query query(final DynamicQResult dqr) {  
  29.         EntityManager em = em();  
  30.   
  31.         Query q = em.createQuery(dqr.getQueryStr());  
  32.         for (Object k : dqr.getParams().keySet()) {  
  33.             q.setParameter((String) k, dqr.getParams().get(k));  
  34.         }  
  35.         return q;  
  36.   
  37.     }  
  38.   
  39.     public static List getResultList(DynamicQResult dqr) {  
  40.         return query(dqr).getResultList();  
  41.     }  
  42.   
  43.     @SuppressWarnings("unchecked")  
  44.     public static JPAEntity findById(Class entityType, Object id) {  
  45.         return (JPAEntity) em().find(entityType, id);  
  46.     }  
  47.   
  48.     public void save() {  
  49.         em().persist(this);  
  50.     }  
  51.   
  52.     public void delete() {  
  53.         em().remove(this);  
  54.     }  
  55.   
  56.     public void update() {  
  57.         em().merge(this);  
  58.     }  
  59.   
  60.     public static Pair P(Object id, Object value) {  
  61.         return new Pair(id, value);  
  62.     }  
  63.   
  64.     public final static Query Q(Object... args) {  
  65.         String qstr = null;  
  66.         List <pair> params = new ArrayList <pair>();  </pair> </pair>
  67.         for (Object o : args) {  
  68.             if (o instanceof String)  
  69.                 qstr = (String) o;  
  70.             if (o instanceof Pair)  
  71.                 params.add((Pair) o);  
  72.             if (o instanceof Pair[]) {  
  73.                 for (Pair p : (Pair[]) o) {  
  74.                     params.add(p);  
  75.                 }  
  76.             }  
  77.             if (o instanceof List) {  
  78.                 for (Object p : (List) o) {  
  79.                     params.add((Pair) p);  
  80.                 }  
  81.             }  
  82.   
  83.         }  
  84.   
  85.         return _q(qstr, params);  
  86.     }  
  87.   
  88.     final static EntityManager em() {  
  89.         return ModelContext.getEntityManager();  
  90.     }  
  91.   
  92.     static Query _q(String qstr, List <pair> params) {  </pair>
  93.         Query q = em().createQuery(qstr);  
  94.         for (Pair p : params) {  
  95.             if (p.id instanceof String) {  
  96.                 q.setParameter((String) p.id, p.value);  
  97.             } else  
  98.                 q.setParameter(Integer.parseInt(p.id.toString()), p.value);  
  99.         }  
  100.         return q;  
  101.     }  
  102.   
  103.     public static class Pair {  
  104.         Object id;  
  105.   
  106.         Object value;  
  107.   
  108.         public Pair(Object id, Object value) {  
  109.             super();  
  110.             this.id = id;  
  111.             this.value = value;  
  112.         }  
  113.   
  114.         public String toString() {  
  115.             return "" + id + "=>" + value;  
  116.         }  
  117.     }  
  118.   
  119.   
  120. }  


封装对多关系的ToMany类如下:
<jpaentity.pair><jpaentity.pair><jpaentity.pair> <pair> <pair> <pair>
</pair> </pair> </pair> </jpaentity.pair></jpaentity.pair></jpaentity.pair>
java 代码
 
  1. package org.w2fun.jpamodel;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import javax.persistence.Query;  
  7.   
  8. import org.w2fun.jpamodel.JPAEntity.Pair;  
  9.   
  10. /** 
  11.  *  
  12.  * @author wyx 
  13.  * @date 2007-3-23--下午04:32:15 
  14.  */  
  15. public class ToMany {  
  16.     protected Object owner;  
  17.   
  18.     protected String prop;  
  19.   
  20.     protected Class entityType;  
  21.   
  22.     protected String eql;  
  23.   
  24.     protected String alias = "o";  
  25.   
  26.     public ToMany(Class clazz, String prop,Object owner) {  
  27.         super();  
  28.         this.owner = owner;  
  29.         this.prop = prop;  
  30.         this.entityType = clazz;  
  31.         initEql();  
  32.     }  
  33.   
  34.     private void initEql() {  
  35.         this.eql = "from " + entityType.getName() + " " + alias + " where "  
  36.                 + alias + "." + prop + " = :" + prop;  
  37.     }  
  38.   
  39.     public ToMany alias(String a) {  
  40.         this.alias = a;  
  41.         this.initEql();  
  42.         return this;  
  43.     }  
  44.   
  45.     Q getQByProps(Pair... props) {  
  46.         StringBuffer qstr = new StringBuffer(eql);  
  47.         List<jpaentity.pair> params = new ArrayList<jpaentity.pair>();  </jpaentity.pair></jpaentity.pair>
  48.         params.add(new Pair(prop, this.owner));  
  49.         for (Pair p : props) {  
  50.             qstr.append(" and " + alias + "." + p.id + " = :" + p.id);  
  51.             params.add(p);  
  52.         }  
  53.         return new Q(qstr.toString(), params);  
  54.     }  
  55.   
  56.     Q getDelQByProps(Pair... props) {  
  57.         Q q = getQByProps(props);  
  58.         return new Q("delete  " + q.qstr, q.params);  
  59.     }  
  60.   
  61.     public List getAll() {  
  62.         return query(getQByProps()).getResultList();  
  63.     }  
  64.   
  65.     private Query query(Q q) {  
  66.         return JPAEntity.Q(q.qstr, q.params);  
  67.     }  
  68.   
  69.     public List findByProps(Pair... props) {  
  70.         return query(getQByProps(props)).getResultList();  
  71.     }  
  72.   
  73.     public Object findSingleByProps(Pair... conditions) {  
  74.         return query(getQByProps(conditions)).getSingleResult();  
  75.     }  
  76.   
  77.     public List findByConditions(Object... args) {  
  78.         return query(getQByConditions(args)).getResultList();  
  79.     }  
  80.   
  81.     public Object findSingleByConditions(Object... args) {  
  82.         return query(getQByConditions(args)).getSingleResult();  
  83.     }  
  84.   
  85.     public Query qfind(Pair... props) {  
  86.         return query(getQByProps(props));  
  87.     }  
  88.   
  89.     public int deleteByProps(Pair... conditions) {  
  90.         return query(getDelQByProps(conditions)).executeUpdate();  
  91.     }  
  92.   
  93.     public static final class Q {  
  94.         String qstr;  
  95.   
  96.         List<jpaentity.pair> params;  </jpaentity.pair>
  97.   
  98.         public Q(String qstr, List <pair> params) {  </pair>
  99.             super();  
  100.             this.qstr = qstr;  
  101.             this.params = params;  
  102.         }  
  103.   
  104.         public String toString() {  
  105.             return "qstr:" + qstr + " Params:" + params;  
  106.         }  
  107.     }  
  108.   
  109.     public static final Pair P(Object id, Object value) {  
  110.         return JPAEntity.P(id, value);  
  111.     }  
  112.   
  113.     Q getQByConditions(Object... args) {  
  114.         if (args == null || args.length == 0)  
  115.             throw new IllegalArgumentException("incorrect args");  
  116.         String conds = (String) args[0];  
  117.         String feql = this.eql + " and " + conds;  
  118.         List <pair> params = new ArrayList <pair>();  </pair> </pair>
  119.         params.add(new Pair(this.prop, this.owner));  
  120.         for (int i = 1; i < args.length; i++) {  
  121.             Object o = args[i];  
  122.             if (o instanceof Pair) {  
  123.                 params.add((Pair) o);  
  124.             } else  
  125.                 throw new RuntimeException("the cdr of args must be Pair");  
  126.         }  
  127.         return new Q(feql, params);  
  128.     }  
  129.   
  130. }  



还有一个ModelContext,用于获取 绑定的EntityManager:

java 代码
 
  1. package org.w2fun.jpamodel;  
  2.   
  3. import javax.persistence.EntityManager;  
  4.   
  5. /** 
  6.  *  
  7.  * @author wyx 
  8.  * @date 2007-3-23--下午03:00:32 
  9.  */  
  10. public class ModelContext {  
  11.     private  static EntityManagerLookup EM_LOOKUP  = null;  
  12.       
  13.     public final static void registerEntityManagerLookup(EntityManagerLookup lookup){  
  14.         EM_LOOKUP = lookup;  
  15.     }  
  16.       
  17.   
  18.     public final static EntityManager getEntityManager() {  
  19.         return EM_LOOKUP.lookupEntityManager();  
  20.     }  
  21.       
  22.     public interface EntityManagerLookup{  
  23.         EntityManager lookupEntityManager();  
  24.     }  
  25.   
  26. }  


这是一个具体的应用中的Lookup地实现,把ApplicationContextLister中作为应用的Bootrap点 注册Lookup:

java 代码
 
  1. package org.w2fun.jpamodel;  
  2.   
  3. import javax.persistence.EntityManager;  
  4. import javax.persistence.EntityManagerFactory;  
  5. import javax.servlet.ServletContext;  
  6. import javax.servlet.ServletContextEvent;  
  7. import javax.servlet.ServletContextListener;  
  8.   
  9. import org.springframework.orm.jpa.EntityManagerHolder;  
  10. import org.springframework.transaction.support.TransactionSynchronizationManager;  
  11. import org.springframework.web.context.WebApplicationContext;  
  12. import org.springframework.web.context.support.WebApplicationContextUtils;  
  13. import org.w2fun.jpamodel.ModelContext.EntityManagerLookup;  
  14.   
  15. /** 
  16.  *  
  17.  * @author wyx 
  18.  * @date 2007-3-23--下午08:08:29 
  19.  */  
  20. public class JPAEmlookupRegister implements ServletContextListener {  
  21.     public static final String DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME = "entityManagerFactory";  
  22.   
  23.     private String entityManagerFactoryBeanName = DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME;  
  24.   
  25.     public String getEntityManagerFactoryBeanName() {  
  26.         return entityManagerFactoryBeanName;  
  27.     }  
  28.   
  29.     public void setEntityManagerFactoryBeanName(  
  30.             String entityManagerFactoryBeanName) {  
  31.         this.entityManagerFactoryBeanName = entityManagerFactoryBeanName;  
  32.     }  
  33.   
  34.     public void contextDestroyed(ServletContextEvent event) {  
  35.   
  36.     }  
  37.   
  38.     protected EntityManagerFactory lookupEntityManagerFactory(ServletContext sc) {  
  39.         WebApplicationContext wac = WebApplicationContextUtils  
  40.                 .getRequiredWebApplicationContext(sc);  
  41.         return (EntityManagerFactory) wac.getBean(  
  42.                 getEntityManagerFactoryBeanName(), EntityManagerFactory.class);  
  43.     }  
  44.   
  45.     public void contextInitialized(final ServletContextEvent event) {  
  46.         EntityManagerLookup eml = new EntityManagerLookup() {  
  47.   
  48.             public EntityManager lookupEntityManager() {  
  49.                 EntityManagerFactory emf = lookupEntityManagerFactory(event  
  50.                         .getServletContext());  
  51.                 if (TransactionSynchronizationManager.hasResource(emf)) {  
  52.                     EntityManagerHolder holder = (EntityManagerHolder) TransactionSynchronizationManager  
  53.                             .getResource(emf);  
  54.                     EntityManager s = holder.getEntityManager();  
  55.                     return s;  
  56.                 } else  
  57.                     return null;  
  58.             }  
  59.         };  
  60.         ModelContext.registerEntityManagerLookup(eml);  
  61.   
  62.     }  
  63.   
  64. }  




相应的例子代码修改自 nihongye的代码  ,见后面的回复帖。

(郁闷,发现Javaeye一个大大的BUG,当java代码里面的常量字符串 含有 ‘> < ‘符号时,格式化出错,还把我后面的内容干掉了。 )
  • RDM.rar (92.6 KB)
  • 描述: 源文件以及依赖的jar
  • 下载次数: 343
   发表时间:2007-03-25  
排乱了...
不过你说的过程很 诱人
把文件打包传上来会好很多
0 请登录后投票
   发表时间:2007-03-25  
修改自 nihongye's domain model的几个骨干类代码如下:


package org.w2fun.jpamodel;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

import org.w2fun.jpamodel.JPAEntity;
import org.w2fun.jpamodel.ToMany;

/**
 * @author nihy
 * @since 2007-3-4-下午11:47:18
 */
@Entity
public class User extends JPAEntity {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	public int id = -1;

	public String name;

	public String department;

	@OneToMany
	public List<Kind> kinds = new ArrayList<Kind>();

	public User(String name, String department) {
		this.name = name;
		this.department = department;
	}

	public Tasks tasks = new Tasks(this);

	public class Tasks extends ToMany {
		

		public Tasks(User user) {
			super(Task.class, "owner",user);
		}

		public Task find_by_name(String task) {
			return (Task) findSingleByProps(P("task", task));
		}

		@SuppressWarnings("unchecked")
		public List<Task> processing_tasks() {
			return alias("t").findByConditions(
					"t.startTime <= :startTime  and t.endTime is null",
					P("startTime", new Date()));
		}

		public boolean detectProcessingTask(String task) {
			for (Task taskEntity : processing_tasks()) {
				if (task.equals(taskEntity.task)) {
					return true;
				}
			}
			return false;
		}
	}

	public static User find_By_Name_And_Department(String name,
			String department) {
		return (User) Q("from User u where u.name = ?1 and u.department = ?2",
				P(1, name), P(2, department)).getSingleResult();
	}

	public static User create(String name, String department) {
		User user = new User(name, department);
		user.save();
		return user;
	}

	public void applyTask(Task task) {
		task.owner = this;
		task.startTime = new Date();
		task.save();
	}

	@SuppressWarnings("unchecked")
	public static List<Task> all_processing_tasks() {
		return Q("from Task t where t.startTime <= ?1  and t.endTime is null",
				P(1, new Date())).getResultList();
	}

	public void endTask(Task task) {
		task.endTime = new Date();
	}
}




package org.w2fun.jpamodel;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.w2fun.jpamodel.JPAEntity;

/**
 * @author nihy
 * @since 2007-3-4-下午11:53:33
 */
@Entity
public class Kind extends JPAEntity {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	public int id = -1;

	public String name;

	public Kind(String name) {
		this.name = name;
	}

	public static Kind create(String name) {
		Kind kind = new Kind(name);
		kind.save();
		return kind;
	}

	public void addBatchTaskToUsers(String task) {
		List<User> users = users();
		for (User user : users) {
			Task taskEntity = new Task(task, this);
			taskEntity.save();
			user.applyTask(taskEntity);
		}
	}

	@SuppressWarnings("unchecked")
	public List<User> users() {
		return Q(
				"select distinct u from User as u inner join u.kinds as kind where kind = ?1",
				P(1, this)).getResultList();

	}

}




package org.w2fun.jpamodel;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

import org.w2fun.jpamodel.JPAEntity;

/**
 * @author nihy
 * @since 2007-3-4-下午11:55:07
 */
@Entity
public class Task extends JPAEntity {
	public Task(String task, Kind kind) {
		this.kind = kind;
		this.task = task;
	}

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	public int id = -1;

	public Date startTime;

	public Date endTime;

	public String task;

	@ManyToOne
	public User owner;

	@ManyToOne
	public Kind kind;

	public static Task create(String name, Kind kind) {
		Task task = new Task(name, kind);
		task.save();
		return task;
	}

	private static Date getCurrentMonthBegin() {
		Calendar calendar = Calendar.getInstance();
		calendar.set(Calendar.DAY_OF_MONTH, 0);
		Date begin = calendar.getTime();
		return begin;
	}

	public static Date getCurrentMonthEnd() {
		Calendar calendar = Calendar.getInstance();
		calendar.set(Calendar.DAY_OF_MONTH, calendar
				.getMaximum(Calendar.DAY_OF_MONTH));
		Date end = calendar.getTime();
		return end;
	}

	@SuppressWarnings("unchecked")
	public static List<Task> allTask_CurrentMonth() {
		Date begin = getCurrentMonthBegin();
		Date end = getCurrentMonthEnd();
		return Q(
				"from Task as t where t.startTime >= ?1 and t.startTime <= ?2",
				P(1, begin), P(2, end)).getResultList();

	}

	@SuppressWarnings("unchecked")
	public static List<Task> processingTasks_CurrentMonth() {
		Date begin = getCurrentMonthBegin();
		Date end = getCurrentMonthEnd();
		return Q(
				"from Task as t where t.startTime >= ?1 and t.startTime <= ?2 and t.endTime is null",
				P(1, begin), P(2, end)).getResultList();

	}

	@SuppressWarnings("unchecked")
	public static List<Task> processedTasks_CurrentMonth() {
		Date begin = getCurrentMonthBegin();
		Date end = getCurrentMonthEnd();
		return Q("from Task as t where t.endTime >= ?1 and t.endTime <= ?2 ",
				P(1, begin), P(2, end)).getResultList();
	}
}




事务的边界可以通过Service加入。

当然,如果想更方便的话, 也可以通过xwork的拦截器或者Filter加入事务。

0 请登录后投票
   发表时间:2007-03-25  
呵呵, 这个讨论延续下去很好啊, 说不定最后形成一个新的, 更好的Java快速web开发框架.

总觉得现在的主流Java范式有问题, 应该改进改进了.
0 请登录后投票
   发表时间:2007-03-25  
引用

*  需要IOC吗? 我认为在实体Model中,不应该需要IOC。 因为一个合理的层次依赖应该是这样:

        Service(Manager)--->Entity

    在实体中 ,自然不应该注入类似Service这样的Component 。


我感觉
既然是 rdo ,自然其功能就包括了各个service,是个功能如何组装的问题,
而不是以前的 service--> entity
rdo本身就包括各种service所需要的功能以及entity的POJO功能,
我觉得接口的继承+IOC也许会是个好方式。

说到上次的讨论,robbin摆了个擂台引出来几段代码后就没说什么了,不过每个关注的人应该都有很多思考的东西,板凳ing
0 请登录后投票
   发表时间:2007-03-25  
jianfeng008cn 写道
引用

*  需要IOC吗? 我认为在实体Model中,不应该需要IOC。 因为一个合理的层次依赖应该是这样:

        Service(Manager)--->Entity

    在实体中 ,自然不应该注入类似Service这样的Component 。


我感觉
既然是 rdo ,自然其功能就包括了各个service,是个功能如何组装的问题,
而不是以前的 service--> entity
rdo本身就包括各种service所需要的功能以及entity的POJO功能,
我觉得接口的继承+IOC也许会是个好方式。

说到上次的讨论,robbin摆了个擂台引出来几段代码后就没说什么了,不过每个关注的人应该都有很多思考的东西,板凳ing

恩 ,这确实是一个 “个人喜好“ 的问题,我个人喜欢在 Service层作为“业务组合者“的角度,比如和外部组建交互的逻辑,我就很习惯放在这里,这样做的目的是 不喜欢让 entity 依赖于这些外部组件,有时候可能是 “反感包的双向依赖‘所致。 

不过,这确实是个人喜好问题。

如果 需要加入 IOC的话,那么可以在JPAEntity的构造器里面进行一次 对entity的 autowireByName (byType ) 使得 实体可以注入 同名的组件。

这样的话 ,Lookup增加一个方法: getApplicationContext() . 然后就可以获得 autowire的能力了。
0 请登录后投票
   发表时间:2007-03-25  
persistence既然已经有各种orm工具做掉了,感觉也可以作为一个bean注入各个rdo来完成这部分的功能啊,为什么要退步到一个一个地写sql呢?
0 请登录后投票
   发表时间:2007-03-26  
要么全盘entity话。事务边界在外面基础设施作,比如filter,intercepter 或者action。

要么 servce 和 rich domain model并存。

我选择先一步步走的方式。

先 service 和 entity并存。 service充当事务和逻辑的组合者。

entity上增加了CRUD的功能,可以使得原本 “贫血模型“中的显得“臃肿“的Service的代码分离很多出去到 Entity中,但是这个分离 我觉得应该有几个指导原则,不然就有点混乱了,有几个指导原则可以给大家作为参考:

*  entity 尽量不需要依赖外围Service组件

* 因为Entity增加了Query,Update的功能,那么原来很多只能在Service上作的代码可以相应作为Entity的领域逻辑 而 移入到 Entity的领域方法中 ,使得 Service的代码大大减少,整个功能的逻辑很好的分离到Service 和entity层中。 


* 属于Entity的逻辑代码 应该 有这么几个特征:
               #  依赖于本身的或者别的实体的状态
               #   依赖于本身的或者别的实体的查找结果 (Query)
               #  修改实体的状态 (包括数据库的状态)

#  如果一个复杂功能需要几个外部组件的完成,Service 负责这些外部组件的调用。
0 请登录后投票
   发表时间:2007-03-26  
我正在写的一个论文章节刚好有些关系, 先简单表达一下我的研究思路, 回头写完了再仔细说.

Domain Model实质上是一个应用系统对所要解决的问题(Problems Domain)所建立的模型, 它范围内Domain Object的状态数据和行为逻辑反应问题本身的特质, 拟画出自身系统的问题元素在受到外界扰动(用户通过界面进行操作 或者 其他系统通过API交互)时所做出的反应. 反应包括 接受输入数据, 增读删改 特定域对象, 回馈输出数据.

而Service, 是自身系统定义好的交互接口(包括 用户界面 和 开发API)的具体实现. Service是负责根据接口的输入产生正确的输出而存在的, 它应该是授命于来自系统外部的请求而立即执行的逻辑.

Service的逻辑应该是去 构造 或 删除 Domain Objects, 或者调用Domain Objects的公开方法去获取信息数据和影响系统内部状态, 而不是假装自己是某个问题元素而做出该对象的行为反应.

而Domain Object的构造方法逻辑, 以及系统应当提供给Domain Object可以重载的 创建/删除 校验逻辑, 以及 Domain Object 开放给Service的所有方法逻辑当属 Domain Logics.

可能这是一个比较可行的划分Service Logic与Domain Logic的界限:
* Service Logic对且仅对外部接口负责, 相当于外部世界的代理, 应该假定自己对系统内部一无所知, 仅通过调用Domain Object暴露的方法获得和影响系统状态信息数据.
* Domain Logic作为Domain Objects的行为等待被动触发, 执行时维护本对象内封装的状态信息, 也可以调用其他Domain Objects的方法将系统外扰动延及到他们, 包括从他们那里获取根据他们自己的封装数据得出的信息数据.

不过有些系统定义的接口其实只是跟域对象的直接交互, 这种接口会让Service Logic和Domain Logic在实现层面上混淆不分. 但是从逻辑上, 我们可以把系统用于实现对外接口(用户界面 或者 公开API)的逻辑都认为是Service Logic; 而Domain Logic 作为Domain Object的行为应该都是用于封装系统内部状态, 不应与系统对外接口(用户界面 或 公开API) 有牵连.

软件系统架构设计的进一步发展会引发出外部可执行逻辑进入本系统Domain Model, 在本地直接与Domain Object进行密集的交互, 然后回写信息的模式. 这有别于预先定义好一系列Service接口作为协议合同的老路, 属于目前还没有的一种软件架构, 我研究时把这种模式叫做 Hosting Based Interfacing - 基于东道的接合方式.
0 请登录后投票
   发表时间:2007-03-26  
complystill 写道
我正在写的一个论文章节刚好有些关系, 先简单表达一下我的研究思路, 回头写完了再仔细说.

Domain Model实质上是一个应用系统对所要解决的问题(Problems Domain)所建立的模型, 它范围内Domain Object的状态数据和行为逻辑反应问题本身的特质, 拟画出自身系统的问题元素在受到外界扰动(用户通过界面进行操作 或者 其他系统通过API交互)时所做出的反应. 反应包括 接受输入数据, 增读删改 特定域对象, 回馈输出数据.

而Service, 是自身系统定义好的交互接口(包括 用户界面 和 开发API)的具体实现. Service是负责根据接口的输入产生正确的输出而存在的, 它应该是授命于来自系统外部的请求而立即执行的逻辑.

Service的逻辑应该是去 构造 或 删除 Domain Objects, 或者调用Domain Objects的公开方法去获取信息数据和影响系统内部状态, 而不是假装自己是某个问题元素而做出该对象的行为反应.

而Domain Object的构造方法逻辑, 以及系统应当提供给Domain Object可以重载的 创建/删除 校验逻辑, 以及 Domain Object 开放给Service的所有方法逻辑当属 Domain Logics.

可能这是一个比较可行的划分Service Logic与Domain Logic的界限:
* Service Logic对且仅对外部接口负责, 相当于外部世界的代理, 应该假定自己对系统内部一无所知, 仅通过调用Domain Object暴露的方法获得和影响系统状态信息数据.
* Domain Logic作为Domain Objects的行为等待被动触发, 执行时维护本对象内封装的状态信息, 也可以调用其他Domain Objects的方法将系统外扰动延及到他们, 包括从他们那里获取根据他们自己的封装数据得出的信息数据.

不过有些系统定义的接口其实只是跟域对象的直接交互, 这种接口会让Service Logic和Domain Logic在实现层面上混淆不分. 但是从逻辑上, 我们可以把系统用于实现对外接口(用户界面 或者 公开API)的逻辑都认为是Service Logic; 而Domain Logic 作为Domain Object的行为应该都是用于封装系统内部状态, 不应与系统对外接口(用户界面 或 公开API) 有牵连.

软件系统架构设计的进一步发展会引发出外部可执行逻辑进入本系统Domain Model, 在本地直接与Domain Object进行密集的交互, 然后回写信息的模式. 这有别于预先定义好一系列Service接口作为协议合同的老路, 属于目前还没有的一种软件架构, 我研究时把这种模式叫做 Hosting Based Interfacing - 基于东道的接合方式.


说那么多还是不直观,用代码说话最好。请直接给出一个典型的业务场景,然后用代码来说明你的观点,到底什么样子的逻辑属于Service Logic,应该写在Service接口的实现中,到底什么样子的逻辑属于Domain Logic,应该写在Domain Model中。

很久以来,我一直反对Domain Model解决一切Logic问题。一个典型的业务场景是一个顺序的Process,这个Process对外表现就是Service Logic的接口。任何一个Process可以很简单,也可以很复杂,简单的Process或许只需要改变某个Domain Object的状态就行了,复杂的Process需要肢解成许多小的Process,并将他们组合起来完成整个逻辑,这个肢解的过程是递归的。这样一个过程是无法直接用Domain Logic来完成而需要一种“外力”来定义,那就是Service Logic.
0 请登录后投票
论坛首页 Java企业应用版

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