项目/微公司
本项目旨在展示使用 Spring Cloud 和 Axon 构建云原生、事件驱动的微服务架构的端到端最佳实践。
内容目录
- 应用程序 '微公司'
什么是云原生
要理解“云原生”,我们首先必须理解“云”。 在这个应用程序的上下文中,云是指平台即服务。PaaS 提供商提供了一个平台,隐藏了应用程序开发人员的基础设施细节,该平台位于基础设施即服务(IaaS)之上。
云原生应用程序是一种专门设计和实现以运行在平台即服务安装上的应用程序,并采用水平弹性扩展。
架构
微服务架构风格是一种将单个应用程序开发为一组小型服务的方式,每个服务运行在自己的进程中,通过轻量级机制(通常是 HTTP 资源 API 或事件驱动的方式)进行通信。
微服务使企业能够更快地创新,并保持竞争优势。但微服务架构的一个主要挑战是分布式数据的管理。每个微服务都有自己独立的数据库。实现维护跨多个服务的数据一致性的业务事务,以及从多个服务检索数据的查询是很困难的。
模式和技术:
- 微服务
- 命令和查询责任分离 (CQRS)
- 事件溯源
- 领域驱动设计 (DDD) - 聚合
技术
- Spring Boot (v1.5.3.RELEASE)
- Spring Cloud (Dalston.RELEASE)
- Spring Data (版本:Ingalls-SR3)
- Spring Data REST
- Axon 框架 (v3.0.4)
- RabbitMQ (v3.5.4) Axon 支持任何 Spring AMQP 支持的平台。
- Docker (v17.05.0-ce-mac11)
主要优点
- 轻松实现跨多个微服务的最终一致性业务事务
- 数据更改时自动发布事件
- 使用物化视图实现更快更可扩展的查询
- 对所有更新的可靠审计
工作原理
领域实际上分为命令端微服务应用程序和查询端微服务应用程序(这是 CQRS 的最字面形式)。
两个微服务之间的通信是 事件驱动
的,本演示使用 RabbitMQ 消息作为在进程(VM)之间传递事件的手段。
命令端处理命令。命令是以某种方式改变状态的操作。执行这些命令会生成 事件
,这些事件由 Axon 持久化,并通过 RabbitMQ 消息传递到其他 VM(任意数量的 VM)。在事件溯源中,事件是系统中的唯一记录。系统使用这些事件来按需描述和重建聚合体,一个事件接一个事件。
查询端是一个事件监听器和处理器。它监听 事件
并以最合理的方式处理它们。在此应用程序中,查询端只是构建和维护一个物化视图,该视图跟踪各个聚合体的状态(产品、博客、客户等)。查询端可以多次复制以实现可扩展性,RabbitMQ 队列持有的消息是持久的,因此如果事件监听器宕机,它们可以暂时存储。
命令端和查询端都具有可以用于访问其功能的 REST API。
阅读 Axon 文档 了解更多有关 Axon 如何在您的应用程序中实现 CQRS 和事件溯源的详细信息,以及如何配置所有内容的详细信息。
服务
后备服务
前提是有一些第三方服务依赖项,应该被视为附加到您的云原生应用程序的资源。后备服务的关键特性是它们作为绑定在其部署环境中的应用程序提供。 每个后备服务必须使用静态定义的路径定位
(服务)注册表
Netflix Eureka 是一个服务注册表。它提供一个 REST API 用于服务实例注册管理和查询可用实例。Netflix Ribbon 是一个 IPC 客户端,与 Eureka 一起工作以进行可用服务实例的客户端负载平衡。
授权服务器(Oauth2)
用于发放令牌和授权请求。
配置服务器
配置服务是任何微服务架构的核心组件。基于十二因素应用方法论,您的微服务应用程序的配置应存储在环境中,而不是项目中。 配置托管在这里:https://github.com/idugalic/micro-company-config.git
API 网关
实现一个 API 网关,它是所有客户端的单一入口。API 网关通过两种方式之一处理请求。一些请求只是代理/路由到相应的服务。它处理其他请求,通过分散到多个服务来处理。
后端微服务
虽然中间层中的后备服务仍被视为微服务,但它们解决的是纯粹的操作和安全相关的关注点。该应用程序的业务逻辑几乎完全位于我们的底层。
博客微服务
博客服务用于管理和查询公司的帖子。它分为命令端微服务应用程序和查询端微服务应用程序。
项目微服务
项目服务用于管理和查询公司的项目。它分为命令端微服务应用程序和查询端微服务应用程序。
管理员服务器 (http://codecentric.github.io/spring-boot-admin/1.3.2/)
Spring Boot Admin 是一个简单的应用程序,用于管理和监控您的 Spring Boot 服务。服务使用 Spring Cloud(例如 Eureka)进行发现。UI 只是基于 Spring Boot Actuator 端点之上的 Angular.js 应用程序。如果您想使用更高级的功能(例如 jmx,日志级别管理),则必须在客户端服务中包含 Jolokia。
请注意,该服务器/服务也可以适用于“后备服务”。在这种情况下,所有服务将使用 Admin Client 连接到该服务(而不是 Eureka)。
运行说明
先决条件
- Java JDK 8
- Git
- Docker
- VirtualBox
步骤 1: 克隆项目
$ git clone https://github.com/idugalic/micro-company.git
步骤2(可选):构建项目
请注意,镜像可在docker hub上获取(https://hub.docker.com/u/idugalic),因此如果您不想构建服务,只需跳到步骤3
构建项目
$ cd micro-company
$ mvn clean install
通过maven构建docker镜像
$ DOCKER_HOST=unix:///var/run/docker.sock mvn docker:build
或者通过maven构建并推送镜像(需要docker仓库用户名和密码):
$ DOCKER_HOST=unix:///var/run/docker.sock mvn docker:build -DpushImage
步骤3:按如下方式运行应用程序
- 单体在localhost上运行,或
- 微服务通过docker在localhost上运行,或
- 微服务在docker swarm(模式)本地集群上运行,或
- 微服务在docker swarm(模式)AWS集群上运行,或
- 微服务在Pivotal Cloud Foundry - PCF Dev上运行
在localhost上运行单体应用
此版本的应用程序部署为单体应用。
通过事件溯源和CQRS应用领域驱动设计。事件溯源如何实现部署灵活性 - 应用程序可以作为单体部署。
领域被字面上分割为命令侧组件和查询侧组件(这是CQRS最字面的形式)。
两个组件之间的通信是事件驱动的,演示使用简单事件存储(在这种情况下是数据库 - JPA)作为在组件之间传递事件的手段。这种情况下不需要RabbitMQ。
Docker
$ cd micro-company/docker
$ docker-compose -f docker-compose-monolithic.yml up -d
Maven
$ cd micro-company
$ mvn clean install
$ cd micro-company/monolithic
$ mvn spring-boot:run
Eclipse
作为Spring Boot项目运行。 我建议也使用Boot Dashboard。
通过docker在localhost上运行微服务
$ cd micro-company/docker
$ docker-compose up -d
在Docker Swarm(模式)本地集群上运行微服务
Docker Engine 1.12+ 包含原生管理Docker Engine集群的swarm模式。https://docs.docker.com/engine/swarm
$ cd micro-company/docker
$ . ./swarm-mode-local.sh
执行此脚本会:
- 创建4个虚拟机(需要VirtualBox)。一个'swarm master'和三个'swarm nodes'
- 在swarm master上初始化集群
- 将节点加入集群
- 直接使用
docker-compose.yml
文件部署服务
请按照控制台日志中的说明进行操作,并祝您玩得愉快 :)
实验性功能
- 服务的聚合日志
- Docker构建有一个新的实验性--squash
在1.13版中,实验性功能现在是标准二进制文件的一部分,可以通过运行带--experimental标志的Deamon启用。我们就这么做。首先我们需要更改dockerd配置并添加标志:
$ docker-machine ssh swmaster -t sudo vi /var/lib/boot2docker/profile
将--experimental标志添加到EXTRA_ARGS变量。在我的例子中,修改后的文件如下:
EXTRA_ARGS='
--label provider=virtualbox
--experimental
'
保存更改并重新启动leader节点:
docker-machine stop swmaster
docker-machine start swmaster
在Docker Swarm(模式)AWS集群上运行微服务
Docker Engine 1.12+ 包含原生管理Docker Engine集群的swarm模式。https://docs.docker.com/engine/swarm
我们将在AWS基础设施上部署服务。你需要准备它。 请注意步骤1和3是可选的。如果您已经有了用户或密钥对,您无需创建它们。
- 步骤1:以root身份登录到您的AWS帐户并创建将要使用的(不是root)用户。按照指南。
- 步骤2:登录您的新用户帐户。
- 步骤3:使用Amazon EC2创建您的密钥对。请注意密钥将由浏览器下载。在我的情况下,它是'/Users/idugalic/.ssh/idugalic.pem'。
- 步骤4:使用CloudFormation模板在AWS上创建堆栈。
- 步骤5:运行下面的shell脚本并按照说明操作。
$ cd micro-company/docker
$ . ./swarm-mode-aws.sh
在Pivotal Cloud Foundry - PCF Dev上运行微服务
在本地工作站上使用PCF Dev运行服务
- 下载并安装PCF:https://pivotal.io/platform/pcf-tutorials/getting-started-with-pivotal-cloud-foundry-dev/introduction
- 启动PCF Dev:
$ cf dev start -m 8192
- 登录PCF Dev:
$ cf login -a https://api.local.pcfdev.io --skip-ssl-validation -u admin -p admin -o pcfdev-org
- 创建用户服务 - configserver:
$ cf cups configserver -p '{"uri":"http://configserver.local.pcfdev.io"}'
- 创建用户服务 - registry:
$ cf cups registry -p '{"uri":"http://registry.local.pcfdev.io"}'
- 创建用户服务 - authserver:
$ cf cups authserver -p '{"uri":"http://authserver.local.pcfdev.io"}'
- 创建cloud foundry服务实例 - mysql:
$ cf create-service p-mysql 512mb mysql
- 创建cloud foundry服务实例 - rabbit:
$ cf create-service p-rabbitmq standard rabbit
- 打开您的浏览器并访问https://local.pcfdev.io。探索!
CLI
在命令行中推送微服务:
$ cd micro-company/
$ mvn clean install
$ cf push -f configserver/manifest.yml -p configserver/target/configserver-0.0.1-SNAPSHOT.jar
$ cf push -f registry/manifest.yml -p registry/target/registry-0.0.1-SNAPSHOT.jar
$ cf push -f authserver/manifest.yml -p authserver/target/authserver-0.0.1-SNAPSHOT.jar
$ cf push -f command-side-blog-service/manifest.yml -p command-side-blog-service/target/command-side-blog-service-0.0.1-SNAPSHOT.jar
$ cf push -f command-side-project-service/manifest.yml -p command-side-project-service/target/command-side-project-service-0.0.1-SNAPSHOT.jar
$ cf push -f query-side-blog-service/manifest.yml -p query-side-blog-service/target/query-side-blog-service-0.0.1-SNAPSHOT.jar
$ cf push -f query-side-project-service/manifest.yml -p query-side-project-service/target/query-side-project-service-0.0.1-SNAPSHOT.jar
$ cf push -f api-gateway/manifest.yml -p api-gateway/target/api-gateway-0.0.1-SNAPSHOT.jar
$ cf push -f adminserver/manifest.yml -p adminserver/target/adminserver-1.3.3.RELEASE.jar
Eclipse
使用'Boot Dashboard'推送微服务:
- 添加本地(dev)cloud foundry目标(https://api.local.pcfdev.io)。
- 一旦连接,开始将项目拖到该实例。
注意:请先运行'configserver',再运行'registry'和其他服务。
使用CURL发布命令和查询
创建博客文章
微服务
$ curl -H "Content-Type: application/json" -X POST -d '{"title":"xyz","rawContent":"xyz","publicSlug":"publicslug","draft":true,"broadcast":true,"category":"ENGINEERING","publishAt":"2016-12-23T14:30:00+00:00"}' http://127.0.0.1:9000/command/blog/blogpostcommands
或在PCF上:
$ curl -H "Content-Type: application/json" -X POST -d '{"title":"xyz","rawContent":"xyz","publicSlug":"publicslug","draft":true,"broadcast":true,"category":"ENGINEERING","publishAt":"2016-12-23T14:30:00+00:00"}' api-gateway.local.pcfdev.io/command/blog/blogpostcommands
单体
$ curl -H "Content-Type: application/json" -X POST -d '{"title":"xyz","rawContent":"xyz","publicSlug":"publicslug","draft":true,"broadcast":true,"category":"ENGINEERING","publishAt":"2016-12-23T14:30:00+00:00"}' http://127.0.0.1:8080/api/blogpostcommands
发布博客文章
微服务
$ curl -H "Content-Type: application/json" -X POST -d '{"publishAt":"2016-12-23T14:30:00+00:00"}' http://127.0.0.1:9000/command/blog/blogpostcommands/{id}/publishcommand
单体
$ curl -H "Content-Type: application/json" -X POST -d '{"publishAt":"2016-12-23T14:30:00+00:00"}' http://127.0.0.1:8080/api/blogpostcommands/{id}/publishcommand
查询博客文章
微服务
$ curl http://127.0.0.1:9000/query/blog/blogposts
或在PCF上:
$ curl api-gateway.local.pcfdev.io/query/blog/blogposts
单体
$ curl http://127.0.0.1:8080/api/blogposts
创建项目
微服务
$ curl -H "Content-Type: application/json" -X POST -d '{"name":"Name","repoUrl":"URL","siteUrl":"siteUrl","description":"sdfsdfsdf"}' http://127.0.0.1:9000/command/project/projectcommands
单体
$ curl -H "Content-Type: application/json" -X POST -d '{"name":"Name","repoUrl":"URL","siteUrl":"siteUrl","description":"sdfsdfsdf"}' http://127.0.0.1:8080/api/projectcommands
查询项目
微服务
$ curl http://127.0.0.1:9000/query/project/projects
单体
$ curl http://127.0.0.1:8080/api/projects
网关上的WebSocket
微服务
所有事件将通过WebSocket发送到浏览器,并显示在http://127.0.0.1:9000/socket/index.html 或在PCF上显示:http://api-gateway.local.pcfdev.io/socket/index.html
关于AXON
命令
命令是发送到系统的消息,意图执行某个操作。它们被发送到命令网关,然后命令总线将其分发到具体的命令处理器。可以在一些拦截器中进行一些验证或安全检查。命令应在某个工作单元中执行,可以在数据库事务上实现。另外,命令处理可以通过JGroups分布在多个节点上。
Axon为这些概念提供了接口、实现和注解。你只需编写你的命令类和简单的处理类,使用一个注解即可。
已经写了一些默认的拦截器,比如用于JSR 303 Bean验证的拦截器。如果需要,你可以通过实现一个简单的接口轻松编写自己的拦截器。
事件
如前所述,命令是意图执行某个操作的消息。另一方面,当这个操作完成后,可以产生另一条消息——事件。它们代表一个事实。
在Axon中,你只需编写自己的事件类。负责处理这些事件的所有基础设施都已为你准备好了。在之前的版本(1.3)中,你必须扩展基础事件类——在当前版本(2.0)中,你不必再这么做。因此,你可以轻松地使用你的事件与其他非Axon系统集成。
领域
你应该非常仔细地建模你的领域。这是本质复杂性所在的地方。没有任何框架能够帮助你解决这个问题。你应该花费大部分时间在这个地方。
感谢Axon,你可以真正这样做。所有的基础设施代码都已经为你准备好。Axon提供给你的是一个基本的聚合根类,你可以扩展它并访问一些有用的方法,以帮助你应对领域建模的困难。
最终,如果你使用事件溯源,你可以使用Abstract Annotated Aggregate Root,并在你的私有方法上使用注解,这些方法应用事件并改变聚合的状态。Axon还会将事件分发给聚合中带注解的实体。
你不应该在未响应事件的方法中更改聚合状态。这在事件溯源中是常规做法。Axon确保你不会违背这一原则,因此一个没有经验的人不会破坏任何东西。
仓库
聚合存储在仓库中。你应该能够通过ID保存和加载聚合。无论你使用的是SQL数据库、平面文件还是其他noSQL解决方案,这一点都无关紧要。持久化技术应当与概念分离。
Axon给你实现了这种分离。你可以选择使用经典的基于JPA的仓库,或事件溯源仓库。它还添加了使用缓存来进行性能调整的可能性。
事件存储
如果你选择使用事件溯源,你需要有效地在某个事件存储中存储你的事件。
Axon为仓库提供接口,并提供了几种实现。最简单的实现基于平面文件,但不如其他实现强大。你还可以使用基于JPA的事件存储,这将只创建两个表——事件条目表和聚合快照条目表。对于这两个解决方案,你需要序列化你的事件。Axon默认使用XStream,但你可以选择其他任何序列化工具。第三个事件存储实现基于MongoDB。如果需要,你可以轻松实现其他任何事件存储。
当你的聚合存在很长时间时,它们可能有相当长的历史。出于性能原因,你可以使用聚合快照以缩短聚合加载时间。
你的事件定义可能会随着时间变化,因此这些事件存储默认为你提供了轻松编写事件升级器的可能性。如果发生冲突,它还为你提供支持,以进行一些高级的冲突解决。
事件总线
在CQRS中,事件生成后,需要处理这些事件以更新查询数据库。它们被分发到事件处理器。那些事件处理器可以位于同一台机器上或分布在一个集群中。
在Axon中,你只需注解那些应该监听事件的组件的方法。你还可以选择是否希望同步或异步处理事件。
如果发生错误,你可以定义如何解决。Axon可以回滚事务(如果你在使用的话),或者可以为你重新安排事件并在一段时间后再次处理它。如果你需要其他错误处理方案,你可以通过实现简单接口自行编写。
如果你有分布式环境,Axon为Spring AMQP提供支持。
有时你需要重播历史事件,Axon也为你提供了支持。
Sagas
有时你的事务需要长时间有效。你不能总是在一个ACID事务中完成所有工作。这就是Sagas的用武之地。另一方面,你可以将Sagas视为工作流程或状态机。
在Axon中,Saga是一个特殊的事件处理器,处理长时间的业务事务。你可以使用Abstract Annotated Saga作为Saga的基类,然后在处理事件的方法上使用注解。
如果你需要关注时间或截止日期,Axon为Saga提供了可以处理时间管理的调度器。你可以使用纯Java实现的简单调度器,或功能更强大的Quartz调度器。
测试
你应该测试你编写的代码,对吧?你可以用常规方法进行测试,如使用模拟等,但如果你使用事件溯源,你有另一种选择。你有一段事件历史,然后你执行某个命令,结果会生成一串事件。你可以这样准确地用Axon进行测试。
Axon为你提供了一个Given When Then Fixture。在Given中,你指定历史事件集,在When中你指定要测试的命令,在Then中你指定应生成的全部事件集。一切都已为你准备好,你只需定义这些事件和命令的测试场景。
当然,你还应该测试你的Sagas。针对这一点,有一个特殊的Annotated Saga Test Fixture。在Given中,你指定一组历史事件,在When中你指定一个事件,在Then中你指定期望的行为或状态变化。如果需要,你还可以模拟时间来测试与时间相关的Sagas。
Spring支持
如今,在Java世界中,每个成熟的框架都应有某种形式的Spring支持。Axon的每个组件都可以配置为一个Spring bean。Axon还为几乎所有功能提供命名空间快捷方式,因此配置尽可能简短。
实验室
该项目已迁移到一个github组织http://ivans-innovation-lab.github.io/。我们可以在那里练习十二要素应用方法论。
你将学到我们如何:
- 做出使用某一模式而非另一模式的决策,
- 随着时间的推移,改变架构、组织(文化)和流程以响应新需求,
- 为成功的数字化奠定基础。
最终目标是更快地交付更好的软件。今天,这无疑意味着连续交付——对于安装产品——或连续部署对于-aaS产品。
源代码托管在Github:https://github.com/ivans-innovation-lab
参考文献和进一步阅读
- http://martinfowler.com/articles/microservices.html
- http://microservices.io/
- http://12factor.net/
- http://pivotal.io/platform/migrating-to-cloud-native-application-architectures-ebook
- http://pivotal.io/beyond-the-twelve-factor-app
- http://www.infoq.com/news/2016/01/cqrs-axon-example
- https://www.infoq.com/articles/microservices-aggregates-events-cqrs-part-2-richardson
- https://axoniq.io/
- http://blog.arungupta.me/deploy-docker-compose-services-swarm/
- http://www.kennybastani.com/2016/04/event-sourcing-microservices-spring-cloud.html
- https://benwilcock.wordpress.com/2016/06/20/microservices-with-spring-boot-axon-cqrses-and-docker/