Domain Driven Design: jak uzyskać dostęp do potomka zagregowanego korzenia

Jeśli mam klasę Porządkową a jako zagregowany root i 1000 pozycji liniowych.

Jak załadować tylko jeden z 1000 pozycji liniowych? O ile rozumiem, do elementu liniowego można uzyskać dostęp tylko za pośrednictwem klasy Order i ma" lokalną " tożsamość. Czy nadal mogę utworzyć metodę repozytorium w OrderRepository, taką jak "GetLineItemById"?

Edit to comment the answer: Obecnie nie sądzę, że jest to rozsądne, aby mieć niezmienne dzieci. Co jeśli mam klasę klienta z kilkoma adresy, umowy i jeszcze więcej kolekcji dzieci. Ogromna istota, na której chcę wykonywać metody CRUD.

I would have

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

Czy muszę zrobić coś takiego, jeśli użytkownik poprawi ulicę adresu lub własność umowy?

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

    public void SetStreetNumberOfAddress(Address address, streetNumber){}
}
Klasa klienta byłaby wtedy pełna metod manipulacji dziećmi. Więc wolałabym zrobić
addressInstance.Street = "someStreet";
Chyba nie rozumiem całej koncepcji.. :)
Author: Chris, 2010-01-20

3 answers

1) nie ma nic złego w dostępie do dzieci zagregowanego korzenia za pomocą prostych właściwości tylko do odczytu lub metod get.

Ważne jest, aby upewnić się, że wszystkie interakcje z dziećmi są pośredniczone przez zagregowany korzeń, tak aby było jedno, przewidywalne miejsce do zagwarantowania niezmienników.

Więc Order.LineItems jest w porządku, o ile zwraca niezmienny zbiór (publicznie) niezmiennych obiektów. Podobnie Order.LineItems[id]. Dla przykładu patrz źródło dla canonical Evans-approved DDD example , gdzie Klasa zagregowanego korzenia Cargo ujawnia kilka swoich dzieci, ale prawa dziecka są niezmienne.

2) zagregowane korzenie mogą zawierać odniesienia do innych zagregowanych korzeni, po prostu nie mogą się zmieniać.

Jeśli masz "niebieską księgę" (Domain-Driven Design), zobacz przykład na stronie 127, który pokazuje, jak możesz mieć Car.Engine, gdzie zarówno Car, jak i Engine są zagregowanymi korzeniami, ale silnik nie jest częścią samochodu agregacji i nie można wprowadzać zmian w silniku przy użyciu żadnej z metod Car (lub odwrotnie).

3) w domain-driven design, nie musisz sprawiać, że wszystkie Twoje klasy skupiają korzenie lub dzieci z agregatów. Potrzebujesz tylko zagregowanych korzeni, aby zamknąć złożone interakcje między spójną grupą klas. Klasa Customer, którą zaproponowałeś, brzmi tak, jakby nie była zbiorczym korzeniem-tylko zwykłą klasą, która zawiera odniesienia do agregatów Contract i Address.

 18
Author: Jeff Sternal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 21:50:38

Kiedy mówisz load w "jak załadować tylko jedną z 1000 pozycji liniowych?"masz na myśli" ładowanie z bazy danych"? Innymi słowy, jak załadować tylko jeden element potomny zagregowanego korzenia z bazy danych?

Jest to nieco skomplikowane, ale możesz sprawić, że Twoje repozytoria zwrócą pochodną zagregowanego korzenia, którego pola są leniwie załadowane. Np.

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"); }
    }
}

Wywołujący OrderRepository.Get(int) otrzymałby coś, co jest faktycznie tylko obiektem zamówienia, ale jest w rzeczywistości / Align = "left" / Oczywiście, aby to zrobić, twoje zagregowane korzenie muszą zapewnić Wirtualnego członka lub dwa i być zaprojektowane wokół tych punktów rozszerzeń.

Edit to address question updates

W przypadku adresu traktowałbym go jako obiekt value, czyli niezmienne kompozycje danych, które są razem traktowane jako pojedyncza wartość.

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;}
}

Następnie, aby zmodyfikować agregat, tworzysz nową instancję adresu. Jest to analogiczne do zachowania DateTime. Możesz również dodać metody metody do adresu, takie jak SetStreet(string), ale powinny one zwracać nowe instancje adresu, tak jak metody DateTime zwracają nowe instancje DateTime.

W Twoim przypadku obiekty o niezmiennej wartości adresu muszą być połączone z pewną obserwacją kolekcji adresów. Prostą i czystą techniką jest śledzenie dodanych i usuniętych wartości adresowych w oddzielnych kolekcjach.

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);
    }
}

Alternatywnie możesz cofnąć adresy za pomocą ObservableCollection<Address>.

To rzeczywiście czyste rozwiązanie DDD, ale wspomniałeś o NHibernate. Nie jestem ekspertem NHibernate, ale wyobrażam sobie, że będziesz musiał dodać trochę kodu, aby NHibernate wiedział, gdzie są przechowywane zmiany adresów.

 10
Author: G-Wiz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 01:19:51

Gdy musisz uzyskać dostęp do elementu potomnego przez Id, sprawia, że element potomny jest zbiorczym korzeniem. Nie ma nic złego w tym, że korzenie zbiorcze mają inne zbiorcze korzenie jako dzieci, a nawet dzieci z odniesieniem do rodzica. Osobne repozytorium dla jednostki potomnej jest w porządku. Kiedy zagregowane korzenie trzymają zagregowane korzenie, musimy pamiętać o pojęciu "ograniczonych kontekstów", aby zapobiec łączeniu zbyt dużych części domeny ze sobą i utrudniać zmianę kodu. Kiedy dzieje się tak, powodem jest większość czasu, że zagnieżdżone korzenie zagnieżdżają się głęboko. Nie powinno to być problemem w Twoim przypadku, zagnieżdżanie lineitemów w kolejności brzmi bardzo rozsądnie.

Aby odpowiedzieć na pytanie, czy należy zagnieżdżać pozycje linii, mam teraz dlaczego chcesz załadować pozycje linii według id, a sprzedaż 1000 pozycji na zamówienie brzmi jak aplikacja będzie sprzedawać dużo?

Gdy zagnieżdżasz pozycje liniowe w zamówieniu i oczekujesz, że zamówienia będą miały dużo elementy linii, można spojrzeć na kilka mapowania/buforowania / query-loading opcji, aby duże zamówienia wykonać jak jest potrzebne przez aplikację. Odpowiedź, jak najszybciej załadować elementy linii, zależy od kontekstu, w którym go używasz.

 1
Author: Paco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-20 22:47:44