Klasy encyjne

0

Witajcie,

ostatnio na inżynierii oprogramowania wykładowca stwierdził, że nie powinno się pisać klas encyjnych. To w takim razie co zamiast tego? Jak odwzorować w kodzie relacyjną bazę danych?

Tworzę obecnie system zarządzania centrum sportowym, i cała baza danych jest u mnie w postaci klas encyjnych. Czy to źle? Jakie Wy macie o tym zdanie?

Zastrzegam, że jestem początkujący, więc mogłem sporo pomieszać :)

0

Wykładowcy często nie wiedzą co mówią - to tak na przyszłość. Ten oczywiście może doskonale wiedzieć o czym mówi, i takich wykładowców Ci życzę:). Tylko że ja nie mam pojęcia o czym Ty piszesz, no nie 100% na pewno;-)

Czym dla Ciebie jest klasa encyjna? Jeśli to klasa, która nie ma w sobie żadnej logiki, jest tylko workiem na dane wymieniane z bazą danych - to zgadzam się z wykładowcą. Takie podejście nazywa się też anemicznymi encjami. Aczkolwiek potrafię sobie wyobrazić parę wyjątków gdzie to może być podejście OK. Ogólnie jednak, odradzam.
Lepiej dołożyć do tych klas logikę którą powinny realizować, a która pewnie została niesłusznie wyciągnięta na zewnątrz.

0
AreQrm napisał(a):

Wykładowcy często nie wiedzą co mówią - to tak na przyszłość. Ten oczywiście może doskonale wiedzieć o czym mówi, i takich wykładowców Ci życzę:). Tylko że ja nie mam pojęcia o czym Ty piszesz, no nie 100% na pewno;-)

Czym dla Ciebie jest klasa encyjna? Jeśli to klasa, która nie ma w sobie żadnej logiki, jest tylko workiem na dane wymieniane z bazą danych - to zgadzam się z wykładowcą. Takie podejście nazywa się też anemicznymi encjami. Aczkolwiek potrafię sobie wyobrazić parę wyjątków gdzie to może być podejście OK. Ogólnie jednak, odradzam.
Lepiej dołożyć do tych klas logikę którą powinny realizować, a która pewnie została niesłusznie wyciągnięta na zewnątrz.

Klasa encyjna to dla mnie klasa oznaczona adnotacją @Entity, a w niej kolumny tabeli z bazy danych.

W takim razie czy moje postępowanie jest niewłaściwe? Całą logikę operowania na danych z bazy mam w klasach serwisowych. Korzystam ze Srping Data, więc nie przychodzi mi na myśl inne rozwiązanie niż to, które obecnie stosuję.

Z jednej strony wykładowca mówił, ze klasy encyjne są złe, z drugiej namawiał do używania serwisów. To ja się pogubiłem w takim razie.

W jaki sposób utworzyć bazę danych niezależnie od kodu, a pobierać z niej dane i na nich operować przy użyciu Spring Data?

7

To jest generalnie dość złożony problem bo z jednej strony mamy Domain Driven Design i OOP które mówią że podstawą programowania obiektowego jest związanie danych oraz operacji w jedną spójną całość, a z drugiej strony mamy podejście Service-Oriented oraz podejście funkcyjne które sugerują, że powinniśmy mieć bezstanowe serwisy / funkcje aplikujące operacje na niemutowalnych obiektach.

Ja osobiście uważam że najsensowniejsze rozwiązanie to zdrowy rozsądek i na "niższym poziomie" kiedy obracamy się wśród obiektów domenowych warto ich logikę przechowywać właśnie w tych obiektach, przy założeniu że mamy sensowną granulacje a nie jakieś god objecty, żeby robić:

sum = order.sumOrderEntries();

Zamiast:

sum = orderService.calculateSum(order);

Jak widać zresztą ta druga opcja automatycznie uniemożliwia stosowanie polimorfizmu i innych udogodnień.

