Использование СУБД в качестве хранилища источников событий


Если бы я использовал СУБД (например, SQL Server) для хранения данных источников событий, как могла бы выглядеть схема?

Я видел несколько вариантов, о которых говорили в абстрактном смысле, но ничего конкретного.

например, скажем, у вас есть объект "продукт", и изменения в этом продукте могут быть представлены в виде: Цена, Стоимость и описание. Я в замешательстве, если бы я:

  1. есть таблица" ProductEvent", которая имеет все поля для продукта, где каждое изменение означает новую запись в этой таблице, плюс "кто, что, где, почему, когда и как" в зависимости от обстоятельств. Когда стоимость, цена или описание изменяются, вся новая строка добавляется для представления продукта.
  2. хранить стоимость, цену и описание продукта в отдельных таблицах, Соединенных с таблицей продукта с внешним ключом отношения. Когда происходят изменения этих свойств, напишите новые строки с помощью WWWWWH, если это необходимо.
  3. хранить WWWWWH, а также сериализованный объект, представляющий событие в таблице "ProductEvent" означает, что само событие должно быть загружено, де-сериализовано и повторно воспроизведено в моем коде приложения, чтобы повторно построить состояние приложения для данного продукта.

особенно я беспокоюсь о варианте 2 выше. В крайнем случае, таблица product будет почти одной таблицей для каждого свойства, где для загрузки состояния приложения для данного продукта потребуется загрузить все события для этого продукта из каждой таблицы событий продукта. Этот стол-взрыв пахнет неправильно для меня.

Я уверен, что" это зависит", и хотя нет ни одного" правильного ответа", я пытаюсь понять, что приемлемо, а что совершенно неприемлемо. Я также знаю, что NoSQL может помочь здесь, где события могут храниться против aggregate root, что означает только один запрос к базе данных, чтобы получить события для восстановления объекта, но мы не используем базу данных NoSQL на данный момент, поэтому я ищу альтернативы.

4 88

4 ответа:

хранилище событий не должно знать о конкретных полях или свойствах событий. В противном случае каждая модификация модели приведет к необходимости переноса базы данных (как в старой доброй состоянием настойчивости). Поэтому я бы не рекомендовал Вариант 1 и 2 вообще.

Ниже приведена схема, используемая в Ncqrs. Как вы можете видеть, таблица "события" хранит связанные данные в виде CLOB (т. е. JSON или XML). Это соответствует вашему варианту 3 (Только то, что нет таблицы" ProductEvents", потому что вам нужна только одна общая таблица" события". В Ncqrs сопоставление с вашими агрегатными корнями происходит через таблицу" EventSources", где каждый источник событий соответствует фактическому агрегатному корню.)

Table Events:
    Id [uniqueidentifier] NOT NULL,
    TimeStamp [datetime] NOT NULL,

    Name [varchar](max) NOT NULL,
    Version [varchar](max) NOT NULL,

    EventSourceId [uniqueidentifier] NOT NULL,
    Sequence [bigint], 

    Data [nvarchar](max) NOT NULL

Table EventSources:
    Id [uniqueidentifier] NOT NULL, 
    Type [nvarchar](255) NOT NULL, 
    Version [int] NOT NULL

механизм сохранения SQL реализация магазина событий Джонатана Оливера состоит в основном из одной таблицы под названием "Commits" с полем BLOB "полезная нагрузка". Это почти то же самое, что и в Ncqrs, только это он сериализует свойства события в двоичном формате (что, например, добавляет поддержку шифрования).

Грег Янг рекомендует подобный подход, как подробно документировано на веб-сайте Грега.

схема его прототипической таблицы "события" гласит:

Table Events
    AggregateId [Guid],
    Data [Blob],
    SequenceNumber [Long],
    Version [Int]

проект GitHub CQRS.NET есть несколько конкретных примеров того, как вы могли бы сделать EventStores в нескольких различных технологиях. На момент написания есть реализация в SQL с помощью Linq2SQL и схема SQL, чтобы пойти с ним, есть один для MongoDB, для DocumentDB (CosmosDB, если вы находитесь в Azure) и один с помощью EventStore (как указано выше). В Azure есть больше, например, хранилище таблиц и большой двоичный объект хранилище, которое очень похоже на плоское хранилище файлов.

