Referencja do serwisu w obiekcie domenowym

Referencja do serwisu w obiekcie domenowym
VD
  • Rejestracja:ponad 10 lat
  • Ostatnio:9 miesięcy
  • Postów:72
0

Cześć,

zacząłem się zastanawiać dlaczego raczej nie przekazuje się serwisów budując obiekty domenowe? Ma to jakieś wady? A może się tak robi, tylko ja tego jeszcze nie widziałem?

Standardowe podejście:

Kopiuj
Book book = booksFactory.create("Ender's Game", "Orson Scott Card")
booksRepository.save(book);

Dlaczego nie robimy np. tak:

Kopiuj
Book book = booksFactory.create("Ender's Game", "Orson Scott Card")
book.save();

W drugim przykładzie zakładam, że fabryka przy tworzeniu obiektu (np. mapowaniu go z postaci bazodanowej) dodatkowo w konstruktorze przekazuje repozytorium.
Jeśli jest to kwestia SRP to można znaleźć inne przykłady gdzie takie przekazanie jakiegoś portu do obiektu wydawałoby się ok. Np book.fetchPage(int page)

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
1

Jeśli miałbym tak zrobić, to nie przekazywałbym tam serwisu jako takiego, tylko raczej jakieś Function albo Supplier.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
VD
  • Rejestracja:ponad 10 lat
  • Ostatnio:9 miesięcy
  • Postów:72
0
Shalom napisał(a):

Jeśli miałbym tak zrobić, to nie przekazywałbym tam serwisu jako takiego, tylko raczej jakieś Function albo Supplier.

A dlaczego tak nie robisz? Bo próbuję znaleźć jakieś wady tego rozwiązania i dlaczego to się nie przyjęło.
Bo poszedłbym z tym nawet dalej - oznaczając wszystkie pola takiej klasy jako private (może oprócz id)

Charles_Ray
  • Rejestracja:prawie 17 lat
  • Ostatnio:około 12 godzin
  • Postów:1873
1

Drugie podejście to tzw. active records, które jest wspierane w takich frameworkach jak np. Ruby on Rails. W Springu też da się to osiągnąć: https://www.baeldung.com/spring-inject-bean-into-unmanaged-objects, chociaż referencja do beana/singletona z obiektu, który żyje jakiś krótszy czas mogłaby wydawać się nieco kontr-intuicyjna.

Osobiście nie spotkałem się z takim podejściem w Javie. Zwykle kod odpowiedzialny za wczytywanie i zapisywanie encji znajdował się poza samą encją. Encja przechowuje stan i nim zarządza (albo serwis), natomiast odczyt i zapis realizowany jest przez jakieś DAO/Repository. Natomiast nie jestem 100% przekonany, że tak musi być i tak jest najlepiej :)


”Engineering is easy. People are hard.” Bill Coughran
W0
  • Rejestracja:ponad 12 lat
  • Ostatnio:około godziny
  • Postów:3538
0

Podejrzewam, że winna temu jest nieszczęsna obsługa transakcji w Springu. Z twojego przykładu - obiekt book nie będzie zarządzane przez kontekst springowy, więc takie @Transactional nie zadziała. Tzn. pewnie nie zadziała, bo dawno się nie bawiłem w Springa.

edytowany 1x, ostatnio: wartek01
Charles_Ray
Jak wywołasz przez proxy, to zadziała. Więc wystarczyłoby do encji wstrzyknąć beana albo pobrać go przez ApplicationContext.
VD
  • Rejestracja:ponad 10 lat
  • Ostatnio:9 miesięcy
  • Postów:72
0
Charles_Ray napisał(a):

Drugie podejście to tzw. active records, które jest wspierane w takich frameworkach jak np. Ruby on Rails. W Springu też da się to osiągnąć: https://www.baeldung.com/spring-inject-bean-into-unmanaged-objects, chociaż referencja do beana/singletona z obiektu, który żyje jakiś krótszy czas mogłaby wydawać się nieco kontr-intuicyjna.

Ale mógłbym też to wstrzykiwać bez całej magii Springa. W ogóle na razie myślę o oderwaniu od springa, transakcji itd. Bo może powód dla którego tego się nie robi to właśnie zapisywanie bezpośrednio encji w bazie danych przez Hibernate.

Najprostsza wersja bez Springa:

Kopiuj
class Book(name: String, save: (Book) -> Unit)

