EF + repository pattern + Unit of Work - czy to zawsze taki zły pomysł i czy CQRS jest lekiem na wszystko nawet bez DDD?

1

Cześć,
poczytałem trochę wątków na forum np. ten i mam spory niedosyt. Wiem, że łatwo jest obśmiać takie podejście, ale nie czuję w każdym przypadku takiego odstręczenia od EF + repository&UoW pattern, jak to się każdy naśmiewa z wyższością dla takich rozwiązań. Dlatego proszę o grzeczne wytłumaczenie dziur w mojej logice.

Załóżmy że mamy WebAPI + EF na już istniejący od dawna kawałek bazy. Nie ma mowy o żadnym DDD. Operacje to nie tylko CRUD na tabelkach (chociaż też), ale dużo bardziej skomplikowane zapytania i również miejscami złożony zapis.

W akcji kontrolera chcemy mieć jak najmniej kodu, to co można jak np. walidacja realizujemy poprzez middlewary. Nie ma tu też miejsca na logikę biznesową, więc większość wydzieli service, managera czy jak kto nazywa ten znienawidzony i prawie zawsze bezsensowny twór.
Tutaj moglibyśmy wstrzyknąć DbContext i poisać query, modyfikować obiekty i na koniec dać _context.Save(). Widzę tutaj jednak zastosowanie dla repozytorium, ponieważ zamykamy te czynności w kolejnej warstwie abstrakcji, którą się łatwiej testuje. Dla czystości kodu z repozytorium umawiamy się, że nie zwracamy nigdy IQueryable tylko IEnumerable. Mamy więc w zamkniętym pudełku chociażby zapytania i nie martwimy się, że przyjdzie stażysta i w warstwie pośredniej po prostu zacznie dopisywać kolejne warunki do query, a widziałem takie bagno wielokrotnie, że ktoś w samym kontrolerze bezpośrednio pisał i modyfikował np. query _context.Product.Where..... Wg mnie dobrze jest zamknąć zapytania i na zewnątrz jedynie IEnumerable wystawić, a samego IDbContext nie wstrzykiwać nawet do kontrolera czy managera/service'u. Jako gratis możemy zrobić generyczną klasę BaseRepository do ogarnięcia prostego CRUDa, który też się zdarzy i nie musimy pisać wiele kodu powtarzając się.

Zacznijmy od tego, że samo EF jest UoW i wydaje się, że nie ma po co opakowywać EF jeszcze Uow. Stosując jednak repository pattern rujnujemy całkiem działający w EF UoW i musimy napisać swój własny UoW, by korzystając z różnych metod w różnych repozytoriach zapewnić transakcyjność i na koniec mieć możliwość użycia jednokrotnego _uow.Save(); Tutaj czuję oczywiście ten zgrzyt, że sami niszczymy abstrakcję, którą zaraz sami produkujemy.

Alternatywą oczywiście przy takim systemie, który nie jest samym CRUDem (ale też nim w pewnym zakresie jest) wydaje się CQRS (nie mieszajmy w to jednak DDD i Event Sourcingu, który w zaistniałym przykładowym systemie jest nie do przeforsowania). Bardziej skomplikowane query i commandy mamy ładnie wydzielone w osobnych klasach (zamiast repozytoriów z długą ilością linii kodu mamy od cholery klas pogrupowane w katalogach - jednego i drugiego nie lubię). DbContext wstrzykujemy tylko w query i commandy co jest eleganckim rozwiązaniem, by ktoś nietestowalnego łatwo query nie budował w akcji kontrolera.
Wg mnie stworzenie generycznych handlerów do obsługi również prostych operacji CRUDowych jest dużo bardziej skomplikowane niż generyczne BaseRepository, albo tworzymy masę takiego samego kodu i klas do obsługi głupiego FindById, GetAll, Create, itd. No chyba że wstrzykniemy jednak do tak prostych operacji IDbContext to kontrolera, co prowadzi do punktu pierwszego i daje stażyście łatwy dostęp by na kolanie użyć w akcji contextu EF.
Ogarnięcie transakcyjności też jest problemem i zazwyczaj widziałem po prostu bardzo długie comandy, które aż się proszą o podzielenie ich na logiczne mniejsze klocki, co wychodzi naturalnie przy repository pattern. Widywałem też wstrzyknięcie IDbContext do kontrolera/managera by na koniec dać _contex.Save(), ale o tym dlaczego tego nie lubię już pisałem kilkukrotnie.

