Mapowanie na Domain Model

0

Jak powinno się implementować Domain Model wraz z ORM.?
Dużo robi się krzyku o to że setery są be bo "naruszają" enkapsulacje.
Tylko, że nikt nigdy nie mówi o konkretnym bugu, w tej konkretnej sytuacji.

Prosty przykład Encji (bez sensu ale mniejsza o to)

public class Order : IValidation
    {
        private string _orederAmount;

        public virtual int Id { get; protected set; }
        public virtual string OrederAmount
        {
            get => _orederAmount;
            protected set => _orederAmount = value;
        }
        public ValidationResult ValidationResult { get; private set; }

        public bool IsValid
        {
            get
            {
                var fiscal = new OrderIsValidValidation();
                ValidationResult = fiscal.Valid(this);
                return ValidationResult.IsValid;
            }
        }

        public decimal Profit(decimal tax)
        {
             var amount = _orederAmount;
            //....
        }
    }

Potem w serwisie mapuje na to tabele Order za pomocą Nhibernate. Co można zarzucić takiemu podejściu?

Spotkałem się z opinią że tak nie wolno, że Presistance dla ORM powinno być oddzielnym bytem a Domain Model To taki adapter na te presistance.
No więc pytam.

0

Jak dla mnie, to po prostu łamanie SRP, a problem może się pojawić wtedy, gdy na tej samej tabeli będzie opartych wiele operacji biznesowych, i taka klasa będzie miała masę metod, które mają sens tylko w określonym kontekście. Zrobi się z tego jakiś god object.
Dla prostych przypadków wolę już mieć model bez logiki i operujące na nim proceduralnie serwisy niż takich biznesowo-bazodanowych transwestytów w kodzie.

0

Wydaje mi się, że przesadzasz, Gdzie te złamanie SRP, że niby Validacja? To twój ogólny problem do Domain Model i DDD. Czy po prostu implementacja ci się nie podoba ?

0

i taka klasa będzie miała masę metod, które mają sens tylko w określonym kontekście. Zrobi się z tego jakiś god object.

A to zleży co dla ciebie jest usługą a co encją z kontekstu dziedziny...

0

Sam siebie waliduje, sam wylicza swój podatek... Potem do tego dojdzie samodzielne wyliczanie obniżki ceny, później samodzielne wyliczenie okresów rozliczeniowych dla zamówień cyklicznych... I tak dalej.

O ile w prostych przypadkach od biedy możesz takie rozwiązanie popełnić, to niestety obawiam się, że bardzo szybko przypadek przestanie być prosty, a Ty zaczniesz rwać włosy z łydek, bo zrobi Ci się kaszana kodzie i to cudo zrobi się takim właśnie god-class.

SRP łamie toto choćby z powodu właśnie walidacji samego siebie. Choć pewno ktoś by mi za to stwierdzenie chętnie urwał kabel od internetu, to od walidacji są osobne mechanizmy, bo samosądy są niefajną sprawą, a i później w sumie nie do końca wiadomo jak to sprzężyć z realną logiką aplikacji żeby wyglądało po ludzku. Tak samo już wyliczanie podatku prawdopodobnie bym odseparował, bo w zależności od systemu prawnego sposób obliczania może się zupełnie różnić, niekoniecznie tylko wysokością ale jakąś większą logiką.(fakt, że o ile się orientuję to czysta fantazja aktualnie, ale jednak jest to jakiś, prymitywny, rodzaj logiki wkraczającej już w domenę na moje oko)

PS: Za dużo edycji... Teraz spojrzałem, że odseparowane to niby jest, choć znowu tworzysz tam sztywne powiązanie w ten sposób.
Nadal jednak podtrzymuję, że walidator lepiej wywołać zewnętrznie, bo wtedy możesz chociażby sterować scenariuszami walidacji w zależności od sytuacji przez, choćby, rzucenie argumentu.

0

Nie ważne, mniejsza o szczegóły pytanie brzmiało jak mapować na Domain Model ...!!

Racja te obliczanie podatku tu nie pasuje, pisałem w nawiasie że przykład jest bez sensu...
Ale widzę tutaj metody np. submit() która zmienia status zamówienia na submit, AddToArchive(), GetTotalCost i tp. Validacja to zależy jeśli chcesz sprawdzić czy np user istnieje w bazie to jest validacja po stronie logiki biznesowej, przecież nie użyjesz do tego ViewModel.

