Laden...

NHibernate: Bewertung des Schichtenmodells

Letzter Beitrag vor 17 Jahren 14 Posts 5.704 Views
NHibernate: Bewertung des Schichtenmodells

verwendetes Datenbanksystem: im Beispiel MS SQL Server2005, aber wegen des OR-Mappers eigentlich unwichtig

Da ich in künftigen Applikationen eine saubere Trennung zwischen Daten - Logik - Präsentation haben möchte, habe ich mir ein Beispielprojekt erstellt. Also eigentlich etwas, dass jeder Programmierer ab einer bestimmten Projektgröße brauchen kann.

Im Zuge dessen habe ich gleich beschlossen NHibernate zu verwenden - die grundsätzlichen Vorteile davon wurden in diesem Forum oft genug geschildert.

Angehängt ist die Aufteilung der Projektmappe:

Presentation (Anwendung)
Ist abhängig von der darunterliegenden BLL-Schicht
Beinhaltet die derzeit ausgewählte Datenbank-Konfiguration: hibernate.sql2005.cfg.xml

In dieser Schicht wird gegen das Interface aus der BLL programmiert, um die konkrete Implementation später austauschen zu können.
________________________________________

Business Logic Layer (Klassenbibliothek)
Ist abhängig von der darunterliegenden DAL-Schicht. Nur hier wird die NHibernate-Assembly referenziert.

Die Validierung der Benutzereingaben geschieht auch in dieser Schicht, die Windows-Form wird soweit wie möglich von Geschäftslogik freigehalten, aber aus Bequemlichkeit kann dies immer wieder mal vorkommen. Darum die Frage, ob es hierfür ein best practice gibt?
________________________________________

Data Access Layer (Klassenbibliothek)
Keinerlei Abhängigkeiten, beherbergt die Objekte mitsamt den zugehörigen Hibernate Mapping-Beschreibungen.
________________________________________

Zur Aufbau der NHibernate-Konfiguration habe ich folgendes Tutorial verwendet: Part1 + Part2

Nun wurde im zweiten Teil empfohlen, um NHibernate optimal zu nutzen, den Konfigurationsteil in eine statische Klasse zu packen. Dies erscheint auch durchaus sinnvoll, damit nicht mehr als eine SessionFactory benutzt wird, zumindest solange eine Datenbank pro Programm ausreicht.


static Configuration _config = null;
static ISessionFactory _factory = null;
static ISession _session = null;

        static OptimisticUserManager()
	 {
                _config = new Configuration();
                _config.Configure("hibernate.sql2005.cfg.xml");

                Assembly tPluginAssembly = Assembly.LoadFrom("DataAccessL.dll");
                _config.AddAssembly(tPluginAssembly);

                _factory = _config.BuildSessionFactory();
                _session = _factory.OpenSession();
	  }

Da das ganze als Ausgangspunkt für zukünftige DB-Projekte dienen soll, würde ich mich um Verbesserungen der Architektur auf dieser noch simplen Ebene sehr freuen.

Jede zusätzliche Abstraktion wäre ein Gewinn für spätere Projekte, die ja beliebig geartet sein könnten.

Hallo Zusammen,

Ich habe vor kurzem auch angefangen mich mit NHibernate zu beschäftigen und wollte fragen ob sich hierzu schon weitere Erkenntnisse ergeben haben.

Vielen Dank für eure Antwort..

Meiki

Hallo,

Wie würdet ihr ein Customer z.b. an verschiedene Gruppen binden?

Ich dachte da an ein Treeview wo mann mit strg mehrere selektieren kann.

Hat das schon mal jemand mit nhibernate gemacht?

MFG

Original von boonkerz
Hallo,

Wie würdet ihr ein Customer z.b. an verschiedene Gruppen binden?

Ich dachte da an ein Treeview wo mann mit strg mehrere selektieren kann.

Hat das schon mal jemand mit nhibernate gemacht?

MFG

Eigentlich so wie du es beschrieben hast, aber ob man dabei Hibernate verwendet oder nicht macht hierbei meiner Meinung nach keinen Unterschied.

Ich benutze beispielsweise die Technik Verzögertes Laden von Daten im TreeView und zeige alles Werte aus der Master-Tabelle an. Wenn nun ein Knoten expandiert werden soll, werden mit dem selektierten Master-Wert die Child-Daten per Hibernate gelesen. Die Business-Schicht wandelt die dabei die Objekte in simple Stringlisten um, damit die Präsentationsschicht von den Details der Implementierung geschützt ist.

