Compartilhar via


Referências de granularidade

Antes de chamar um método na granularidade, você primeiro precisará de uma referência a essa granularidade. Uma referência de granularidade é um objeto proxy que implementa a mesma interface de granularidade que a classe de granularidade correspondente. Ela encapsula a identidade lógica (tipo e chave exclusiva) do grão de destino. As referências de granularidade são usadas para fazer chamadas para a granularidade de destino. Cada referência de grão destina-se a um único grão (uma única instância da classe de grão), mas é possível criar diversas referências independentes para o mesmo grão.

Como uma referência de grão representa a identidade lógica do grão de destino, ela é independente da localização física do grão e permanece válida mesmo após uma reinicialização completa do sistema. Os desenvolvedores podem usar referências de grão como qualquer outro objeto .NET. Ela pode ser transmitida para um método, usada como um valor de retorno de método etc., e pode até mesmo ser salva em um armazenamento persistente.

Uma referência de grão pode ser obtida transmitindo a identidade de um grão para o método IGrainFactory.GetGrain<TGrainInterface>(Type, Guid), em que T é a interface do grão e key é a chave exclusiva do grão dentro do tipo.

Veja a seguir exemplos de como obter uma referência de grão da interface IPlayerGrain definida acima.

De dentro de uma classe de granularidade:

// This would typically be read from an HTTP request parameter or elsewhere.
Guid playerId = Guid.NewGuid();
IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);

Dê um código do cliente Orleans:

// This would typically be read from an HTTP request parameter or elsewhere.
Guid playerId = Guid.NewGuid();
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);

As referências de granularidade contêm três informações:

  1. O tipo de granularidade, que identifica exclusivamente a classe de granularidade.
  2. A chave de granularidade, que identifica exclusivamente uma instância lógica dessa classe de granularidade.
  3. A interface que a referência de granularidade deve implementar.

Observação

O tipo e a chave da granularidade formam a identidade da granularidade.

Observe que as chamadas acima para IGrainFactory.GetGrain aceitaram apenas duas dessas três coisas:

  • A interface implementada pela referência de granularidade, IPlayerGrain.
  • A chave de granularidade, que é o valor de playerId.

Apesar de afirmar que uma referência de granularidade contém um tipo, chave e interface de granularidade, os exemplos apenas forneceram a Orleans a chave e a interface. Isso porque Orleans mantém um mapeamento entre interfaces de granularidade e tipos de granularidade. Quando você pede à fábrica de granularidade por IShoppingCartGrain, Orleansconsulta seu mapeamento para encontrar o tipo de granularidade correspondente para que ele possa criar a referência. Isso funciona quando há apenas uma implementação de uma interface de granularidade, mas se houver várias implementações, você precisará desambiguá-las na chamada GetGrain. Para obter mais informações, consulte a próxima seção, desambiguando a resolução do tipo de granularidade.

Observação

Orleans gera tipos de implementação de referência de granularidade para cada interface de granularidade em seu aplicativo durante a compilação. Essas implementações de referência de granularidade herdam da classe Orleans.Runtime.GrainReference. GetGrain retorna instâncias da implementação gerada Orleans.Runtime.GrainReference correspondente à interface de granularidade solicitada.

Desambiguando a resolução do tipo de granularidade

Quando há várias implementações de uma interface de granularidade, como no exemplo a seguir, Orleans tenta determinar a implementação pretendida ao criar uma referência de granularidade. Considere o exemplo a seguir, no qual há duas implementações da interface ICounterGrain:

public interface ICounterGrain : IGrainWithStringKey
{
    ValueTask<int> UpdateCount();
}

public class UpCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}

public class DownCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}

A chamada GetGrain a seguir gerará uma exceção porque Orleans não sabe como mapear sem ambiguidadeICounterGrain para uma das classes de granularidade.

// This will throw an exception: there is no unambiguous mapping from ICounterGrain to a grain class.
ICounterGrain myCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");

Um System.ArgumentException gerará com a seguinte mensagem:

Unable to identify a single appropriate grain type for interface ICounterGrain. Candidates: upcounter (UpCounterGrain), downcounter (DownCounterGrain)

A mensagem de erro informa qual implementação de granularidade de Orleanscorresponde ao tipo de interface de granularidade solicitado, ICounterGrain. Mostra os nomes de tipo de granularidade (upcounter e downcounter), bem como as classes de granularidade (UpCounterGrain e DownCounterGrain).

Observação

