Actualisation parallèle des données dans l'API Web ASP.NET

Je veux vous dire comment nous avons organisé une mise à jour des données d'arrière-plan lors d'une demande à un service REST.

La tâche est la suivante: le système stocke les données utilisateur. Le service fonctionne de manière isolée et n'a pas d'accès direct aux bases de données contenant ces données. Pour que le service fonctionne, il est nécessaire d'avoir les noms et prénoms des utilisateurs dans sa base de données interne. Ils peuvent être obtenus auprès de l'identité de l'utilisateur actuel au moment de la demande. Obligatoire pour ajouter ou mettre à jour des noms lors de chaque demande. Il est conseillé de l'implémenter dans un thread séparé afin que ce travail n'affecte pas le temps d'exécution de la requête principale.

Affinement des tâches


Dans la base de données des services, nous stockons les noms et prénoms des utilisateurs. Les clients en ont besoin pour savoir qui a créé ou modifié la ressource.

Ces données ne sont pas significatives sur le plan systémique: s'il n'y a soudainement aucun enregistrement nécessaire dans la base de données, il ne se passera rien de mal. Par conséquent, nous ne voulons pas enregistrer notre travail d'arrière-plan dans ASP à l'aide de QueueBackgroundWorkItem, afin de compliquer la surcharge du domaine d'application.
Il est conseillé de résoudre le problème le plus simplement possible.

Pour ceux qui veulent en savoir plus sur les tâches d'arrière-plan dans ASP.NET, je vous conseille de lire un bon article à ce sujet.

Solution


Nous avons une classe DbRefresher qui ajoute ou modifie les données utilisateur dans la méthode RefreshAsync.

Nos contrôleurs utilisent l'attribut Authorize. Ajoutez notre descendant à cette classe et remplacez la méthode OnAuthorization:

public override void OnAuthorization(HttpActionContext actionContext) { base.OnAuthorization(actionContext); if (IsAuthorized(actionContext)) DbRefresher.RefreshAsync(actionContext.RequestContext.Principal) .ContinueWith(t => { LogFactory.For<AuthorizeAndRefreshUserAttribute>() .ErrorException("Error occured", t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); } 

DbRefresher.RefreshAsync est une méthode asynchrone qui renvoie un objet Task qui continue de s'exécuter dans un autre thread. La méthode OnAuthorize se ferme immédiatement sans attendre la fin de la tâche. En cas de plantage, un message d'erreur sera ajouté au journal.

C'est tout: il ne reste plus qu'à remplacer l'attribut Authorize dans les contrôleurs par le nom de notre nouvel attribut. Le nouvel attribut renvoie le contrôle immédiatement après avoir vérifié les droits de l'utilisateur actuel, après quoi le contrôleur commence son travail. La base de données sera mise à jour parallèlement à la préparation de la réponse par le contrôleur.

Test


Un test qui exécute plusieurs demandes de service simultanées aidera à identifier les problèmes possibles:

 [TestMethod] public void ConcurrentTest() { const int threadCount = 10; var tasks = new Task[threadCount]; for (int i = 0; i < threadCount; i++) { // DoOperations contains several CRUD operations on resources tasks[i] = Task.Factory.StartNew(DoOperations); } Task.WaitAll(tasks); } 

Les problèmes


Si pour le fonctionnement de certaines méthodes d'action du contrôleur, il est nécessaire que la base de données contienne évidemment les données de l'utilisateur actuel, cette approche n'est pas appropriée. Vous devrez utiliser un appel explicite à DbRefresher.RefreshAsync dans le corps de la méthode.

Il peut y avoir des problèmes lors de l'ajout d'un nouvel utilisateur à la base de données avec plusieurs demandes simultanées. Si une tentative est faite pour ajouter un utilisateur à une table avec une clé existante, vous devez intercepter l'exception de violation de clé primaire et arrêter de fonctionner. Ensuite, tout le travail de mise à jour des données utilisateur sera effectué par un seul thread.

Conclusion


Cette approche fonctionne avec succès dans l'un de nos services à Konfermit depuis plus d'un an.
Cela me semble simple et élégant. Il serait intéressant de connaître l'opinion de la communauté.

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


All Articles