Fermer

novembre 1, 2022

Premiers pas avec les services de travail


Découvrez ce que sont les services de travail et où ils sont couramment utilisés, puis nous créerons un projet de service de travail dans .NET 6 à partir de zéro pour en voir un en pratique.

Dans de nombreuses situations, vous devez effectuer des tâches en arrière-plan telles que des vérifications d’état et le nettoyage d’anciennes données. Pour faciliter ces tâches, nous pouvons créer un service de travail qui s’exécutera à un intervalle de temps et effectuera les charges de travail pour lesquelles il a été configuré.

Découvrez dans cet article ce que sont les services de travail et comment les implémenter dans .NET 6.

Que sont les services aux travailleurs ?

Les services de travail sont un type d’application qui peut être configuré pour s’exécuter en arrière-plan selon l’intervalle d’exécution prédéfini, qui peut être à court ou à long terme.

Un projet de service de travail dans .NET est créé à l’aide d’un modèle qui fournit des ressources utiles, telles que l’hôte responsable de la maintenance de la durée de vie de l’application. De plus, l’hôte fournit également des fonctions telles que l’enregistrement, l’injection de dépendances et la configuration.

Auparavant appelé Windows Service, il était limité à Windows uniquement. Mais avec la création de Worker Services, cette limitation n’existe plus : vous pouvez développer des services d’arrière-plan multiplateformes.

Où pouvons-nous utiliser les services des travailleurs ?

L’utilisation des services de travail est très courante, car elle permet la planification des tâches afin que nous n’ayons pas à nous soucier de notifier manuellement un service, nous pouvons créer un travailleur pour travailler pour nous, nous informant simplement de son début et de sa fin d’exécution .

Vous trouverez ci-dessous quelques scénarios dans lesquels l’utilisation de nœuds de calcul est la meilleure option :

  • Envoi de messages/événements
  • Réagir aux modifications d’une entité
  • Manipulation d’un magasin de données
  • Nettoyer les données inutiles
  • Environnements d’égalisation
  • Services de résilience

Ce ne sont là que quelques exemples d’utilisation de travailleurs, mais il existe de nombreux autres scénarios dans lesquels nous pouvons utiliser des travailleurs pour automatiser des processus.

Création d’un service de travail

Ensuite, nous allons créer un service de travail dans .NET 6 à partir de zéro. Ce travailleur s’exécutera toutes les 30 secondes et, lorsqu’il démarrera, il récupérera les données des utilisateurs à partir d’une API externe et vérifiera si ces utilisateurs existent dans la base de données locale. S’ils n’existent pas, ils seront insérés ; s’ils existent déjà, il affichera uniquement un journal indiquant qu’aucun nouvel enregistrement n’a été trouvé à ajouter. Tous les traitements seront affichés dans les journaux, au début, au milieu et à la fin.

Vous pouvez accéder à l’intégralité code source du projet sur ce lien.

Conditions préalables

  • Visual Studio 2022
  • SDK .NET 6

Dépendances du projet

Vous devez ajouter les dépendances du projet, soit directement dans le code du projet « UserEqualizerWorkerService.csproj », soit en téléchargeant les packages NuGet :

<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />

Création du projet

Pour créer le projet :

  • Ouvrez Visual Studio 2022
  • Créer un nouveau projet
  • Tapez « worker » dans la barre de recherche, et choisissez l’option « C# Worker Service »
  • Cliquez sur Suivant
  • Nommez le projet (« UserEqualizerWorkerService » est suggéré)
  • Cliquez sur Suivant
  • Choisissez « .NET 6 .0 (support à long terme) »
  • Créer

Création des entités

Dans ce tutoriel, de fausses données provenant d’une API publique seront utilisées. Le site officiel est Espace réservé Json.

Ensuite, créons deux entités principales : l’entité chargée de recevoir les données de l’API (PlaceHolderUser) et l’entité qui représentera la table de la base de données (User), que nous créerons plus tard.

