1. Einführung und Ziele

1.1. Aufgabenstellung

Das Ziel dieses Systems ist es, eine Materialsammlung für die "Modulare Online Plattform für Studierende" (kurz: "MOPS"), bereitzustellen. Dabei verwalten wir sämtliche Dateien, die Studierende, Korrektoren und Organisatoren benötigen, in einem File-System. Das System basiert dabei auf einer Gruppenstruktur. Solche Gruppen können sowohl Veranstaltungen als auch Lerngruppen repräsentieren.

Mögliche Anwendungsszenarien:

Name Szenario

Ein Mitglied möchte eine Datei einsehen.

Das Mitglied navigiert zur Gruppendateiübersicht. Dort navigiert es in den gewünschten Ordner, in dem die entsprechende Datei liegt.

Ein Administrator lädt einzelne Datei hoch.

Der Administrator einer Gruppe navigiert zur Gruppendateiübersicht. Dort kann er mit einem Upload-Button den Dateiupload beginnen.

Ein Mitglied möchte Verzeichnis erstellen.

Ein Mitglied einer Gruppe navigiert zur Gruppendateiübersicht. Dort kann es mit dem "Neuer Ordner"-Button einen neues Verzeichnis anlegen.

Ein Organisator möchte Verzeichnis mit Dateien löschen.

Der Organisator navigiert zur Gruppendateiübersicht. Dort klickt er das gesuchte Verzeichnis an und wählt "Verzeichnis löschen". Er bestätigt die Löschaktion und das Verzeichnis wird gelöscht

1.2. Qualitätsziele

Unsere Entwicklung haben wir an folgender Prioritätensetzung orientiert, um eine bestmögliche Lernumgebung für alle Nutzergruppen zu gewährleisten.

Qualitätsziel Erläuterung

Performance

Jeder Request muss in kürzester Zeit bearbeitet werden, um dem Benutzer ein möglichst flüssige Nutzungserlebnis zu bieten.

Zuverlässigkeit

Das System muss zu jedem Zeitpunkt erreichbar und voll funktionsfähig sein, um Nutzer nicht in Problemlagen durch fehlenden Zugriff zu bringen. Außerdem soll der Wartungsaufwand durch modulare Aufbauweise minimiert werden.

Sicherheit

In jedem Fall werden die Daten der Nutzer vertraulich behandelt und Fremdzugriff verhindert.

1.3. Involvierte Gruppen

Um die Erwartungen und Anforderungen der Nutzergruppen unserer Plattform zu erfüllen, haben wir im Folgenden eine Aufstellung der Erwartungshaltungen zusammengefasst, um diese in unserer Entwicklung zu berücksichtigen.

Nutzergruppe Erwartungshaltung

Studierende

Studierende brauchen eine Plattform, die einfachen Zugriff auf die Inhalte ihrer Lernveranstaltungen ermöglicht. Sie möchten sich mit ihren Mitstudierenden organisieren können und erwarten eine zuverlässige und nach heutigem Stand der Technik performante Plattform. Ausfälle durch Wartungsarbeiten und ungeplante Systemausfälle müssen minimiert werden, um eine vertrauenswürdige Umgebung zu schaffen.

Organisatoren

Organisatoren legen Wert auf eine intuitive Plattform, der es trotzdem nicht an Funktionalität mangelt. Hierbei spielt ein übersichtliches Layout eine entscheidende Rolle. Die Verlässlichkeit und Zuverlässigkeit des Systems hat weiterhin Priorität, um verlängerte Arbeitszeiten zu vermeiden. Organisatoren möchten viele Veranstaltungen einfach verwalten können, Materialien einsehen und organisieren, sowie Aktivitäten von Kursteilnehmern einsehen.

Systemadministratoren

Systemadministratoren legen Wert auf eine hohe Wartbarkeit und Zuverlässigkeit des Systems. Im Idealfall muss das System zur Wartung/Datensicherung nicht ganz abgeschaltet werden, es reichen vorübergehende Modulabschaltungen oder gar keine Funktionsreduktion. Eindeutige Modularisierung, ausführliche aber sinnvolle Laufzeitanalyse, sowie verständliche Dokumentation sind hierbei besonders hilfreich für die Überprüfung, Systemwartung und Erweiterbarkeit.

2. Randbedingungen

