2012-06-27 3 views
17

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

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, возможно, не подходит для каждого проекта, особенно для коричневого поля. Или нет?

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

+0

Я просто смотрел одно и то же видео, и мне было интересно то же самое. Что вы думаете о передаче poco в конструкторе, а также иметь свойство readonly в классе Member, чтобы вернуть клон этого poco? Таким образом, вы можете получить данные в объекте домена и из него, чтобы сохранить его или передать его. – stralsi

+0

Что-то вроде моментального снимка объекта? Вероятно, это сработает, но также потребует некоторого взлома, чтобы заставить его работать с инструментом ORM. Я лично не вижу легкого пути, и это принесет много абстракций и обобщений, которые вам придется сражаться повсюду в приложении. Событие sourcing - единственный способ пойти IMO –

+0

Я на самом деле просто смотрел это видео и думал о том же; означает ли это, что вам нужен набор объектов DTO/POCO для уровня данных/персистентности, который ваш ORM увлажняет, а затем использовать сопоставление, например AutoMapper, для отображения объекта домена? Что-то подобное происходит в репозитории? Кажется, что ORM, как EF Code First, ожидает POCO с геттерами и сеттерами. – Abe

ответ

1

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

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

+0

Прохладный, спасибо за информацию NH! –

+0

Но как насчет конструктора? –

+0

Я не вижу в вашем примере частного/защищенного или внутреннего конструктора. Таким образом, NHibernate будет использовать значение по умолчанию, которое не имеет значения параметра. Я бы использовал этот тип конструктора, который вы делали только для обеспечения того, чтобы объект заселялся кодом, а не с помощью orm. – Luka

0

При первом выполнении 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 только для хранения доменов Aggregate Roots, в то время как остальная (в основном инфраструктура) может быть сохранена в обычный способ (реляционные таблицы). Я думаю, что CQRS дает вам большую гибкость в этом вопросе.

+0

thanx для ответа, но как должна загружаться репозиторий? –

+0

Загрузите их с какой целью? :) – MikeSW

+0

Участник предоставляет IEnumerable , поэтому для использования в классе обслуживания или что-то в этом роде. –

11

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

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

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

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

В основном почтовые защитники объекта домена Product (агрегат), чтобы обернуть 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); 
Смежные вопросы