Область, которую Ведут Дизайном: Как получить доступ к ребенку совокупного корня

Если у меня есть класс Заказа как совокупный корень и 1 000 позиций.

Как я загружаю только одну из этих 1 000 позиций? Насколько я понимаю, к позиции можно только получить доступ через класс Заказа и имеет «местную» идентичность. Я все еще создал бы метод хранилища в OrderRepository как «GetLineItemById»?

Отредактируйте, чтобы прокомментировать ответ: В настоящее время я не думаю, что разумно иметь неизменных детей. Что, если у меня есть Потребительский класс с несколькими адресами, контрактами и еще большим количеством детских коллекций. Огромное предприятие я хочу выполнить методы ГРЯЗИ на.

Я имел бы

public class Customer
{
    public IEnumerable<Address> Addresses { get; private set; }
    public IEnumerable<Contracts> Contracts { get; private set; }
    ...
}

Я должен был бы сделать что-то вроде этого, если пользователь исправляет улицу адреса или собственность контракта?

public class Customer
{
    public void SetStreetOfAddress(Address address, street){}

    public void SetStreetNumberOfAddress(Address address, streetNumber){}
}

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

addressInstance.Street = "someStreet";

Я думаю, что неправильно понимаю целое понятие..:)

25
задан Chris 20 January 2010 в 21:58
поделиться

3 ответа

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

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

Итак, Order.LineItems в порядке, если он возвращает неизменяемую коллекцию (общедоступных) неизменяемых объектов. Аналогично Order.LineItems [id] . В качестве примера см. источник канонического утвержденного Эвансом примера ddd , где агрегированный корневой класс Cargo предоставляет несколько своих дочерних элементов, но дочерние элементы неизменны.

2) Совокупные корни могут содержать ссылки на другие агрегированные корни, они просто не могут менять друг друга.

Если у вас есть «синяя книга» ( Domain-Driven Design ), см. Пример на странице 127, где показано, как у вас может быть Car.Engine , где оба Автомобиль и Двигатель являются совокупными корнями, но двигатель не является частью агрегата автомобиля, и вы не можете вносить изменения в двигатель с помощью любого из методов Car (или наоборот).

3) При проектировании, основанном на предметной области, вам не нужно делать все классы агрегированными корнями или дочерними элементами агрегатов. Вам нужны только агрегированные корни, чтобы инкапсулировать сложные взаимодействия между сплоченной группой классов.Предложенный вами класс Customer звучит так, как будто он вообще не должен быть агрегированным корнем - просто обычный класс, содержащий ссылки на агрегаты Contract и Address .

20
ответ дан 28 November 2019 в 21:39
поделиться

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

Чтобы ответить на вопрос, если вы должны представить элементы строки, у меня есть теперь, почему вы хотите загрузить элементы строки по ID, и продавать 1000 элементов на заказ звуки, как приложение, будет много продавать?

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

1
ответ дан 28 November 2019 в 21:39
поделиться

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

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

namespace Domain
{
    public class LineItem
    {
        public int Id { get; set; }
        // stuff
    }

    public class Order
    {
        public int Id { get; set; }

        protected ReadOnlyCollection<LineItem> LineItemsField;
        public ReadOnlyCollection<LineItem> LineItems { get; protected set; }
    }

    public interface IOrderRepository
    {
        Order Get(int id);
    }
}

namespace Repositories
{
    // Concrete order repository
    public class OrderRepository : IOrderRepository
    {
        public Order Get(int id)
        {
            Func<IEnumerable<LineItem>> getAllFunc = () =>
                {
                    Collection<LineItem> coll;
                    // { logic to build all objects from database }
                    return coll;
                };
            Func<int, LineItem> getSingleFunc = idParam =>
                {
                    LineItem ent;
                    // { logic to build object with 'id' from database }
                    return ent;
                };

            // ** return internal lazy-loading derived type **
            return new LazyLoadedOrder(getAllFunc, getSingleFunc);
        }
    }

    // lazy-loading internal derivative of Order, that sets LineItemsField
    // to a ReadOnlyCollection constructed with a lazy-loading list.
    internal class LazyLoadedOrder : Order
    {
        public LazyLoadedOrder(
            Func<IEnumerable<LineItem>> getAllFunc,
            Func<int, LineItem> getSingleFunc)
        {
            LineItemsField =
                new ReadOnlyCollection<LineItem>(
                    new LazyLoadedReadOnlyLineItemList(getAllFunc, getSingleFunc));
        }
    }

