.NET Core WebAPI - serwisy, repozytoria, automapery, DTO, Dapper, EF

6

Witam.
Dręczy mnie to trochę i chciałbym znać wasze zdanie. W pewnym poście dostałem po tyłku za programowanie i owszem, należało mi się, ale są rzeczy, których nie rozumiem i mam przeczucie, że mnie nie dotyczą. A że z natury jestem człowiekiem, który nie trzyma się zasad jak nie mają sensu to później dostaje bo łbie za pisanie głupot...

  1. Czy konieczny jest podział solucji na projekty, gdzie każdy projekt za coś odpowiada. Dlaczego nie można tego rozdzielić folderami w jednym projekcie?
  2. Czym się różnią serwisy od repozytoriów i dlaczego to nazewnictwo jest takie ważne?
  3. Co powinien zawierać kontroler? Dlaczego akcja kontrolera nie może zawierać logiki biznesowej?
  4. Gdzie powinno być połączenie do bazy jeśli nie korzystam z EF, a na przykład z Dappera?
  5. Obsługa błędów, wyjątków, co wysyłać do klienta, co obsłużyć po stronie API?
  6. Czy asynchroniczność jest konieczna w przypadku WebAPI?
  7. W jakich przypadkach przydaje się automapper?
  8. Do czego "używa się" DTO?

Pytania dodatkowe

  1. Czy to nie jest strata czasu?
  2. Czy jeśli jestem jedynym programistą w firmie to te wszystkie zasady mnie "obowiązują" lub powinny "obowiązywać"?
5

Cześć,

Z mojej perspektywy (bardziej doświadczeni pewnie mnie poprawią):

Ad. 1. Nie jest konieczny, jest wskazany to poniekąd podążanie za zasadami SOLID w większym uogólnieniu. Każdy projekt może realizować różne zadania i możesz deploywać pojedyncze projekty zamiast całości.
Ad. 2. Repozytoria to dodatkowa warstwa, która po pierwsze ułatwia testowanie, a po drugie w przypadku rozbudowanych projektów i np. widoku który potrzebuje danych z dwóch serwisów masz możliwość połączenia tego właśnie w repozytorium. (Tutaj może lepiej jakby ktoś bardziej doświadczony to wyjaśnił).
Ad. 3. Nie może to duże słowo. Nie powinna, od tego masz warstwę logiki biznesowej, aby tam ją umieścić. Znowu - ułatwia to testowanie i wprowadza pewien porządek w projekcie. Kontrolery są po to, aby zwracać konkretne rzeczy (np. model) do widoku.
Ad. 4. W tym samym miejscu co w przypadku EF. To i to ostatecznie jest ORMem, wiec dlaczego chciałbyś to umieszczać w innych miejscach? Możesz to w przypadku ASP Core dodać do konfiguracji.
Ad. 5. Co dokładnie masz na myśli? Do użytkownika wysyłasz komunikat zwrotny, że coś poszło nie tak. Cały wyjątek obsługujesz po stronie API np. przekierowując użytkownika do innego widoku.
Ad. 6. Konieczna to znowu duże słowo. Przyspiesza działanie aplikacji, więc dlaczego z tego nie korzystać?
Ad. 7. Automapper przydaje się kiedy aplikacja sama nie potrafi sobie przetłumaczyć obiektów EF na Twoje modele.
Ad. 8. To kontener na dane. Pobierając całą encję z bazy możesz sobie w obiekcie DTO usunąć property których nie potrzebujesz lub nie chcesz pokazywać (np. hasła) i używać obiektu DTO.

Ad. 1. Nie, nie jest. To są dobre zasady programowania których może teraz nie rozumiesz i nie doceniasz, ale z czasem (i nabytym doświadczeniem) zobaczysz, że to wszystko ma jakiś sens.
Ad. 2. Parafrazując tekst który kiedyś przeczytałem - nie ważne jak zły kod napisałeś jeśli napisałeś go sam to będziesz wiedział co robi. Teraz wyobraź sobie, że pracujesz z kimś i ktoś ma zmienić coś po Tobie. Nie trzymając się dobrych praktyk byłaby to masakra. Nawet jeśli sam będzie pracował w jakimś projekcie i zrobisz go w niepoprawny sposób, ale pomimo to będzie działał to wyobraź sobie sytuację, że za pół roku siadasz znowu do tego samego projektu i masz coś zmienić, a okazuje się, że projekt to takie spaghetti code, że szybciej i łatwiej będzie napisać ten projekt od zera niż zmieniać to co jest.

Pozdrawiam

0
  1. Podział na projekty wiele ułatwia. Przykładowo dwie różne warstwy mogą być siebie nieświadome i nie kusi wtedy żeby je przypadkiem powiązać, żeby coś szybciej zrobić.
  2. Nie wiem co dokładnie autor ma na myśli. Powiedziałbym ze serwisy to warstwa biznesowa a repozytoria to warstwa danych.
  3. Ja to sobie tłumaczę tak, że kontroler nie jest częścią aplikacji. On tylko spina aplikację ze światem. Kontroler służy do tego, żeby przyjąć dane i zwrócić dane. Nie interesuje go jakie to są dane. Jeżeli cała logika biznesowa by była w kontrolerach to miałaby mocne zależności do protokołu http, a przecież teoretycznie mogłbyś chcieć jakiś kawałek aplikacji wystawić gdzieś indziej, zdala od WebApi, MVC czy czegokolwiek takiego.
  4. Połączenie do bazy tworzy warstwa danych oczywiście. Logika biznesowa nic o bazie danych nie wie.
  5. Rozwiń pytanie.
  6. Nie jest konieczna, ale poprawia jej skalowalność jeżeli spodziewasz się regularnego przyrostu ilości zapytań.
  7. Automapper przydaje się jeżeli często tworzysz obiekty dwóch róznych typów o podobnej strukturze.
  8. DTO to obiekt, którego zadaniem jest przenosić dane między warstwami w aplikacji. Każda warstwa ma swoje typy, nie powinieneś na przykład w kontrolerze operować na encjach EF - od tego są DTO.

1, 2. Nie jest stratą czasu. Dobrze napisany kod dużo łatwiej poźniej rozwijać. Poza tym nie masz pewności, że zawsze będziesz tam sam.

1

Nie wiem, czy wprowadzę coś nowego, ale tak:

  1. Czy konieczny jest podział solucji na projekty, gdzie każdy projekt za coś odpowiada. Dlaczego nie można tego rozdzielić folderami w jednym projekcie?

Pewnie, że można. Jednak nie jest to wskazane. Wyobraź sobie, że masz napisać aplikację desktopową do przerabiania zdjęć. Np. dajesz zdjęcie, a ona przerabia Ci każdy czerwony piksel na czarny. Napisałeś ją w WinForms. No i super, wszystko działa.
Lecz teraz przychodzi moment, kiedy ktoś mówi - ta aplikacja jest zajebista, wrzuć ją do Microsoft Store! I to niby nie wymaga niczego, bo wystarczy ją wrzucić... ale chyba nie wrzucisz. Bo to nie jest UWP. A ponoć tylko UWP (i ostatnio słyszałem, że WPF) można wrzucać do MS Store. Więc co robisz? Piszesz tą samą aplikację od nowa?

Najbardziej poprawna droga jest taka, żeby oddzielić GUI od zadania. Czyli powstają Ci w ten sposób przynajmniej dwa projekty - GUI i dll z faktycznymi operacjami do zmiany obrazów. I co się dzieje teraz? Możesz stworzyć trzeci projekt - UWP - tylko GUI, które będzie korzystało z Twojej dllki. Teraz niech ktoś podejdzie i powie - super, przerzuć to do Weba i na telefony. Masz właściwie problem z głowy, bo już masz wszytko gotowe. Musisz tylko naklepać nowe GUI. To taki najprostszy przykład, dlaczego warto używać kilku projektów.

  1. Czym się różnią serwisy od repozytoriów i dlaczego to nazewnictwo jest takie ważne?

Repozytorium to warstwa danych. Odpowiada za odczyt/zapis danych. Serwis odpowiada za obróbkę tych danych, np:

Repozytorium:

public void SaveUser(User user);
public User LoadUser(int userId);

Serwis:

public User CreateNewUser(string name, string pass)
{
  if(string.IsNullOrWhitespace(name))
    throw new ArgumentException("Nie podano nazwy użytkownika!");

  if(!PasswordHelper.PassIsValid(pass))
    throw new ArgumentException("Niepoprawne hasło!");

  User createdUser = new User(name, pass);
  repo.SaveUser(createdUser);

  return createdUser;
}

Co tu widać:

  • repozytorium jest odpowiedzialne tylko za przekazywanie danych
  • serwis jest odpowiedzialny za logikę biznesową - może wykorzystywać repozytorium, ale tak naprawdę ma w głębokim poważaniu, gdzie dane są zapisywane. Czy to będzie MSSQL, MySql, SQLite, a może plik tekstowy. To wie tylko repozytorium. Dzięki temu, rozdzielasz sobie zapis danych od działania na tych danych. I dobrze. Dzięki repozytorium możesz zapisywać dane w dowolnym formacie, a tak naprawdę logika biznesowa w ogóle się nie zmienia. Ułatwia to też testowanie. Gdy testujesz metodę CreateNewUser, to nie testujesz zapisu danych, tylko odpowiednią logikę biznesową - np. czy się wywali, jak nie będzie podanego hasła. Repozytorium mockujesz.
  1. Co powinien zawierać kontroler? Dlaczego akcja kontrolera nie może zawierać logiki biznesowej?
    Widziałem sporo projektów, gdzie zawierała. Przede wszystkim mnóstwo przykładowych projektów dla różnych frameworków właśnie tak jest skonstruowana, że kontroler zawiera logikę. Utrudnia to jednak testowanie. Dlatego zdecydowanie lepszy kod jest taki, gdzie kontroler korzysta z serwisu, a sam robi zupełne minimum tak, żeby go nie trzeba było testować.
  1. Gdzie powinno być połączenie do bazy jeśli nie korzystam z EF, a na przykład z Dappera?
    Tu wchodzimy w kolejną warstwę. Warstwę danych. Czyli osobny projekt :)
    Tutaj połączenie z bazą może być na poziomie repozytorium. Albo jeszcze niżej.
  1. Obsługa błędów, wyjątków, co wysyłać do klienta, co obsłużyć po stronie API?
    Generalnie API nie powinno się nigdy wywalić :)
    Do klienta powinieneś wysłać informację o statusie operacji - zakończono sukcesem / zakończono błędem.
    Jeśli jest błąd, powinieneś mu wysłać ten błąd (chociażby jako BadRequest, jeśli boisz się że błąd może posłużyć shackowaniu API). Ale miło jest jak klient otrzyma jakąś wskazówkę, dlaczego błąd wystąpił: "Za krótkie hasło / niepoprawna nazwa użytkownika / Błąd bazy danych". Nie powinieneś jednak wysyłać pewnych problemów, np: "Invalid syntax near: "INSERT into tabela....", bo to może zagrać na Twoją niekorzyść.
  1. Czy asynchroniczność jest konieczna w przypadku WebAPI?
    Nie
  1. W jakich przypadkach przydaje się automapper?
    Wg mnie w prostych, standardowych, nieproblematycznych.
  1. Do czego "używa się" DTO?
    To jest prosta klasa do przesyłania danych. Np masz model usera:
public class User
{
    public int Id {get;set;}
    public string Name {get;set;}
    public string UserName {get;set;}
    public string EMail {get;set;}
   //i mnóstwo innych pól, w tym jakieś krytyczne
}

I teraz musisz przesłać dane z API do klienta albo na odwrót. Nie musisz pakować wszystkich danych. Wystarczy, że zrobisz sobie jakąś lekką klasę:

public class UserDto
{
    public int Id {get;set;}
    public string UserName {get;set;}
}

i tyle. Bez żadnej logiki bez niczego więcej. Bo musisz przekazywać akurat tylko Id i nazwę użytkownika. Nic więcej nie jest Ci potrzebne (takie założenie). Do tego jest DTO.

Pytania dodatkowe

  1. Czy to nie jest strata czasu?

Na początku tak. Bo projekt powstaje dłużej. Jednak późniejszy jego rozwój i utrzymanie zajmują dużo mniej czasu niż monolitycznego odpowiednika. Co więcej, dobrze rozdzielony projekt łatwo testować automatycznie. A więc masz produkt dużo bardziej stabliny.

  1. Czy jeśli jestem jedynym programistą w firmie to te wszystkie zasady mnie "obowiązują" lub powinny "obowiązywać"?

Jak najbardziej.

4

To są bardzo dobre pytania, bo nie wszystkie "dobre praktyki" są dobre w każdym kontekście, już nie wspominając że określenie że kod jest "dobrze napisany" jest bardzo subiektywne.
Także bardzo dobrze, że zadajesz, dobre pytania a nie podążasz na ślepo za cargo kultem dobrych praktyk, tylko próbujesz dociec dlaczego daną rzecz się stosuje i zrozumieć jaki problem ona rozwiązuje. Także zakończę ten drobny offtop mottem ze świata reacta: "Rethinking best practices".

0

Ok, argumentacja jest i dociera do mnie. Kilka osób wspomniało o testach. Moim zdaniem kolejna strata czasu. Rozumiem, że "dobre praktyki" robi się po to, aby się aplikację dobrze testowało?

TESTOWANIE

Ad. 2. Repozytoria to dodatkowa warstwa, która po pierwsze ułatwia testowanie,

Co więcej, dobrze rozdzielony projekt łatwo testować automatycznie. A więc masz produkt dużo bardziej stabliny.
...
Przede wszystkim mnóstwo przykładowych projektów dla różnych frameworków właśnie tak jest skonstruowana, że kontroler zawiera logikę. Utrudnia to jednak testowanie


DZIWNE ODPOWIEDZI
Albo ja zbyt logicznie podchodzę do tego co tutaj piszecie.
@Fuffu Chcesz powiedzieć, że używasz dobrych praktyk, aby cię "nie kusiło", żeby rozwiązać coś inaczej, szybciej, niezgodnie z dobrymi praktykami? 🤔🙈

Podział na projekty wiele ułatwia. Przykładowo dwie różne warstwy mogą być siebie nieświadome i nie kusi wtedy żeby je przypadkiem powiązać, żeby coś szybciej zrobić.


DZIELENIE NA PROJEKTY
@Krzysztof Pe Jeśli wprowadzisz coś nowego w jednym z projektów to i tak musisz z tego skorzystać w kontrolerze, aby w ogóle wyszło to "na świat". To jest dobre w kwestii poprawek. Wtedy podmieniasz jedną DLL-kę i działa.

Każdy projekt może realizować różne zadania i możesz deploywać pojedyncze projekty zamiast całości.

PODSUMOWANIE:
Spróbuje to ogarnąć jakoś w sumie, odpowiedzi są bardzo podobne. Skoro repozytorium zapisuje i odczytuje dane to dlaczego nie mogę ich od razu "obrobić" w repozytorium i zapisać? Chcecie mi powiedzieć, że ze względu na "dobre praktyki" powinienem stworzyć interfejs (serwis), który mi te dane przerobi, tylko po to, aby repozytorium mogło zapisać w takiej formie w jakiej powinno być zapisane?

PRZYKŁAD:

public bool CancelAgreement(int agreementId)
{
    var agreement = _dbContext.Agreements.Find(agreementId);
    
    //Te dwie linie poniżej powinny być w serwisie? Po co?
    agreement.Canceled = true;
    agreement.CancelDate = DateTime.Now;

    _dbContext.Agreements.Update(agreement);
    _dbContext.SaveChanges();
}

