如何正确理解事件溯源架构模式?
在微服务架构盛行的当下,DDD(领域驱动设计)也得到了崭新的发展。同时,随着DDD的不断发展,也诞生了一些新的设计思想和开发模式,今天要介绍的事件溯源是其中具有代表性的一种模式。
事件溯源模式是DDD领域中一种新的架构模式,专门用来处理应用程序中的状态变化。事件溯源模式的实现方式与传统的应用程序开发方法有很大不同,表现在对应用程序状态的存储和检索过程。
事件溯源模式设计理念
在对事件溯源进行详细展开之前,我们先来回顾一下传统应用程序中关于“状态”的维护方法。让我们先从一个典型的应用场景开发说起,在日常开发过程中,开发人员经常需要处理与用户相关的操作,例如用户的创建、密码修改、地址更新、权限变更等。

在这个场景中,代表用户概念的User就是核心的领域对象。那么,基于该领域对象,你会怎么处理对应的业务操作呢?
域溯源模式
在传统应用程序中,我们使用常见的数据库(如关系数据库、NoSQL数据库)来创建、修改或查询业务领域对象的状态。另一方面,我们通过把这些状态持久化到一定的数据存储媒介之后,也可以通过领域事件的形式把事件发布到消息中间件。基于以上分析,我们认为这种状态变化的过程是领域对象来驱动的,也就是说只有领域对象主动更新自己的状态并生成领域事件,我们才能获取到应用程序状态的变化。

反过来讲,如果领域对象没有主动更新数据或生成任何领域事件,那么我们也就无法感知到应用程序的状态已经发生了变化。从状态的源头(Source)来讲,我们认为这种处理方式是一种域溯源(Domain Sourcing)的方式。
基于域溯源模式,我们在对领域对象执行一定操作之后会把对象的最新状态持久化到数据库中,也就是说数据库中的数据反映了对象当前最新的状态。

从上图中,我们可以看到针对用户状态更新的各个操作,在数据库中只会存在一条数据记录,反应了该用户的当前的最新信息。至于各个操作的具体执行过程,通常可能会设计一张操作日志表。
显然,域溯源的实现过程比较简单,也符合开发人员的直观认知,因为它们使用了存储和查询应用程序状态的传统实现方案。只要能够正确保存领域对象,我们就能获取应用程序最新的状态信息。
事件溯源模式
接下来,我们要引入的事件溯源机制则采用了另一种设计理念。与域溯源模式不同,事件溯源模式只关注于处理领域对象上发生的领域事件。也就是说,事件溯源模式不是保存对象的最新状态,而是保存这个对象所经历的每个事件,所有由对象产生的事件会按照时间先后顺序有序的存放在数据库中。

当我们需要这个对象的最新状态时,只要先创建一个空的对象,然后把和该对象相关的所有事件按照发生的先后顺序从先到后全部执行一遍。这个过程就是事件溯源,如下图所示。

从上图中可以看到,事件溯源的核心设计思想在于:不保存对象的最新状态,而是保存导致对象状态发生变化的所有事件,这样就可以通过对这些事件进行溯源得到对象的最新状态。
显然,基于事件溯源的设计思想,一个事件就是表示一个事实,事实是不能被磨灭或修改的,所以事件本身是不可修改的(Immutable),我们只能执行新增和查询操作。而对比域溯源模式和事件溯源模式,我们不难发现有两个差异点。

以上两点构成了事件溯源模式的能够得以实施的前置条件。当我们把应用程序的状态变更全部进行持久化之后,接下来我们就可以真正实现所谓的溯源操作了。
实现事件溯源模式
理解了事件溯源的基本概念之后,接下来我们讨论具体的实现过程。
事件存储
实现事件溯源的第一步是确保领域事件都得到持久化。在事件溯源模式中,存储事件的组件被称为事件存储器(Event Store)。下图展示了采用事件溯源机制下各组件的交互示意图。

结合上图,我们来举一个具体的示例。如果我们正在设计一个典型的用户应用程序,那么基于上图中的交互过程,在用户更新密码时,就会生成一个PasswordUpdatedEvent这样一个领域事件。我们只需要对这个领域事件进行持久化,而不需要保存User这个领域对象。PasswordUpdatedEvent事件将被持久化到一个专门构建的事件存储器UserEventStore中。
事件回放
接下来,假设需要获取用户应用程序中User这个领域对象的最新状态,那么基于事件溯源机制,我们将采用一种比较特殊的实现方式。首先,我们会从事件存储器中加载User对象上已经发生的所有领域事件。然后,我们在User对象上依次执行所有领域事件所包含的状态变化信息,从而确保User对象达到当前的最新状态。整个执行过程如下图所示。

