我在AWS Lambda上运行了一个基于微服务的应用程序。其中两个微服务,即最关键的微服务,使用事件源/ cqrs。
背景:(这也是我整理自己的想法)
我使用this library并在DynamoDB中存储事件并在AWS S3中进行投影。
写入部分就像一个魅力:每个命令调用从DynamoDB加载聚合的当前状态(通过处理程序运行事件和/或加载缓存聚合),它决定接受或拒绝基于某些命令的命令然后,业务逻辑使用KeyConditionExpression: 'aggregateId = :a AND version >= :v'
写入DynamoDB,其中版本是为该聚合处理的事件计数。如果发生冲突,则写入失败。对我来说似乎是一个很好的系统!
然后将每个事件广播到SNS(主题名称是服务名称),以便其他服务可以根据需要对事件做出反应。
我真正挣扎的部分是阅读。预测存储在S3中,并使用为每个事件源处理的最后一个commitId进行标记。当读取查询进入时,它会从S3 (对于所有聚合)加载整个投影状态,查询所有较新事件的事件源,计算最新状态(同样,对所有聚合 - 并写入更新的对象(如果它是更新的),并根据查询参数返回状态的相关部分。
我的问题:(或其中一个)
我认为我做错了。
我的大多数预测只按重要属性分组ID,因此文件保持相对较小。但我还需要一种方法来检索单个聚合。使用投影看起来很疯狂,因为我需要每次加载整个状态(即每个预测的聚合)将新事件应用于该状态,然后检索我想要的记录(它可能甚至没有改变)。
这就是我现在所做的,它表现良好(<100k记录),但我无法想象它会持续更长时间。
另一个问题是查询。我需要为我需要查询的每个属性构建一个投影映射值来匹配aggregateIds!必须有更好的方法!
无论我如何看待这个问题,预测总是需要整个当前状态+任何新事件才能返回一条没有改变的记录。
答案 0 :(得分:7)
我认为我做错了。
我也这么认为;听起来你的查询与你的预测有关
是的,这听起来像是一团糟。或者更具体地说,这听起来像是查询正在触发投影要完成的工作。当读取查询进入时,它会从S3加载整个投影状态(对于所有聚合),查询所有较新事件的事件源,计算最新状态
如果您可以将查询与投影分离,那么事情会变得更容易。基本的想法是,您的查询不会描述当前的状态,它们描述了上次投影运行时的状态 。
同样的想法,不同的拼写:您从S3中缓存的文档中回答查询。检测到新事件时,运行预测,根据需要加载新数据,计算新文档,并替换缓存中的条目。
我想到了一个三角形
三角形的每条腿与其他部分异步运行。
我建议您从查询中反向工作 - 您需要哪些文档来支持每个查询?您必须击败的延迟目标是什么?然后你开始平衡权衡 - 对于这个新的查询,我是从现有文档创建结果,还是我需要一个用更精细的粒子构建的新文档?
如果我理解正确,我应该在事件进入时触发投影更新,而不是在进行查询时聚合。这使我无法在每个查询中查询事件存储中的新事件
是的,而且......事件只是触发的一种方式;您还可以通过时钟触发投影过程(每15分钟检查一次,看看我们是否需要更新)或者是人工操作员的心血来潮(嗯,看起来您的账户余额过时了,让我试着更新一下为了你)。不止一种方法,你可以混合和匹配策略。
在更新投影时和加载单个聚合时,我仍然需要加载整个状态。
不一定。没有规则说您不能使用以前缓存的表示作为起点,然后从记录簿中仅提取您需要的更改。
例如,假设您正在构建一个结合了聚合A{id:7}
和B{id:9}
的视图。您获取缓存的副本,并查看其元数据(您将其放在上一次写入的位置)并在其中查找内容,如metadata:{A:{id:7, version:21}, B:{id:9, version:19}}
。现在,您只需要在上次使用的事件之后加载事件,在内存中更新本地副本,更新元数据的本地副本,并将批次推送到缓存。
答案 1 :(得分:6)
我不熟悉您的技术基础架构,但我实施预测的方式如下:
每个域事件都有一个全局序列号,跨越所有聚合根。投影是具有任意名称的读取模型,并且该最终处理位置由该全局序列号表示。我可以随时添加一个新的投影,以及它的事件处理程序,它将从位置0开始。我可以随时清除投影并将位置设置为0.我也可以使用添加新的组合将替换现有版本的投影,即使需要数天也会进行构建,然后删除旧版本。
有一项服务可以监控投影并使用事件存储,就像一个队列。投影服务在当前位置后检查具有全局ID 的事件,并将这些事件交给处理程序,然后更新位置。这是您的投影甚至可以过滤事件类型以提高性能的地方。
这是基本的想法。您的预测就是您查询的内容。一旦投影赶上了&#34; head&#34;事件存储中的事件将被投射到投影中。
这将如何转化为您的技术空间我不太确定。我有一个名为Shuttle.Recall的实验正在进行C#,如果您想了解一些想法。