    // lazy-loading backing store for LazyLoadedOrder.LineItems
    internal class LazyLoadedReadOnlyLineItemList : IList<LineItem>
    {
        private readonly Func<IEnumerable<LineItem>> _getAllFunc;
        private readonly Func<int, LineItem> _getSingleFunc;

        public LazyLoadedReadOnlyLineItemList(
            Func<IEnumerable<LineItem>> getAllFunc,
            Func<int, LineItem> getSingleFunc)
        {
            _getAllFunc = getAllFunc;
            _getSingleFunc = getSingleFunc;
        }

        private List<LineItem> _backingStore;
        private List<LineItem> GetBackingStore()
        {
            if (_backingStore == null)
                _backingStore = _getAllFunc().ToList(); // ** lazy-load all **
            return _backingStore;
        }

        public LineItem this[int index]
        {
            get
            {
                if (_backingStore == null)        // bypass GetBackingStore
                    return _getSingleFunc(index); // ** lazy-load only one from DB **

                return _backingStore[index];
            }
            set { throw new NotSupportedException(); }
        }

        // "getter" implementations that use lazy-loading
        public IEnumerator<LineItem> GetEnumerator() { return GetBackingStore().GetEnumerator(); }

        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

        public bool Contains(LineItem item) { return GetBackingStore().Contains(item); }

        public void CopyTo(LineItem[] array, int arrayIndex) { GetBackingStore().CopyTo(array, arrayIndex); }

        public int Count { get { return GetBackingStore().Count; } }

        public bool IsReadOnly { get { return true; } }

        public int IndexOf(LineItem item) { return GetBackingStore().IndexOf(item); }

        // "setter" implementations are not supported on readonly collection
        public void Add(LineItem item) { throw new NotSupportedException("Read-Only"); }

        public void Clear() { throw new NotSupportedException("Read-Only"); }

        public bool Remove(LineItem item) { throw new NotSupportedException("Read-Only"); }

        public void Insert(int index, LineItem item) { throw new NotSupportedException("Read-Only"); }

        public void RemoveAt(int index) { throw new NotSupportedException("Read-Only"); }
    }
}

Абоненты orderrepository.get (int) получит что-то, что эффективно просто объект заказа, но на самом деле находится лазиловый заказ. Конечно, для этого ваши совокупные корни должны предоставить виртуальный элемент или два и оформлены вокруг этих точек расширения.

Отредактируйте для решения обновлений вопроса

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

public class Address
{
  public Address(string street, string city)
  {
    Street = street;
    City = city;
  }
  public string Street {get; private set;}
  public string City {get; private set;}
}

Затем, чтобы изменить агрегат, вы создаете новый экземпляр адреса. Это аналогично поведению DateTime. Вы также можете добавить методы методов для адреса, такие как SetStreet (String) , но они должны вернуть новые экземпляры адреса, так же как методы DateTime возвращает новые экземпляры DateTime.

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

public class Customer
{
    public IEnumerable<Address> Addresses { get; private set; }

    // backed by Collection<Address>
    public IEnumerable<Address> AddedAddresses { get; private set; } 

    // backed by Collection<Address>
    public IEnumerable<Address> RemovedAddresses { get; private set; }

    public void AddAddress(Address address)
    {
      // validation, security, etc
      AddedAddresses.Add(address);
    }

    public void RemoveAddress(Address address)
    {
      // validation, security, etc
      RemovedAddresses.Add(address);
    }

    // call this to "update" an address
    public void Replace(Address remove, Address add)
    {
      RemovedAddresses.Add(remove);
      AddedAddresses.Add(add);
    }
}

В качестве альтернативы вы можете вернуться адреса с помощью наблюдательного элемента <адрес> .

Это действительно чистое решение DDD, но вы упомянули NHIBERNATE. Я не эксперт NhiBernate, но я представляю, вам придется добавить какой-код, чтобы сообщить Nibernate знать, где хранятся изменения в адресах.

10
ответ дан 28 November 2019 в 21:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: