该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2007-03-25
拿出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 代码
封装对多关系的ToMany类如下: <jpaentity.pair><jpaentity.pair><jpaentity.pair> <pair> <pair> <pair> </pair> </pair> </pair> </jpaentity.pair></jpaentity.pair></jpaentity.pair> java 代码
还有一个ModelContext,用于获取 绑定的EntityManager: java 代码
这是一个具体的应用中的Lookup地实现,把ApplicationContextLister中作为应用的Bootrap点 注册Lookup: java 代码
相应的例子代码修改自 nihongye的代码 ,见后面的回复帖。 (郁闷,发现Javaeye一个大大的BUG,当java代码里面的常量字符串 含有 ‘> < ‘符号时,格式化出错,还把我后面的内容干掉了。 ) 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2007-03-25
排乱了...
不过你说的过程很 诱人 把文件打包传上来会好很多 |
|
返回顶楼 | |
发表时间: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加入事务。 |
|
返回顶楼 | |
发表时间:2007-03-25
呵呵, 这个讨论延续下去很好啊, 说不定最后形成一个新的, 更好的Java快速web开发框架.
总觉得现在的主流Java范式有问题, 应该改进改进了. |
|
返回顶楼 | |
发表时间:2007-03-25
引用 * 需要IOC吗? 我认为在实体Model中,不应该需要IOC。 因为一个合理的层次依赖应该是这样: Service(Manager)--->Entity 在实体中 ,自然不应该注入类似Service这样的Component 。 我感觉 既然是 rdo ,自然其功能就包括了各个service,是个功能如何组装的问题, 而不是以前的 service--> entity rdo本身就包括各种service所需要的功能以及entity的POJO功能, 我觉得接口的继承+IOC也许会是个好方式。 说到上次的讨论,robbin摆了个擂台引出来几段代码后就没说什么了,不过每个关注的人应该都有很多思考的东西,板凳ing |
|
返回顶楼 | |
发表时间: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的能力了。 |
|
返回顶楼 | |
发表时间:2007-03-25
persistence既然已经有各种orm工具做掉了,感觉也可以作为一个bean注入各个rdo来完成这部分的功能啊,为什么要退步到一个一个地写sql呢?
|
|
返回顶楼 | |
发表时间: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 负责这些外部组件的调用。 |
|
返回顶楼 | |
发表时间: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 - 基于东道的接合方式. |
|
返回顶楼 | |
发表时间: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. |
|
返回顶楼 | |