Czy relacje dwukierunkowe między klasami, np. Post ma listę Comment a Comment ma Posta to antywzorzec? Jakie jest wasze zdanie? Stosujecie czy unikacie jak ognia?
- Rejestracja:ponad 12 lat
- Ostatnio:około 10 godzin
- Postów:3537
Pytanie do czego służą te klasy.
Jeśli struktura jest przechowywana tylko i wyłącznie w pamięci to trzymanie relacji dwukierunkowej może mieć sens. Ot np. trudno byłoby zaimplementować listę dwukierunkową w której Node
nie miałaby wskaźnika na następny i poprzedni element, podobnie np. wszelkiego rodzaju drzewa, a także trochę bardziej specjalistyczne klasy do zarządzania obiektami.
Jeśli chodzi o model jako taki to staram się tego unikać kiedy mogę. Da się to osiągnąć tylko wtedy gdy jest to relacja 1:1 lub 1:N - ale rzadko faktycznie można ową relację zignorować. W twoim przykładzie nie widzę żadnego problemu, żeby Post
miał listę Comment
i Comment
miał odwołanie do Post
. Przy N:N to już niestety prawie zawsze odniesienie być musi.
Nie, nie jest to antywzorzec. Jest to bardzo dobre i w pewnym sensie spodziewane, rozwiązanie. ORM służy do tłumaczenia modelu bazodanowego na model obiektowy. Zatem coś, co w bazie danych jest antywzorcem, to przy projektowaniu obiektowym może być OK. Podany przykład Post do Comment jest wręcz podręcznikowym przykładem takiej relacji. Problem polega jednak na prawidłowym określeniu reguł związanych z wybieraniem danych przez ORM, tak żeby pobranie pojedynczego komentarza nie spowodowało pobrania wszystkich komentarzy związanych z danym postem.
Relacje dwukierunkowe są OK, ale wymagają dużo uwagi i rozsądku przy konfigurowaniu.
- Rejestracja:ponad 2 lata
- Ostatnio:dzień
- Postów:135
Koziołek napisał(a):
Nie, nie jest to antywzorzec. Jest to bardzo dobre i w pewnym sensie spodziewane, rozwiązanie. ORM służy do tłumaczenia modelu bazodanowego na model obiektowy. Zatem coś, co w bazie danych jest antywzorcem, to przy projektowaniu obiektowym może być OK. Podany przykład Post do Comment jest wręcz podręcznikowym przykładem takiej relacji. Problem polega jednak na prawidłowym określeniu reguł związanych z wybieraniem danych przez ORM, tak żeby pobranie pojedynczego komentarza nie spowodowało pobrania wszystkich komentarzy związanych z danym postem.
Relacje dwukierunkowe są OK, ale wymagają dużo uwagi i rozsądku przy konfigurowaniu.
No właśnie w ORM (Hibernate) podobno te relacje dwukierunkowe są zalecane (mniej zapytań) https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/ Tylko domenowo wydaje mi się to bez sensu, bo Comment może istnieć jedynie w obrębie Posta (aggregate root) i wszystkie operacje na nim powinny być wykonywane przez Posta (np. może być takie wymaganie, że nie można edytować komentarza jeśli post został zablokowany itp), ale nie musi o nim wiedzieć. Chyba dobrą praktyką jest, żeby obiekty wiedziały o sobie jak najmniej.
Relacje dwukierunkowe są OK, ale wymagają dużo uwagi i rozsądku przy konfigurowaniu.
To może z nich zrezygnować i stosować tylko w jakiś szczególnych przypadkach?
@Nofenak: trochę mieszasz różne koncepcje. Jeżeli popatrzysz na to z poziomu DDD, to rzeczywiście relacja dwukierunkowa nie do końca ma sens. Comment powinien być obsługiwany przez Post, ale to tylko jeden z przypadków. Komentarz może „żyć” bez Postu w momencie, gdy interesują nas operacje typu wyszukiwanie. W tym momencie musisz mieć możliwość przejścia z Komentarza do Postu. Inaczej mówiąc, odszukania właściciela danego Komentarza. Dużo zależy od kontekstu użycia.
Kolejna sprawa to rodzaj samej relacji na razie mówimy o relacjach One-to-Many (i pośrednio Many-to-Many), a przecież jest jeszcze One-to-One, która to relacja z punktu widzenia bazy danych jest „chora” (nieznormalizowana). Ale to osobny temat.

- Rejestracja:ponad 2 lata
- Ostatnio:dzień
- Postów:135
Koziołek napisał(a):
@Nofenak: trochę mieszasz różne koncepcje. Jeżeli popatrzysz na to z poziomu DDD, to rzeczywiście relacja dwukierunkowa nie do końca ma sens. Comment powinien być obsługiwany przez Post, ale to tylko jeden z przypadków. Komentarz może „żyć” bez Postu w momencie, gdy interesują nas operacje typu wyszukiwanie. W tym momencie musisz mieć możliwość przejścia z Komentarza do Postu. Inaczej mówiąc, odszukania właściciela danego Komentarza. Dużo zależy od kontekstu użycia.
Kolejna sprawa to rodzaj samej relacji na razie mówimy o relacjach One-to-Many (i pośrednio Many-to-Many), a przecież jest jeszcze One-to-One, która to relacja z punktu widzenia bazy danych jest „chora” (nieznormalizowana). Ale to osobny temat.
W przypadku wyszukiwania nadal trzeba to zrobić przez Post, bo jeśli zrobimy to tylko przez Comment to w sytuacji gdy używamy methody w stylu getCommentsById i dostajemy pustą listę, to nie wiadomo jak ją zinterpretować - czy Post nie ma żadnych Comments czy Post w ogóle nie istnieje.
Nofenak napisał(a):
W przypadku wyszukiwania nadal trzeba to zrobić przez Post, bo jeśli zrobimy to tylko przez Comment to w sytuacji gdy używamy methody w stylu getCommentsById i dostajemy pustą listę, to nie wiadomo jak ją zinterpretować - czy Post nie ma żadnych Comments czy Post w ogóle nie istnieje.
Wyszukiwanie byId
to jest lekka patologia ;) Inny lepszy przykład – wszystkie komentarze, które mają wspólną cechę, np. są tego samego autora lub są z tego samego przedziału czasu. Komentarz powinien znać rodzica. Inaczej dostajemy blob komentarzy, z którymi niewiele co możemy zrobić. I teraz najciekawsze, bo to jest dobrzy przykład na to, jak działają ORMy, bo w relacji jednokierunkowej całość wygląda:
Entity
@Table(name = "posts")
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "post_id")
private List<Comment> comments = new ArrayList<>();
//…
}
@Entity
@Table(name = "comments")
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String text;
//…
}
Więc komentarz nie zna rodzica, ale po stronie bazy danych:
CREATE TABLE posts (
id BIGINT PRIMARY KEY,
content VARCHAR(255),
title VARCHAR(255)
);
CREATE TABLE comments (
id BIGINT PRIMARY KEY,
text VARCHAR(255),
post_id BIGINT,
FOREIGN KEY (post_id) REFERENCES posts(id)
);
To post jest „nieświadomy”. Wprowadzając relację dwukierunkową:
@Entity
@Table(name = "posts")
public class Post {
// ...
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
// ...
public List<Comment> getComments() {
return comments;
}
public void setComments(List<Comment> comments) {
this.comments = comments;
}
}
@Entity
@Table(name = "comments")
public class Comment {
// ...
@ManyToOne
@JoinColumn(name = "post_id")
private Post post;
// ...
public Post getPost() {
return post;
}
public void setPost(Post post) {
this.post = post;
}
}
Ułatwiamy sobie późniejsze modelowanie różnych zachowań. Wracając tutaj do DDD, to możemy dodać kontekst, odpowiednio mapując Komentarz i Post do konkretnych klas domenowych. I tu dochodzimy do kolejnego elementu – wdrażając DDD, wielu programistów ułatwia sobie życie, traktując encje jako klasy domenowe.