使用Docker+springboot+springcloud+axon 构建CQRS/ES的微服务架构

原文地址:https://benwilcock.wordpress.com/2016/06/20/microservices-with-spring-boot-axon-cqrses-and-docker/

在过去几年中,软件架构的变化步伐迅速发展。 DevOps,Microservices和Containerisation等新方法已成为热门话题,采用率迅速提高。 在这篇文章中,我想向您介绍一个我一直在研究的微服务项目,它结合了过去几年中两个突出的架构进步:命令和查询责任分离(CQRS)和容器化。

在第一部分中,我将向您展示使用容器分发和运行多服务器微服务应用程序是多么容易。

为了做到这一点,我使用Docker创建了一个包含运行演示所需的所有微服务的容器套件。 在撰写本文时,该套件中有七个微服务; 他们是:-
这里写图片描述

该演示的源代码可在Github上获得,并演示如何实现和集成“云原生”Java所需的一些功能,包括: -
- 使用Java和Spring Boot的微服务;
- 使用Docker容器在任何地方构建,发送和运行;
- 使用Axon Framework v2,MongoDB和RabbitMQ进行命令和查询责任分离(CQRS)和事件源(ES);
- 使用Spring Cloud进行集中配置,服务注册和API网关;

How it works

这里介绍的微服务示例项目围绕着一个虚构的“product”主数据应用程序,类似于您在大多数零售或制造公司中找到的应用程序。可以使用简单的RESTful服务API从此主数据中添加,存储,搜索和检索产品。随着更改的发生,通过使用消息发送通知给感兴趣的各方。

