Implementacja najlepszych praktyk .NET Full Webdev na przykładzie własnej aplikacji pisanej po godzinach

4

Mój pierwszy post więc w ramach przywitania się z community od razu podzielę się wiedzą i swoją pracą ;)

Na moim koncie gh:
https@://github.com/piotr-mamenas/performance-app

Udostępniłem aplikacje nad którą pracuję po godzinach od dwóch lat, apka składającą się w sumie z 10 assembly, w tym backendowego Serwisu RESTowego w Web Api 2 oraz aplikacji Postback (wspomaganej akcjami SPA w Ajax) napisanej w MVC 5. Służyła mi ona do pogłębiania wiedzy odnośnie architektury aplikacji. Generalnie jest to ekstrakt najlepszych praktyk sugerowanych przez guru .NET developmentu takich jak: Dino Esposito, Martin Fowler, Robert C. Martin czy Gary McLean Hall i wiele wielu innych których chętnie i fanatycznie czytam :)

Co zatem znajdziecie w środku?

  1. Najlepsze praktyki z zakresu Domain Driven Development z wykorzystanie jednego Bounded Context zawierającego kilkanaście agregatów Encji z pojedyńczym Entity Root per Agregat, odpowiadającym za tranzakcyność. Ponadto podział na Base Entities, Relationship Entities oraz ValueObjects. Nie-anemiczny model domeny (Encje domenowe zawierają logikę biznesową),
  2. Podział na tzn Domain Layer, Infrastructure Layer, Service Layer i Presentation Layer wraz z zastosowaniem reguł SOA
  3. Zastosowane najlepsze praktyki z zakresu SOLID. Patternów Go4 (np Facade, Singleton, Builder, Decorator, Strategy), DRY, KISS oraz innych.
  4. Na frontendzie kod Javascriptowy jest podzielony na oddzielne komponenty izolowane Revealing Module Pattern opartej na Closures. Jeśli chodzi o strukturę strony to głównie jest to Bootstrap 3 z mieszanką flexa.
  5. Dependency Injection z wykorzystaniem kontenera Ninject w celu wtrzykiwania Repozytoriów i Serwisów bezpośrednio do Konstruktorów Kontrolerów.
  6. Implementacja tranzakcyjności operacji poprzez patterny Unit of Work i Generic Repository z użyciem Entity Framework 6 podpiętej pod bazę relacyjną. Z podziałem na zewnętrzne konfiguracje oraz przeładowaniami konfiguracji dla Base Entities.
  7. Najlepszymi praktykami w serializacji danych z użyciem DTO, View Modeli oraz mapowania obiektów AutoMapperem.
  8. Authentykacje i Autoryzacje opartą na Identity Framework 2 oraz ciasteczkach.

Jeśli ktoś się uczy lub chciałby po prostu poszerzyć swoją wiedzę z zakresu dobrych praktyk to jest to miejsce w którym można podpatrzeć rozwiązania implementacyjne i mam nadzieję nauczyć się czegoś nowego ;)

4

Zaraz przyjdzie @somekind i Ci powie że Generic Repository to anti-pattern.

1
kzkzg napisał(a):

Zaraz przyjdzie @somekind i Ci powie że Generic Repository to anti-pattern.

I częściowo będzie miał racje, ale to zależy też od konkretnego zastosowania, jeśli aplikacja głównie wykonuję operacje CRUD'owe w REST to generic repository może się okazać dobry patternem bo kod jest bardzo powtarzalny i użycie generic repository ułatwia development. Jeśli jest to jednak aplikacja ze sporą ilością customowej logiki to powinno się skłaniać bardziej w stronę zwykłego repository (Większość case'ów).

Generalnie generyczność sprawia że wystawione są metody którę potencjalnie developer mógłby użyć do wylania logiki fetchującej dane poza repozytoria do kontrolerów a to zaszarza kod na długą metę i generuje duplikacje. W małych zespołach które są dobrze zdyscyplinowane nie stanowi to problemu przy odpowiedniej komunikacji ale w duży zespołach nie da się upilnować żeby jakiś junior nie przepchnął pull requesta z dziwnym kodem.

0

Jeśli ktoś robi repozytorium do CRUDa, to znaczy, że w ogóle nie rozumie po co istnieją repozytoria.
A opakowanie contextu EF we własne genetyczne repozytorium to zwykły WTF.

0

Wszystko zalezy od sytuacji. EF to konkretna technologia i uzywajac jej wszedzie bezposrednio wiazemy z nia nasz kod. A juz na pewno nie powinnismy wiazac z nim logiki domeny. Raz jeszcze podkreslam bo to wazne- zalezy od sytuacji. Gdzies takie uzaleznianie bedzie akceptowalne, a gdzies nie. Tak naprawde dyskusja dotyczaca wrapowania ORMa fajnie obnaza kwestie tego ze nie ma uniwersalnych dogmatow (o czym wspomnialem w komentarzu wyzej). Nagle okazje sie ze zasada programowania do interfejsow jednak nie zawsze jest stosowana. Bardzo dobrze! Jesli nie ma takiej potrzeby, to sie tego nie robi. Podobnie z wrapowaniem ORMow. Inna osoba/zespol natomiast moze wolec wyabstrahowac technologie perzystencji/ORM- rowniez nie ma problemu.

Chociaz w tym przypadku, kiedy czytam DDD i CRUD to jakos jedno nie za bardzo z drugim pasuje. Albo mamy jakas konkretna, chociaz minimalnie zlozona domene albo cos do czego wystarczy zwykly CRUD. Jesli to drugie, to gdzie tu miejsce na repozytoria w sensie DDD? Swoja droga w tym linku wyraznie jest napisane A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection, wiec jesli uznac Martina Fowler'a za jakis autorytet (chyba mozna sie zgodzic ze tak wlasnie jest, prawda?) to wychodzi na to ze abstrahowanie EF (mapping layer) jest czyms normalnym.

1

Ale właśnie w podanym cytacie jest ładnie wyjaśnione czym jest repozytorium i co przechowuje. Zdecydowanie nie chodzi o przechowywanie encji bazodanowych, tylko o obiekty domenowe. Z wszystkimi ich zachowaniami i relacjami. Z tego co widzę, tutaj "repo" po pierwsze nie przechowuje żadnych obiektów, a tylko je zwraca, po drugie, zwraca właśnie encje, a nie obiekty domenowe.

