Introdução aos Atores Virtuais (Grains) em .NET com Proto.Actor

No artigo anterior, discuti a configuração de um ator simples. Este artigo foca em Virtual Actor, um conceito que amplia o modelo tradicional de atores com gerenciamento automático de ciclo de vida e comunicação simplificada. Virtual Actor (Grain) O modelo de Virtual Actor (ou grain), popularizado pelo framework Orleans da Microsoft, abstrai o gerenciamento manual do ciclo de vida dos atores. Enquanto atores tradicionais exigem criação explícita e referência via PID, os atores virtuais são identificados por uma chave única. O framework os cria, ativa ou reativa automaticamente conforme necessário. Essa abstração simplifica a escalabilidade em sistemas distribuídos, dissociando a identidade do ator de sua localização física ou estado. Diferenças-chave em relação aos atores clássicos: Gerenciamento de Ciclo de Vida: O framework (ex: Orleans ou Proto.Actor) cuida da ativação/desativação. Endereçamento: A comunicação usa identificadores lógicos, não PIDs. Persistência de Estado: Integra camadas de gerenciamento de estado para tolerância a falhas. Requisitos .NET 6+ Pacotes NuGet: Proto.Actor Proto.Remote Proto.Cluster - Enables distributed virtual actor clusters via gRPC. Proto.Cluster.CodeGen Proto.Cluster.TestProvider Grpc.Tools Microsoft.Extensions.Hosting Definindo o Virtual Actor (Grain) O Proto.Actor usa Protocol Buffers para definir interfaces de atores. Crie um arquivo Greeting.proto: syntax = "proto3"; option csharp_namespace = "VirtualActor"; import "google/protobuf/empty.proto"; message SayHelloRequest { string name = 1; } service GreetingGrain { rpc SayHello(SayHelloRequest) returns (google.protobuf.Empty); } Isso gera: Classes de requisição/resposta (ex: SayHelloRequest). Classe base (GreetingGrainBase) para a lógica do ator. Atualize o .csproj para habilitar a geração de código: None Implementando o Ator Crie uma classe GreetingActor herdando de GreetingGrainBase: public class GreetingActor( IContext context, ClusterIdentity clusterIdentity, ILogger logger ) : GreetingGrainBase(context) { private int _invocationCount = 0; public override Task SayHello(SayHelloRequest request) { logger.LogInformation( "Hello {Name} (Cluster ID: {ClusterId} | Invocation Count: {Count})", request.Name, clusterIdentity.Identity, _invocationCount++ ); return Task.CompletedTask; } } Detalhes: Gerenciamento de Estado: _invocationCount rastreia chamadas (thread-safe graças ao modelo de atores). Injeção de Dependências: ILogger é injetado via ActivatorUtilities. Configurando o Sistema de Atores Configure o cluster com TestProvider (para desenvolvimento) e PartitionIdentityLookup: var actorSystemConfig = Proto.ActorSystemConfig.Setup(); var remoteConfig = GrpcNetRemoteConfig.BindToLocalhost(); var clusterConfig = ClusterConfig .Setup( clusterName: "VirtualActor", clusterProvider: new TestProvider(new TestProviderOptions(), new InMemAgent()), identityLookup: new PartitionIdentityLookup() ) .WithClusterKind( kind: GreetingGrainActor.Kind, prop: Props.FromProducer(() => new GreetingGrainActor((context, clusterIdentity) => ActivatorUtilities.CreateInstance(provider, context, clusterIdentity))) ); return new ActorSystem(actorSystemConfig) .WithServiceProvider(provider) .WithRemote(remoteConfig) .WithCluster(clusterConfig); Integrando ao .NET Registre o serviço de cluster como um IHostedService: public class ActorSystemClusterHostedService(ActorSystem actorSystem) : IHostedService { public async Task StartAsync(CancellationToken cancellationToken) { await actorSystem.Cluster().StartMemberAsync(); } public async Task StopAsync(CancellationToken cancellationToken) { await actorSystem.Cluster().ShutdownAsync(); } } Registre em Program.cs: services.AddHostedService(); Interagindo com Atores Virtuais Use GetGreetingGrain para referenciar um ator por ID: var actor = actorSystem.Cluster().GetGreetingGrain(fromName); await actor.SayHello(new SayHelloRequest { Name = toName }, CancellationToken.None); Exemplo de fluxo: while (true) { Console.Write("Seu nome (ou 'q' para sair): "); var fromName = Console.ReadLine(); if (fromName == "q") break; Console.Write("Nome do destinatário: "); var toName = Console.ReadLine(); if (toName == "q") break; await actor.SayHello(new SayHelloRequest { Name = toName }); } Benefícios dos Atores Virtuais Concorrência Simplificada: Processamento sequencial de mensagens evita condições de corrida. Escalabilidade Elástica: Adicione/remova nós sem reconfigurar atores. Resiliência: Reativação automática garante comporta

Apr 11, 2025 - 01:00
 0
Introdução aos Atores Virtuais (Grains) em .NET com Proto.Actor

No artigo anterior, discuti a configuração de um ator simples. Este artigo foca em Virtual Actor, um conceito que amplia o modelo tradicional de atores com gerenciamento automático de ciclo de vida e comunicação simplificada.

Virtual Actor (Grain)