2

TL;DR
Jedyne co bym mógł zarzucić temu kawałkowi kodu nie znając szerszego kontekstu, to to że walidacja powinna być metodą a nie propertisem ;)


W obecnym podejściu niewiele można zarzucić temu fragmentu kodu, bo jest wyrwany z kontekstu i nie wiemy wszystkiego. No ale po kolei :

  • settery są "be" jeśli są publiczne, jeśli są prywatne lub chronione to nie naruszają enkapsulacji, no chyba że patrz punkt następny
  • czyli kwestia czy mieć oddzielny persistence model i domain model, czy jeden wspólny, jeśli nasze źródło danych jest modyfikowane tylko wyłącznie za pomocą naszego modelu, to jak najbardziej jeden model może być wystarczający, no chyba że tak nie jest, wtedy pojawia się punkt następny
  • czyli poprawność modelu do której wszystko się sprowadza(z tym ze model zawsze powinien być w poprawnym stanie prawie wszyscy się zgadzają w środowisku DDD)

Więc jeśli odtworzenie stanu modelu ze źródła danych nie może wprowadzić wspólnego modelu w stan niepoprawny to wszystko jest jak najbardziej okej (i setery i wspólny model):
-zawsze poprawne źródło danych -> ORM używa propertisów do odtworzenia stanu -> poprawny wspólny model
natomiast jeśli nasz scenariusz wygląda następująco (setetry są be, jak i wspólny model jest be)
-źródło danych które może być zmienione za plecami naszego modelu -> ORM używa propertisów do odtworzenia stanu -> dostajemy model w nie wiadomo w jakim stanie -> musimy walidować przy każdej próbie skorzystania z encji -> :(

Wiec pytanie czym jest walidacja i po co ona jest w tej encji?

  • jeśli do sprawdzenia czy niezmienniki danej encji są poprawne np (OrederAmount > 0), to oznacza że możemy mieć model w stanie niepoprawnym, a tego nie chcemy, model musi być zawsze poprawny
  • czy do sprawdzenia poprawności zamówienia z punktu biznesowego czyli np czy OrederAmount < od stanu magazynowego w chwili składania zamówienia, jest jak najbardziej okej

Odnośnie SRP i gdzie walidować, no to tutaj już nie ma konsensusu, jedni preferują encje, jedni serwisy biznesowe. Tak samo z tym liczeniem zysku, jeśli jest to coś bardzo prostego i niezmiennego uszło by zostawienie tego w encji, jakiś bardziej skomplikowany proces -> serwis.

**Podsumowując : model domenowy chcemy mieć zawsze w stanie poprawnym **

1
MrBean Bean napisał(a):

Wydaje mi się, że przesadzasz, Gdzie te złamanie SRP, że niby Validacja?

SRP jest złamane bo Order ma dwa powody do zmiany - zarówno zmianę logiki biznesowej jak i przechowywanych danych.
Wsadzenie tam jeszcze walidacja to też naruszenie SRP, ale głównym problemem jest co innego - encje nie powinny się walidować po utworzeniu na polecenie zewnętrznych obiektów, po prostu nie powinno być w ogóle możliwości utworzenia niespójnej encji. A więc walidacja powinna być zrealizowana na etapie tworzenia obiektu, najczęściej w konstruktorze.

To twój ogólny problem do Domain Model i DDD.

Ale jakiego DDD? Tu nic takiego nawet nie widać.

neves napisał(a):

W obecnym podejściu niewiele można zarzucić temu fragmentu kodu, bo jest wyrwany z kontekstu i nie wiemy wszystkiego. No ale po kolei :

To zależy co to miało być, bo jeśli DDD, to w tym kontekście można zarzucić wszystko.

Więc jeśli odtworzenie stanu modelu ze źródła danych nie może wprowadzić wspólnego modelu w stan niepoprawny to wszystko jest jak najbardziej okej (i setery i wspólny model):

Ale później, przy pomocy publicznych setterów można już ten model wprowadzić w stan niespójny, co nie jest OK, dlatego przy DDD nie powinno ich być.

-zawsze poprawne źródło danych

Coś takiego w ogóle istnieje?

Odnośnie SRP i gdzie walidować, no to tutaj już nie ma konsensusu, jedni preferują encje, jedni serwisy biznesowe.

W serwisach domenowych powinno się walidować tylko to, czego encja sama nie może.

0
neves napisał(a):

TL;DR
źródło danych które może być zmienione za plecami naszego modelu -> ORM używa propertisów do odtworzenia stanu -> dostajemy model w nie wiadomo w jakim stanie -> musimy walidować przy każdej próbie skorzystania z encji -> :(

Czyli chodzi ci o zbieżność w tym co zapisują różne źródła danych do sesji ORM? Mnie to wygląda jak źle napisany kod, to tak jak byś nie używał sesji tak jak powinieneś. Może podać jakiś przykład.?

0
somekind napisał(a):
MrBean Bean napisał(a):

Wydaje mi się, że przesadzasz, Gdzie te złamanie SRP, że niby Validacja?

Ale później, przy pomocy publicznych setterów można już ten model wprowadzić w stan niespójny, co nie jest OK, dlatego przy DDD nie powinno ich być.

Czytałem DDD Vrnona i Evana ale jakoś nikt nigdzie o tym nie wspomnieli raczej pachnie mi to Fowlerem i jego wywodami na temat Anemic Domain. Które i tak są trochę bezsensu bo to biznes deklaruje złożoność modelu a nie czyjeś zachcianki.

Jeśli masz taki z tym problem to przecież możesz zrobić sobie dwa konstruktory jeden pusty dla ORM i chronione propertysy i drugi constructor dla twojej dyspozycji wtedy nie będziesz mógł zmienić stanu po stworzeniu obiektu.

0
MrBean Bean napisał(a):
neves napisał(a):

TL;DR
źródło danych które może być zmienione za plecami naszego modelu -> ORM używa propertisów do odtworzenia stanu -> dostajemy model w nie wiadomo w jakim stanie -> musimy walidować przy każdej próbie skorzystania z encji -> :(

Czyli chodzi ci o zbieżność w tym co zapisują różne źródła danych do sesji ORM? Mnie to wygląda jak źle napisany kod, to tak jak byś nie używał sesji tak jak powinieneś. Może podać jakiś przykład.?

Nie każda baza należy do jednej aplikacji, i nie zawsze źródłem danych jest baza.

MrBean Bean napisał(a):

Jeśli masz taki z tym problem to przecież możesz zrobić sobie dwa konstruktory jeden pusty dla ORM i chronione propertysy i drugi constructor dla twojej dyspozycji w tedy nie będziesz mógł zmienić stanu po stworzeniu obiektu.

Ja nie mam z tym problemu, bo po prostu w skomplikowanych przypadkach mam rich domain model odseparowany od persistence modelu i nie łamię SRP, a w przypadku prostej logiki piszę logikę w transaction scriptach operujących bezpośrednio na persistence modelu. Ergo albo programuję proceduralnie albo obiektowo, ale za każdym razem świadomie i stosując się do zasad, a nie tworząc jakieś dziwne hybrydy, które prędzej czy później spowodują problemy.

0
somekind napisał(a):
MrBean Bean napisał(a):
neves napisał(a):

TL;DR
źródło danych które może być zmienione za plecami naszego modelu -> ORM używa propertisów do odtworzenia stanu -> dostajemy model w nie wiadomo w jakim stanie -> musimy walidować przy każdej próbie skorzystania z encji -> :(

Czyli chodzi ci o zbieżność w tym co zapisują różne źródła danych do sesji ORM? Mnie to wygląda jak źle napisany kod, to tak jak byś nie używał sesji tak jak powinieneś. Może podać jakiś przykład.?

Nie każda baza należy do jednej aplikacji, i nie zawsze źródłem danych jest baza.

MrBean Bean napisał(a):

Jeśli masz taki z tym problem to przecież możesz zrobić sobie dwa konstruktory jeden pusty dla ORM i chronione propertysy i drugi constructor dla twojej dyspozycji w tedy nie będziesz mógł zmienić stanu po stworzeniu obiektu.

Ja nie mam z tym problemu, bo po prostu w skomplikowanych przypadkach mam rich domain model odseparowany od persistence modelu i nie łamię SRP, a w przypadku prostej logiki piszę logikę w transaction scriptach operujących bezpośrednio na persistence modelu. Ergo albo programuję proceduralnie albo obiektowo, ale za każdym razem świadomie i stosując się do zasad, a nie tworząc jakieś dziwne hybrydy, które prędzej czy później spowodują problemy.

Nie rozumiem dlaczego uważasz, że mapowanie z ORM na DM to złamanie SRP. Przecież DM ma odpowiadać jednemu obiektowi w bazie i zawierać jego niezbędne zachowania. Zachowania które są bardziej ogólne lub należą do warstwy widoku i tp. należy wywalić za płot. Jak to u ciebie wygląda najpierw mapujesz na presistance a potem gdzieś w serwisie mapujesz to na ten twój rich obiekt, czy jakiś adapter z niego robisz? Mnie to bardziej przypomina jakiś sadomasochizm. No chyba, że tak bardzo ci zależy na auto mapowaniu nie wiem.

Nie każda baza należy do jednej aplikacji, i nie zawsze źródłem danych jest baza.

To tak trudno zaimplementować Unit Of Work?
O co ci teraz z tym chodzi?

Ergo albo programuję proceduralnie albo obiektowo

Ty tak na serio z tym ?

2
MrBean Bean napisał(a):

Nie rozumiem dlaczego uważasz, że mapowanie z ORM na DM to złamanie SRP. Przecież DM ma odpowiadać jednemu obiektowi w bazie i zawierać jego niezbędne zachowania.

Kto tak powiedział? DM to model logiki i procesów, on nie ma nic wspólnego z żadną bazą danych ani ze szczegółami formatu zapisu danych. To jest inna warstwa abstrakcji.

Zachowania które są bardziej ogólne lub należą do warstwy widoku i tp. należy wywalić za płot.

Nie ma co wywalać, bo DM nawet nie ma nic wspólnego z widokiem, obiekty DM nigdy nie są przetwarzane na obiekty widoku.

Jak to u ciebie wygląda najpierw mapujesz na presistance a potem gdzieś w serwisie mapujesz to na ten twój rich obiekt, czy jakiś adapter z niego robisz?

Mapuję, robienie adaptera byłoby wyciekiem abstrakcji.

Mnie to bardziej przypomina jakiś sadomasochizm. No chyba, że tak bardzo ci zależy na auto mapowaniu nie wiem.

Nie zależy mi na mapowaniu, to smutna konieczność. Sadomasochizmem jest za to przerabianie logiki biznesowej i jej testów z powodu zmiany formatu zapisu w źródle danych.

To tak trudno zaimplementować Unit Of Work?

Lol. :D
I jak Unit of Work w mojej aplikacji zabezpieczy bazę danych przed wstawieniem niepoprawnych danych przez jakiś zewnętrzny system?

0

Kto tak powiedział? DM to model logiki i procesów, on nie ma nic wspólnego z żadną bazą danych ani ze szczegółami formatu zapisu danych. To jest inna warstwa abstrakcji.

Tak ale encja może mieć związek z bazą jeśli jej stan ma być utrwalany ale jednocześnie może być czym abstrakcyjny w stosunku do bazy itp.

Nie ma co wywalać, bo DM nawet nie ma nic wspólnego z widokiem, obiekty DM nigdy nie są przetwarzane na obiekty widoku.

No właśnie ;)

Mapuję, robienie adaptera byłoby wyciekiem abstrakcji.

To powiesz jak to ma wyglądać?

Nie zależy mi na mapowaniu, to smutna konieczność. Sadomasochizmem jest za to przerabianie logiki biznesowej i jej testów z powodu zmiany formatu zapisu w źródle danych.

Nie rozumiem o co ci chodzi...? Co format zapisu ma do mapowania ? To jaki formatu zapisu w źródle danych potrzebuje anemicznej encji. ?

Lol. :D
I jak Unit of Work w mojej aplikacji zabezpieczy bazę danych przed wstawieniem niepoprawnych danych przez jakiś zewnętrzny.

Tak z tym wyskoczyłeś, że niezrozumiałem co to ma do tematu. Myślałem że mówimy o jakiejś spójności z kilku źródeł danych.

5

Tak ale encja może mieć związek z bazą jeśli jej stan ma być utrwalany ale jednocześnie może być czym abstrakcyjny w stosunku do bazy itp.

Nie. Utrwalasz sobie jakieś dane co najwyżej. Może są związane z jedna encją a może z wieloma na raz? Ale jedno z drugim w kodzie nie powinno być nijak związane. Model logiki biznesowej powinien być zupełnie niezależny zarówno od widoku (patrz: mvc/mvp) jak i od innych dupereli jak persystencja. Zmiana frameworka do Prezentacji nie powinna nijak wpływać na logikę, tak samo jak zmiana sposobu przechowywania danych. Jak napiszesz grę w szachy to przecież logika gry nie ulega zmianie bo zamiast buttonów w okienku nagle masz szachownicę 3d w OpenGL. Analogicznie z punktu widzenia logiki nic się nie zmieniło jeśli statystyki przechowujesz teraz w bazie danych a nie w xmlu na dysku. Partia szachów nadal przebiega tak samo!

0
Shalom napisał(a):

Tak ale encja może mieć związek z bazą jeśli jej stan ma być utrwalany ale jednocześnie może być czym abstrakcyjny w stosunku do bazy itp.

Nie. Utrwalasz sobie jakieś dane co najwyżej. Może są związane z jedna encją a może z wieloma na raz? Ale jedno z drugim w kodzie nie powinno być nijak związane. Model logiki biznesowej powinien być zupełnie niezależny zarówno od widoku (patrz: mvc/mvp) jak i od innych dupereli jak persystencja. Zmiana frameworka do Prezentacji nie powinna nijak wpływać na logikę, tak samo jak zmiana sposobu przechowywania danych. Jak napiszesz grę w szachy to przecież logika gry nie ulega zmianie bo zamiast buttonów w okienku nagle masz szachownicę 3d w OpenGL. Analogicznie z punktu widzenia logiki nic się nie zmieniło jeśli statystyki przechowujesz teraz w bazie danych a nie w xmlu na dysku. Partia szachód nadal przebiega tak samo!

Czyli w DM nie mogę utworzyć instancji obiektu persystencji a jedynie deklarować pola składowa przez konstruktor.??

A co jeśli klasa persystencji ma taką samą nazwę jak encja DM to jedynie namespace je identyfikuje czy używacie jakiejś konwencji dla nazwy?

Mapowanie z persystencji na DM musi być w serwisie ? czy, może w repo ?

2
MrBean Bean napisał(a):

Czyli w DM nie mogę utworzyć instancji obiektu persystencji a jedynie deklarować pola składowa przez konstruktor.??

Tak.

A co jeśli klasa persystencji ma taką samą nazwę jak encja DM to jedynie namespace je identyfikuje czy używacie jakiejś konwencji dla nazwy?

Zazwyczaj sufiksem, np. PersistenceModel albo Dbo.

Mapowanie z persystencji na DM musi być w serwisie ? czy, może w repo ?

W serwisie być nie może, bo to będzie wyciek perzystencji do domeny. To repozytorium jest odpowiedzialne za dostarczenie encji.

0

No ale jeśli mam klasę typu POCO a szczegóły mapowania ORM mam wywalone do innej klasy to też nie mogę tego POCO używać w DM?. Na przykład: chciał bym te POCO przekazać jako parametr i w środku poustawiać co trzeba.

0

Jeśli oprócz określonej domeny chcę wyświetlić zwykły gird w widoku to w warstwie Aplikacji nad Domeną muszę zrobić serwis który będzie operował na ORM z Infrastruktury i mapował to na DTO...? Czy to rzeczywiście taka ergonomia?

0
MrBean Bean napisał(a):

No ale jeśli mam klasę typu POCO a szczegóły mapowania ORM mam wywalone do innej klasy to też nie mogę tego POCO używać w DM?. Na przykład: chciał bym te POCO przekazać jako parametr i w środku poustawiać co trzeba.

To raczej nie będzie zgodne z DDD, to będzie anemiczny model.

MrBean Bean napisał(a):

Jeśli oprócz określonej domeny chcę wyświetlić zwykły gird w widoku to w warstwie Aplikacji nad Domeną muszę zrobić serwis który będzie operował na ORM z Infrastruktury i mapował to na DTO...? Czy to rzeczywiście taka ergonomia?

Nie, to nie jest ergonomia. Moim zdaniem w ogóle mieszkanie logiki biznesowej i wyświetlania danych to zły pomysł.

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