我正在研究CQRS/ES架构。我们将多个异步投影并行运行到读取存储中,因为某些投影可能比其他投影慢得多,我们希望对于更快的投影与写入端保持更同步。
我试图了解如何生成读取模型的方法以及这可能涉及多少数据重复的问题。以订单和订单项为例,一个订单可以有多个订单项,每个订单项都有一个名称。订单和订单项是独立的聚合。
我可以尝试以更标准化的方式保存读取模型,其中为每个订单和订单项创建一个实体或文档,然后进行引用 - 或者我可能希望以更非标准化的方式保存它,其中包含订单并包含其订单项。 标准化
使用更规范化的格式可以让单个投影处理影响/影响物品和订单并更新相应对象的事件。这也意味着任何对物品名称的更改都会影响所有订单。例如,客户可能会收到与相应发票所列商品不同的交货单(因此显然该模型可能不够好,会导致与非规范化相同的问题...)。 非规范化
但是,反规范化需要一些源代码,可以查找当前相关数据,例如项目。这意味着我要么必须传输我可能需要的所有信息,要么必须跟踪为我的非规范化提取的数据。这也意味着我可能需要针对每个投影执行一次此操作 - 例如,我可能需要一个非规范化的ItemForOrder以及一个非规范化的ItemForSomethingElse - 它们只包含每个非规范化实体或文档所需的最少属性(无论何时创建或修改它们)。
如果我在读取存储中共享相同的Item,则可能会混合来自不同时间点的项定义,因为项目和订单的投影可能不以相同的速度运行。在最坏的情况下,项目的投影可能尚未创建我需要用于其属性的项目。
通常,在处理事件流的关系时,我有哪些方法?
更新2016-06-17
目前,我通过对非规范化读取模型及其相关数据运行单个投影来解决此问题。如果我有多个读取模型必须共享相同的相关数据,则可以将它们放入同一个投影中,以避免重复需要进行查找的相同相关数据。
这些相关模型甚至可能有点规范化,针对我必须访问它们进行优化。我的投影是唯一读取和写入它们的东西,所以我知道它们被如何读取。
然而,这里更隐晦的问题是何时更新相关数据。这在很大程度上取决于业务流程。例如,在订单下达后,我不想更改订单的商品描述。当投影处理事件时,我只能根据业务流程更新已更改的数据。因此,可以认为将此信息放入事件中(并使用客户端发送的数据?)。如果我们发现以后需要其他数据,则可能必须返回到从事件流中投影相关数据并从中读取的方式...对于纯CQRS架构来说,这可能看作是类似的问题:什么时候更新文档中的非规范化数据?什么时候在向用户呈现数据之前刷新数据?同样,业务流程可能驱动这个决定。
我试图了解如何生成读取模型的方法以及这可能涉及多少数据重复的问题。以订单和订单项为例,一个订单可以有多个订单项,每个订单项都有一个名称。订单和订单项是独立的聚合。
我可以尝试以更标准化的方式保存读取模型,其中为每个订单和订单项创建一个实体或文档,然后进行引用 - 或者我可能希望以更非标准化的方式保存它,其中包含订单并包含其订单项。 标准化
{
Id: Order1,
Items: [Item1, Item2]
}
{
Id: Item1,
Name: "Foosaver 9000"
}
{
Id: Item2,
Name: "Foosaver 7500"
}
使用更规范化的格式可以让单个投影处理影响/影响物品和订单并更新相应对象的事件。这也意味着任何对物品名称的更改都会影响所有订单。例如,客户可能会收到与相应发票所列商品不同的交货单(因此显然该模型可能不够好,会导致与非规范化相同的问题...)。 非规范化
{
Id: Order1,
Items: [
{Id: Item1, Name: "Foosaver 9000"},
{Id: Item2, Name: "Foosaver 7500"},
]
}
但是,反规范化需要一些源代码,可以查找当前相关数据,例如项目。这意味着我要么必须传输我可能需要的所有信息,要么必须跟踪为我的非规范化提取的数据。这也意味着我可能需要针对每个投影执行一次此操作 - 例如,我可能需要一个非规范化的ItemForOrder以及一个非规范化的ItemForSomethingElse - 它们只包含每个非规范化实体或文档所需的最少属性(无论何时创建或修改它们)。
如果我在读取存储中共享相同的Item,则可能会混合来自不同时间点的项定义,因为项目和订单的投影可能不以相同的速度运行。在最坏的情况下,项目的投影可能尚未创建我需要用于其属性的项目。
通常,在处理事件流的关系时,我有哪些方法?
更新2016-06-17
目前,我通过对非规范化读取模型及其相关数据运行单个投影来解决此问题。如果我有多个读取模型必须共享相同的相关数据,则可以将它们放入同一个投影中,以避免重复需要进行查找的相同相关数据。
这些相关模型甚至可能有点规范化,针对我必须访问它们进行优化。我的投影是唯一读取和写入它们的东西,所以我知道它们被如何读取。
// related data
public class Item
{
public Guid Id {get; set;}
public string Name {get; set;}
/* and whatever else is needed but not provided by events */
}
// denormalised info for document
public class ItemInfo
{
public Guid Id {get; set;}
public string Name {get; set;}
}
// denormalised data as document
public class ItemStockLevel
{
public ItemInfo Item {get; set;} // when this is a document
public decimal Quantity {get; set;}
}
// or for RDBMS
public class ItemStockLevel
{
public Guid ItemId {get; set;}
public string ItemName {get; set;}
public decimal Quantity {get; set;}
}
然而,这里更隐晦的问题是何时更新相关数据。这在很大程度上取决于业务流程。例如,在订单下达后,我不想更改订单的商品描述。当投影处理事件时,我只能根据业务流程更新已更改的数据。因此,可以认为将此信息放入事件中(并使用客户端发送的数据?)。如果我们发现以后需要其他数据,则可能必须返回到从事件流中投影相关数据并从中读取的方式...对于纯CQRS架构来说,这可能看作是类似的问题:什么时候更新文档中的非规范化数据?什么时候在向用户呈现数据之前刷新数据?同样,业务流程可能驱动这个决定。
[ArticleDefined(article1, "Foosaver 9000"), OrderCreated(order1, customer1), ItemAdded(order1, article1), ArticleRebranded(article1, "Supersaver 9000"]
为例 - 订单投影需要了解订单中的文章以将其反规范化到订单中。我想在读取端需要一个ArticleRepository
,在处理ItemAdded
事件时查询文章的名称和其他细节。我的担忧是我要么需要每个生成器一个存储库,要么就会出现不一致... - urbanhusky