Подтвердить что ты не робот

Богатая модель домена с поведением и ORM

После просмотра презентации NDC12 "Создание злобных моделей доменов" у Джимми Богарда (http://ndcoslo.oktaset.com/Agenda) я бродил по тому, как упорствовать в таких модель домена.
Это пример класса из презентации:

public class Member
{
    List<Offer> _offers;

    public Member(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
        _offers = new List<Offer>();
    }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public IEnumerable<Offer> AssignedOffers { 
        get { return _offers; }
    }

    public int NumberOfOffers { get; private set; }

    public Offer AssignOffer(OfferType offerType, IOfferValueCalc valueCalc)
    {
        var value = valueCalc.CalculateValue(this, offerType);
        var expiration = offerType.CalculateExpiration();
        var offer = new Offer(this, offerType, expiration, value);
        _offers.Add(offer);
        NumberOfOffers++;
        return offer;
    }
}

поэтому в этой модели домена есть некоторые правила:
 - Член должен иметь имя и фамилию
 - Количество предложений не может быть изменено вне
 - Участник отвечает за создание нового предложения, вычисляя его значение и назначение

Если при попытке сопоставить это с ORM, например с Entity Framework или NHibernate, это не сработает. Итак, какой лучший подход для сопоставления такого типа модели с базой данных с ORM?
Например, как я могу загрузить AssignedOffers из БД, если нет сеттера?

Единственное, что имеет для меня смысл, это использование архитектуры команд/запросов: запросы всегда выполняются с результатом DTO, а не с сущностями домена, а команды выполняются на моделях домена. Кроме того, поиск событий идеально подходит для поведения в модели домена. Но такая архитектура CQS, возможно, не подходит для каждого проекта, особенно для коричневого поля. Или нет?

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

4b9b3361

Ответ 1

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

По моему опыту есть три способа решения этой проблемы:

  • Как уже упоминалось Luka, NHibernate поддерживает сопоставление с частными полями, а не с настройщиками свойств.
  • Если вы используете EF (который, как я полагаю, не поддерживает выше), вы можете использовать memento pattern для восстановления состояния объектов домена. например вы используете инфраструктуру сущности для заполнения объектов "memento", которые ваши сущности домена принимают, чтобы установить свои частные поля.
  • Как вы указали, использование CQRS с использованием источника событий устраняет эту проблему. Это мой предпочтительный метод создания идеально инкапсулированных объектов домена, которые также обладают всеми дополнительными преимуществами источника событий.

Ответ 2

Старый поток. Но там более поздняя публикация (конец 2014 года) Вон Вернон, которая затрагивает именно этот сценарий, с особым упором на Entity Framework. Учитывая, что я как-то изо всех сил пытался найти такую ​​информацию, может быть, полезно также опубликовать ее здесь.

В основном пост защищает объект Product domain (aggregate), чтобы обернуть объект данных ProductState EF POCO для того, что касается стороны вещей. Разумеется, объект домена все равно добавит все его поведение в богатом домене через методы/аксессоры, специфичные для домена, но прибегает к внутреннему объекту данных, когда ему нужно получить/установить его свойства.

Копирование фрагмента прямо из сообщения:

public class Product
{
  public Product(
    TenantId tenantId,
    ProductId productId,
    ProductOwnerId productOwnerId,
    string name,
    string description)
  {
    State = new ProductState();
    State.ProductKey = tenantId.Id + ":" + productId.Id;
    State.ProductOwnerId = productOwnerId;
    State.Name = name;
    State.Description = description;
    State.BacklogItems = new List<ProductBacklogItem>();
  }

  internal Product(ProductState state)
  {
    State = state;
  }

  //...

  private readonly ProductState State;
}

public class ProductState
{
  [Key]
  public string ProductKey { get; set; }

  public ProductOwnerId ProductOwnerId { get; set; }

  public string Name { get; set; }

  public string Description { get; set; }

  public List<ProductBacklogItemState> BacklogItems { get; set; }
  ...
}

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

Один бит, который я могу добавить себе, заключается в том, что, возможно, объект домена Product должен быть загрязнен еще одним аксессуаром только с целью сохранения через EF: в том же самом значении, что и new Product(productState), можно загрузить объект домена из базы данных, противоположный путь должен быть разрешен через что-то вроде:

public class Product
{
   // ...
   internal ProductState State
   {
     get
     {
       // return this.State as is, if you trust the caller (repository),
       // or deep clone it and return it
     }
   }
}

// inside repository.Add(Product product):

dbContext.Add(product.State);

Ответ 3

Для AssignedOffers: если вы посмотрите на код, вы увидите, что AssignedOffers возвращает значение из поля. NHibernate может заполнить это поле следующим образом: Map (x = > x.AssignedOffers).Access.Field().

Согласитесь с использованием CQS.

Ответ 4

При первом использовании DDD вы игнорируете проблемы с сохранением. ORM является tighlty, связанным с РСУБД, так что это проблема сохранения.

Структура сохранения объектов ORM НЕ является доменом. В основном репозиторий должен "преобразовывать" полученный составной корень в один или несколько объектов сохранения. Ограниченный контекст имеет большое значение с момента изменения Агрегатного Корня в соответствии с тем, что вы пытаетесь выполнить.

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

public interface IAssignOffer
{
    int OwnerId {get;}
    Offer AssignOffer(OfferType offerType, IOfferValueCalc valueCalc);
    IEnumerable<Offer> NewOffers {get; }
}

public class Member:IAssignOffer
{
    /* implementation */ 
 }

 public interface IDomainRepository
 {
    void Save(IAssignOffer member);    
 }

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

Что касается EVENT Sourcing, я думаю, что вам нужно посмотреть, подходит ли он вашему домену, и я не вижу проблем с использованием Event Sourcing только для хранения доменных агрегированных корней, в то время как остальная (в основном инфраструктура) может храниться в обычном (реляционные таблицы). Я думаю, что CQRS дает вам большую гибкость в этом вопросе.