Może kiedyś nadejdzie dzień, w którym zrozumiem wasze "podejście". Niektóre argumenty są dobre, np.: DTO, szczególnie przy Dapperze, gdzie można sobie stworzyć taki obiekt, który jest wynikiem dość potężnego join`owania.

**Wniosek? **
Uważam, że więcej czasu spędza się na generowaniu tych wszystkich interfejsów do serwisów i repozytoriów niż na faktycznym programowaniu. Rozumiem jakby omówione kwestie miały wpływ na pracę aplikacji, jej szybkość, wydajność, ale to jest tylko, albo też aż, jakiś sposób na organizację projektu. Jeśli więcej osób pracuje nad jednym projektem to ok, każdy piszę tak samo i każdym wie gdzie i czego się spodziewać.

PS.
Ja pracuje w bardzo małej firmie (5 osób, razem ze mną). Programowanie to tylko dodatek do usług jakie oferujemy. Ostatnio zrobiło się zapotrzebowanie na webowe aplikacje, ponieważ ludzie chcą mieć dostęp zewsząd. Przerzucając się z WinForms, to dla mnie to duża nowość. Mam już kilka projektów na swoim koncie, ale żaden z nich nie jest zrobiony w sposób w jaki tutaj opisujecie i (co dziwne) wszystko działa.

0

@AdamWox ja kieruje się trochę innymi kryteriami (tak na szybko, bo notatki są w pracy):

  • czy da radę coś wykorzystać w innym projekcie (oszczędzić sobie pracy)
  • czy będę wymieniał komponenty na inne (uaktualniał, lub całkowicie zastępował, patrz - żywotność projektu)
  • czy ktoś inny będzie zmieniał kod (i chcę żeby mógł taplać się w swoim bagienku, aż skończy), jak bardzo chcę mu ułatwić współpracę (a sobie mękę przy merge-owaniu)
  • czy zakładam, że system przetrwa rok, dwa, pięć, dziesięć
  • jak szybko muszę wprowadzać zmiany i na ile jest to ważny system (np. mam taki jeden na utrzymaniu, gdzie wszystko jest w miarę trywialne, ale jest testowane do ^n tylko po to żebym mógł wypuszczać na produkcję w każdym momencie po zmianie kilku linijek)
  • jaki system ma potencjał na rozrośnięcie się (jak ważny jest dla biznesu)
  • czy będzie musiał udostępniać dane, współpracować z innym systemem.

W zależności od powyższych stosuje więcej lub też mniej 'dobrych' praktyk. Generalnie starając się dowozić porządnie i szybko, a żeby było szybko często muszę włożyć wysiłek w odpowiednie podzielenie projektu, testowanie, wybór komponentów na których można polegać, etc

0

Potrzebne Ci w ogóle to repozytorium? Wstrzyknij sobie dbContext do kontrolera i heja :)

1
AdamWox napisał(a):

Uważam, że więcej czasu spędza się na generowaniu tych wszystkich interfejsów do serwisów i repozytoriów niż na faktycznym programowaniu. Rozumiem jakby omówione kwestie miały wpływ na pracę aplikacji, jej szybkość, wydajność, ale to jest tylko, albo też aż, jakiś sposób na organizację projektu. Jeśli więcej osób pracuje nad jednym projektem to ok, każdy piszę tak samo i każdym wie gdzie i czego się spodziewać.

To nie jest tak, że każdy serwis potrzebuje interfejs. Jeżeli nie planujesz zmieniać bazy danych i nie przeszkadza Ci zawiłe linq w serwisach to nie potrzebujesz również dodatkowej warstwy opakowującej dbContext. Co do Twojego podejścia do testowania to się zupełnie nie zgadzam. Dla Twojego dobra i klientów twojej firmy warto się do tego przekonać. Jeżeli zdecydujesz się na testowanie tych serwisów to możesz to zrobić poprzez InMemoryDbContext lub/oraz SqliteInMemory.

PS.
Ja pracuje w bardzo małej firmie (5 osób, razem ze mną). Programowanie to tylko dodatek do usług jakie oferujemy. Ostatnio zrobiło się zapotrzebowanie na webowe aplikacje, ponieważ ludzie chcą mieć dostęp zewsząd. Przerzucając się z WinForms, to dla mnie to duża nowość. Mam już kilka projektów na swoim koncie, ale żaden z nich nie jest zrobiony w sposób w jaki tutaj opisujecie i (co dziwne) wszystko działa.

Tutaj groźniejsze jest to, że gdy będzie potrzebna nowa funkcjonalność ty lub przyszli programiści pracujący nad danym projektem mogą sobie nie poradzić w zadowalającym czasie.

0
Krzysztof Pe napisał(a):

Cześć,

Z mojej perspektywy (bardziej doświadczeni pewnie mnie poprawią):

Ad. 2. ...a po drugie w przypadku rozbudowanych projektów i np. widoku który potrzebuje danych z dwóch serwisów masz możliwość połączenia tego właśnie w repozytorium. (Tutaj może lepiej jakby ktoś bardziej doświadczony to wyjaśnił).

Tutaj mala uwaga - jest dokladnie odwrotnie. Repozytorium jest pojeciem, ktore jest zwiazane jednoznacznie i nierozerwalnie z konkretna encja i nie powinien np obslugiwac kwerend relacyjnych. W takim przypadku wykorzystujemy obiekt klasy Query. Natomiast serwis jest "rozszerzeniem" kontrolera i jego lacznikiem z repozytorium (repozytoriami w przypadku jesli kontroler tego wymaga). Logika umieszczona w serwisie umozliwia maksymalne odchudzenie kontrolera, tak by ten zajmowal sie wylacznie przekazaniem danych (np do widoku). Injekcja repozytoriow wprost do kontrolera jest bledem i uzasadnia ja wylacznie brak koniecznosci dodawania jakiejkolwiek logiki zwiazanej np z obrobka/przygotowania parametrow koniecznych do wywolania konkretnej metody repozytorium. W przypadku mapowania, transformacji itp parametrow, logika zwiazana z tymi procesami powinna zostac umieszczona w serwisie. Z poziomu kontrolera wolamy tylko o dane, wszystkie inne rzecz takie jak autentykacja, autoryzacja, walidacja itp itd od biedy mozna umiescic serwisie, chociaz to tez jest blad i nalezy wykorzystac do tego middleware.

3
AdamWox napisał(a):
  1. Czy konieczny jest podział solucji na projekty, gdzie każdy projekt za coś odpowiada. Dlaczego nie można tego rozdzielić folderami w jednym projekcie?

W małym projekcie mozna. W dużym to robi się męczące, gdy w jednym projekcie masz setki/tysiące klas. Ciężko coś znaleźć, cieżko się korzysta z IDE, które podpowiadanie ma zaśmiecone wszystkim, co jest w projekcie. Dlatego w miarę z rozrostem projektu dobrze jest dzielić coraz bardziej.

  1. Czym się różnią serwisy od repozytoriów i dlaczego to nazewnictwo jest takie ważne?

Serwisem można nazwać właściwie wszystko realizujące jakąkolwiek logikę, a repozytoria to specyficzny element DDD.

  1. Co powinien zawierać kontroler? Dlaczego akcja kontrolera nie może zawierać logiki biznesowej?

Bo to wtedy nie jest już kontroler tylko jakiś god-object.
Kontroler powinien odebrać i zwalidować dane wejściowe, a następnie przekazać je do niższych warstw. A w drugą stronę powienien odebrać wyniki i przekazać je w prawidłowej formie na zewnątrz.

  1. Obsługa błędów, wyjątków, co wysyłać do klienta, co obsłużyć po stronie API?

Do klienta najlepiej prosty komunikat oraz unikalne ID błędu, po którym będziesz w stanie znaleźć zdarzenie w logach i je naprawić.

  1. Czy asynchroniczność jest konieczna w przypadku WebAPI?

Konieczna nie, po prostu pomaga serwerowi działać w niektórych sytuajach. Jeśli aplikacja ma raptem kilku użytkowników, to zysku nie będzie z niej wielkiego.

  1. Do czego "używa się" DTO?

To też szerokie pojecie. Ogólnie do przesyłania danych, np. między aplikacją a webservisem, można też między warstwami aplikacji, tylko to technicznie nie jest transfer, więc nazwa DTO nie brzmi najlepiej.

Pytania dodatkowe

  1. Czy to nie jest strata czasu?

Zależy od skali projektu.

  1. Czy jeśli jestem jedynym programistą w firmie to te wszystkie zasady mnie "obowiązują" lub powinny "obowiązywać"?

Nie, chociaż w takiej sytuacji większość z nich bym stosował będąc na Twoim miejscu. Ale ja juz taki jestem, że wolę sobie ułatwiać niz utrudniać.

AdamWox napisał(a):

Ok, argumentacja jest i dociera do mnie. Kilka osób wspomniało o testach. Moim zdaniem kolejna strata czasu.

Testy do systemu, którym zajmuję się obecnie wykonują się kilka minut. Gdybym chciał wszystko sprawdzić ręcznie zajęłoby to chyba z tydzień.
Generalnie inwestycja w pisanie testów to jedna z najszybciej zwracających się inwestycji. Generalnie w ciągu dnia można napisać testy pozwalające zaoszczędzić dziesiątki godzin w skali roku.
No chyba, że ktoś wali strony-wizytówki albo coś takiego, wtedy testowanie automatyczne nie ma sensu.

Rozumiem, że "dobre praktyki" robi się po to, aby się aplikację dobrze testowało?

Nie tylko testowało, ale też rozwijało i naprawiało. Jeśli w projekcie panuje porządek, to gdy coś się stanie, wiadomo gdzie szukać błędu i jak go naprawić. Gdy panuje bałagan nie jest to takie łatwe.

@Fuffu Chcesz powiedzieć, że używasz dobrych praktyk, aby cię "nie kusiło", żeby rozwiązać coś inaczej, szybciej, niezgodnie z dobrymi praktykami?

Pójście na skróty to często dołożenie sobie roboty w przyszłości. Trzeba umieć wybrać, co ma sens, a co nie w danej sytuacji.

Spróbuje to ogarnąć jakoś w sumie, odpowiedzi są bardzo podobne. Skoro repozytorium zapisuje i odczytuje dane to dlaczego nie mogę ich od razu "obrobić" w repozytorium i zapisać? Chcecie mi powiedzieć, że ze względu na "dobre praktyki" powinienem stworzyć interfejs (serwis), który mi te dane przerobi, tylko po to, aby repozytorium mogło zapisać w takiej formie w jakiej powinno być zapisane?

Nie, bo używanie repozytorium jeśli nie jest Ci potrzebne, to nie jest dobra praktyka. Podobnie jak robienie bezcelowych interfejsów.

W prostej aplikacji w zupełności wystarczy taki podział: Controller <-> Service <-> ORM <-> baza danych, gdzie Service to tak naprawdę Transaction Script. Repozytoria są potrzebne w skomplikowanej domenie, tam gdzie stosuje się DDD, w każdym innym przypadku to jakiś antywzorcowy kult cargo.

PRZYKŁAD:

public bool CancelAgreement(int agreementId)
{
    var agreement = _dbContext.Agreements.Find(agreementId);
    
    //Te dwie linie poniżej powinny być w serwisie? Po co?
    agreement.Canceled = true;
    agreement.CancelDate = DateTime.Now;

    _dbContext.Agreements.Update(agreement);
    _dbContext.SaveChanges();
}

No to jest właśnie przykład skryptu transakcji. Nazywanie tego repozytorium to kompletne poplątanie pojęć.

Uważam, że więcej czasu spędza się na generowaniu tych wszystkich interfejsów do serwisów i repozytoriów niż na faktycznym programowaniu.

No tak, jeśli na siłę próbuje się używać repozytoriów tam, gdzie nie mają sensu, to masz rację.
Nazywasz kulty cargo dobrymi praktykami, a potem stwierdzasz, że nie mają sensu. Wnioski dobre tylko teza fałszywa.

0
somekind napisał(a):
AdamWox napisał(a):
  1. Czy konieczny jest podział solucji na projekty, gdzie każdy projekt za coś odpowiada. Dlaczego nie można tego rozdzielić folderami w jednym projekcie?
    ...
    Repozytoria są potrzebne w skomplikowanej domenie, tam gdzie stosuje się DDD, w każdym innym przypadku to jakiś antywzorcowy kult cargo.

W przypadku programowania mozna wiele rzeczy podpiac pod kult cargo, ale tutaj akurat za szeroko pojechales po bandzie.
Jesli chodzi o reszte - swietne podsumowanie.

0

@Fuffu Chcesz powiedzieć, że używasz dobrych praktyk, aby cię "nie kusiło", żeby rozwiązać coś inaczej, szybciej, niezgodnie z dobrymi praktykami? 🤔🙈

Trochę tak, trochę nie. Rozumiem, że pytanie odnosi się do podziału solucji na projekty.

Podział solucji na projekty dla mnie sprawia, że wszystko staje się bardziej czytelne. Każda warstwa, każda odpowiedzialność ma z góry zdefiniowane miejsce. Wtedy trudno o to, żeby przypadkiem powiązać ze sobą warstwy, które nie powinny o sobie nic wiedzieć. I to, że nie kusi wtedy, żeby zrobić coś brzydkiego jest REZULTATEM takiego podejścia a nie samym celem.

0

@somekind: Nawiązanie do kultu cargo mnie położyło, "miszcz" :D wydaje mi się, że twoja odpowiedź najlepiej odpowiada na moje pytania.

@Fuffu Czepiłem się bardziej tego, że potrzebujesz "dobrych praktyk" w projekcie, aby nie musieć myśleć. Cały czas powinno się mieć na uwadze jak rozwiązać problem/wdrożenie bez względu na to jak twój projekt wygląda i czy ma repozytoria, serwisy, "dobre praktyki", czy na chama "wciskasz" dbContext do kontrolera.

0
Constantic napisał(a):

W przypadku programowania mozna wiele rzeczy podpiac pod kult cargo, ale tutaj akurat za szeroko pojechales po bandzie.

Nazywanie repozytorium dowolnego DAO w dowolnym projekcie. nawet jeśli nie daje żadnej wartości dodanej, nie wprowadza żadnej abstrakcji, a jedynie jest zazwyczaj prostym wrapperem na ORMa, to nie jest kult cargo?
Bo jak dla mnie to jest doskonały przykład kultu cargo. Ludzie robią coś, co gdzieś u kogoś widzieli, w ogóle nie rozumiejąc po co się to robi.

0
somekind napisał(a):

Nazywanie repozytorium dowolnego DAO w dowolnym projekcie. nawet jeśli nie daje żadnej wartości dodanej, nie wprowadza żadnej abstrakcji, a jedynie jest zazwyczaj prostym wrapperem na ORMa, to nie jest kult cargo?
Bo jak dla mnie to jest doskonały przykład kultu cargo. Ludzie robią coś, co gdzieś u kogoś widzieli, w ogóle nie rozumiejąc po co się to robi.

Stary, glosisz tutaj jakies wlasne prawdy objawione, ja natomiast zacytuje jedno ze zrodel, z ktorymi zgadza sie kilka innych osob (widoczne w komentarzach) zywiac nadzieje, iz akurat ta czesc Twoich pogladow zostanie potraktowana z dystansem. Zacytuje:

  • "...repozytorium ma znajdować się pomiędzy warstwą dostępu do danych, a domeną aplikacji i zachowywać się jak kolekcja, czyli dostarczać możliwość zapisywania, pobierania i usuwania obiektów."
  • "...logika biznesowa ma operować na interfejsach repozytoriów, których konkretne implementacje znajdują się w innym module aplikacji, który potrafi obsługiwać konkretne źródło danych."
  • "...repozytorium niekoniecznie musi być powiązane z bazą danych. Równie dobrze może ono korzystać z plików albo być fasadą np. na SOAPowe API zewnętrznego serwisu. Jedyne co jest istotne, to fakt, że daje imitujący kolekcję dostęp do obiektów domenowych."

zrodlo: Repozytorium najbardziej niepotrzebny wzorzec projektowy

1

Lol :D :D :D No takiej argumentacji przeciwko sobie się nie spodziewałem. ;)

Autor tego artykułu w tamtym czasach nie znał zwrotu kult cargo, dlatego go nie użył. Niemniej jednak już wstępny akapit doskonale opisuje to zjawisko - robienia pewnych bezsensownych rzeczy z przyzwyczajenia i bezmyślnie. A dalej następuje już obalanie poszczególnych składowych tego kultu.

0

A swistaki dalej zwijaja swoje sreberka :D.

6

Czy ja mam zwidy, czy właśnie @Constantic zaczął argumentować przeciwko somekidowi na podstawie bloga, którego SK jest autorem? xD

giphy.gif
3

Stary somekind zaorał nowego somekinda :-) Normalnie Matrix :-)
screenshot-20191208221644.png

0

Macie jakiś link do repozytorium na githubie gdzie są właściwie rozdzielone repozytoria i serwisy, tak jak to powinno być?

EDIT//
https://github.com/SheikArbaz/todo-service/tree/master/src/main/java/com/example/todo
dobry przykład?

EDIT 2//
Skoro wg was nie nalezy do każdej encji robić własnego repozytorium, to jak należy je obsługiwać by pobierać dane z niej czy je do niej wrzucać? Tworzymy jedno takie god-repository które oddziałowuje na wszystko?jak to zaimplementować?
Jak możecie to z prostym przykładem w C#/Javie

1

@piotrek2137: w linkowanym repo nic ciekawego nie ma. Co do tego dlaczego repo pre encja to głupota chodzi o to, że (klasycznie) dla modelu Zamowienie-Pozycja zamówienia to repozytorium dla pozycji nie ma żadnego sensu, bo dostęp do nich powinien być jedynie poprzez odpowiednie zamówienie.

1
piotrek2137 napisał(a):

Skoro wg was nie nalezy do każdej encji robić własnego repozytorium, to jak należy je obsługiwać by pobierać dane z niej czy je do niej wrzucać? Tworzymy jedno takie god-repository które oddziałowuje na wszystko?jak to zaimplementować?

Repozytorium tworzy się dla całego agregatu, a obiektem, który repozytorium przyjmuje i zwraca jest aggregate root. W danym agregacie poza rootem może być jeszcze sporo encji.
To nie jest wtedy żadne god-repository, to po prostu użycie repozytorium zgodnie z zamysłem DDD, którego repozytoria są elementami.

2

Ad 1. Zależy, ale w większości przypadków nie. Polecam przeczytać
https://programmingwithmosh.com/net/should-you-split-your-asp-net-mvc-project-into-multiple-projects/
Ad 3. Logikę biznesową w controllerach trudno się testuje, ponieważ mają dużo zależności. Prosta rada, której zawsze przestrzegam - staraj się zawsze rozdzielać kod, który ma dużo zależności (controllery), od tego, który wykonuje logikę biznesową (np encje), wtedy nie będziesz miał problemów z testowaniem.
Ad 5. Wyjątki, których nie możesz obsłużyć powinny być łapane przez exception middleware i zwracać 500 klientowi. Jeśli potrafisz obsłużyć wyjątek to robisz to na najniższym poziomie.Tutaj jest dość fajnie opisany error handling i trochę o exceptionach:
https://enterprisecraftsmanship.com/posts/what-is-exceptional-situation/
https://enterprisecraftsmanship.com/posts/error-handling-exception-or-result/
Ad 6. Asynchroniczność nie przyspiesza działania aplikacji wbrew temu co możesz przeczytać w innych wypowiedziach. Postaraj się dobrze zrozumieć samą ideę. Asynchroniczność może (ale nie musi) poprawić skalowalność aplikacji w określonych warunkach.
Ad 7. Automappera polecam do prostych mapowań, jeśli musisz tworzyć dużo reguł w konfiguracji tzn, że coś może być nie tak. Nigdy nie używaj mappera do tworzenia encji.
Ad 8. DTO do zdefiniowania kontraktu. Gdybyś w swoim API zwracał klientowi model biznesowy, to np. przy każdej zmianie nazwy property w modelu, klient musiałby się dostosować i zmienić to też u siebie. DTO nie powinny się zmieniać od tak, podczas gdy modele/encje zmieniają się ciągle.

Ad 1. Zależy od projektu. Jeśli nad projektem pracuje spory team to jak najbardziej ma to sens.1.

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