Projektowanie oparte na domenie i interakcja między domenami

10

Jestem względnym nowicjuszem w DDD, ale czytam wszystko i wszystko, co mogę, aby ugotować i wydobyć z siebie moją wiedzę.

Natknąłem się na to pytanie DDD i jedna z odpowiedzi mnie intryguje.

Konteksty i domeny związane z DDD?

W jednej z odpowiedzi plakat podaje przykład systemu e-commerce z produktami w co najmniej 2 domenach:

1) Katalog produktów 2) Zarządzanie zapasami

OK, to wszystko ma sens, tzn. W interfejsie e-commerce jesteś zainteresowany wyświetlaniem informacji o produkcie, a nie zarządzaniem zapasami.

ALE. Możesz wyświetlić poziom zapasów na stronie internetowej lub możesz wyświetlić numer wydania zapasów w magazynie (wyobraź sobie, że twoje zapasy to książki, czasopisma itp.). Te informacje pochodzą z domeny zasobów reklamowych.

Jak sobie z tym poradzisz? Czy ty

a) Załadować zarówno domenę produktu, jak i agregaty domeny zasobów reklamowych? b) Czy zachowałbyś jakieś właściwości w jednostce domeny produktu dla numeru w magazynie i edycji w magazynie, a następnie używałeś Zdarzenia domeny, aby je zaktualizować, gdy jednostka Inwentarz jest aktualizowana?

Jedno ostatnie pytanie. Wiem, że mamy zapomnieć / zignorować trwałość domeny i po prostu pomyśleć o tej domenie. Ale żeby to przemyśleć, w powyższym przykładzie otrzymalibyśmy potencjalnie 2 tabele DB dla katalogu produktów i spisu produktów. Czy używamy w nich tego samego identyfikatora, ponieważ jest to ten sam produkt. Czy możemy użyć 1 tabeli i 1 wiersza tabeli dla danych i po prostu zmapować odpowiednie dane na właściwości agregujące?

PendorPaul
źródło

Odpowiedzi:

8

Możesz wyświetlić poziom zapasów na stronie internetowej lub możesz wyświetlić numer wydania zapasów w magazynie (wyobraź sobie, że twoje zapasy to książki, czasopisma itp.). Te informacje pochodzą z domeny zasobów reklamowych.

Najważniejszą rzeczą, na którą należy zwrócić uwagę w tym momencie, jest to, że mówisz o widoku, co oznacza, że ​​używanie przestarzałych danych jest dopuszczalne.

To powiedziawszy, nie musisz wchodzić w interakcje z agregatami (które są odpowiedzialne za zapobieganie naruszeniom niezmiennika biznesowego), ale z reprezentacją najnowszej kopii stanu agregacji.

Tak więc normalnie spodziewałbym się uruchomienia zapytania względem katalogu produktów i innego uruchomienia w wykazie oraz czegoś, co połączy te dwa elementy w DTO, którego potrzebujesz do obsługi tego widoku.

Czy załadować zarówno domenę produktu, jak i agregaty domeny zasobów reklamowych?

Więc to blisko . Nie musimy ładować agregatów, ponieważ niczego nie zmienimy. Ale potrzebujemy ich państwa; żebyśmy mogli to załadować. To powiedziawszy, normalnie oczekiwałbym, że te dwie domeny będą działać w różnych procesach. Dlatego dzwonilibyśmy do obu, nie ładując obu.

Czy zachowałbyś jakieś właściwości w jednostce domeny produktu dla numeru w magazynie i edycji w magazynie, a następnie używałeś zdarzeń domeny, aby je zaktualizować, gdy jednostka zapasów jest aktualizowana?

„Nie przekraczaj strumieni. Byłoby źle”.

Wykorzystywanie zdarzeń do koordynowania informacji w różnych kontekstach domen: świetny pomysł. Wprowadzanie pojęć należących do jednej domeny do drugiej: przeciwieństwo świetnego pomysłu, poza tym bardziej.

Chcesz utrzymać domeny w czystości. Do aplikacji , które oddziałują z domenami, to nie jest tak ważne. Na przykład uzasadnione jest, aby aplikacja Inventory wywoływała usługę w aplikacji produktu w celu zapytania niektórych koncepcji specyficznych dla produktu, które można dodać do widoku. Lub odwrotnie.