Niemniej z drugiej strony nie ma co popadać w skrajność i tworzyć wydumanych obiektów tylko po to żeby móc upchnąć wszystkie obiekty w jedno drzewo. Jeśli mamy jakąś złożoną logikę która nie pasuje do żadnego obiektu domenowego to pasuje zapewne właśnie do serwisu, zgodnie z SRP.

Ja bym chętnie poczytał co na ten temat powiedzą np. @somekind @katelx @Koziołek @Krolik

0

Mam klasę domenową:

@Entity
@Table(name = "users")
public class User {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@Column
	private String address;
	@Column
	private String mail;
	@Column
	@Enumerated(EnumType.STRING)
	private Role role;
	@Column
	private String lastName;
	@Column
	private String phonenumber;
	@Column
	private String name;
	@Column
	private String login;
	@Column
	private String password;
	@OneToOne(cascade = CascadeType.ALL)
	private Customer customer;
	@OneToOne(cascade = CascadeType.ALL)
	private Employee employee;
	@Column
	private Boolean enabled;

Mam repozytorium

public interface UserRepository extends CrudRepository<User, Long>

Wszystkie operacje jakie wykonuje na danych z bazy robię przy pomocy

@Autowired
UserRepository userRepo;

Metody wykonujące coś na danych z bazy mam w klasie serwisowej.

Teraz pytanie - czy robię dobrze, czy coś knocę i lepiej od razu zmienić taktykę?

To projekt zaliczeniowy. To jak teraz postępuję ma taki plus, że 4 osoby posiadając ten sam kod mają taką samą bazę danych, bez stawiania jej na serwerze.

12

Nie wiem o co chodziło wykładowcy, ale z mojego doświadczenia z wykładowcami wynika, że im czasem o nic nie chodzi: Wzorce projektowe klasy CASE

Być może rację ma @AreQrm, że chodziło o Anemic Domain Model, który jest antywzorcem. Tylko nie każda aplikacja ma zaawansowaną logikę, którą trzeba w DDD upychać. Większość aplikacji to CRUD, w który nie ma prawie żadnej logiki, więc i klasa, która ma same pola, bez zachowania nie jest niczym złym.
@insectoman, piszesz, że Ty właśnie masz anemiczne encje i całość logiki w serwisie - to się nazywa transaction script i takie podejście może być dobre albo i nie, w zależności od przypadku. Moim zdaniem w przypadku prostej logiki nie ma co na siłę upychać jej w encjach i utrudniać sobie życie przez DDD, bo to spowoduje więcej problemów niż pożytku. Generalnie jestem zdania, że lepiej wyjść od takiej architektury jak Twoja, a później ewentualnie refaktoryzować w stronę DDD, i to tylko w tych elementach systemu, w których faktycznie jest potrzeba.

Drugi często zapominany fakt, to to, że model domeny i model składowania danych, to nie jest to samo. A więc encje mogą mieć dane i logikę na nich operującą, ale w warstwie DAL te encje mogą być konwertowane na właśnie takie struktury danych bez metod. Jak rozumiem Java wymaga do takiego tworu adnotacji @Entity, mimo że faktycznie nie będzie encją (ani nawet obiektem), no ale to już jest konwencja technologii i nic z tym nie zrobimy. (Mnie też wkurza, że NHibernate stosuje wszędzie nazwę Entity, mimo że encji żadnych nie mam.)

@azalut, zgodnie z DDD, serwisy są po to, aby realizować operacje, które wymagają kilku encji. Np. przypisanie losowym klientom losowych faktur. To nie pasuje ani do Order ani do Invoice, prawda?

Co do niemutowalności i OOP - celem niemutowalności jest to, aby nic przypadkiem nie zepsuło obiektu. W OOP do tego służy enkapsulacja. Dopóki stan utworzonego obiektu potrafi zmieniać tylko on sam, dopóty nie ma dużego niebezpieczeństwa. W tym celu, np. klasa Order powinna mieć oddzielne metody zmieniającą status zamówienia (np. confirm(), send(), finish()) zamiast publicznej setStatus(StatusEnum newStatus), którą każdy może zepsuć stan obiektu.
A jeśli chcemy mieć pełną niemutowalność, to metody confirm() i tak dalej mogą zwracać nam nowy Order ze zmienioną wartością pola... Ale czy to ma sens? Wątpliwe.

Tak przy okazji - DDD to tak naprawdę buzzword, mądrzejsza nazwa dla OOP. Gdyby ludzie trzymali się abstrakcji i enkapsulacji, to żadnego DDD nie trzeba byłoby wymyślać. :)

