JdonMVC+JDON+CQRS演绎智能对象

阅读本贴之前请先阅读如下几个帖子
Domain Event 救世主
Domain Event 异步应用
DCI架
智能领域对象设计

领域对象应该有丰富的业务行为,它们不应该感知技术环境,技术环境应该为domain服务,domain对象还可能在不同场景里面表现出不同的行为和具备相关场景属性,所以jdon做的是在挑战传统ssh编程模式,ssh被banq喻为模型层的汇编语言,下面以JdonMVC+Jdon来演示一下domain event或者说cqrs,或者说非贫血模型。

也许是非常不严谨的实践,以后还会更新,大家可以了解其中的思想,整个代码可以到http://code.google.com/p/jdonmvc/downloads/list下载,war包直接放到tomcat里就可以跑,程序运行界面如下:



下面上代码解释:

这是User类,没有写get set到这里,get ,set尽量只在UI和DB层用,因为那是OO世界的过程化边界,jivejdon用get来在UI层做全局懒加载。


@Model
public class User {
private int userId;
private String name;

@Inject
private DomainEvents domainEvents; //jdon对Model的enhance对象,隔离domain和技术的耦合

//记忆
public void remember() {
domainEvents.saveDomainObject(this);
}
//状态变化,domain对象In Memory管理
public void updateMemory(){
domainEvents.domainHasBeenEdit(this);
}
//失忆
public void forget(){
domainEvents.domainHasBeenDelete(this);
}
}

无状态服务类UserService


public interface UserService {

User enhance(User user);//借用jdon的领域事件模式,enhance领域对象

}

CQRS中的命令上下文


@Resource
public class UserCommandContext {

@Service("userService")
private UserService userService;

@Path(
"/users")
@Post
public String create(User user) {
User usermodel = userService.enhance(user);
usermodel.remember();
//记忆
return
"redirect:/";
}

@Path(
"/user/{<[0-9]+>user.userId}")
@Delete
public String delete(User user) {
User usermodel = userService.enhance(user);
usermodel.forget();
//失忆
return
"redirect:/";
}

@Path(
"/user")
@Put
public String update(User user) {
User usermodel = userService.enhance(user);
usermodel.updateMemory();
//状态更新
return
"redirect:/";
}

}

这是CQRS中的查询路径,UserQueryService直达DB


@Resource
public class UserQueryResource {

@Service("userQueryService")
private UserQueryService userQueryService;

@Path(
"/")
public Represent index(Page<User> page) {
Page<User> pagedata;
if (page == null)
pagedata = userQueryService.getUserList(new Page<User>());
else
pagedata = userQueryService.getUserList(page);

CssPageBar cssPageBar = userQueryService.getCssPageBar(pagedata);

Map<String, Object> map = new HashMap<String, Object>();
map.put(
"page", pagedata);
map.put(
"cssPageBar", cssPageBar);
return new Html(
"/index.ctl", map);
}

@Path(
"/user/{<[0-9]+>userId}")
public Represent user(Integer userId) {
User user = userQueryService.getUser(userId);
return new Html(
"/user.ctl", "user", user);
}


}


[该贴被oojdon于2010-07-13 14:59修改过]
[该贴被oojdon于2010-07-13 15:04修改过]
[该贴被oojdon于2010-07-13 17:52修改过]

axonframework是一个cqrs框架
http://code.google.com/p/axonframework/
它的Domain代码如下,大家可以做个对比