Owszem sam CQRS często przy użyciu MediatR daje nam kilka innych możliwości, których nawet nie musimy potrzebować jak np. pipeline'y, gdyż często łatwiej można rozwiązać autoryzację i logowanie poprzez middleware'y/filtry w ASP, a notifications w ogóle możemy też nie potrzebować. Nie pomaga też fakt, że jak ktoś tłumaczy właśnie CQRS to robi to na przykładzie CRUDa albo czegoś równie prostego.

Naprawdę ciężko mi znaleźć złoty środek dla takiej aplikacji, która realizuje skomplikowane procesy, wyliczenia, raporty i jednocześnie ma dawać dostęp do prostego CRUDa i to wszystko bez możliwość DDD, bo relacyjna baza już stoi pełna ciągle używana przez inne części systemu i nie wolno ci stworzyć nowej. Za wszelkie cenne wskazówki i polemikę będę wdzięczny. Za krótkie rzucenie ogólnikowych haseł bez wyjaśnienia nie bardzo.

Tematu robienia abstrakcji, bo kiedyś się podmieni bazę, ORMa na coś innego nawet nie biorę pod uwagę bo jeszcze nigdy w mojej karierze tego nie widziałem, choć często takie podejście z automatu daje fajne pole do manewrów z mockowaniem przy testach.

2

Widzę tutaj jednak zastosowanie dla repozytorium, ponieważ zamykamy te czynności w kolejnej warstwie abstrakcji, którą się łatwiej testuje.

Istnieją drivery do EF Core m.in do quasi-bazy InMemory, a jeżeli chcesz coś bardziej przypominającego bazę relacyjną oraz lekkiego, to jest Sqlite, więc pytanie czy to nie jest wystarczające aby łatwo testować?

Mamy więc w zamkniętym pudełku chociażby zapytania i nie martwimy się, że przyjdzie stażysta i w warstwie pośredniej po prostu zacznie dopisywać kolejne warunki do query,

dopisywanie warunków do query czy tam używanie IQueryable to tam pół biedy, przecież może napsuć nieskończenie wiele innych rzeczy, zatem może jednak code review faktycznym jest rozwiązaniem tego "problemu stażysty"?

0

Istnieją drivery do EF Core m.in do quasi-bazy InMemory, a jeżeli chcesz coś bardziej przypominającego bazę relacyjną oraz lekkiego, to jest Sqlite, więc pytanie czy to nie jest wystarczające aby łatwo testować?

Jeśli chodzi o testowanie miałem na myśli coś innego. Spróbuj testować skomplikowane query, które jest w akcji kontrolera, gdzie jeszcze coś się dzieje. Łatwiej zrobić mocha dbsetu czy in-memroy dbcontext i to tylko wstrzyknąć do repo, gdzie się już testy napisze dla konkretnych metod. Ale jakby to nazwać DAL, a nie repository, tutaj jeśli chodzi o testowanie na jedno by wyszło.

dopisywanie warunków do query czy tam używanie IQueryable to tam pół biedy, przecież może napsuć nieskończenie wiele innych rzeczy, zatem może jednak code review faktycznym jest rozwiązaniem tego "problemu stażysty"?

W idealnym świecie to masz zawsze nawet biznes, który zna świetnie swoją domenę i potrafi wyrecytować już zaimplementowane warunki biznesowe w swoich systemach. Piszę o stażystach, a wiadomo że może chodzić też o osoby siedzące na stołku wyżej od ciebie. Jak się da wolę zrobić tak architekturę by dawała jak najmniej możliwości pisania złego kodu i gdzie się da wymuszała sposób pisania. Wszystkiego nie zabezpieczysz, ale warto próbować. Po łapach stażysta dostanie jak wstrzyknie mi context do kontrolera, a jak ma już go dostępnego to będzie od razu rzeźbił na contextcie.

0

Spróbuj testować skomplikowane query, które jest w akcji kontrolera, gdzie jeszcze coś się dzieje.

Co tak naprawdę zmieni nam gdy kontroler dostanie repo czy czysty context? w obu przypadkach jest słabo i nie powinno to się dziać w kontrolerze, ale mimo to nie widzę jakiejś znacznej różnicy w testowaniu. No chyba że chodzi o te "skomplikowane query", ale nie wiem co tu masz na myśli.

Ba, może nawet nie trzeba będzie pisać n mocków.

[Fact]
public async Task Test1()
{
    var opt = new DbContextOptionsBuilder().UseSqlite($"fileName={Guid.NewGuid().ToString()}.db");
    var ctx = new Context(opt.Options);
    ctx.Database.EnsureCreated();

    var controller = new OtherController(ctx);
    var result = await controller.TestAction(5);
    Assert.True(result is OkResult);
}

Łatwiej zrobić mocha dbsetu czy in-memroy dbcontext i to tylko wstrzyknąć do repo, gdzie się już testy napisze dla konkretnych metod.

Czego konkretnych metod?

3
shusty napisał(a):

W akcji kontrolera chcemy mieć jak najmniej kodu, to co można jak np. walidacja realizujemy poprzez middlewary. Nie ma tu też miejsca na logikę biznesową, więc większość wydzieli service, managera czy jak kto nazywa ten znienawidzony i prawie zawsze bezsensowny twór.

No manager to akurat nieco bezwartościowa nazwa, ale czemu uważasz wydzielenie logiki aplikacji do serwisu za bezsensowny twór?

Tutaj moglibyśmy wstrzyknąć DbContext i poisać query, modyfikować obiekty i na koniec dać _context.Save().

Moglibyśmy też mieć ten _context.Save() w jakimś interceptorze czy behaviorze, albo wydzielić wykonywanie transakcji do oddzielnej metody czy klasy, jeśli to jest problemem.

Widzę tutaj jednak zastosowanie dla repozytorium, ponieważ zamykamy te czynności w kolejnej warstwie abstrakcji, którą się łatwiej testuje. Dla czystości kodu z repozytorium umawiamy się, że nie zwracamy nigdy IQueryable tylko IEnumerable. Mamy więc w zamkniętym pudełku chociażby zapytania

No i to nie jest żadne repozytorium tylko zwykły Data Access Object.
I co chciałbyś w nim testować? Jednostkowo i tak się nie da, dane do bazy trzeba wstawić, wywołać metodę i sprawdzić, co zostanie zwrócone. Jak dla mnie takie testy są bardziej wartościowe na poziomie całej aplikacji, a nie tylko jednej warstwy.

Alternatywą oczywiście przy takim systemie, który nie jest samym CRUDem (ale też nim w pewnym zakresie jest) wydaje się CQRS (nie mieszajmy w to jednak DDD i Event Sourcingu, który w zaistniałym przykładowym systemie jest nie do przeforsowania). Bardziej skomplikowane query i commandy mamy ładnie wydzielone w osobnych klasach (zamiast repozytoriów z długą ilością linii kodu mamy od cholery klas pogrupowane w katalogach - jednego i drugiego nie lubię).

No zawsze można mieć po prostu jedną klasę z jedną metodą i dużo parametrów typu bool. Tym da się ogarnąć nawet średniego CRMa.
Ale tak bardziej na poważnie, to dużo małych klas stosujących SRP jest znacznie lepsze niż kilka dużych łamiących SRP, bo możesz łatwiej wprowadzać zmiany. Gdy masz te "repozytoria" z metodami używanymi w wielu miejscach serwisu, to stażysta zmieniając zapytanie w miejscu, w którym potrzebuje, może rozsypać wiele innych miejsc systemu.

