Próbuję nauczyć się DDD i powiązanych tematów. Wpadłem na pomysł prostego, ograniczonego kontekstu, aby zaimplementować „bank”: istnieją konta, pieniądze można wpłacać, wypłacać i przenosić między nimi. Ważne jest również, aby zachować historię zmian.
Zidentyfikowałem podmiot konta i pozyskanie tego zdarzenia byłoby dobrze śledzić zmiany w nim. Inne byty lub obiekty wartości nie mają znaczenia dla problemu, więc nie wspomnę o nich.
W przypadku wpłat i wypłat - jest to stosunkowo proste, ponieważ zmodyfikowano tylko jeden agregat.
Podczas transferu jest inaczej - dwa agregaty muszą zostać zmodyfikowane przez jedno zdarzenie MoneyTransfers . DDD przestaje modyfikować modyfikowanie wielu agregatów w jednej transakcji. Z drugiej strony zasada pozyskiwania zdarzeń polega na stosowaniu zdarzeń do podmiotów i modyfikowaniu stanu na ich podstawie. Gdyby zdarzenie mogło być przechowywane po prostu w bazie danych, nie byłoby problemu. Aby jednak nie dopuścić do jednoczesnej modyfikacji jednostek pochodzących ze zdarzeń, musimy zaimplementować coś zmieniającego strumień zdarzeń każdego agregatu (aby zachować granice transakcji). Z wersjonowaniem wiąże się kolejny problem - nie mogę używać prostych struktur do przechowywania zdarzeń i odczytywania ich z powrotem w celu zastosowania ich do agregacji.
Moje pytanie brzmi - jak mogę połączyć te trzy zasady: „jedna agregacja jedna transakcja”, „zdarzenie-> zmiana agregacji” i „jednoczesne zapobieganie modyfikacjom”?
źródło
Ważny szczegół w zrozumieniu rachunków transakcyjnych:
balance
atrybutaccount
jest w rzeczywistości przykładem denormalizacji. Jest tam dla wygody. W rzeczywistości saldo konta jest sumą jego transakcji i tak naprawdę nie potrzebujesz samego konta, aby mieć saldo.Mając to na uwadze, przekazanie pieniędzy nie powinno polegać na aktualizacji,
account
ale na wkładaniutransaction
.Biorąc to pod uwagę, istnieje jeszcze jedna ważna zasada: czynność dodawania a
transaction
powinna być atomowa z aktualizacją (zdormalizowanego pola bilansu)account
.Teraz, jeśli rozumiem pojęcie agregatów DDD, wydaje się, że istotne są następujące kwestie:
Pod względem projektu DDD sugerowałbym:
Istnieje jeden agregat reprezentujący przeniesienie
Agregat składa się z następujących obiektów: transfer (obiekt główny); obiekt główny jest powiązany z dwiema listami transakcji (po jednej dla każdego konta); a każda lista transakcji jest połączona z jednym kontem.
Cały dostęp do transferu powinien medytować obiekt główny (the
transfer
).Jeśli próbujesz wdrożyć obsługę transferu asynchronicznego, Twój główny kod powinien martwić się o utworzenie transferu w stanie „oczekującym”. Możesz mieć inny wątek lub zadanie, które faktycznie przenosi pieniądze (wkładanie do historii transakcji, a tym samym aktualizowanie sald) i ustawia przelew na „zaksięgowany”.
Jeśli chcesz wdrożyć blokującą transakcję transferu w czasie rzeczywistym, logika biznesowa powinna utworzyć
transfer
obiekt, który koordynowałby inne działania w czasie rzeczywistym.Jeśli chodzi o zapobieganie problemom z współbieżnością, pierwszym zleceniem powinno być wstawienie transakcji debetowej do listy transakcji dla konta źródłowego (oczywiście aktualizacja salda). Musiałoby to być wykonywane atomowo na poziomie bazy danych (za pomocą procedury składowanej). Po zaksięgowaniu obciążenia reszta przelewu powinna zakończyć się powodzeniem bez względu na problemy z współbieżnością, ponieważ nie powinna istnieć żadna reguła biznesowa uniemożliwiająca uznanie konta docelowego.
(W prawdziwym świecie konta bankowe mają koncepcję postu z notatkami, który obsługuje koncepcję leniwego zatwierdzania dwufazowego. Utworzenie tego postu jest lekkie i łatwe, a także można je bez problemu wycofać. Konwersja memo post na twardy post ma miejsce, gdy pieniądze faktycznie się poruszają - nie można tego przywrócić - i reprezentują drugą fazę zatwierdzenia dwufazowego, występującą dopiero po sprawdzeniu wszystkich reguł sprawdzania poprawności).
źródło
Jestem również na etapie uczenia się. Z punktu widzenia realizacji, właśnie tak czuję, że wykonasz tę akcję.
Dispatch TransferMoneyCommand, który wywołuje następujące zdarzenia [MoneyTransferEvent, AccountDebitedEvent]
Uwaga: zanim pojawi się te zdarzenie, należy przeprowadzić powierzchowną weryfikację poleceń i weryfikację logiki domeny, tj. Czy konto ma wystarczającą saldo?
Utrwal zdarzenia (z wersją), aby upewnić się, że nie występują problemy ze spójnością. Zauważ, że może istnieć inne równoległe polecenie (takie jak wycofanie wszystkich pieniędzy), któremu udało się odnieść sukces i zapisać zdarzenia przed tym, więc bieżący stan agregatu może być nieaktualny, a zatem zdarzenia są wywoływane w starym stanie i są niepoprawne. Jeśli zapisywanie zdarzeń się nie powiedzie, będziesz musiał ponowić polecenie od początku.
Po pomyślnym zapisaniu zdarzeń w bazie danych możesz opublikować dwa zdarzenia, które zostały zgłoszone.
AccountDebitedEvent usunie pieniądze z konta płatnika (zaktualizuje stan zagregowany i wszelkie powiązane modele widoku / projekcji)
MoneyTransferEvent uruchamia Saga / Process Manager.
Zadaniem menedżera sagi / procesu będzie próba uznania rachunku odbiorcy, jeśli to się nie powiedzie, będzie musiał zwrócić saldo z powrotem płatnikowi.
Menedżer Saga / Procesu opublikuje CreditAccountCommand, który jest stosowany do rachunku odbiorcy, a jeśli zakończy się powodzeniem, zostanie wygenerowany AccountCreditedEvent.
Z punktu widzenia pozyskiwania zdarzeń, jeśli chcesz cofnąć to działanie, wszystkie zdarzenia w tej transakcji będą miały identyfikator korelacji / związku przyczynowego jako oryginalny TransferMoneyCommand, którego możesz użyć do wywołania zdarzeń dla operacji cofania / cofania.
Możesz sugerować wszelkie problemy lub potencjalne ulepszenia powyższego.
źródło