使用Axon框架开发CQRS应用

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修改过]

顶一下。

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

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

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

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

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

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

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

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

2011年01月08日 15:31 "xmuzyu"的内容
DCI中引入了场景的概念,这个场景类似于四色原型中的角色模型, ...

我认为,DCI的C(场景)概念与四色原型中的IM(活动)相似,DCI的I(交互)与四色原型中的Role(角色)相似。不同的是DCI告诉我们领域模型(D)在场景(C)扮演不同角色的原因:I(领域模型之间交互或相互作用),四色原型告诉我们领域模型(PPT)在活动(IM)中交互或相互作用的结果:Role(领域模型扮演某种角色)。DCI与四色原型对领域模型进入场景或参与活动的描述角度有所不同:前者侧重于描述原因,或者侧重于描述结果。理解不同角度的描述,对这些东西可能会有更全面、深刻的认识。

对于事件看到一些不同的描述,我也不太肯定你所要表达的是哪种含义。事件机制,在图形界面开发中,是放在控制器(界面)中,控制器将事件映射或传到给模型,模型来执行。事件机制,也隐藏了一种观点:由各种模型组成的系统是离散的。这点与OO的基本观点:世界是由对象组成的,倒是相通。Niklaus Wirth说OO源于离散系统,可能就是从这点出发的。

现在要将一整套事件机制从“界面”(UI)搬到“领域”中去。从你的描述看来,更具体地说是在“场景”中引入“事件机制”,那么就要对“场景”进行再次切分,考虑什么是事件?事件由谁产生?又由谁处理?举个比较常见的模式,命令者模式。命令即事件,命令的调用者即领域模型(相当于事件触发者),命令的执行者即底层的技术框架(相当于事件的处理者)。至于事件的undo(撤销)、redo(重播)等机制,命令者模式结合组合模式可以做到(对非幂等事件会比较麻烦一些)。

这里有点不解,事件的处理怎么会交给底层技术框架来执行?我以为事件的传导(调度)或映射过程,才可以交给框架来做。领域事件的处理,是领域场景的核心,业务不同,这个核心也会不同,能由底层框架统一做吗?

有点绕,我觉得你最主要的意思可能是想通过“领域事件”将“领域模型”与“领域场景(的核心)”进行分离,不过解释起来有点费劲,因为大量的“领域模型”是“领域事件”的调用者,也是“领域事件”的执行者(此时,领域模型在场景扮演了某种角色)。

也许可以这么理解,解释起来简单一些。将“领域事件”看作DCI中I(Interaction)的一种实现方式,作为“角色行为注入”的替代方案。
领域模型通过“执行领域事件”来扮演角色,进行交互(I),而不是通过“角色行为注入”来完成角色扮演。

目前我写代码时使用过程化或内部类表达角色扮演或模型交互的概念,这种方法非常原始,但简单。DCI和Event框架的核心任务,在于自动化完成“角色行为注入”或“领域事件的传导、调度或映射机制”。

至于集群、并发,我觉得是“领域”之外的事情,是纯粹的技术问题。领域只须关心领域的问题,技术问题与其可以分开考虑。分开不意味着两者的重要程度有高低之别,只是为了降低并控制问题的复杂度,顺序有先后之别而已。事实上,Donald Knuth说得更彻底—“在我看来,这种现象(并发)或多或少是由于硬件设计者已经无计可施了导致的,他们将Moore定律失效的责任推脱给软件开发者”。

2011年01月09日 14:03 "jdon007"的内容
至于集群、并发,我觉得是“领域”之外的事情,是纯粹的技术问题。领域只须关心领域的问题,技术问题与其可以分开考虑。分开不意味着两者的重要程度有高低之别,只是为了降低并控制问题的复杂度,顺序有先后之别而已。事实上,Donald Knuth说得更 ...

领域和技术是不能完全分离的,在分析阶段也许是可以分开考虑,到了实现阶段,还分开?就比如我前面说的那个client jar包的问题。你得考虑你的领域模型到底是跑在你本身的web层集群的内存中呢?还是从业务层通过远程调用接口获取,这就考虑到性能问题,是不能避免的,就好比目公司的项目,几乎所有的系统都是分布式的,每天系统之间的远程调用量有上百亿次,你必须从技术的角度去将你的领域模型放到合适的地方,另外设计领域模型的时候,也要讲数据的获取和行为进行抽象,不能融合,领域模型是负责行为的,领域模型进行操作需要的数据,你需要很好的进行抽象,你可以是通过远程调用接口从其他业务系统获取的。