Wg mnie stworzenie generycznych handlerów do obsługi również prostych operacji CRUDowych jest dużo bardziej skomplikowane niż generyczne BaseRepository

Ale czemu trudniejsze? Kod samej operacji jest identyczny, tylko inaczej opakowany.

Ogarnięcie transakcyjności też jest problemem i zazwyczaj widziałem po prostu bardzo długie comandy, które aż się proszą o podzielenie ich na logiczne mniejsze klocki, co wychodzi naturalnie przy repository pattern.

Dzielenie kodu na mniejsze części jest naturalne dla pewnych osób, dla innych nie jest. Nikt nie broni dzielić commandów na mniejsze części, wydzielać z nich innych klas, itd.

Naprawdę ciężko mi znaleźć złoty środek dla takiej aplikacji, która realizuje skomplikowane procesy, wyliczenia, raporty i jednocześnie ma dawać dostęp do prostego CRUDa i to wszystko bez możliwość DDD, bo relacyjna baza już stoi pełna ciągle używana przez inne części systemu i nie wolno ci stworzyć nowej. Za wszelkie cenne wskazówki i polemikę będę wdzięczny. Za krótkie rzucenie ogólnikowych haseł bez wyjaśnienia nie bardzo.

No moim zdaniem to jest idealne miejsce na zastosowanie właśnie MediatR, a co dalej, to zależy. W prostych przypadkach w handlerze można mieć kontekst ORMa, w tych bardziej skomplikowanych można tworzenie i wykonywanie zapytań delegować niżej. Transakcją może zająć się jakiś behavior. No i tyle jak dla mnie. Użycie MediatRa nie nakazuje ani nie zabrania mieć pod spodem skryptu transakcji, DAO, DDD, czy jakichkolwiek innych wzorców.

0

@somekind:

Moglibyśmy też mieć ten _context.Save() w jakimś interceptorze czy behaviorze, albo wydzielić wykonywanie transakcji do oddzielnej metody czy klasy, jeśli to jest problemem.

Takie zapewnienie UoW akceptuję.

Widzę tutaj jednak zastosowanie dla repozytorium, ponieważ zamykamy te czynności w kolejnej warstwie abstrakcji, którą się łatwiej testuje. Dla czystości kodu z repozytorium umawiamy się, że nie zwracamy nigdy IQueryable tylko IEnumerable. Mamy więc w zamkniętym pudełku chociażby zapytania

No i to nie jest żadne repozytorium tylko zwykły Data Access Object.
I co chciałbyś w nim testować? Jednostkowo i tak się nie da, dane do bazy trzeba wstawić, wywołać metodę i sprawdzić, co zostanie zwrócone. Jak dla mnie takie testy są bardziej wartościowe na poziomie całej aplikacji, a nie tylko jednej warstwy.

W takim DAO czasem w linq zapisane są fat queries i używane w kilku miejscach w systemie. Wg mnie warto napisać unit test w takim przypadku. Przy EF bardzo przyjemnie mi się testuje in-memory. Przygotowuję powtarzalny do testów set danych w odwzorowanej bazie i testy śmigają aż miło. Gdyby DAO pod spodem było np. wołaniem procedury w bazie czy sqlem, to faktycznie byłoby ciężko. Ale rozumiem twój punkt widzenia.

Alternatywą oczywiście przy takim systemie, który nie jest samym CRUDem (ale też nim w pewnym zakresie jest) wydaje się CQRS (nie mieszajmy w to jednak DDD i Event Sourcingu, który w zaistniałym przykładowym systemie jest nie do przeforsowania). Bardziej skomplikowane query i commandy mamy ładnie wydzielone w osobnych klasach (zamiast repozytoriów z długą ilością linii kodu mamy od cholery klas pogrupowane w katalogach - jednego i drugiego nie lubię).