Product Data应用程序使用CQRS架构风格构建。在诸如ADD之类的CQRS命令中,物理上与诸如’VIEW(其中id = 1)`之类的查询分开。实际上,在这个特定的例子中,Product域的代码库已经完全分为两个独立的组件 - 命令端微服务和查询端微服务。

像大多数12 factor apps一样,每个微服务都有一个单一的责任;拥有自己的数据存储区;并且可以独立于另一个进行部署和扩展。这是CQRS和微服务中最直译的解释。 CQRS或微服务都不能以这种方式实现,但为了本演示的目的,我选择创建一个非常明确的读写问题分离。
逻辑架构如下所示: -
这里写图片描述

命令端和查询端微服务都是使用Spring Boot框架开发的。 命令和查询微服务之间的所有通信纯粹是“事件驱动的”。 使用RabbitMQ消息传递在微服务组件之间传递事件。 消息传递提供了一种可扩展的方法,以松散耦合的方式在进程,微服务,遗留系统和其他方之间传递事件。

请注意,这两个服务的数据库与另一个服务器的数据库不同。
这很重要,因为它为每项服务提供了高度自治,这反过来又有助于各项服务独立于系统中的其他服务进行扩展。
有关CQRS架构的更多信息,请查看我的幻灯片共享CQRS微服务上面的幻灯片。

CQRS架构模式中存在的高度自治和隔离为我们提供了一个有趣的问题 - 我们应该如何分配和运行如此松散耦合的组件? 在我看来,容器化提供了最好的机制,并且Docker被广泛使用,它的格式已成为容器映像的事实标准,大多数流行的云平台提供直接支持。 它也很容易使用,这肯定有帮助。

命令端微服务

命令是“actions which change state”。 命令端微服务包含所有域逻辑和业务规则。 命令用于添加新产品或更改其状态。 在特定产品上执行这些命令会导致生成“事件event”,这些事件由Axon框架持久保存到MongoDB中,并通过RabbitMQ消息传递到其他进程(尽可能多的进程)。

在event-soursing中,事件是系统的唯一状态记录。 它们被系统用于描述和重建任何实体的当前状态(通过一次一个地重放它的过去事件,直到重新应用所有先前的事件)。 这听起来很慢,但实际上因为事件很简单,它非常快,可以使用名为“快照”的汇总进一步调整。

在领域驱动设计(DDD)中,实体通常被称为“Aggregate”或“AggregateRoot”。

查询端微服务

查询端微服务充当事件监听器和视图。 它监听命令端发出的“事件”,并将它们处理成最有意义的形状(例如表格视图)。

在这个特定的例子中,查询端只是构建和维护一个“物化视图”或“投影”,它保存了各个产品的最新状态(就其ID和描述以及它们是否可销售而言)。 可以多次复制查询端以实现可伸缩性,并且可以使RabbitMQ队列保存的消息持久,因此如果消息发生故障,它们甚至可以代表查询端临时存储消息。

命令端和查询端都有REST API,可用于访问其功能。

有关更多信息,请参阅Axon文档,该文档描述了Axon如何为您的Java应用程序带来CQRS和事件源,以及有关如何配置和使用它的大量详细信息。

运行例子

运行演示代码很简单,但您需要先在计算机上安装以下软件。 作为参考,我使用 Ubuntu 16.04 作为我的操作系统,但我也成功测试了新的 Docker for Windows Beta上的应用程序。

  • Docker (I’m using v1.8.2)
  • Docker-compose (I’m using v1.7.1)

如果您已经拥有MongoDB或RabbitMQ,请在继续之前关闭这些服务以避免端口冲突。

第1步:获取Docker-compose配置文件

在新的空文件夹中,在终端执行以下命令以下载此演示的最新docker-compose配置文件。

$ wget https://raw.githubusercontent.com/benwilcock/microservice-sampler/master/docker-compose.yml

尽量不要更改文件名 - Docker默认寻找名为“docker-compose.yml”的文件。 如果更改了名称,请在以下步骤中使用参数 -f 切换。

第2步:启动微服务

因为我们正在使用docker-compose,所以启动微服务现在只是执行以下命令的情况。

 docker-compose up

在下载并运行docker镜像时,您会在终端窗口中看到大量下载和记录输出。

共有七个docker镜像,分别是mongodb,rabbitmq,config-service,discovery-service,gateway-service,product-cmd-side和product-qry-side。
如果要查看正在运行的docker实例(并获取其本地IP地址),请打开一个单独的终端窗口并执行以下命令: -

docker ps 

一旦实例启动并运行(这可能需要一些时间),您可以立即使用浏览器环顾四周。您应该能够访问: -

  • 端口“15672”上的Rabbit Management Console
  • 端口“8761”上的Eureka Discovery Server控制台
  • 端口“8888”上的配置服务器映射
  • 端口’8080’上的API网关路由

第3步:使用产品

到现在为止还挺好。现在我们要测试产品的添加。

在这个手动系统测试中,我们将向命令端REST API发出add命令。

当命令端处理完命令时,引发’ProductAddedEvent’,存储在MongoDB中,并通过RabbitMQ转发到查询端。然后查询端处理此事件并将产品的记录添加到其物化视图(实际上是这个简单演示的H2内存数据库)。事件处理完毕后,我们可以使用查询端微服务查找有关已添加新产品的信息。在执行这些任务时,您应该在docker-compose终端窗口中观察一些日志记录输出。

步骤3.1:添加新产品

为了执行测试,我们首先需要打开第二个终端窗口,我们可以在这里发出一些CURL命令而不停止我们在第一个窗口中运行的docker组合实例。

出于本次测试的目的,我们将在产品目录中添加一个名为“Everything is Awesome”的MP3产品。为此,我们可以使用命令端REST API并使用POST请求发出它,如下所示……

$ curl -X POST -v --header "Content-Type: application/json" --header "Accept: */*" "http://localhost:8080/commands/products/add/01?name=Everything%20Is%20Awesome"

如果您没有“CURL”,则可以使用自己喜欢的REST API测试工具(例如Postman,SoapUI,RESTeasy等)。

如果您正在使用Docker for Mac或Windows的公共测试版(强烈推荐),则需要将“localhost”替换为在终端窗口运行docker ps时显示的IP地址。

您应该看到类似于以下响应的内容。

* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080(#0)
> POST /commands/products/add/01?name=Everything%20Is%20Awesome HTTP/1.1
> Host: localhost:9000
> User-Agent: curl/7.47.0
> Content-Type: application/json
> Accept: */*$ http://localhost:8080/commands/products/01
< HTTP/1.1 201 Created
< Date: Thu, 02 Jun 2016 13:37:07 GMTThis
< X-Application-Context: product-command-side:9000
< Content-Length: 0
< Server: Jetty(9.2.16.v20160414)

响应代码应为“HTTP / 1.1 201 Created”。这意味着MP3产品“Everything is Awesome”已成功添加到命令端事件源存储库中。

步骤3.2:查询新产品

现在让我们检查一下,我们可以查看刚刚添加的产品。为此,我们发出一个简单的’GET’请求。

$ curl http://localhost:8080/queries/products/1

您应该看到以下输出。这表明查询端微服务有我们新添加的MP3产品的记录。该产品列为不可销售(salable = false)。

{
  name: "Everything Is Awesome",
  saleable: false,
  _links: {
    self: {
    href: "http://localhost:8080/queries/products/1"
    },
  product: {
    href: "http://localhost:8080/queries/products/1"
    }
  }
}

ok!如果您愿意,可以继续重复测试以添加更多产品,请注意不要在POST时尝试重复使用相同的产品ID,否则您将看到错误。

如果您熟悉MongoDB,则可以检查数据库以查看您创建的所有事件。同样,如果您了解RabbitMQ管理控制台的方法,您可以在命令端和查询端微服务之间看到消息。

关于作者
Ben Wilcock是一名自由软件架构师兼技术主管,对微服务,云和移动应用程序充满热情。 Ben帮助几家富时100指数公司提高了响应能力,创新能力和敏捷性。 Ben也是一位受人尊敬的技术博主,他的文章在Java Code Geeks,InfoQ,Android Weekly等中都有所体现。您可以通过LinkedIn,Twitter和Github与他联系。

猜你喜欢

转载自blog.csdn.net/quguang65265/article/details/81535931