Nie znam żadnego powodu, dla którego jedna aplikacja musi być ograniczona do jednej domeny. Tak długo, jak istnieje jedno źródło prawdy, możesz rozpowszechniać transakcje w dowolny sposób.

Ale żeby to przemyśleć, w powyższym przykładzie otrzymalibyśmy potencjalnie 2 tabele DB dla katalogu produktów i spisu produktów. Czy używamy w nich tego samego identyfikatora, ponieważ jest to ten sam produkt.

To byłby łatwy sposób. Mówiąc szerzej, używasz tego samego identyfikatora, ponieważ istota świata rzeczywistego jest taka sama; dwa różne konteksty ograniczone modelują tę jednostkę inaczej, ale model nie jest bytem świata rzeczywistego.

Jeśli to nie zadziała, będziesz potrzebować zapytania, aby wypełnić lukę. Myślę, że najczęstszą odmianą tego jest to, że nowszy byt zachowuje identyfikator starszego bytu. Zobaczysz to również w ciągu jednego BC: po zatwierdzeniu kandydaci zostaną klientami. Jest to inny agregat (stan związany z klientem podlega innemu niezmiennikowi niż stan wnioskodawcy); więc jeśli twoja warstwa trwałości używa strumieni zdarzeń, strumień nowego agregatu będzie wymagał innego identyfikatora. Tak więc gdzieś będzie informacja, że ​​„ten wnioskodawca został tym klientem”.

Czy możemy użyć 1 tabeli i 1 wiersza tabeli dla danych i po prostu zmapować odpowiednie dane na właściwości agregujące?

TAK! Nie rób tego. Dodajesz niezgodność transakcji bez żadnego uzasadnienia biznesowego.

VoiceOfUnreason
źródło
Zaznaczyłem to jako odpowiedź, również dla @ guillaume poniżej, który również wskazał, że odczyt danych do wyświetlenia w widoku nie wymaga ładowania agregatów. Dzięki za tak długą szczegółową odpowiedź. Wychodząc z pierwszego „tradycyjnego” modelu danych, trudno mi było zmusić się do zapomnienia o warstwie trwałości i skupienia się na języku domeny. Właśnie kiedy myślę, że go dostałem, czytam kolejny artykuł, który rozrywa moje zrozumienie.
PendorPaul
Właśnie przeczytałem ten artykuł msdn.microsoft.com/en-us/magazine/dn802601.aspx, który zawiera szczegółowe informacje o zdarzeniach domenowych w celu zduplikowania niektórych danych między kontekstami. Przykładem jest udostępnianie listy klientów między obsługą klienta a systemem przetwarzania zamówień. Jest to powielanie danych klientów w systemie zamówień i wykorzystywanie Zdarzenia do synchronizacji danych. To wbrew temu, co tutaj odpowiedzieliśmy. Z pewnością w połączonej próbce kontekst zamówienia potrzebuje tylko ID klienta, który można wypełnić w aplikacji, która ma dostęp do kontekstu obsługi klienta.
PendorPaul
Tutaj będziesz chciał być bardzo precyzyjny. Kopiowanie danych między systemami NIE jest tym samym, co kopiowanie danych między modelami domen. Za każdym razem, gdy widzisz komunikat „tylko do odczytu [mamrocz]], to duża wskazówka, że ​​dane nie należą do tej domeny (nawet jeśli aplikacja może nadal o to dbać).
VoiceOfUnreason
Tak też myślałem. Zmiana procesu myślowego jest wystarczająco trudna bez „ekspertów” publikujących artykuły, które mętnieją w wodach. Spędziłem dzisiaj naprawdę analizując moją domenę, próbując i naprawdę w pełni zrozumieć, gdzie leżą moje BC i Korzenie Agregatów. Prawie tam jestem, ale wiem też, że mogę udoskonalać i refaktoryzować mój model. Utknąłem w piekle analizy od wielu dni, martwię się, aby zacząć pisać kod, obawiając się, że nie mam DDD prosto w głowie. Myślę, że najlepiej jest włamać się i wciąż kwestionować mój kod i czy model jest odpowiedni. Dostanę się tam. Dzięki
PendorPaul
Być może źle odczytałem, ale praktyka, o której, jak sądzę, mówisz, nosi nazwę przenoszenia stanu przenoszonego przez zdarzenie (przeniesienie stanu przenoszonego przez zdarzenie) i może być bardzo potężnym modelem, szczególnie w widokach pulpitu / tabeli, w których musisz filtrować i wyświetlać dane strony między usługami. Możesz budować „projekcje” (w zasadzie widoki) ze zdarzeń domeny, aby sterować tymi pulpitami nawigacyjnymi. Użyłem go już całkiem skutecznie. Może to być bardzo potężne narzędzie we właściwym scenariuszu. Najważniejsze jest tutaj, abyś zdał sobie sprawę, że te prognozy nie reprezentują modeli domen i nie powinny zawierać logiki biznesowej.
Jordan
3