No zawsze można mieć po prostu jedną klasę z jedną metodą i dużo parametrów typu bool. Tym da się ogarnąć nawet średniego CRMa.
Ale tak bardziej na poważnie, to dużo małych klas stosujących SRP jest znacznie lepsze niż kilka dużych łamiących SRP, bo możesz łatwiej wprowadzać zmiany. Gdy masz te "repozytoria" z metodami używanymi w wielu miejscach serwisu, to stażysta zmieniając zapytanie w miejscu, w którym potrzebuje, może rozsypać wiele innych miejsc systemu.

Widocznie mamy nieporozumienie. Błędnie nazywałem repozytorium DAO. Za dużo artykułów się naczytałem chyba, gdzie prezentowane jest EF + repozytorium osobne dla każdej encji, najlepiej dziedziczone po generycznym repo + nad tym UoW. Niby repozytorium nazwane, a tak naprawdę to DAO to każdej encji. Żadnej logiki domenowej tam nie ma.
https://docs.microsoft.com/pl-pl/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
https://medium.com/@mlbors/using-the-repository-pattern-with-the-entity-framework-fa4679f2139
https://gunnarpeipman.com/ef-core-repository-unit-of-work/
https://www.plukasiewicz.net/Artykuly/EF_Repository_UnitOfWork
Bardzo dużo można znaleźć artykułów z takim podejściem.

Naprawdę ciężko mi znaleźć złoty środek dla takiej aplikacji, która realizuje skomplikowane procesy, wyliczenia, raporty i jednocześnie ma dawać dostęp do prostego CRUDa i to wszystko bez możliwość DDD, bo relacyjna baza już stoi pełna ciągle używana przez inne części systemu i nie wolno ci stworzyć nowej. Za wszelkie cenne wskazówki i polemikę będę wdzięczny. Za krótkie rzucenie ogólnikowych haseł bez wyjaśnienia nie bardzo.

No moim zdaniem to jest idealne miejsce na zastosowanie właśnie MediatR, a co dalej, to zależy. W prostych przypadkach w handlerze można mieć kontekst ORMa, w tych bardziej skomplikowanych można tworzenie i wykonywanie zapytań delegować niżej. Transakcją może zająć się jakiś behavior. No i tyle jak dla mnie. Użycie MediatRa nie nakazuje ani nie zabrania mieć pod spodem skryptu transakcji, DAO, DDD, czy jakichkolwiek innych wzorców.

Dzięki, mniej więcej rozjaśnia mi się wizja. Dalej się zastanawiam kiedy faktycznie potrzebować będę np. mediatR, a kiedy to sztuka dla sztuki i w samej akcji prościej oraz wystarczająco po prostu będzie wywołać _orderService.PushOrderToWarehouse(params...), a w samym serwisie trochę logiki biznesowej i wywołanie metod z warstwy DAO, gdzie siedzą już query i modyfikacje za pomocą contextu.

0

Myślę, że mam za dużo teorii by sobie to poukładać w głowie, a za mało widzę przykładowych poprawnych implementacji.** Zna ktoś ciekawe publiczne repo**, gdzie architektura jest dobrze skomponowana z EF, warstwą logiki aplikacyjnej i biznesowej, najlepiej z sensownym użyciem CQRSa np. z pomocą Mediatora z MediatR i bez podejścia DDD?

Przeglądam np. podejście Kamila w jego projekcie: https://github.com/kgrzybek/modular-monolith-with-ddd i patrzę nie na cały system, a na użycie jednego modułu.

title

UoW jest zaimplementowany i wystawia interfejs, ale wykonywany jest poprzez command handler decoratorów.
Z akcji w kontrolerze kod płynie przez middlewary i przez mediatR lecą query i commandy.
CommandHandlery operaują na repozytoriach, które bardziej przypominają DAO wyglądając przeważnie tak:

    internal class MeetingGroupProposalRepository : IMeetingGroupProposalRepository
    {
        private readonly MeetingsContext _context;

        internal MeetingGroupProposalRepository(MeetingsContext context)
        {
            _context = context;
        }

        public async Task AddAsync(MeetingGroupProposal meetingGroupProposal)
        {
            await _context.MeetingGroupProposals.AddAsync(meetingGroupProposal);
        }

        public async Task<MeetingGroupProposal> GetByIdAsync(MeetingGroupProposalId meetingGroupProposalId)
        {
            return await _context.MeetingGroupProposals.FirstOrDefaultAsync(x => x.Id == meetingGroupProposalId);
        }
    }