Z tego powodu jest tylko wrapperem, głupią przelotką. I poza zasłonięciem odwołania do EF niczego nie oferuje, więc w gruncie rzeczy jest zbędne w tym przypadku i dodaje niepotrzebną warstwę, którą trzeba przeszukać w kodzie, gdy szuka się jak jest budowane wyciąganie encji.

Edit: Przy czym w tym kontekście mam na myśli, że "Encja" to encja w sensie bazy danych, nie obiektu domenowego jakim jest w DDD.

0

@Klojtex: zaznaczam że nie pisałem o generycznym repo tak jak w tym przypadku. Masz jak najbardziej rację. Jedno tylko sprostowanie- z dzisiejszymi frameworkami rzadko kiedy istnieje potrzeba oddzielenia encji z baz danych od modeli domenowych. W większości przypadków to właśnie model domenowy będzie zapisywany jako encje. Zazwyczaj nie ma potrzeby na klasy pośrednie (encje) ponieważ modele domenowe mogą być używane przez ORM przy zachowaniu ignorancji perzystencji.

EDIT: przepraszam, użyłem złego taga.

1
Aventus napisał(a):

Zazwyczaj nie ma potrzeby na klasy pośrednie (encje) ponieważ modele domenowe mogą być używane przez ORM przy zachowaniu ignorancji perzystencji.

Pod warunkiem, że użyje się ORMa, który to umożliwia. Czyli nie EF.

0

Rozumiem, że się bardzo starasz i to co powiem może zaboleć. No więc.

Najlepsze praktyki z zakresu Domain Driven Development z wykorzystanie jednego Bounded Context zawierającego kilkanaście agregatów Encji z pojedyńczym Entity Root per Agregat, odpowiadającym za tranzakcyność. Ponadto podział na Base Entities, Relationship Entities oraz ValueObjects. Nie-anemiczny model domeny (Encje domenowe zawierają logikę biznesową),

Nie odróżniasz Encji od ValueObject ale nie martw się to powszechne. Ogólnie słaby model wywalasz wszędzie porpertisy i enumy, zamiast modelować język. Nie używasz identityField, zamiast tego agregujesz inne agregaty :P.

W niektórych sytuacjach obarczasz agregat odpowiedzialnością, która go nie dotyczy, zamiast spleść to w fabryce.

Mógłbyś spokojnie wydzielić 3 konteksty i 3 dziedziny w tym jedną główną. Pewnie nigdy nie narysowałeś ani jednej mapy kontekstów, zgadza się.?

Podział na tzn Domain Layer, Infrastructure Layer, Service Layer i Presentation Layer wraz z zastosowaniem reguł SOA

A jeśli to SOA z DDD, to dlaczego warstwa serwisowa-prezentacyjna widzi wszystkie warstwy.? Moim zdanie takie coś nazywa się lazanią.

Udostępniłem aplikacje nad którą pracuję po godzinach od dwóch lat,

Nie ściemniaj, dobra. :P

Szczerze to nie chcę mi się wszystkiego wytykać. Poza tym mam swoją robotę.

1

@RoboCat @somekind @Klojtex

  1. Jeśli chodzi o repozytoria to jest to często źle rozumiany koncept, repozytorium jest implementacją idei Inversion of Control w SOLID a zatem:
    "High-level modules should not depend on low-level modules. Both should depend on abstractions."

Taką formą abstrakcji od Entity Framework będącego "High-level module" jest repozytorium. Dzięki użyciu repozytorium jesteśmy w stanie odizolować wykorzystanie metod ORM'a od faktycznej logiki biznesowej i nawet wymienić ORM'a jak zmienią się wymagania co nie byłoby wykonalne gdybyśmy wszędzie mieli kwerendy linq'owe bijące do czystego context'u. Druga sprawa to czysty porządek. W tej architekturze repozytoria odpowiadają za enkapsulacje logiki chwytającej dane, serwisy za wykonywanie logiki biznesowej a kontrolery tylko wystawiają serie operacji. O wiele przyjemniej czyta się w kontrolerach coś takiego:

  var partnerPayments = paymentRepository.getByPartnerId(partnerId);
  var paymentSettlementResult = paymentService.settlePayments(partnerPayments);
  if (paymentSettlementResult.IsValid) {
    etc ...
  }

aniżeli setki opatrzonych grubymi komentarzami kwerend linqowych lub co gorsza bicia do procedur na bazie. W ten sposób unikamy też dużej ilości duplikacji kodu bo po nazwach łatwo wnioskuje się że coś już istnieje i nie trzeba tego jeszcze raz pisać ;)

W tym kontekście, dobrze się czyta tych panów:
https://programmingwithmosh.com/tag/repository-pattern/
https://lostechies.com/jimmybogard/2008/08/21/services-in-domain-driven-design/

Inna kwestia to zastosowanie izolacji tranzakcyjnej na poziomie aplikacyjnym przez użycie Unit of Work pattern, co pozwala zapewnić że wszystkie operacje wykonane w jednym requescie albo wszystkie sie powiodą albo wszystkie trafi szlag a nie że tranzakcje w połowie będą zapisywane do bazy bo developer przy użyciu contextu może sobie dowolnie zapisywać zmiany kilkukrotnie w jednym requescie. Repozytorium umożliwia wdrożenie UoW.

Nie jest prawdą że repozytorium to część DDD, repozytorium może być tak samo używane z ideą Event Sourcing'u w których modele domenowe jako takie mogą nawet nie istnieć, a źródłem danych są wyłącznie zdarzenia (fajnie współpracuje to z BDD). Są też rozwiązania które łączą wszystko razem z użyciem CQRS'a naprzykład. Co się tyczy konkretnej implementacji to zależy od Use Case'a, w niektórych małych aplikacjach bawienie się w zaawansowaną architekturę nie ma sensu i dodaje tylko complexity. W przypadku zmiany samego Generic Repository na proste Repository to jest to na liście poprawek od jakiegoś czasu w tym projekcie ;)

@Aventus @RoboCat @somekind @Klojtex
2. Encje i modele Domenowe to jedno i to samo, w przypadku Entity Framework 6 z użyciem Code First reprezentowane pojedyńczymi klasami POCO oraz opcjonalną klasą konfiguracyjną pozwalającą nam zkonkretyzować jak ORM ma się zachowywać w danej situacji.

