【InfoQ】Zocdoc 的事件驱动架构实践( 三 )


【InfoQ】Zocdoc 的事件驱动架构实践
本文插图

在 Zocdoc , 我们大量使用了 DynamoDB , 因为它是一个完全托管的无服务器 NoSQL 数据库 , 提供完美的可伸缩性和性能 , 并且免费提供高达 25GB 的存储空间 。 考虑到 DynamoDB 流的便利 , 它是基于 CDC 的事件驱动服务的完美选择 。 设置完成后 , 这些流可以调用其他 AWS 服务 , 如简单通知服务(SNS)或 Lambda 。
然后 , 事件可以扇出到任意数量的订阅者 。 一个非常有用的 AWS 无服务器模式(我们在 Zocdoc 大量使用)是使用 SNS 将事件发布到一个或多个 SQS 队列 。 这为我们提供了一种可靠的方法 , 可以通过基本可靠的传递异步发送事件 , 并且还提供了消息限流的好处 。
不过 , 这种方法有一个缺点 。 虽然我们在事件流中发布数据存储的所有更改 , 但数据存储本身只捕获数据的最新状态 。 结果 , 我们从设计上就丢了数据 。 不可重放 , 不能审计 , 也没有方法能查询特定历史点的数据状态 。 这对我们来说是一个明显的限制 , 因为我们不仅需要动态地确定预约归属 , 而且还需要能够追溯(以防需要修正) 。
因此 , 对我们来说 , 了解执行更改的时间和详细信息以及用户标识是非常重要的 。 我们还考虑把生成的 CDC 事件(如插入、更新、删除 , 并做少量的转换)存储到我们的 Redshift 分析仓库中 , 但这意味着我们的 OLAP 数据会背离我们的 OLTP 数据 , 在一个有很多步骤的数据工程管道里 , 如果数据在其中的任何一个步骤里丢了 , 都没有办法恢复 。
以事件为事实来源
【【InfoQ】Zocdoc 的事件驱动架构实践】这把我们导向了一个越来越流行的模式 , 该模式已经成为传统 CRUD 应用程序的替代方案——事件源 。
事件源
事件源是将实体状态的更改建模为不可变的事件“日志” 。 然后 , 这个“事件日志”或“事件存储”就成了事实的来源 , 而系统状态纯粹是从它派生出来的 。 由于保存事件是一个操作 , 因此 , 该模式本质上是原子性的 , 并且最小化了数据更新冲突的可能 。 这里的一个事件代表了一些在该领域发生的某件事 , 如:PracticeWebsiteAdded、PracticeWebsiteModified、PracticeWebsiteStatusChanged、AppointmentConfirmed 等 。
通常 , 事件存储将这些事件发布到事件流(或消息代理) , 使用者订阅这些事件并根据需要处理它们 。 这可以使用前面提到的 CDC 来实现 。 如果事件源中的事件语义级别太低 , 可以考虑发布更高级的域事件 , 而不是使用另外的事件处理程序 。
CQRS
不幸的是 , 一旦应用了事件源模式 , 就无法再轻松地查询数据了 。 这将我们引向一个不同但密切相关的模式 , 称为命令查询责任隔离或 CQRS 。 CQRS 的思想是隔离命令(写请求)和查询(读请求)之间的职责 , 并在应用程序中以不同的方式处理它们 。 你甚至可以分割数据存储 , 创建单独的读和写数据存储 , 从而实现更好的隔离和独立扩展 。
因此 , 在一个典型的事件源 + CQRS 应用程序中 , 最终你会得到一个“事件”表(命令会以追加的方式写入)和“投影”表(也称为“状态表”或“物化视图”或“持久化读取模式”) , 通过一个灵活的模式来支持快速高效的查询 。 服务通过订阅由事件日志发布的域事件来更新该投影 。 在 Zocdoc , 我们过去已经成功地实现了这些类型的投影存储 , 例如我们的 wasabi 基础设施 。
【InfoQ】Zocdoc 的事件驱动架构实践
本文插图

在 AWS 的生态下
结合 DynamoDB 灵活的基于键值的数据模型和条目级活动的流式推送 , 我们能够将事件源 + CQRS 应用到我们的 practice-website-service 。 最后 , 我们得到两个不同的 DynamoDB 表 , 它们支持不同的查询模式 , 并有一个 DynamoDB 流将这两个表连接起来 。 对于状态表 , 我们的模式相当简单 。 我们使用 practiceId 作为散列(分区)键 , 使用 Url 作为范围(排序)键 , 因为我们必须保证(practiceId , Url)的值唯一 。


推荐阅读