Я думаю, что главный момент здесь заключается в том, что все они соответствуют одному и тому же принципалу/контракту. Все они хранят информацию в одном месте / контейнере / таблице, они используют метаданные для идентификации одного события из другого и "просто" хранят все событие, как оно было - в некоторых случаях сериализовано, в поддерживающих технологиях, как это было. Поэтому в зависимости от того, выбираете ли вы базу данных документов, реляционную базу данных или даже плоский файл, есть несколько разных способов чтобы все они достигли одного и того же намерения хранилища событий (это полезно, если вы передумаете в любой момент и обнаружите, что вам нужно перенести или поддерживать более одной технологии хранения).

как разработчик проекта я могу поделиться некоторыми соображениями о некоторых из вариантов, которые мы сделали.

во-первых, мы нашли (даже с уникальными UUIDs / GUIDs вместо целых чисел) по многим причинам последовательные идентификаторы происходят по стратегическим причинам, поэтому просто наличие идентификатора не было достаточно уникальным для ключа, поэтому мы объединили наши основной столбец ключа ID с типом данных / объекта для создания того, что должно быть действительно (в смысле вашего приложения) уникальным ключом. Я знаю, что некоторые люди говорят, что вам не нужно хранить его, но это будет зависеть от того, являетесь ли вы Гринфилдом или должны сосуществовать с существующими системами.

мы застряли с одним контейнером / таблицей/коллекцией по причинам ремонтопригодности, но мы играли с отдельной таблицей на объект / объект. Мы нашли на практике, что означало либо необходимое приложение " создать" разрешения (что вообще говоря не очень хорошая идея... как правило, всегда есть исключения/исключения) или каждый раз, когда новый объект/объект появился или был развернут, необходимо было создать новые контейнеры хранения/таблицы/коллекции. Мы обнаружили, что это было болезненно медленно для местной разработки и проблематично для развертывания производства. Вы не можете, но это был наш реальный опыт.

еще одна вещь, чтобы помнить, что просить действие X произойти может привести ко многим различные события, происходящие, таким образом, зная все события, генерируемые командой/событие/что когда-либо полезно. Они также могут быть разными типами объектов, например, нажатие "купить" в корзине покупок может вызвать события учетной записи и складирования для запуска. Потребляющее приложение может хотеть знать все это, поэтому мы добавили CorrelationId. Это означало, что потребитель может запросить все события, возникшие в результате их запроса. Вы увидите это в - схемы.

в частности с SQL, мы обнаружили, что производительность действительно стала узким местом, если индексы и разделы не использовались должным образом. Помните, что события должны быть переданы в обратном порядке, если вы используете снимки. Мы попробовали несколько различных индексов и обнаружили, что на практике некоторые дополнительные индексы необходимы для отладки реальных приложений в производстве. Снова вы увидите, что в - схемы.

другие метаданные в производстве были полезны во время производственных исследований, временные метки дали нам представление о порядке, в котором события сохранялись и поднимались. Это дало нам некоторую помощь в особенно сильно событийной системе, которая вызвала огромное количество событий, давая нам информацию о производительности таких вещей, как сети и распределение систем по сети.

Ну вы могли бы дать взглянуть на Datomic.

Datomic-это гибкая база данных,факты, основанные на времени, поддержка запросов и соединений, с эластичной масштабируемостью и кислотными транзакциями.

Я написал подробный ответ!--9-->здесь

вы можете посмотреть выступление Стюарта Хэлоуэя, объясняющего дизайн Datomic здесь

поскольку Datomic хранит факты во времени, вы можете использовать его для события примеры использования источников и многое другое.

возможная подсказка-это дизайн, за которым следует "медленно меняющийся размер" (type=2), который должен помочь вам покрыть:

  • порядок событий, происходящих (через суррогатный ключ)
  • долговечность каждого состояния (действительный от - действительный до)

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