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

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:
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 viaActivatorUtilities
.
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
- 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 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
- Repositório completo: (GitHub)
- Documentação do Proto.Actor: Guia de Introdução a Grains/Virtual Actors (.NET)