Co się tyczy EF, to jest to jedna z możliwych implementacji, równie dobrze można użyć innego ORM takiego jak nHibernate albo klasyczne relacje zdesignowac pod document model i chwycić po np Mongo albo RavenDB. Nie jest również prawdą że EF nie obsługuje ignorancji persystencji:
Lista providerów EF6: https://docs.microsoft.com/en-us/ef/ef6/fundamentals/providers/

Warto podkreślić że EF jest jednak ORM'em skupionym głównie na bazach relacyjnych, tak samo jak nikt raczej nie wymaga żeby MongoDriver nagle zaczął dobijać się do baz relacyjnych, tak nie oczekujmy że EF promowany jako Object Relational Mapper nagle zacznie bezkrytycznie współpracować tak z waszymi plikami płaskimi jak i bazami noSQLowymi.

Imho singleton nie jest anti-patternem, jest to natomiast pattern który jest dosyć nadużywany i przez to często mówi się o nim jako anti-patternie. W miejscach gdzie nie możemy pozwolić na to żeby w aplikacji istniały dwie różne instancje tej samej klasy lub chcemy zablokować możliwość równoczesnego dostępu do property singleton sprawdza się świetnie. Mówię tu chociażby o konfiguracji aplikacji czy dostępie do security. W praktyce wszystko zależy od case'a. Nawet Service Location może być wykorzystany w wyjątkowych sytuacjach poprawnie bez odnoszenia się do niego jako Anti-patterna.

@Hosap
Strasznie dużo nieuzasadnionej wyższości i arogancji w tym poście ale mimo wszystko postaram się odnieść (choć już widzę po komentarzach innych że mam do czynienia z trollem).

Nie wiem co rozumiesz przez nieodróżnianie Entity od ValueObject, ValueObject to wartość wbudowana wewnątrz entity czyli zamiast oddzielać coś do innej tabeli, przechowujemy jako compound type na innych tabelach zduplikowany. Najprostszym przykładem może być typ Money któremu samemu w sobie tabela się nie należy ale możemy przy jej pomocy zmodelować operacje na gotówce (monety etc) i fajnie żeby bez dodatkowej tabeli ta logika móc wykorzystać w poszczególnych encjach. I w ten właśnie sposób Value Object użyty jest w tej konkretnej implementacji.

Pozostałe rzeczy które powiedziałeś nie mają najmniejszego sensu. Mylisz też pojęcia, Monolith często nazywany lazanią jest typem architektury, tak jak typem architektury są mikroserwisy (I tak to repo to monolith). SOA i DDD natomiast to paradygmaty które można użyć w konkretnych typach architektury, DDD można użyć tak w Onion'ie jak i Mikroserwisach. Równie dobrze można zastąpić DDD, Event Sourcingiem albo użyć Event Sourcing w połączeniu z CQRS'em i DDD. Nie mają zbyt wiele wspólnego z samym typem architektury.

Nie ściemniaj, dobra. :P

Nie rozumiem? masz historię commitów na repo? Projekt zaczynałem dwa lata temu w celu dogłębnego zrozumienia rzeczy z którymi spotykałem się na co dzień na projektach w pracy i nie zawsze do końca rozumiałem zależności.

@Aventus @RoboCat @somekind @Klojtex @Hosap
Część implementacji może nie być optymalna bo dawno nie były revisitowane tak jak w przypadku braku podziału na Bounded Contexts czy użyciu Generic Repository gdzie klasyczne Repo starczyłoby. To tylko zbiór mający zobrazować jak wszystkie zasady wyglądają wspólnie i był moim polem eksperymentalnym w wielu wypadkach, nie wszystko jest perfect, fair enough, zapraszam do submittowania pull requestów albo podzielenia się własną pracą. Ja mam własną listę poprawek i na bieżąco ulepszam tą architekturę żeby ludzie mieli wgląd jak wygląda to wszystko razem i samemu też na bieżąco się czegoś uczyć.

1

Równie dobrze można zastąpić DDD, Event Sourcingiem(...)

To akurat wprowadza w blad. DDD nie jest wzorcem ani zadnym konkretnym rodzajem architektury i nie powinien byc przedstawiany w opozycji do ES czy CQRS.

1

@roch.mamenas: pobieżnie przeglądałem sobie Twój kod bo nie mam czasu żeby bardziej nad tym przysiąść. Odnośnie warstwy prezentacji, a konkretnie organizacji kodu to proponuję poczytać o feature folders/slices. Nie każdemu to musi odpowiadać, ale moim zdaniem (i wielu innych) jest to lepszy sposób organizowania kodu UI.

Ponadto warto pomyśleć o przepisaniu tego na ASP Core.

1

odpuść, dlatego nikt mu nie odpisuje. To takie ostatnie wycie trolla, wszyscy ignorują to co pisze to zaczyna wyzywać ludzi aby tylko dostać jakąkolwiek negatywną reakcje. Gość nawet nie ma pojęcia o czym piszę co już kilka osób mu w tym poście wytknęło.

Co wytknęło i gdzie.? To ty nie masz pojęcie, o czym mówisz te twoje "dobre praktyki" mają tyle wspólnego z dobrymi praktykami co historia papiestwa ze świętością.

Proszę, naruszenie tak podstawowych zasad, jak Open Close.

Od kiedy to UintOfWork jest fasadą na "repozytoria"?

https://github.com/piotr-mamenas/performance-app/blob/master/Infrastructure/UnitOfWork.cs

W domenie wystawiłeś schemat bazy danych i udajesz, że to model domeny. Pfff....

Bardzo byś chciał, żebym kogoś wyzywał, bo wtedy można by mnie wytknąć palcem i zamieść pod stół, póki co to wy mnie wyzywacie razem z Aventusem od Trola, trzeba być nie powiem kim, żeby tego nie zauważyć. Szukanie kolegów w ten sposób jest żałosne, nie rób tak.

Edit:
W ogóle to po co ci tam te interfejsy od repozytoriów ?
Jak się nazywa taka odmiana Unit of work? "Unit of work manager"?
Tak ja wiem zaraz odpiszesz, bo tak pisało w książce Freemana albo to jest dobra praktyka, bo jakiś hindus na YouTube tak mówi.
Żałosne...

