Referencias de grano
Antes de llamar a un método en grano, primero necesita una referencia a ese grano. Una referencia de grano es un objeto proxy que implementa la misma interfaz de grano que la clase de grano correspondiente. Encapsula la identidad lógica (tipo y clave única) del grano de destino. Las referencias de grano se usan para realizar llamadas al grano de destino. Cada referencia de grano se establece con un solo grano (una sola instancia de la clase de grano), pero puede crear varias referencias independientes al mismo grano.
Dado que una referencia de grano representa la identidad lógica del grano de destino, es independiente de la ubicación física del grano y sigue siendo válida incluso después de un reinicio completo del sistema. Los desarrolladores pueden usar las referencias de grano igual que cualquier otro objeto de .NET. Pueden pasarse a un método, usarse como un valor devuelto de método e incluso guardarse en el almacenamiento persistente.
Para obtener una referencia de grano, hay que pasar la identidad de un grano al método IGrainFactory.GetGrain<TGrainInterface>(Type, Guid), donde T
es la interfaz de grano y key
es la clave única del grano dentro del tipo.
A continuación se muestran ejemplos de cómo obtener una referencia de grano de la interfaz IPlayerGrain
definida anteriormente.
Desde dentro de una clase de grano:
// This would typically be read from an HTTP request parameter or elsewhere.
Guid playerId = Guid.NewGuid();
IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);
Desde el código de cliente de Orleans:
// This would typically be read from an HTTP request parameter or elsewhere.
Guid playerId = Guid.NewGuid();
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
Las referencias de grano contienen tres fragmentos de información:
- El tipo de grano, que identifica unívocamente la clase de grano.
- La clave de grano, que identifica de forma única una instancia lógica de esa clase de grano.
- La interfaz que debe implementar la referencia de grano.
Nota:
El tipo y clave de grano forman la identidad de grano.
Observe que las llamadas anteriores a IGrainFactory.GetGrain solo aceptaron dos de esas tres cosas:
- La interfaz implementada por la referencia del grano,
IPlayerGrain
. - La clave de grano, que es el valor de
playerId
.
A pesar de indicar que una referencia de grano contiene un tipo, clave e interfaz de grano, los ejemplos solo proporcionaban a Orleans la clave y la interfaz. Esto se debe a que Orleans mantiene una asignación entre interfaces de grano y tipos de grano. Cuando pide IShoppingCartGrain
a la fábrica de grano, Orleans consulta su asignación para encontrar el tipo de grano correspondiente y poder crear la referencia. Esto funciona cuando solo hay una implementación de una interfaz de grano, pero si hay varias implementaciones, tendrá que desambiguarlas en la llamada a GetGrain
. Para más información, consulte la sección siguiente, Desambiguación de la resolución de tipos de grano.
Nota:
Orleans genera tipos de implementación de referencia de grano para cada interfaz específica de la aplicación durante la compilación. Estas implementaciones de referencia de grano heredan de la clase Orleans.Runtime.GrainReference. GetGrain
devuelve instancias de la implementación de Orleans.Runtime.GrainReference generada correspondiente a la interfaz de grano solicitada.
Desambiguación de la resolución de tipos de grano
Cuando hay varias implementaciones de una interfaz de grano, como en el ejemplo siguiente, Orleans intenta determinar la implementación prevista al crear una referencia de grano. Considere el siguiente ejemplo, en el que existen dos implementaciones de la interfaz de 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 siguiente llamada a GetGrain
producirá una excepción porque Orleans no sabe cómo asignar sin ambigüedades ICounterGrain
a una de las clases de grano.
// This will throw an exception: there is no unambiguous mapping from ICounterGrain to a grain class.
ICounterGrain myCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");
Se producirá un System.ArgumentException con el siguiente mensaje:
Unable to identify a single appropriate grain type for interface ICounterGrain. Candidates: upcounter (UpCounterGrain), downcounter (DownCounterGrain)
El mensaje de error le indica qué implementaciones de grano tiene Orleans que coincidan con el tipo de interfaz de grano solicitado, ICounterGrain
. Muestra los nombres de tipo de grano (upcounter
y downcounter
), así como las clases de grano (UpCounterGrain
y DownCounterGrain
).
Nota:
Los nombres de tipo de grano del mensaje de error anterior, upcounter
y downcounter
, se derivan de los nombres de clase de grano, UpCounterGrain
y DownCounterGrain
respectivamente. Este es el comportamiento predeterminado en Orleans y se puede personalizar agregando un atributo [GrainType(string)]
a la clase de grano. Por ejemplo:
[GrainType("up")]
public class UpCounterGrain : IUpCounterGrain { /* as above */ }
Hay varias maneras de resolver esta ambigüedad detallada en las siguientes subsecciones.
Desambiguación de tipos de grano mediante interfaces de marcador únicas
La forma más clara de desambiguar estos granos es darles interfaces de grano únicas. Por ejemplo, si agregamos la interfaz IUpCounterGrain
a la clase UpCounterGrain
y agregamos la interfaz IDownCounterGrain
a la clase DownCounterGrain
, como en el siguiente ejemplo, podemos resolver la referencia correcta del grano pasando IUpCounterGrain
o IDownCounterGrain
a la llamada GetGrain<T>
en lugar de pasar el tipo ambiguo ICounterGrain
.
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 crear una referencia a cualquiera de los granos, considere el siguiente 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");
Nota:
En el ejemplo anterior, creó dos referencias de grano con la misma clave, pero tipos de grano diferentes. La primera, almacenada en la variable myUpCounter
, es una referencia al grano con el identificador upcounter/my-counter
. La segunda, almacenada en la variable myDownCounter
, es una referencia al grano con el id. downcounter/my-counter
. Es la combinación de tipo y clave de grano lo que identifica de forma única a un grano. Por lo tanto, myUpCounter
y myDownCounter
hacen referencia a diferentes granos.
Desambiguación de tipos de grano proporcionando un prefijo de clase de grano
Puede proporcionar un prefijo de nombre de clase de grano a IGrainFactory.GetGrain, por ejemplo:
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Up");
ICounterGrain myDownCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Down");
Especificación de la implementación de grano predeterminada mediante la convención de nomenclatura
Al desambiguar múltiples implementaciones de la misma interfaz de granos, Orleans seleccionará una implementación usando la convención de quitar una 'I' inicial del nombre de la interfaz. Por ejemplo, si el nombre de la interfaz es ICounterGrain
y hay dos implementaciones, CounterGrain
y DownCounterGrain
, Orleans elegirá CounterGrain
cuando se le pida una referencia a ICounterGrain
, como en el siguiente ejemplo:
/// This will refer to an instance of CounterGrain, since that matches the convention.
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");
Especificación del tipo de grano predeterminado mediante un atributo
El atributo Orleans.Metadata.DefaultGrainTypeAttribute puede agregarse a una interfaz de grano para especificar el tipo de grano de la implementación predeterminada para esa interfaz, como en el siguiente ejemplo:
[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");
Desambiguación de tipos de grano proporcionando el identificador de grano resuelto
Algunas sobrecargas de IGrainFactory.GetGrain aceptan un argumento de tipo Orleans.Runtime.GrainId. Cuando se usan estas sobrecargas, Orleans no necesita asignar de un tipo de interfaz a un tipo de grano y por lo tanto no hay ninguna ambigüedad que resolver. Por ejemplo:
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"));