Suppression logicielle dans l'API REST

image

Pour que l'utilisateur ne ressente pas la douleur des données irrémédiablement perdues, il vaut la peine de penser à une suppression douce. Avec la suppression logicielle, l'enregistrement n'est pas physiquement supprimé de la base de données, mais uniquement marqué comme supprimé. Cela facilite la récupération des données en réinitialisant l'indicateur.

J'ai récemment implémenté la suppression logicielle dans l'un de nos services REST. Ceux qui sont intéressés par ce que j'ai fait, je vous invite au chat.

Entrée obligatoire


Le débat sur l'opportunité d'un retrait léger est très ancien. Regardez les longs holivars ici et ici .

Le plus raisonnable est la position selon laquelle tout dépend de la situation. Il y a des cas où la suppression douce est pratique ou même nécessaire; il y a des cas où les arguments des opposants à la suppression douce méritent attention. Soit dit en passant, un argument important contre la suppression logicielle est la réponse venue de 2018: si nous parlons de comptes d'utilisateurs, la suppression logicielle est contraire au RGPD .

Nous avons décidé que dans notre service de stockage de documents, une suppression logicielle était nécessaire.

Approche RESTful


Si nous voulons implémenter la suppression logicielle dans un service, nous devons comprendre à quoi il devrait ressembler du point de vue de l'interface. Une recherche sur Internet a montré qu'une question typique des gens est de savoir s'il faut utiliser DELETE {ressource} comme avant, ou est-il préférable d'utiliser la méthode PATCH à la place avec un corps qui inclut quelque chose comme {status: 'supprimé'} .

Ici, l'opinion du peuple est sans équivoque: il faut utiliser DELETE comme avant. Du point de vue du client, la suppression est également une suppression en Afrique. Rien ne doit changer: si une ressource est supprimée, elle devient inaccessible; si le client souhaite supprimer la ressource, il sait que la méthode HTTP DELETE est à cet effet. Il n'est pas nécessaire de dédier le client aux détails de la façon exacte dont le service implémente la suppression.

Mais en plus de cela, j'étais préoccupé par la question de savoir comment récupérer les ressources supprimées. Bien sûr, ce problème est résolu en administrant la base de données. Cependant, j'aimerais pouvoir le faire via l'API REST. Et ici, nous entrons en conflit. Il s'avère que le client doit encore être dédié aux détails de mise en œuvre?

La recherche n'a donné aucun résultat pendant longtemps, jusqu'à ce que je tombe sur un bon article de Dan Yoder . L'article examine la sémantique des différentes requêtes HTTP et suggère qu'au lieu de la suppression physique, déplacez les ressources distantes vers l' archive . De plus, ce sera bien si DELETE renvoie un lien vers la ressource archivée. L'utilisateur peut toujours restaurer la ressource supprimée en envoyant une requête POST à ​​l'archive.

La conception


Notre service REST est construit sur l'API Web ASP.NET en utilisant Entity Framework. Comme je l'ai dit, je fais une suppression douce pour une ressource appelée document.

Donc, vous devez d'abord ajouter les colonnes au tableau correspondant. Comme indicateur, j'utilise un horodatage appelé Supprimé. Si la valeur n'est pas NULL, la ressource est considérée comme supprimée. De plus, il est utile d'avoir des informations sur qui a supprimé la ressource.

ALTER TABLE Documents ADD Deleted datetime NULL, DeletedBy int NULL GO 

L'action DELETE dans le contrôleur va maintenant simplement définir les valeurs de ces champs au lieu de supprimer physiquement l'enregistrement. De plus, DELETE renverra un corps avec une référence standard au document archivé:

 { "links": { "archive": "documents/{id}/deleted" } } 

En fait, c'est un point important: le lien aide le client à comprendre que le document n'est pas supprimé, mais déplacé .

Le nouveau contrôleur pour les documents archivés doit fournir les méthodes suivantes:
GET documents / suppriméObtient une collection de tous les documents supprimés
GET documents / {id} / suppriméRenvoie le document supprimé
Documents POST / {id} / supprimésRécupère le document supprimé;
ne nécessite pas de corps; renvoie 201 Créé
SUPPRIMER les documents / {id} / supprimésSupprime physiquement un document

Implémentation


Au départ, j'avais prévu d'ajouter deux vues à ma base de données:

 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 