12
roch.mamenas napisał(a):

Generalnie jest to ekstrakt najlepszych praktyk sugerowanych przez guru .NET developmentu takich jak: Dino Esposito, Martin Fowler, Robert C. Martin czy Gary McLean Hall i wiele wielu innych których chętnie i fanatycznie czytam :)

Ten kod to zbiór najpopularniejszych antywzorców, kultów cargo, niekiedy doprawione zwykłym spaghetti:

  1. DAO nazwane dla niepoznaki repozytorium.
  2. "Repozytoria" wstrzykiwane bezpośrednio do kontrolerów.
  3. Podobnie jak i Unit of Work.
  4. Jedno "repozytorium" per tabela: https://github.com/piotr-mamenas/performance-app/tree/master/Infrastructure/Repositories/Business
  5. Logika biznesowa w kontrolerach. https://github.com/piotr-mamenas/performance-app/blob/master/Web/Controllers/AccountController.cs#L111
  6. Eksponowanie detali ORMa (czyli IQueryable) z "repozytorium": https://github.com/piotr-mamenas/performance-app/blob/2ad09663e79edd1cd42d468a1255c413ac0990e5/Infrastructure/Repositories/Business/PartnerRepository.cs#L17
  7. No i nieśmiertelne, nikomu niepotrzebne GetAll: https://github.com/piotr-mamenas/performance-app/blob/436ea821a85e4b8ed0fb284820a7021dbfea4430/Infrastructure/Repositories/Repository.cs#L23
  8. Style CSS w domenie: https://github.com/piotr-mamenas/performance-app/blob/master/Core/Domain/TileWidgets/FontAwesomeIcon.cs Ktoś tu pisał coś o SOA?
  9. Infrastruktura w domenie (uwierzytelnianie): https://github.com/piotr-mamenas/performance-app/tree/master/Core/Domain/Identity
  10. Infrastruktura w domenie (jakieś joby jak sądzę): https://github.com/piotr-mamenas/performance-app/blob/master/Core/Domain/Tasks/ServerTask.cs
  11. Używanie klas infrastrukturalnych w domenie, zamiast tworzenia typów opisujących domenowe przeznaczenie: https://github.com/piotr-mamenas/performance-app/blob/master/Core/Services/AssetService.cs#L25
  12. Walidacja zwracająca stringi: https://github.com/piotr-mamenas/performance-app/blob/master/Core/Domain/Partners/Partner.cs#L46
  13. UoW będący wrapperem na repozytoria. Ktoś coś pisał o SOLID?
  14. Anemic domain model - mutowalne "encje" będące odwzorowaniem struktury bazy. Wstawienie czasem publicznej metody, nie zamienia ADM w DDD.

I tylko tego nie umiem nazwać: https://github.com/piotr-mamenas/performance-app/blob/master/Web/Controllers/ContactController.cs#L27 - jakieś podejrzane rzutowanie parametru konstruktora, bo pole jest innego typu. WTF?

roch.mamenas napisał(a):
  1. Jeśli chodzi o repozytoria to jest to często źle rozumiany koncept, repozytorium jest implementacją idei Inversion of Control w SOLID a zatem:
    "High-level modules should not depend on low-level modules. Both should depend on abstractions."

Repozytoria zostały zdefiniowane przez Fowlera (definicję podlinkował @Aventus) oraz Evansa:

For each type of object that needs global access, create an object that can provide the illusion of an in-memory collection of all objects of that type. Set up access through a well-known global interface. Provide methods to add and remove objects, which will encapsulate the actual insertion or removal of data in the data store. Provide methods that select objects based on some criteria and return fully instantiated objects or collections of objects whose attribute values meet the criteria, thereby encapsulating the actual storage and query technology. Provide REPOSITORIES only for AGGREGATE roots that actually need direct access. Keep the client focused on the model, delegating all object storage and access to the REPOSITORIES

Nie ma potrzeby silenia się na własne definicje.

Taką formą abstrakcji od Entity Framework będącego "High-level module" jest repozytorium. Dzięki użyciu repozytorium jesteśmy w stanie odizolować wykorzystanie metod ORM'a od faktycznej logiki biznesowej

Mylisz repozytorium z DAO.

i nawet wymienić ORM'a jak zmienią się wymagania

Ale Ty tego nie zrobisz - bo wystawiasz z "repozytoriów" IQueryable.

Sprawa jest prosta - mamy DDD, mamy encje, mamy aggregate rooty i kolekcje operujące na tychże AR to są repozytoria. Jak nie mamy DDD, to nie mamy repozytoriów tylko DAO.

Druga sprawa to czysty porządek. W tej architekturze repozytoria odpowiadają za enkapsulacje logiki chwytającej dane

No nie bardzo: https://github.com/piotr-mamenas/performance-app/blob/master/Web/Controllers/PartnerController.cs#L135

serwisy za wykonywanie logiki biznesowej

Jedną metodę na cały ten projekt.

aniżeli setki opatrzonych grubymi komentarzami kwerend linqowych

Tak, bo nie da się zrobić porządnie, trzeba mieć albo "repozytoria" wstrzyknięte w kontrolery, albo grube kontrolery na kontekście EF.

W tym kontekście, dobrze się czyta tych panów:
https://programmingwithmosh.com/tag/repository-pattern/
https://lostechies.com/jimmybogard/2008/08/21/services-in-domain-driven-design/

I gdzie tam jest napisane, żeby wstrzykiwać repozytoria i UoW do kontrolerów?

developer przy użyciu contextu może sobie dowolnie zapisywać zmiany kilkukrotnie w jednym requescie.

Przy użyciu UoW też może.

Repozytorium umożliwia wdrożenie UoW.

Niczego nie umożliwia, UoW można mieć zupełnie niezależnie od repozytorium, nawet na gołym kontekście EF. Co więcej, nie da się nawet używać EF bez UoW.

Nie jest również prawdą że EF nie obsługuje ignorancji persystencji:
Lista providerów EF6: https://docs.microsoft.com/en-us/ef/ef6/fundamentals/providers/

Bosz... "Persistence ignorance" nie oznacza dowolności wyboru bazy danych, tylko uzależnienie kodu "encji" od technologii ORMa.

