ddd-cqrs-axon
现代的消息传递和事件驱动的应用程序具有与传统企业应用程序明显不同的要求。 明确的迹象是,重点已从保证传递单个消息(传递的责任主要在中间件上)转移到“智能端点和愚蠢的管道”,这取决于应用程序来监视传递和守时。 结果是,消息传递基础结构上的服务质量要求更多地是关于吞吐量而不是传递保证。 消息没有达到预期的目标是发送者和接收者都必须解决的问题,因为它们从根本上负责受此类故障影响的业务需求,并且最有能力确定适当的响应。
发生的另一个变化是存储和处理能力的价格稳定下降的直接结果,以及将这些资源分配给应用程序组件的灵活性提高了:命令查询责任隔离(简称为CQRS)和事件源。 在您的应用程序环境中,没有一个组件来管理“黄金记录”以进行更新和查询,而是将这两个职责分开,并提供了多个查询源。 Command分量(通常是等式的低频分量)可以针对验证和存储进行优化。 然后,使用事件向企业的其他成员宣布经过验证的更改,其中(多个)查询组件使用它们来构建优化的模型。 前向高速缓存和批处理副本的使用增加,是一个预警信号,表明迫切需要此体系结构模式,并且具有可重播事件存储的查询模型将此处所需的许多解决方案形式化。 通过使用导致事件的事件序列定义实体的当前状态,事件搜索在此方面取得了进步。 这意味着,我们不保留记录的可更新存储,而是使用仅追加事件存储,从而使我们可以使用Write-Once语义并同时获得不中断的审核跟踪。
为了支持这些更改,我们将传统组件(即数据库)和面向消息的中间件(具有所需功能)以及新的,专用的基础结构组件进行了扩展。 在Java和Kotlin软件开发领域,开源Axon框架提供了CQRS和事件源范例的领先实现,但它仅为应用程序的各个模块提供了解决方案。 如果将应用程序放在一个整体的安装中,这无疑是提供最快的方式启动并运行以进行未开发的开发工作,则无法利用其对更分布式架构的支持似乎是一种浪费。 就其本身而言,基于Axon的应用程序的体系结构很容易被拆分或“勒死”,这已成为更流行的术语。 那么问题是我们如何支持消息传递和事件存储实现。
典型的CQRS应用程序具有交换命令和事件的组件,并具有通过显式命令处理的聚合的持久性以及优化的查询模型 并根据报告汇总状态的事件进行构建。 可以在RDBMS存储层或NoSQL文档组件上构建此设置中的聚合持久层,并且框架核心中包括基于标准JPA / JDBC的存储库。 存储查询模型也是如此。
可以使用大多数标准消息传递组件来解决用于交换消息的通信,但是使用模式确实支持针对不同场景的特定实现。 只要我们可以确保没有消息丢失,我们就可以使用几乎任何现代消息解决方案作为发布-订阅模式,因为我们希望查询模型能够忠实地表示聚合状态。 对于命令,如果仅要确保我们可以检测到命令处理程序的不可用,就需要将基本的单向消息传递扩展为请求-应答模式。 其他答复可能是汇总的结果状态,或者如果否决了更新,则是详细的验证失败报告。 在查询方面,简单的请求-应答模式不足以实现分布式微服务体系结构。 我们还希望查看分散收集和先进先出模式,以及通过持续更新获得的流式结果。
对于事件源,聚合持久性层可以替换为Write-Once-Read-Many层,该层捕获命令产生的所有事件,并为特定聚合提供重播支持,这些重播可用于查询模型,从而使我们使用内存存储重新实现它们,或者在我们怀疑数据不一致时提供重新同步。 快照的使用是一个有用的改进,因此我们可以避免重放可能很长的更改历史记录,并且Axon Framework为聚合提供了标准的快照实现。
现在,如果我们看一下在应用程序的基础结构组件中收集的内容,则需要满足以下条件:
Axon框架提供了附加模块,以集成各种开源产品,例如用于事件分发的基于Kafka和基于AMQP的解决方案。 但是,毫不奇怪,AxonIQ自己的Axon Server也可以用作一体式解决方案。 本系列文章介绍了如何进行安装和运行,从简单的本地安装开始,然后逐步进行基于Docker的安装(包括docker-compose和Kubernetes)以及“云中”的VM。
首先,让我们考虑一个小程序,以演示我们要添加到体系结构中的组件。 我们将使用Spring Boot来简化配置,Axon有一个Spring Boot启动器,它将扫描我们使用的注释。 作为第一个迭代,我们将其保留在一个简单的应用程序中,该应用程序发送导致事件的命令。 为此,我们需要处理以下命令:
@CommandHandler
public void processCommand(TestCommand cmd) {
log.info("handleCommand(): src = '{}', msg = '{}'.",
cmd.getSrc(), cmd.getMsg());
final String eventMsg = cmd.getSrc() + " says: " + cmd.getMsg();
eventGateway.publish(new TestEvent(eventMsg));
}
这里的命令和事件是简单的值对象,第一个指定源和一条消息,其他仅一条消息。 同一类还定义了将接收上面发布的事件的事件处理程序:
@EventHandler
public void processEvent(TestEvent evt) {
log.info("handleEvent(): msg = '{}'.", evt.getMsg());
}
要完成此应用程序,我们需要添加一个发送命令的“启动器”:
@Bean
public CommandLineRunner getRunner(CommandGateway gwy) {
return (args) -> {
gwy.send(new TestCommand("getRunner", "Hi there!"));
SpringApplication.exit(ctx);
};
}
对于第一个版本,我们还需要一些支持代码来补偿缺少像CommandHandler这样的实际集合,因为Axon Framework希望每个命令都具有某种标识,从而可以使同一接收者的后续命令相关联。完整的代码可在GitHub上下载,除上述代码外,它还包含TestCommand和TestEvent类,并基于随机密钥配置路由策略,有效地告诉Axon不必打扰。 这需要在CommandBus实现上进行配置,在这里我们必须开始研究架构组件的实现。
如果我们在没有任何特定命令总线和事件总线实现的情况下运行该应用程序,则Axon运行时将假定基于Axon Server的分布式安装程序,并尝试连接到该应用程序。 Axon Server标准版可在AxonIQ开源许可下免费获得,并且可以从AxonIQ网站获得Axon Framework和Server的预编译软件包。 如果将可执行JAR文件放在其自己的目录中,然后使用Java 11运行它,它将开始使用合理的默认值。 请注意,以下运行使用“ 4.3”版本,您的情况可能会有所不同,具体取决于您下载它的时间。
$ unzip AxonQuickStart.zip
…
$ mkdir server-se
$ cp axonquickstart-4.3/AxonServer/axonserver-4.3.jar server-se/axonserver.jar
$ cp axonquickstart-4.3/AxonServer/axonserver-cli-4.3.jar server-se/axonserver-cli.jar
$ cd server-se
$ chmod 755 *.jar
$ java -version
openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1)
OpenJDK 64-Bit Server VM (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1, mixed mode, sharing)
$ ./axonserver.jar
_ ____
/ \ __ _____ _ __ / ___| ___ _ ____ _____ _ __
/ _ \ \ \/ / _ \| '_ \\___ \ / _ \ '__\ \ / / _ \ '__|
/ ___ \ > < (_) | | | |___) | __/ | \ V / __/ |
/_/ \_\/_/\_\___/|_| |_|____/ \___|_| \_/ \___|_|
Standard Edition Powered by AxonIQ
version: 4.3
2020-02-20 11:56:33.761 INFO 1687 --- [ main] io.axoniq.axonserver.AxonServer : Starting AxonServer on arrakis with PID 1687 (/mnt/d/dev/AxonIQ/running-axon-server/server/axonserver.jar started by bertl in /mnt/d/dev/AxonIQ/running-axon-server/server)
2020-02-20 11:56:33.770 INFO 1687 --- [ main] io.axoniq.axonserver.AxonServer : No active profile set, falling back to default profiles: default
2020-02-20 11:56:40.618 INFO 1687 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8024 (http)
2020-02-20 11:56:40.912 INFO 1687 --- [ main] A.i.a.a.c.MessagingPlatformConfiguration : Configuration initialized with SSL DISABLED and access control DISABLED.
2020-02-20 11:56:49.212 INFO 1687 --- [ main] io.axoniq.axonserver.AxonServer : Axon Server version 4.3
2020-02-20 11:56:53.306 INFO 1687 --- [ main] io.axoniq.axonserver.grpc.Gateway : Axon Server Gateway started on port: 8124 - no SSL
2020-02-20 11:56:53.946 INFO 1687 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8024 (http) with context path ''
2020-02-20 11:56:53.948 INFO 1687 --- [ main] io.axoniq.axonserver.AxonServer : Started AxonServer in 21.35 seconds (JVM running for 22.317)
设置好之后,我们的测试应用程序将启动,连接到Axon Server,然后运行测试。
...handleCommand(): src = "getRunner", msg = "Hi there!".
...handleEvent(): msg = "getRunner says: Hi there!".
作为一种很好的措施,请运行几次,如果幸运的话,您实际上可能会看到多个事件在处理。 如果不是,请在命令发送与对“ SpringApplication.exit()”的调用之间添加“ Thread.sleep(10000)”,然后重试。 这项小测试表明,我们已经将客户端应用程序(由于它是Axon Server的客户端,所以我们将其命名)连接到Axon Server,并使用它来将命令发送到处理程序,之后,它将其发送回客户端进行处理。 处理程序发送了一个事件,该事件的路线相同,尽管在EventBus而不是CommandBus上。 此事件存储在Axon Server的事件存储中,并且事件处理程序最初连接时将重播所有事件。 实际上,如果您添加当前日期和时间,比如说简单地在消息中添加“ new Date()”,您就会发现事件实际上在它们进入时井井有条。
从Axon Framework的角度来看,有两种类型的事件处理器:订阅和跟踪。 订阅事件处理器将从订阅之时开始就订阅事件流。 跟踪事件处理器改为跟踪其自身在流中的进度,并且默认情况下将从请求重播存储中的所有事件开始。 您还可以将其视为推入事件(对于预订事件处理器)与自己拉出事件(对于跟踪事件处理器),并且框架中的实现实际上以这种方式工作。 要知道这种差异的两个最重要的应用是用于构建查询模型和用于事件源聚合。 这是因为您要确保在那里具有事件的完整历史记录。 我们不会在这里和现在进行详细介绍,但您可以在《 Axon参考指南》中的https://docs.axoniq.io上阅读有关内容。
在测试程序中,我们可以添加配置以选择事件处理器类型:
@Profile("subscribing")
@Component
public class SubscribingEventProcessorConfiguration {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Autowired
public void configure(EventProcessingConfigurer config) {
log.info("Setting using Subscribing event processors.");
config.usingSubscribingEventProcessors();
}
}
这样,如果您在激活Spring概要文件“订阅”的情况下启动应用程序,则只会看到一个事件被处理,该事件将在该运行中发送。 在没有此配置文件的情况下启动程序,您将获得默认模式,即跟踪,并且所有先前事件(包括在订阅模式下发送的事件)将再次出现。
既然我们已经准备好使用客户端,那么让我们对Axon Server方面进行更详细的了解,因为我们期望这里涉及消息处理和事件存储方面的事情。 在上一部分中,我们创建了一个名为“ axonserver-se”的目录,并将该目录的两个JAR文件放入其中。 现在,当它运行时,您将看到它已生成一个包含进程ID的“ PID”文件,以及一个包含数据库文件和事件存储在名为“ default”的目录中的“ data”目录。 目前,所有事件存储都包含一个文件用于事件存储,另一个文件用于快照。 这些文件看起来会很大,但是就它们已经预先分配以确保空间的可用性而言,它们是“稀疏的”,尽管它们仅包含一些事件。 我们可能期望但没有看到的是带有Axon Server输出副本的日志文件,这是我们要纠正的第一件事。
Axon Server是基于Spring Boot的应用程序,它使我们能够轻松添加日志记录设置。 默认属性文件的名称为“ axonserver.properties”,因此,如果我们使用该名称创建文件并将其放置在Axon Server运行的目录中,则将获取设置。 Spring Boot还会在当前工作目录中查看一个名为“ config”的目录,因此,如果要创建脚本设置,我们可以将具有通用设置的文件放在预期的工作目录中,同时保留“ config / axonserver.properties” ”文件进行自定义。 记录所需的最简单的属性是所有Spring-boot应用程序提供的属性:
logging.file=./axonserver.log
logging.file.max-history=10
logging.file.max-size=10MB
使用这些文件,在初始标题之后,日志记录将发送到“ axonserver.log”,它将最多保留10个文件,大小最大为10MiB,可以很好地进行清理。 接下来,让我们确定一些我们可能希望定义为“公共”的属性:
axoniq.axonserver.event.storage=./events
axoniq.axonserver.snapshot.storage=./event
s axoniq.axonserver.controldb-path=./data
axoniq.axonserver.pid-file-location=./events
logging.file=./data/axonserver.log
/var/log
下为Axon Server提供目录,并添加日志轮转设置,甚至使用某些功能。例如“ logging.config=logback-spring.xml
”,并将其用于更详细的设置。 axoniq.axonserver.replication.log-storage-folder=./log
通过这些设置,我们构造了Axon Server使用磁盘空间的方式并进行了设置,以便可以使用网络或云存储,从而可以将其部署在CI / CD管道中。 在该存储库中,我还将添加将在后台运行Axon Server的启动和关闭脚本。
由于我们绝对是“需要保护”的,因此我们将在服务器上设置访问控制和TLS。 访问控制将成为对REST和gRPC端点的请求所必需的令牌,并且UI将需要一个帐户。 此外,某些功能需要特定的角色,其中Enterprise Edition具有一组更为详尽的角色,并且还允许根据上下文指定角色。 从标准版开始,我们可以通过在属性文件中设置一个标志并提供令牌来启用访问控制:
axoniq.axonserver.accesscontrol.enabled=true
axoniq.axonserver.accesscontrol.token=my-token
您可以使用命令行工具(如uuidgen)生成随机令牌,这些令牌将用于身份验证。 现在,如果您使用这些启动Axon Server,则不仅需要为CLI工具指定令牌,而且即使我们尚未创建任何用户,UI也会突然要求您登录。 我们可以使用CLI工具轻松解决这一问题:
$ ./axonserver-cli.jar register-user -t my-token -u admin -p test -r ADMIN
$ ./axonserver-cli.jar users -t my-token
Name
admin
$
使用此功能,您可以再次登录。 此外,如果要使CLI的工作变得更轻松,可以创建一个名为“ security”的目录,并将令牌复制到其中的名为“ .token”的文件中。 CLI将检查相对于当前工作目录的目录和文件:
$ mkdir security
$ echo my-token > security/.token
$ chmod 400 security/.token && chmod 500 security
$ ./axonserver-cli.jar users
Name
Admin
$
在客户端,我们还需要指定令牌:
$ axonserver-quicktest-4.3-SNAPSHOT-exec.jar
2020-04-23 09:46:10.914 WARN 1438 --- [ main] o.a.a.c.AxonServerConnectionManager : Connecting to AxonServer node [localhost]:[8124] failed: PERMISSION_DENIED: No token for io.axoniq.axonserver.grpc.control.PlatformService/GetPlatformServer
**********************************************
* *
* !!! UNABLE TO CONNECT TO AXON SERVER !!! *
* *
* Are you sure it's running? *
* Don't have Axon Server yet? *
* Go to: https://axoniq.io/go-axon *
* *
**********************************************
To suppress this message, you can
- explicitly configure an AxonServer location,
- start with -Daxon.axonserver.suppressDownloadMessage=true
2020-04-23 09:46:10.943 WARN 1438 --- [.quicktester]-0] o.a.e.TrackingEventProcessor : Fetch Segments for Processor 'io.axoniq.testing.quicktester' failed: No connection to AxonServer available. Preparing for retry in 1s
2020-04-23 09:46:10.999 WARN 1438 --- [ main] o.a.c.gateway.DefaultCommandGateway : Command 'io.axoniq.testing.quicktester.msg.TestCommand' resulted in org.axonframework.axonserver.connector.command.AxonServerCommandDispatchException(No connection to AxonServer available)
$ AXON_AXONSERVER_TOKEN=my-token axonserver-quicktest-4.3-SNAPSHOT-exec.jar
2020-04-23 09:46:48.287 INFO 1524 --- [mandProcessor-0] i.a.testing.quicktester.TestHandler : handleCommand(): src = "QuickTesterApplication.getRunner", msg = "Hi there!".
2020-04-23 09:46:48.352 INFO 1524 --- [.quicktester]-0] i.a.testing.quicktester.TestHandler : handleEvent(): msg = "QuickTesterApplication.getRunner says: Hi there!".
$
鉴于此,下一步是添加TLS,只要我们在本地运行,就可以使用自签名证书来完成此操作。 我们可以使用“ openssl”工具集生成PEM格式的X509证书来保护gRPC连接,然后将密钥和证书打包为HTTP端口的PKCS12格式密钥库。 以下内容将:
$ cat > csr.cfg <
我们现在有:
要在Axon Server中配置它们,我们使用:
# SSL for the HTTP port
server.ssl.key-store-type=PKCS12
server.ssl.key-store=tls.p12
server.ssl.key-store-password=axonserver
server.ssl.key-alias=axonserver
security.require-ssl=true
# SSL enabled for gRPC
axoniq.axonserver.ssl.enabled=true
axoniq.axonserver.ssl.cert-chain-file=tls.crt
axoniq.axonserver.ssl.private-key-file=tls.key
两种方法之间的差异源于所使用的运行时支持:HTTP端口由Spring-boot使用其自己的“服务器”前缀属性提供,并且需要PKCS12密钥库。 而是使用Google的库来设置gRPC端口,这些库需要PEM编码的证书。 将这些添加到“ axonserver.properties”后,我们可以重新启动Axon Server,它现在应该宣布“使用SSL ENABLED初始化的配置和访问控制ENABLED的配置”。 在客户端,我们需要告诉它使用SSL,并且由于我们使用的是自签名证书,因此我们也必须传递该证书:
axon.axonserver.ssl-enabled=true
axon.axonserver.cert-file=tls.crt
请注意,我已将“ axonserver.megacorp.com”作为主机名添加到系统的“主机”文件中,以便其他应用程序可以找到它,并且名称与证书中的名称匹配。 这样,我们的快速测试人员可以使用TLS(删除时间戳等)进行连接:
...Connecting using TLS...
...Requesting connection details from axonserver.megacorp.com:8124
...Reusing existing channel
...Re-subscribing commands and queries
...Creating new command stream subscriber
...Worker assigned to segment Segment[0/0] for processing
...Using current Thread for last segment worker: TrackingSegmentWorker{processor=io.axoniq.testing.quicktester, segment=Segment[0/0]}
...Fetched token: null for segment: Segment[0/0]
...open stream: 0
...Shutdown state set for Processor 'io.axoniq.testing.quicktester'.
...Processor 'io.axoniq.testing.quicktester' awaiting termination...
...handleCommand(): src = "QuickTesterApplication.getRunner", msg = "Hi there!".
...handleEvent(): msg = "QuickTesterApplication.getRunner says: Hi there!".
...Released claim
...Worker for segment Segment[0/0] stopped.
...Closed instruction stream to [axonserver]
...Received completed from server.
从操作角度来看,运行Axon Server Enterprise Edition与Standard Edition并没有什么不同,但最主要的区别是:
在连接性方面,我们获得了一个额外的gRPC端口,用于集群中节点之间的通信,默认为端口8224。
Axon Server节点群集将为(基于Axon Framework的)客户端应用程序提供多个连接点,从而分担管理消息传递和事件存储的负担。 服务于特定上下文的所有节点都维护完整副本,并由“上下文领导者”控制分布式事务。 根据RAFT协议 ,领导者由选举决定。 在本文中,我们不会深入研究RAFT及其工作原理,但重要的结果与这些选举有关:节点需要能够赢得选举,或者至少要获得明显多数的支持。 因此,尽管Axon Server群集不需要具有奇数个节点,但是每个单独的上下文都需要,以排除在选举中出现平局的可能性。 这也适用于名为“ _admin”的内部上下文,该内部上下文由管理节点使用并存储集群结构数据。 结果,大多数集群将具有奇数个节点,并且只要大多数(对于特定上下文)正在响应并存储事件,它们就将保持正常运行。
Axon Server群集中的节点在上下文中可以具有不同的角色:
从备份策略的角度来看,活动备份可用于保留异地副本,该副本始终是最新的。 如果您有两个活动的备份节点,则可以在其中一个节点上停止Axon Server来对事件存储文件进行备份,而另一个将继续接收更新。 被动备份节点提供了一种替代策略,上下文领导者将异步发送更新。 虽然这不能保证您始终保持最新状态,但最终还是会显示事件,即使使用单个备份实例,您也可以关闭Axon Server并进行文件备份,而不会影响群集的可用性。 当它重新联机后,领导者将立即开始发送新数据。
支持多个上下文和不同角色(每个节点可设置每个角色)的结果是,这些单个节点在提供给客户端应用程序的服务上可能有很大的不同。 在那种情况下,增加节点数不会对所有上下文产生相同的影响:尽管消息传递负载将由支持上下文的所有节点共享,但事件存储区必须将数据分发到其他节点,并且大多数需要在客户端可以继续之前确认存储。 要记住的另一件事是,“ ACTIVE_BACKUP”和“ PASSIVE_BACKUP”角色具有相当(特定于筏)的含义,即使这些名称可能暗示了与高可用性世界不同的解释。 通常,Axon Server节点的角色不会仅仅为了解决可用性问题而更改。 只要上下文中有大多数节点可用,群集就可以继续运行,但是如果对于“ _admin”上下文丢失了大多数节点,则群集配置更改也不能提交。
对于本地运行的集群,我们需要对“公共”属性集进行一些补充,其中最重要的是与集群初始化有关:当节点启动时,它尚不知道它是否将成为新的核心。群集,或将其添加到具有特定角色的现有群集中。 因此,如果启动Axon Server EE并立即开始连接客户端应用程序,则会收到一条错误消息,指示没有可用的初始化群集。 如果只想将所有节点注册为“ PRIMARY”的集群,则可以添加自动集群属性:
axoniq.axonserver.autocluster.first=axonserver-1.megacorp.com
axoniq.axonserver.autocluster.contexts=_admin,default
添加这些参数后,其主机名和群集内部端口与“ first”设置匹配的节点(当然未指定默认端口为8224)将在需要时初始化“ default”和“ _admin”上下文。 其他节点将使用指定的主机名和端口向群集注册自己,并请求将其添加到给定的上下文中。 在单个主机上启动多节点群集的典型解决方案是使用端口属性,使它们彼此相邻。 然后,第二个节点将使用:
server.port=8025
axoniq.axonserver.port=8125
axoniq.axonserver.internal-port=8225
axoniq.axonserver.name=axonserver-2
axoniq.axonserver.hostname=localhost
第三个可以使用8026、8126和8226。在下一部分中,我们将研究Docker部署,并且还将自定义用于集群内部通信的主机名。
围绕启用访问控制可能需要一些解释,尤其是从客户端的角度。 如上所述,结果是客户端应用程序在连接到Axon Server时必须提供令牌。 此令牌用于HTTP和gRPC连接,并且Axon Server为此使用了一个名为“ AxonIQ-Access-Token”的自定义HTTP标头。 对于Standard Edition,两种连接类型都有一个令牌,而Enterprise Edition维护一个应用程序列表,并为每个连接生成UUID作为令牌。 群集内部端口使用另一个令牌,需要使用“ axoniq.axonserver.internal-token
”在属性文件中对其进行配置。
可能的另一种身份验证是使用用户名和密码,该用户名和密码仅适用于HTTP端口。 通常用于UI,如果启用,它会显示登录屏幕,但也可以用于使用BASIC身份验证的REST调用:
$ curl -u admin:test http://localhost:8024/v1/public/users
[{"userName":"admin","password":null,"roles":["ADMIN@*"]}]
$
CLI还是一种客户端应用程序,但只能通过REST API。 如前所述,启用访问控制后,您可以使用令牌进行连接,但是如果在Axon Server EE上尝试使用此令牌,则会注意到这条路已经关闭。 原因是将单个系统范围的令牌替换为应用程序专用的令牌。 实际上,CLI仍然有一个令牌,但是它现在是每个节点本地的,由Axon Server生成,并且相对于节点的工作目录,它存储在名为“ security / .token”的文件中。 在查看向CLI提供令牌时,我们也遇到了该文件。 在第二部分中,我们将回顾Docker和Kubernetes,并为其介绍一个秘密。
至此,本系列的第一部分在运行Axon Server上结束了。 在第二部分中,我们将转向Docker,docker-compose和Kubernetes,并从它们为我们带来的有关卷管理的差异中获得乐趣。 下次见!
是AxonIQ的高级软件架构师和开发人员,拥有超过25年的经验,最近几年主要是Java。 他是互联网访问基金会(Internet Access Foundation)的联合创始人,该基金会是一个非营利性组织,有助于荷兰北部和东部的互联网解锁。 他从90年代作为开发人员开始,后来转到软件体系结构,并在一家保险公司担任战略顾问和企业架构师一职。 现在,在AxonIQ(Axon Framework和Axon Server的幕后公司),他致力于产品的开发,重点是软件体系结构和DevOps,并为客户提供帮助。
翻译自: https://www.infoq.com/articles/axon-server-cqrs-event-sourcing-java/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1
ddd-cqrs-axon