Repository-Muster - Wie kann man es verstehen und wie funktioniert es mit „komplexen“ Entitäten?

77

Es fällt mir schwer, das Repository-Muster zu verstehen.

Es gibt viele Meinungen zu diesem Thema, wie in Repository-Muster richtig gemacht, aber auch andere Dinge wie Repository ist das neue Singleton oder wieder wie in DAO nicht verwenden Repository verwenden oder einfach Spring JPA Data + Hibernate + MySQL + MAVEN nehmen, wo irgendwie Ein Repository scheint mit einem DAO-Objekt identisch zu sein.

Ich werde es leid, dieses Zeug zu lesen, da dies imho nicht so schwer sein kann, wie es in vielen Artikeln gezeigt wird.

Ich sehe es so: Es scheint, dass ich so etwas will:

         ------------------------------------------------------------------------
         |                            Server                                    |
         ------------------------------------------------------------------------
         |                    |                        |                        |
Client <-|-> Service Layer  <-|->  Repository Layer  <-|-> ORM / Database Layer |
         |                    |                        |                        |  
         ------------------------------------------------------------------------

Das Service Layernimmt *DTOObjekte und gibt diese an den weiter Repository Layer, der im Grunde nichts anderes als "der Typ" ist, der weiß, wie eine Entität gespeichert werden kann.

Angenommen, Sie haben eine Zusammensetzung einiger Tools ( bitte beachten Sie, dass dies nur Pseudocode ist ).

@Entity
class ToolSet {
  @Id
  public Long id;
  @OneToOne
  public Tool tool1;
  @OneToOne
  public Tool tool2;
}

@Entity
class Tool {
  @Id
  public Long id;
  @OneToMany
  public ToolDescription toolDescription;
}

@Entity
class ToolDescription {
  @Id
  public Long id;
  @NotNull
  @OneToOne
  public Language language

  public String name;
  public String details;
}

Das, was ich nicht bekomme, ist der Teil, in dem ich ein ToolSetDTOObjekt vom Client bekomme .

So wie ich es bisher verstanden habe, konnte ich eine ToolSetRepositorymit einer Methode schreiben ToolSetRepository.save(ToolSetDTO toolSetDto), die " weiß, wie man speichert " ToolSetDTO. Aber fast jedes Tutorial besteht nicht das, *DTOsondern das Entity.

Was mich hier stört, ist, dass ToolSetich die folgenden Schritte ausführen muss , wenn Sie mein Beispiel von oben nehmen :

  1. Nehmen Sie toolSetDtound überprüfen Sie, wenn nichtnull
  2. Für jedes tool*DtoEigentum von toolSetDto
    a) Wenn es eine gültige ID hat, konvertieren Sie von, DTOum Entityandernfalls einen neuen Datenbankeintrag zu erstellen.
    B) toolDescriptionDtound konvertieren / speichern Sie es in die Datenbank oder erstellen Sie einen neuen Eintrag
  3. Nachdem Sie die oben genannten Instanzen ToolSet(Entitäten) überprüft und eingerichtet haben, um sie in der Datenbank zu speichern

All dies ist zu komplex, um die Servicefunktion (Schnittstelle für den Client) einfach damit umgehen zu lassen.

Ich habe darüber nachgedacht, zB ein zu erschaffen, ToolSetRepositoryaber die Frage hier ist

  • Nimmt es ein ToolSetEntitätsobjekt oder verwendet es ein DTOObjekt?
  • Auf jeden Fall: Ist der *Repositoryzu dürfen verwenden andere Objekte Repository? Zum Beispiel, ToolSetwenn ich speichern möchte, aber speichern muss Toolund ToolDescriptionzuerst - würde ich verwenden ToolRepositoryund ToolDescriptionRepositorydrinnen ToolSetRepository?
    Wenn ja: Warum wird das Repository-Muster nicht beschädigt? Wenn dieses Muster im Grunde eine Schicht zwischen dem Dienst und meinem ORM-Framework ist, fühlt es sich aus Abhängigkeitsgründen einfach nicht "richtig" an, Abhängigkeiten zu anderen *RepositoryKlassen hinzuzufügen .

Ich weiß nicht, warum ich das nicht verstehen kann. Es klingt nicht so kompliziert, aber es gibt immer noch Hilfe wie Spring Data. Eine andere Sache, die mich stört, da ich wirklich nicht sehe, wie das etwas einfacher macht . Zumal ich Hibernate bereits verwende - ich sehe den Vorteil nicht (aber vielleicht ist das eine andere Frage).

Also ... ich weiß, dass dies eine lange Frage ist, aber ich habe bereits ein paar Tage lang darüber geforscht. Es gibt bereits Code, an dem ich gerade arbeite, der zu einem Chaos wird, weil ich dieses Muster einfach nicht durchschauen kann.

