使用Axon框架开发CQRS应用

                   
banq 11-01-05

Tutorial – Getting started with CQRS and Axon Framework « JTeam Blog / JTeam: Enterprise Java, Open

Axon是一个基于DDD领域驱动设计的搭建CQRS架构的框架,特点是可以和Spring整合。

Axon可以帮助简化建立基于EDA架构的CQRS应用,它对Domain Events的支持比较丰富,特别增强了JdonFramework没有的领域事件中的事务支持(不过个人觉得处理起来要复杂,因为事务本身就很棘手)。

Axon最大特点就是严格按照CQRS定义来编程,比如Command EventStore等等,好处是熟悉CQRS的人比较易懂,个人认为缺点是和DDD的统一语言要求有些距离了,如果只懂DDD,不懂CQRS的术语,还是不能方便使用Axon,这方面好像有些逻辑上疙瘩。个人观点。

开发步骤:
1.结合Maven建立项目Project,配置好需要的包。
2.建立Command类,如下:

public class CreateOrderCommand {

private final String orderId;
private final String productId;

public CreateOrderCommand(String orderId, String productId) {
this.orderId = orderId;
this.productId = productId;
}

public String getOrderId() {
return orderId;
}

public String getProductId() {
return productId;
}
}


建立Domain Events类,如下:

import org.axonframework.domain.DomainEvent;

public abstract class AbstractOrderEvent extends DomainEvent {
public String getOrderId() {
return getAggregateIdentifier().asString();
}
}

public class OrderCreatedEvent extends AbstractOrderEvent {

private final String productId;

public OrderCreatedEvent(String productId) {
this.productId = productId;
}

public String getProductId() {
return productId;
}
}

如果用Annotation注解来标明DomainEvent,也许更好(应该有),使用继承有些侵入性。

这些步骤之前,应该假设Order等领域模型已经建立好,上面这些类都应该属于行为操作性质,以前我们是写在Service中的。

3.建立CommandHandler,前面有事件发生,这里就有事件处理了,这里实际遵循事件模式中两个主题:触发者;接收者,CommandHandler属于接收者。关于CommandHandler讨论可见OOJDON的观点
接受Order订单的创建命令的处理者Handler代码如下,需要使用@CommandHandler标注:

@CommandHandler
public void createOrder(CreateOrderCommand command) {
orderRepository.add(new Order(command.getOrderId(), command.getProductId()));
}

@CommandHandler
public void confirmOrder(ConfirmOrderCommand command) {
Order order = orderRepository.load(new StringAggregateIdentifier(command.getOrderId()));
order.confirm();
}


在confirmOrder中,我们注意它委托了order.confirm(),让领域模型Order的方法confirm()确认自己的创建。

从这里看出,CommandHandler类似我们MVC中Controller,也就是控制器,专门接受来自界面的命令事件,类似Struts等中Action;JSF等中的界面Bean,或者类似Swing等中Listerner,或者类似SOA中的服务。当然在JdonFramework中,结合Struts 1.x的Handler可以节省不用写。

在DCI架构中,Handler这里实际就是场景发生地,在简洁优雅上,DCI架构要比CQRS更简单,更倾向于业务统一语言,否则,只懂业务的领域专家还要学习设计模式中事件命令模式,将业务再翻译成事件语言,多一层翻译,就多一层信息失真,以前是将业务翻译成数据库语言,现在推翻了,就不能再多翻译这一层了。

前面谈到CommandHanlder再次触发Domain Model领域模型 Order中的方法,内容如下:

public void confirm() {
// we can only confirm an open order.
if (status == Status.OPEN) {
apply(new OrderConfirmedEvent());
}
}

@EventHandler
//Handler调用这里方法
private void onConfirm(OrderConfirmedEvent event) {
status = Status.CONFIRMED;
}


最后一步,编写查询Query组件:

@Component //Spring的标注
public class OrderEventHandler {

@EventHandler
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println(String.format(
"An order is created: [%s] for product: [%s]",
event.getOrderId(),
event.getProductId()));
}

@EventHandler
public void handleOrderCancelled(OrderCancelledEvent event) {
System.out.println(String.format(
"An order is cancelled: [%s]", event.getOrderId()));
}

@EventHandler
public void handleOrderConfirmed(OrderConfirmedEvent event) {
System.out.println(String.format(
"An order is confirmed: [%s]", event.getOrderId()));
}
}

看得出来,Axon可能没有对查询进行更多支持,JdonFramework提供查询缓存优化,自动分页查询等功能,将查询和命令之间数据共享通过缓存联系封装起来,更简化CQRS的查询和命令分离后的再结合。