Myślę, że twoje pytanie naprawdę wymaga 2 ortogonalnych zestawów opcji -

  • Czy ładujesz dwa obiekty i prezentujesz ich dane razem, czy ładujesz 1 obiekt zawierający wszystko, co chcesz?

  • Czy używasz agregatów do wyświetlania rzeczy, czy czegoś innego?

Jeśli wierzysz w podejście CQRS, okazuje się, że agregaty mogą nie być najlepszym wyborem dla odczytów. Za każdym razem, gdy ładujesz agregat, czy chcesz wyświetlać jego dane, czy go modyfikować, dodajesz współbieżność i rywalizację do swojego systemu. Ponadto agregaty są potencjalnie większe i wolniej się ładują niż w przypadku korzystania z modeli odczytu ad-hoc dostosowanych do wyświetlania.

Rozwiązanie a) z twojego Q wydaje się podlegać wielu z tych pułapek. Opcja b) może być poprawna, ale użyłbym jej tylko wtedy, gdy dane z InventoryManagementBC są potrzebne do wymuszenia niezmienników podczas mutacji Productagregacji. Lepiej, jeśli agregat zawiera wszystkie dane potrzebne do sprawdzenia reguł biznesowych po modyfikacji, ale po stronie odczytu mogą siedzieć w dowolnym miejscu.

Jeśli chodzi o dane, powszechnym zaleceniem jest nadanie ograniczonym kontekstom własnej bazy danych (ze względu na możliwość wdrożenia i ze względu na SoC). Prawdopodobnie będziesz musiał użyć tych samych identyfikatorów, jeśli chcesz dopasować produkty między dwoma BC.

O interakcjach między BC, możesz także zajrzeć na /programming/16713041/communicating-between-two-bounded-contexts-in-ddd

guillaume31
źródło
1
OK, to pomaga. Zasadniczo używamy Agregatów Roots i BC, aby zapewnić, że nasze niezmienniki są spójne, a nasze dane są prawidłowe, a operacje, które chcemy wykonać, są zamknięte w naszym agregacie lub BC. Jeśli chodzi o odczyt i wyświetlanie tych danych, możemy sobie z tym poradzić osobno i lekko, bez uwodnienia wszystkich agregatów / BC. W końcu dlaczego musimy ładować agregat, gdy wszystko, co robimy, to wyświetlanie danych w raporcie lub na ekranie. To ma sens. Dzięki.
PendorPaul
W tym miejscu naprawdę świeci CQRS. Możesz po prostu wykonać zapytanie SQL, dołączyć do tabel i zwrócić zapytanie dostosowane do konkretnego widoku. Usuwa również twoje repozytorium z bałaganu metody zapytania. Ponadto możesz nawet replikować dane w serwisie, takie jak ElasticSearch i wysyłać do nich zapytania.
znaczenia
1

DDD jest przeznaczony do aplikacji, w których logika biznesowa jest złożona. „wydrukuj coś” nie jest złożoną logiką biznesową. To wcale nie jest logika biznesowa.

Jeśli logika biznesowa w jednym kontekście potrzebuje pewnych informacji, aby poprawnie obsłużyć pewien przypadek użycia, to informacja ta jest częścią tego kontekstu. Pomysł, że jeden ograniczony kontekst może potrzebować informacji dostępnych w innym ograniczonym kontekście, nie ma sensu, ponieważ ograniczony kontekst zawiera wszystkie potrzebne informacje.