Der Beitrag ist zwar schon etwas älter, aber ich hoffe das Thema ist immernoch aktuell.

Ich beschäftige mich auch intensiv mit Anwendungsarchitekturen und habe ein paar Fragen zu Deinem Beispielprojekt.

Wenn das Business-Objekt "User" in der Datenzugriffsschicht liegt, aber die Präsentationsschicht nichts von der Datenzugriffsschicht weiss (weil sie ja nur von der Geschäftslogikschicht wissen soll), wie soll die Präsentationsschicht dann einen User anzeigen?
Das geht doch gar nicht, oder? Der OptimisticUserManager enthält ja nur die Geschäftsregeln für die Entität "User", repräsentiert aber selbst keinen User (Oder habe ich das falsch verstanden). Wenn ich z.B. 30 User einer bestimmten Verwaltungsgruppe auflisten möchte, in welcher Form würde die Präsentationsschicht die Daten dieser 30 User übertragen bekommen?
Wenn die Geschäftslogik auf einem Applikationsserver liegt (Wie das bei 3-Schichtigen Anwendungen üblich ist), wie geht dann die Validierung der Benutzereingaben im Windows.Form von statten? Für jeden Klick einen Netzwerkzugriff?

Meine Fragen klingen vielleicht deshalb etwas provokant, weil ich bisher keinen Weg gefunden habe, die Geschäftsregeln zentral an einem Ort (also einer Schicht) zu halten. Mich interessiert, wie es andere halten mit dem erhwürdigen Schichtenmodell. Hast Du Erfahrungen in realen Projekten mit diesem Aufbau gemacht?

Die Fragen sind mehr als berechtigt, bin eher neu in geschichteten Anwendungen und möchte nun Erfahrungen und best practices sammeln.

Der OptimisticUserManager war so gedacht, dass nur ganz bestimmte Operationen mit dem User-Objekt möglich sind. Im Beispielprojekt waren dies etwa:


public List<String> getUserNames()
public void DeleteAllGirls()
public void addNewUser(String name, String password, String email)

Dadurch braucht also die Präsentationsschicht keinen direkten Zugriff auf das User-Objekt. Für jedes spezielle Bedürfnis ist die Präsentationsschicht auf die Businessschicht angewiesen.

Da ich den Zugriff Präsentationsschicht -> Businessschicht möglichst einfach halten wollte, habe ich nur einfache/primitive Datentypen verwendet. So wie hier im Beispiel Strings oder List<String>.
Die Implementierung des User-Objekts kann damit intern gewechselt werden, ohne dass die Präsentationsschicht davon etwas mitbekommt. Das ist wohl zumindest eine Mindestanforderung an ein 3-stufiges Schichtenmodell.

Aber dein Einwand mit einer komplexeren Anfrage, also etwa alle Userobjekte mit allen zugehörigen Eigenschaften, bereitet mir Kopfzerbrechen 🤔

Hier besteht nun wirklich das Problem, wie dies vernünftig abstrahiert werden kann ohne zuviele unnötige Methodenaufrufe.

Eine Quick&Dirty-Lösung wäre eine so geartete Funktion


void getAllUsers(List<Hashtable>)

die für pro User eine Hashtable füllt und jede Hashtable in einer Liste anfügt. Die Präsentationsschicht könnte dann die gewünschten Werte auslesen, es genügte ihr die Kenntnis der Namen der Eigenschaften.

Aber ob das dem Sinn des Schichtenmodells entspricht vermag ich nicht zu beurteilen.

OR-Mapper

Du verwendest einen OR-Mapper, um mit der Datenbank zu reden. Dieser bildet Dir Datensätze typsicher in Objekten ab. Damit aus Abfrageergebnissen der Datenbank aber Objekte werden, muss der OR-Mapper einige Arbeitsschritte verrichten. Mit OR-Mapping erkauft man sich typsichere intuitive Objekte durch Einschränkungen und geringere Geschwindigkeit.
Und das Ganze nur, um es gleich wieder wegzukapseln und hinterher mit Arrays und Hashtables zu arbeiten, die nicht typsicher sind? Da stimmt was nicht.

Die Objekte, die der OR-Mapper erzeugt gehören gar nicht zur Datenzugriffsschicht, sondern sind als Schnittstellen zu betrachten. Im WCF-Jargon würde man sagen, dass es DataContracts sind. Manche Leute nennen solche Objekt auch DTO (Data Transfer Objekts). Das trifft es auch ganz gut, denn es sind eigentlich nur Container, die Daten zwischen den Schichten bzw. zwischen Komponenten (je nach Sichtweise) hin und her transportieren. Deshalb muss sowohl die Geschäftsdienstschicht als auch die Benutzerdienstschicht (oder Präsentationsschicht) diese DTOs kennen (Kennen=Verweis auf die Assembly im Visual Studio Projekt haben).

Die Schnittstelle des UserManagers könnte dann z.B. so aussehen:


public interface IUserManager
{
    IList<User> GetAllUsers();

    User GetUser(string userName);

    void LockUser(string userName);

    void UnlockUser(string userName);

    IList<LockHistoryEntry> GetUserLockHistory(string userName);

    void SaveUsers(IList<User> users);
}

Die Benutzeroberfläche könnte z.B. das DataGridView einsetzen, um eine Benutzerliste anzuzeigen:


// Proxy für den Zugriff auf die Benutzerverwaltung erzeugen
IUserManager manager=ServiceFactory<IUserManager>.GetProxy();

// Benutzerliste abrufen
IList<User> users=manager.GetAllUsers();

// Benutzerliste anzeigen
_userGrid.DataSource=users;

Ich habe mit diesem Ansatz sehr gute Erfahrungen gemacht. Allerdings verwende ich keinen OR-Mapper, sondern DataTables oder Typisierte DataSets und für den Datenzugriff ADO.NET. DataTables sind wesentlich komfortabler als Objekte, da man sie out-of-the-Box im Hauptspeicher sortieren und filtern kann. Solche Funktionen werden in fast jeder Benutzeroberfläche benötigt, da der Benutzer die Anzeige der Daten ja meistens anpassen kann. Bisher konnte mir noch niemand einen wirklichen Vorteil von OR-Mapping sagen. Aber das ist letztendlich Geschmacksache.

Stimmt, für mich gehörten die Klassen der NHibernate-Objekte eindeutig zur untersten Datenzugriffsschicht. Die Kapselung zwischen BLL und Klient war deswegen so, um eine möglichste lose Kopplung zu erhalten. Aber auch beim Hashtable-Ansatz muß der Klient die Bezeichnungen der User-Eigenschaften kennen, wodurch dann eigentlich nichts gewonnen ist.

Um mich mit dieser doch recht unterschiedlichen Sichtweise vertraut zu machen, habe ich folgenden interessanten Artikel zu den von dir eingeführten DTOs gefunden.
Er ist zwar eigentlich für J2EE ausgelegt, aber dort hat man ja die gleichen Probleme zu lösen, was das ganze übertragbar macht (außer Entsprechungen für Session oder Entity Beans zu finden ist voll doof g).

Use a Transfer Object to encapsulate the business data. A single method call is used to send and retrieve the Transfer Object. When the client requests the enterprise bean for the business data, the enterprise bean can construct the Transfer Object, populate it with its attribute values, and pass it by value to the client.

Hier wird wie du es beschrieben hast, das DTO an die Präsentationsschicht, sprich dem Klienten, übergeben und zwar als Wert und nicht als Referenz.

Kann ich mir dies in etwa so vorstellen:
In der Business-Schicht, was einem in Java einem EJB entspricht, wird zuerst ein User-Objekt mithilfe der Datenzugriffstechnologie (bei mir ein OR-Mapper) mit Werten aus der DB gefüllt.
Danach wird dieses Objekt geklont, weil ja laut Artikel keine Referenz sondern nur eine wertgleiche Kopie ausgetauscht werden soll.

Für änderungsfähige DTOs wird dann die Updatable Transfer Objects Strategy eingeführt, die recht überzeugend wirkt.

Instead of providing fine-grained set methods for each attribute, which results in network overhead, the BusinessObject can expose a coarse-grained setData() method that accepts a Transfer Object as an argument. The Transfer Object passed to this method holds the updated values from the client.

Um nochmal auf deine konkrete Vorgehensweise zurückzukommen: Wenn ich dich richtig verstehe, erzeugst du mit


// Proxy für den Zugriff auf die Benutzerverwaltung erzeugen
IUserManager manager=ServiceFactory<IUserManager>.GetProxy();

eine (möglicherweise entfernte, also beliebige Interprozesstechnik) Referenz auf die Managerinstanz welche in der BLL läuft. Oder wird erst verzögert beim GetAllUsers() der erste Remotezugriff nötig?

Verteilte Anwendungen

