Relacje dwukierunkowe - antywzorzec?

0

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?

0

Jak robisz modele to jak inaczej chcesz to zrobić?

1

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.

3

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.

0
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?

3

@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.

0
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.

0

@Nofenak: nie, wyszukujesz po comments, bo szukasz commentów a post jest tylko relacją by pokazać skąd są. Nie szukasz commentów w poście tylko commentów niezależnie w jakim poście są.

2
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.

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