Zapewnienie spójności transakcyjnej z DDD

9

Zaczynam od DDD i rozumiem, że do zapewnienia spójności ponadnarodowej używa się zagregowanych korzeni. Nie powinniśmy modyfikować wielu agregatów w jednej usłudze aplikacji.

Chciałbym jednak wiedzieć, jak poradzić sobie z następującą sytuacją.

Mam zagregowany katalog główny o nazwie Produkty.

Istnieje również zagregowany katalog główny o nazwie Grupa.

Oba mają identyfikatory i mogą być edytowane niezależnie.

Wiele produktów może wskazywać na tę samą grupę.

Mam usługę aplikacji, która może zmienić grupę produktów:

ProductService.ChangeProductGroup(string productId, string groupId)

  1. Grupa kontrolna istnieje
  2. Pobierz produkt z repozytorium
  3. Ustaw jego grupę
  4. Napisz produkt z powrotem do repozytorium

Mam również usługę aplikacji, w której grupę można usunąć:

GroupService.DeleteGroup(string groupId) 1. Pobierz produkty z repozytorium, dla którego groupId jest ustawiony na podany groupId, upewnij się, że liczba wynosi 0 lub przerwij 2. Usuń grupę z repozytorium grup 3. Zapisz zmiany

Moje pytanie dotyczy następującego scenariusza, co by się stało, gdyby:

W ProductService.ChangeProductGroup sprawdzamy, czy grupa istnieje (tak jest), a następnie tuż po tym sprawdzeniu oddzielny użytkownik usuwa productGroup (za pośrednictwem innego GroupService.DeleteGroup). W takim przypadku umieściliśmy odniesienie do produktu, który właśnie został usunięty?

Czy to wada mojego projektu polegająca na tym, że powinienem używać innego projektu domeny (w razie potrzeby dodając dodatkowe elementy), czy też musiałbym używać transakcji?

morleyc
źródło

Odpowiedzi:

2

Mamy ten sam problem.

I nie mam sposobu, aby rozwiązać ten problem, oprócz transakcji lub kontroli spójności w bazie danych, aby uzyskać wyjątek w najgorszym przypadku.

Możesz także użyć blokady pesymistycznej, blokując zagregowany katalog główny lub tylko jego części dla innych klientów, dopóki transakcja biznesowa nie zostanie zakończona, co jest w pewnym stopniu równoważne z transakcją możliwą do serializacji.

Sposób, w jaki się poruszasz, zależy w dużej mierze od systemu i logiki biznesowej.
Współbieżność wcale nie jest łatwym zadaniem.
Nawet jeśli potrafisz wykryć problem, jak go rozwiążesz? Po prostu anulować operację lub zezwolić użytkownikowi na „scalenie” zmian?

Używamy Entity Framework, a EF6 domyślnie używa read_commited_snapshot, więc dwa kolejne odczyty z repozytorium mogą dać nam niespójne dane. Pamiętamy o tym w przyszłości, gdy procesy biznesowe zostaną bardziej precyzyjnie nakreślone i będziemy mogli podejmować błędne decyzje. I tak, nadal sprawdzamy spójność na poziomie modelu. To pozwala przynajmniej testować BL niezależnie od DB.

Proponuję również przemyśleć spójność repozytorium w przypadku „długich” transakcji biznesowych. Jak się okazało, jest to dość trudne.

Pavel Voronin
źródło
1

Myślę, że nie jest to pytanie specyficzne dla domeny, ale kwestia zarządzania zasobami.

Zasoby muszą być po prostu zablokowane podczas ich pozyskiwania .

Dzieje się tak, gdy usługa aplikacji wie z góry, że pozyskany zasób ( Groupagregat, w twoim przykładzie) zostanie zmodyfikowany. W projektowaniu opartym na domenie blokowanie zasobów jest aspektem należącym do repozytorium.

rucamzu
źródło
Nie, blokowanie nie jest „koniecznością”. W rzeczywistości jest to dość okropne z długoterminowymi transakcjami. Lepiej jest korzystać z optymistycznej współbieżności, którą każdy nowoczesny ORM obsługuje od razu po wyjęciu z pudełka. Wielką zaletą optymistycznej współbieżności jest to, że działa ona również z nietransakcyjnymi bazami danych, o ile obsługują one aktualizacje atomowe.
Aaronaught
1
Zgadzam się z wadami blokowania wewnątrz długotrwałych transakcji, ale scenariusz opisany przez @ g18c sugeruje coś wręcz przeciwnego, to znaczy krótkie operacje na poszczególnych agregatach.
rucamzu
0

Dlaczego nie przechowujesz identyfikatorów produktów w podmiocie Grupy? W ten sposób masz do czynienia tylko z jednym agregatem, który ułatwi ci wszystko.

Będziesz wtedy musiał zaimplementować pewien rodzaj wzorca współbieżności, np. Jeśli wybierzesz optymistyczną współbieżność, po prostu dodaj właściwość wersji do encji Grupy i wyrzuć wyjątek, jeśli wersje nie pasują podczas aktualizacji, tj.

Dodaj produkt do grupy

  1. Sprawdź, czy produkt istnieje
  2. Pobierz grupę z repozytorium (w tym właściwość id wersji)
  3. Group.Add (ProductId)
  4. Zapisz grupę za pomocą repozytorium (zaktualizuj identyfikator nowej wersji i dodaj klauzulę where, aby upewnić się, że wersja się nie zmieniła).

Wiele ORM ma optymistyczną współbieżność wbudowaną za pomocą identyfikatorów wersji lub znaczników czasu, ale łatwo jest stworzyć własną. Oto dobry post na temat tego, jak to zrobić w Nhibernate http://ayende.com/blog/3946/nhibernate-mapping-concurrency .

Scott Millett
źródło
0

Chociaż transakcje mogą pomóc, istnieje inny sposób, zwłaszcza jeśli Twój scenariusz jest ograniczony tylko do kilku przypadków.

Możesz włączyć kontrole do zapytań, które powodują mutacje, skutecznie czyniąc każdą parę kontroli i mutacji operacją atomową.

Wewnętrzne zapytanie aktualizujące produkt dołącza do grupy (nowo przywołanej). Powoduje to, że nic nie aktualizuje, jeśli ta grupa zniknie.

Kwerenda usuwająca grupę dołącza do wszystkich produktów do niej wskazujących i ma dodatkowy warunek, że (dowolna kolumna) wynik łączenia musi być zerowy. Powoduje to, że nic nie usuwa, jeśli wskazują na to jakiekolwiek produkty.

Zapytania zwracają liczbę wierszy, które pasowały lub zostały zaktualizowane. Jeśli wynik wynosi 0, oznacza to, że przegrałeś wyścig i nic nie zrobiłeś. Może zgłaszać wyjątek, a warstwa aplikacji może obsłużyć to, co chce, na przykład próbując od początku.

Timo
źródło