Original von wdb.lizardking
(außer Entsprechungen für Session oder Entity Beans zu finden ist voll doof g).

EJBs sind Klassen, die auf einem Java-Applikationsserver laufen und dessen Infrastruktur nutzen. Die Entsprechung dafür, die am besten passt sind ServicedComponents (Enterprise Services). Aber im Groben kannst Du Dir EJBs in .NET als Klassen denken, die von MarshalByRef abgeleitet sind und in einem Remoting-Host leben (Natürlich könnte es auch WCF oder sonst was sein). Dein UserManager wäre in Java wohl eine EJB.

Original von wdb.lizardking
Kann ich mir dies in etwa so vorstellen:
In der Business-Schicht, was einem in Java einem EJB entspricht, wird zuerst ein User-Objekt mithilfe der Datenzugriffstechnologie (bei mir ein OR-Mapper) mit Werten aus der DB gefüllt.
Danach wird dieses Objekt geklont, weil ja laut Artikel keine Referenz sondern nur eine wertgleiche Kopie ausgetauscht werden soll.

Die DTOs werden nicht explizit geklont. Das "klonen" besteht vielmehr darin, dass sie serialisiert werden, übers Netzwerk zum Client geschickt und dort wieder deserialisiert werden. Nach diesem Vorgang existiert ein DTO theoretisch einmal auf dem Server und einmal auf dem Client (der ja Kopie übers Netzwerk bekommen hat). Nur theoretisch deshalb, weil das DTO auf dem Server sofort wieder entsorgt wird, sobald der entfernte Methodenaufruf ausgeführt ist. Man kann also eher sagen: Das DTO wurde vom Server zum Client geschickt und ist jetzt dort.
DTOs dürfen deshalb NIEMALS von MarshalByRef oder ContextBoundObject abgeleitet werden und müssen das Attribut [Serializable] bekommen, damit sie serialisierbar sind.
Die Geschäftsklassen wie z.B. der UserManager leben idealerweise nur einen Methodenaufruf lang (SingleCall). So werden die Ressourcen des Servers (vor allem der Arbeitsspeicher) nur sehr kurze Zeit in Anspruch genommen. Das ist wichtig, damit der Server möglichst viele Clients schnell bedienen kann.

Original von wdb.lizardking
Für änderungsfähige DTOs wird dann die Updatable Transfer Objects Strategy eingeführt, die recht überzeugend wirkt.

Das würde meiner UserManager.SaveUsers(IList<User>)-Methode entsprechen. Du sendest ein oder mehrere zu speichernde DTOs in einer Liste an den Server und dieser sorgt dafür, dass die Änderungen korrekt in die Datenbank geschrieben werden. DataTables/DataSets haben auch hier die Nase vorn, da die einzelnen DataRows immer wissen, ob sie gändert, gelöscht oder neuangelegt wurden. Einfache Objekte wie User wissen das nicht (Was im Falle von nHibernate aber nicht Dein Problem ist, da sich der OR-Mapper darum kömmert).

Original von wdb.lizardking
... Wenn ich dich richtig verstehe, erzeugst du mit

  
// Proxy für den Zugriff auf die Benutzerverwaltung erzeugen  
IUserManager manager=ServiceFactory<IUserManager>.GetProxy();  
  

eine (möglicherweise entfernte, also beliebige Interprozesstechnik) Referenz auf die Managerinstanz welche in der BLL läuft. Oder wird erst verzögert beim GetAllUsers() der erste Remotezugriff nötig?

Das kommt auf die Interprozesstechnik an. Im Falle von serverseitig aktivierten Typen bei Remoting, passiert erst dann auf dem Server etwas, wenn über den Proxy eine Methode aufgerufen wird. Bei SingleCall-Aktivieren, wird ja für jeden Methodenaufruf ein neues Objekt auf dem Server erzeugt, welches die Clientanfrage abarbeitet und anschließend sofort wieder entsorgt wird (Natürlich tatsächlich erst, wenn es vom GC abgeräumt wird). Wenn ich GetAllUsers also fünf Mal aufrufe, werde ich von fünf verschiedenen Serverobjekten bedient (Immernoch ausgehend von serverseitiger SingleCall-Aktivierung).

Was die Aktivierungsarten bei Remoting angeht, ist vielleicht dieser Artikel interessant:
C/S mit Remoting: Beispiele für Client-/Server Activated Objects

Eine ähnliche Diskussion über Remoting im speziellen findest Du in diesem Artikel (Ist vielleicht auch lesenswert in diesem Zusammenhang): [Erledigt] C/S-Remoting: Sinnvolle Struktur der Schnittstellen?