Euforyk
źródło
OK, więc weźmy coś takiego jak Amazon, to złożony system o złożonej logice biznesowej. Posiadają zarządzanie katalogami i zarządzanie zapasami. Nie potrzebują podsumowań zapasów do zarządzania katalogiem, to znaczy nazwy produktu, opisu, typu, stanu itp. Jednak na pierwszej stronie sklepu pokazują liczbę produktów w magazynie. W tym scenariuszu, w którym chcesz oddzielić domeny zarządzania katalogiem produktów i zapasów produktów, ale musisz wyświetlić informacje o zapasach na stronie produktu, jak to zrobić?
PendorPaul
Myślę, że to, co mówię, to, że domena katalogu produktów zawiera wszystkie informacje potrzebne do zarządzania katalogiem produktów. Domena zapasów zawiera wszystkie informacje potrzebne do zarządzania zapasami. Jednak gdy chcę wyświetlić użytkownikowi pewne informacje, a dane te pochodzą z 2 domen, gdzie to zrobić. Czy po prostu ładuję obie domeny w interfejsie użytkownika i wiążę właściwości, które chcę pokazać? Czy mam jakiś mechanizm raportowania / odczytu, w którym zwracam anonimowy typ lub DTO zawierające dane potrzebne do mojego interfejsu użytkownika?
PendorPaul
To komiczne, patrząc wstecz na moje stare komentarze 11 miesięcy temu, ale wydaje się, że to całe życie. Miałeś całkowitą rację Euforię w kwestii czytania i pisania. Aplikacja, nad którą pracuję, ewoluowała w miarę ewolucji. Teraz używam metod CQRS za pośrednictwem Jimmy'ego Bogardsa Mediatra. Sama elastyczność posiadania poleceń współdziałających z agregatami, ale użycie zapytań i programów obsługi zapytań w celu przywrócenia wszystkiego, czego potrzebuję do wyświetlenia, jest niewiarygodna. Podsumuj to w widokach, które wywołują te narzędzia QueryHandlers, a oddzielenie jest dobre w tym przypadku. Dzięki
PendorPaul
@PendorPaul, myślę, że byłem tam 11 miesięcy (teraz 13) miesięcy temu. Co cię tam doprowadziło?
Greg Bell
1
@GregBell Musisz wymusić zmianę sposobu myślenia. Utknąłem w podejściu „zaprojektować bazę danych, zbudować warstwę danych, zbudować logikę biznesową ... itd.”. I byłem skoncentrowany na tworzeniu wszystkich obejmujących bytów. tj. „produkt” w witrynie eCommerce, która zajmowała się wszystkim, od wyceny, opisu, zapasów, lokalizacji zapasów… ale staje się to niezwykle skomplikowane. Podejście kontekstowe oznacza, że ​​w kontekście zapasów „Produkt” zawiera tylko informacje i zachowanie do zarządzania zapasami. Opis i obrazy są zarządzane w kontekście treści, a ceny w kontekście cen.
PendorPaul
1

Z mojego punktu widzenia istnieją różne definicje „produktu” - każdy kontekst ograniczający ma własną definicję domeny „produkt”:

  • W kontekście zarządzania treścią-kontekstu produkt ma obraz i tekst opisu.
  • W kontekście ograniczania zapasów produkt ma zapasy, sprzedawca produktu, podcasty, kiedy produkt będzie dostępny
  • W kontekście obliczania cen - ograniczenia - istnieją zasady, ile produkt może kosztować za ilość.

Oprócz tego dodałbym dodatkowy Kontekst ograniczający sklep z własną definicją produktu (odpowiednia kombinacja domen produktów innych kontekstów ograniczających).

Produkt sklepowy miałby „obraz i tekst opisu” z treści i dostępności z „Zapasów”, ale nie „sprzedawca produktu” z zapasów.

Ten dodatkowy kontekst ograniczający do sklepu zależy od zawartości ograniczającej, zawartości, zapasów, ceny

k3b
źródło
Jak więc tworzysz ten sklepowy produkt BC? Czy w tym kontekście masz odniesienie do BC produktu i ekwipunku i czy nawadniasz je ze swojego sklepu trwałości po załadowaniu BC produktu-sklepu, a następnie oferujesz właściwości, które chcesz z tych BC za pośrednictwem właściwości StoreProduct? Znalazłem ten artykuł, który jest zgodny z tym, co myślałem, krzyżując zdarzenia BC, ale @ guillaume13 powyżej wskazał, że do celów wyświetlania mogę uniknąć BC i po prostu wycofać dane potrzebne do mojego widoku. msdn.microsoft.com/en-us/magazine/dn802601.aspx
PendorPaul