Os nomes de tipo de granularidade na mensagem de erro anterior, upcounter e downcounter, são derivados dos nomes de classe de granularidade, UpCounterGrain e DownCounterGrain respectivamente. Esse é o comportamento padrão em Orleans e pode ser personalizado adicionando um atributo [GrainType(string)] à classe de granularidade. Por exemplo:

[GrainType("up")]
public class UpCounterGrain : IUpCounterGrain { /* as above */ }

Há várias maneiras de resolver essa ambiguidade detalhadas nas subseções a seguir.

Desambiguando tipos de granularidade usando interfaces de marcador exclusivas

A maneira mais clara de desambiguar essas granularidades é dar-lhes interfaces de granularidade únicas. Por exemplo, se adicionarmos a interface IUpCounterGrain à classe UpCounterGrain e adicionarmos a interface IDownCounterGrain à classe DownCounterGrain, como no exemplo a seguir, poderemos resolver a referência de granularidade correta passando IUpCounterGrain ou IDownCounterGrain para a chamada GetGrain<T> em vez de passar o tipo ICounterGrain ambíguo.

public interface ICounterGrain : IGrainWithStringKey
{
    ValueTask<int> UpdateCount();
}

// Define unique interfaces for our implementations
public interface IUpCounterGrain : ICounterGrain, IGrainWithStringKey {}
public interface IDownCounterGrain : ICounterGrain, IGrainWithStringKey {}

public class UpCounterGrain : IUpCounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}

public class DownCounterGrain : IDownCounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}

Para criar uma referência a qualquer granularidade, considere o seguinte código:

// Get a reference to an UpCounterGrain.
ICounterGrain myUpCounter = grainFactory.GetGrain<IUpCounterGrain>("my-counter");

// Get a reference to a DownCounterGrain.
ICounterGrain myDownCounter = grainFactory.GetGrain<IDownCounterGrain>("my-counter");

Observação

No exemplo anterior, você criou duas referências de granularidade com a mesma chave, mas tipos de granularidade diferentes. A primeira, armazenada na variável myUpCounter, é uma referência à granularidade com a ID upcounter/my-counter. A segunda, armazenada na variável myDownCounter, é uma referência à granularidade com a ID downcounter/my-counter. É a combinação do tipo e da chave de granularidade que identificam exclusivamente uma granularidade. Portanto, myUpCounter e myDownCounter referem-se a granularidades diferentes.

Desambiguação de tipos de granularidade fornecendo um prefixo de classe de granularidade

Você pode fornecer um prefixo de nome de classe de granularidade para IGrainFactory.GetGrain, por exemplo:

ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Up");
ICounterGrain myDownCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Down");

Especificando a implementação de granularidade padrão usando a convenção de nomenclatura

Ao desambiguar várias implementações da mesma interface de granularidade, Orleans selecionará uma implementação usando a convenção de remover um 'I' à esquerda do nome da interface. Por exemplo, se o nome da interface for ICounterGrain e houver duas implementações, CounterGrain e DownCounterGrain, Orleans escolherá CounterGrain quando for solicitado por uma referência a ICounterGrain, como no exemplo a seguir:

/// This will refer to an instance of CounterGrain, since that matches the convention.
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");

Especificando o tipo de granularidade padrão usando um atributo

O atributo Orleans.Metadata.DefaultGrainTypeAttribute pode ser adicionado a uma interface de granularidade para especificar o tipo de granularidade da implementação padrão para essa interface, como no exemplo a seguir:

[DefaultGrainType("up-counter")]
public interface ICounterGrain : IGrainWithStringKey
{
    ValueTask<int> UpdateCount();
}

[GrainType("up-counter")]
public class UpCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}

[GrainType("down-counter")]
public class DownCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}
/// This will refer to an instance of UpCounterGrain, due to the [DefaultGrainType("up-counter"')] attribute
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");

Desambiguação de tipos de granularidade fornecendo a ID de granularidade resolvida

Algumas sobrecargas de IGrainFactory.GetGrain aceitam um argumento do tipo Orleans.Runtime.GrainId. Ao usar essas sobrecargas, Orleans não precisa mapear de um tipo de interface para um tipo de granularidade e, portanto, não há ambiguidade a ser resolvida. Por exemplo:

public interface ICounterGrain : IGrainWithStringKey
{
    ValueTask<int> UpdateCount();
}

[GrainType("up-counter")]
public class UpCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}

[GrainType("down-counter")]
public class DownCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}
// This will refer to an instance of UpCounterGrain, since "up-counter" was specified as the grain type
// and the UpCounterGrain uses [GrainType("up-counter")] to specify its grain type.
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>(GrainId.Create("up-counter", "my-counter"));