DDD - 具有树结构的事件采购

时间:2015-11-20 13:44:50

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

这是我的示例结构:

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

NodePersonaggregates event sourcing提供支持。 Leaf持有NodeIDPerson持有PersonID。它们之间不包含直接参考。

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

  1. 获取some time
  2. 之前发生的所有事件
  3. 从事件聚合重建
  4. 但我的问题是如何重建整个树结构以在过去的某个时刻看到它?

    我的模特:

    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的原因。

1 个答案:

答案 0 :(得分:4)

好吧,因为您正在使用事件源,我假设您有用于查询目的的非规范化投影,并且您没有查询域模型。

这意味着你目前有一个非规范化的结构,如(简化):

Node (id , type, parent_id)

当处理NodeCreated等事件时,我假设您当前INSERT INTO Node,当您处理NodeDeletedDELETE FROM Node等等。

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

为了执行时间查询,您需要一个时态表结构。一些关系数据库具有内置支持,如SQL Server 2016。如果您的数据库不支持,请不要担心,实现它是微不足道的。

要实现简单的时态表,您只需要在表中添加start_date datetime NOT NULLend_date datetime NULL列。您还可以添加约束以避免AR ID为end_date IS NULL的多行。

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;

enter image description here