Um unerwartete Verzögerung im Entwicklungsablauf zu vermeiden, haben wir in der Planungsphase und während der Implementierung der Kernkomponenten berücksichtigt, welche Randbedingungen und technische sowie fachliche Einschränkungen den Ablauf der Entwicklung beeinflussen könnten.

Bedingung Erläuterung

Zusammenarbeit mit anderen Gruppen

Schnittstellen mit anderen Gruppen beeinflussen unsere Implementierung. Vor allem, wenn diese Gruppen selbst noch keine abgeschlossenen Konzepte entworfen haben.

Fehlende Ordnerstruktur in MinIO

Da MinIO nur eine virtuelle Ordnerstruktur unterstützt, ist die Anlegung einer eigenen Ordnerstruktur in einer externen Datenbank notwendig.

Keycloak erschwert UnitTests

Aufgrund der Sicherheitskomponenten werden die Controller-Tests stark verkompliziert, da die Sicherheitsfeatures gemockt werden müssen.

All diese möglichen Probleme haben wir jedoch schlussendlich überwunden und konnten das Produkt ohne größere Schwierigkeiten fertig stellen.

3. Kontextabgrenzung

Kommunikationsbeziehung Eingabe Ausgabe

Weiterleitung von der Gruppenbildung

Das Gruppenbildungsmodul stellt eine Anfrage mit der GruppenID um einen Weiterleitungslink zu erhalten.

Wir liefern einen auf die GruppenID bezogenen Link zurück.

Überprüfen des Adminstatus eines Nutzer in einer Gruppe

Wir erfragen mit einer GruppenID und dem Benutzernamen die Berechtigungen eines Nutzers in einer bestimmten Gruppe.

Wir erhalten als Bestätigung einen Boolean zurück.

Abfrage aller Gruppenupdates

Wir erfragen die Veränderungen in den Gruppendaten seit der letzten Anfrage.

Wir erhalten eine Liste aller veränderten Gruppen zurück.

Erfragen der Mitglieder einer Gruppe

Wir erfragen mittels einer GruppenID die Mitgliederdaten dieser spezifischen Gruppe.

Wir erhalten eine Liste von Nutzerdaten zurück.

Weiterleitung von der Gruppenbildung (Anfrage der Gruppenurl)

Anfrage:

Tabelle 1. /material1/group/{groupId}/url
Parameter Description

groupId

The group id.

Antwort (.json Objekt):

Path Type Description

.group_id

Number

The id of the group.

.root_dir_id

Number

The id of the group’s root directory.

.root_dir_url

String

The url of the group’s root directory.

Beispielrequest:

$ http GET 'http://localhost:8080/material1/group/1/url'

Beispielantwort:

{"group_id":1,"root_dir_url":"/material1/dir/2","root_dir_id":2}
Gruppenbildungsservice

Der Service, der unsere Gruppenverwaltung abwickelt, muss mit der Gruppenbildung interagieren. Dazu haben wir folgende Anfragen an die REST-API der Gruppenbildung implementiert:

  1. doesGroupExist: fragt ab, ob eine gegebene Gruppe existiert

  2. isUserInGroup: fragt ab, ob ein gegebener User in einer gegebenen Gruppe Mitglied ist

  3. isUserAdminInGroup: fragt ab, ob ein gegebener User in einer gegebenen Gruppe Administrator ist

  4. returnAllGroups: gibt alle Gruppen zurück, die seit einem bestimmten Zeitpunkt, der durch den Parameter eventId bestimmt wird, verändert worden sind

  5. returnUsersOfGroup: gibt alle Mitglieder einer Gruppe zurück

  6. returnGroupsOfUsers: gibt alle Gruppen eines Users zurück

Dabei werden von uns nicht alle dieser Anfragen genutzt. Dennoch sind sie implementiert um in Zukunft die Erweiterbarkeit zu vereinfachen. Diese verschiedenen Anfragen sind notwendig für die verschiedenen Funktionalitäten, die unsere Anwendung bereitstellt:

  • Anzeige aller Gruppen

  • Garbage Collection (Überprüfen ob eine Gruppe noch existiert)

  • Überprüfung von Berechtigungen eines Users

  • Erstellung einer Platzhalterberechtigung für alle Rollen

Da uns während der Entwicklungszeit der externe Service nicht zur Verfügung stand, haben wir einen Stub entwickelt, welcher festgesetzte Rollen zurückgibt. Wir haben uns dazu an dem REST-API Wiki der Gruppenbildung orientiert.

