Go Persistenz mit GORM

Grundlagen

Persistenz ist die Speicherung von Objekten über die Laufzeit des Programms hinaus. Dafür gibt es unterschiedliche Möglichkeiten. Siehe dazu die Seite „Objekte dauerhaft speichern„.

  • Relationale Datenbanken (RDBMS) werden in Webapplikationen sehr häufig zur Speicherung verwendet.
    • Weit verbreitet, oft „strategisch“, weil schon lange im Unternehmen eingeführt
    • Keine direkte strukturelle Übereinstimmung mit Datenstrukturen der Programmiersprache → „Impedance Mismatch“
    • Object Relational Mapping (ORM) → Standardtechnik für objektorientierte Programmiersprachen (s. „Objektrelationale Abbildungen„)
    • In Go wird daraus Go Relational Mapping (GORM), s.u.
Neue Aspekte in Programmen durch Persistenz

Viele Programme können gleichzeitig auf persistente Objekte zugreifen

  • Transaktionen, Locking
    • Zugriff auf persistente Objekte aus mehreren Prozessen oder Programmen
    • Atomare Lese/Schreiboperationen sicherstellen → Transaktionen sichern die Konsistenz (ACID)
    • Konkurrierende Zugriffe verhindern → Locking reserviert exklusiven Zugriff

Datenbanken stellen mächtige Suchfunktionen zur Verfügung

  • Queries
    • Objekte existieren unabhängig von Programm in DB → Suchen nicht nur nach der Objekt-ID
    • Abfragen in Anlehnung an SQL, Query by Example

Verwendung von GORM

GORM, „The fantastic ORM library for Golang“, ist eine häufig verwendete Go Bibliothek für Persistenz mit Go.

  • Installation mit go get -u github.com/jinzhu/gorm

Gorm unterstützt verschiedene relationale Datenbanken:

  • PostgreSQL, MySQL, SQLite, Microsoft SQL Server
  • Die SQLite Anbindung braucht den gcc Compiler → unter Windows sehr langes Compilieren
  • Im Beispiel wird PostgreSQL verwendet
    • Installation des PostgreSQL-Treibers für go: go get -u github.com/lib/pq
    • Komfortable PostgreSQL Installer sind für verschiedene Betriebssysteme verfügbar, mein Favorit kommt von EnterpriseDB
      • Achtung, richtigen 32 oder 64 Bit Installer wählen!
    • pgadmin ist ein gutes Tool zum Management der PostgreSQL Installation, beim EDB Installer mit dabei
    • Ein paar Folien zur PostgreSQL Installation für GORM

Die Grundlagen der Arbeit mit GORM ist auf diesen Folien zur Gorm Programmierung beschrieben. Sie basieren auf dem GORM Tutorial von Jinzhu.

Lange Transaktionen

Wenn Daten von Benutzern geändert werden können, sind fast immer lange Transitionen notwendig (siehe Folie 25 auf den Gorm Programmierungsfolien und die Code Erläuterungen weiter unten). Damit wird verhindert, dass Tabellen der Datenbank längere Zeit für andere Benutzer gesperrt werden. Dabei besteht die Chance, dass die Daten in der Datenbank geändert wurden, während sie vom Benutzer bearbeitet wurden.Gorm hat keinen eingebauten Mechanismus, um konkurrierende Veränderungen zu erkennen. Jedoch lässt sich dies einfach mit den Gorm Callback Methoden implementieren.

Die Zusammenführung der Änderungen wird auf einer eigenen Seite beschrieben.

Gorm in der Beispielapplikation

Änderungshistorie speichern

Im Beispiel sollen alle Veränderungen an den Datensätzen der Bewerber nachvollziehbar gespeichert werden. Gorm stellt dazu standardmäßig passende Mechanismen bereit. Alle Datensätze, die ein Feld deleted_at  besitzen, werden bei Delete Operationen nicht gelöscht, sondern nur durch Einfügen eines Zeitstempels in dieses Feld als gelöscht markiert. Bei Select Operationen werden diese Datensätze ignoriert, wenn nicht ein spezieller Scope gesetzt wird.

Um dies zu nutzen werden die Daten für einen Bewerber (Applicant) in zwei Go structs aufgeteilt:

UML Klassenmodell für Applicant
UML Klassenmodell für Applicant
  • Applicant enthält einen eindeutigen semantikfreien Primärschlüssel sowie Informationen, wann dieses Objekt angelegt oder gelöscht wurde sowie eine Referenz auf die eigentlichen Daten. Gorm erzeugt daraus eine Tabelle applicant und eine Fremdschlüsselbeziehung, s. Zeile 37.
  • ApplicantData enthält eine Referenz auf das zugehörige Applicant Objekt sowie alle fachlich relevanten Daten. Außerdem ist die struct Model als anonymes Feld eingeschlossen. Gorm führt beide structs bei der Abbildung auf die Datenbank zusammen.
  • Model enthält die Information über Veränderungen: Wer und Wann.
Anwendungsfälle

Mit diesen Datenstrukturen können folgende Anwendungsfälle realisiert werden:

  1. Bewerber registriert sich: Neue Einträge in den Tabellen applicant und applicant_datawerden angelegt. Die IP des Bewerbers wird in das Feld UpdatedBy geschrieben (TODO).
  2. Bewerberdaten werden verändert (Einschreibung, Korrektur von Angaben, Eintrag von Testergebnissen): Es wird ein neuer applicant_data Datensatz mit den aktuellen Werten und der Identität des Bearbeiters angelegt und mit dem applicant Datensatz verknüpft. Der alte Datensatz bleibt erhalten, wird aber mit dem deleted_at Zeitstempel als gelöscht markiert. Alle als gelöscht markierten Datensätze können noch über den Fremdschlüssel applicant_id zugeordnet werden.
  3. Bewerber wird exmatrikuliert: applicant wird mit Zeitstempel als gelöscht markiert, in einem neuen applicant_data Record wird der Zeitstempel der Exmatrikulation und der Bearbeiter registriert. Alle anderen Daten bleiben erhalten.
  4. Exmatrikulation wird rückgängig gemacht: Der Löschungs-Zeitstempel bei applicant  wird auf null gesetzt, in einem neuen applicant_data  Record wird der Zeitstempel der Reimmatrikulation und der Bearbeiter registriert und die Exmatrikulationszeit auf null gesetzt.
Gorm Code für lange Transaktionen und Änderungshistorie

Beide Datenbankoperationen werden in einer Gorm Callback Methode des Typs Applicant zusammengefasst. Diese Methode wird jedesmal innerhalb einer Gorm-Transaktion aufgerufen, wenn ein Applicant Objekt gespeichert werden soll.

152 – 155
Applicant wurde als gelöscht markiert und wird deshalb mit tx.First() nicht gefunden (Anwendungsfall 4, s.o.). Daher wird mit tx.Unscoped() auch auf als gelöscht markierte Objekte zugegriffen.
156 – 160
Ein konkurrierender Update wird durch den Vergleich der Modifikations-Zeitstempel entdeckt. Wenn ein error Objekt zurückgegeben wird, bricht Gorm die Transaktion ab, führt ein Rollback durch und gibt den Fehler an den Aufrufer weiter.
161 – 165
Das ApplicantData Objekt wird durch Zuweisung eines neuen Models mit Id 0 für Gorm als neu modifiziert. Der aktuell angemeldete Benutzer, der diese Transaktion durchführt und in der Session gespeichert ist (siehe dort), wird aus dem alten Model in das neue Model übertragen. Da es in Go keine zu einem Thread bzw. einer Goroutine lokalen Variablen gibt, müssen die Informationen über den aktuellen Benutzer im alten Model an die Gorm Callback Methode weitergegeben werden.
166 – 169
Jetzt kann der alte applicant_data Eintrag in der Datenbank gelöscht werden (d.h. deleted_at wird gesetzt). Anschließend wird dem Applicant-Objekt die bearbeitete Kopie der Daten zugewiesen. Da err auf nil gesetzt wird, wird die Transaktion von Gorm fortgesetzt und das Applicant Objekt gespeichert. Das in der struct enthaltene ApplicantData Objekt wird automatisch mit gespeichert. Da seine Id auf 0 gesetzt wurde, wird ein neuer Eintrag in der applicant_data Tabelle angelegt und die Id auf den automatisch vergebenen neuen Wert gesetzt.

Dieser Vorgang kann mit pgadmin in der Datenbank genau nachvollzogen werden. Der alte Eintrag in Zeile 5 wurde durch den neuen Eintrag in Zeile 7 ersetzt. Die Änderung wurde von  Project office member(proj)  durchgeführt. Beide Einträge referenzieren den gleichen Applicant mit der Id 2456.

Changes in database
Änderungen in der Datenbank

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.