关于DCI和四色原型的关系,我觉得如果他们都是通过不同概念,或者方式来进行业务抽象的一种方式,其实有时候业务的划分也很难界定,比如一个业务操作,你到底将哪部分逻辑分配给角色,而哪部分让MI实现呢?这一块,我也还不能完全确定,希望真正实践过的朋友能分享下自己的心得。DCI的重点其实就是引入了一个场景的概念,有了场景以后,我们就不会再为有时候一个实体非要和service通信的这种类似问题困扰了,通过场景来即可解决。

最后,我也想领域模型和服务之间的关系到底是什么?我觉得领域模型来实现逻辑,在实现业务逻辑的过程中可能需要数据,这部分就交给服务去做,服务是没有逻辑的,仅仅是用来给领域模型提供业务操作所需要的数据,另外服务也需要负责将业务逻辑的操作结果在系统之间传递,比如持久化,或者异步发生到其他系统等。


2011年01月09日 19:40 "xmuzyu"的内容
比如一个业务操作,你到底将哪部分逻辑分配给角色,而哪部分让MI实现呢? ...

对于领域与技术的关系,我并没有厚此薄彼,或割裂其联系使其对立的意思。我只是觉得分工明确,对于不同专长的伙伴,更能取长补短,发挥众人之长。用户体验与性能分别从“人”与“计算机或网络”的角度考虑如何更好地使用或支持(部署)“领域”模型,它们之间没什么矛盾(若有,多半是人为的),实现时自然也要交融在一起,不然领域模型生有何用?

至于你说的将领域模型打包,在每个client上构建或部署,减少client与server交互的次数,节约网络资源不必要的开销,这个与领域模型本身并没有对立的关系,有人擅长部署,有人擅长领域建模,完全可以进行很好的合作。经验较丰富的同事向我提出节约网络资源开销的一些建议,我可以马上用上,修改的部分符合OCP原则,也就是修改时可以做到对外封闭。

接触Web开发的时间不算长,对于练手的小项目,没有用框架,从零开始,按照MVC,DCI的观点,组织代码。在自己的小项目中,将独立的业务操作分配给领域模型(D或PPT),将交互的业务操作分配给场景(C或IM),更准确地说临时分配给在场景中扮演各种角色(Role)或相互作用(I)的领域模型。之前在另一个帖子我说:模型(实体)与场景(服务)是对领域的一种划分,模型关注领域的个体行为,场景关注领域的群体行为,模型关注领域的静态结构,场景关注领域的动态功能。

目前我是通过内部类与过程化的表达等最原始的方式来实现角色扮演或交互。框架给你方便,让你付出受其约束的代价,甚至可能迷失于诸多细枝末节中,所以至少在初期,在简单的项目中,我愿意以最直接、最原始的方式来使用和体验这些范式。DCI是Trygve Reenskau工作几十年退休后,无所事事,纯粹出于个人的兴趣和研究提出来的。我实在很喜欢他老人家提出的这两个范式,它们如此地简洁而深刻,也非常地浅显,不必怀疑,深刻的思想往往也很简单。我非常希望大家在阐述它们时,最好深入浅出,不要让新手们错失思维上的美好体验。就拿我个人来说,这两种范式也给我提供了一种新的眼光来审视GoF的模式。

2011年01月08日 15:31 "xmuzyu"的内容
领域模型可能会在场景中触发事件。然后再由底层技术框架去完成事件的响应。 ...

DDD和DCI混合起来使用,我也有想过,但总是局部的,很难完全整合起来。在我深入DDD的时候就想到持久化是一个完全技术概念,应该是框架做的事——监控缓存中的实体模型,当实体改变时,发送持久事件给持久模块,持久事件拥有的是实体状态(快照),并不是实体,刚好符合了CQRS了。

若果结合DDD和DCI,我在想,DCI的D是不是更接近实体模型快照呢?领域模型的领域行为触发事件,对应的场景获得领域模型的快照,场景完成后得到的新领域状态,交给框架去更新。这个方式,可以去掉大部分setter,也确保实体的状态迁移的原子性。

share-nothing的DCI的Context是相互独立,不应该相互干扰,所以其中的对象(实体)应该是不变的,类似scala引入的对象不变性。但对象不变,好像很不自然,所以从CQRS引入了对象(实体)快照,也就是实体的某个时刻的状态。也就是一个事件跟实体的当前状态相关,与其过去和未来无关。

但这样回过来一想,实体就变成了事件发出者,不是行为载体了,感觉与富模型有点相悖。呵呵,当然不要为富而富,或者换个角度,其实它还是富模型——事件是相关交互行为的概括,只是通过事件引发场景,把行为延迟加入当前状态的实体中。当然,也存在个人行为一说,但这个是否十分必要,是否可以统一起来,我还在思考中~~~

以上只是我一些探索,关键点是对象不变性,即一切交互都是只与实体当前状态相关。

有谁了解 AxonFramework 叫典型的成功案例吗?