Prometheus

Im PrometheusComponent werden unsere Prometheus Statistiken registriert.

Zur Verfügung gestellt werden:

Global:

  • Speicherplatzverbrauch

  • Dateianzahl

  • Ordneranzahl

  • Gruppenanzahl

Für jede Gruppe einzeln (mit der Gruppen-UUID der Gruppenbildung getaggt):

  • Speicherplatzverbrauch

  • Dateianzahl

  • Ordneranzahl

4. Lösungsstrategie

4.1. Docker-Compose

Beim Design der docker-compose Konfigurationen hatten wir komplizierte Anforderungen.

Ziel:

In Production soll ein docker-compose […​] up die Anwendung und alle Dependencies (also nur die Datenbank) starten.

In Development soll ein anderes docker-compose […​] up nur die Dependencies (die Datenbank und MinIO) starten, damit die Anwendung separat aus der IDE gestartet werden kann.

Zusätzlich soll es eine Möglichkeit geben, die Anwendung für Tutoren zu demonstrieren. Noch ein anderer docker-compose […​] up Befehl soll alle Dependencies (diesmal die Datenbank und MinIO) und die Anwendung lokal starten.

In all diesen Fällen sollen sämtliche Umgebungsvariablen und Startparameter richtig konfiguriert sein.

Gelöst wird dies durch das Benutzen von mehreren docker-compose .yml Dateien, die zusammen geladen werden. Denn docker-compose erlaubt es, bereits definierte Services in einer anderen Konfigurationsdatei zu ergänzen.

Also gibt es nun:

docker-compose.yml: Enthält die Definition des Datenbank-Containers ohne Konfiguration.

docker-compose.dev.yml: Enthält die Definition des MinIO-Containers mit Konfiguration und zusätzlich die Konfiguration für den in der docker-compose.yml definierten Datenbank-Container.

docker-compose.prod.yml: Enthält die Definition des Anwendungs-Containers mit Build-Anweisungen und Konfiguration und zusätzlich die Konfiguration für den in der docker-compose.yml definierten Datenbank-Container.

docker-compose.demo.yml: Überschreibt Teile der Konfiguration des Anwendungs-Containers aus der docker-compose.prod.yml und fügt ein weiteres depends_on auf den MinIO-Container aus docker-compose.dev.yml ein.

Geladen werden die Konfigurationen aus den Dateien dev.env und prod.env. Eine vollständige Liste der Umgebungsvariablen findet sich in der prod.env und diese sind dort auch dokumentiert.

Somit kann man sich für jeden Fall aus den docker-compose-Konfigurationsdateien die passende Konfiguration zusammenbauen.

4.2. Controller Tests

Eine Schwierigkeit der Controller-Tests liegt in der Berücksichtigung der sicherheitsrelevanten Aspekte von spring-security und keycloak.

Wir hatten uns zunächst dafür entschieden diesen Teil zu mocken. Anfangs hatten wir Probleme, @WithMockUser zu verwenden, daher entschieden wir uns für das Mocken des gesamten SecurityContext. Durch SecurityContextUtil wird dieser jedem ControllerTest zur Verfügung gestellt.

Später wurden wir dann auf die Bibliothek spring-security-test-keycloack-addons aufmerksam, die das Testen massiv vereinfacht. Nun kann man die Annotation @WithMockKeycloakAuth benutzen, um Tests mit einer gültigen Keycloak Authentifizierung auszustatten.

4.3. Datenbank

Zur Auswahl standen hier MySQL, MariaDB und PostgreSQL, da wir mit diesen Datenbanken bereits Erfahrungen gesammelt haben.

Der Großteil unserer Erfahrungen beruhte bisher auf MySQL, also haben wir uns zunächst für die neuere Variante MariaDB entschieden.

Leider dauert das Starten der MariaDB-Datenbank in einem Docker-Container recht lange (ähnlich wie MySQL) und es ist umständlich, diesen Docker-Container dauerhaft aktiviert zu haben. Aus diesem Grund haben wir uns entschieden, während des Testings H2 zu verwenden. H2 ist eine Embedded In-Memory Datenbank, die sehr schnell startet und die wir bereits kennengelernt haben.

Nach einiger Zeit sind wir dann doch auf PostgreSQL umgestiegen, weil es von den Server-Admins der HHU bevorzugt wird und für ein Testdeployment auf Heroku notwendig ist. Das war ein schmerzloser Prozess, da wir bis dahin noch nichts mit der echten Datenbank gemacht haben.