Pozostałe rzeczy które powiedziałeś nie mają najmniejszego sensu. Mylisz też pojęcia, Monolith często nazywany lazanią jest typem architektury, tak jak typem architektury są mikroserwisy

Można mieć lazanię zarówno w monolicie jak i mikroserwisach, to są ortogonalne pojęcia.

Aventus napisał(a):

@Klojtex: zaznaczam że nie pisałem o generycznym repo tak jak w tym przypadku. Masz jak najbardziej rację. Jedno tylko sprostowanie- z dzisiejszymi frameworkami rzadko kiedy istnieje potrzeba oddzielenia encji z baz danych od modeli domenowych.
W większości przypadków to właśnie model domenowy będzie zapisywany jako encje. Zazwyczaj nie ma potrzeby na klasy pośrednie (encje) ponieważ modele domenowe mogą być używane przez ORM przy zachowaniu ignorancji perzystencji.

To nie jest kwestia frameworka. To się nazywa Single Responsibility Principle. Jeśli klasa zawiera jednocześnie logikę i opisuje model składowania, to ma dwie odpowiedzialności, więc SRP łamie. A jeśli system zawiera jakąś faktyczną logikę biznesową, a nie tylko CRUD na sterydach, to encje domenowe znacząco się różnią od tego, co trzeba przechowywać, więc tym bardziej trzymanie jednego zestawu klas do dwóch celów nie ma sensu. Do tego encje DDD powinny się opierać o enkapsulację. Jaką masz enkapsulację z publicznymi setterami wymaganymi przez EF?

Niestety, większość ludzi wciąż myśli tabelkami i procedurami, zmapują sobie ADM przy użyciu ORMa ukrytego pod "repozytoriami" i twierdzą, że mają DDD, a to tak naprawdę skrypt transakcji rozbity na biedawarstwy (za to modnie nazwane!).

A jak bardzo EF nie jest PI opisuje chociażby ten wpis: https://enterprisecraftsmanship.com/2014/11/29/entity-framework-6-7-vs-nhibernate-4-ddd-perspective/

0

Jaką masz enkapsulację z publicznymi setterami wymaganymi przez EF?

Nie wiem, ja tam używam EF Core z wartościami przekazywanymi przez konstruktor i prywatnymi properties. Mało tego, możesz nawet używać read only properties. Nie chcę nic mówić ale na dzień dzisiejszy to NHibernate bardziej łamie PI... I tak naprawdę nie ma nic złego w takim drobnym łamaniu PI (np. wymagany virtual w NH. Chociaż to brzydko wygląda).

Liczba rzeczy jakie musisz zmieniać w POCO aby było kompatybilne z EF Core: 0. Naprawdę. Wszystko można skonfigurować przez konwencję EF.

3
somekind napisał(a):

Jak nie mamy DDD, to nie mamy repozytoriów tylko DAO.

Biorąc pod uwagę że repozytoria oficjalnie narodziły się w książce Fowlera wydanej w 2002 roku, natomiast DDD oficjalnie narodziło się w książce Evans wydanej w 2003 roku, rodzi się zasadnicze pytanie :D, Skąd Fowler miał repozytoria skoro repozytoria nie mogą istnieć bez DDD które powstało później :D?

Odpowiedź że Fowlera jest ziomkiem Evans i miał wcześniejszy dostęp do rękopisu się nie liczy :P.

Widzę tylko dwie możliwości:

  • albo podróże w czasie istnieją
  • albo repozytoria poza DDD istnieją
0
neves napisał(a):
somekind napisał(a):

Jak nie mamy DDD, to nie mamy repozytoriów tylko DAO.

Biorąc pod uwagę że repozytoria oficjalnie narodziły się w książce Fowlera wydanej w 2002 roku, natomiast DDD oficjalnie narodziło się w książce Evans wydanej w 2003 roku, rodzi się zasadnicze pytanie :D, Skąd Fowler miał repozytoria skoro repozytoria nie mogą istnieć bez DDD które powstało później :D?

Odpowiedź że Fowlera jest ziomkiem Evans i miał wcześniejszy dostęp do rękopisu się nie liczy :P.

Widzę tylko dwie możliwości:

  • albo podróże w czasie istnieją
  • albo repozytoria poza DDD istnieją

@somekind Ma rację:

Równie dobrze możesz zapytać dlaczego w książce Fowlera są opisane Value Objecty, IdnetityField oraz Domain Model, Sokoro są również w książce o DDD.

DDD, nie wymyśla, żadnych nowych wzorców DDD uczy jak je poprawnie używać. Jak modelować dziedzinę.

Nie wiem, ja tam używam EF Core z wartościami przekazywanymi przez konstruktor i prywatnymi properties. Mało tego, możesz nawet używać read only properties. Nie chcę nic mówić ale na dzień dzisiejszy to NHibernate bardziej łamie PI... I tak naprawdę nie ma nic złego w takim drobnym łamaniu PI (np. wymagany virtual w NH. Chociaż to brzydko wygląda).

Liczba rzeczy jakie musisz zmieniać w POCO aby było kompatybilne z EF Core: 0. Naprawdę. Wszystko można skonfigurować przez konwencję EF.

Zawsze bardziej czytelne i ergonomiczne będzie używanie dwóch modeli. Poza tym 80% ludzi nie rozumie, że id w bazie to nie jest tożsamość encji i wystawianie togo w modelu dziedziny jako encji lub agregatu tylko dlatego, że "ORM tak chce" nie sensu.

0

Pozwólcie, że odpowiem poście na komentarze:

A tak z ciekawości. Jak trzeba zrobić test jednostkowy np z użyciem EF Core . Np dodanie czegoś do bazy a następnie wyszukanie po ID. Id w teście nie przypiszesz sam bo masz set jako private. Kiedyś pisałem na forum wątek ale nikt mi nie odpowiedział jak się za to zabrać. W EF Core w sumie problem rozwiązuje InMemory (bo sam wygeneruje ID) ale w EF 6 jak musisz podpiąc pod DBSeta jakąś fejkową listę to ona nie nada żadnego ID po zrobieniu SaveChanges.

To nie są żadne testy jednostkowe tylko funkcyjne, ewentualnie akceptacyjne i używa się do nich innego frameworka.

Spoko, wiem. Jak wspominałem już dawno to robiłem i ostatnie projekty wzoruje na tym: https://github.com/JasonGT/NorthwindTraders