class BooksFactory(
    private val booksRepository: BooksRepository
) {

    fun create(name: String) = Book(name, booksRepository::save)

}
Charles_Ray napisał(a):

Osobiście nie spotkałem się z takim podejściem w Javie. Zwykle kod odpowiedzialny za wczytywanie i zapisywanie encji znajdował się poza samą encją. Encja przechowuje stan i nim zarządza (albo serwis), natomiast odczyt i zapis realizowany jest przez jakieś DAO/Repository. Natomiast nie jestem 100% przekonany, że tak musi być i tak jest najlepiej :)

Też co do samego zapisywania nie jestem przekonany czy powinno być zawarte w obiekcie, po prostu najprościej mi było sobie to wyobrazić.
Ale równie dobrze mogłaby być np. metoda translate(String language), która zwróci książkę z przetłumaczonym tytułem (uderzając przy tym do jakiegoś serwisu tłumaczącego)

edytowany 1x, ostatnio: VeloxDigitis
Charles_Ray
  • Rejestracja:prawie 17 lat
  • Ostatnio:około 12 godzin
  • Postów:1873
0

Ale mógłbym też to wstrzykiwać bez całej magii Springa. W ogóle na razie myślę o oderwaniu od springa, transakcji itd. Bo może powód dla którego tego się nie robi to właśnie zapisywanie bezpośrednio encji w bazie danych przez Hibernate.

Hibernate zakłada, że istnieje transakcja z aktywną sesją. W jakiś sposób musisz ją umieścić w ThreadLocal, co swoją magią zapewnia (albo i nie - różnie z tym bywa) Spring. Nie wiem czy nie skończyłoby się na manualnej obsłudze transakcji z poziomu JPA API wtedy. Aby coś zapisać w bazie, musisz mieć transakcję, przynajmniej na poziomie JDBC.

Ale równie dobrze mogłaby być np. metoda translate(String language), która zwróci książkę z przetłumaczonym tytułem (uderzając przy tym do jakiegoś serwisu tłumaczącego)

To już raczej update takiej encji, skoro coś tłumaczysz. Albo zwrócenie nowej. A co jeśli proces tłumaczenia jest asynchroniczny - zaczyna puchnąć taka encja, która miała ładnie wszystko chować. Nie wiem czy idea active records nie była taka, że to jest po prostu "głupi" rekord z bazy danych, który możesz sobie zapisać. Obawiam się, że przy mieszaniu encji jako stanu z operacjami biznesowymi na encji powstanie niezły potworek.


”Engineering is easy. People are hard.” Bill Coughran
edytowany 2x, ostatnio: Charles_Ray
VD
  • Rejestracja:ponad 10 lat
  • Ostatnio:9 miesięcy
  • Postów:72
0
Charles_Ray napisał(a):

Hibernate zakłada, że istnieje transakcja z aktywną sesją. W jakiś sposób musisz ją umieścić w ThreadLocal, co swoją magią zapewnia (albo i nie - różnie z tym bywa) Spring. Nie wiem czy nie skończyłoby się na manualnej obsłudze transakcji z poziomu JPA API wtedy. Aby coś zapisać w bazie, musisz mieć transakcję, przynajmniej na poziomie JDBC.

Dawno nie używałem Hibernate, więc może nie widzę problemu. Może nie chciałbym wrzucać tego obiektu Book do hibernate tylko mapować go sobie na encję bazodanową w innej warstwie i nią sobie zarządzać? Na razie myślę o tym w oderwaniu od springa/jdbc/hibernate.

Charles_Ray napisał(a):

To już raczej update takiej encji, skoro coś tłumaczysz. Albo zwrócenie nowej. A co jeśli proces tłumaczenia jest asynchroniczny - zaczyna puchnąć taka encja, która miała ładnie wszystko chować. Nie wiem czy idea active records nie była taka, że to jest po prostu "głupi" rekord z bazy danych, który możesz sobie zapisać. Obawiam się, że przy mieszaniu encji jako stanu z operacjami biznesowymi na encji powstanie niezły potworek.

Wiadomo, że trzeba się pilnować (tak samo jak przy olbrzymich serwisach, które wszystko robią), ale jeśli ta metoda translate sprawdzałaby tylko stan wewnętrzny i delegowała akcję do jakiegoś portu to nie wydaje mi się, żeby musiał z tego powstać potworek. Metoda mogłaby zwracać jakiś Future<TranslatedBook> czy coś w tym stylu. Plusem tego rozwiązania jest to, że takie obiekty ładnie enkapsulują swoje właściwości i pozwalają na fajną eksplorację domeny. W głowie mi się to na razie klei :D