O modelo de Virtual Actor (ou grain), popularizado pelo framework Orleans da Microsoft, abstrai o gerenciamento manual do ciclo de vida dos atores. Enquanto atores tradicionais exigem criação explícita e referência via PID, os atores virtuais são identificados por uma chave única. O framework os cria, ativa ou reativa automaticamente conforme necessário. Essa abstração simplifica a escalabilidade em sistemas distribuídos, dissociando a identidade do ator de sua localização física ou estado.

Diferenças-chave em relação aos atores clássicos:

  1. Gerenciamento de Ciclo de Vida: O framework (ex: Orleans ou Proto.Actor) cuida da ativação/desativação.
  2. Endereçamento: A comunicação usa identificadores lógicos, não PIDs.
  3. Persistência de Estado: Integra camadas de gerenciamento de estado para tolerância a falhas.

Requisitos

Definindo o Virtual Actor (Grain)

O Proto.Actor usa Protocol Buffers para definir interfaces de atores. Crie um arquivo Greeting.proto:

syntax = "proto3";

option csharp_namespace = "VirtualActor";

import "google/protobuf/empty.proto";

message SayHelloRequest {
  string name = 1;
}

service GreetingGrain {
  rpc SayHello(SayHelloRequest) returns (google.protobuf.Empty);
}

Isso gera:

  • Classes de requisição/resposta (ex: SayHelloRequest).
  • Classe base (GreetingGrainBase) para a lógica do ator.

Atualize o .csproj para habilitar a geração de código:


   Include="Greeting.proto">
    None
  



   Include="Greeting.proto" />

Implementando o Ator

Crie uma classe GreetingActor herdando de GreetingGrainBase:

public class GreetingActor(
    IContext context,
    ClusterIdentity clusterIdentity,
    ILogger<GreetingActor> logger
) : GreetingGrainBase(context)
{
    private int _invocationCount = 0;

    public override Task SayHello(SayHelloRequest request)
    {
        logger.LogInformation(
            "Hello {Name} (Cluster ID: {ClusterId} | Invocation Count: {Count})",
            request.Name,
            clusterIdentity.Identity,
            _invocationCount++
        );
        return Task.CompletedTask;
    }
}

Detalhes:

  • Gerenciamento de Estado: _invocationCount rastreia chamadas (thread-safe graças ao modelo de atores).
  • Injeção de Dependências: ILogger é injetado via ActivatorUtilities.

Configurando o Sistema de Atores

Configure o cluster com TestProvider (para desenvolvimento) e PartitionIdentityLookup:

var actorSystemConfig = Proto.ActorSystemConfig.Setup();
var remoteConfig = GrpcNetRemoteConfig.BindToLocalhost();

var clusterConfig = ClusterConfig
    .Setup(
        clusterName: "VirtualActor",
        clusterProvider: new TestProvider(new TestProviderOptions(), new InMemAgent()),
        identityLookup: new PartitionIdentityLookup()
    )
    .WithClusterKind(
        kind: GreetingGrainActor.Kind,
        prop: Props.FromProducer(() => 
            new GreetingGrainActor((context, clusterIdentity) => 
                ActivatorUtilities.CreateInstance<GreetingActor>(provider, context, clusterIdentity)))
    );

return new ActorSystem(actorSystemConfig)
    .WithServiceProvider(provider)
    .WithRemote(remoteConfig)
    .WithCluster(clusterConfig);

Integrando ao .NET

Registre o serviço de cluster como um IHostedService:

public class ActorSystemClusterHostedService(ActorSystem actorSystem) : IHostedService
{
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        await actorSystem.Cluster().StartMemberAsync();
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        await actorSystem.Cluster().ShutdownAsync();
    }
}

Registre em Program.cs:

services.AddHostedService<ActorSystemClusterHostedService>();

Interagindo com Atores Virtuais

Use GetGreetingGrain para referenciar um ator por ID:

var actor = actorSystem.Cluster().GetGreetingGrain(fromName);
await actor.SayHello(new SayHelloRequest { Name = toName }, CancellationToken.None);

Exemplo de fluxo:

while (true)
{
    Console.Write("Seu nome (ou 'q' para sair): ");
    var fromName = Console.ReadLine();
    if (fromName == "q") break;

    Console.Write("Nome do destinatário: ");
    var toName = Console.ReadLine();
    if (toName == "q") break;

    await actor.SayHello(new SayHelloRequest { Name = toName });
}

Benefícios dos Atores Virtuais

  1. Concorrência Simplificada: Processamento sequencial de mensagens evita condições de corrida.
  2. Escalabilidade Elástica: Adicione/remova nós sem reconfigurar atores.
  3. Resiliência: Reativação automática garante comportamento "sempre ativo".

Conclusão

Atores Virtuais (ou Grains) revolucionam o desenvolvimento de sistemas distribuídos ao abstrair complexidade enquanto mantêm os benefícios do modelo de atores. Com o Proto.Actor, desenvolvedores .NET podem:

  • Focar na Lógica de Negócio: O framework gerencia ativação, escalabilidade e recuperação.
  • Construir Sistemas Resilientes: Reativação automática e persistência de estado garantem tolerância a falhas.
  • Escalar sem Esforço: Transparência de localização e clustering elástico distribuem carga entre nós.

Para produção:

  • Substitua TestProvider por provedores como Kubernetes ou Consul.
  • Adicione armazenamento persistente (ex: Redis, PostgreSQL).
  • Implemente monitoramento e health checks.

Referência