Command Query Responsibility Segregation,CQRS 这个架构好象最近博客园里讨论得比较多,有几篇园友的文章很有深度,推荐阅读:

CQRS架构简介

浅谈命令查询职责分离(CQRS)模式

DDD CQRS架构和传统架构的优缺点比较

比较有趣的是,以往一断谈及架构思路、OO这些,往往都是java大佬们的专长,而CQRS这个话题,好象.NET占了上风。园友汤雪华ENODE开源大作,在github上人气也很旺。

于是,我逆向思路搜索了下java的类似项目,果然有一个AxonFramework,甚至还有一个专门的网站。按文档上的介绍,弄了一个hello world,记录一下:

CRQS是基于事件驱动的,其主要架构并不复杂,见下图:

简单来讲,对数据库的修改操作,UI层只管发送各种命令(Command),触发事件(Event),然后由EventHandler去异步处理,最终写入master DB,对于数据库的查询,则查询slave DB(注:这里的master db, slave db只是一个逻辑上的区分,可以是真正的主-从库,也可以都是一个库)。 这样的架构,很容易实现读写分离,也易于大型项目的扩展。

项目结构:

package的名称上大概就能看出用途:

command包定义各种命令,

event包定义各种事件,

handler包定义事件处理逻辑,

model包相当于领域模型

最外层的ToDOItemRunner相当于应用程序入口。

gradle依赖项:

group 'yjmyzz'
version '1.0' apply plugin: 'java'
apply plugin: 'application' sourceCompatibility = 1.8 repositories {
mavenLocal()
maven {
url 'http://maven.oschina.net/content/groups/public/'
}
mavenCentral()
} dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile "org.axonframework:axon:2.4.3"
compile "org.axonframework:axon-core:2.4.3"
compile "org.axonframework:axon-test:2.4.3"
compile 'org.springframework:spring-core:4.2.3.RELEASE'
compile 'org.springframework:spring-beans:4.2.3.RELEASE'
compile 'org.springframework:spring-context:4.2.3.RELEASE'
compile 'org.springframework:spring-context-support:4.2.3.RELEASE'
compile 'org.springframework:spring-aop:4.2.3.RELEASE'
compile 'org.springframework:spring-test:4.2.3.RELEASE'
compile 'org.apache.logging.log4j:log4j-slf4j-impl:2.5'
compile 'org.apache.logging.log4j:log4j-core:2.5'
compile 'javax.persistence:persistence-api:2.1'
} mainClassName='demo.axon.ToDoItemRunner'

command命令:

这里我们假设了二个命令:创建命令、完成命令

CreateToDoItemCommand

package demo.axon.command;

import org.axonframework.commandhandling.annotation.TargetAggregateIdentifier;

public class CreateToDoItemCommand {

    @TargetAggregateIdentifier
private final String todoId;
private final String description; public CreateToDoItemCommand(String todoId, String description) {
this.todoId = todoId;
this.description = description;
} public String getTodoId() {
return todoId;
} public String getDescription() {
return description;
}
}

MarkCompletedCommand

package demo.axon.command;

import org.axonframework.commandhandling.annotation.TargetAggregateIdentifier;

public class MarkCompletedCommand {

    @TargetAggregateIdentifier
private final String todoId; public MarkCompletedCommand(String todoId) {
this.todoId = todoId;
} public String getTodoId() {
return todoId;
}
}

Event事件:

ToDoItemCreatedEvent

package demo.axon.event;

public class ToDoItemCreatedEvent {

    private final String todoId;
private final String description; public ToDoItemCreatedEvent(String todoId, String description) {
this.todoId = todoId;
this.description = description;
} public String getTodoId() {
return todoId;
} public String getDescription() {
return description;
}
}
ToDoItemCompletedEvent
package demo.axon.event;

public class ToDoItemCompletedEvent {

    private final String todoId;

    public ToDoItemCompletedEvent(String todoId) {
this.todoId = todoId;
} public String getTodoId() {
return todoId;
}
}

EventHandler事件处理

package demo.axon.handler;

import demo.axon.event.ToDoItemCompletedEvent;
import demo.axon.event.ToDoItemCreatedEvent;
import org.axonframework.eventhandling.annotation.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class ToDoEventHandler { Logger logger = LoggerFactory.getLogger(ToDoEventHandler.class); @EventHandler
public void handle(ToDoItemCreatedEvent event) {
logger.info("We've got something to do: " + event.getDescription() + " (" + event.getTodoId() + ")");
} @EventHandler
public void handle(ToDoItemCompletedEvent event) {
logger.info("We've completed a task: " + event.getTodoId());
}
}

上面的代码只是演示,将事件信息输出而已,真实应用中,这里可以完成对db的更新操作。 

领域模型model

package demo.axon.model;

