Zamodelowanie agregatu procesu akceptacji

2

Witam,

potrzebuję pomocy w odpowiednim zamodelowaniu agregatu, jakim jest akceptacja zadania. Wyróżniamy wiele typów zadań np. akceptacja polisy, akceptacja anulacji polisy itd. W zależności od typu operacji, zadanie musi być zaakceptowane przez 1, 2 lub więcej osób. Jest to w pełni konfigurowalne, a samo sprawdzanie odbywa się po Roli, lub po konkretnym użytkowniku.
Przykładowo: żeby zadanie zostało zaakceptowano musi pójść approve od kogoś z rolą administrator, lub od użytkownika Jan Kowalski. Tego typu warunku można ze sobą dowolnie łączyć przez grupowanie działań w nawisach i operatory "lub", "i".
screenshot-20220519141147.png

Mam jednak problem z zamodelowaniem zadania do akceptacji

screenshot-20220519141218.png

Czy agregat powinien wiedzieć o konfiguracji warunków do spełnienia? Czy mogą mu być one dostarczone do metody Accept w postaci obiektu działaniem przypominającym wzorzec specyfikacji? Głównym problemem jest to, że najpierw muszę sprawdzić czy wpadła pojedyncza akceptacja, a później czy całe zadanie można zaakceptować.

Poniżej wklejam roboczy kod.

public Result Accept(AcceptanceRequirements requirements, Approver approver)
        {
            if (!requirements.CanApprove(approver))
            {
                return new CannotApproveFailure();
            }
            
            AddApprovalBy(approver);
            _events.Add(new ApprovalAddedToTaskEvent(approver.Id));

            if (requirements.AllAcceptanceConditionsMet(this))
            {
                MarkAsAccepted();
                _events.Add(new TaskAcceptedEvent());
            }

            return Result.Success();
        }

Gdyby do akceptacji była wymagana tylko jedna osoba, bez żadnych dodatkowych warunków, to w agregacie trzymałbym informację o tym kto musi zatwierdzić dane zadanie. Tutaj jednak warunki akceptacji są skomplikowane, dlatego widzę je jako odrębny byt, który mogę zserializowany zapisac w bazie, a następnie odtworzyć w postaci specyfikacji.

Jak Wy byście podeszli do tego zadania?
Pozdrawiam

3

Nie modelujesz "agregatu akceptacji", tylko proces akceptacji (o ile zrozumiałem sens). Interesuje cię nie tylko "stan", ale również kto, kiedy i jaką akcję (np. akceptację) wykonał:

Czyli wynikowy interface powinien wyglądać z grubsza tak:

interface BusinessFlowItem{
  BusinessState getBusinessState();
  List<Action> getPossibleActions();
  List<Action> getHistory();
  ActionResult executeAction(Action action);

  //jakieś metody do sprawdzenia co właściwie powinno zostać zaakceptowane
}

Implementacja tego interface to już szczegół :)
Zakładając, że to jest proste, wszystko ma być "w kodzie", czyli będziemy mieli jakieś osobne klasy do obsługi różnych flow, bo idealnie nadaje się to pod zastosowanie jakiegoś silnika reguł biznesowych, to całość danych, jakie bym przechowywał, to historia akcji wykonanych na tym obiekcie: utworzony, zaakceptowany, odrzucony, cofnięty do poprawki itp.
Na podstawie akcji z historii obliczałbym aktualny stan np. ACCEPTED, REJECTED, IN_PROGRESS itd.
Na podstawie aktualnego stanu, tworzyłbym possibleActions - tutaj przyda się koncepcja maszyny stanów. W dostępnych akcjach możesz podać dodatkowe informacje, kto może taką akcję wykonać. Znajdziesz gotowe systemy do definiowania takich workflow. Proces potrzebuje swojej wersji, bo może zmienić się w momencie przetwarzania takiego zadania i wtedy całość zawiśnie w niebycie
Jeżeli akcja, którą próbujesz wykonać w executeAction, jest na liście możliwych, to dopisujesz ją na końcu listy, zwracasz OK i zrobione. Jak się nie znajduje, to zwracasz błąd, jak wygodniej.

0

Być może nie byłem wystarczająco precyzyjny w opisie zadania.

  1. Zadania do akceptacji nie potrzebują swojej wersji, ponieważ przy zmianie warunków akceptacji, będą aktualizowane. Dla przykładu - zmieniam zasady, zamiast dwóch osób do akceptacji jest wymagana jedna. Aktualizuje wtedy wszystkie zadania oczekujące na akceptację, Jeśli któreś zadania miało już jedną wymaganą akceptację, automatyczne oznaczam je jako zadanie zaakceptowane i publikuję event, że zadanie zostało zaakceptowane. To już po stronie subskrybentów tego eventu jest decyzja co dalej z tym robią.

  2. Zadanie do akceptacji może być jedynie: utworzone, zaakceptowane, odrzucone. Nie ma przewidzianej możliwości poprawki.

  3. Odnośnie possibleActions -> tutaj zawsze będzie jedynie możliwość zaakceptowania i odrzucenia. Jeśli dana osoba może zaakceptować, to też zawsze może odrzucić. Nie ma tutaj przewidzianej możliwości rozbicia na inne akcje do których będą różne uprawnienia.

  4. Historia zadania do akceptacji będzie zapisywana, tj. jakie operacje, kto i kiedy wykonał na zadaniu.

  5. Jeśli chodzi o warunki akceptacji: możemy jedynie wskazać wymaganą rolę, lub konkretnego użytkownika. Żadnych innych możliwości tutaj nie będzie.

