Persistenz
Für strukturierte Daten (Aufgaben, Übungen etc.) wird PostgreSQL verwendet. Für halb-strukturierte Daten (Lösungen, Kursdateien) wird das Dateisystem für die Speicherung verwendet, Metadaten (z.B. Abgabedatum der Lösung) werden in PostgreSQL abgelegt. Dadurch kann eine relationale Verknüpfung mit anderen Entitäten erfolgen.
Organisation von Daten im Dateisystem
Es gibt zwei Verzeichnisse, die von Subato verwendet werden:
- subato2-data: Enthält ergänzende Daten (zusätzlich zur Datenbank). Hier werden z.B. die tatsächlichen Lösungsdateien abgelegt
- subato2-tmp: Tempoäre Daten, die einfach reproduziert werden können (wie z.B. die Speicherung der lokalen Kopie von Git-Repositories der Aufgaben-Pools).
subato2-data
- subato2-data
- solutions
- 2023
- 01
- 05
- 717
- First.java
- Second.java
- task
- 1
- files
- text
- meta.xml
- 2
- courseFile
- 1
- Test.pdf
- 2
Jeder Unterordner (solution
, task
, ...) steht für verschiedene Entitäten, innerhalb des Unterordners werden ergänzende Dateien zu den Metadaten in der Datenbank mit einem Ordner pro Datensatz strukturiert. Der Ordername wird in der Regel aus dem Id-Wert abgeleitet. Im Fall von Lösungen wurde davon abgewichen, da bei einer hohen Anzahl an Lösungen eine manuelle Einsicht des Dateisystems nicht mehr möglich wäre, würden alle Lösungsordner auf der selben Ebene abgespeichert werden.
Es erfolgt bisher kein Konsistenzabgleich des subato2-data Ordners. D.h. es ist nicht klar, ob die existierenden Verzeichnisse immer noch einem Datensatz zuzuordnen sind oder ob der Datensatz inzwischen gelöscht wurde. Die Services selbst löschen zwar die Dateien, wenn Datensätze gelöscht werden aber bei manuellem Reset der Datenbank hilft das natürlich nicht.
subato2-tmp
- subato2-tmp
- pool
- 1
- ...
- 03201f07-....-....
Repositories der Aufgaben-Pools werden unter pool
gespeichert, die importierten Aufgaben selbst befinden sich aber im task
-Ordner. Deshalb sind diese pool
-Ordner nur als Cache/Kopie des Repositories zu sehen, die gelöscht werden kann. Bei jeder Synchronisation würde der Ordner erneut angelegt werden.
Die verbleibenden Ordner sind zufällig erzeugt (UUID) und repräsentieren Zwischenzustände (wie z.B. Tests, ob bei Änderung eines Aufgaben-Pools das Repository geclont werden konnte)
Tree-Cache für Dateien
Zusätzlich wird ein Tree-Cache über unstrukturierte Daten im Dateisystem erstellt, der derzeit als Datei tree.json
in jedem relevanten Ordner innerhalb von subato2-data
abgelegt wird. Das ermöglicht die effiziente Abfrage von einzelnen Dateien oder z.B. die Anzeige der Struktur von komplexen Lösungen, die sich über mehrere Ordner/Dateien erstrecken.
Datenbank/ORM
Für den Zugriff auf die Datenbanksysteme wird das Repository Pattern verwendet, die Repository-Implementierungen werden über die Spring Implementierung der JPA bereitgestellt.
Migrations
Zur strukturierten Weiterentwicklung des Datenbankschemas sind Migrations erforderlich. Da die Mittel im Spring Framework sehr begrenzt sind, wird Liquibase eingesetzt.
Die Migrations werden in src/main/resources/db
organisiert. Im Verzeichnis changelog
wird die Historie der Änderungen in Unterverzeichnissen (<jahr>/<monat>
) abgelegt. Eventuelle Seed-Daten können als CSV gespeichert und dann in den Migrations referenziert werden.
Nach jeder Änderung an den Entities soll eine neue Migration angelegt werden. Diese soll die Änderungen an der Entity reflektieren und ggf. Defaultwerte setzen, damit beim nächsten Deployment das Schema der Produktionsdatenbank aktualisiert wird.
Für IntelliJ kann das Plugin JPA-Buddy verwendet werden. In der kostenlosen Version können zwar keine differentiellen Migrations angelegt werden, jedoch werden Education-Lizenzen für Lehre und Forschung angeboten.
Statisch getypte Specifications
Spring bietet mit der Specification-API die Möglichkeit, Queries dynamisch zusammen zu stellen. Dadurch können z.B. Filter umgesetzt oder Code allgemein einfach nur wiederverwendet werden.
In Specifications werden Referenzen zu anderen Entities über Strings aufgelöst, was zu Fehlern zur Laufzeit führen kann. Mit Metamodellen kann das Problem behoben werden. Für die Generierung dieser Metamodelle wird der JPA Static Model Generator eingesetzt.
Bei jeder Kompilierung werden die Metamodelle erstellt. <Entity>_
ist das Metamodell der Klasse mit dem Namen <Entity>
.