假设我们有一个汇总User
,其中UserPortraitImage
和Contract
为PDF文件。我想将文件存储在专用的基于文档的存储中,并在事件中保存与进程相关的数据(带有BLOB数据的链接)。
但是,当我必须存储文件并存储新事件时,如何避免两阶段提交?
首先我会存储文件然后存储事件;如果第一个事务失败则无关紧要,命令失败。如果第二个事务失败,即使我们在商店中生成了一些死文件也没关系,命令失败;我们甚至可以应用回滚。 但是还有其他问题吗?
接下来的问题是如何设计聚合和事件。如果聚合只保存对BLOB存储的引用,那么调用SignUp
命令后的进程是什么?
SignUpCommand
==>存储文档(UserPortraitImage
和Contract
)==>使用给定的BLOB存储引用创建新的User
聚合并存储它?
是否有更好的设计可以减轻知道BLOB数据保存在另一个商店的总量?谁负责存储BLOB数据并将引用转发给聚合?
答案 0 :(得分:3)
听起来你正在使用类似于AtomPub media-entry/media-link-entry对的东西。 blob将进入您的数据存储,元数据将被复制到聚合历史记录
但是,当我必须存储文件并存储新事件时,如何避免两阶段提交?
在实践中,你可能不会。
也就是说,如果blob存储和聚合存储碰巧是同一个数据库,那么您可以在同一个事务中更新它们。这两家商店结合在一起,并为您选择的存储添加了一些非常强大的限制,但这是可行的。
另一种可能性是你接受你所做的两个变化是彼此隔离的,因此在一段时间内两个商店彼此不一致。
在第二种情况下,the saga pattern正是您所寻找的,而且正是您所描述的;如果第二个操作失败,则将第一个操作与补偿操作配对。所以"手册"回滚。
或者不是 - 从某种意义上说,git对象数据库使用两阶段提交;一个对象被复制到对象存储中,然后树得到更新,然后提交...垃圾收集后来丢弃你不需要的对象。
谁负责存储BLOB数据并将引用转发给聚合?
嗯,最终这是一个基础设施问题;您的模型是否确实需要与文档进行交互,或者只是带有可以在以后兑换的claim check?
答案 1 :(得分:1)
首先我会存储文件然后存储事件;如果是第一个 事务失败没关系,命令失败。如果是第二个 交易失败即使我们生成了一些也没关系 在商店中死文件,命令失败;我们甚至可以申请一个 回滚。但是还有其他问题吗?
除了浪费的磁盘空间,我不能想到。当我想避免分布式事务或者它们在两种类型的数据存储中不可用时,我通常会这样做。通常,两个操作中的一个不太重要,即使主操作稍后失败,您也可以让它完成。
在异常处理期间,可以通过@VoiceOfUnreason解释,在异常处理过程中或作为Saga的一部分来完成清理拙劣的尝试。
SignUpCommand ==>存储文件(UserPortraitImage和Contract)==> 使用给定的BLOB存储引用创建新的User聚合 存储它?
是。通常,应用程序层组件(在您的情况下为命令处理程序)充当不同数据存储之间的协调器,并在与另一个或与域通信之前从一个存储中获取所需的全部内容。