Jak ktoś tworzy paczki i zależności pomiędzy nimi w ten sposób Domain.Infrastructure.
To znaczy, że nie ma pojęcie czym jest Port Adapter i jakakolwiek inna pokrewna architektura która się z niej wywodzi.

https://github.com/JasonGT/NorthwindTraders/tree/master/Northwind.Domain

Sama "Domena" to dalej ORM Development.

https://github.com/JasonGT/NorthwindTraders/tree/master/Northwind.Domain/Entities

0
KreonPoKawie napisał(a):

Zawsze bardziej czytelne i ergonomiczne będzie używanie dwóch modeli. Poza tym 80% ludzi nie rozumie, że id w bazie to nie jest tożsamość encji i wystawianie togo w modelu dziedziny jako encji lub agregatu tylko dlatego, że "ORM tak chce" nie sensu.

Czemu nie ma sensu :)? Evans w niebieskiej książce nie ma nic przeciwko jeśli id zostanie wygenerowane przez źródło danych:

This means of identification may come
from the outside, or it may be an arbitrary identifier created by and
for the system, but it must correspond to the identity distinctions in
the model.

0

Czemu nie ma sensu :)? Evans w niebieskiej książce nie ma nic przeciwko jeśli id zostanie wygenerowane przez źródło danych:

Id może również być wygenerowany w serwisie i podany w fabryce etc. Nie o to mi chodzi, chodzi mi o to, że brak rozdzielenia dwóch modeli sprzyja tworzeniu różnych dziwnych "tworów", które są zależne od mechaniki ORM'a między innymi również myleniu Value Objectu z Entity.

Ludzie zwykle myślą w ten sposób, Hmm Client, Hmm... PruductOwner Hmm... Identyfikator, bo muszę to jakoś zapisać, w bazie danych więc jest to encja. Właśnie, że w większości przypadków jest to wartość.

Poza tym trochę odbiegając od głównego tematu, mapowanie każdej wartości na relacje w bazie tylko dlatego, że ORM tak chce, jest przypadkową złożonością, a nawet niepożądaną a używanie jednego modelu właśnie temu sprzyja.

Edit:

A "Identity Field" to też jest encja tylko dlatego że ma Id? Ja zawsze myślałem że to wartość.

Edit2:

Tutaj masz przykład z Czerwonej książki: https://github.com/VaughnVernon/IDDD_Samples_NET/tree/master/iddd_collaboration/Domain.Model/Collaborators

3

@roch.mamenas: Próbowałem znaleźć chociaż jeden kawałek logiki w domenie i jakiś test do tego. Nie znalazłem, ale C# . NET to nie moja działka. Może źle szukam? Możesz wskazać jakieś testy, najlepiej do jakiegoś nietrywialnego kawałka logiki?

0

Ja właśnie też próbowałem ogarnąć. W domenie są same DTO? Czy cały projekt Core to domena - jeśli tak to tam są same walidacje czy coś więcej?

0

Ja właśnie też próbowałem ogarnąć. W domenie są same DTO? Czy cały projekt Core to domena - jeśli tak to tam są same walidacje czy coś więcej?

Zwykle Core używa się do oznaczenia części domeny tej "korowej" jak Domain.Core.

Mnie bardziej zastanawia czy ten gościu wróci z refaktoryzowanym projektem czy raczej "wymięknie".

0

Nie wierzę w "najlepsze praktyki dla technologii X", bo zwykle to bullshit.

Jako JavaScriptowiec czasem czytam np. o "najlepszych praktykach dla Reacta", i pukam się w głowę, ponieważ zwykle jest tam mnóstwo bullshitu i subiektywnej agendy ("to jest najlepsza praktyka dlatego, że nasza firma tak robi"). Ew. chwilowych mód, czy uwarunkowań zależnych od wersji Reacta.

Zastosowane najlepsze praktyki z zakresu SOLID. Patternów Go4 (np Facade, Singleton, Builder, Decorator, Strategy), DRY, KISS oraz innych.

patterny Go4 z połączeniu z KISS brzmią trochę dziwnie o tyle, że większość osób, która używa patternów Go4 robi to po to, żeby pokomplikować kod, a nie uprościć.

(tzn. patterny Go4 nie są złe, po prostu ludzie je wykorzystują w złych celach, dla zaspokojenia ego i napompowania kodu patternami, żeby poczuć się "pro").

roch.mamenas napisał(a):

Dependency Injection z wykorzystaniem kontenera Ninject

Nie wiem, co to ten Ninject, ale wątpię, żeby użycie konkretnej biblioteki do wzorca projektu można było nazwać najlepszą praktyką.

jak dla mnie najlepsza praktyka to jeden z najczęściej powtarzanych buzzwordów, razem z "jakością kodu" czy "programowaniem zwinnym". I zadziwiająco często "najlepsze praktyki" nie sprawdzają się w... praktyce, projekty z (podobno) wysoką "jakością kodu" są nieutrzymywalne, a programowanie "zwinne" przypomina często waterfall inaczej nazwany.

3

Ile ludzi tyle najlepszych praktyk. Ogólnie warto znać te najlepsze i podważać. Mnie natomiast najbardziej denerwują ludzie którzy potrafią wywalić coś prostego w myśl refaktoryzacji, zrobią np. jakiegoś Chaina bez sensu i ich jedynym argumentem jest to że teraz jest obiektowo. Dupa nie obiekty, jak żyję nie widziałem obiektowego kodu.

AA i ta sama osoba potrafi namnożyć milion extensionów do klasy - gdzie w c# są zwyczajnymi metodami statycznymi - i gdzie te obiekty?

