Services de grain

Les services de grain sont des services partitionnés accessibles à distance pour prendre en charge la fonctionnalité grains. Chaque instance d’un service de grain est responsable d’un ensemble de grains, et ces grains peuvent obtenir une référence au service de grain qui est actuellement en charge de leur maintenance à l’aide d’un GrainServiceClient.

Les services de grain existent pour prendre en charge les cas où la responsabilité de la maintenance des grains doit être répartie à travers le cluster Orleans. Par exemple, les rappels Orleans sont implémentés à l’aide de services de grain : chaque silo est responsable de la gestion des opérations de rappel pour un sous-ensemble de grains et de la notification de ces grains quand leurs rappels sont déclenchés.

Les services de grain sont configurés sur des silos et sont initialisés au démarrage du silo, avant la fin de l’initialisation de ce dernier. Ils ne sont pas collectés lorsqu’ils sont inactifs et, à la place, ils ont plutôt des durées de vie qui s’étendent pendant la durée de vie du silo lui-même.

Créer un GrainService

Un GrainService est un grain spécial qui n’a pas d’identité stable et qui s’exécute dans chaque silo du démarrage jusqu’à l’arrêt. L’implémentation d’une interface IGrainService passe par plusieurs étapes.

  1. Définissez l’interface de communication du service de grain. La génération de l’interface d’un GrainService obéit aux mêmes principes que ceux qui s’appliquent à la génération de l’interface d’un grain.

    public interface IDataService : IGrainService
    {
        Task MyMethod();
    }
    
  2. Créez le service de grain DataService. Il est à noter que vous pouvez aussi injecter un IGrainFactory pour vous permettre d’effectuer des appels de grain à partir de votre GrainService.

    [Reentrant]
    public class DataService : GrainService, IDataService
    {
        readonly IGrainFactory _grainFactory;
    
        public DataService(
            IServiceProvider services,
            GrainId id,
            Silo silo,
            ILoggerFactory loggerFactory,
            IGrainFactory grainFactory)
            : base(id, silo, loggerFactory)
        {
            _grainFactory = grainFactory;
        }
    
        public override Task Init(IServiceProvider serviceProvider) =>
            base.Init(serviceProvider);
    
        public override Task Start() => base.Start();
    
        public override Task Stop() => base.Stop();
    
        public Task MyMethod()
        {
            // TODO: custom logic here.
            return Task.CompletedTask;
        }
    }
    
    [Reentrant]
    public class DataService : GrainService, IDataService
    {
        readonly IGrainFactory _grainFactory;
    
        public DataService(
            IServiceProvider services,
            IGrainIdentity id,
            Silo silo,
            ILoggerFactory loggerFactory,
            IGrainFactory grainFactory)
            : base(id, silo, loggerFactory)
        {
            _grainFactory = grainFactory;
        }
    
        public override Task Init(IServiceProvider serviceProvider) =>
            base.Init(serviceProvider);
    
        public override Task Start() => base.Start();
    
        public override Task Stop() => base.Stop();
    
        public Task MyMethod()
        {
            // TODO: custom logic here.
            return Task.CompletedTask;
        }
    }
    
  3. Créez une interface pour que le GrainServiceClient<TGrainService>GrainServiceClient soit utilisé par d’autres grains pour se connecter au GrainService.

    public interface IDataServiceClient : IGrainServiceClient<IDataService>, IDataService
    {
    }
    
  1. Créez le client de service de grain. Habituellement, les clients jouent le rôle de proxy pour les services de grain qu’ils ciblent. Vous ajoutez donc généralement une méthode pour chaque méthode sur le service cible. Ces méthodes doivent obtenir une référence au service de grain qu’elles ciblent afin de pouvoir l’appeler. La classe de base GrainServiceClient<T> fournit plusieurs surcharges de la méthode GetGrainService qui peut retourner une référence de grain correspondant à un GrainId, un hachage numérique (uint) ou un SiloAddress. Les deux dernières surcharges concernent des cas avancés où un développeur veut utiliser un mécanisme différent pour mapper la responsabilité aux hôtes ou cibler directement un hôte. Dans notre exemple de code ci-dessous, nous définissons une propriété, GrainService, qui retourne le IDataService pour le grain qui appelle le DataServiceClient. Pour ce faire, nous utilisons la surcharge GetGrainService(GrainId) conjointement avec la propriété CurrentGrainReference.

    public class DataServiceClient : GrainServiceClient<IDataService>, IDataServiceClient
    {
        public DataServiceClient(IServiceProvider serviceProvider)
            : base(serviceProvider)
        {
        }
    
        // For convenience when implementing methods, you can define a property which gets the IDataService
        // corresponding to the grain which is calling the DataServiceClient.
        private IDataService GrainService => GetGrainService(CurrentGrainReference.GrainId);
    
        public Task MyMethod() => GrainService.MyMethod();
    }
    
  1. Créez le client de service de grain proprement dit. Il agit plus ou moins comme un proxy pour le service de données. Malheureusement, vous devez taper manuellement tous les mappages de méthode, qui sont de simples unilignes.

    public class DataServiceClient : GrainServiceClient<IDataService>, IDataServiceClient
    {
        public DataServiceClient(IServiceProvider serviceProvider)
            : base(serviceProvider)
        {
        }
    
        public Task MyMethod() => GrainService.MyMethod();
    }
    
  1. Injectez le client de service de grain dans les autres grains qui en ont besoin. Rien ne garantit que le GrainServiceClient accédera au GrainService dans le silo local. Il est possible que votre commande soit envoyée au GrainService dans n’importe quel silo du cluster.

    public class MyNormalGrain: Grain<NormalGrainState>, INormalGrain
    {
        readonly IDataServiceClient _dataServiceClient;
    
        public MyNormalGrain(
            IGrainActivationContext grainActivationContext,
            IDataServiceClient dataServiceClient) =>
                _dataServiceClient = dataServiceClient;
    }
    
  2. Configurez le service de grain et le client du service grain dans le silo. Vous devez effectuer cette opération pour que le silo démarre le GrainService.

    (ISiloHostBuilder builder) =>
        builder.ConfigureServices(
            services => services.AddGrainService<DataService>()
                                .AddSingleton<IDataServiceClient, DataServiceClient>());
    

Remarques supplémentaires

Il existe une méthode d’extension sur GrainServicesSiloBuilderExtensions.AddGrainService qui est utilisée pour inscrire les services de grain.

services.AddSingleton<IGrainService>(
    serviceProvider => GrainServiceFactory(grainServiceType, serviceProvider));

Le silo récupère les types IGrainService auprès du fournisseur de services au moment de démarrer : orleans/src/Orleans.Runtime/Silo/Silo.cs

var grainServices = this.Services.GetServices<IGrainService>();

Le package NuGet Microsoft.Orleans.Runtime doit être référencé par le projet GrainService.

Le package NuGet Microsoft.Orleans.OrleansRuntime doit être référencé par le projet GrainService.

Pour que cela fonctionne, vous devez inscrire le service et son client. Le code se présente comme suit :

var builder = new HostBuilder()
    .UseOrleans(c =>
    {
        c.AddGrainService<DataService>()  // Register GrainService
        .ConfigureServices(services =>
        {
            // Register Client of GrainService
            services.AddSingleton<IDataServiceClient, DataServiceClient>();
        });
    })