Ciekawe czy takie podejście w końcu nie doprowadzi do powstania licznych metod jak: "GetCustomersWithInvoicesWithoutEmployeesWithFavouriteMoviesWithoutMistressesByWifeBloodTypeAndBossDaughterCatName"

Sama logika jest fajnie upchana w command handlery i w domenę czyli tutaj konkretnie w encje EF czyli nie mamy anemicznych modeli biznesowych.

Oczywiście cały projekt jest bardziej rozbudowany niż bym potrzebował do analizy Mając DDD, Event sourcing i wiele więcej.

Podsyłajcie inne ciekawe implementacje dobrego rozwiązania architektonicznych, dla przypadków podobnych jakie opisałem w tym temacie. Może ktoś sam machnął na swoim repo jakiś szkielet proof of concept?

4
shusty napisał(a):

W takim DAO czasem w linq zapisane są fat queries i używane w kilku miejscach w systemie. Wg mnie warto napisać unit test w takim przypadku. Przy EF bardzo przyjemnie mi się testuje in-memory. Przygotowuję powtarzalny do testów set danych w odwzorowanej bazie i testy śmigają aż miło. Gdyby DAO pod spodem było np. wołaniem procedury w bazie czy sqlem, to faktycznie byłoby ciężko. Ale rozumiem twój punkt widzenia.

OK, motywacja słuszna. Jednakże dla mnie test, który korzysta z bazy danych (wszystko jedno, czy w pamięci, czy nie) nie jest już testem jednostkowym, bo nie używa jedynie Twojego kodu, ale też zewnętrznego elementu jakim jest baza.

Bardzo dużo można znaleźć artykułów z takim podejściem.

Doskonale zdaję sobie z tego sprawę, niestety tak jest. Dlatego ja zawsze poprawiam. :)

Dzięki, mniej więcej rozjaśnia mi się wizja. Dalej się zastanawiam kiedy faktycznie potrzebować będę np. mediatR, a kiedy to sztuka dla sztuki i w samej akcji prościej oraz wystarczająco po prostu będzie wywołać _orderService.PushOrderToWarehouse(params...)

Nie uważam, żeby to kiedykolwiek była sztuka dla sztuki, bo użycie MediatR sprawia, że:

  • Masz niejako wymuszone SRP (tzn. oczywiście da się to obejść, ale wymaga to perwersyjnego samozaparcia) - jeden handler wykonuje jedna operację, więc jego kod będzie krótki. OrderService, który ma wykonywać wszystkie operacje na orderach dla wszystkich kontrolerów szybko może stać się (i zazwyczaj tak się właśnie dzieje) przerośniętym god objectem.
  • Masz wymuszone przekazywanie danych z kontrolera do handlera za pomocą obiektu, a nie zestawów dziesiątek parametrów, jak często bywa przy podejściu z serwisami.
  • W kontrolerach z kolei masz tylko jedną zależność - od MediatRa, niezależnie od tego, ilu serwisów dany kontroler używa.
shusty napisał(a):

Myślę, że mam za dużo teorii by sobie to poukładać w głowie, a za mało widzę przykładowych poprawnych implementacji.** Zna ktoś ciekawe publiczne repo**, gdzie architektura jest dobrze skomponowana z EF, warstwą logiki aplikacyjnej i biznesowej, najlepiej z sensownym użyciem CQRSa np. z pomocą Mediatora z MediatR i bez podejścia DDD?

Cierpliwości, pracuję nad tym od 7 lat, jestem gdzieś tak w połowie.