Ale to mój taki prywatny ból d**y :(

2

A powiedzcie, czy zamiast robić tak:

return asset.Returns
                    .Where(r => r.Id == 0)
                    .Select(crr => crr.Rate)
                    .SingleOrDefault();

Nie lepiej zrobić tak:

return asset.Returns.SingleOrDefault(r => r.Id == 0).Rate

?

2

@somekind:

1 Skąd wziąłeś stwierdzenie o DAO? Wszystkie encje to POCO's i są w całości zdecouple-owane od implementacji ORM'a, nawet virtuali na kolekcjach dla lazy loadu nie znajdziesz (co zresztą jest imho anty-praktyką)
2,3 Już było podnoszone i jest to opinia
4 Każdy agregat ma oddzielne repozytorium, aż musiałem się upewnić czy nie mam jakiegoś dzikiego repozytorium dla encji która nie jest Entity Rootem które kiedyś dodałem ale przyznam że na pierwszy rzut oka nie widzę.
6 7 Sam podniosłem ten temat parę postów wcześniej i sam zwróciłem uwagę że jest to zaszłość która idzie do poprawki..
8 Jak chcesz to inaczej zrobić i co w alternatywnej implementacji będzie lepsze? Jakoś trzeba na bazie zapisać która ikona wyświetla się na widgecie.
9 To nie infrastruktruralny kod, User jest zcouple'owany do innych encji domenowych takich jak UserWidgets, w przypadku wydzielenia tego do
infrastruktury nie jesteś w stanie odwołać się do nich z powrotem w domenie z powodu circular ref, jeśli uważasz to za anty praktykę, to proszę o podanie alternatywy. Pozostały kod Identity który faktycznie odpowiada za authentykacje a nie jest tylko POCO jak ta klasa User, jest w infrastrukturze.
10 Ten cały agregat jest tak czy siak do wywalenia bo zrezygnowałem z pisania własnego task serwera (w kodzie jest sporo takich zaszłości które zacząłem niedawno czyścić, po prostu chciałem coś wypróbować, poeksperymentować i później zostało a teraz czyszczę na bieżąco żeby już ten projekt zamknąć ;))
11 O jakiej klasie infrastrukturalnej mówisz, przecież to czysty serwis z jedną metodą.
13 Też było już dwukrotnie podnoszone i przyznałem Ci w tym wypadku racje. Implementacja UoW powstawała 2 lata temu i od dawna jest na liście do refactoringu wraz z Generic Repository i tym castowaniem w konstruktorach.

Ta aplikacja to ok 8 tys linii kodu + markup i piszę ją sam po godzinach 4fun od 2 lat. Doszukiwanie się stringów w walidacjach, czy logiki która nie została jeszcze wyekstraktowana do serwisów (bo zwyczajnie nie było sensu pisać serwisu dla 2 linijek kodu!) tylko dlatego żeby na siłę pokazać "temu siakiemu owakiemu który śmie napisać że niby dobre praktyki!!oneone1 ja mu pokaże!!" jest nadużyciem. Jeśli coś znajdujesz, to wymień i ja to poprawię jeśli ma sens w wolnej chwili (tak jak przyznałem Ci rację w kilku punktach powyzej), nie sądzę że znajdziesz kod jakiegokolwiek programisty który byłby idealny (lub pasujący do swoich przekonań), zawsze do czegoś idzie się doczepić.. dlatego, naprawdę.. teksty typu "Ten kod to zbiór najpopularniejszych antywzorców, kultów cargo, niekiedy doprawione zwykłym spaghetti" są toksyczne i nic do dyskusji nie wnoszą.

Jeśli chcesz przyśpieszyć to zapraszam do submittowania pull request'ów, ja zmerguje wszystko co ma sens.

1
Aventus napisał(a):

Nie wiem, ja tam używam EF Core z wartościami przekazywanymi przez konstruktor i prywatnymi properties. Mało tego, możesz nawet używać read only properties.

To wspaniale, że Microsoftowi za trzecim chyba razem udało zrobić się ORMa, który ma ficzery obecne w ORMach od kilkunastu lat.
Tylko pytanie było o EF, Ty odpowiadasz o EFC. Tak trochę nie na temat, bez związku i w ogóle w stylu "a u was biją murzynów".

Nie chcę nic mówić ale na dzień dzisiejszy to NHibernate bardziej łamie PI... I tak naprawdę nie ma nic złego w takim drobnym łamaniu PI (np. wymagany virtual w NH. Chociaż to brzydko wygląda).

No tyle, że jak łamiemy PI, to już go nie mamy, więc po co udawać, że jest?
A virtual w NH nigdy nie był, ani nie jest ogólnie wymagany.

Liczba rzeczy jakie musisz zmieniać w POCO aby było kompatybilne z EF Core: 0. Naprawdę. Wszystko można skonfigurować przez konwencję EF.

No świetnie, tylko jak pokazał niedawny temat, nadal nie można nawet wybrać rodzica z odfiltrowaną kolekcją dzieci.
Swoją drogą muszę kiedyś sprawdzić, jak łatwo zmapować Dictionary.

neves napisał(a):

Biorąc pod uwagę że repozytoria oficjalnie narodziły się w książce Fowlera wydanej w 2002 roku, natomiast DDD oficjalnie narodziło się w książce Evans wydanej w 2003 roku

Uch... to będzie brutalne, ale ktoś Ci to musi napisać.

Wzorce projektowe nie biorą się z książek, są efektem praktycznych zastosowań, które się sprawdziły i zdobyły popularność. Niektórzy ludzie je potem opisują, tak jak to zrobił GoF z podstawowym zestawem wzorców, czy tak jak zrobił Fowler z wzorcami "enterprise".

Technicznie rzecz biorąc, repozytoria czyli pseudo-kolekcje modeli domenowych stanowiące abstrakcję nad źródłem danych istniałyby nawet gdyby nikt ich nigdy nie opisał ani nie nazwał. I nadal tak samo różniłyby się od innych obiektów/wzorców.

Repozytoria to nie są wrapery na ORMa, repozytoria nie służą do crudowych operacji na tabelach bazy za pośrednictwem anemicznych encji. Jeśli ktoś ma takie obiekty, to to nie są repozytoria - nawet jeśli je tak nazwie. Nazewnictwo niezgodna z zachowaniem i implementacją nie jest dowodem sprytu tylko ignorancji bądź niechlujstwa.

roch.mamenas napisał(a):

1 Skąd wziąłeś stwierdzenie o DAO? Wszystkie encje to POCO's i są w całości zdecouple-owane od implementacji ORM'a, nawet virtuali na kolekcjach dla lazy loadu nie znajdziesz (co zresztą jest imho anty-praktyką)

Mam wrażenie, że nie do końca rozumiesz co to DAO, skoro piszesz coś o encjach POCO. DAO to takie obiekty dające dostęp do warstwy perzystencji, najczęściej nieprawidłowo nazywane repozytoriami.

