DDD, Entity Framework и сопоставление сущностей

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

Слои выглядят следующим образом, в основном каждый слой является отдельной сборкой:

DB -> Domain -> Application -> Web API -> Clients

Для доступа к БД я использую EF Core 1.0.

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

Домен:

class Task
{
  private int State { get; set; }
  private string Description { get; set; }
  private int CreatedById { get; set; }            
}

Данные (EF):

class TaskData
{
  public int State { get; set; }
  public string Description { get; set; }
  public int CreatedById { get; set; }
  public User CreatedBy { get; set; }
}

В идеале я хотел бы использовать мою доменную сущность непосредственно с ORM, но задача и TaskData не идентичны. В Task entity I don’t need the navigation property CreatedBy, i’m fine with just an Id, so I don’t want to pollute The domain with stuff it doesn’t care about.

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

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

Это приводит меня к memento pattern, поэтому я создаю новый класс, который, кажется, просто DTO:

class TaskSnapshot
{
  public int State { get; set; }
  public string Description { get; set; }
  public int CreatedById { get; set; }
}

И моя первоначальная сущность задачи теперь выглядит так:

class Task
{
  ...           
  public Task(TaskSnapshot snapshot)
  {
    this.State = snapshot.State;
    this.Description = snapshot.Description;
    this.CreateById = snapshot.CreatedById;
  }

  public TaskSnapshot ToSnapshot()
  {
    return new TaskSnapshot() 
    {
      State = this.State, 
      Description = this.Description, 
      CreatedBy = this.CreatedBy };
  }
}

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

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

Что я мог сделать:

  1. Сделайте getters и setters общедоступными в доменных сущностях, поэтому мне не понадобятся снимки. — >>Определенно нет!

  2. Добавьте ненужные свойства (CreatedBy navigation) к сущности задачи и используйте ее непосредственно с ORM. — >>Я бы не хотел.

  3. Преобразуйте классы моментальных снимков в модель данных и используйте их с ORM. — >Я, вероятно, не возражал бы против навигационных свойств там, но это означало бы, что модель данных является частью сборки домена. — > >Я не знаю, мне это не нравится.

Существует ли рекомендация, как я мог бы эффективно уменьшить количество классов или сконцентрировать все назначения (сопоставление) в одном месте/классе mapper без ущерба для домена?

Спасибо!.

1 ответ

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

    Также посмотрите здесь, где говорится: «навигация по свойствам ассоциации является ключевым понятием в DDD.»

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