【问题标题】:DDD - Event Sourcing with tree structureDDD - 具有树结构的事件溯源
【发布时间】:2015-11-20 13:44:49
【问题描述】:

这是我的示例结构:

Node 
 - Node 
   - Node (with propety type === leaf)
     + Person
     + Person
 - Node
 - Node

NodePersonaggregates,由event sourcing提供支持。 Leaf 持有 NodeIDPerson 持有 PersonID。它们之间不包含直接引用。

现在我知道如何查看过去的一个聚合并查看 Person 的整个历史。

  1. 获取some time之前发生的所有事件
  2. 根据事件进行聚合重建

但我的问题是如何重建整个树结构以在过去的某个时候看到它?

我的模特:

Node
  {
      private string name;
      private string parentNodeID;
      private string type;
      private Array  hiredPersons;  

      Node(string name, string parentNodeID, string type) {
          this.apply(new NodeHasCreated(name, parentNodeID, type));
      }

      public void hirePerson(Person person)
      {
          if(this.type === 'leaf') {
              this.apply(new PersonHasBeenHired(person.id));
          }
      }          

  }

我不想实现的是保持从节点到节点的直接引用。这就是我使用 parentNodeID 的原因。

【问题讨论】:

  • 你现在如何构建结构?为什么不能只获得一些初始状态和汇总更改?
  • 我没有。我在这个项目的早期阶段。只是在找资料。初始状态?是的,但我需要获取根节点汇总事件的初始状态并获取有关关联节点的信息并对它们执行相同的操作。并且递归到底部,会花费很多时间。特别适用于 1k 个节点。好吧,我不知道,也许我的模型是错误的?如果你这么认为,请告诉我。
  • 您将举办哪些类型的活动?某些类型的事件可以与许多节点相关吗?你也可以有很多初始状态
  • Node 和 Leaf 的初始状态将始终相同。我称它为叶子只是为了显示可以有多个人的节点。因为真正的 Leaf 只是具有特定 type 属性的节点。 Node 中存在的事件例如:NodeHasBeenCreated、PersonH​​asBeenHired(仅当 type === 'leaf' 时才可能)、NodePositionHasChanged、NodeHasBeenDeleted、NodeTypeHasChanged。
  • 我稍微更新了描述。

标签: java c# domain-driven-design cqrs event-sourcing


【解决方案1】:

好吧,既然您使用的是事件溯源,我假设您出于查询目的对投影进行了非规范化,并且您没有针对域模型进行查询。

这意味着您当前有一个非规范化结构,例如(简化):

Node (id , type, parent_id)

当处理NodeCreated 之类的事件时,我假设您当前是INSERT INTO Node,当您处理NodeDeleted 时,您是DELETE FROM Node,等等。

这允许您重建树的最新表示,但不允许时间表示。

为了执行时态查询,您需要一个时态表结构。一些关系数据库对这些具有内置支持,例如SQL Server 2016。如果您的数据库不支持,请不要担心,实现一个很简单。

要实现一个简单的临时表,您只需将start_date datetime NOT NULLend_date datetime NULL 列添加到您的表中。您还可以添加一个约束以避免在end_date IS NULL by AR id 的位置超过一行。

Then where you usually:

did an INSERT you: 
    INSERT INTO tbl (..., start_date) VALUES (..., currentDate)

did an UPDATE you:
    UPDATE tbl SET end_date = GETDATE() WHERE [update predicate] AND end_date IS NULL
    INSERT INTO tbl (..., start_date) VALUES (..., currentDate)
did a DELETE you:
    UPDATE tbl SET end_date = GETDATE() WHERE [delete predicate] AND end_date IS NULL

使用这种简单的方法,您可以查询任何日期的数据。

SELECT *
FROM tbl
WHERE start_date <= someDate AND (end_date IS NULL OR end_date > someDate)

这里是an example

CREATE TABLE Tree (
    id int NOT NULL,
    parent_id int NULL,
    name nvarchar(50) NOT NULL,
    start_date_time datetime NOT NULL,
    end_date_time datetime NULL
);

GO

CREATE UNIQUE NONCLUSTERED INDEX UN_Tree_id_end_date_time
ON Tree (id, end_date_time)
WHERE end_date_time IS NULL;

INSERT INTO Tree (
    id,
    parent_id,
    name,
    start_date_time,
    end_date_time
)
VALUES
    (1, NULL, 'A', GETDATE(), NULL), -- node A created
    (2, NULL, 'B', GETDATE(), NULL), -- node B created
    (3, 1, 'A.1', GETDATE(), NULL), -- node A.1 created
    (4, 2, 'A.1.1', GETDATE(), NULL); -- node A.1.1 added

-- Node A.1 renamed
UPDATE Tree
SET end_date_time = GETDATE()
WHERE id = 3 AND end_date_time IS NULL;

INSERT INTO Tree VALUES (3, 1, 'A.1_renamed', GETDATE(), NULL);

-- Node A.1.1 removed a day after
UPDATE Tree
SET end_date_time = DATEADD(d, 1, GETDATE())
WHERE id = 4 AND end_date_time IS NULL;


-- Query nodes from root A as of now using a recursive CTE
-- Note: Did not manage to declare a @asOf variable variable in SQL Fiddle
WITH data AS (
    SELECT id, parent_id, name
    FROM Tree
    WHERE 
        id = 1
        AND start_date_time <= GETDATE() 
        AND (end_date_time IS NULL OR end_date_time > GETDATE())

    UNION ALL

    SELECT child.id, child.parent_id, child.name
    FROM data d
    INNER JOIN Tree child
        ON 
            child.parent_id = d.id
            AND start_date_time <= GETDATE() 
            AND (end_date_time IS NULL OR end_date_time > GETDATE())
)   
SELECT *
FROM data;

【讨论】:

  • 我印象深刻,这太容易了。 :) 但我几乎没有担心:我将持有大量额外的数据,这些数据可能永远不会用于读取模型。我还需要以这样的分辨率存储事件吗?因为我在读取模型中拥有所有历史数据。我可以将事件推送到消息队列而不将它们存储在事件存储中。
  • 我主要使用 Postgres,它具有 JSON 列类型。所以我想创建这样的临时表会更有效: CREATE TABLE Tree ( id int NOT NULL, parent_id int NULL, data JSON NOT NULL, start_date_time datetime NOT NULL, end_date_time datetime NULL );数据列将包含任何额外信息,例如:名称、类型等。它将帮助我为每条记录节省几个字节。那怎么办?
  • 读取模型应该是可丢弃的。存储的事件是您唯一的事实来源,所有内容都必须存储在写入端(事件存储)。你可以按照你想要的方式对你的读取模型进行建模,但它应该针对查询进行优化。如有必要,您可以对同一数据有多种表示形式来完成各种查询。
猜你喜欢
  • 1970-01-01
  • 2013-01-24
  • 1970-01-01
  • 1970-01-01
  • 2020-02-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-23
  • 2016-04-08
相关资源
最近更新 更多