Сегодня мы поговорим о важном вопросе: как база данных приложения вписывается в концепцию всегда действительной модели домена?

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

1. Всегда действительная модель домена

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

Основное преимущество этого руководства заключается в том, что оно помогает облегчить бремя обслуживания. После создания объекта домена вы можете быть уверены, что он находится в допустимом состоянии. Это устраняет все сомнения относительно того, подтвердили ли вы это или нет; вы просто не можете создать экземпляр недопустимого объекта домена.

Чтобы придерживаться этого правила, вам необходимо поддерживать четкую границу между (всегда действующей) моделью предметной области и (не всегда действительным) внешним миром:

2. Всегда действующая граница и база данных

Теперь возникает вопрос: где находится база данных на приведенной выше диаграмме? Является ли она частью внешнего мира или частью всегда действующей границы?

Другими словами, следует ли доверять достоверности данных в базе данных? Или вам следует проверять его каждый раз, когда вы его запрашиваете?

Рассматривайте базу данных как часть всегда действующей границы

Обращайтесь с базой данных так же, как с моделью предметной области:

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

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

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

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

Но что, если вы поделитесь своей базой данных? Как тогда лечить?

3. Всегда действующая граница и общая база данных.

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

Что же тогда делать?

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

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

4. Управляемые и неуправляемые зависимости

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

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

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

5. Защита vs проверка

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

Я объяснил это более подробно в моей статье Всегда действительная модель домена, но вот изображение, которое суммирует различия:

Входные данные, поступающие за пределами всегда действующей границы (отображаемой как модель предметной области на этой диаграмме), должны быть отфильтрованы перед передачей его далее в модель предметной области. Это первая линия защиты от несогласованности данных. На этом этапе любые неверные данные разрешены и встречают вежливое «попробуйте еще раз».

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

Охранники помогут вам выявить эти ошибки. Это отказоустойчивый механизм, последняя линия защиты, которая гарантирует, что данные на всегда действительной границе действительно действительны. В отличие от проверок, охранники генерируют исключения; они соответствуют принципу Fail Fast.

Как эта защита выглядит на практике, когда дело касается данных в базе данных приложения? Взгляните на этот пример кода:

public class Customer : Entity
{
    private string _name;
    public virtual CustomerName Name
    {
        get => (CustomerName)_name; // Throws if conversion fails
        set => _name = value;
    }
}

Здесь мы используем NHibernate для материализации данных в Customer. NHibernate (а также EF Core) обходит конструктор и использует отражение для присвоения имени клиента непосредственно закрытому полю _name.

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

get => (CustomerName)_name;

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

Вот еще один пример, в котором вместо NHibernate используется EF Core:

public class Student : Entity
{
    public Email Email { get; private set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Student>(x =>
    {
        // Calling .Value throws if conversion from string to Email fails
        x.Property(p => p.Email)
            .HasConversion(p => p.Value, p => Email.Create(p).Value);
}

В этом примере мы используем функцию Преобразование ценностей EF Core для преобразования между string и объектом значения Email. Преобразование из string в Email также небезопасно, потому что вызов .Value в

Email.Create(p).Value

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

6. Использование ORM в пределах и за пределами всегда действительных границ.

Часто задают вопрос о том, как использовать ORM внутри и за пределами всегда действительных границ. Это выглядит примерно так:

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

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

Как сделать так, чтобы ORM (EF Core или NHibernate) не обходил все проверки при создании объектов домена?

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

Но как насчет общей базы данных, где требуется проверка ? Как для этого можно использовать ORM?

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

7. Резюме

  • Рассматривайте базу данных приложения как часть всегда действующей границы
  • Если база данных используется совместно с другими приложениями, отделите ее части, которые видны другим приложениям, от частей, которые не
  • Это руководство распространяется на все управляемые зависимости, включая файловую систему.
  • Вы по-прежнему можете защититься от данных, поступающих из базы данных приложения.
  • Не используйте ORM при работе с общей базой данных

Подписаться

Подпишитесь, чтобы читать больше таких статей: https://enterprisecraftsmanship.com/subscribe

Первоначально опубликовано на https://enterprisecraftsmanship.com.