有时候,我们把在User对象上重新执行领域事件的这个过程成称为事件回放(Event Replay)。可以看到,基于事件溯源机制,我们采用的是一种纯事件驱动的实现方法。
整合DDD和事件溯源
在DDD中,我们把某一个独立的业务模块划分成一个限界上下文。在每个限界上下文中势必会存在聚合对象。生成领域事件的一般就是这些聚合对象,而聚合的创建常见是在应用服务中。因此,DDD和事件溯源机制的整合效果如下图所示。
另一方面,领域事件一般都具有传播性,如果我们想要把领域事件传播出去,那么可以引入一个事件路由器来实现这一目标。

结合前面介绍的案例场景,那么User就是聚合对象,而我们可以把对应的事件路由器命名为UserEventRouter。PasswordUpdatedEvent这个领域事件被我们发送到事先已经设计好的消息路由通道,从而供其他限界上下文中的事件处理器进行消费。这个过程中,领域事件是否被持久化实际上是没有任何约束的。

在应用程序开发过程中,我们通常使用关系型数据库来维护业务对象的最新状态。这是一种比较传统的实现方案,但如果我们想要进一步明确该业务对象所执行的各种操作,问题就变得没有那么简单。而今天介绍的事件溯源机制为我们提供了另一种完全不同的实现思路。在事件溯源机制中,我们保存的不是数据状态本身,而是引起这些状态发生的各种事件。通过在业务对象上依次执行这些事件,我们就能够获取该业务对象所经历的各种操作过程和结果。在日常开发过程中,针对如何实现事件溯源,我们需要考虑事件的存储、事件的回放等技术组件,同时也需要考虑DDD和事件溯源的整合过程,因为时间溯源的主要应用方式就是在DDD应用程序中。
相关文章:
如何正确理解事件溯源架构模式?
在微服务架构盛行的当下,DDD(领域驱动设计)也得到了崭新的发展。同时,随着DDD的不断发展,也诞生了一些新的设计思想和开发模式,今天要介绍的事件溯源是其中具有代表性的一种模式。 事件溯源模式是DDD领域中…...
【漏洞复现】电信网关配置管理系统 rewrite.php 文件上传漏洞
0x01 产品简介 中国电信集团有限公司(英文名称"China Telecom”、简称“"中国电信”)成立于2000年9月,是中国特大型国有通信企业、上海世博会全球合作伙伴。电信网关配置管理系统是一个用于管理和配置电信网络中网关设备的软件系统。它可以帮助网络管理员…...
线性调整率:LINE REGULATION详解
目录 一、概述 二、 举例 一、概述 LDO(低压差线性稳压器)的LINE REGULATION(线路调整或线性调整)参数是一个衡量稳压器输出稳定性的重要指标。它反映了LDO输出电压对输入电压变化的响应程度。 当输入电压在其规定的工作范围内变…...
Workfine默认首页功能详解
一、基本介绍 Workfine V6.3推出了默认的用户首页功能,这样用户在登入系统后就可以通过默认的首页栏进行一些业务操作。第一版的用户首页功能布局了审批,制单,业务导航,便捷入口,消息和预警六大块内容,后续…...
CSAPP Lab07——Malloc Lab完成思路
等不到天黑 烟火不会太完美 回忆烧成灰 还是等不到结尾 ——她说 完整代码见:CSAPP/malloclab-handout at main SnowLegend-star/CSAPP (github.com) Malloc Lab 按照惯例,我先是上来就把mm.c编译了一番,结果产生如下报错。搜索过后看样子应…...
简单、免费、无广告的高性能多线程文件下载工具
一、简介 1、它是一款免费、无广告的高性能多线程文件下载工具。它界面简洁,简单好用,压缩包大小仅有 0.7MB,目前仅支持 Windows 平台。 2、使用方法:点击程序左上角的【】按钮,将需要的链接输入进去后点击【下载】即…...
【退役之重学 SQL】什么是笛卡尔积
一、初识笛卡尔积 概念: 笛卡尔积是指在关系型数据库中,两个表进行 join 操作时,没有指定任何条件,导致生成的结果集,是两个表中所有行的组合。 简单来说: 笛卡尔积是两个表的乘积,结果集中的每…...
Vue3禁止 H5 界面放大与缩小功能
Vue3禁止 H5 界面放大与缩小功能 一、前言1.第一步2.第二部3.总结 一、前言 当涉及到禁止 H5 界面的放大与缩小功能时,Vue 3 提供了一种方便的方式来处理。我们可以使用 <script setup> 语法,将相关代码添加到 App.vue 组件中,以确保在…...
上位机图像处理和嵌入式模块部署(f407 mcu中tf卡读写和fatfs挂载)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 很早之前,个人对tf卡并不是很重视,觉得它就是一个存储工具而已。后来在移植v3s芯片的时候,才发现很多的soc其实…...
汽车识别项目
窗口设计 这里的代码放在py文件最前面或者最后面都无所谓 # 创建主窗口 window tk.Tk() window.title("图像目标检测系统") window.geometry(1000x650) # 设置窗口大小# 创建背景画布并使用grid布局管理器 canvas_background tk.Canvas(window, width1000, height…...
【面试题-012】什么是Spring 它有哪些优势
文章目录 Spring有哪些优势有哪些优势Spring和Springboot区别在 Spring 框架中,什么是AOP核心概念应用场景 Spring有哪些通知类型 Spring 是一个开源的 Java 平台,由 Rod Johnson 创建,用于简化企业级 Java 应用程序的开发。它于 2003 年首次…...
ImageButton src图片会照成内存泄露吗 会使native内存增加吗?
在Android开发中,ImageButton 是用来显示按钮的视图组件,它通常用于显示图标或图片。对于ImageButton使用的src属性(即按钮上的图片)通常不会导致内存泄漏,但是有几种情况可能会导致内存问题: 1. **不正确…...
负载均衡与容错性:集群模式在分布式系统中的应用
本文作者:小米,一个热爱技术分享的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! 大家好,我是小米,一个热爱分享技术的29岁程序员。今天我们来聊一聊分布式系统中的一个重要概念:集群(Cluster)模式。相信很多朋友在日常开发…...
【UE5.1 角色练习】09-物体抬升、抛出技能 - part1
前言 在上一篇(【UE5.1 角色练习】08-传送技能)的基础上继续实现控制物体抬升、抛出的功能。 效果 步骤 一、准备技能动画 1. 在项目设置中新建一个操作映射,这里命名为“Skill_GravityControl”,用按键4触发 2. 通过IK重定向…...
最大的游戏交流社区Steam服务器意外宕机 玩家服务受影响
易采游戏网6月3日消息:众多Steam游戏玩家报告称,他们无法访问Steam平台上的个人资料、好友列表和社区市场等服务。同时,社区的讨论功能也无法正常使用。经过第三方网站SteamDB的确认,,这一现象是由于Steam社区服务器突…...
如何手动批准内核扩展 Tuxera NTFS for mac内核扩展需要批准 内核扩展怎么打开
在了解如何手动批准内核扩展之前,我们应该先了解什么叫做内核扩展。内核扩展又被称为KEXT,通过它可以实现macOS系统与软件组件之间的交互,例如磁盘管理、任务管理和内存管理等等。 kext 是内核扩展(Kernel Extension)…...
ffmpeg常用命令
推流 ffmpeg -re -stream_loop -1 -i in.flv -c copy -f flv outurl 推流追加时间戳 ffmpeg -stream_loop -1 -re -i move.flv -vf "settbAVTB,setptstrunc(PTS/1K)*1Kst(1,trunc(RTCTIME/1K))-1K*trunc(ld(1)/1K),drawtextfontfilearial.ttf:text%{localtime}.%{eif\:…...
在MongoDB中,您可以通过以下步骤来创建账号密码,并限制其在特定数据库上的访问权限
在MongoDB中,您可以通过以下步骤来创建账号密码,并限制其在特定数据库上的访问权限: 连接到MongoDB数据库: 使用MongoDB的客户端(如mongo shell或者MongoDB Compass)连接到MongoDB服务器。 切换到admin数…...
前端JS必用工具【js-tool-big-box】学习,检测密码强度
js-tool-big-box 前端工具库,实用的公共方法越来越多了,这一小节,我们带来的是检测密码强度。 我们在日常开发中,为了便于测试,自己总是想一个简单的密码,赶紧输入。但到了正式环境,我们都应该…...
PHP精度处理
一、问题缘由 PHP 服务接收前端传过来的单价(字符串形式)和数量,把单价转成分(单价*100),然后传给下游的 Golang 服务,不过最后从两个服务日志中发现金额相差 1。 以下为前端传的 {"amount": 4,"price": "9.2&qu…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...