0

Dzięki wielkie za takie rozjaśnienie tematu. Zabrałem się za projekt, zacząłem pisać, a tu na wykładzie się dowiaduje, że robię źle. :D

Dzięki!

0

To dobrze, że masz wykładowce w miarę na czasie, i nie utknął 20 lat temu w programowaniu :-)

1

Sprawę ładnie opisał @somekind. Od strony praktycznej można to zrealizować tworząc klasę dziedzinową w rodzaju:

class Order{

    public double sumOfElements();

}

która będzie zawierać w sobie pola OrderDto i OrderDao, które jest reprezentacją sposób składowania. Od modelu serwisowego różni się to tym, że klasa Order ma stan. Jedynym, ale w sumie drobnym, problemem jest inicjowanie tego wszystkiego. Technicznie znowuż sprowadza się to do napisania już bezstanowego serwiso-fabryki z metodą find, który zwróci prawidłowo skonfigurowany obiekt domenowy. Po systemie śmigają obiekty domenowe, a to co się dzieje pod spodem... who cares.

1
insectoman napisał(a):

wykładowca stwierdził, że nie powinno się pisać klas encyjnych
z tym racja, jesli chcesz je miec to lepiej ich nie pisac z palca tylko generowac przy uzyciu wlasnego lub 3rd party toola.

insectoman napisał(a):

Jak odwzorować w kodzie relacyjną bazę danych?
moze poza prostymi crudami z tutoriali, (imo) relacyjne bazy danych dosc kiepsko sie odzwierciedla w obiektowym srodowisku. nie pracuje zbyt duzo z relacyjnymi bazami danych, ale jesli chodzi o dane same w sobie to preferuje nastepujacy schemat (dla Order + podlegle mu Execution):

  • OrderStorage, ExecutionStorage - abstrakcyjny, wystawiajacy metody typu setId, load/fill, release + oczywiscie jakies specyficzne dla siebie akcesory
  • OrderMatchingTransform, OrderExecutionSummarizer, OrderExecuteCommand, OrderPricePredicate, OrderQuantityConverter - te klasy przyjmuja przynajmniej jeden (juz zaladowany) OrderStorage i/lub ExecutionStorage i maja metody typu sum, transform, execute etc
  • OrderProcessor - cos co spina to wszystko do kupy
0

Ok to chwila chwila, bo teraz to i mnie zaczęło ciekawić
czy w projektach, które robicie dajecie logike (operującą na danej encji) do encji? Prawde mówiąc nigdy się z tym nie spotkałem, zawsze myślałem, że rozdzielenie odpowiedzialności tzn logika to jakies klasy serwisowe/utilsowe a entity to po prostu odwzorowanie tabeli - to raczej dobry pattern, a nie że DDD mówi inaczej.
@somekind Weźmy np. Spring Data JPA - po co by było tworzenie implementacji metody bazując na jej nazwie, skoro wg tego co mówicie logike np zapisu/czytania encji mam mieć w samej encji?

1

@azalut nie nie nie. Mylisz pojęcia. Zasada jednej odpowiedzialności nadal obowiązuje. Obiekty Biznesowe / Encje powinny zawierać logikę związaną ze sobą, a serwisy logikę powiązanych encji. Wyciąganie czegoś z bazy wymaga połączenia z bazą i nijak sie ma do tego co dany obiekt oznacza "domenowo".

0

Czyli jak mam metodę operującą na jednej encji to mam ją w klasie z tą encją, a jak metodę operujacą na danych z wielu encji to do serwisu?

1

Niekoniecznie, powiedziałbym że to może zależeć od złożoności takiej metody. Bo na upartego to niektórzy chcą upchnąć w jednej klasie co sie tylko da ;) Pamiętaj też o kompozycji, może te kilka encji składa się na inną encje?