import demo.axon.command.CreateToDoItemCommand;
import demo.axon.command.MarkCompletedCommand;
import demo.axon.event.ToDoItemCompletedEvent;
import demo.axon.event.ToDoItemCreatedEvent;
import org.axonframework.commandhandling.annotation.CommandHandler;
import org.axonframework.eventhandling.annotation.EventHandler;
import org.axonframework.eventsourcing.annotation.AbstractAnnotatedAggregateRoot;
import org.axonframework.eventsourcing.annotation.AggregateIdentifier; public class ToDoItem extends AbstractAnnotatedAggregateRoot { @AggregateIdentifier
private String id; public ToDoItem() {
} @CommandHandler
public ToDoItem(CreateToDoItemCommand command) {
apply(new ToDoItemCreatedEvent(command.getTodoId(), command.getDescription()));
} @EventHandler
public void on(ToDoItemCreatedEvent event) {
this.id = event.getTodoId();
} @CommandHandler
public void markCompleted(MarkCompletedCommand command) {
apply(new ToDoItemCompletedEvent(id));
}
}

然后让Spring将这些东西串在一起,配置文件如下:

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:axon="http://www.axonframework.org/schema/core"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.axonframework.org/schema/core http://www.axonframework.org/schema/axon-core-2.0.xsd"> <axon:command-bus id="commandBus"/>
<axon:event-bus id="eventBus"/> <axon:event-sourcing-repository id="toDoRepository"
aggregate-type="demo.axon.model.ToDoItem"/> <axon:aggregate-command-handler id="toDoItemHandler"
aggregate-type="demo.axon.model.ToDoItem"
repository="toDoRepository"
command-bus="commandBus"/> <axon:filesystem-event-store id="eventStore" base-dir="events"/> <axon:annotation-config /> <bean class="demo.axon.handler.ToDoEventHandler"/> <bean class="org.axonframework.commandhandling.gateway.CommandGatewayFactoryBean">
<property name="commandBus" ref="commandBus"/>
</bean> </beans>

最后,提供一个舞台,让整个应用run起来:

package demo.axon;

import demo.axon.command.CreateToDoItemCommand;
import demo.axon.command.MarkCompletedCommand;
import org.axonframework.commandhandling.gateway.CommandGateway;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; import java.util.UUID; public class ToDoItemRunner { private CommandGateway commandGateway; public ToDoItemRunner(CommandGateway commandGateway) {
this.commandGateway = commandGateway;
} public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("sampleContext.xml");
ToDoItemRunner runner = new ToDoItemRunner(applicationContext.getBean(CommandGateway.class));
runner.run();
} private void run() {
final String itemId = UUID.randomUUID().toString();
commandGateway.send(new CreateToDoItemCommand(itemId, "Need to do this"));
commandGateway.send(new MarkCompletedCommand(itemId));
}
}

输出结果:

12:01:36,113 <demo.axon.handler.ToDoEventHandler>  INFO [main]: We've got something to do: Need to do this (3126f293-67fd-4bb7-b152-069acba775b6)
12:01:36,114 <org.axonframework.commandhandling.callbacks.LoggingCallback> INFO [main]: Command executed successfully: demo.axon.command.CreateToDoItemCommand
12:01:36,205 <demo.axon.handler.ToDoEventHandler> INFO [main]: We've completed a task: 3126f293-67fd-4bb7-b152-069acba775b6
12:01:36,206 <org.axonframework.commandhandling.callbacks.LoggingCallback> INFO [main]: Command executed successfully: demo.axon.command.MarkCompletedCommand

axon框架测试也很容易:

package test.demo.axon;

import demo.axon.command.CreateToDoItemCommand;
import demo.axon.command.MarkCompletedCommand;
import demo.axon.event.ToDoItemCompletedEvent;
import demo.axon.event.ToDoItemCreatedEvent;
import demo.axon.model.ToDoItem;
import org.axonframework.test.FixtureConfiguration;
import org.axonframework.test.Fixtures;
import org.junit.Before;
import org.junit.Test; public class ToDoItemTest { private FixtureConfiguration fixture; @Before
public void setUp() throws Exception {
fixture = Fixtures.newGivenWhenThenFixture(ToDoItem.class);
} @Test
public void testCreateToDoItem() throws Exception {
fixture.given()
.when(new CreateToDoItemCommand("todo1", "need to implement the aggregate"))
.expectEvents(new ToDoItemCreatedEvent("todo1", "need to implement the aggregate"));
} @Test
public void testMarkToDoItemAsCompleted() throws Exception {
fixture.given(new ToDoItemCreatedEvent("todo1", "need to implement the aggregate"))
.when(new MarkCompletedCommand("todo1"))
.expectEvents(new ToDoItemCompletedEvent("todo1"));
} }

given/when/expectEvents的意思是,给(given)一个事件,然后当(when)某个命令被调用时,期待(expectEvents)某个事件被触发。

最后 github上还有一个比较复杂的示例项目:https://github.com/AxonFramework/Axon-trader,想深入了解的可以研究下