Alors, créez un nouveau dossier appelé « Modèles » et à l’intérieur, créez un nouveau dossier « PlaceHolder ». À l’intérieur de cela, ajoutez la classe ci-dessous :

PlaceHolderUser

using System.Text.Json.Serialization;

namespace UserEqualizerWorkerService.PlaceHolder.Models;

public class PlaceHolderUser
{
    [JsonPropertyName("id")]
    public long? Id { get; set; }

    [JsonPropertyName("name")]
    public string? Name { get; set; }

    [JsonPropertyName("username")]
    public string? Username { get; set; }

    [JsonPropertyName("email")]
    public string? Email { get; set; }

    [JsonPropertyName("address")]
    public Address? Address { get; set; }

    [JsonPropertyName("phone")]
    public string? Phone { get; set; }

    [JsonPropertyName("website")]
    public string? Website { get; set; }

    [JsonPropertyName("company")]
    public Company? Company { get; set; }
}

public class Address
{
    [JsonPropertyName("street")]
    public string? Street { get; set; }

    [JsonPropertyName("suite")]
    public string? Suite { get; set; }

    [JsonPropertyName("city")]
    public string? City { get; set; }

    [JsonPropertyName("zipcode")]
    public string? Zipcode { get; set; }

    [JsonPropertyName("geo")]
    public Geo? Geo { get; set; }
}

public class Geo
{
    [JsonPropertyName("lat")]
    public string? Lat { get; set; }

    [JsonPropertyName("lng")]
    public string? Lng { get; set; }
}

public class Company
{
    [JsonPropertyName("name")]
    public string? Name { get; set; }

    [JsonPropertyName("catchphrase")]
    public string? CatchPhrase { get; set; }

    [JsonPropertyName("bs")]
    public string? Bs { get; set; }
}

Maintenant, dans le dossier « Modèles », ajoutez la classe ci-dessous :

Utilisateur

namespace UserEqualizerWorkerService.Models;

public class User
{
    public long? Id { get; set; }
    public string? Name { get; set; }
    public string? Username { get; set; }
    public string? Email { get; set; }
    public Address? Address { get; set; }
    public string? Phone { get; set; }
    public string? Website { get; set; }
    public Company? Company { get; set; }
}

public class Address
{
    public long? Id { get; set; }
    public string? Street { get; set; }
    public string? Suite { get; set; }
    public string? City { get; set; }
    public string? Zipcode { get; set; }
    public Geo? Geo { get; set; }
}

public class Geo
{
    public long? Id { get; set; }
    public string? Lat { get; set; }
    public string? Lng { get; set; }
}

public class Company
{
    public long? Id { get; set; }
    public string? Name { get; set; }
    public string? CatchPhrase { get; set; }
    public string? Bs { get; set; }
}

Création du contexte de données

La classe de contexte sera utilisée pour communiquer avec la base de données via les fonctionnalités Entity Framework Core.

Créez donc un nouveau dossier appelé « Data » et créez à l’intérieur la classe ci-dessous :

UserDbContext

using Microsoft.EntityFrameworkCore;
using UserEqualizerWorkerService.Models;

namespace UserEqualizerWorkerService.Data;

public class UserDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder options) => 
        options.UseSqlite("DataSource = userBD; Cache=Shared");

    public DbSet<User> Users { get; set; }
    public DbSet<Address> Address { get; set; }
    public DbSet<Geo> Geo { get; set; }
    public DbSet<Company> Company { get; set; }
}

Implémentation de la communication avec l’API externe

Ensuite, créons la classe qui récupérera la liste des utilisateurs dans la fausse API. Donc, dans le dossier « Data », créez un nouveau dossier appelé « Api » et à l’intérieur, créez la classe ci-dessous :

PlaceHolderClient

using Newtonsoft.Json;
using UserEqualizerWorkerService.PlaceHolder.Models;

namespace UserEqualizerWorkerService.Data.Api
{
    public class PlaceHolderClient
    {
        private readonly HttpClient _httpClient;

