2007-03-03
domain model的延伸讨论
关键字: domain model
domain model,又称为领域模型,是Java企业应用讨论的一个热门话题,JavaEye也曾经多次围绕这个话题讨论,我们来看个简单的例子:
一个简单的公司工时管理系统,记录员工的个人信息,每个员工的工作任务分配,以及工作所属类别(例如开发,还是测试,还是培训等等),其中每个员工有n个任务,员工和任务是一对多关系,每个员工也分别隶属于多个不同的工作类别,员工和类型是多对多关联关系,而每个任务也分别隶属于唯一的工作类别,任务和类别是多对一关系。另外系统不要求对部门信息进行维护,不需要department表。因此,在这个系统中使用四张数据库表:
users表保存员工信息,有name, password, gender, department, salary
tasks表保存工作任务信息,有name,start_time, end_time
kinds表保存工作所属类别,有name
kinds_users表是一张关联表,保存users表和kinds表的多对多关联外键的
系统的功能需求如下:
1、某部门录用一名新员工
2、某部门员工总薪水总和
3、某员工已经开始但尚未结束的任务
4、给某员工分配一项任务
5、所有用户当前已经开始但尚未结束的任务
6、对某一类别,给所有和此一类别相关的员工,批量新增一批任务
7、针对任务的统计功能,给定某类别,统计当月总的任务数,已完成任务数,未完成任务数
我们先看看用ruby如何实现系统的领域模型:
1、某部门录用一名新员工
2、某部门员工总薪水总和
3、某员工已经开始但尚未结束的任务
4、给某员工分配一项任务
5、所有用户当前已经开始但尚未结束的任务
6、对某一类别,给所有和此一类别相关的员工,批量新增一批任务
7、针对任务的统计功能,给定某类别,统计当月总的任务数,已完成任务数,未完成任务数
这里值得注意的是,RoR可以很方便的采用充血的领域模型,所有的业务逻辑都可以放在相关的domain model里面。这里的user,task和kind都是对应于数据库表的领域模型,而department是不对应数据库的纯业务逻辑的domain model。总共4个ruby文件,4个domain model,55行代码,所有要写的代码都在这里了,代码量确实非常少,每个domain model的颗粒度都比较大。
然后我们再看看如何用Java:
Java版本的实现代码大家都比较熟悉,因此绝大部分代码都省略了。Java版本需要3个持久对象,3个映射XML文件,3个DAO接口和实现类,4个Service和实现类,和一个IoC的bean组装文件,总共21个文件,全部逻辑写完整,代码行数至少上千行。
通过对比,我们可以看到Java比较流行的实现是贫血的模型,按照面向对象的基本原则,对象的状态应该和它的行为封装在一起,因此Java多出来的这些XXXService是一些从纯理论角度而言应该放入其相应的持久对象中去。但是Java实现充血模型从技术上有一定的难度,如何Service方法挪入到持久对象中呢?如何解决Dao的注入问题?如何解决domain logic方法的事务封装问题?前者可以通过AspectJ的静态织入来解决,后者也许可以通过织入或者annotation声明来解决。但不管怎么说,Java从技术上很难实现充血模型,而且即使实现充血模型,也会导致一个Java类好几百行代码的状况,其代码的可阅读性,模块解藕能力都会变得很差,因此我们认为Java不适合充血模型,在表达复杂的业务逻辑的能力上,Java要比ruby差很多:
结论:
对于Java来说,更加适合采用贫血的模型,Java比较适合于把一个复杂的业务逻辑分离到n个小对象中去,每个小对象描述单一的职责,n个对象互相协作来表达一个复杂的业务逻辑,这n个对象之间的依赖和协作需要通过外部的容器例如IoC来显式的管理。但对于每个具体的对象来说,他们毫无疑问是贫血的。
这种贫血的模型好处是:
1、每个贫血对象职责单一,所以模块解藕程度很高,有利于错误的隔离。
2、非常重要的是,这种模型非常适合于软件外包和大规模软件团队的协作。每个编程个体只需要负责单一职责的小对象模块编写,不会互相影响。
贫血模型的坏处是:
1、由于对象状态和行为分离,所以一个完整的业务逻辑的描述不能够在一个类当中完成,而是一组互相协作的类共同完成的。因此可复用的颗粒度比较小,代码量膨胀的很厉害,最重要的是业务逻辑的描述能力比较差,一个稍微复杂的业务逻辑,就需要太多类和太多代码去表达(针对我们假定的这个简单的工时管理系统的业务逻辑实现,ruby使用了50行代码,但Java至少要上千行代码)。
2、对象协作依赖于外部容器的组装,因此裸写代码是不可能的了,必须借助于外部的IoC容器。
对于Ruby来说,更加适合充血模型。因为ruby语言的表达能力非常强大,现在用ruby做企业应用的DSL是一个很热门的领域,DSL说白了就是用来描述某个行业业务逻辑的专用语言。
充血模型的好处是:
1、对象自洽程度很高,表达能力很强,因此非常适合于复杂的企业业务逻辑的实现,以及可复用程度比较高。
2、不必依赖外部容器的组装,所以RoR没有IoC的概念。
充血模型的坏处是:
1、对象高度自洽的结果是不利于大规模团队分工协作。一个编程个体至少要完成一个完整业务逻辑的功能。对于单个完整业务逻辑,无法再细分下去了。
2、随着业务逻辑的变动,领域模型可能会处于比较频繁的变动状态中,领域模型不够稳定也会带来web层代码频繁变动。
附件是完整的RoR版本的项目示例代码。要运行它,需要安装MySQL数据库(InnoDB表类型),Ruby和Ruby on rails环境。在MySQL数据库中分别创建demo数据库和demo_test数据库,修改demo\config\database.yml中的MySQL数据库配置,改成你的数据库密码。然后在项目跟目录下面执行:
rake db:migrate
rake db:test:clone_structure
rake test
即创建开发环境数据库,创建测试环境数据库,和执行所有的单元测试。领域模型代码位于demo\app\models目录下面;单元测试代码位于demo\test\units目录下面
引用
一个简单的公司工时管理系统,记录员工的个人信息,每个员工的工作任务分配,以及工作所属类别(例如开发,还是测试,还是培训等等),其中每个员工有n个任务,员工和任务是一对多关系,每个员工也分别隶属于多个不同的工作类别,员工和类型是多对多关联关系,而每个任务也分别隶属于唯一的工作类别,任务和类别是多对一关系。另外系统不要求对部门信息进行维护,不需要department表。因此,在这个系统中使用四张数据库表:
users表保存员工信息,有name, password, gender, department, salary
tasks表保存工作任务信息,有name,start_time, end_time
kinds表保存工作所属类别,有name
kinds_users表是一张关联表,保存users表和kinds表的多对多关联外键的
系统的功能需求如下:
1、某部门录用一名新员工
2、某部门员工总薪水总和
3、某员工已经开始但尚未结束的任务
4、给某员工分配一项任务
5、所有用户当前已经开始但尚未结束的任务
6、对某一类别,给所有和此一类别相关的员工,批量新增一批任务
7、针对任务的统计功能,给定某类别,统计当月总的任务数,已完成任务数,未完成任务数
我们先看看用ruby如何实现系统的领域模型:
class User < ActiveRecord::Base
has_and_belongs_to_many :kinds
has_many :tasks, :dependent => :destroy do
def processing_tasks
find :all, :conditions => ["start_time <= ? AND end_time is null", Time.now]
end
end
def apply_task(task_name)
self.tasks << Task.new(:name => task_name, :start_time => Date.today)
end
def self.all_processing_tasks
Task.find :all, :conditions => ["start_time <= ? AND end_time is null AND user_id is not null",Time.now]
end
end
class Task < ActiveRecord::Base
belongs_to : owner, :class_name => 'User', :foreign_key => 'user_id'
belongs_to :kind
def self.current_month_tasks(kind)
kind.tasks.current_month_tasks
end
end
class Kind < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :tasks do
def current_month_tasks
month_begin = Date.today - Date.today.mday + 1
month_end = Date.today - Date.today.mday + 30
processing_tasks = find :all, :conditions => ["start_time <= ? AND end_time is null ", month_begin]
processed_tasks = find :all, :conditions => ["end_time >= ? AND end_time <= ? ", month_begin, month_end]
all_tasks = processing_tasks.clone
all_tasks << processed_tasks unless processed_tasks.size == 0
return all_tasks, processed_tasks, processing_tasks
end
end
def add_batch_task_to_users(task_name)
self.users.each do |user|
task = Task.new(:name => task_name, :start_time => Date.today)
user.tasks << task
self.tasks << task
end
end
end
class Department
def self.employee(username, department)
User.create(:name => username, :department => department)
end
def self.total_salary(department)
User.sum :salary, :conditions => ["department = ?", department]
end
end
1、某部门录用一名新员工
Department.employee("robbin","开发部")
2、某部门员工总薪水总和
Department.total_salary("开发部")
3、某员工已经开始但尚未结束的任务
user.tasks.processing_tasks
4、给某员工分配一项任务
user.apply_task("学习Java")
5、所有用户当前已经开始但尚未结束的任务
User.all_processing_tasks
6、对某一类别,给所有和此一类别相关的员工,批量新增一批任务
kind.add_batch_task_to_users("学习单元测试")
7、针对任务的统计功能,给定某类别,统计当月总的任务数,已完成任务数,未完成任务数
Task.current_month_tasks(kind)
这里值得注意的是,RoR可以很方便的采用充血的领域模型,所有的业务逻辑都可以放在相关的domain model里面。这里的user,task和kind都是对应于数据库表的领域模型,而department是不对应数据库的纯业务逻辑的domain model。总共4个ruby文件,4个domain model,55行代码,所有要写的代码都在这里了,代码量确实非常少,每个domain model的颗粒度都比较大。
然后我们再看看如何用Java:
public class User {
private Long id;
private String name;
private String password;
private String gender;
private String department;
private int salary = 0;
private List<Task> tasks = new ArrayList<Task>();
# omit getter/setter methods ......
}
# omit User's ORM Mapping file
public class Task {
private Long id;
private String name;
private int duration = 0;
private User owner;
# omit getter/setter methods ......
}
# omit Task's ORM Mapping file
public class Kind {
......
}
# omit Kind's ORM Mapping file
public interface UserDao {
public void addUser(User user);
public loadUserById(Long id);
# omit CRUD and other persistent methods ......
public List<User> findByDeparment(String department);
}
public interface TaskDao {
# omit CRUD and other persistent methods ......
}
public class UserDaoImpl {
# omit implementations ......
}
public class TaskDaoImpl {
# omit implementations ......
}
public class UserService {
private UserDao userDao;
public setUserDao(UserDao userDao) { this.userDao = userDao; }
public int workload(User user) {
int totalDuration = 0;
for (Task task : user.getTasks()) {
totalDuration += task.duration;
}
return totalDuration;
}
public employee(String username, String department) {
User user = new User();
user.setName(username);
user.setDepartment(department);
userDao.addUser(user);
}
}
public class TaskService {
private TaskDao taskDao;
public void setTaskDao(TaskDao taskDao) { this.taskDao = taskDao }
public applyTask(String taskName, User user) {
Task task = new Task();
task.setName(taskName);
task.setUser(user);
taskDao.addTask(task);
}
}
public class DepartmentService {
private UserDao userDao;
public void setUserDao(UserDao userDao) { this.userDao = userDao; }
private UserService userService;
public void setUserService(UserService userService) { this.userService = userService; }
public int totalSalary(String department) {
......
}
......
}
# omit IoC Container weaving configuration's file
Java版本的实现代码大家都比较熟悉,因此绝大部分代码都省略了。Java版本需要3个持久对象,3个映射XML文件,3个DAO接口和实现类,4个Service和实现类,和一个IoC的bean组装文件,总共21个文件,全部逻辑写完整,代码行数至少上千行。
通过对比,我们可以看到Java比较流行的实现是贫血的模型,按照面向对象的基本原则,对象的状态应该和它的行为封装在一起,因此Java多出来的这些XXXService是一些从纯理论角度而言应该放入其相应的持久对象中去。但是Java实现充血模型从技术上有一定的难度,如何Service方法挪入到持久对象中呢?如何解决Dao的注入问题?如何解决domain logic方法的事务封装问题?前者可以通过AspectJ的静态织入来解决,后者也许可以通过织入或者annotation声明来解决。但不管怎么说,Java从技术上很难实现充血模型,而且即使实现充血模型,也会导致一个Java类好几百行代码的状况,其代码的可阅读性,模块解藕能力都会变得很差,因此我们认为Java不适合充血模型,在表达复杂的业务逻辑的能力上,Java要比ruby差很多:
结论:
对于Java来说,更加适合采用贫血的模型,Java比较适合于把一个复杂的业务逻辑分离到n个小对象中去,每个小对象描述单一的职责,n个对象互相协作来表达一个复杂的业务逻辑,这n个对象之间的依赖和协作需要通过外部的容器例如IoC来显式的管理。但对于每个具体的对象来说,他们毫无疑问是贫血的。
这种贫血的模型好处是:
1、每个贫血对象职责单一,所以模块解藕程度很高,有利于错误的隔离。
2、非常重要的是,这种模型非常适合于软件外包和大规模软件团队的协作。每个编程个体只需要负责单一职责的小对象模块编写,不会互相影响。
贫血模型的坏处是:
1、由于对象状态和行为分离,所以一个完整的业务逻辑的描述不能够在一个类当中完成,而是一组互相协作的类共同完成的。因此可复用的颗粒度比较小,代码量膨胀的很厉害,最重要的是业务逻辑的描述能力比较差,一个稍微复杂的业务逻辑,就需要太多类和太多代码去表达(针对我们假定的这个简单的工时管理系统的业务逻辑实现,ruby使用了50行代码,但Java至少要上千行代码)。
2、对象协作依赖于外部容器的组装,因此裸写代码是不可能的了,必须借助于外部的IoC容器。
对于Ruby来说,更加适合充血模型。因为ruby语言的表达能力非常强大,现在用ruby做企业应用的DSL是一个很热门的领域,DSL说白了就是用来描述某个行业业务逻辑的专用语言。
充血模型的好处是:
1、对象自洽程度很高,表达能力很强,因此非常适合于复杂的企业业务逻辑的实现,以及可复用程度比较高。
2、不必依赖外部容器的组装,所以RoR没有IoC的概念。
充血模型的坏处是:
1、对象高度自洽的结果是不利于大规模团队分工协作。一个编程个体至少要完成一个完整业务逻辑的功能。对于单个完整业务逻辑,无法再细分下去了。
2、随着业务逻辑的变动,领域模型可能会处于比较频繁的变动状态中,领域模型不够稳定也会带来web层代码频繁变动。
附件是完整的RoR版本的项目示例代码。要运行它,需要安装MySQL数据库(InnoDB表类型),Ruby和Ruby on rails环境。在MySQL数据库中分别创建demo数据库和demo_test数据库,修改demo\config\database.yml中的MySQL数据库配置,改成你的数据库密码。然后在项目跟目录下面执行:
rake db:migrate
rake db:test:clone_structure
rake test
即创建开发环境数据库,创建测试环境数据库,和执行所有的单元测试。领域模型代码位于demo\app\models目录下面;单元测试代码位于demo\test\units目录下面
评论
xhyccc
2007-08-18
我认为领域驱动的意义就是保证对象间的关联不会因为生命周期阶段的不同而变化,我们在内存中用引用即地址来标示一个对象 而在持久层就用ID,来标示,还要区分值对象 实体之类,但ID是逻辑上的而引用是物理的。所以在本人正在研究的java中间件中提出了URA的概念,既是基于一个文件系统,以对象为单位实行虚拟存储,分配统一的容器地址,持久的引用记录的是“地址”与逻辑无关。所以不存在DDD带来的困扰,虚拟了持久层,是在内存或持久层的对象不加区别,不论形态,以统一的URA进行引用,甚至实现分布式系统在容器中也可以动过专有的Servlet-port访问。
lihy70
2007-06-14
public class User extends ObjectEntity {
/**
* Name of "User" object.
*/
public static final String _ = "User";
.......
public User(Object... nameValue) {
super(_);
set(nameValue);
}
public static User load(long id, List<String> selectPropNames, Object... options) throws CoatException {
return (User) AbstractNappy.load(User.class, com.qz.dcoat.util.N.asOid(ID, id), selectPropNames, options);
}
public static List<User> find(List<String> selectPropNames, Condition condition, Object... options) throws CoatException {
return AbstractNappy.find(User.class, _, selectPropNames, condition, options);
}
public static int count(Condition condition, Object... options) throws CoatException {
return AbstractNappy.count(_, condition, options);
}
.......
}
1、某部门录用一名新员工
User user = new User(User.NAME, "newUser", User.DEPARTMENT, "dep"); user.store();
......
4、给某员工分配一项任务
user.add(task);
......
上面只写了完任务1, 4的代码实例。User是自动生成的。
其它的代码和xml配置(不上十行)就懒得贴了。
sutra
2007-03-20
用不同的方法论去看待这个问题会得出不同的结果。
从所谓的充血到贫血,在Java的方法论里被认为是进化。
还记得曾经被用来作为Java学习圣典的Jive就是采用的充血模型,而后来认为应该把域对象里面的属性和行为分离开来来达到解耦等的目的。如果沿用此方法论,在把它设计成为充血模式,那岂不是倒退?当然除非你换了一个方法论。
从所谓的充血到贫血,在Java的方法论里被认为是进化。
还记得曾经被用来作为Java学习圣典的Jive就是采用的充血模型,而后来认为应该把域对象里面的属性和行为分离开来来达到解耦等的目的。如果沿用此方法论,在把它设计成为充血模式,那岂不是倒退?当然除非你换了一个方法论。
dingyuan
2007-03-16
java里最接近ror的开发框架,我觉得是seam了
歆渊
2007-03-05
我觉得 nihongye 的这个JPA实现确实是不错的Rich Domain Model in Java, 但回到这个主题我回第一篇帖(http://www.javaeye.com/post/230190)时的想法, 就是这样的写法我个人感觉会让很多Java大牛不屑, 不知道大家怎么想的?
robbin
2007-03-05
nihongye 写道
引用
nihongye的代码有一个问题,em是通过静态工厂获得的,不是外部注入的。但不严格去追究的话,也很不错了,写的很漂亮!
嗯,考虑到并发,em的管理需要用ThreadLocal,发生PersistenceExceptin异常时还要将它替换成新的em(这点不是很好做,没详细考虑过)。如果不是非要求注入,感觉这样写代码可以接受。另外代码是模拟了你写的。
这里其实有一个不同编程语言的语法差异问题存在。例如在ruby里面,我可以在model里面随便定义静态方法,然后在其他model里面不经注入,可以直接引用这个model的静态方法。但是Java往往这样做是bad smell。
这是因为ruby没有接口,类型也是动态的,类别也是开放的,所以随时随地可以给类别添加行为,这样用没有什么问题,不需要通过IoC注入。但是Java如果不使用IoC方式注入,而是直接调用依赖类的静态方法,行为就被限定死了,复杂的类依赖关系的创建和织入就是一个很麻烦的问题,这也是Java引入IoC的主要原因。
nihongye
2007-03-05
引用
nihongye的代码有一个问题,em是通过静态工厂获得的,不是外部注入的。但不严格去追究的话,也很不错了,写的很漂亮!
嗯,考虑到并发,em的管理需要用ThreadLocal,发生PersistenceExceptin异常时还要将它替换成新的em(这点不是很好做,没详细考虑过。在redsoftjpa entityManagerFactory对entityManager有这种em的管理方式,但早期的规范有,后来去掉了)。如果不是非要求注入,感觉这样写代码可以接受。另外代码是模拟了你写的。
newman
2007-03-05
norther写道:
"我认为领域模型就是OO模型在实际企业开发环境下的表现形式,根本就不是谁比谁高级的问题,领域模型是在企业开发中实现OO模型的最自然的形式..."
re: 没错,域对象首先是OO,那么毫无疑问,域对象起码==OO,但是不同的是,域对象是“域”中的对象,也就是业务中的对象,因此,在抽象层面上要高于OO。域驱动开发的不是一个方法论,而是一种思想,设计的一切重心均要围绕于“域”,域对象就是承载这种思想的高级OO,比如,实现透明持久化。不知这种解释norther以为然否。另外,有网友写了一篇“小议领域模型”,我很认同这篇文章的多数观点,可以参考:
http://www.cnblogs.com/yimlin/archive/2006/06/15/426929.html
"我认为领域模型就是OO模型在实际企业开发环境下的表现形式,根本就不是谁比谁高级的问题,领域模型是在企业开发中实现OO模型的最自然的形式..."
re: 没错,域对象首先是OO,那么毫无疑问,域对象起码==OO,但是不同的是,域对象是“域”中的对象,也就是业务中的对象,因此,在抽象层面上要高于OO。域驱动开发的不是一个方法论,而是一种思想,设计的一切重心均要围绕于“域”,域对象就是承载这种思想的高级OO,比如,实现透明持久化。不知这种解释norther以为然否。另外,有网友写了一篇“小议领域模型”,我很认同这篇文章的多数观点,可以参考:
http://www.cnblogs.com/yimlin/archive/2006/06/15/426929.html
robbin
2007-03-05
nihongye 写道
引用
能简单说明一下吗? 这个是不是用JPA? 具体依赖于哪些库, 测试用的什么底层数据库等等.
每次都Query的办法逻辑是比较清晰, 不过效率会不会有比较大影响?
每次都Query的办法逻辑是比较清晰, 不过效率会不会有比较大影响?
嗯,用的是redsoftfactory实现的jpa,测试数据库用hsqldb.
依赖的包包括(只运行这个测试,下面的jar不是最小的依赖:)):
antlr cglib common-collections commons-lang commongs-logging ejb3-persistence.jar javassist.jar
junit.jar log4j.jar readsoft_jpa.jar hsqldb.jar
nihongye的代码有一个问题,em是通过静态工厂获得的,不是外部注入的。但不严格去追究的话,也很不错了,写的很漂亮!
翅膀
2007-03-05
user.addUser()
领域模型的操作就一定是向持久层保存吗?
如果不是:那应该去区分下什么时候是与持久层打交道.而通过dao所做的操作是向持久层增加,通过domain所做的操作都不经过持久层.
如果是:
那在想想ruby的ActiveRecord,如果说过段时间,有人会感觉到ActiveRecord不是特别好,自己作了个ActiveRecord_New,而这个ActiveRecord_New大家都感觉非常棒,那ruby的使用者该怎么选择呢?
个人觉得在持久层方面,java的jpa出来的有些晚,它如果在hiberante或jdo等早出来,那么大家都有一个标准,就像ActiveRecord一样.其实ruby的ActiveRecord在功能上感觉像java的jpa.我们完全可以在damain中通过jpa去实现rich domain model.所以,个人完全赞同nihongye的做法.通过jpa去做rich domain model.
还有,搞不懂java为啥非要搞个get,set方法,虽然这些代码可以自动生成.完全可以通过关键字去做呀,除非是比较特殊的地方使用get,set.
领域模型的操作就一定是向持久层保存吗?
如果不是:那应该去区分下什么时候是与持久层打交道.而通过dao所做的操作是向持久层增加,通过domain所做的操作都不经过持久层.
如果是:
那在想想ruby的ActiveRecord,如果说过段时间,有人会感觉到ActiveRecord不是特别好,自己作了个ActiveRecord_New,而这个ActiveRecord_New大家都感觉非常棒,那ruby的使用者该怎么选择呢?
个人觉得在持久层方面,java的jpa出来的有些晚,它如果在hiberante或jdo等早出来,那么大家都有一个标准,就像ActiveRecord一样.其实ruby的ActiveRecord在功能上感觉像java的jpa.我们完全可以在damain中通过jpa去实现rich domain model.所以,个人完全赞同nihongye的做法.通过jpa去做rich domain model.
还有,搞不懂java为啥非要搞个get,set方法,虽然这些代码可以自动生成.完全可以通过关键字去做呀,除非是比较特殊的地方使用get,set.
nihongye
2007-03-05
引用
能简单说明一下吗? 这个是不是用JPA? 具体依赖于哪些库, 测试用的什么底层数据库等等.
每次都Query的办法逻辑是比较清晰, 不过效率会不会有比较大影响?
每次都Query的办法逻辑是比较清晰, 不过效率会不会有比较大影响?
嗯,用的是redsoftfactory实现的jpa,测试数据库用hsqldb.
依赖的包包括(只运行这个测试,下面的jar不是最小的依赖:)):
antlr cglib common-collections commons-lang commongs-logging ejb3-persistence.jar javassist.jar
junit.jar log4j.jar readsoft_jpa.jar hsqldb.jar
歆渊
2007-03-05
nihongye 写道
public class Department {
public static void employee(String name, String department) {
User.create(name,department);
}
}
.....
能简单说明一下吗? 这个是不是用JPA? 具体依赖于哪些库, 测试用的什么底层数据库等等.
每次都Query的办法逻辑是比较清晰, 不过效率会不会有比较大影响?
jianfeng008cn
2007-03-05
归纳我的一下思路,给出这样的伪代码,意思是在richDomainObject处铺平相关逻辑,其余各个部分设计于贫血模型无异,当然在ico等方面还需要更多的思考,具体实现还在思考之中。
这里的铺平实现,的确会有很多 委托的代码,但是这样如果具有一般规律的话,自己采取手段处理一下应该也简单的吧,ajoo的酒窝也提到了,在这方面的一些使用。
如果是这个帖子在于讨论 java 实现 rdo 的技术难度,那我们是否应该将思考多多集中在如何实现上呢,到最后也可以有个相对的结论吧
我看现在的把子明显有这个意思“java不好实现 rdo,ror天然支持, ror能更好的描述复杂业务逻辑”,很多人没关注问题的实质也是很自然了
这里的铺平实现,的确会有很多 委托的代码,但是这样如果具有一般规律的话,自己采取手段处理一下应该也简单的吧,ajoo的酒窝也提到了,在这方面的一些使用。
如果是这个帖子在于讨论 java 实现 rdo 的技术难度,那我们是否应该将思考多多集中在如何实现上呢,到最后也可以有个相对的结论吧
我看现在的把子明显有这个意思“java不好实现 rdo,ror天然支持, ror能更好的描述复杂业务逻辑”,很多人没关注问题的实质也是很自然了
public class richDomainObject implements richDomainObjectInterface{
private domainObjectPO;
private domainObjectDAO<richDomainObject>;
private domainLogic1<richDomainObject>;
private domainLogic2<richDomainObject>;
...
}
public class action<richDomainObject>{}
nihongye
2007-03-05
public class Department {
public static void employee(String name, String department) {
User.create(name,department);
}
}
@Entity
public class Kind {
@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);
Context.em.persist(kind);
return kind;
}
public void addBatchTaskToUsers(String task) {
List<User> users = users();
for (User user : users) {
Task taskEntity = new Task(task, this);
Context.em.persist(taskEntity);
user.applyTask(taskEntity);
}
}
public List<User> users() {
Query query = Context.em
.createQuery("select distinct u from User as u inner join u.kinds as kind where kind = ?1");
query.setParameter(1, this);
return query.getResultList();
}
}
@Entity
public class Task {
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);
Context.em.persist(task);
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;
}
public static List<Task> allTask_CurrentMonth() {
Date begin = getCurrentMonthBegin();
Date end = getCurrentMonthEnd();
Query query = Context.em.createQuery("from Task as t where t.startTime >= ?1 and t.startTime <= ?2");
query.setParameter(1,begin);
query.setParameter(2,end);
return query.getResultList();
}
public static List<Task> processingTasks_CurrentMonth() {
Date begin = getCurrentMonthBegin();
Date end = getCurrentMonthEnd();
Query query = Context.em.createQuery("from Task as t where t.startTime >= ?1 and t.startTime <= ?2 and t.endTime is null");
query.setParameter(1,begin);
query.setParameter(2,end);
return query.getResultList();
}
public static List<Task> processedTasks_CurrentMonth() {
Date begin = getCurrentMonthBegin();
Date end = getCurrentMonthEnd();
Query query = Context.em.createQuery("from Task as t where t.endTime >= ?1 and t.endTime <= ?2 ");
query.setParameter(1,begin);
query.setParameter(2,end);
return query.getResultList();
}
}
@Entity
public class User {
@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);
class Tasks {
private User user;
public Tasks(User user) {
this.user = user;
}
public Task find_by_name(String task) {
Query query = Context.em
.createQuery("from Task t where t.owner = ?1 and t.task = ?2");
query.setParameter(1, user);
query.setParameter(2, task);
return (Task) query.getSingleResult();
}
public List<Task> processing_tasks() {
Query query = Context.em
.createQuery("from Task t where t.startTime <= ?1 and t.owner = ?2 and t.endTime is null");
query.setParameter(1, new Date());
query.setParameter(2, user);
return query.getResultList();
}
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) {
Query query = Context.em
.createQuery("from User u where u.name = ?1 and u.department = ?2");
query.setParameter(1, name);
query.setParameter(2, department);
return (User) query.getSingleResult();
}
public static User create(String name, String department) {
User user = new User(name, department);
Context.em.persist(user);
return user;
}
public void applyTask(Task task) {
task.owner = this;
task.startTime = new Date();
Context.em.persist(task);
}
public static List<Task> all_processing_tasks() {
Query query = Context.em
.createQuery("from Task t where t.startTime <= ?1 and t.endTime is null");
query.setParameter(1, new Date());
return query.getResultList();
}
public void endTask(Task task) {
task.endTime = new Date();
}
}
Norther
2007-03-04
newman 写道
昨天晚上就为这个话题在网上逗留了许久,今天再一看发现robbin同学很是快手,把这个话题又重新整理了一遍,真是辛苦,虽然争论依然激烈,不过很多问题也得到了澄清,看着各位大侠在场上刀来剑往,我想在这里说上几句:
1.这个问题的来源是fatzhen发的一个名为"主题: 为什么java里不能把域对象和DAO合并,rails里面就可以?",原贴子的地址是http://www.javaeye.com/topic/56949。
2.在这篇贴子里,robbin还提到了另一篇精彩的文章叫“完美就是生产力”,一位老兄在半夜挑灯夜战抽烟搞出来的一篇文章,我建议参与讨论的网友如果对ruby不够了解的去看看,我看了后是很有收获的,很多背景问题(因为我是搞java的,rr只是搞了些皮毛)能搞清楚。
3.讨论的问题还是域对象模型在两种语言中的支持和实现优劣,而不是某某要取代某某。
4.域模型比oo模型要高级,对业务的描述性更好,也利于业务的计算实现,rr对域模型的支持和实现我认为是要优于java,但是,java也未必需要如rr一般的方法去实现域对象模型,正好比一个使刀的,一个用剑的,没必要说非得弃剑用刀或者弃刀用剑,尤其是java语言和rr语言在本质和风格上面相差太大。
5.代码强阅读性和LOC少是rr的最大热点,当然ActiveRecord做得也是非常不错,但是Java同样有很多出色的优点。
6.贫血模型还是涨血模型,我看还是根据实际应用来论,robbin也对这个问题做了分析,我也不多说了,我认为分析得还是很到位。
7.既然是刀来剑往,我想没有一个人能够100%保持彬彬有礼的形象,出言不逊在所难免,但是既然都是武林中人,能够互相切磋技艺心法,实在是人生之幸事,那些激昂言语也大可在一笑中抛诸脑后。
1.这个问题的来源是fatzhen发的一个名为"主题: 为什么java里不能把域对象和DAO合并,rails里面就可以?",原贴子的地址是http://www.javaeye.com/topic/56949。
2.在这篇贴子里,robbin还提到了另一篇精彩的文章叫“完美就是生产力”,一位老兄在半夜挑灯夜战抽烟搞出来的一篇文章,我建议参与讨论的网友如果对ruby不够了解的去看看,我看了后是很有收获的,很多背景问题(因为我是搞java的,rr只是搞了些皮毛)能搞清楚。
3.讨论的问题还是域对象模型在两种语言中的支持和实现优劣,而不是某某要取代某某。
4.域模型比oo模型要高级,对业务的描述性更好,也利于业务的计算实现,rr对域模型的支持和实现我认为是要优于java,但是,java也未必需要如rr一般的方法去实现域对象模型,正好比一个使刀的,一个用剑的,没必要说非得弃剑用刀或者弃刀用剑,尤其是java语言和rr语言在本质和风格上面相差太大。
5.代码强阅读性和LOC少是rr的最大热点,当然ActiveRecord做得也是非常不错,但是Java同样有很多出色的优点。
6.贫血模型还是涨血模型,我看还是根据实际应用来论,robbin也对这个问题做了分析,我也不多说了,我认为分析得还是很到位。
7.既然是刀来剑往,我想没有一个人能够100%保持彬彬有礼的形象,出言不逊在所难免,但是既然都是武林中人,能够互相切磋技艺心法,实在是人生之幸事,那些激昂言语也大可在一笑中抛诸脑后。
此兄的观点我有几点不同意(我是搞java的)
我认为领域模型就是OO模型在实际企业开发环境下的表现形式,根本就不是谁比谁高级的问题,领域模型是在企业开发中实现OO模型的最自然的形式,所谓道法自然,OO的精髓不久是模拟现实么,java在这方面确实不完美,贫血模型在任何一个比hello world稍微复杂点的系统开发中暴露的种种弊病,我想做过几个项目的人都能感觉出来,我认为领域模型就是企业开发的王道,准备开始学习ror.
charon
2007-03-04
robbin 写道
不知道大家有没有想过,为什么Java这么强调面向接口编程?面向接口编程为什么对Java这么重要?但是对于动态面向对象语言ruby来说,却根本不需要接口这种概念。这是因为在Java中是类型决定行为,所以类型的地位很重要,你一继承,对象的行为就被限制死了,所以Java很忌讳继承的使用;但是ruby的类型不决定行为,所以随便你怎么继承,也不会限制对象的行为。
duck typing也是typing,我琢磨着即便是动态语言,类型决定行为(或者行为决定类型)还是必要的,区别在于是不是有显式的类型。
但是大型的python应用中如zope,twisted,peak等大都引入了自己的interface方式, python3000也在讨论引入interface的某个类似物(貌似解决方案讨论反复了多次).
另外一点,"设计模式"这本书出版(特别是酝酿)的时候,java的毛还没长全,里面的主打语言还是c++/smalltalk,后者就是动态语言。而此时四大仙就有了面向接口编程的说法.
yuxie
2007-03-04
robbin 写道
可以省略DAO,也可以省略Service,请你把符合rich domain object的Java代码贴出来,别光说不练。
不知道大家有没有注意过 .net的 castle http://www.castleproject.org/ 项目?它通过对nhibernate的集成(使用标注)实现了.net上的ActiveRecord。
代码类似这样
// Copyright 2004-2006 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace BlogSample
{
using System;
using System.Collections;
using Castle.ActiveRecord;
[ActiveRecord]
public class Blog : ActiveRecordBase
{
private int id;
private String name;
private String author;
private IList posts = new ArrayList();
public Blog()
{
}
public Blog(String name)
{
this.name = name;
}
[PrimaryKey]
public int Id
{
get { return id; }
set { id = value; }
}
[Property]
public String Name
{
get { return name; }
set { name = value; }
}
[Property]
public String Author
{
get { return author; }
set { author = value; }
}
[HasMany(typeof(Post),
Table="Posts", ColumnKey="blogid",
Inverse=true, Cascade=ManyRelationCascadeEnum.AllDeleteOrphan)]
public IList Posts
{
get { return posts; }
set { posts = value; }
}
public static void DeleteAll()
{
ActiveRecordBase.DeleteAll(typeof(Blog));
}
public static Blog[] FindAll()
{
return (Blog[]) ActiveRecordBase.FindAll(typeof(Blog));
}
public static Blog Find(int id)
{
return (Blog) ActiveRecordBase.FindByPrimaryKey(typeof(Blog), id);
}
}
}
如果使用.net 2.0,还可以利用范型把所有的ActiveRecordBase去掉,换成自己本身的类名。
.net可以做到,java为什么做不到?
当然,这个ActiveRecord比起ror的来,差的老远:
*不能自动从数据库映射字段
*hibernate的缺点(长长的sql等)它都有
*数据库改了字段它必须修改代码
大部分缺点都是由java和.net静态语言的特性造成的
不过如果可以忍受这些,java和.net也是可以做到充血模型的
chunshui
2007-03-04
谢谢Robbin好文
firstline78
2007-03-04
JavaInActoin 写道
robbin 写道
你这个领域模型是根本跑不起来的。数据库根本就没有department这个表,你的department的users属性根本就是null,一跑就会出错。你的user.addTask方法也执行不了,没有Dao的支持,你再调用addTask,数据库里面也不增加记录。UserManager也不应该存在,这是属于user的domain logic。
请不要拿一个根本不能运行的错误代码出来,请先在你本地搭建一个实际的web项目,自己测试通过了,再贴出来。
单独的领域模型当然跑不起来,和其它层装配起来就跑的很以欢畅了。
我写的Domain Model是根据Eric Evans的观点来的,很多持久化(包括事务)的操作超越了业务逻辑,需要被安排在应用服务层。
你写的那段不是MF的充血模型,而是事务脚本。
robbin 写道
请不要拿一个根本不能运行的错误代码出来,请先在你本地搭建一个实际的web项目,自己测试通过了,再贴出来。
这个不是讨论问题的最佳方法,太累了,你写的代码同样不能运行
robbin 写道
public employee(String username, String department) {
寻找这样的错误是没有意义的,只要思路正确,在座的各位都可以把代码调试好。
OO思想或者实现不能应用到系统所有地方,JavaInActoin这么说也是很有道理的。
newman
2007-03-04
接chinaet的话题,从java开源大全网站获取SpringXT的摘要信息如下:
“SpringXT是Spring框架的一个扩展用于开发richer domain models与richer user interfaces的应用程序。采用Domain Driven Design设计原则。为此SpringXT提供两个框架SpringXT Modeling Framework:提供一些组件来开发rich domain model(它集中了所有业务逻辑,规则和约束,完全独立于应用程序的其它部分)并能够让它们与其它应用软件分层"优雅"结合。SpringXT Ajax Framework:一个完全与Spring MVC集成在一起,基于事件的Ajax框架。”
java开源大全网址:
http://www.open-open.com/open184107.htm
springxt官方网址:
http://springxt.sourceforge.net/index.php/Main_Page
“SpringXT是Spring框架的一个扩展用于开发richer domain models与richer user interfaces的应用程序。采用Domain Driven Design设计原则。为此SpringXT提供两个框架SpringXT Modeling Framework:提供一些组件来开发rich domain model(它集中了所有业务逻辑,规则和约束,完全独立于应用程序的其它部分)并能够让它们与其它应用软件分层"优雅"结合。SpringXT Ajax Framework:一个完全与Spring MVC集成在一起,基于事件的Ajax框架。”
java开源大全网址:
http://www.open-open.com/open184107.htm
springxt官方网址:
http://springxt.sourceforge.net/index.php/Main_Page
- 浏览: 1677813 次
- 性别:

- 来自: 上海

- 详细资料
搜索本博客
我的相册
游乌镇
共 33 张
共 33 张
链接
最新评论
-
mod_rails尝鲜
我觉得还是mod_fcgid(不是mod_fastcgi)实际点
-- by zgd -
mod_rails尝鲜
hostingrails也已经提供mod_rails了
-- by leondu -
mod_rails尝鲜
dreamhost已经提供mod_rails了
-- by zgd -
关于JavaEye网站未来发展 ...
期待第三阶段目标的实现,但第三目标好像类似于google的云计算,建议赶紧开发, ...
-- by selectme_2008 -
总结一下大家对JavaEye网 ...
javaeye是我比较喜欢的一个网站,但盈利模式还是比较单一,让人怀疑网站是否能 ...
-- by selectme_2008






评论排行榜