DDD - 具有树结构的事件溯源
DDD - Event Sourcing with tree structure
这是我的示例结构:
Node
- Node
- Node (with propety type === leaf)
+ Person
+ Person
- Node
- Node
Node
和 Person
是 aggregates
由 event sourcing
提供支持。
Leaf
持有 NodeID
而 Person
持有 PersonID
。它们不包含彼此之间的直接引用。
现在我知道如何查看过去的一个集合,例如查看 Person 的整个历史。
- 获取
some time
之前发生的所有事件
- 从事件中进行聚合重建
但我的问题是如何重建整个树结构以在过去的某个时间点看到它?
我的模特:
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 的原因。
既然您使用的是事件溯源,那么我假设您出于查询目的对投影进行了非规范化,并且您没有针对域模型进行查询。
这意味着您当前有一个非规范化结构,例如(简化):
Node (id , type, parent_id)
当处理 NodeCreated
等事件时,我假设您当前 INSERT INTO Node
,当您处理 NodeDeleted
时,您 DELETE FROM Node
,等等
这允许您重建树的最新表示,但不允许时间表示。
为了执行时态查询,您需要一个时态 table 结构。一些关系数据库内置了对这些的支持,例如 SQL Server 2016。如果您的数据库不支持,请不要担心,实现一个是微不足道的。
要实现简单的临时 table,您只需将 start_date datetime NOT NULL
和 end_date datetime NULL
列添加到 table。您还可以添加一个约束,以避免在 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;
这是我的示例结构:
Node
- Node
- Node (with propety type === leaf)
+ Person
+ Person
- Node
- Node
Node
和 Person
是 aggregates
由 event sourcing
提供支持。
Leaf
持有 NodeID
而 Person
持有 PersonID
。它们不包含彼此之间的直接引用。
现在我知道如何查看过去的一个集合,例如查看 Person 的整个历史。
- 获取
some time
之前发生的所有事件
- 从事件中进行聚合重建
但我的问题是如何重建整个树结构以在过去的某个时间点看到它?
我的模特:
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 的原因。
既然您使用的是事件溯源,那么我假设您出于查询目的对投影进行了非规范化,并且您没有针对域模型进行查询。
这意味着您当前有一个非规范化结构,例如(简化):
Node (id , type, parent_id)
当处理 NodeCreated
等事件时,我假设您当前 INSERT INTO Node
,当您处理 NodeDeleted
时,您 DELETE FROM Node
,等等
这允许您重建树的最新表示,但不允许时间表示。
为了执行时态查询,您需要一个时态 table 结构。一些关系数据库内置了对这些的支持,例如 SQL Server 2016。如果您的数据库不支持,请不要担心,实现一个是微不足道的。
要实现简单的临时 table,您只需将 start_date datetime NOT NULL
和 end_date datetime NULL
列添加到 table。您还可以添加一个约束,以避免在 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;