nHibernate in Ehren, aber hast Du Dir schon mal typisierte DataSets und ADO.NET angesehen?

So, erstmal vielen Dank Rainbird für deine unschätzbar guten Erklärungen.

Ich habe nun diese Vorgehensweise mit serialisierbaren DTOs anhand des Beispielprojekts implementiert und finde die Schichtentrennung nun annehmbar, aber natürlich immer noch ausbaufähig.

Konkret wird bei einer Klientenanfrage nun erstmal durch eine Assembler-Klasse ein UserDTO mit sendenswerten Daten gefüllt. Auch innerhalb der Business-Schicht wird nur noch mit der DTO-Repräsentation gearbeitet, nicht mehr direkt mit dem NHibernate-Objekt.

Die UserDTO-Klasse sieht der User-Klasse von NHibernate zwar ziemlich ähnlich und bedeuted einen nicht unbeutenden Mehraufwand, aber so werden nur genau die Daten übers Netz geschickt, die wirklich auch gebraucht werden.

Der Client muß nur die DTO-Klassen kennen was zum Vorteil hat, dass bei einer DB-Änderung die DTO-Klassen nicht zwangsweise mitgeändert werden müssen.

Desto mehr Daten ich von haus aus in die DTO-Klassen packe, desto weniger Zugriffe auf die UserManager-Fassade sind nötig. Hier kann ich als Programmierer immer selber abwägen, ob mir nun ein schlankes Datenpaket oder weniger Netzwerkzugriffe wichtig sind.

Original von Rainbird
nHibernate in Ehren, aber hast Du Dir schon mal typisierte DataSets und ADO.NET angesehen?

Informationen zu typisierten DataSets habe ich mir im Vergleich zu anderen Datenhaltungstechnologien durchgelesen und halte das ebenso für einen guten Ansatz. Die Nachteile bei diesem Ansatz sind auch ziemlich auf NHibernate übertragbar

MSDN
* Deployment. The assembly containing the typed DataSet class must be deployed to all tiers that use the business entity.
* Support of Enterprise Services (COM+) callers. If a typed DataSet will be used by COM+ clients, the assembly containing the typed DataSet class must be given a strong name and must be registered on client computers. Typically, the assembly is installed in the global assembly cache. These steps will also be required for custom entity classes, as described later in this document.
* Extensibility issues. If the database schema is modified, the typed DataSet class might need to be regenerated to support the new schema. The regeneration process will not preserve any custom code that was implemented in the typed DataSet class. The assembly containing the typed DataSet class must be redeployed to all client applications.
* Instantiation. You cannot instantiate the type by using the new operator.
* Inheritance. Your typed dataset must inherit from DataSet, which precludes the use of any other base classes.

NHibernate verwende ich aber nicht nur wegen der objektorientierten Kapselung sondern genauso wegen der automatischen Unterstützung von DB-Systemen. Auch HQL (Hibernate Query Language) als Ersatz für natives SQL stellt ein großes Plus dar.

Original von wdb.lizardking
NHibernate verwende ich aber nicht nur wegen der objektorientierten Kapselung sondern genauso wegen der automatischen Unterstützung von DB-Systemen. Auch HQL (Hibernate Query Language) als Ersatz für natives SQL stellt ein großes Plus dar.

Das ist natürlich ein triftiger Grund, für den Einsatz dieses Systems. Wenn die Anwendung wirklich mit verschiedenen RDBMS kompatibel sein muss, ist das wohl ein klarer Punkt für nHibernate.

Könntest Du aber nicht das User-Objekt (welches von der Hibernate-Engine befüllt wird) direkt als DTO verwenden? Oder erbt die Klasse user von irgendwelchen Hibernate-Basisklassen (Ich kenne mich mit Hibernate nicht so gut aus)?

Original von Rainbird

Könntest Du aber nicht das User-Objekt (welches von der Hibernate-Engine befüllt wird) direkt als DTO verwenden? Oder erbt die Klasse user von irgendwelchen Hibernate-Basisklassen (Ich kenne mich mit Hibernate nicht so gut aus)?

Ursprünglich wurde das User-Objekt bis zur Fassade weitergereicht, die damit gearbeitet hat.

