
Damit der Benutzer keine Schmerzen durch unwiederbringlich verlorene Daten verspürt, sollten Sie über das sanfte Löschen nachdenken. Beim weichen Löschen wird der Datensatz nicht physisch aus der Datenbank gelöscht, sondern nur als gelöscht markiert. Dies erleichtert das Wiederherstellen von Daten durch Zurücksetzen des Flags.
Ich habe kürzlich das weiche Löschen in einem unserer REST-Services implementiert. Diejenigen, die sich für das interessieren, was ich getan habe, lade ich zur Katze ein.
Erforderlicher Eintrag
Die Debatte ĂĽber die Verwendung einer milden Entfernung ist sehr alt. Schauen Sie sich
hier und
hier die langen Holivars
an .
Am vernünftigsten ist die Position, nach der alles von der Situation abhängt. Es gibt Fälle, in denen eine weiche Löschung zweckmäßig oder sogar notwendig ist, und es gibt Fälle, in denen die Argumente der Gegner einer weichen Löschung Beachtung verdienen. Ein wichtiges Argument gegen das weiche Löschen ist übrigens die Antwort aus dem Jahr 2018: Wenn es sich um Benutzerkonten handelt, widerspricht das weiche Löschen der
DSGVO .
Wir haben entschieden, dass in unserem Service zur Speicherung von Dokumenten ein sanftes Löschen erforderlich ist.
RESTful Ansatz
Wenn wir das weiche Löschen in einem Dienst implementieren möchten, müssen wir verstehen, wie es aus Sicht der Schnittstelle aussehen sollte. Eine Suche im Internet ergab, dass eine typische Frage ist, ob DELETE {resource} wie zuvor verwendet werden soll oder ob es besser ist, die PATCH-Methode stattdessen mit einem Body zu verwenden, der so etwas wie
{status: 'deleted'} enthält. .
Hier ist die Meinung der Leute eindeutig: Sie müssen immer noch DELETE verwenden. Aus Sicht des Kunden ist das Löschen auch in Afrika ein Löschen. Nichts sollte sich ändern: Wenn eine Ressource gelöscht wird, kann nicht mehr darauf zugegriffen werden. Wenn der Client die Ressource löschen möchte, weiß er, dass die HTTP DELETE-Methode für diesen Zweck vorgesehen ist. Es ist nicht erforderlich, den Client genau zu beschreiben, wie der Dienst das Löschen implementiert.
Abgesehen davon war ich besorgt über die Frage, wie gelöschte Ressourcen wiederhergestellt werden können. Dieses Problem wird natürlich durch die Verwaltung der Datenbank gelöst. Ich möchte dies jedoch über die REST-API tun können. Und hier kommen wir in Konflikt. Es stellt sich heraus, dass der Client noch den Implementierungsdetails gewidmet sein muss?
Die lange Suche ergab keine Ergebnisse, bis ich auf
einen guten Artikel von Dan Yoder stieß . Der Artikel untersucht die Semantik verschiedener HTTP-Anforderungen und schlägt vor, Remote-Ressourcen anstelle des physischen Löschens in das
Archiv zu verschieben . Außerdem ist es schön, wenn DELETE einen Link zur archivierten Ressource zurückgibt. Der Benutzer kann die gelöschte Ressource jederzeit wiederherstellen, indem er eine POST-Anforderung an das Archiv sendet.
Design
Unser REST-Service basiert auf der ASP.NET-Web-API unter Verwendung des Entity Framework. Wie gesagt, ich lösche sanft eine Ressource namens document.
Zuerst müssen Sie die Spalten zur entsprechenden Tabelle hinzufügen. Als Flag verwende ich einen Zeitstempel namens Gelöscht. Wenn der Wert nicht NULL ist, wird die Ressource als gelöscht betrachtet. Darüber hinaus ist es hilfreich, Informationen darüber zu haben, wer die Ressource gelöscht hat.
ALTER TABLE Documents ADD Deleted datetime NULL, DeletedBy int NULL GO
Die Aktion LÖSCHEN in der Steuerung legt jetzt einfach die Werte dieser Felder fest, anstatt den Datensatz physisch zu löschen. Darüber hinaus gibt DELETE einen Text mit einem Standardverweis auf das archivierte Dokument zurück:
{ "links": { "archive": "documents/{id}/deleted" } }
In der Tat ist dies ein wichtiger Punkt: Der Link hilft dem Kunden zu verstehen, dass das Dokument nicht gelöscht, sondern
verschoben wird .
Der neue Controller fĂĽr archivierte Dokumente sollte die folgenden Methoden bereitstellen:
Dokumente abrufen / gelöscht | Ruft eine Sammlung aller gelöschten Dokumente ab |
Dokumente abrufen / {id} / gelöscht | Gibt das gelöschte Dokument zurück |
POST-Dokumente / {id} / gelöscht | Stellt gelöschtes Dokument wieder her; benötigt keinen Körper; gibt 201 Erstellt zurück |
Dokumente löschen / {id} / gelöscht | Löscht ein Dokument physisch |
Implementierung
Zunächst wollte ich meiner Datenbank zwei Ansichten hinzufügen:
CREATE VIEW DeletedDocuments AS SELECT * FROM Documents WHERE Deleted IS NOT NULL GO CREATE VIEW AvailableDocuments AS SELECT * FROM Documents WHERE Deleted IS NULL GO
Es schien mir, dass dies weniger Probleme bereiten würde: Anstatt Bedingungen im Code festzulegen, erhalte ich nur zwei verschiedene DbSet-Eigenschaften in meinem DB-Kontext. Zwar müssen zwei identische Entitäten im Modell vorhanden sein, dies ist jedoch die Eigenschaft von POCO-Objekten im Kontext von EF - jede Tabelle entspricht genau einer Entität.
Übrigens können Darstellungen in SQL für das Entity Framework in anderer Hinsicht nützlich sein: Mit ihrer Hilfe können Sie beispielsweise auf Tabellen aus einer anderen Datenbank verweisen, wenn Sie nicht mehrere DB-Kontexte erstellen möchten.
In meinem Fall hat die Nummer mit den Ansichten jedoch nicht bestanden. Während der Autorisierung müssen Sie mit allen Dokumenten arbeiten, da Benutzer die gleichen Rechte zum Löschen von Dokumenten haben wie vorhandene.
Aus diesem Grund habe ich beschlossen, nur ein DbSet-Dokument in DbContext und im Code zu haben, wenn ich herausfinde, was genau im Moment benötigt wird:
var availableDocuments = DbContext.Documents.Where(d => d.Deleted == null); var deletedDocuments = DbContext.Documents.Where(d => d.Deleted != null); var allDocuments = DbContext.Documents;
Verwandte Ressourcen
Ein Dokument ist eine Ressource, der andere Ressourcen zugeordnet sind. Zum Beispiel haben wir einen Dokumentalias. Das heißt, Sie können ein Dokument nicht nur über den Pfad
documents / {id} abrufen, sondern auch ĂĽber den Pfad
documents / {alias} , wobei
alias eine eindeutige Zeichenfolge ist.
Nach dem Löschen eines Dokuments sollten alle damit verbundenen Aliase "unsichtbar" werden: Wenn der Client früher eine Liste aller Aliase mit GET-Dokumenten / Aliasen erhalten hat, werden nach dem Löschen des Dokuments seine Aliase aus der Liste ausgeblendet.
Aber sie blieben in der Datenbank! Wir möchten die Möglichkeit bieten, das Dokument in dem Zustand wiederherzustellen, in dem es gelöscht wurde. Dies kann zu Verwirrung für den Client führen: Er versucht, einen neuen Alias ​​für ein anderes Dokument hinzuzufügen, die von
GET-Dokumenten / Aliasnamen zurückgegebene Liste enthält keine solche Zeile, und der Dienst weigert sich dennoch, sie hinzuzufügen.
Ich denke nicht, dass dies ein ernstes Problem ist. Wenn Sie es dennoch lösen müssen, können Sie die Endpunkt-
GET-Dokumente / gelöschten / Aliase hinzufügen. Dann passt alles zusammen: Der Dienst kann keinen Alias ​​hinzufügen, da ein solcher Wert bereits vom entfernten Dokument verwendet wird.
Es kann sich die Frage stellen: Lohnt es sich, einen Alias ​​aus der Liste zu werfen, die von
Dokumenten / Aliasnamen zurückgegeben wird ? Lass sie bleiben! Ich denke nicht, dass eine solche Entscheidung richtig wäre. Dann stellt sich heraus, dass die Liste der Aliase fehlerhafte Links enthält, da der Dienst 404 Not Found zurückgibt, wenn der Client versucht, das gelöschte Dokument per Alias ​​abzurufen. Wenn es um die untergeordneten Ressourcen geht, die dem Dokument zugeordnet sind, sollte das Verhalten genau so sein, als ob wir das Dokument physisch löschen würden.
Archivreinigung
Das weiche Löschen bietet neben der einfachen Wiederherstellung von Daten mehrere weitere Vorteile. Der Löschvorgang in relationalen Datenbanken ist ein teurer Vorgang. Und wenn selbst das Löschen eines Datensatzes zum kaskadierenden Löschen von Datensätzen in anderen Tabellen führt, ist dies mit Deadlocks behaftet. Daher ist die weiche Entfernung schneller und zuverlässiger als die physische Entfernung.
Es gibt jedoch einen wesentlichen Nachteil. Die Basis beginnt zu wachsen.
Daher sollten Sie sich im Endstadium um die automatische Reinigung des Archivs kümmern. Sie können die Basis natürlich manuell reinigen, aber es ist besser, diesen Prozess zu automatisieren. Wenn wir das Ablaufdatum eines Remote-Objekts direkt festlegen, z. B. 30 Tage, kann der Client die Archivseite anzeigen, auf der die Elemente hervorgehoben werden, deren Lebensdauer sich dem Ende nähert.
Meine Hände haben diese Aufgabe noch nicht erreicht. Wir planen, unserem Task-System eine Task hinzuzufügen, die einmal täglich eine einfache SQL-Abfrage ausführt, mit der alle fehlerhaften Objekte aus dem Archiv entfernt werden. Als Parameter sollte die Aufgabe ein Ablaufdatum haben. Es muss sichergestellt werden, dass der aktuelle Wert dieses Parameters an einem Ort gespeichert wird. Anschließend kann im Dienst eine Methode implementiert werden, die diesen Wert an den Client zurückgibt.