4.4. Logging

Für das Logging haben wir uns zunächst auf zwei Fälle beschränkt. Zum einen info und error. Die info werden ausschließlich in den Controller geschrieben, da es sonst zu redundanten Lognachrichten kommen würde. Dies ist ausreichend, da jede Aktion unserer Applikation von einem Controller gestartet wird.

Die error-Nachrichten werden an jeder Stelle geschrieben, wo auch eine Exception geschmissen wird. Dies soll es einfach im Log machen den Stacktrace nach zu verfolgen.

Als Technologie benutzen wir SLF4J (Simple Logging Face for Java). Dies ist nativ in Spring-Boot 2.x und lässt sich mit einer einfachen Annotation (SLF4J) jeder Klasse hinzufügen.

Wir haben SLF4J nicht konfiguriert, also wird standardmäßig Logback benutzt.

Wir haben uns dafür entschieden, weil unsere Internetrecherche die meisten Treffer dazu ergeben hat und unser Tutor uns unabhängig davon zu geraten hat. Die Einfachheit des Tools ist ebenfalls ein Entscheidungsgrund gewesen.

5. Bausteinsicht

5.1. Gesamtabgrenzung

Gesamtabgrenzung

Enthaltene Bausteine
Komponente Funktion Schnittstellen

Keycloak

Authentifizierung und Authentifikation der User.

Wir erzeugen uns dafür ein eigenes Account Objekt.

Gruppenbildung

Erzeugen und Verwalten von Gruppen.

Erfragen von Gruppendaten und Berechtigungen.

5.2. Ebene 1

Ebene 1

Enthaltene Bausteine
Komponente Funktion Schnittstellen

Presentation Layer

Enthält alles, um die Darstellung der Daten für den Benutzer zu ermöglichen.

Es werden Anfragen an die Business Layer gestellt, um von dort die benötigten Daten zu erhalten.

Business Layer

Enthält die Services, welche die eigentliche Logik der Anwendung darstellen. Hier werden die rohen Daten verarbeitet und für die Presentation Layer nutzbar gemacht.

Es werden Anfragen an die Persistence Layer gestellt, um von dort die benötigen rohen Daten zu erhalten.

Persistence Layer

Enthält die Repositories, welche die Anfragen der Business Layer an die Database Layer weiterleiten.

Es werden Anfragen an die Database Layer gestellt, um die Daten aus der Datenbank bzw. MinIO zu erhalten.

Database Layer

Enthält die Datenbank sowie MinIO, welche die absoluten Rohdaten enthalten.

Es werden keine Anfragen gestellt, die Database Layer erhält nur Anfragen.

5.3. Ebene 2

Ebene 2 zeigt die einzelnen Schichten im Detail und deren Komponenten. Darstellung nach gleichem Schema wie zuvor.

Ebene 2

Erklärung:

Von der HTML Presentation aus wird je nach Route ein bestimmter Controller in der Presentation Layer angesteuert. Jeder Controller repräsentiert damit einen Routenanfang /material1/{routenBeginn}. Hierdurch werden zu große Controller vermieden und Übersicht geschaffen.

Die Services der Business Layer bearbeiten die Anfragen der Controller und stellen diese die angefragten Daten zur Verfügung. Die von ihnen benötigten Daten holen sie sich wiederum von der Persitence Layer.

In der Persistence Layer liegen die Repositorys, die im Prinzip direkte Schnittstellen zwischen den Anfragen der Services und der Database Layer darstellen. Sie verarbeiten also deren Anfragen und leiten sie an die Database Layer weiter.

Die Database Layer wiederum beinhaltet alle Daten, die bei uns gespeichert werden. Sie stellen diese ja nach Anfrage der Repositorys zur Verfügung.

Presentation Layer
Komponente Funktion Schnittstellen

DirectoryController

Stellt den Inhalt eines angefragten Ordners zur Verfügung.

Dateiinformation bekommt er vom FileService und die Ordnerinformationen vom DirectoryService. Berechtigungen werden vom PermissionService sowie vom SecurityService erfragt. Um Dateien zu löschen, wird auf den DeleteService zugegriffen. Um nach Dateien zu suchen, wird auf den SearchService zugegriffen. Um Ordner zum Download zur Verfügung zu stellen, wird auf den ZIPService verwiesen.