从Axon和JdonFramework 6.0版本等CQRS框架看得出来,事件是一个解决领域驱动技术架构的好方法,但是如果事件比较多,都在空中飞来飞去,增加编程复杂性和代码可阅读性,这时,DCI架构,将模型带上角色帽子,拉到当前场景,让其演出,表演交互行为,就更加符合业务,符合DDD统一语言这个概念。







案例源码:
http://axonframework.googlecode.com/files/axon_tutorial.zip

[该贴被banq于2011-01-05 10:02修改过]

5
banq
2011-01-07 12:02

顶一下。

xmuzyu
2011-01-08 15:31

CQRS让我认识到了领域模型的使命,领域模型是用来完成业务逻辑的,是完成对系统状态进行改变的操作,而对于读取操作,其实更多的与UI有关系,而UI又是经常发生变化的,因此个人觉得不要将领域模型拿去UI显示,领域模型的核心在完成系统写操作,领域模型只要把业务操作的结果存到一个地方了,不用关心怎么显示的问题。
另外一点,领域模型与我们所谓的数据库实体其实也没有必然的关系,数据中的实体可能是行为少,只是数据容器,并且在一个大型的分布式系统中,领域模型应该以client包的形式,让其跑在各个机器的本地内存中,不要通过网络传递具有丰富逻辑的领域模型,网络传递的仅仅其实是那些数据而已,等数据通过网络传递到本地的领域模型以后,我们即可进行进一步的业务操作。

DCI中引入了场景的概念,这个场景类似于四色原型中的角色模型,而之前是将业务逻辑写到了service中,这样就造成的不便JDON上已经讨论很久了,就不多说了,采用DCI以后,业务逻辑应该由场景来控制,在具体的场景中来,有领域模型参与完成逻辑。比如一次电子商务的交易,一个用户以买家角色,另外一个用户以卖家角色进入了交易场景,共同完成交易操作,但是这里面需要注意一个问题,在一个大型系统中,交易集群和用户集群,以及商品集群都是分布在不同的集群服务器中,即使是交易系统,web层,业务层也都是分布在不同的集群服务器,这个时候就需要将领域模型打成客户端jar包,让web层去用,不要将领域模型通过远程调用的方式来传递,也就是领域模型应该是在本地web机器中构建,而不是通过远程接口来查询。这样对性能是个挑战,因为一个日访问量数亿的网站,你每次传递的数据假如只有几百个字节的话,每天100亿次的调用量,你想想浪费的网络开销有多大。

最后说一下事件,我们都知道在采用充血模型以后,往往有时候会存在将领域服务或者仓库以参数的形式传递给实体的时候,这个时候感觉就有点别扭,尤其是你给那些没有接触过DDD的人说,他们肯定一口否决,怎么能让实体和服务关联呢?那这个时候怎么办呢?其实就是引入场景,注意是引入场景来解决,那么事件又是什么?在完成某种业务操作的过程中,领域模型可能会在场景中触发事件。然后再由底层技术框架去完成事件的响应。

因此我觉得DDD+DCI+Event组合起来使用,会很好的解决在采用充血模型中所遇到的问题,当然这也需要一个最佳的实践,最佳实践我和OOJDON也在慢慢研究中,在接下来的时间中,我和oojdon会将我们采用充血模型构建项目的一些心得,遇到的问题和大家讨论。

banq
2011-01-08 19:42

2011年01月08日 15:31 "xmuzyu"的内容
因此我觉得DDD+DCI+Event组合起来使用,会很好的解决在采用充血模型中所遇到的问题,当然这也需要一个最佳的实践,最佳实践我和OOJDON也在慢慢研究中,在接下来的时间中,我和OOjdon会将我们采用充血模型构建项目的一些心得,遇到的 ...


这是一个新点子,创新点子,完全可以尝试一下,为什么不能将DCI和Domain Events结合起来使用呢?为什么我之前没有想到呢?总是把它们对立起来比较啦,说明我思维有惯性,哈哈。

如果能结合起来使用,也是国人原创啊,肯定领先了,还没看到老外有这方面文章出来。

flyzb
2011-01-09 01:10

我也非常赞同,目前JF中事件还没有发挥它真正应有个的威力。
对象受到刺激(事件)而产生响应(行为),这是客观事实。什么是场景?所谓场景就是一组相关刺激及其响应罢了,这也是业务逻辑聚合的本质。所以在我看来,DDD强调封装访问式的聚合太过于形式化,同时DCI中也强调因为角色而聚合也有些偏颇,而“事件的聚合”才是真正的业务聚合。

2Go 1 2 下一页