Serviços de granularidade

Os Serviços de Granularidade são serviços particionados e acessíveis remotamente para dar suporte aos grãos de funcionalidade. Cada instância de um serviço de granularidade é responsável por alguns conjuntos de grãos, e esses grãos podem obter uma referência ao serviço de granularidade que é responsável pela manutenção deles usando um GrainServiceClient.

Os Serviços de Granularidade existem para dar suporte a casos em que a responsabilidade pela manutenção de grãos deve ser distribuída ao redor do cluster Orleans. Por exemplo, lembretes Orleans são implementados usando serviços de granularidade: cada silo é responsável por lidar com operações de lembrete para um subconjunto de grãos e notificar esses grãos quando seus lembretes são acionados.

Os Serviços de Granularidade são configurados em silos e são inicializados quando o silo é iniciado, antes que o silo conclua a inicialização. Eles não são coletados quando ociosos e, em vez disso, têm tempos de vida que se estendem pelo tempo de vida do próprio silo.

Criar um GrainService

Um GrainService é um grão especial, que não tem identidade e é executado em cada silo desde a inicialização até o desligamento. Há várias etapas envolvidas ao implementar uma interface IGrainService.

  1. Defina a interface de comunicação do serviço de granularidade. A interface de um GrainService é criada usando os mesmos princípios que você usaria para criar a interface de um grão.

    public interface IDataService : IGrainService
    {
        Task MyMethod();
    }
    
  2. Crie o serviço de granularidade DataService. É bom saber que você também pode injetar um IGrainFactory para que você possa fazer chamadas de granularidade de seu 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. Crie uma interface para que o GrainServiceClient<TGrainService>GrainServiceClient seja usado por outras granularidades para se conectar ao GrainService.

    public interface IDataServiceClient : IGrainServiceClient<IDataService>, IDataService
    {
    }
    
  1. Crie o cliente do serviço de granularidade. Normalmente, os clientes atuam como proxies para os serviços de granularidade direcionados, portanto, você geralmente adicionará um método para cada método no serviço de destino. Esses métodos precisarão obter uma referência ao serviço de granularidade de destino para que possam chamá-lo. A classe base GrainServiceClient<T> fornece várias sobrecargas do método GetGrainService que podem retornar uma referência de granularidade correspondente a um GrainId, um hash numérico (uint) ou um SiloAddress. As duas últimas sobrecargas são para casos avançados em que um desenvolvedor deseja usar um mecanismo diferente para mapear a responsabilidade para hosts ou deseja endereçar a um host diretamente. Em nosso código de exemplo abaixo, definimos uma propriedade , GrainService, que retorna o IDataService para o grão que está chamando o DataServiceClient. Para fazer isso, usamos a sobrecarga GetGrainService(GrainId) em conjunto com a propriedade 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. Crie o cliente de serviço de granularidade real. Ele praticamente atua apenas como um proxy para o serviço de dados. Infelizmente, você precisa digitar manualmente todos os mapeamentos de método, que são apenas linhas únicas simples.

    public class DataServiceClient : GrainServiceClient<IDataService>, IDataServiceClient
    {
        public DataServiceClient(IServiceProvider serviceProvider)
            : base(serviceProvider)
        {
        }
    
        public Task MyMethod() => GrainService.MyMethod();
    }
    
  1. Injete o cliente de serviço de granularidade nas outras granularidades que precisam dele. Não há garantia que o GrainServiceClient acesse o GrainService no silo local. Seu comando pode potencialmente ser enviado ao GrainService em qualquer silo no cluster.

    public class MyNormalGrain: Grain<NormalGrainState>, INormalGrain
    {
        readonly IDataServiceClient _dataServiceClient;
    
        public MyNormalGrain(
            IGrainActivationContext grainActivationContext,
            IDataServiceClient dataServiceClient) =>
                _dataServiceClient = dataServiceClient;
    }
    
  2. Configure o serviço de granularidade e o cliente do serviço de granularidade no silo. Você precisa fazer isso para que o silo inicie o GrainService.

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

Observações adicionais

Há um método de extensão em GrainServicesSiloBuilderExtensions.AddGrainService que é usado para registrar os serviços de granularidade.

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

O silo busca tipos IGrainService do provedor de serviços ao iniciar: orleans/src/Orleans.Runtime/Silo/Silo.cs

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

O pacote NuGet Microsoft.Orleans.Runtime deve ser referenciado pelo projeto GrainService.

O pacote NuGet Microsoft.Orleans.OrleansRuntime deve ser referenciado pelo projeto GrainService.

Para que isso funcione, você precisa registrar o serviço e seu cliente. O código fica mais ou menos parecido com isto:

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