Wie kann man Daten am besten historisieren?
Viele Wege führen nach Rom – ich möchte an dieser Stelle einen beschreiben.
Möglichkeit 1: Man nutzt Frameworks in der Zielsprache. Zu erwähnen wäre Hibernate Envers oder Entity Framework Auto-History bzw. Audit.NET.
Möglichkeit 2: Man lässt das die Persistenz erledigen. Datenbank wie SQL Server unterstützen Temporale Tabellen. PostgreSQL hat Plugins – aber man ist auch gut beraten, selber Hand anzulegen – warum, zeige ich gleich. Temporal Tables sind im SQL:2011 Standard zu finden.
Folgende Überlegungen haben mich zu Möglichkeit 2 geführt: Komplexität. Hibernate bereitet sehr oft Kopf weh. Ja – mag es mit Unwissenheit bzw. Fehlbedienung zu tun haben. Trotzdem will man sich keinen Datenverlust leisten durch „Fehlkonfigurationen“ und „Vergessene Annotationen“. Betrachtet man Separation of Concerns (SoC), so sagt mir mein Bauchgefühl, dass Historisierung eher bei der Persistenz zu sehen ist.
Die Anforderungen:
- In einem Service ändert meist ein Request Daten. Ich will wissen, welcher Request welchen Datensatz geändert hat.
- Der Zeitstempel des Requests soll sich über mehrere Tabellen hinwegziehen. Somit kann ich sagen: Wie war der Datenstand vor und nach dem Request
- Transaktionssicherheit
- Einfach zu Reviewen
Die erste Frage war natürlich: Historisierung in einer Tabelle oder in zwei Tabellen.
Für eine Tabelle spricht: Einfach, da weniger Tabellen.
Für zwei Tabellen spricht:
- Indizes können für beide Tabellen anders gestaltet werden – die Art der Abfragen sind auch anders
- Partitioning oder andere Optimierungen kann man getrennt vornehmen
- Hibernate fühlt sich auch wohler, da Hibernate unbedingt einen eindeutigen Identifier braucht (sonst bekommt man einen „Multiple Row“ Fehler)
Dazu habe ich ein Beispiel konstruiert:
Points of Interest:
- Jede Tabelle halt folgende Spalten:
- changed_by_request_id: Welcher Request hat den Datensatz in den aktuellen Zustand gebracht?
- deleted_by_request_id: Soft-delete finde ich persönlich eine gute Variante, weil es vor Datenverlust schützt. Man kann bei zu viel „Müll“ noch immer mit einem simplen Query aufräumen.
- History Tabellen haben immer folgende Spalten:
- history_valid_from: Seit wann ist der Datensatz gültig?
- history_valid_to: Bis wann ist der Datensatz gültig? Zu beachten: PostgreSQL hat auch Range Types, die nochmal gesondert indiziert werden können. Allerdings wird die Menge an Datensätzen im Beispiel über die Id so stark eingeschränkt, dass ich keine Notwendigkeit sehe.
- Trigger „erkennen“ Updates und fügen eine Kopie in die Historie Tabelle ein. Einzige Challenge: Man muss natürlich bei Soft-Deletes aufpassen. Annahme: Es wird nur gelöscht – aber die Daten nicht mehr geändert. Trigger sind auch Concurrency-sicher.
Points of Interest:
- Ein Datensatz mit der Id = 123 kann in der History mehrfach vorkommen. Es ist daher wichtig, dass man einen Filter anlegt, der nicht erwünschte Datensätze rausfiltern. Weil zum Zeitpunkt X hatte Id = 123 auch nur einen Zustand …
- Filter müssen auch auf alle Abhängigkeiten angewandt werden
- Filter werden pro Session angewandt
Fazit: Mir gefällt die Methode sehr gut und man kann sie definitiv für viele Bereiche empfehlen.