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


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

对于事件 , 我们不能使用相同的主键 , 因为对于给定的组合(practiceId , url)可能有多个事件 , 而(practiceId , EventType)也不太合适 , 因为单个条目可能有多个类型为 UPDATED 的事件 。 很自然地 , 我们就选择使用(practiceId , EventId)作为主键 。 我们选择用 GUID(保证惟一性)和时间戳(允许按 EventId 排序)的组合作为 Event ID 。
【InfoQ】Zocdoc 的事件驱动架构实践
本文插图

然后 , 我们使用 AWS Lambda 创建触发器 , 响应事件表 DynamoDB 流中的事件 。 一个触发器处理这些事件并更新投影表以反映最新的状态 。 另一个触发器将这些事件发布到 SNS 主题 , 以流的形式发送到订阅服务 。
接收触发器事件:
{"Records":[{"EventSourceArn":"arn:aws:dynamodb:us-east-1:1234:table/practice-website-events/stream/2019-09-05T19:28:23.205" ,"AwsRegion":"us-east-1" ,"Dynamodb":{"ApproximateCreationDateTime":"2019-12-19T21:05:23Z" ,"Keys":{"PracticeId":{"S":"noj6XuZmMU6aq-7Pg9m5sB"} ,"EventId":{"S":"2019-10-02T16:51:39Z_1234567-890a-bcde-fghi-123456789012"}} ,"NewImage":{"DomainType":{"S":"ProviderOwned"} ,"Version":{"S":"1.0.0"} ,"PracticeId":{"S":"noj6XuZmMU6aq-7Pg9m5sB"} ,"EventId":{"S":"2019-10-02T16:51:39Z_1234567-890a-bcde-fghi-123456789012"} ,"TimestampUtc":{"S":"2019-06-13T17:05:00Z"} ,"Url":{"S":"realdoctorwebsite.com"} ,"Name":{"S":"PracticeWebsiteAdded"} ,"InitiatedBy":{"S":"johndoe@mail.com"}} ,"OldImage":{} ,"SequenceNumber":"499848100000000006586459546" ,"SizeBytes":307 ,"StreamViewType":{"Value":"NEW_IMAGE"}} ,"EventID":"f00000ffe0000e448db06a1abcdefghij" ,"EventName":{"Value":"INSERT"} ,"EventSource":"aws:dynamodb" ,"EventVersion":"1.1" ,"UserIdentity":null}]}
【InfoQ】Zocdoc 的事件驱动架构实践
本文插图
优缺点
事件源有几个好处 。 首先 , 它提供了可靠的数据审计日志 , 是审计、遵从性、数据治理和数据分析系统的最佳选择 。 它让实现临时查询成为可能 , 从而可以确定业务实体在任何时间点的状态 。 如前所述 , 它还解决了实现事件驱动的架构原子性和数据一致性中的一些关键问题 。
另外 , 因为它持久化事件而不是域对象 , 所以它基本上避免了对象 - 关系阻抗不匹配的问题 。 当与 CQRS 相结合时 , 它提供了一种方便的方式来独立地扩展读和写 。
不过 , 它也有一些不足:
编程模型更复杂 , 学习曲线更高 。 我们团队花了相当多的时间来适应建模和持久化事件的思想 , 那不同于 CRUD 应用程序中的实体状态 。
你的应用程序现在必须处理最终一致的数据 。 使用 CQRS , 如果应用程序从尚未更新的投影中读取数据 , 那么它可能会遇到过期数据 。 这种情况可能会发生 , 例如 , 如果更新投影的 Lambda 出现了冷启动问题 , 这是我们自己遇到过的问题 。 此外 , 我们提供的医生网站的用户界面在一个旧网页上 , 每次编辑后都会重新加载 , 使得最终一致性有时有点太……最终 。 作为一个解决方案 , 我们设置了一个 Lambda 预热 , 它每 5 分钟调用一次我们的 Lambda , 这样就减了少启动时间 , 将延迟问题减少到一个很小的可以忽略的百分比 。
你必须为建模的域事件选择正确的粒度级别 。 一个事件粒度太大(比如 PracticeWebsiteEvent) , 所有的消费者都必须监听这些事件 , 并弄清楚它是否会影响它们 。 如果一个事件粒度太小(如 PracticeWebsiteUrlChanged& PracticeWebsiteDomainTypeChanged) , 消费者就必须监听多个事件 , 以便在采取行动之前将它们组合起来 。 此外 , 事件永远不会被删除 。 即使发生了应用程序级的故障或不一致 , 你也要运行补偿事务 , 创建更多事件 。 如果事件范围太小 , 还会产生大量的数据 。 通常 , 事件粒度是一个需要解决的棘手问题 , 需要投入大量时间来理解域并与域专家进行交谈 。 新近的热门技术事件风暴可以帮助使用更多领域驱动的设计方法解决这个问题 。


推荐阅读