ExceptionController

Fängt sämtliche Exceptions, die im Projekt geworfen werden und stellt diese bereit.

Ein Administrator kann hier alle Exceptions einsehen.

FileController

Stellt angefragte Dateien zur Verfügung.

Alle Informationen bekommt er vom FileService.

GroupController

Stellt den Inhalt einer Gruppe zur Verfügung.

Gruppeninformationen werden beim GroupService angefragt und den darzustellenden Gruppenordner bekommt er vom DirectoryService.

GroupsController

Stellt alle Gruppen eines Benutzers zur Verfügung.

Alle Gruppeninformationen erhält er vom GroupService.

Material1Controller

Stellt Weiterleitungen für den initialen Aufruf und für das Logout bereit.

Dafür muss er auf keine weitere Komponente zugreifen.

HTML Presentation

UI

Stellt Anfragen an die Controller.

Business Layer
Komponente Funktion Schnittstellen

DeleteService

Ermöglicht das Löschen von kompletten Ordnern inklusive aller Unterordner sowie enthaltenen Dateien.

Um die Berechtigungen für das Löschen zu überprüfen, wird auf den SecurityService zugegriffen. Um die Löschen-Operationen an sich durchzuführen, werden der DirectoryService sowie der FileInfoService genutzt.

SearchService

Stellt die Suche nach Dateien in Ordnern und Unterordnern bereit.

Um die Such-Operationen durchzuführen, werden der FileService (für das Sammeln aller Dateien in einem Ordner) sowie der DirectoryService (für das Sammeln aller Unterordner in einem Ordner) genutzt.

DirectoryService

Stellt sämtliche Funktionen zur Verwaltung eines Ordners zur Verfügung. Das beinhaltet das Erstellen, Löschen und Durchsuchen von Ordnern, das Wechseln zu einem Unterordner, sowie das Hochladen einer Datei in den Ordner.

Die Berechtigungen des Ordners werden vom PermissionService zur Verfügung gestellt und können verändert werden. Die vom GroupService bereitgestellten Rollen werden vom SecurityService überprüft, damit ein User nur erlaubte Aktionen ausführen kann. Für das Speichern und Abrufen der Ordner wird mit dem DirectoryRepository interagiert.

FileInfoService

Verwaltet alle Metadaten einer Datei in Form des Objektes FileInfo.

Um die benötigten Daten zu erhalten wird eine Anfrage an das FileInfoRepository gestellt.

FileService

Verwaltet die Funktionen einer Datei. Dabei wird die Datei selbst mit ihren Metadaten verknüpft. Ermöglicht außerdem das Löschen von Dateien.

Mittels einer Anfrage an den FileInfoService werden die Metadaten angefragt. Mit diesen wiederum kann eine Anfrage an das FileRepository gestellt werden, um die Datei selbst zu erhalten. Berechtigungen werden mittels des DirectoryService und des SecurityService überprüft. Der Verfügbarkeitszeitraum der Dateien wird mittels des TimeService überprüft.

GarbageCollector

Löscht in regelmäßigen Zeitintervallen verwaiste Metadaten von Dateien oder Binaries. Außerdem werden nicht mehr existierende Gruppen inklusive all ihrer Inhalte gelöscht.

Holt sich die Metadaten von Dateien vom FileInfoService und die Binaries vom FileService. Die Existenz von Gruppen wird mittels des GroupService überprüft.

GruppenbildungsService

Erlaubt es Anfragen an die Gruppenbildung zu stellen.

Kommunziert nur über eine REST-API mit der Gruppenbildung.

GroupUpdater

Überprüft in regelmäßigen Zeitintervallen die aktuell existierenden Gruppen und aktualisiert die bei uns gespeicherten Daten.

Vom LatestEventIDService wird die ID des letzten gespeicherten Events geholt und mit dieser das Event vom GroupService angefragt. Mittels dieser Daten kann nun der Gruppenbildungsservice aufgerufen werden.

GroupService

Stellt den Zugang zu unserer Gruppen-Datenbank bereit.

Befragt die Datenbank über das GroupRepository nach Gruppen.

LatestEventIdService

Stellt den Zugang zu der gespeicherten latestEventId zur Verfügung. Wird für die Gruppenbildung benötigt.

Befragt die Datenbank über das LatestEventIdRepository nach der gespeicherten latestEventId.

PermissionService

