Ich habe viel zu diesem Thema gelesen und bin gerade mitten in der Entwicklung einer größeren Web-Anwendung und des dazugehörigen Back-Ends.
Ich habe jedoch mit einem Design begonnen, bei dem ich ein Repository anfordere, Daten aus der Datenbank abzurufen und sie in ein DTO zu mappen. Warum DTO? Einfach weil bis jetzt im Grunde alles einfach war und keine Komplexität mehr nötig war. Wenn es etwas komplexer wurde, dann begann ich, z.B. 1-zu-n-Beziehungen direkt in der Service-Schicht. Etwas wie:
%Vor%Das funktioniert natürlich, aber es stellt sich heraus, dass manchmal die Dinge komplexer werden und ich beginne zu erkennen, dass der Code ziemlich hässlich aussehen kann, wenn ich nichts dagegen mache.
Natürlich könnte ich wheelMap
in CarRepository
laden, dort das Rad-Mapping durchführen und nur vollständige Objekte zurückgeben, aber da SQL-Abfragen manchmal ziemlich komplex aussehen, möchte ich nicht alle cars
holen und ihre wheels
plus kümmert sich um das Mapping in getCars(Long ownerId)
.
Ich vermisse eindeutig eine Business-Schicht, richtig? Aber ich bin einfach nicht in der Lage, meine Best Practice zu verstehen.
Nehmen wir an, ich habe Car
und% Owner
business-objects. Würde mein Code ungefähr so aussehen:
Das sieht so einfach aus wie es sein kann, aber was würde innen passieren? Die Frage zielt insbesondere auf CarOwner#getAllCars()
.
Ich stelle mir vor, dass diese Funktion Mapper und Repositories verwenden würde, um die Daten zu laden, und dass speziell auf den relationalen Mapping-Teil geachtet wird:
%Vor% Aber wie? Stellt% CarMapper
die Funktionen getAllCarsWithWheels()
und getAllCarsWithoutWheels()
bereit? Dies würde auch die CarRepository
und die WheelRepository
in CarMapper
verschieben, aber ist dies der richtige Ort für ein Repository?
Ich würde mich freuen, wenn mir jemand ein gutes praktisches Beispiel für den obigen Code zeigen könnte.
Ich benutze kein ORM - stattdessen gehe ich mit jOOQ. Es ist im Wesentlichen nur eine typsichere Art, SQL zu schreiben (und es macht ziemlich viel Spaß, es zu benutzen).
Hier ist ein Beispiel, wie das aussieht:
%Vor%Das Musterrepository gehört zu der Gruppe der Muster für Datenzugriffsobjekte und bedeutet normalerweise eine Speicherabstraktion für Objekte des gleichen Typs . Denken Sie an Java-Sammlung, die Sie verwenden können, um Ihre Objekte zu speichern - welche Methoden hat es? Wie funktioniert es?
Nach dieser Definition kann das Repository nicht mit DTOs arbeiten - es ist ein Speicher von Domain-Entities . Wenn Sie nur DTOs haben, benötigen Sie ein generischeres DAO- oder wahrscheinlich CQRS-Muster. Es ist üblich, eine separate Schnittstelle und Implementierung eines Repositorys zu haben, wie es zum Beispiel in Spring Data gemacht wird (es generiert die Implementierung automatisch, so dass Sie nur die Schnittstelle angeben müssen, die wahrscheinlich grundlegende CRUD-Operationen von dem allgemeinen Superinterface CrudRepository erbt). Beispiel:
%Vor% Die Dinge werden kompliziert, wenn Ihr Domänenmodell ein Baum von Objekten ist und Sie diese in einer relationalen Datenbank speichern. Nach Definition dieses Problems benötigen Sie ein ORM. Jeder Code, der relationalen Inhalt in das Objektmodell lädt, ist ein ORM. Ihr -Repository verfügt daher über ein ORM als Implementierung . In der Regel führen JPA-ORMs die Verdrahtung von Objekten im Hintergrund durch. Einfachere Lösungen wie benutzerdefinierte Mapper auf der Basis von JOOQ oder JDBC müssen dies manuell tun. Es gibt kein Wundermittel , das alle ORM-Probleme effizient und korrekt löst: Wenn Sie sich für ein benutzerdefiniertes Mapping entschieden haben, ist es besser, die Verkabelung im Repository zu behalten, damit die Business-Schicht (Services) funktioniert wahre Objektmodelle.
In Ihrem Beispiel kennt CarRepository
ungefähr Car
s. Car
weiß von Wheel
s, also hat CarRepository
bereits eine transitive Abhängigkeit von Wheel
s. In der Methode CarRepository#findByOwnerId()
können Sie entweder die Wheel
s für ein Auto direkt in derselben Abfrage abrufen, indem Sie einen Join hinzufügen, oder diese Aufgabe an WheelRepository
delegieren und dann nur die Verdrahtung durchführen. Der Benutzer dieser Methode erhält den vollständig initialisierten Objektbaum. Beispiel:
Welche Rolle spielt die Business-Schicht (manchmal auch Service-Schicht genannt)? Die Business-Schicht führt unternehmensspezifische Vorgänge für Objekte aus . Wenn diese Vorgänge atomar sein müssen, verwaltet Transaktionen . Im Grunde ist es bekannt, wann Transaktionsstart, Transaktionsbestätigung und Rollback signalisiert werden muss, hat aber kein grundlegendes Wissen darüber, was diese Nachrichten tatsächlich bei der Implementierung des Transaktionsmanagers auslösen. Aus Business-Schicht-Perspektive gibt es nur Operationen auf Objekte, Grenzen und Isolation von Transaktionen und nichts anderes. Es muss nicht auf Mapper oder was auch immer hinter der Repository-Schnittstelle steht, achten.
Meiner Meinung nach gibt es keine richtige Antwort. Das hängt wirklich von der gewählten Designentscheidung ab, die wiederum von Ihrem Stack und dem Komfort Ihres Teams abhängt.
Fall 1:
Ich stimme den unten hervorgehobenen Abschnitten in Ihrer Aussage nicht zu:
"Natürlich könnte ich Wheelmap in das CarRepository laden, dort das Wheel-Mapping durchführen und nur komplette Objekte zurückgeben, aber da SQL-Abfragen manchmal recht komplex aussehen können möchte ich nicht abholen alle Autos und ihre Räder plus kümmert sich um das Mapping in getCars (Long ownerId) "
sql Join für die oben genannten wird einfach sein. Darüber hinaus ist es möglicherweise schneller, da Datenbanken für Joins und das Abrufen von Daten optimiert sind. Nun habe ich diesen Ansatz als Case1 bezeichnet, weil dies verfolgt werden könnte, wenn Sie sich dazu entschließen würden, Daten über Ihr Repository mithilfe von sql-Joins abzurufen. Anstatt einfach sql für einfache CRUD zu verwenden und dann Objekte in Java zu manipulieren. (unten)
Fall 2: Das Repository wird nur verwendet, um Daten für "jedes" Domänenobjekt zu holen, das einer "einzigen" Tabelle entspricht.
In diesem Fall ist das, was Sie tun, bereits richtig. Wenn Sie WheelDTO niemals separat verwenden, müssen Sie keinen separaten Dienst dafür einrichten. Sie können alles im Autoservice vorbereiten. Aber, wenn Sie WheelDTO separat benötigen, dann machen Sie verschiedene Dienste für jeden. In diesem Fall kann eine Hilfeschicht über der Serviceschicht vorhanden sein, um die Objekterstellung durchzuführen. Ich schlage nicht vor, ein Orm von Grund auf neu zu implementieren, indem ich Repositorys mache und alle Joins für jedes Repo lade (benutze stattdessen direkt Hibernate oder mybatis)
Wieder IMHO, egal welchen Ansatz Sie aus dem oben genannten herausnehmen, ergänzen die Service- oder Business-Schichten es einfach. Also, es gibt keine feste Regel, versuchen Sie flexibel zu sein nach Ihren Anforderungen. Wenn Sie sich für die Verwendung von ORM entscheiden, ändern sich einige der oben genannten Punkte erneut.
Grundsätzlich verwende ich die Daten unverändert und lade sie auf Anfrage.
%Vor%Tags und Links repository-pattern business-logic dto abstraction-layer