Effizienter Datentransport – Sicherung kritischer Informationen
Die Verwendung von Datentransferobjekten hat zwei Ziele:
- Informationen in einer für die Verwendung und den Netzwerktransport optimierten Weise bereitzustellen;
- den Transfer möglicherweise sicherheitsrelevanter interner Informationen über Systemgrenzen (ein- und ausgehend) zu unterbinden.
Wikipedia gibt dazu folgende Definition:
Das Transferobjekt oder Datentransferobjekt (Abkürzung DTO) ist ein Entwurfsmuster aus dem Bereich der Softwareentwicklung. Es bündelt mehrere Daten in einem Objekt, sodass sie durch einen einzigen Programmaufruf übertragen werden können. Transferobjekte werden in verteilten Systemen eingesetzt, um mehrere zeitintensive Fernzugriffe durch einen einzigen zu ersetzen.
Implementierung
Für die Attribute von Transferobjekten steht nur eine begrenzte Auswahl an Datentypen zur Verfügung. Diese besteht aus primitiven Datentypen, einfachen Klassen und anderen Transferobjekten. Zusätzlich muss darauf geachtet werden, dass keine komplizierten Beziehungen zwischen den Transferobjekten entstehen. Empfehlenswert ist eine einfache Hierarchie, deren Klassendiagramm ein Baum ist.
Dabei wird der Transports über Netze in den Vordergrund gestellt. In Java bedeutet dies, dass DTO Objekte serialisierbar sein müssen. Der Aspekt der Sicherheit wird dabei nicht betrachtet. Sehr viel umfassender und auf die JEE Anwendungsarchitektur fokussiert werden Transferobjekte bei den Core JEE Patterns beschrieben. Teilweise ist dies auch auf Spring Applikationen übertragbar, speziell wenn andere View-Technologien als Vaadin verwendet werden.
Einsatz von DTOs in der Spring-Vaadin-Groovy-Architektur
Im Beispielprogramm werden DTOs als Datenschnittstelle zwischen dem View-Layer und dem Service-Layer verwendet. Auf den ersten Blick fällt dabei der Aspekt des Netzwerk-Transports weg, da die Vaadin-GUI mit Java-Klassen auf dem Server implementiert wird. Die Kommunikation mit dem in JavaScript geschriebenen Client im Browser wird automatisch durch Vaadin realisiert. Da aber dieser Datenaustausch weitgehend außerhalb der Kontrolle des Java-Programmierers abläuft, ist es auch hier besser, keine internen Informationen preiszugeben. Darüber hinaus kann die Vaadin-GUI potentiell auf mehrere Server verteilt werden, wenn die Applikation in großem Stil skalieren soll. Beschränkender Faktor ist die intensivere Kommunikation zwischen JavaScript-Client und Java Backend. Nach Angaben der Vaadin-Entwickler kann ein Vaadin-Server ca. 9000 simultane Benutzer bedienen, so dass bei sehr erfolgreichen Projekten die GUI-Schicht verteilt werden muss. Dann ergibt sich wieder eine Netzwerkkommunikation zwischen Service und GUI, die leichter angreifbar ist als eine Server-interne Kommunikation.
Primär werden die DTOs aber verwendet, um geeignet aufbereitete Daten für die Benutzerschnittstelle zur Verfügung zu stellen. Diese Daten sind oft stark denormalisiert, enthalten Informationen mehrerer Domain Objekte und orientieren sich an den Erfordernissen einzelner Use Cases oder Benutzergruppen. Es ist dabei sinnvoll, zwischen ausgehenden (Service zur GUI) und eingehenden (GUI zum Service) DTOs zu unterscheiden. Mehr dazu im Kapitel über CQRS.
Bei ausgehenden DTOs hat sich eine Unterscheidung in List-DTOs und Full-DTOs bewährt:
- List-DTOs enthalten für eine Menge von Objekten eindeutige Schlüssel (z.B. die Persistenz-ID) zusammen mit Strings zur Darstellung in UI-Elementen zur Objektauswahl. Dabei können Baumstrukturen oder einfache Listenstrukturen sinnvoll sein. Bei Auswahl eines angezeigten Elements in der GUI kann über den in der Regel nicht angezeigten Schlüssel die vollständige Information für das Element geholt werden.
- Full-DTOs enthalten alle Informationen, die zur Anzeige in einer Detailansicht benötigt werden. Das können Informationen aus mehreren Domain-Klassen sein.
Die Anzahl der DTO-Klassen ist relativ groß. Daher bewährt es sich, diese Klassen pro Use Case (bzw. Gruppe von Use Cases) zu organisieren. Dafür können Packages oder Klassen mit inneren Klassen (wie im Beispiel) verwendet werden. Die Service-Schicht ist dafür zuständig, DTOs bereitzustellen bzw. als Eingabe entgegenzunehmen.
Beispiel
Die Klasse TaskDTO stellt DTOs für die Auswahl, Darstellung und Veränderung von Task-Objekten als innere Klassen bereit. Die Namen der Query-Klassen beginnen mit Q, der Command-Klassen mit C.
class TaskDto { public static class QList { LinkedHashMap<Long, String> all = [:] Long getFirstId() { if (all) { all.keySet().iterator().next() } else { 0 } } } public static class QFull { Long id String classname String tag String description Long estimate Long summedEstimate Long spent Boolean completed ProjectDto.QList project = new ProjectDto.QList() SprintDto.QList sprints = new SprintDto.QList() QList supertask = new QList() List<QNode> subtasks = [] } public static class QNode { Long id String tag List<QNode> children = [] } public static class CSet { Long id = 0 String classname String tag String description Long estimate Long spent Boolean completed Long projectId List<Long> sprintIds = [] Long supertaskId List<Long> subtaskIds = [] } }
- Zeile 7
- Die Klasse TaskDto.QList verwaltet IDs und Namen einer Menge von Tasks in einer LinkedHashMap
- Zeile 19
- Klasse TaskDto.QFull enthält nicht nur die Werte der Felder von Task, sondern auch List-DTOs mit den assoziierten Projekten und Sprints und der Supertask. Mit Hilfe der subtask-Variablen (Zeile 31) wird ein Baum untergeordneten Tasks bereitgestellt.
- Zeile 34
- TaskDto.QNode enthält eine rekursive Datenstruktur, die zur Implementierung der Taskbäume dient.
- Zeile 40
- Klasse TaskDto.CSet wird für Create und Update Operationen verwendet. Hier werden nur die Werte für ein einziges Objekt übertragen, allerdings mit den IDs aller assoziierten Objekte. Damit können die Assoziationen angelegt oder aktualisiert werden
Die anderen DTOs sind analog aufgebaut.
Aufgabe
Implementieren Sie DTOs für Ihre erweiterten Query- und Command-Modelle (s. Aufgabe bei CQRS).