0

@Shalom A okej, na pewnym poziomie abstrakcji rozumiem o co chodzi. Czyli serwis w postacji springowego CrudRepository jest jak najbardziej ok bo mapuje encje na tabele, ale to co oznacza ta encja domenowo ma być realizowane przez metody utworzone (upraszczając): ?

  1. w encji jeśli metoda operuje na danej encji
  2. w jakimś serwisie jeśli połączona jest logika 2 encji w jakiś sposób
  3. jesteś dzikusem i pakujesz logike łączącą dwie encje do jednej encji wybranej szybkim skryptem generującym liczbe pseudolosową lub kostką do gry xxx

Bo na upartego to niektórzy chcą upchnąć w jednej klasie co sie tylko da ;)

xxx I własnie min. dlatego od zawsze preferowałem service-oriented architecture, bo kto to będzie potem szukać czy developerowi chciało się to dodać do Invoice.. a może do Order.. albo może spiął to w Bucket..

edit
oczywiście zakładając że jesteśmy full DDD i nie chcemy miec encji wyłącznie z polami

0
azalut napisał(a):

Ok to chwila chwila, bo teraz to i mnie zaczęło ciekawić
czy w projektach, które robicie dajecie logike (operującą na danej encji) do encji? Prawde mówiąc nigdy się z tym nie spotkałem, zawsze myślałem, że rozdzielenie odpowiedzialności tzn logika to jakies klasy serwisowe/utilsowe a entity to po prostu odwzorowanie tabeli - to raczej dobry pattern, a nie że DDD mówi inaczej.

Nie DDD tylko programowanie obiektowe.
Obiekt = dane + zachowanie. Jak nie masz zachowania, nie masz obiektu, tylko strukturę danych. Oddzielanie danych od operacji na nich, to cecha programowania proceduralnego. Ideą obiektów jest połączenie jednego z drugim.

Weźmy np. Spring Data JPA - po co by było tworzenie implementacji metody bazując na jej nazwie, skoro wg tego co mówicie logike np zapisu/czytania encji mam mieć w samej encji?

Nie wiem, co to Spring Data JPA, ale jeśli masz encje, które się same zapisują i odczytują, to masz wzorzec active record, który moim zdaniem jest łamiącym SRP antywzorcem.

0

@azalut, w encjach tego się nie robi z przyczyn praktycznych, bo choć można by upchnąć część logiki bezpośrednio w encji tak by potrafiła "odpowiadać na pytania" w rodzaju "czy jesteś raportem finalnym, czy draftem?" albo "ile masz dokumentów zależnych" to nie robi się tego ponieważ ORM-y potrafią doczepić się do nazewnictwa metod.

@somekind, active record to jest osobna para kaloszy. Zaimplementowany w prost, tak jak w railsach, rzeczywiście łamie SRP, ale już jego implementacja w COBOLu "dzieje się" pod spodem i na zewnątrz masz tylko dane i operacje na nich. Jeżeli dobrze dobierze się API takiego obiektu, bez metod dostępowych, to masz ładny obiekt domenowy.

0

A co gdyby zrobić obiekty domenowe od razu w POJO tego rodzaju?

http://www.tutorialspoint.com/hibernate/hibernate_examples.htm

0

No właśnie to nie są obiekty, to są struktury danych, o których pisał przed chwilą @somekind. One nie mają żadnych zachowań. Poza metodami które ustawiają im wartości to czyste... dane.

0
AreQrm napisał(a):

No właśnie to nie są obiekty, to są struktury danych, o których pisał przed chwilą @somekind. One nie mają żadnych zachowań. Poza metodami które ustawiają im wartości to czyste... dane.

OK, pytam czysto teoretycznie - czy ktoś próbował (choćby myślowo) dodawać zachowania do takich klas? Jaki był efekt?

0

@vpiotr, hibernate się pluje, bo zawsze dodasz metodę w rodzaj isDaneWJedynymSłusznymFormacie albo getDaneObróconeo360Stopni i będzie ona brana pod uwagę przy mapowaniu encji, bo przedrostek is/get ma znaczenie w sensie POJO.