Stellt die Berechtigungen eines Ordners zur Verfügung.

Befragt die Datenbank über das DirectoryPermissionsRepository nach Ordnerberechtigungen.

PrometheusComponent

Registriert die Prometheus Statistiken, um diese einem externen Tool zur Verfügung zu stellen.

Beim GroupService werden alle zur Zeit existierenden Gruppen angefragt. Für jede Gruppe wird beim DirectoryService und beim FileInfoService angefragt, um die Statistiken zu erhalten. Außerdem können bei diesen beiden auch die globalen Statistiken überprüft werden.

SecurityService

Überprüft die Rollenberechtigungen eines Nutzers.

Er vergleicht die Berechtigungen, die aus dem PermissionService kommen mit den Rollen des Users, die aus dem GroupService kommen.

ZipService

Zippt einen Ordner.

Die Informationen dazu bekommt er vom DirectoryService und FileService.

TimeService

Stellt die aktuelle Zeit bereit.

Befragt die Java Zeit-API nach der aktuellen Zeit.

Persistence Layer
Komponente Funktion Schnittstellen

DirectoryPermissionRepository

Ist die Schnittstelle zur Database für die Berechtigungen eines Ordners.

Stellt eine Anfrage an die Database, um ein Berechtigungsobjekt zu erhalten.

DirectoryRepository

Ist die Schnittstelle zur Database für das Erhalten eines Ordners.

Stellt eine Anfrage an die Database, um ein Ordnerobjekt zu erhalten.

FileInfoRepository

Ist die Schnittstelle zur Database für die Metadaten einer Datei.

Stellt eine Anfrage an die Database, um ein Dateimetadatenobjekt zu erhalten.

GroupRepository

Ist die Schnittstelle zur Database für die Gruppen.

Stellt eine Anfrage an die Database, um ein Gruppenobjekt zu erhalten.

LatestEventIdRepository

Ist die Schnittstelle zur Database für die latestEventId.

Stellt eine Anfrage an die Database, um das latestEventId-Objekt zu erhalten. Es wird immer nur höchstens eins geben.

FileRepository

Ist die Schnittstelle zu MinIO.

Stellt eine Anfrage mit einer DateiID an MinIO um die entsprechende Datei zu erhalten.

Database Layer
Komponente Funktion Schnittstellen

Datenbank

Stellt sämtliche Datenbankobjekte zur Verfügung.

Es werden ausschließlich Anfragen an die Database gestellt.

MinIO

Stellt benötigte Dateien zur Verfügung.

Es werden ausschließlich Anfragen an MinIO gestellt.*

5.4. Controller Paths

DirectoryController

Dieser hat folgende Routen:

Tabelle 2. /material1/dir/{dirId}
Parameter Description

dirId

The directory id.

Tabelle 3. /material1/dir/{dirId}/create
Parameter Description

dirId

The directory id.

Tabelle 4. /material1/dir/{dirId}/edit
Parameter Description

dirId

The directory id.

Tabelle 5. /material1/dir/{dirId}/rename
Parameter Description

dirId

The directory id.

Tabelle 6. /material1/dir/{dirId}/delete
Parameter Description

dirId

The directory id.

Tabelle 7. /material1/dir/{dirId}/search
Parameter Description

dirId

The directory id.

Tabelle 8. /material1/dir/{dirId}/upload
Parameter Description

dirId

The directory id.

Tabelle 9. /material1/dir/{dirId}/zip
Parameter Description

dirId

The directory id.

ExceptionController

Dieser hat die folgenden Routen:

$ http GET 'http://localhost:8080/material1/error'
FileController

Dieser hat folgende Routen:

Tabelle 10. /material1/file/{fileId}
Parameter Description

fileId

The file id.

Tabelle 11. /material1/file/{fileId}/delete
Parameter Description

fileId

The file id.

Tabelle 12. /material1/file/{fileId}/rename
Parameter Description

fileId

The file id.

Tabelle 13. /material1/file/{fileId}/download
Parameter Description

fileId

The file id.

GroupController

Dieser hat folgende Routen:

Tabelle 14. /material1/group/{groupId}
Parameter Description

groupId

The group id.

Tabelle 15. /material1/group/{groupId}/url
Parameter Description

groupId

The group id.

Tabelle 16. /material1/group/{groupId}/search
Parameter Description

groupId

The group id.

GroupsController

Dieser hat folgende Routen:

