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