0

Ja dodawałem. W zachowaniach (poza getterami/setterami) starałem się nie używać przedrostków typu get, ale z tego co widzę mam jedną metodę isPossible i wszystko działało. Korzystałem z JPA (EclipseLink). https://github.com/tdudzik/reservly/blob/master/src/main/java/com/reservly/core/domain/entity/Reservation.java

0

dobra to w końcu POJO takie jak wysłał @vpiotr:

A co gdyby zrobić obiekty domenowe od razu w POJO tego rodzaju?

http://www.tutorialspoint.com/hibernate/hibernate_examples.htm

są okej czy nie?

moze są okej, ale łamią zasady OOP - tzn używa się takich struktur danych (co by je obiektem nie nazwać) by łatwo mapować tabele na obiekty w javie i nie przeszkadzać np hibernatowi, ale ogólnie wg OOP tak być nie powinno?

i wciąż mam to samo pytanie co @vpiotr i czuje że nie uzyskał satysfakcjonującej odpowiedzi: czy ktoś próbował dodawać do encji metody i jeśli hibernate/eclipselink się nie pluł możę powiedzieć jak to wyszło na dłuższą metę? Okazało się, że to bullshit i lepiej to przenieść do serwisów czy może na odwrót?

@somekind
ciekwe ;) możesz polecić jakieś źródło żeby poczytać o takich rzeczach jak piszesz po pierwszym cytacie? (na temat różnych paradygmatów nie tylko oop)

0

@azalut, by hibernate się nie pluł trzeba podejść do problemu inaczej. Po prostu olać mechanizmy automatycznego rozpoznawania i mapowania przenosząc konfigurację do XMLi separując mapowania od kodu albo samodzielnie adnotując w odpowiedni sposób wszystkie pola i metody; adnotacja @Transient twoim przyjacielem.

0

i to wszystko tylko po to żeby być pro-poprawnym koderem znającym OOP? :) czy to daje jakieś realne korzyści

1

@azalut w prawdziwym życiu częściej widziałem podejście takie że klasy @Entity do wyciągania danych z bazy były strukturami danych a następnie była warstwa "mapperów" które przepisywały te struktury na obiekty domenowe (czyli na encje DDD).

0

@Shalom hmm no chyba że tak
ja jeszcze młodzik jestem, ale do tej pory nie spotkałem się z podejściem by np do klasy User dodawać logike operującą na userze. Chyba jestem przyzyczajony do service-oriented i szczerze ci powiem właśnie piszę swój projekt gdzie mam zamiar mieć troche encji i teraz nie wiem
1 zostac przy servicach tak jak do tej pory
2 czy robić tak jak piszesz: encje -> obiekty domenowe

pierwsze wydaje sie oklepane, ale bardziej poukładane (mam kilka warstw aplikacji które coś robią, więc obracam się jak gdyby pomiędzy warstwami aplikacji, a nie obiektami w tej aplikacji)
drugie wydaje się ciekawym podejściem, ale przy większej ilości entities przy bardziej złożonej logice; bo jak logika nie jest skomplikowana i jedyne co trzeba to cos wyczytać z bazy i miec do tego dostep w serwisach do prostszych operacji to nie wiem czy jest sens robić warstwe entities, mapperow, obiektow domenowych i jeszcze serwisów jakby niektore obiekty domentowe musiały działać razem (np logika nie pasująca ani do User ani do Order)

które rozwiązanie byście wybrali? @somekind @Koziołek @Shalom
wiem że na poczatku to nie estymacje, ale oceniam projekt na mały-średni :D

0

@azalut ale zauważ ze to właśnie przy bardziej rozbudowanym projekcie podejscie oparte o serwisy i DTO staje się trudne do ogarniecia, szczególnie jeśli starasz się mieć klasy po max 100 linii. Bo nagle tych serwisów masz bardzo bardzo dużo i ich odpowiedzialnosci się zacierają.