CQRS框架:AxonFramework 之 Hello World的更多相关文章

  1. CQRS框架(nodejs的DDD开发落地框架)初识感想

    CQRS是啥?DDD又是啥? 这两个概念其实没什么神秘的,当然此文章中的这两个概念以曾老师的课程为准(关于CQRS和DDD的标准概念,google上已经很多了,不再赘述.) DDD(Domain Dr ...

  2. DDD CQRS架构和传统架构的优缺点比较

    明天就是大年三十了,今天在家有空,想集中整理一下CQRS架构的特点以及相比传统架构的优缺点分析.先提前祝大家猴年新春快乐.万事如意.身体健康! 最近几年,在DDD的领域,我们经常会看到CQRS架构的概 ...

  3. 一种简单的CQRS架构设计及其实现

    一.为什么要实践领域驱动? 近一年时间我一直在思考一个问题:"如何设计一个松耦合.高伸缩性.易于维护的架构?".之所以有这样的想法是因为我接触的不少项目都是以数据库脚本来实现业务逻 ...

  4. [超简洁]EasyQ框架-应对WEB高并发业务(秒杀、抽奖)等业务

    背景介绍 这几年一直在摸索一种框架,足够简单,又能应付很多高并发高性能的需求.研究过一些框架思想如DDD DCI,也实践过CQRS框架. 但是总觉得复杂度高,门槛也高,自己学都吃力,如果团队新人更难接 ...

  5. CQRS架构设计及其实现

    CQRS架构设计及其实现 一.为什么要实践领域驱动? 近一年时间我一直在思考一个问题:“如何设计一个松耦合.高伸缩性.易于维护的架构?”.之所以有这样的想法是因为我接触的不少项目都是以数据库脚本来实现 ...

  6. [转] (CQRS)命令和查询责任分离架构模式(一) 之 什么是CQRS

    什么是CQRS? 这个问题网上可以找到很多资料,未接触过的童鞋请先查看Udi Dahan, Grey Young, Rinat Abdullin,园子里dax.net,以及Jdon社区上的相关文章. ...

  7. [翻译]高并发框架 LMAX Disruptor 介绍

    原文地址:Concurrency with LMAX Disruptor – An Introduction 译者序 前些天在并发编程网,看到了关于 Disruptor 的介绍.感觉此框架惊为天人,值 ...

  8. asp.net core系列 62 CQRS架构下Equinox开源项目分析

    一.DDD分层架构介绍 本篇分析CQRS架构下的Equinox开源项目.该项目在github上star占有2.4k.便决定分析Equinox项目来学习下CQRS架构.再讲CQRS架构时,先简述下DDD ...

  9. Equinox开源项目CQRS架构分析

    CQRS架构下Equinox开源项目分析 一.DDD分层架构介绍 本篇分析CQRS架构下的Equinox开源项目.该项目在github上star占有2.4k.便决定分析Equinox项目来学习下CQR ...

随机推荐

  1. javascript的变量作用域--对比js、php和c的for循环

    为什么要写这篇文章呢?主要是给自己提个醒,js的水很深,需要小心点儿才能趟过去,更何况自己不是专业人士,那就得更加小心了. 看下面的js代码: <!DOCTYPE html> <ht ...

  2. 分享在winform下实现模块化插件编程-优化版

    上一篇<分享在winform下实现模块化插件编程>已经实现了模块化编程,但我认为不够完美,存在以下几个问题: 1.IAppContext中的CreatePlugInForm方法只能依据完整 ...

  3. JavaScript易错知识点整理

    前言 本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一 ...

  4. 给jquery-validation插件添加控件的验证回调方法

    jquery-validation.js在前端验证中使用起来非常方便,提供的功能基本上能满足大部分验证需求,例如:1.内置了很多常用的验证方法:2.可以自定义错误显示信息:3.可以自定义错误显示位置: ...

  5. Conversations is being developed

    Development Conversations is being developed on GitHub by a team of volunteers under the lead of pro ...

  6. asp.net core 1.1 升级后,操作mysql出错的解决办法。

    遇到问题 core的版本从1.0升级到1.1,操作mysql数据库,查询数据时遇到MissingMethodException问题,更新.插入操作没有问题. 如果你也遇到这个问题,请参照以下步骤进行升 ...

  7. 【无私分享:ASP.NET CORE 项目实战(第八章)】读取配置文件(二) 读取自定义配置文件

    目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 我们在 读取配置文件(一) appsettings.json 中介绍了,如何读取appsettings.json. 但随之产生 ...

  8. 搭建公司内部的NuGet Server

    随着公司业务慢慢的拓展,项目便会越来越来多,很多项目会依赖其他项目DLL,比如一些底层的技术框架DLL引用,还有各业务系统的也有可能会有引用的可能. 项目多,交叉引用多,如果要是有一个DLL更新,那就 ...

  9. C#开发微信门户及应用(5)--用户分组信息管理

    在上个月的对C#开发微信门户及应用做了介绍,写过了几篇的随笔进行分享,由于时间关系,间隔了一段时间没有继续写这个系列的博客了,并不是对这个方面停止了研究,而是继续深入探索这方面的技术,为了更好的应用起 ...

  10. 初识Servlet

    1.创建DispatcherServlet package myservlet; import java.io.IOException; import javax.servlet.ServletExc ...