$ http GET 'http://localhost:8080/material1/groups'
Material1Controller

Dieser hat die folgenden Routen:

$ http GET 'http://localhost:8080/material1'

6. Entwurfsentscheidungen

6.1. Aufbau der Datenbank

ER Modell

6.1.1. Directory

Das Datenobjekt Directory stellt einen Ordner dar. Directory hat ein Attribut name. Dieses stellt den Namen des Ordners dar.

Außerdem hat der Entity Directory ein Attribut group. Dort wird eine ID gespeichert, welche die Gruppe des Ordners identifiziert. Zusätzlich hat ein Ordner eine eindeutige ID.

6.1.2. DirectoryPermissions

Das Datenbankobjekt DirectoryPermissions stellt die Berechtigungen für die erstellten Ordner dar. Die DirectoryPermissions haben eine eindeutige ID.

6.1.3. DirectoryPermissionsEntry

Das Datenbankobjekt DirectoryPermissionsEntry stellt Berechtigungsinformationen dar. DirectoryPermissionsEntry hat eine eindeutige ID. Außerdem hat DirectoryPermissionsEntry ein Attribut role. role stellt die Rolle (Student, Organisator) in der Gruppe dar. Für die Rolle gibt es Informationen, ob der Benutzer lesen, schreiben oder löschen darf. Aus diesem Grund haben wir 3 boolean Attribute für Lesen, Schreiben und Löschen erstellt (canRead, canWrite, canDelete).

6.1.4. FileInfo

In dem Datenbankobjekt FileInfo sind Informationen einer Datei dargestellt.

Das Attribut owner speichert den Namen des Inhabers der Datei. Außerdem speichern wir die Größe (size), den Namen (name) und den Typ der Datei (type). Zusätzlich hat jede FileInfo eine eindeutige ID.

6.1.5. FileTag

Hier speichern wir Tags für die Files (Dateien). Tags sind Kategorien oder Etiketten, die zur Suche und Filterung von Dateien nützlich sind. Das Attribut name speichert den Namen des Tags. Außerdem hat jeder Tag eine eindeutige ID.

6.1.6. Beziehungen

Directory hat FileInfo

Hier haben wir eine 1 zu N Beziehung, da ein Ordner (Directory) mehrere Dateien (FileInfo) enthalten kann. Im Gegenzug kann eine Datei in genau einem Ordner liegen. Entweder sie liegt im Root-Directory, oder in einem Unterordner.

Directory parent Directory

Directory hat eine rekursive Beziehung mit sich selbst. Ein Ordner kann mehrere Unterordner haben und jeder Ordner liegt in höchstens einem Ordner. Ein Ordner kann auch in keinem Ordner liegen, da wir beschlossen haben, dass eine Gruppe auch ein Ordner ist. Dies wird als Stammordner (Root-Directory) der Gruppe bezeichnet.

FileInfo hat FileTag

Hier haben wir eine 1 zu N Beziehung. Logischerweise kann eine Datei mehrere Tags haben, aber immer wenn ein Tag erstellt wird, wird er genau zu einer Datei zugeordnet.

Directory hat DirectoryPermissions

Jedem Ordner wird genau eine Berechtigung zugeteilt. Dadurch wissen wir, welche Zugriffsberechtigungen die Rollen haben. Eine Berechtigung (DirectoryPermissions) kann mehreren Ordnern zugeteilt werden, da wir beschlossen haben, dass nur die one-level Ordner eine Berechtigung haben. Alle Unterordner erben die Berechtigungen der one-Level Ordner. Außerdem hat das Root-directory auch eine eigene Berechtigung (DirectoryPermissions), denn hier können auch Dateien gespeichert werden.

DirectoryPermissions hat DirectoryPermissionsEntry

Hier haben wir eine 1 zu N Beziehung. Das Datenbankobjekt DirectoryPermissions erhält Berechtigungsinformationen von DirectoryPermissionsEntry. Somit kann eine Permission mehrere Berechtigungsinformationen für bestimmte Rollen haben. Wiederum wird immer eine DirectoryPermissionsEntry zu genau einer DirectoryPermissions zugeordnet.

6.2. ArchUnit Tests

Zur Vorbereitung haben wir uns die Vorlesung zu ArchUnit Vorlesung 14 PP2 durchgelesen und bearbeitet. Danach haben wir die Tests AGGREGATE_ROOT_PUBLIC_NOTHING_ELSE, ONE_AGGREGATE_ROOT_PER_PACKAGE und CHECK_LAYERED_ARCHITECTURE geschrieben und dazu eine Hilfsannotation AggregateRoot erstellt.

