Condividi tramite


Riferimenti granulari

Prima di chiamare un metodo con granularità, è necessario prima di tutto un riferimento a tale granularità. Un riferimento granulare è un oggetto proxy che implementa la stessa interfaccia granulare della classe granulare corrispondente. Incapsula l'identità logica (tipo e chiave univoca) della granularità di destinazione. I riferimenti granulari vengono usati per effettuare chiamate all'granularità di destinazione. Ogni riferimento granulare si riferisce a un'unica granularità (una singola istanza della classe granulare), ma è possibile creare più riferimenti indipendenti alla stessa granularità.

Poiché un riferimento granulare rappresenta l'identità logica della granularità di destinazione, è indipendente dalla posizione fisica del grano e rimane valido anche dopo un riavvio completo del sistema. Gli sviluppatori possono usare riferimenti granulari come qualsiasi altro oggetto .NET. Può essere passato a un metodo, usato come valore restituito dal metodo e anche salvato nell'archiviazione permanente.

È possibile ottenere un riferimento granulare passando l'identità di un granulare al metodo IGrainFactory.GetGrain<TGrainInterface>(Type, Guid), dove T è l'interfaccia di granularità e key è la chiave univoca del granularità all'interno del tipo.

Di seguito sono riportati esempi di come ottenere un riferimento granulare dell'interfaccia IPlayerGrain definita in precedenza.

Dall'interno di una classe granulare:

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

Dal codice client Orleans:

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

I riferimenti granulari contengono tre informazioni:

  1. Tipo di granularità, che identifica in modo univoco la classe granulare.
  2. La chiave granulare, che identifica in modo univoco un'istanza logica di tale classe di granularità.
  3. L’interfaccia che deve essere implementata dal riferimento granulare.

Nota

Il tipo di granularità e la chiave formano l'identità granulare.

Si noti che le chiamate precedenti a IGrainFactory.GetGrain accettano solo due di queste tre cose:

  • L’interfaccia implementata dal riferimento granulare, IPlayerGrain.
  • La chiave di granularità, ovvero il valore di playerId.

Nonostante il fatto che un riferimento granulare contenga un tipo, una chiave e un’interfaccia di granularità, gli esempi hanno fornito solo Orleans con la chiave e l'interfaccia. Ciò è dovuto al fatto che Orleans mantiene un mapping tra le interfacce di granularità e i tipi di granularità. Quando si chiede alla factory di granularità IShoppingCartGrain, Orleans consulta il relativo mapping per trovare il tipo di granularità corrispondente in modo che possa creare il riferimento. Questa operazione funziona quando è presente una sola implementazione di un'interfaccia granulare, ma se sono presenti più implementazioni, sarà necessario disambiguarle nella chiamata GetGrain. Per altre informazioni, vedere la sezione successiva, disambiguare la risoluzione dei tipi di granularità.

Nota

Orleans genera tipi di implementazione di riferimento granulari per ogni interfaccia granulare nell'applicazione durante la compilazione. Queste implementazioni di riferimento granulare ereditano dalla classe Orleans.Runtime.GrainReference. GetGrain restituisce istanze dell'implementazione Orleans.Runtime.GrainReference generata corrispondente all'interfaccia granulare richiesta.

Risoluzione dei tipi di granularità non ambigua

Quando sono presenti più implementazioni di un'interfaccia di granularità, ad esempio nell'esempio seguente, Orleans tenta di determinare l'implementazione prevista durante la creazione di un riferimento granulare. Si consideri l'esempio seguente, in cui sono presenti due implementazioni dell'interfaccia 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
}

La chiamata seguente a GetGrain genererà un'eccezione perché Orleans non sa come eseguire il mapping univoco di ICounterGrain a una delle classi di granularità.

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

Verrà generata un'eccezione System.ArgumentException con il messaggio seguente:

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

Il messaggio di errore indica a quale Orleans dell’implementazione di granularità corrisponde il tipo di interfaccia granulare richiesto, ICounterGrain. Mostra i nomi dei tipi di granularità (upcounter e downcounter) e le classi di granularità (UpCounterGrain e DownCounterGrain).

Nota

I nomi dei tipi di granularità nel messaggio di errore precedente, upcounter e downcounter, sono derivati dai nomi delle classi granulari, rispettivamente UpCounterGrain e DownCounterGrain. Si tratta del comportamento predefinito in Orleans e può essere personalizzato aggiungendo un attributo [GrainType(string)] alla classe grano. Ad esempio:

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

Esistono diversi modi per risolvere questa ambiguità dettagliata nelle sottosezioni seguenti.

Disambiguazione dei tipi di granularità tramite interfacce marcatori univoche

Il modo più chiaro per disambiguare questi grani consiste nel dare loro delle interfacce di granularità univoche. Ad esempio, se si aggiunge l'interfaccia IUpCounterGrain alla classe UpCounterGrain e si aggiunge l'interfaccia IDownCounterGrain alla classe DownCounterGrain, come nell'esempio seguente, è possibile risolvere il riferimento granulare corretto passando IUpCounterGrain o IDownCounterGrain alla chiamata GetGrain<T> anziché passare il tipo di ICounterGrain ambiguo.

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
}

Per creare un riferimento a uno dei due tipi di granularità, prendere in considerazione il codice seguente:

// 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");

Nota

Nell'esempio precedente sono stati creati due riferimenti granulari con la stessa chiave, ma tipi di granularità diversi. Il primo, archiviato nella variabile myUpCounter, è un riferimento alla granularità con l'ID upcounter/my-counter. Il secondo, archiviato nella variabile myDownCounter, è un riferimento alla granularità con l'ID downcounter/my-counter. È la combinazione di tipo di granularità e chiave di granularità che identificano in modo univoco una granularità. Pertanto, myUpCounter e myDownCounter fanno riferimento a grani diversi.

Disambiguare i tipi di granularità fornendo un prefisso di classe granulare

È possibile specificare un prefisso di nome di classe granulare a IGrainFactory.GetGrain, ad esempio:

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

Specificare l'implementazione di granularità predefinita tramite la convenzione di denominazione

Quando si disambiguano più implementazioni della stessa interfaccia di granularità, Orleans selezionerà un'implementazione usando la convenzione di rimuovere una 'I' iniziale dal nome dell'interfaccia. Ad esempio, se il nome dell'interfaccia è ICounterGrain e sono presenti due implementazioni, CounterGrain e DownCounterGrain, Orleans sceglie CounterGrain quando viene richiesto un riferimento a ICounterGrain, come nell'esempio seguente:

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

Specificare il tipo di granularità predefinito usando un attributo

L'attributo Orleans.Metadata.DefaultGrainTypeAttribute può essere aggiunto a un'interfaccia granulare per specificare il tipo di granularità dell'implementazione predefinita per tale interfaccia, come nell'esempio seguente:

[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");

Disambiguare i tipi di granularità fornendo l'ID granulare risolto

Alcuni overload di IGrainFactory.GetGrain accettano un argomento di tipo Orleans.Runtime.GrainId. Quando si usano questi overload, Orleans non ha bisogno di eseguire il mapping da un tipo di interfaccia a un tipo di granularità e pertanto non è necessario risolvere alcuna ambiguità. Ad esempio:

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"));