Ich hoffe, jemand kann mir ein größeres Bild geben als die meisten Artikel und Tutorials, die nicht über die Implementierung eines sehr, sehr einfachen Beispiels eines Repository-Musters hinausgehen.

Stefan Falk
quelle
Meiner Ansicht nach sollte das ToolSetRepository nur die ToolSet-Entität kennen ... und im ToolSet können Sie auch die JaxB-Annotationen verwenden, um die Entität als DTO zu verwenden. Auf der Clientseite haben Sie dann nur die JaxB-Klassen, die mit Jaxws Clientgen aus der von der Webservice-URL empfangenen WSDL plus "? WSDL" generiert wurden. Auf der Serverseite erhalten Sie dann die "nicht verwaltete" Entität. Dann müssen Sie entitymanager.merge verwenden, um es in den verwalteten Zustand zu versetzen. das ist alles. Meiner Ansicht nach wird ein bestimmtes Repository nur für komplexe Kriterien benötigt, bei denen Sie keine benannten Abfragen verwenden können. zB Kriterien-API-Abfragen.
StefanHeimberg
@StefanHeimberg Aber wie würde man ToolSetRepositoryzum Beispiel mit der Persistenz von Toolund umgehen ToolDescription? Von denen sollten diese schon bestehen geblieben sein? Wenn diese zu diesem Zeitpunkt bereits bestehen sollten, wo würde ich das dann tun? Dies in meiner Servicemethode zu tun, fühlt sich nicht richtig an, da komplexe Entitäten wie ToolSetden Servicemethodencode aufblähen würden. Imho eine Service-Methode sollte nur ein paar Initialisierungs- und grundlegende Überprüfungsarbeiten durchführen und dann die Arbeit an die nächste Ebene delegieren.
Stefan Falk
Wenn Sie die "nicht verwaltete" Entität in der Serviceschicht (Transaktionsgrenze) erhalten und dann merge () für den Entitätsmanager verwenden, ist die Entität bereits mit der Entitätsverwaltung bekannt. Nach Abschluss der Servicemethode werden die Transaktions-Commits und die Änderungen im Entity Manager in der Datenbank gespeichert ...
StefanHeimberg
1
AFAIK Hibernate (und JPA) sind eine ganze DAO-Schicht, da die Verbindung mit der Datenquelle (in diesem Fall Datenbank) trotz der zugrunde liegenden Details (MySQL, Oracle, SQL Server usw.) hergestellt werden muss und Sie die Datenquelle auf viele Arten abfragen können . Wenn Sie bestimmte Abfragen für Ihre Entitäten verwenden möchten / müssen, können Sie Kriterien verwenden, die für die Verwendung im Repository angegeben sind, sodass der Ruhezustand letztendlich sowohl Dao als auch Repository ist. Darüber hinaus erstellen Sie eine eigene Ebene, um dieses Dao (oder Repository) oder was auch immer Sie verwenden, um dies zu implementieren und die Programmierung fortzusetzen.
Luiggi Mendoza
2
merge () prüft bereits, ob neu oder nicht. und erstellen Sie dann eine Einfüge- oder Aktualisierungsabfrage. Meiner Ansicht nach liegt dies in der Verantwortung des zugrunde liegenden ORM. zB JPA.
StefanHeimberg

Antworten:

113

Sie können meinen Beitrag "Repository für Dummies" lesen , um das einfache Prinzip des Repositorys zu verstehen . Ich denke, Ihr Problem ist, dass Sie mit DTOs arbeiten und in diesem Szenario das Repository-Muster nicht wirklich verwenden, sondern ein DAO.

Der Hauptunterschied zwischen einem Repository und einem Dao besteht darin, dass ein Repository nur Objekte zurückgibt , die von der aufrufenden Schicht verstanden werden . Meistens wird das Repository von der Geschäftsschicht verwendet und gibt daher Geschäftsobjekte zurück. Ein Dao gibt Daten zurück, die möglicherweise ein ganzes Geschäftsobjekt sind oder nicht, dh die Daten sind kein gültiges Geschäftskonzept.

Wenn Ihre Geschäftsobjekte nur Datenstrukturen sind, kann dies ein Hinweis darauf sein, dass Sie ein Modellierungsproblem haben, dh ein schlechtes Design. Ein Repository ist sinnvoller mit "reichen" oder zumindest richtig gekapselten Objekten. Wenn Sie nur Datenstrukturen laden / speichern, benötigen Sie wahrscheinlich kein Repository. Der Orm reicht aus.

Wenn Sie mit Geschäftsobjekten arbeiten, die aus anderen Objekten (einem Aggregat) bestehen und dieses Objekt alle seine Teile benötigt, um konsistent zu sein (ein Aggregatstamm), ist das Repository-Muster die beste Lösung, da es alle Persistenzdetails abstrahiert . Ihre App fragt nur nach einem "Produkt" und das Repository gibt es als Ganzes zurück, unabhängig davon, wie viele Tabellen oder Abfragen zum Wiederherstellen des Objekts erforderlich sind.