        public PlaceHolderClient(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public async Task<List<PlaceHolderUser>> GetPlaceHolderUsers()
        {
            var uri = "/users";

            var responseString = await _httpClient.GetStringAsync(uri);

            var placeHolederUsers = JsonConvert.DeserializeObject<List<PlaceHolderUser>>(responseString);

            return placeHolederUsers ?? new List<PlaceHolderUser>();
        }
    }
}

Création du service

La classe de service contiendra les règles métier du travailleur. Il accédera à la couche API, obtiendra les utilisateurs dans l’API externe, vérifiera si ces utilisateurs existent déjà dans la base de données et s’ils ne le font pas, ils seront ajoutés et ainsi les deux places seront égalisées.

Donc, créez un nouveau dossier appelé « Services » et à l’intérieur d’un nouveau dossier « v1 » et à l’intérieur ajoutez la classe ci-dessous :

UserEqualizerService

using UserEqualizerWorkerService.Data;
using UserEqualizerWorkerService.Data.Api;
using UserEqualizerWorkerService.Models;
using UserEqualizerWorkerService.PlaceHolder.Models;

namespace UserEqualizerWorkerService.Services.v1;

public class UserEqualizerService
{
    private readonly ILogger<UserEqualizerService> _logger;
    private readonly PlaceHolderClient _client;
    private readonly UserDbContext _context;

    public UserEqualizerService(ILogger<UserEqualizerService> logger, PlaceHolderClient client, UserDbContext context)
    {
        _logger = logger;
        _client = client;
        _context = context;
    }

    public virtual async Task<bool> ExecuteService()
    {
        _logger.LogInformation("Starting process");

        var placeHolderUsers = await _client.GetPlaceHolderUsers();

        var result = await EqualizeUsers(placeHolderUsers);

        _logger.LogInformation("Ending process");

        return result;
    }

    public virtual async Task<bool> EqualizeUsers(List<PlaceHolderUser> phUsers)
    {
        var users = _context.Users.ToList();

        var newUsers = phUsers.Where(x => !users.Any(x1 => x1.Username != x.Username && x1.Email != x.Email)).ToList();

        if (!newUsers.Any())
        {
            _logger.LogInformation("No new users to add");
            return true;
        }

        _logger.LogInformation($"Found {newUsers.Count} new users");

        return await SaveNewUsers(newUsers);
    }

    public virtual async Task<bool> SaveNewUsers(List<PlaceHolderUser> newUsers)
    {
        try
        {
            _logger.LogInformation("Saving new users");

            var newUsersEntity = newUsers.Select(x => new User
            {
                Id = x.Id,
                Name = x.Name,
                Username = x.Username,
                Email = x.Email,
                Address = new Models.Address
                {
                    Id = x.Id,
                    City = x.Address?.City,
                    Geo = new Models.Geo { Id = x.Id, Lat = x.Address?.Geo?.Lat, Lng = x.Address?.Geo?.Lng },
                    Street = x.Address?.Street,
                    Suite = x.Address?.Suite,
                    Zipcode = x.Address?.Zipcode,
                },
                Phone = x.Phone,
                Website = x.Website,
                Company = new Models.Company { Id = x.Id, Name = x.Company?.Name, CatchPhrase = x.Company?.CatchPhrase, Bs = x.Company?.Bs }
            }).ToList();

            await _context.Users.AddRangeAsync(newUsersEntity);
            await _context.Address.AddRangeAsync(newUsersEntity.Select(x => x.Address).ToList());
            await _context.Geo.AddRangeAsync(newUsersEntity.Select(x => x.Address.Geo).ToList());
            await _context.Company.AddRangeAsync(newUsersEntity.Select(x => x.Company).ToList());
            await _context.SaveChangesAsync();

            _logger.LogInformation("New users successfully saved");
            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError($"Error saving new users - {ex.Message}");
            return false;
        }
    }
}

Ajout des configurations

Dans l’archive Program.cs, au-dessus du paramètre « services.AddHostedService(); » ajouter le code suivant :