class Contact extends AbstractAnnotatedAggregateRoot {

public Contact(String name) {
apply(new ContactCreatedEvent(name));//domain存在了发一个create事件
}

public void registerAddress(AddressType type, Address address) {
if (addresses.containsKey(type)) {
apply(new AddressChangedEvent(type, address));
} else {
apply(new AddressAddedEvent(type, address));
}
}

public void removeAddress(AddressType type) {
if (addresses.remove(type) != null) {
apply(new AddressRemovedEvent(type));
}
}

public void changeName(String name) {
apply(new ContactNameChangedEvent(name));
//更新
}

public void delete() {
apply(new ContactDeletedEvent());
//删除
}

不错,有空杯心态和很强的实践能力。先读读你的代码,再做评论。

真不错,不过我头也有点晕,要仔细研究一下,oojdon深入能力很强。

现在外面招聘人才,就像高考一样,要求面面俱到,结果,招到的人往往什么都知道,什么都做不了,无法深入。

我看的也有点晕,所以产生了一个问题,为什么如此简单的功能在使用两个框架的基础上还写了那么多代码呢?
[该贴被cuwkuhaihong于2010-07-14 23:54修改过]

2010年07月14日 23:53 "cuwkuhaihong"的内容
为什么如此简单的功能在使用两个框架的基础上还写了那么多代码呢 ...

可能是我把代码包分得太细了之后的错觉吧!
以User行为中心

public interface User {
public void remember();
public void updateMemory();
public void forget();
}

然后行为的结果都以事件抛给了技术,技术在infrastructure这个包中,怎么抛要借助jdon的aop拦截,jivejdon是通过service的get隐式注射的,现在我是手动enhance,Repository接口是DDD中的概念,接口实现是一个内存并发map,查询可以先不看,查询和memDB直接在交互,不知这样讲一下之后是否还有问题?

呵呵,谢谢两位老大的关注,希望多提意见。
[该贴被oojdon于2010-07-15 09:29修改过]

JdonMVC 增删改查以及分页演示 ,这个例子是不是整的太简单了点。
起码得弄个检索,然后编辑得像.net里面的Grid那样吧?

2010年07月18日 19:43 "eppen"的内容
JdonMVC 增删改查以及分页演示 ,这个例子是不是整的太简单了点。
起码得弄个检索,然后编辑得像.net里面的Grid那样吧? ...

呵呵,框架都还没定型,demo级别。

我试了一下,在jboss-5.1.0.GA下报错:
01:11:54,570 ERROR [[jsp]] Servlet.service() for servlet jsp threw exception
java.lang.ClassCastException: org.apache.catalina.connector.RequestFacade cannot be cast to javax.servlet.http.HttpServletRequest
at com.jdon.rest.RestFilter.doFilter(RestFilter.java:58)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.jdon.util.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:92)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:235)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:190)
at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:92)
at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.process(SecurityContextEstablishmentValve.java:126)
at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:70)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:330)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:829)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:598)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
at java.lang.Thread.run(Thread.java:662)

多谢楼上,最近比较忙,等闲下来了我会做多容器测试。

我的思想是数据库只和框架有关系,与业务模型没任何关系。领域中的领域实体被框架监控,当发生改变时,自动更新内存,定时交给数据库,当然定时也可以设定为即改即交。不知道这是否和你的架构思考同道或者冲突呢?

2010年12月07日 14:30 "SpeedVan"的内容
我的思想是数据库只和框架有关系,与业务模型没任何关系。领域中的领域实体被框架监控,当发生改变时,自动更新内存,定时交给数据库,当然定时也可以设定为即改即交。不知道这是否和你的架构思考同道或者冲突呢? ...

是的,重点是怎么自动,怎么监控,目前Jdon的尝试就是引入cglib,让在模型上发生的状态变更被拦截然后扔向感兴趣的监听者,所以在模型内部还是有一个手动扔消息的代码,包括axon这个框架,不过JiveJdon的帖子阅读次数是通过ScheduledExecutorService定时的。

但jivejdon当中service还是聚合了dao,我觉得dao应该由框架来驱动,也就是只有框架知道dao的存在。这样内存的作用就越来越大了(我们操作就只有内存上了),当然内存事务就得解决的问题。对于设计者来说,是否实时持久(或者更甚,是否需要持久)应该是一个简单的是否的选择。

以上是我认为而已,只是不知道你的框架的思想,弄清楚,至少不要让我在讨论中弄个笑话,呵呵。