Basierend auf Ihrem Codebeispiel haben Sie keine "echten" Geschäftsobjekte. Sie haben Datenstrukturen, die von Hibernate verwendet werden. Ein Geschäftsobjekt basiert auf Geschäftskonzepten und Anwendungsfällen. Das Repository ermöglicht es dem BL, sich nicht darum zu kümmern, wie dieses Objekt beibehalten wird. In gewisser Weise fungiert ein Repository als "Konverter / Mapper" zwischen dem Objekt und dem Modell, das beibehalten wird. Grundsätzlich reduziert das Repo die Objekte auf die für Persistenzdaten erforderlichen Daten.

Ein Business - Objekt ist nicht ein ORM entity.It könnte aus technischen Sicht, sondern aus einem Entwurf pov, ein Geschäftsmodell Sachen der anderen Modellen Persistenz Zeug. In vielen Fällen sind diese nicht direkt kompatibel.

Der größte Fehler besteht darin, Ihr Geschäftsobjekt entsprechend den Speicheranforderungen und der Denkweise zu gestalten. Und im Gegensatz zu dem, was viele Entwickler glauben, besteht ein ORM-Zweck nicht darin, Geschäftsobjekte zu erhalten. Ihr Zweck ist es, eine 'oop'-Datenbank auf einem rdbms zu simulieren. Die ORM-Zuordnung erfolgt zwischen Ihren Datenbankobjekten und -tabellen, nicht zwischen App-Objekten (noch weniger beim Umgang mit Geschäftsobjekten) und Tabellen.

MikeSW
quelle
1
Hallo! Jetzt ist mir viel klarer, was ein Repository ist, aber ich möchte nach meinem spezifischen Codebeispiel fragen, wenn ich darf: Die Sache ist, dass auf meiner Client-Seite nicht viel los ist, so dass es nie mehr viel zu tun geben wird tun als im Grunde zB ToolSetin der Datenbank speichern. Das heißt aber immer noch nicht, dass ich schreiben kann, ToolSetRepositorydas im Grunde meine Entitätsklasse aufbaut und sie dann beibehält. Ist das richtig? Und ein letztes großes Problem, das ich in meiner Frage bekam, ist " Auf jeden Fall: Darf das *Repositoryandere Repository-Objekte (darin) verwenden? ".
Stefan Falk
1
Keines der Tutorials, die ich gesehen habe, zeigte ein komplexeres Beispiel, in dem das Repository tatsächlich das tun muss, wofür die Leute behaupten, es sei da. Ich sehe nur ein brutal einfaches Beispiel, bei dem ich mich frage, wofür ich das brauche. ^^ Außer es ist erlaubt, andere Repository-Objekte in anderen Repository-Objekten zu verwenden, um die Arbeit zu erledigen. :)
Stefan Falk
4
@StefanFalk Das Repository-Muster ist in der Tat das Design der Schnittstelle . Sobald Sie die Schnittstelle haben, können Sie mit der eigentlichen Implementierung wild werden. Über ein Repository, das andere Repositorys verwendet, denke ich nicht, das ist das falsche Design. Es würde bedeuten, dass Sie zwei Repos haben, die sich mit denselben Objekten befassen. Ein Repository kann jedoch viele DAOs verwenden. In meiner Codebasis wissen die Repos nichts voneinander. Sie benötigen ein geeignetes Geschäftsmodell (mit klaren Konsistenzgrenzen), um das Repository korrekt verwenden zu können. Andernfalls ist dies nur eine Komplikation.
MikeSW
3
Übrigens ist der Zweck des Repositorys das Speichern / Abrufen von Sachen. Je komplexer das Zeug, desto besser ist es. Denken Sie jedoch an das Wichtige: Dieses Objekt muss ein Geschäftskonzept darstellen , das entweder einfach (eine Struktur) oder komplex (viele Regeln oder viele untergeordnete Elemente) ist.
MikeSW
1
Nein! Ein Repository ist nur ein Problem der Persistenz. Setzen Sie niemals Dinge wie http ein. Sie verstoßen gegen das Prinzip der Trennung von Bedenken. Sie möchten wirklich ein Objekt haben, das alles kann? Die Repo-Implementierung ist immer Teil der Persistenz (als Konzept). Ihre App sollte nur die Abstraktion per Injektion verwenden, dh der di-Container sollte das Repo erstellen, das NUR von der Datenbank gespeichert / geladen werden soll, ohne die Sicherheit zu überprüfen. Sie können Dinge 'überprüfen', wenn sie Teil der Abfrage sind, z. B. 'Wählen Sie * aus Tools mit id = @ 0 und userId = @ 1'.
MikeSW