obscurity
  • Rejestracja:około 6 lat
  • Ostatnio:dzień
0

Robi się tak, czasem to widuję w różnych dllkach czy api. Ale później okazuje się to utrudnieniem i jest łatwiej jak obiekty trzymające dane (rekordy) są głupie. Przykładowo możesz zserializować takie książki lokalnie do zapisania, zdeserializować przy następnej sesji i zapisać, przy active records się tak nie da - musiałbyś mapować obiekt na inny głupszy obiekt i z powrotem.
Jest parę innych przykładów gdzie to utrudnia - przykładowo klonowanie obiektów, lub kopiowanie danych między dwoma bazami (zapis do innej niż odczyt).
Albo powiedzmy w Twoim przykładzie z translate - zmiana języka tłumaczenia (parametrów lub serwisu tłumaczącego) podczas działania aplikacji może być trudniejsza.
Trudniejsze staje się testowanie tego obiektu i wszystkich klas które z niego korzystają. Trudniejsze staje się tworzenie builderów i fabryk które może tworzą klasy które korzystają z tych obiektów.
Ogólnie może prowadzić to do przekazywania tych serwisów i żonglowania nimi w wielu miejscach gdzie byś ich normalnie nie potrzebował.
Możesz próbować - utrudnienia wyjdą w praniu. W prostej aplikacji nie powinno być z tym problemów


"A car won't take your job, another horse driving a car will." - Horse influencer, 1910
edytowany 2x, ostatnio: obscurity
LU
  • Rejestracja:około 11 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Gdańsk
0

Trochę średni pomysł moim zdaniem wrzucać do encji/dto repozytorium. Dtosy raczej nie mają logiki.


VD
Ale to nie DTO, tylko jakiś agregat
Charles_Ray
  • Rejestracja:prawie 17 lat
  • Ostatnio:około 12 godzin
  • Postów:1873
1

Plusem tego rozwiązania jest to, że takie obiekty ładnie enkapsulują swoje właściwości i pozwalają na fajną eksplorację domeny. W głowie mi się to na razie klei :D

IMO to dobry kierunek myślenia, który pozwala minimalizować anemiczne encje i rozsianie logiki po wielu serwisach. Swoją droga jednym z częstych pytań odnośnie implementacji DDD jest w jaki sposób wstrzyknąć do encji EventPublishera - podobny wątek.


”Engineering is easy. People are hard.” Bill Coughran
edytowany 1x, ostatnio: Charles_Ray
S9
Właśnie gdy sobie rozkminiam na temat (nie)anemiczności encji to się zastanawiam w jak w przypadku DDD ogarniać takie rzeczy jak komunikacja z RESTem itp
Shalom
Ale komunikacja czego z restem? Zresztą to raczej nie jest powiązane z DDD jako takim (bo DDD to raczej "jak budować domenę") a bardziej z jakimś Clean Design/Hexagonal i generalnie nie do końca widzę problem. W którym kierunku coś jest nie tak?
S9
No chodzi m o to że jeśli mamy jakiś obiekt domenowy z logiką binesową który do wykonia jakiejś pojedyńcznej akcji biznesowej potrzebuje np. w środku skomunikować sie z innym API, trudno mi teraz o jakiś przykład.
Shalom
Trudno mi to sobie wyobrazić. Albo pobierasz potrzebne dane w trakcie budowy tego obiektu domenowego (np. zbierasz dane z jakichś restów, DB itd) albo masz serwis domenowy który koordynuje pracę i np. obiekt domenowy ogarnia biznesową walidację danej operacji, a serwis domenowy zajmuje się: pobraniem obiektu domenowego, striggerowaniem akcji i jeśli sie powiodła to np. wysłaniem wyniku gdzieś dalej/zapisem do bazy/cośtam innego.
VD
Albo wrzucasz funkcję pobierającą przez REST w konstruktor obiektu domenowego i potem on jak potrzebuje to sobie jej używa ;D A potem się pytasz na forum dlaczego tak się nie robi
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około godziny
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4706
2

Jest jeszcze jeden myk:

Kopiuj
Book {
 Function<Repository, BookPage> fetchPage(int i);
}

Potem, żeby to się lepiej komponowało zmieniamy jeszcze to Function w normalną Nomadę i jest całkiem ok.


jeden i pół terabajta powinno wystarczyć każdemu
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)