Jeśli zadanie oczekuje na akceptację to zawsze sprawdzamy warunki akceptacji na ten moment. W momencie utworzenia zadania do akceptacji nie wiemy tak naprawdę na jakich warunkach zostanie ono finalnie zaakceptowane. Jeśli te się zmienią to zadanie musi być zaakceptowane na podstawie nowych warunków. Dlatego warunki akceptacji nie mogą być zapisane w zadaniu do akceptacji, bo zawsze akceptujemy na aktualnych zasadach.

Dlatego też kluczowe jest dla mnie wiedzieć, czy warunki te mogą zostać przekazane jako parametr do metody Accept?

@piotrpo: Dziękuję za pomoc. Myślę, że Twój sposób lepiej by się sprawdził gdyby problem był bardziej skomplikowany. Być może się mylę, muszę się z tym przespać :)

1

Dalej próbujesz przekombinowywać prostą sprawę i wydaje ci się, że będzie prościej. Jeżeli masz już historię w postaci akcji, które były robione, to masz tylko prościej. Kiedyś napisałem coś takiego, może ci się przyda https://github.com/piotrpo/SimpleFsm

0

Dalej nie rozumiem, jaki problem rozwiązuje mi tutaj maszyna stanów?

3

Ogólnie, workflow to zawsze jest maszyna stanów, nawet jeżeli ci się wydaje, że nie jest. Jeżeli masz coś prostego typu stany:

  • Procesowany
  • Zatwierdzony
  • Odrzucony

i proste przejścia pomiędzy, dalej jest maszyna stanów, tylko da się ją dość prosto ogarnąć if'ami. Jeżeli to się rozrasta (a z tego co piszesz, to się rozrośnie), to kodowanie tego tworzy zwykle kosmicznego potworka if'ów, którego trudno doprowadzić do stanu oczekiwanego przez biznes, a zmiany w nim robią się wyjątkowo wredne.
Ogólnie największy problem jaki jest przy implementacji, to oifowanie warunków przejść. Przykład:

Wniosek urlopowy,

  • musi zostać zatwierdzony przez bezpośredniego przełożonego, szefa działu i kadry
  • jeżeli bezpośredni przełożony nie zatwierdzi go przez 7 dni, to wystarczy zatwierdzenie szefa działu
  • kadry mogą odrzucić wniosek w dowolnym momencie, chyba, ze już go zatwierdziły
  • do momentu pójścia na urlop wniosek może zostać cofnięty przez kogoś tam, ale wtedy kadry muszą zostać o tym powiadomione

To co jest na githubie pisałem pod dość prosty flow do uzgadniania terminu spotkania pomiędzy kilkoma aktorami, gdzie tych było dosłownie kilka stanów i kilka tranzycji, z jakimiś tam warunkami przy każdej. Skończyło się na napisaniu tego co wyżej, bo zwyczajnie narysowanie diagramu stanów, pokazanie go biznesowi i ręczne przeklepanie go w te enumy było szybsze i bardziej niezawodne, niż "proste kodowanie".

Ogólnie przymierzałem się wtedy do tego, żeby dać im jakieś narzędzie do UML'a lub nawet zrobić coś prostego i dopisać generator kodu, lub coś na ten kształt.

Plusy takiego poejścia są takie, że możesz:

  • prosto zapisywać stac (właśnie w postaci historii przejść), wrzucasz to do bazy danych, w razie czego przepuszczasz historię przez maszynę i masz aktualny stan
  • prosto odpowiedzieć jakie guziki można wyświetlić w danym momencie danemu użytkownikowi
  • możesz dość prosto zapisywać definicję takiej maszyny w jakimś dokumencie i ogólnie wyciągnąć ją z kodu do zewnętrznej konfiguracji
4

Mamy tutaj chyba 2 tematy:

  1. Projektowanie agregatow
  2. Orkiestracja procesu biznesowego

Po kolei:
Ad. 1) zacząłbym od wymyślenia API (jaki command mam obsłużyć, co dostaję na wejściu) oraz jakie warunki muszą być spełnione, aby taką operację zrealizować (tzw. niezmienniki). To jest generalnie trudne i kluczowe, ponieważ ustawia powiązania między domenami i serwisami.
Ad. 2) tutaj z kolei są 2 powiązane ze sobą tematy - (A) zamodelowanie stanu agregatu (np. wniosek utworzony, wniosek zaakceptowany, wniosek usunięty itd) oraz (B) oczekiwanie na różne zdarzenia, które zmieniają stan, które mogą przyjść w rożnej kolejności itd.
Ad. A) może wystarczyć pole „status”, ale warto rozważyć wydzielenie osobnych agregatów per status, jeśli miałyby udostępniać różne API. Inaczej będzie dużo ifologii na statusie, ale moze nie hyc to problemem w przypadku small-mid size serwisu.
Ad. B) tutaj wspomniana maszyna stanów, nazywana również w literaturze process managerem

1 użytkowników online, w tym zalogowanych: 0, gości: 1