        services.AddTransient<UserDbContext>();
        services.AddDbContext<UserDbContext>();
        services.AddTransient<UserEqualizerService>();
        services.AddHttpClient<PlaceHolderClient>(client =>
        {
            client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
        });

Le code ci-dessus ajoute les paramètres de classe UserDbContext, UserEqualizerService, PlaceHolderClient.

Pour la classe PlaceHolderClient, une BaseAddress est créée qui reçoit l’URL de l’API JsonPlaceHolder externe. Ainsi, lorsque nous invoquons la méthode users get dans la classe PlaceHolderClient, nous transmettons simplement la fin de l’URL, dans ce cas « /users ».

Enfin, remplacez le contenu du fichier « Worker.cs » par le code suivant :

using UserEqualizerWorkerService.Services.v1;

namespace UserEqualizerWorkerService
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;
        private readonly UserEqualizerService _userService;

        public Worker(ILogger<Worker> logger, UserEqualizerService userService)
        {
            _logger = logger;
            _userService = userService;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Starting service...");

            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

                var result = await _userService.ExecuteService();

                string resultLogMessage = result ? "Successfully processed" : "Processed with failure";

                _logger.LogInformation(resultLogMessage);

                _logger.LogInformation("Stoping service...");

                await Task.Delay(30000, stoppingToken);
            }
        }
    }
}

Le code ci-dessus exécute la méthode de la classe de service que nous venons de créer. Si une erreur se produit dans le traitement, un message d’erreur sera consigné ; sinon, un message de réussite.

La méthode « wait Task.Delay(30000, shutdownToken) » est utilisée pour définir le délai d’attente pour chaque exécution de travailleur. La valeur attendue est en millisecondes, donc 30000 millisecondes équivaut à une demi-minute, ce qui signifie que notre travailleur s’exécutera toutes les 30 secondes. Cette valeur est configurable et dépendra de chaque scénario.

Exécution des commandes EF Core

Avant d’exécuter l’application et de voir comment cela fonctionne en pratique, nous devons créer la base de données avec les commandes EF Core.

Pour exécuter les commandes EF Core, le CLI .NET les outils doivent être installés. Sinon, les commandes entraîneront une erreur.

La première commande créera une migration appelée InitialModel et la seconde demandera à EF de créer une base de données et un schéma à partir de la migration.

Plus d’informations sur les migrations sont disponibles dans Documentation officielle de Microsoft.

Vous pouvez exécuter les commandes ci-dessous dans un terminal racine du projet.

  • dotnet ef migrations add InitialModel
  • dotnet ef database update

Vous pouvez également exécuter les commandes suivantes à partir de la console du gestionnaire de packages dans Visual Studio :

  • Add-Migration InitialModel
  • Update-Database

Exécuter le travailleur

Si vous avez suivi toutes les étapes ci-dessus, le nœud de calcul est prêt à s’exécuter.

Lors de la première exécution, le travailleur récupère la liste des utilisateurs de l’API externe et vérifie si ces utilisateurs existent dans la base de données. Comme la base de données est encore vide, ils seront insérés, égalisant les deux environnements.

L’image ci-dessous montre les journaux d’exécution du début à la fin.

Exécution de la première ouvrière

Lors de la deuxième exécution, le travailleur vérifiera si les utilisateurs existent déjà, puis enregistrera simplement un message indiquant qu’il n’y avait pas de nouveaux utilisateurs à ajouter.

Exécution d'un deuxième travailleur

Conclusion

Les services de travail sont un excellent outil pour automatiser les processus – après tout, ils peuvent faire le travail sans avoir besoin d’un déclencheur. Définissez simplement un temps de pause et il fera tout le reste lui-même.

Dans cet article, nous avons vu une introduction aux services de travail et développé une application de travail à l’aide du modèle de service de travail disponible dans .NET 6, qui utilise des approches très courantes dans la vie quotidienne telles que l’enregistrement dans une base de données et les requêtes à une API externe. Si vous souhaitez approfondir les services de travail, je vous suggère de lire la documentation officielle de Microsoft sur Ouvriers.




Source link

novembre 1, 2022