DDD i MediatR - wywoływanie eventów

0

Cześć :D
Mam krótkie pytanie z architektury oprogramowania - czy publikacja eventu MediatR w event handlerze to dobry pomysł czy narusza to jednak SRP? W różnych artykułach (np.: https://cezarywalenciuk.pl/blog/programing/command-wywolujacy-kolejny-command-to-kiepski-pomysl ) jasno stoi, że wywoływanie commandów z poziomu command handlerów to kiepski pomysł i wydaje mi się to tutaj oczywiste ale jak jest w przypadku eventów? Intuicja mi podpowiada, że to również powinno być naruszenie SRP ale co w przypadku, kiedy muszę odpalić event w reakcji na wykonanie poprzedniego eventu?

Pozdrawiam :D
Dawid.

0

Robiąc to co opisałeś prosisz się o kłopoty. Co jeżeli po zapisie danych handlerze a przed rzuceniem eventu nie ważne czy w pamięci czy na kolejkę padnie aplikacja?

Zastosuj Outbox Pattern. Wtedy w ramach jednej transakcji zapisujesz dane razem z eventami do tej samej bazy ale do osobnych tabel ofc. Poza wątkiem aplikacji w tle na serwerze działa sobie usługa która cyklicznie czyta/nasłuchuje tabele z eventami i jak się pojawi nowy event do wysłania to go wysyła i po udanej transkacji aktualizuje status eventu w bazie na przeprocesowany.

Poczytaj o at least once oraz at most once delivery czyli gwarancjach dostarczenia wiadomości.

0

O i fajnie, tego mi trzeba było żeby uodpornić proces. Fajny artykuł znalazłem na ten temat (https://www.kamilgrzybek.com/blog/posts/the-outbox-pattern) i dziękuję serdecznie za tę wskazówkę!
Nie dostałem jednak odpowiedzi na inne nurtujące mnie pytanie więc dopytam - w command handlerze można publikować eventy i o ile wiem to jest dopuszczalna praktyka nienaruszająca SRP. A czy w event handlerze można publikować kolejne eventy albo wysyłać commandy/query czy to również naruszenie SRP?

1

Można w CH publikować zdarzenia nie łamiąc SRP ale masz wtedy at-most-once delivery czyli wiadomoac może zostać dostarczona raz lub wcale, innymi słowy może zostać zgubiona.

Publikować zdarzenia z event handlera też można ale tu znowu masz at-most-once delivery więc musisz sobie odpowiedzieć czy utrata wiadomoacibw twoim przypadku jest akceptowalna czy nie.

Wywoływanie query handlera z EH czy CH też nie ma większego sensu bo albo zapisujesz, albo odczytujesz, albo publikujesz zdarzenie.

Biorąc powyższe pod uwagę najlepiej jest zrobić Outbox Pattern bo wtedy masz at-least-once delivery i nie łamiesz SRP

1
markone_dev napisał(a):

Wywoływanie query handlera z EH czy CH też nie ma większego sensu bo albo zapisujesz, albo odczytujesz, albo publikujesz zdarzenie.

A często i tak się kończy wysyłaniem query, albo co gorsze, commanda z innego query/commanda :(

Pamiętam nawet kiedyś przed startem jednego projektu taką rozmowę z tech leadem, gdzie mając doświadczenia z innego projektu właśnie dopytywałem go co sądzi o wysyłaniu query/commanda z innego query/command handlera. No i wtedy na początku miał zdanie że tak nie powinno być, co generalnie chyba jest też ogólnie uważane za dobrą praktykę w przypadku korzystania z MediatR.

Nie minęły ze 2-3 miesiące developmentu i co drugi command handler odpalał jakieś inne query handlery.
Tam wydaje mi się, że to wynikało z przyjętego podziału na moduły/vertical slice w wewnętrznych warstwach, gdzie później okazywało się, że command handler z jednego modułu, jednak potrzebował danych z innego modułu. Chociaż szczerze mówiąc, nie do końca wiem jak to rozwiązać inaczej.

1

Tam wydaje mi się, że to wynikało z przyjętego podziału na moduły/vertical slice w wewnętrznych warstwach, gdzie później okazywało się, że command handler z jednego modułu, jednak potrzebował danych z innego modułu.

Być może, nie wiemy. Podstwowe pytanie co rozumiesz przez moduł czym się autorzy sugerowali wyznaczając takie a nie inne granice. Mi to wygląda na źle zaimplementowany model domenowy. Sam podział na vertical slice'y niewiele tu zmienia. Jak masz command handler to logika powinna wyglądać mniej więcej tak

public class FooCommandHandler
{
  public void Handle(Foo command)
  {
    var foo = fooRepository.Get(command.FooId); // pobierasz encję która ma wszystkie informacje potrzebne do wykonania logiki
    var fooResult = foo.DoSomething();

    // ewentualnie mielisz logikę na getterach i setterach
    
    foo.Status = FooStatus.Bar;
    foo.UpdatedOn = DateTime.UtcNow;
    
    fooRepository.Save(foo); // zapisujesz stan

    // można też opublikować zdarzenie w tym miejscu, chociaż lepiej użyć Outbox Pattern
  }
}

W przykładzie powyżej pobierając encję czy agregat Foo powinieneś mieć już w nim wszystkie informacje których potrzebujesz do wykonania logiki.

0
markone_dev napisał(a):

Być może, nie wiemy. Podstwowe pytanie co rozumiesz przez moduł czym się autorzy sugerowali wyznaczając takie a nie inne granice.

IMO wolna amerykanka, ale ja w tamtym projekcie nie brałem udziału od początku więc nie wiem jaki był tok myślowy.
Przy czym ja sam w modelowaniu domenowym nie czuję się zbyt mocny :D

Przykład - przy tworzeniu/aktualizacji danych Foo trzeba było zweryfikować poprawność części danych z zewnętrznym serwisem (później nawet pojawiło się kilka takich serwisów). Z jakiegoś powodu były to oddzielne moduły aplikacji o nazwach Foo i FooValidation. FooValidation udostępniał commanda do walidacji i moduł Foo w command handlerach Create/Update wołał sobie commanda z FooValidation.

0

W artykule, który podesłałem wyżej (https://www.kamilgrzybek.com/blog/posts/the-outbox-pattern) Autor robi implementację na Quartz.NET. W projekcie mamy Hangfire ale tak pobieżnie jak sobie czytam o Quartz to wydaje się czymś bardzo podobnym. Czy macie jakieś doświadczenia z obydwoma narzędziami i jakieś porównanie?

1

Hangfire ssie wydajnościowo jeśli masz więcej niż 10 eventów na dzień.
Quartz nie wiem, pewnie jest OK, skoro go celebryci używają.

1
somekind napisał(a):

Hangfire ssie wydajnościowo jeśli masz więcej niż 10 eventów na dzień.
Quartz nie wiem, pewnie jest OK, skoro go celebryci używają.

Co proponujesz wzamian?
Bo jak rozumiem Hangfire słaby, a z Quartzem sam nie masz doświadczenia. Tzn. musisz używać czegoś innego ;)

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