Granularità dei servizi

I servizi granulari sono accessibili in remoto, servizi partizionati per supportare i grani di funzionalità. Ogni istanza di un servizio di granularità è responsabile di alcuni grani e questi grani possono ottenere un riferimento al servizio di granularità che è attualmente responsabile della manutenzione tramite un GrainServiceClient.

Esistono dei servizi granulari per supportare i casi in cui la responsabilità per la manutenzione dei grani deve essere distribuita intorno al cluster Orleans. Ad esempio, i Promemoria Orleans vengono implementati tramite servizi granulari: ogni silo è responsabile della gestione delle operazioni di promemoria per un subset di granularità e notifica tali granularità quando vengono generati i loro promemoria.

I servizi granulari vengono configurati in silo e vengono inizializzati all'avvio del silo, prima che il silo completi l'inizializzazione. Non vengono raccolti quando sono inattivi e hanno invece delle durate che si estendono per la durata del silo stesso.

Creare un GrainService

Un GrainService è una granularità speciale, una che non ha un'identità stabile e viene eseguita in ogni silo dall'avvio all'arresto. Durante l'implementazione di un'interfaccia IGrainService sono necessari diversi passaggi.

  1. Definire l'interfaccia di comunicazione del servizio granulare. L'interfaccia di un oggetto GrainService viene compilata usando gli stessi principi che si userebbero per la compilazione dell'interfaccia di una granularità.

    public interface IDataService : IGrainService
    {
        Task MyMethod();
    }
    
  2. Creare il servizio granulare DataService. È bene sapere che è anche possibile inserire un oggetto IGrainFactory in modo da poter effettuare chiamate granulari da 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. Creare un'interfaccia per l'oggetto GrainServiceClient<TGrainService>GrainServiceClient da utilizzare da altre granularità per connettersi all'oggetto GrainService.

    public interface IDataServiceClient : IGrainServiceClient<IDataService>, IDataService
    {
    }
    
  1. Creare il client del servizio granulare. I client in genere fungono da proxy per i servizi granulari di destinazione, quindi in genere si aggiungerà un metodo per ogni metodo nel servizio di destinazione. Questi metodi dovranno ottenere un riferimento al servizio granulare di destinazione in modo che possano chiamarlo. La classe base GrainServiceClient<T> fornisce diversi overload del metodo GetGrainService che possono restituire un riferimento granulare corrispondente a un GrainId, un hash numerico (uint) o un SiloAddress. Gli ultimi due overload sono per i casi avanzati in cui uno sviluppatore vuole usare un meccanismo diverso per eseguire il mapping della responsabilità agli host o vuole gestire direttamente un host. Nel codice di esempio seguente viene definita una proprietà, GrainService, che restituisce IDataService per la granularità che chiama DataServiceClient. A tale scopo, viene usato l'overload GetGrainService(GrainId) insieme alla proprietà 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. Creare il client del servizio granulare effettivo. Funziona praticamente come proxy per il servizio dati. Sfortunatamente, è necessario digitare manualmente in tutti i mapping dei metodi, che sono solo semplici righe singole.

    public class DataServiceClient : GrainServiceClient<IDataService>, IDataServiceClient
    {
        public DataServiceClient(IServiceProvider serviceProvider)
            : base(serviceProvider)
        {
        }
    
        public Task MyMethod() => GrainService.MyMethod();
    }
    
  1. Inserire il client del servizio granulare nelle altre granularità che ne hanno bisogno. GrainServiceClient non garantisce l'accesso a GrainService nel silo locale. Il comando potrebbe essere potenzialmente inviato a GrainService in qualsiasi silo nel cluster.

    public class MyNormalGrain: Grain<NormalGrainState>, INormalGrain
    {
        readonly IDataServiceClient _dataServiceClient;
    
        public MyNormalGrain(
            IGrainActivationContext grainActivationContext,
            IDataServiceClient dataServiceClient) =>
                _dataServiceClient = dataServiceClient;
    }
    
  2. Configurare il servizio granulare e il client del servizio granulare nel silo. È necessario eseguire questa operazione in modo che il silo avvii GrainService.

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

Note aggiuntive

Esiste un metodo di estensione in GrainServicesSiloBuilderExtensions.AddGrainService che viene usato per registrare i servizi granulari.

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

Il silo recupera i tipi IGrainService dal provider di servizi all'avvio: orleans/src/Orleans. Runtime/Silo/Silo.cs

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

The pacchetto NuGet Microsoft.Orleans.Runtime deve fare riferimento al progetto GrainService.

Il pacchetto NuGet Microsoft.Orleans.OrleansRuntime deve fare riferimento al progetto GrainService.

Per consentire il funzionamento, è necessario registrare sia il servizio che il client. Il codice è simile al seguente:

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