2,3 Już było podnoszone i jest to opinia

To nie jest opinia. Wpychanie logiki biznesowej do kontrolerów sprawia, że:

  • nie masz już MVC;
  • aplikacja jest nietestowalna bez postawienia serwera HTTP albo mockowania kontekstu HTTP. Słabe to strasznie.

4 Każdy agregat ma oddzielne repozytorium, aż musiałem się upewnić czy nie mam jakiegoś dzikiego repozytorium dla encji która nie jest Entity Rootem które kiedyś dodałem ale przyznam że na pierwszy rzut oka nie widzę.

Możliwe, że moje wrażenie jest po prostu mylne, po prostu zobaczyłem bardzo zbliżoną liczbę repozytoriów i encji, co by świadczyło raczej o ich nieprawidłowym użyciu. Jeśli nie to jest powodem, to strzelam, że przykład, który wybrałeś sobie na swoją aplikację jest zbyt trywialny, aby stosować tu DDD.

8 Jak chcesz to inaczej zrobić i co w alternatywnej implementacji będzie lepsze? Jakoś trzeba na bazie zapisać która ikona wyświetla się na widgecie.

Baza powinna trzymać to, co jest istotne w modelu biznesowym. To warstwa prezentacji powinna tenże model na elementy ekranowe konwertować. Nie wiem do czego konkretnie ma to służyć, więc nie mogę odpowiedzieć bardziej precyzyjnie.

9 To nie infrastruktruralny kod, User jest zcouple'owany do innych encji domenowych takich jak UserWidgets, w przypadku wydzielenia tego do
infrastruktury nie jesteś w stanie odwołać się do nich z powrotem w domenie z powodu circular ref, jeśli uważasz to za anty praktykę, to proszę o podanie alternatywy.

Zarządzanie rolami i użytkownikami, to jest infrastruktura. Po co w ogóle odwoływać się do tych obiektów w domenie?

11 O jakiej klasie infrastrukturalnej mówisz, przecież to czysty serwis z jedną metodą.

O Tuple<,>. Tam powinna być jakaś konkretna klasa.

Ta aplikacja to ok 8 tys linii kodu + markup i piszę ją sam po godzinach 4fun od 2 lat. Doszukiwanie się stringów w walidacjach, czy logiki która nie została jeszcze wyekstraktowana do serwisów (bo zwyczajnie nie było sensu pisać serwisu dla 2 linijek kodu!) tylko dlatego żeby na siłę pokazać "temu siakiemu owakiemu który śmie napisać że niby dobre praktyki!!oneone1 ja mu pokaże!!" jest nadużyciem. Jeśli coś znajdujesz, to wymień i ja to poprawię jeśli ma sens w wolnej chwili (tak jak przyznałem Ci rację w kilku punktach powyzej), nie sądzę że znajdziesz kod jakiegokolwiek programisty który byłby idealny (lub pasujący do swoich przekonań), zawsze do czegoś idzie się doczepić.. dlatego, naprawdę.. teksty typu "Ten kod to zbiór najpopularniejszych antywzorców, kultów cargo, niekiedy doprawione zwykłym spaghetti" są toksyczne i nic do dyskusji nie wnoszą.

Fajnie, że włożyłeś dużo pracy, i że chcesz się podzielić, ale uważam po prostu, że aby reklamować to jako zbiór dobrych praktyk powinieneś to najpierw bardziej oczyścić i zrefaktoryzować. Na razie to wygląda jak taki zbiór "wprawek" - tu sobie przetestuję coś, tam coś innego, żeby sprawdzić jakieś podejście, które w pracy się przyda. Też mam takie projekty, tylko się nimi nie chwalę.
No i może jednak dziedzina jest zbyt prosta, żeby robić z tego DDD, skoro masz tak mało logiki biznesowej, a tak wiele cruda.

0

Zarządzanie rolami i użytkownikami, to jest infrastruktura. Po co w ogóle odwoływać się do tych obiektów w domenie?

Jest to domena generyczna lub pomocnicza i powinna mieć swój oddzielny kontekst.
Może być również z powodzeniem zamodelowana w Domain Modelu. Autoryzacja - tak to na pewno jest część infrastruktury, ale nie przydzielanie ról użytkownikowi.
Błąd autora polega na tym, że używa w DM zależności do komponentu ASP. Co jest nie do przyjęcia.

No tyle, że jak łamiemy PI, to już go nie mamy, więc po co udawać, że jest?
A virtual w NH nigdy nie był, ani nie jest ogólnie wymagany.

Ale wtedy wyłączysz sobie lazy loading to również jest trochę w stylu "a u was biją murzynów"

Jeśli nie to jest powodem, to strzelam, że przykład, który wybrałeś sobie na swoją aplikację jest zbyt trywialny, aby stosować tu DDD.

Nie zgadzam się nawet w tym trywialnym przykładzie można spokojnie stosować część wzorców taktycznych, jak i strategicznych. Autor po prostu ma słabe pojęcie o modelowaniu w Domain Modelu i ogólnie DDD.

O Tuple<,>. Tam powinna być jakaś konkretna klasa.

@roch.mamenas
Tuple znaczy tyle co krotka czyli rekord w tabeli.

0

@somekind

aplikacja jest nietestowalna bez postawienia serwera HTTP albo mockowania kontekstu HTTP. Słabe to strasznie.

Dlaczego to jest słabe? testujesz realną ścieżkę w aplikacji

Test controller logic in ASP.NET Core

Tak samo z fakowaniem db do testów - w 99% nie ma sensu używać prawdziwej, ale czasem zdarzy się, że np. po zmianie bazy takie testy wykryją Ci jakiegoś buga typu: niestandardowy znaczek nagle nie chce się dać zapisać, a wcześniej działał i był używany.


Jak ludzie radzą sobie z Includami używając repo?

Czy taki Helper jest repem?

public class SomeLayer
{
	private readonly Context _data;
	
	public SomeLayer(Context context)
	{
		_data = context;
	}

	public User GetUserByIdSoftly(Guid Id)
	{
		return _data.DataTable.Where(x => !x.IsSoftDeleted).FirstOrDefault(x => x.Id == Id);
	}

	public User GetUserById(Guid Id)
	{
		return _data.DataTable.FirstOrDefault(x => x.Id == Id);
	}
}

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