Шаблон репозитория без ORM - PullRequest
20 голосов
/ 16 февраля 2011

Я использую шаблон репозитория в приложении .NET C #, которое не использует ORM. Однако проблема, с которой я сталкиваюсь, заключается в том, как заполнить свойства списка «один ко многим» сущности. например если у клиента есть список заказов, т.е. если у класса Customer есть свойство List с именем Orders, а в моем хранилище есть метод GetCustomerById, то?

  • Должен ли я загрузить список заказов в методе GetCustomerById?
  • Что если у самого ордера есть другое свойство списка и т. Д.?
  • Что делать, если я хочу сделать ленивую загрузку? Где бы я поместил код для загрузки свойства Orders в клиент? В свойстве Orders есть метод доступа {}? Но тогда я должен был бы внедрить хранилище в сущность домена? который я не считаю правильным решением.

Это также вызывает вопросы к таким функциям, как отслеживание изменений, удаление и т. Д.? Так что я думаю, что конечный результат - я могу сделать DDD без ORM?

Но сейчас меня интересует только отложенная загрузка свойств List в сущностях моего домена? Есть идеи?

Набиль

Я предполагаю, что это очень распространенная проблема для тех, кто не использует ORM в дизайне, управляемом доменом? Есть идеи?

Ответы [ 3 ]

12 голосов
/ 16 февраля 2011

можно ли сделать DDD без ORM?

Да, но ORM упрощает вещи.

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

Например, почему вы смоделировали совокупность клиентов, чтобы получить список заказов? Если ответ «потому что у клиента могут быть заказы», ​​то я не уверен, что вы настроены на DDD.

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

Я подозреваю, что Клиент должен быть отдельным агрегатом без списка заказов, а Заказ должен быть агрегатом со списком строк заказа. Если вам необходимо выполнить операции с каждым заказом для клиента, используйте orderRepository.GetOrdersForCustomer (customerID); внесите изменения, затем используйте orderRespository.Save (order);

Что касается отслеживания изменений без ORM, есть несколько способов сделать это, например, агрегат заказов может вызывать события, которые репозиторий заказов прослушивает для удаленных строк заказа. Затем они могут быть удалены, когда единица работы завершена. Или чуть менее элегантный способ - поддерживать удаленные списки, то есть order.DeletedOrderLines, которые, очевидно, может прочитать ваш репозиторий.

Подводя итог:

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

РЕДАКТИРОВАТЬ в ответ на комментарий:

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

Однако я не должен ограничиваться «правилами» DDD, когда это, кажется, не имеет смысла, так что ... Если в маловероятном сценарии есть ряд операций, выполняемых над заказом В объекте, для которого не нужно заполнять строки заказа И , часто существует большое количество строк заказа, связанных с заказом (оба должны быть верны, чтобы я считал это проблемой) сделать это:

Имейте это личное поле в объекте заказа:

private Func<Guid, IList<OrderLine>> _lazilyGetOrderLines;

Что будет передано хранилищем заказов в заказ на создание:

Order order = new Order(this.GetOrderLines);

Где это закрытый метод в OrderRepository:

private IList<OrderLine> GetOrderLines(Guid orderId)
{
    //DAL Code here

}

Тогда свойство в строках заказа может выглядеть так:

public IEnumberable<OrderLine> OrderLines
{ 
    get 
    {
         if (_orderLines == null)
            _orderLines = _lazilyGetOrderLines(this.OrderId);

         return _orderLines;
    }
}

Редактировать 2

Я нашел этот пост в блоге, который имеет подобное решение, но немного более элегантный:

http://thinkbeforecoding.com/post/2009/02/07/Lazy-load-and-persistence-ignorance

4 голосов
/ 16 февраля 2011

1) Нужно ли загружать список заказов в методе GetCustomerById?

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

2) Что если у самого Ордера есть другое свойство списка и т. Д.

Логика собрать всех вместе должна где-то жить; хранилище связанных агрегатов так же хорошо, как и любое другое.

3) Что делать, если я хочу сделать ленивую загрузку? Где бы я поместил код для загрузки свойства Orders в клиент? В свойстве Orders есть метод доступа {}? Но тогда я должен был бы внедрить хранилище в сущность домена? я не думаю, что это правильное решение.

Лучшее решение, которое я видел, - заставить ваш репозиторий возвращать сущности домена в подклассе (используя что-то вроде Castle DynamicProxy ) - что позволяет вам сохранять невежество в вашей доменной модели.

1 голос
/ 17 февраля 2011

Другой возможный ответ - создать новый объект Proxy, который наследуется от Customer, назовите его CustomerProxy и обработать там ленивую загрузку. Все это псевдокод, так что это дает вам представление, а не просто копирует и вставляет его для использования.

Пример:

public class Customer
{
    public id {get; set;}
    public name {get; set;}
    etc...
    public virtual IList<Order> Orders {get; protected set;}
}

вот класс Customer "proxy" ... этот класс находится не на бизнес-уровне, а на уровне данных вместе с вашим Context и Data Mappers. Обратите внимание, что любые коллекции, которые вы хотите сделать lazy-load, вы должны объявить как виртуальные (я считаю, что EF 4.0 также требует, чтобы вы делали виртуальные реквизиты, как если бы раскручивали прокси-классы во время выполнения на чистых POCO, чтобы Context мог отслеживать изменения)

internal sealed class CustomerProxy : Customer
{
   private bool _ordersLoaded = false;
    public override IList<Order> Orders
    {
        get
        {
            IList<Order> orders = new List<Order>();
            if (!_ordersLoaded)
            {
                //assuming you are using mappers to translate entities to db and back
                //mappers also live in the data layer
                CustomerDataMapper mapper = new CustomerDataMapper();
                orders = mapper.GetOrdersByCustomerID(this.ID);
                _ordersLoaded = true;

                // Cache Cases for later use of the instance
                base.Orders = orders;
            }
            else
            {
                orders = base.Orders;
            }
            return orders;
        }
   }
}

Таким образом, в этом случае наш объект-сущность Customer по-прежнему свободен от вызовов кода базы данных / datamapper, а это то, что нам нужно ... "чистые" POCO. Вы делегировали ленивую загрузку прокси-объекту, который находится на уровне данных, и создает экземпляры картографов данных и делаете вызовы.

у этого подхода есть один недостаток: вызов клиентского кода не может переопределить ленивую загрузку ... он либо включен, либо выключен. Так что это зависит от вас в ваших конкретных условиях использования. Если вы знаете, что, возможно, в 75% случаев вам всегда будут нужны Заказы Клиента, ленивая нагрузка, вероятно, не лучшая ставка. Было бы лучше, если бы ваш CustomerDataMapper заполнил эту коллекцию, когда вы получите сущность Customer.

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

Если вы не используете Заказы так часто, используйте ленивую загрузку, чтобы заполнить коллекцию Заказов.

Я надеюсь, что это "правильно" и является способом ленивой загрузки правильного способа для моделей доменной модели. Я все еще новичок в этом деле ...

Mike

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