Ale tak jak mówiłem wcześniej: tu nie chodzi o to żeby mieć "albo, albo", tylko o to żeby sensownie żonglować logiką aplikacji.

Tu nie chodzi o to że user przechowuje "operacje dla usera" a już na pewno nie wszystkie. Chodzi o to żeby nie wyciągać prostej logiki związanej ściśle z danym obiektem poza ten obiekt ;]

0

@Shalom czyli tu nie chodzi o wybór, a dobre dostosowanie wszystkiego do potrzeb? tylko w takim razie jak zaczniesz robić troche tak, troche tak, a troche jeszcez inaczej to jak projekt urośnie i ktoś będzie chciał go kontynuować to sie pogubi bo będzie wszędzie inne podejście
troche jak projekty na studia które kontynuuje 5 pokoleń z roku na rok

trzeba mieć jakieś dobre wyważenie typu: encje User mapuje na UserDomenowy bo ma cięższą logikę i będzie to dla mnie wygodne, ale mniejsze encje tj. Invoice czy Receipt obsługuje serwisami bo będzie mi prościej

Tu nie chodzi o to że user przechowuje "operacje dla usera" a już na pewno nie wszystkie. Chodzi o to żeby nie wyciągać prostej logiki związanej ściśle z danym obiektem poza ten obiekt ;]

o to mi chodziło tylko może źle dobrałem słowa, jesli np mamy w userze jakieś pola i na ich podstawie mamy powiedzieć czy user "jest jakiś" (np obiekt firma i metoda czyJestRentowna na podstawie pól tej klasy) to np o to ci chodzi? (bądź bardziej skomplikowane operacje, tylko na ciekawy przyklad wpaść nie moge)

2
azalut napisał(a):

moze są okej, ale łamią zasady OOP - tzn używa się takich struktur danych (co by je obiektem nie nazwać) by łatwo mapować tabele na obiekty w javie i nie przeszkadzać np hibernatowi, ale ogólnie wg OOP tak być nie powinno?

To nie jest OOP, ale to nie jest nic złego, dopóki służą jedynie do mapowania na tabele, albo aplikacja jest w CRUDem bez żadnej zaawansowanej logiki.

Żeby nie było - ja nie jestem ewangelistą DDD (ani żadnych innych "złotych młotków"), w życiu nie widziałem poprawnej aplikacji DDD - bo takich nie ma, a nawet jakby były, to byłby to overkill, ale o DDD wiem często więcej niż jego wielcy fani. Ogólnie uważam, że struktura danych + ooperujący na niej serwis dają czytelniejszy kod w 90% przypadków, a DDD nadaje się do pozostałych 10%, w których faktycznie jest jakiś zaawansowany biznes.

czy ktoś próbował dodawać do encji metody i jeśli hibernate/eclipselink się nie pluł możę powiedzieć jak to wyszło na dłuższą metę? Okazało się, że to bullshit i lepiej to przenieść do serwisów czy może na odwrót?

Nie wierzę, że w Hibernate nie da się decydować o tym, które elementy klasy mają być mapowane, a które nie, skoro w NHibernate nie ma z tym problemu. :)
Tylko to nie zawsze ma sens - jak już pisałem, domain model to nie to samo, co persitence model. Np., żeby stwierdzić, czy klientowi należny jest rabat za to, że dokonał już 100 zakupów za co najmniej 5000 zł, nie potrzebujemy w klasie Customer mieć jego nazwiska, ani adresu.
Ogólnie, możemy mieć w systemie warstwę struktur danych mapowanych na tabele, a nad nimi warstwę prawdziwych encji, które będą miały różne pola i metody w różnych bounded contextach. Czym innym jest Klient z punktu widzenia magazynu, oczym innym z punktu widzenia modułu faktur, a czym innym z punktu widzenia modułu CRM.

ciekwe ;) możesz polecić jakieś źródło żeby poczytać o takich rzeczach jak piszesz po pierwszym cytacie? (na temat różnych paradygmatów nie tylko oop)

O tym, że obiekt to dane + zachowanie? To na Wikipedii albo w każdym podręczniku do dowolnego języka obiektowego.

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