Die Funktion des ersten Tests ist die Überprüfung dass nur AggregateRoots public und alle anderen Klassen private sind.

Der zweite Test, stellt sicher, dass es nur eine einzige AggregateRoot pro Package gibt, welche mit anderen Packages kommunizieren kann.

Der dritte Test überprüft, ob die Schicht-Architektur eingehalten wird. Die zusätzliche Annotation ermöglicht es, die AggregateRoot zu markieren, um die Überprüfung zu erleichtern. Die Lesbarkeit und Verständlichkeit der Klassen und Packages werden dadurch auch verbessert.

Hier haben wir drei neue Tests hinzugefügt:

Der vierte Test ALL_CONTROLLERS_SHOULD_RESIDE_IN_MOPS_PRESENTATION sorgt dafür, dass alle Controller im Presentation-Layer liegen, da dies bei der Schicht-Architektur eine wichtige Vorgabe ist.

Der fünfte Test EVERYTHING_IN_PRESENTATION_SHOULD_BE_A_CONTROLLER ist eine Ergänzung des vorherigen Test und stellt sicher, dass sich alle Controller im Presentation-Package befinden, da sie nur von dort erreichbar sein sollen.

Der letzte Test, ARE_THERE_ANY_CYCLES_WITHIN_PACKAGES, ist eine Überprüfung, dass keine cycles im Projekt auftreten. Dies wird durch die Schicht-Architektur vorausgesetzt ist, aber dies ist ein zusätzlicher Test zur Sicherstellung, ob dies auch wirklich eingehalten wird, falls z.B. der LayerCheck nicht alles erwischt kann dies ein Auffangmechanismus sein.

Dann haben wir noch zwei weitere Tests für Services implementiert:

Der nun siebte Test SERVICES_ARE_ANNOTATED_WITH_SERVICE untersucht, ob alle Services auch richtig mit @Service annotiert sind.

Der achte Test, der mit dem siebten sehr in Einstimmung ist, überprüft, ob alle Klassen, die mit @Service annotiert sind, auch Service im Namen haben, damit die Struktur des Projekts nicht verwirrend wird.

6.3. FileStorage

Als Schnittstelle für die Dateispeicherung haben wir MinIO gewählt, weil es vorgegeben wurde. Die erforderliche Konfiguration erfolgt über Umgebungsvariablen um einen möglichst flexiblen Einsatz zu ermöglichen.

Die erforderlichen Umgebungsvariablen lauten:

  • MATERIAL1_MINIO_HOST

  • MATERIAL1_MINIO_PORT

  • MATERIAL1_MINIO_BUCKET_NAME

  • MINIO_ACCESS_KEY

  • MINIO_SECRET_KEY

Im Entwicklungsprozess sind diese durch Default-Werte (welche in der application.properties) vorgegeben.

Die Komponente stellt eine einfache Schnittstelle für die Dateispeicherung dar und kann einfach ersetzt werden, wenn eine andere Lösung zur Speicherung von Dateien genutzt wird.

7. Glossar

Begriff Definition

Directory

Ein (virtueller) Ordner im System.

Directory Permissions

Die Berechtigungen (Lesen, Hochladen, Löschen) die jede Rolle hat; einem Ordner zugeordnet.

File Info

Die Metadaten einer Datei (Name, Größe, Typ) im System.

FileListEntry

Kombiniert die Metadaten einer Datei und ihre Berechtigungen für eine einfachere Anzeige im HTML-Template.

MinIO

Die verwendete Cloud-Speicherlösung, die zur Speicherung der Nutzerdateien benutzt wird.

PostgreSQL

Das verwendete Datenbankmanagementsystem. Hier werden Verzeichnisse und Metadaten gespeichert.

Root Directory

Ein Ordner, der eine Gruppe im System repräsentiert. Jede Gruppe hat genau ein Root Directory.

Rolle

Die Berechtigungsstufe eines Nutzers im System; je nach Kontext entweder global oder lokal in einem Kurs.

Test (Modultest)

Modultests überprüfen dauerhaft die Funktion aller Komponenten (Module) des Systems. Dadurch wird sichergestellt, dass keine Fehler während der Entwicklung übersehen werden.