Объект значения в источнике событий - PullRequest
0 голосов
/ 16 сентября 2018

Есть ли место для объектов-значений в модели предметной области?

Позволяет определить объект-значение как объект с неизменным состоянием, которое защищает его инвариантыи не имеет конкретного идентификатора.

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

Прошла дискуссия о действительности использования объектов-значений в событиях - этот вопрос идет немного дальше: есть ли у объектов-значений место в событиивсе домены вообще ?

(потенциальная) проблема с использованием объектов-значений заключается в том, что довольно сложно изменить домен таким образом, чтобы инварианты были сжаты.

Примером этого сценария может быть объект значения Username с единственным ограничением, что имя должно быть где-то между 2 и 16 символами.

Хотя в течение некоторого времени это работало хорошо,бизнес решает разрешить имена пользователей не менее 5 символов.Начинается период миграции, и пользователям с именами менее 5 символов предлагается обновить свои имена.

Допустим, процесс прошел успешно, исправления применены, и все довольны.Мы ужесточаем ограничения для нашего объекта значения Username, требуя не менее 5 символов.

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

Теперь перед нами исключение из нашего Username объекта: загружая исторические данные, мы нарушаем инвариант нашего домена.

Правила для объектов-значений применяются задним числом -делает ли это их по сути непригодными для источников событий?Стоит ли применять управление версиями объектов-значений?Есть ли более простой способ избежать таких проблем?

Ответы [ 4 ]

0 голосов
/ 30 октября 2018

Мы решили это немного по-другому. Отделяя public API наших объектов-значений от внутреннего API (только для домена), мы можем развивать один, не влияя на другой.

Например:

public class Username
{
    private readonly string value;

    // Domain-only (internal) constructor.
    // Does not enforce constriants and can only be called within the domain.
    internal Username(string value)
    {
        this.value = value;
    }

    // Public factory method.
    // Enforces business constraints. Used by consumers of the domain (application layer etc.)
    // to create new instances of the value object.
    public static Username Create(string value)
    {
        // Business constraints. These will evolve and grow over time.
        if (value == null)
        {
            // throw exception etc.
        }

        if (value.Length < 2)
        {
            // throw exception etc.
        }

        return new Username(value);
    }
}

Потребители домена должны использовать статический метод Create для создания нового экземпляра объекта значения. Этот фабричный метод содержит все наши бизнес-ограничения и предотвращает создание экземпляра в недопустимом состоянии.

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

Преимущества этого дизайна:

  • Один класс используется для представления концепции домена (нет необходимости в нескольких классах, управлении версиями и т. Д.).
  • Деловые правила могут свободно развиваться с течением времени.
  • Исторические данные всегда работают. Username год назад - это имя пользователя , даже если наши правила изменились.
0 голосов
/ 16 сентября 2018

Имеют ли объекты значения место в доменах, полученных из событий?

Да.

Есть ли более простой способ избежать таких проблем?

«Не делай этого».

Проблема, которую вы описываете, на самом деле связана с обменом сообщениями - если мы вносим обратно несовместимые изменения в наши сообщения, тогда что-то ломается.

(Точнее, у вас есть сообщение «Имя пользователя», и вы пытаетесь повторно использовать это сообщение с новым набором ограничений, которые отклоняют некоторые ранее действительные использования сообщения).

Ответ заключается в том, что вы не вносите обратно несовместимые изменения - вместо этого вводите новые имена, которые соответствуют новым требованиям, и осуждаете старые.

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

Книга Грега Янга Управление версиями в системе источников событий посвящает несколько глав этой идее. Кроме того, Рич Хики заканчивает тем, что затрагивает эти важные идеи в большинстве своих выступлений - я бы предложил начать с Спекуляция .

«Объект-значение», означающий, что тип, используемый текущей реализацией модели предметной области для перемещения информации, представляет собой отдельную проблему из сообщений. Структуры данных, которые мы используем в памяти, не должны быть связаны с нашими форматами сериализации.

Представление информации на проводе отличается от представления информации в памяти, и это, в свою очередь, отличается от абстракций, которые манипулируют информацией в памяти.

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

0 голосов
/ 17 сентября 2018

Хотя уже ответили, я нахожу это интересной ситуацией.

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

То, что говорится при изменении правил, изменяет и домен. Основная часть проекта, управляемого доменами, состоит в том, чтобы захватить столько области (правил / структуры), сколько требуется. Если это так, не следует ли сохранить изменения в правилах?

Например, если у нас есть Username Объект значения и он начинается с правил от 2 до 16 символов, то это кодируется так:

public class Username
{
    public string Value { get; }

    public Username(string value)
    {
        if (value.Length < 2 || value.Length > 16)
        {
            throw new DomainException("Username must be between 2 and 16 characters");
        }

        Value = value;
    }
}

Теперь мы получаем 1 марта 2018 и правило меняется. Мы можем придерживаться правила:

public class Username
{
    public string Value { get; }

    public Username(string value, DateTime registrationDate)
    {
        if (registrationDate < new Date(2018, 3, 1) &&
            (value.Length < 2 || value.Length > 16))
        {
            throw new DomainException("Username must be between 2 and 16 characters");
        }

        if (registrationDate >= new Date(2018, 3, 1) &&
            (value.Length < 5 || value.Length > 16))
        {
            throw new DomainException("Username must be between 5 and 16 characters");
        }

        Value = value;
    }
}

Это основная идея. Таким образом, мы сохраняем наши «старые» правила. Это может стать довольно хлопотным, но у меня недостаточно опыта, чтобы сказать. Изменение наших правил задним числом может привести к некоторой довольно сложной ситуации, поэтому я думаю, что нужно будет оценить это в каждом конкретном случае.

Просто мысль.

0 голосов
/ 16 сентября 2018

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

Поскольку у слова есть два разных значения, вы должны как-то сделать его явным в коде. «Управление версиями» - это один из способов, хотя я бы не использовал такое универсальное решение, есть разные варианты моделирования.

Вы могли бы прямо указать, что история «имени пользователя» - это всего лишь история. Так, например, создайте HistoricUsername, который является источником событий, даже объектом значения, если хотите. И создайте Username, который всегда является именем пользователя с самыми последними правилами, который вообще не сохраняется, но создается из HistoricUsername, если это возможно.

Некоторые люди иногда предлагают извлечь «правила» из объекта и повторно применить их позже. Таким образом, сам объект всегда действителен, и вы можете попросить его проверить себя на соответствие правилам, которые могут измениться. Я действительно не предпочитаю подобные решения, но это вариант, и Username все равно будет объектом-значением.

Таким образом, проблема не в том, что объекты-значения не вписываются в источник событий, а в том, что моделирование должно быть более точным.

...