Il me semblait que cela poserait moins de problèmes: au lieu de définir des conditions dans le code, j'obtiens simplement deux propriétés DbSet différentes dans mon contexte de base de données. Certes, vous devez avoir deux entités identiques dans le modèle, mais telle est la propriété des objets POCO dans le contexte d'EF - chaque table correspond exactement à une entité.

Soit dit en passant, les vues en SQL peuvent être utiles pour Entity Framework à d'autres égards: avec leur aide, par exemple, vous pouvez vous référer aux tables d'une autre base de données si vous ne souhaitez pas créer plusieurs contextes de base de données.

Cependant, dans mon cas, le nombre avec les vues n'a pas passé. Pendant l'autorisation, vous devez travailler avec tous les documents, car les utilisateurs ont les mêmes droits sur les documents supprimés que les documents existants.

Par conséquent, j'ai décidé de n'avoir qu'un seul document DbSet dans DbContext, et dans le code chaque fois que je trouve exactement ce qui est nécessaire pour le moment:

 var availableDocuments = DbContext.Documents.Where(d => d.Deleted == null); var deletedDocuments = DbContext.Documents.Where(d => d.Deleted != null); var allDocuments = DbContext.Documents; 

Ressources connexes


Un document est une ressource à laquelle d'autres ressources sont associées. Par exemple, nous avons un alias de document. Autrement dit, vous pouvez obtenir un document non seulement par le chemin documents / {id} , mais également par le chemin documents / {alias} , où alias est une chaîne unique.

Après la suppression d'un document, tous les alias qui lui sont associés doivent devenir «invisibles»: si plus tôt le client a reçu une liste de tous les alias utilisant des documents / alias GET, puis après la suppression du document, ses alias de la liste disparaîtront.

Mais ils sont restés dans la base de données! Nous voulons offrir la possibilité de restaurer le document dans l'état dans lequel il a été supprimé. Cela peut être source de confusion pour le client: il essaie d'ajouter un nouvel alias pour un autre document, la liste renvoyée par les documents / alias GET ne contient pas une telle ligne, et le service refuse néanmoins de l'ajouter.

Je ne pense pas que ce soit un problème grave. Néanmoins, si vous avez besoin de le résoudre, vous pouvez ajouter les documents d' extrémité GET / supprimé / alias . Ensuite, tout se met en place: le service ne peut pas ajouter d'alias, car une telle valeur est déjà utilisée par le document distant.

La question peut se poser: vaut-il la peine de jeter un alias de la liste renvoyée par les documents / alias ? Laissez-les rester! Je ne pense pas qu'une telle décision serait correcte. Il s'avère ensuite que la liste des alias contiendra des liens rompus, car le service renverra 404 Not Found si le client essaie d'obtenir le document supprimé par alias. S'il s'agit des ressources enfants associées au document, le comportement doit être exactement le même que si nous supprimions le document physiquement.

Nettoyage d'archives


La suppression logicielle, en plus de pouvoir récupérer facilement des données, présente plusieurs autres avantages. L'opération de suppression dans les bases de données relationnelles est une opération coûteuse. Et si même la suppression d'un enregistrement entraîne la suppression en cascade des enregistrements dans d'autres tables, cela est lourd de blocages. Par conséquent, le retrait progressif est plus rapide et plus fiable que le retrait physique.

Mais il y a un inconvénient important. La base commence à se développer.

Par conséquent, au stade final, vous devez vous occuper du nettoyage automatique de l'archive. Vous pouvez bien sûr nettoyer la base manuellement, mais il est préférable d'automatiser ce processus. Si nous définissons directement la date d'expiration d'un objet distant, disons 30 jours, le client peut afficher la page d'archive sur laquelle les éléments dont la durée de vie est proche de la fin seront mis en évidence.

Mes mains n'ont pas encore atteint cette tâche. Nous prévoyons d'ajouter à notre système de tâches une tâche qui, une fois par jour, exécutera une simple requête SQL qui supprime tous les objets malveillants de l'archive. En tant que paramètre, la tâche doit prendre une date d'expiration. Il sera nécessaire de s'assurer que la valeur actuelle de ce paramètre est stockée quelque part en un seul endroit. Il sera alors possible d'implémenter une méthode dans le service qui renvoie cette valeur au client.

Source: https://habr.com/ru/post/fr440886/


All Articles