CommandHandlery operaują na repozytoriach, które bardziej przypominają DAO wyglądając przeważnie tak:

    internal class MeetingGroupProposalRepository : IMeetingGroupProposalRepository
    {
        private readonly MeetingsContext _context;

        internal MeetingGroupProposalRepository(MeetingsContext context)
        {
            _context = context;
        }

        public async Task AddAsync(MeetingGroupProposal meetingGroupProposal)
        {
            await _context.MeetingGroupProposals.AddAsync(meetingGroupProposal);
        }

        public async Task<MeetingGroupProposal> GetByIdAsync(MeetingGroupProposalId meetingGroupProposalId)
        {
            return await _context.MeetingGroupProposals.FirstOrDefaultAsync(x => x.Id == meetingGroupProposalId);
        }
    }

Bynajmniej nie jest to DAO. Masz interfejs, który definiuje dwie konkretne operacje, które na danej encji można wykonać, a nie jakiś generyczny wrapper na tabelę.

Ciekawe czy takie podejście w końcu nie doprowadzi do powstania licznych metod jak: "GetCustomersWithInvoicesWithoutEmployeesWithFavouriteMoviesWithoutMistressesByWifeBloodTypeAndBossDaughterCatName"

Takie metody powstają wtedy, gdy ktoś ma DAO do jednej tabeli używane globalnie w całym projekcie. Przy dwusetnej metodzie w repozytorium pomysły na nazwy się kończą. ;) Gdy repozytoria są małe i specjalizowane w ramach agregatu, jak u Kamila, to nie ma na to szans.

1

@somekind: Dzięki za rzeczowy komentarz. Niestety patrzę w monolity i różne api, które są w firmie, a jest tego dużo i faktycznie muszę po prostu pracować z takim kodem, gdzie jest EF, a "repository" to tylko operacje na tabelkach (każda tabelka - encja ma swoje repository), do tego anemiczne modele i nijak się do agregatów i bounded contextów z prawdziwego zdarzenia. A robić trzeba, zadań za dużo i story z refactoringiem product owner niechętnie zatwierdzi jak się pali bo się prawo np. zmieniło.

Do czynienia mam też z licznymi api, które wydaje się tylko niektórym, że mają do czynienia z prawdziwymi mikroserwisami. Bo spec po przeprowadzeniu event stormingu uzyskałby całkowicie inny podział. Zresztą nawet nie wiem jak nazwać taki twór gdzie mamy niby pół monolitu, który korzysta w większości z CRUDa poprzez restowe api i żeby było zabawniej jedno api pyta synchronicznie kolejne api, które musi zapytać kolejne api. Widziałem gdzieś nazwę rozproszony monolit na taki twór. Nie ma mowy o obecnej sytuacji by serwisy komunikowały się robiąc publisha eventu i niego asynchronicznie reagowały zainteresowane systemy.
Z drugiej strony prawda jest taka, że system "jakoś" działa i zarabia, każdy zespół ma tam kilka swoich api, a trzeba się w tym środowisku odnaleźć i jak trzeba do systemu dołożyć klocek (a tym się np. teraz zajmuję) to starać się go zrobić lepiej niż wszystko dookoła, zintegrować jako tako z resztą istniejącego systemu, a później powolutku próbować przekształcać cały system w coś bardziej wzorowego.

Spore wyzwanie, chyba większe niż przyjść i zaprojektować wykonać cały system od nowa po bożemu bez blokerów w postaci legacy.

2

@shusty, no jeśli poza utrzymywaniem monolitu możesz jakieś nowe mikroserwisy tworzyć, to przynajmniej w nich możesz robić sensowniej, np. bez tych repozytoriów per tabela, które przy crudzie są zwykłą stratą czasu i tylko dodatkowym kodem do utrzymania.

Generalnie to co opisałeś, to po prostu taka norma w pracy. Tylko w startupach nie masz legacy, a i to tylko przez pierwsze dwa lata. ;)

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