Aber in dem von dir aufgebrachten DTO-Pattern wird aus Performanzgründen empfohlen, seperate DTOs für Geschäftsprozesse anzulegen. Siehe hier.
Die Hibernate-Klassen entsprechen im Schaubild den Klassen _Album _und Artist.
Um nun nicht zuviele (kostenintensive) Methodenaufrufe zu vermeiden, baut ein Assembler fette Klassen zusammen, die eher zuviel denn zu wenig Informationen aus der DB enthalten.

Auch können sich so die Strukturen der DB-Tabellen ändern, was zu einer Änderung der Hibernate-bezogenen Klassen führt.
Die DTOs müssen dagegen nur angepasst werden, wenn sich die DB-Änderung auf die Anwendung durchschlägt.
Und da die Präsentationsschicht nur DTOs und keine keine grundlegenden Klassen kennt, ist eine Neuübersetzung der Clientanwendung nicht zwingend nötwendig.

Nachteil ist halt der Mehraufwand für die DTOs sowie die Assemblertätigkeit. Aber was tut man nicht alles für gute Architektur 🙂

Die im Text angesprochene Remote Facade wird durch die Klasse UserManager repräsentiert.

Original von wdb.lizardking
Um nun nicht zuviele (kostenintensive) Methodenaufrufe zu vermeiden, baut ein Assembler fette Klassen zusammen, die eher zuviel denn zu wenig Informationen aus der DB enthalten.

Das trifft aber nur zu, wenn Du auch wirklich mehrere "Tabellen" auf einmal übertragen willst. Wenn ich z.B. nur Kopfdaten eines bestimmten Interpreten haben will, lasse ich mir doch kein Objekt zurückgeben, welches den Interpreten und all seine Alben enthält.
Übertragen auf den DataSet/DataTable-Ansatz: Wenn ich nur eine Tabelle abrufen, gebe ich kein DataSet, sondern nur eine DataTable zurück. Wenn ich aber mehrere Tabellen (die evtl. auch miteinander in Beziehung stehen) auf einmal abfragen will, gebe ich ein DataSet zurück.

Es kommt also auf die Anwendungsfälle der Anwendung an, ob zusammengefasste DTOs sinvoll sind, oder nicht. Ich würde das deshalb nicht pauschal festlegen, sondern von Fall zu Fall abwägen.

Außerdem würde ich niemals alle Eigenschaften der einzelnen Hibernate-Objekte im "Sammel-DTO" nachmals definieren. Wäre nicht auch folgendes denkbar?


[Serializable]
public class ArtistDataContainer
{
    public Artist Artist
    {
        ...
    }

    public IList<Album> Albums
    {
        ...
    }    
}

Alles typsicher, aber trotzdem die Original-Typen beibehalten.

Original von Rainbird
Wenn ich z.B. nur Kopfdaten eines bestimmten Interpreten haben will, lasse ich mir doch kein Objekt zurückgeben, welches den Interpreten und all seine Alben enthält.

So wie ich den Ansatz verstanden habe, sollte man es tatsächlich so machen. Ist zwar auf Anhieb ein wenig merkwürdig, aber im Laufe eines Geschäftsprozesses kommt es desöfteren vor dass im Laufe der Abarbeitung einzelne Informationen sequentiell geholt werden müssen.
Und nun anstatt mehrere Aufrufe, die nur auf die jeweilige Anfrage passen, wird gleich ein Gesamtpaket geschnürrt.
Die Details dieses Gesamtpakets obliegen ja dem Entwickler und können im einfachen Falle auch durchaus von schlanker Natur sein.

Original von Rainbird
Es kommt also auf die Anwendungsfälle der Anwendung an, ob zusammengefasste DTOs sinvoll sind, oder nicht. Ich würde das deshalb nicht pauschal festlegen, sondern von Fall zu Fall abwägen.

Eben 👍
Aber eine Mischung aus DTOs und Datenklassen, welche an den Client geliefert werden, würde mir irgendwie missfallen.

Original von Rainbird
Außerdem würde ich niemals alle Eigenschaften der einzelnen Hibernate-Objekte im "Sammel-DTO" nachmals definieren. Wäre nicht auch folgendes denkbar?

Alles typsicher, aber trotzdem die Original-Typen beibehalten.

Dies würde ich mit Der Client muß nur die DTO-Klassen kennen was zum Vorteil hat, dass bei einer DB-Änderung die DTO-Klassen nicht zwangsweise mitgeändert werden müssen. versuchen zu rechtfertigen.
Wenn die DTO-klassen dagegen direkt mit den Datenklassen assoziiert sind, wäre dies nicht mehr der Fall. Aber dieser Punkt ist wirklich streitbar.