2015-11-20 4 views
1

Вот мой пример структуры:DDD - Event Sourcing со структурой дерева

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

Node и Person являются aggregates питание от event sourcing. Leaf проводит NodeID и Person проводит PersonID. Они не содержат прямой ссылки между друг другу.

Теперь я знаю, как смотреть в прошлом для одного агрегата и, например, видеть всю историю Личности.

  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.

+0

как построить структуру сейчас? Почему вы не можете просто получить начальное состояние и свернуть изменения? – gabba

+0

Я этого не делаю. Я нахожусь на ранней стадии этого проекта.Просто ищите информацию. Начальное состояние? Е., но мне нужно будет принять начальное состояние событий свертки корневого узла и получить информацию о связанных узлах и сделать то же самое для них. И рекурсивный на дно, это будет стоить много времени. Специально для 1k узлов. Ну, я не знаю, может быть, моя модель просто ошибается? Если вы так думаете, скажите мне. – Dariss

+0

Какие типы событий у вас будут? Можно ли связать некоторые типы событий со многими узлами? Вы также можете иметь много начальных состояний – gabba

ответ

4

Ну, так как вы используете Event Sourcing Я предполагаю, что у вас есть денормализованные проекции для запросов и что вы не запрашиваете модель домена.

Это означает, что вы в настоящее время имеете денормализованную структуру, как (упрощенный):

Node (id , type, parent_id) 

Когда такое событие, как NodeCreated обрабатывается я предполагаю, что вы в настоящее время INSERT INTO Node и при обращении с NodeDeleted вам DELETE FROM Node и т.д.

Это позволяет вам восстанавливать обновленные представления ваших деревьев, но не допускает временных представлений.

Для выполнения временных запросов вам потребуется структура временных таблиц. Некоторые реляционные базы данных имеют встроенную поддержку для них, например SQL Server 2016. Не беспокойтесь, если ваша БД не поддерживает это, тривиально реализовать ее.

Чтобы реализовать простую временную таблицу, вам просто нужно добавить столбцы и end_date datetime NULL к вашему столу. Вы также можете добавить ограничение, чтобы избежать наличия более одной строки, где end_date IS NULL по 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; 

enter image description here

+0

Я впечатлен, это так просто. :) Но у меня мало проблем: У меня будет много дополнительных данных, которые никогда не могут использоваться в модели чтения. Также мне нужно хранить события с таким разрешением? Потому что у меня есть все мои исторические данные в Read Model. Я мог просто нажимать события в очереди сообщений, не сохраняя их в хранилище событий. – Dariss

+0

Я в основном использую Postgres, у него есть тип колонки JSON. Так что я думаю, это было бы более эффективным, чтобы создать такую ​​временную таблицу: CREATE TABLE Tree ( ID Int NOT NULL, parent_id INT NULL, данные JSON NOT NULL, START_DATE_TIME DateTime NOT NULL, END_DATE_TIME DateTime NULL ); В столбце данных будет содержаться дополнительная информация, например: имя, тип и т. Д. Это поможет мне сохранить несколько байтов для каждой записи. Как насчет этого? – Dariss

+1

Модель считывания должна быть отбрасываемой. Хранимые события - ваш единственный источник правды, все должно храниться на стороне записи (хранилище событий). Вы можете моделировать свою модель чтения так, как вы хотите, но она должна быть оптимизирована для запросов. Вы можете иметь несколько представлений одних и тех же данных, если это необходимо для выполнения различных запросов. – plalx