グレイン参照
グレインに対してメソッドを呼び出す前に、まずそのグレインへの参照が必要です。 グレイン参照は、対応するグレイン クラスと同じグレイン インターフェイスを実装するプロキシ オブジェクトです。 ターゲット グレインの論理 ID (型と一意のキー) をカプセル化します。 グレイン参照は、ターゲット グレインを呼び出すために使用されます。 各グレイン参照は 1 つのグレイン (グレイン クラスの 1 つのインスタンス) に対して行われますが、同じグレインに独立した複数の参照を作成できます。
グレイン参照はターゲット グレインの論理 ID を表すため、グレインの物理的な場所とは無関係であり、システムの完全な再起動後も有効なままです。 開発者は、他の .NET オブジェクトと同様にグレイン参照を使用できます。 また、メソッドに渡したり、メソッドの戻り値として使用したり、永続ストレージに保存したりすることもできます。
グレイン参照は、グレインの ID をT
がグレイン インターフェイスであり、key
が型内部のグレインの一意のキーである IGrainFactory.GetGrain<TGrainInterface>(Type, Guid) メソッドに渡すことによって取得できます。
上で定義した IPlayerGrain
インターフェイスのグレイン参照を取得する方法の例を次に示します。
グレイン クラス内から:
// This would typically be read from an HTTP request parameter or elsewhere.
Guid playerId = Guid.NewGuid();
IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);
Orleans クライアント コードから:
// This would typically be read from an HTTP request parameter or elsewhere.
Guid playerId = Guid.NewGuid();
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
グレイン参照には、次の 3 つの情報が含まれています。
- グレインの type。グレイン クラスを一意に識別します。
- グレインの key。そのグレイン クラスの論理インスタンスを一意に識別します。
- グレイン参照で実装する必要がある interface。
Note
グレインの type と key によってグレイン ID が形成されます。
上記の IGrainFactory.GetGrain に対する呼び出しでは、次の 3 つのうち 2 つのみが受け入れられたことに注目してください。
- グレイン参照
IPlayerGrain
によって実装された interface。 - グレインの key (値は
playerId
です)。
グレイン参照には、グレインの type、key、interface が含まれていると説明しましたが、この例では、key と interface を指定した Orleans のみを示しました。 これは、Orleans がグレイン インターフェイスとグレインの種類間のマッピングを維持しているためです。 グレイン ファクトリに IShoppingCartGrain
を要求すると、Orleans はそのマッピングを参照して対応するグレインの種類を見つけます。これにより、参照を作成できるようになります。 これは、グレイン インターフェイスの実装が 1 つだけである場合は機能しますが、実装が複数ある場合は、GetGrain
の呼び出しでそれらのあいまいさを解消する必要があります。 詳細については、次のセクションの「グレインの種類の解決のあいまいさを解消する」を参照してください。
Note
Orleans により、コンパイル時にアプリケーション内の各グレイン インターフェイスのグレイン参照実装の種類が生成されます。 これらのグレイン参照の実装は、Orleans.Runtime.GrainReference クラスから継承されます。 GetGrain
は、要求されたグレイン インターフェイスに対応する、生成された Orleans.Runtime.GrainReference 実装のインスタンスを返します。
グレインの種類の解決のあいまいさを解消する
次の例のように、グレイン インターフェイスの実装が複数ある場合、グレイン参照の作成時に、Orleans は意図した実装を決定しようとします。 ICounterGrain
インターフェイスの実装が 2 つあるという次の例を考えてみましょう。
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
}
次の GetGrain
の呼び出しによって例外がスローされます。これは、グレイン クラスのいずれかに ICounterGrain
を明確にマップする方法を Orleans が認識していないためです。
// This will throw an exception: there is no unambiguous mapping from ICounterGrain to a grain class.
ICounterGrain myCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");
次のメッセージと共に System.ArgumentException がスローされます。
Unable to identify a single appropriate grain type for interface ICounterGrain. Candidates: upcounter (UpCounterGrain), downcounter (DownCounterGrain)
エラー メッセージで、要求されたグレイン インターフェイスの種類 ICounterGrain
と一致するグレイン実装の Orleans を確認できます。 グレインの種類名 (upcounter
と downcounter
) とグレインのクラス (UpCounterGrain
と DownCounterGrain
) が表示されます。
Note
上記のエラー メッセージに含まれるグレインの種類名 (upcounter
と downcounter
) は、それぞれグレインのクラス名 UpCounterGrain
と DownCounterGrain
から派生したものです。 これは Orleans の既定の動作であり、グレイン クラスに [GrainType(string)]
属性を追加することでカスタマイズできます。 次に例を示します。
[GrainType("up")]
public class UpCounterGrain : IUpCounterGrain { /* as above */ }
このあいまいさを解決するにはいくつかの方法があり、次のサブセクションで詳しく説明します。
独自のマーカー インターフェイスを使ってグレインの種類のあいまいさを解消する
これらのグレインのあいまいさを解消する最もわかりやすい方法は、それらに固有のグレイン インターフェイスを与えることです。 たとえば、次の例のように、インターフェイス IUpCounterGrain
を UpCounterGrain
クラスに追加し、インターフェイス IDownCounterGrain
を DownCounterGrain
クラスに追加する場合、あいまいな ICounterGrain
の種類を渡すのではなく、IUpCounterGrain
または IDownCounterGrain
を GetGrain<T>
の呼び出しに渡すことで正しいグレイン参照を解決できます。
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
}
いずれかのグレインへの参照を作成するには、次のコードを検討してください。
// 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");
Note
前の例では、キーは同じですがグレインの種類が異なる 2 つのグレイン参照を作成しました。 1 つ目は、myUpCounter
変数に格納されており、ID upcounter/my-counter
のグレインへの参照です。 2 つ目は、myDownCounter
変数に格納されており、ID downcounter/my-counter
のグレインへの参照です。 これは、グレインを一意に特定するグレインの type とグレインの key の組み合わせです。 そのため、myUpCounter
と myDownCounter
は異なるグレインを参照します。
グレイン クラスのプレフィックスを指定してグレインの種類のあいまいさを解消する
たとえば、次のように、グレイン クラス名のプレフィックスを IGrainFactory.GetGrain に指定できます。
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Up");
ICounterGrain myDownCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Down");
名前付け規則を使って既定のグレイン実装を指定する
グレイン インターフェイスが同じである実装のあいまいさを解消するときに、Orleans はインターフェイス名から先頭の 'I' を取り除く規約を使って実装を選びます。 たとえば、インターフェイス名が ICounterGrain
であり、CounterGrain
と DownCounterGrain
の 2 つの実装がある場合、次の例のように、ICounterGrain
への参照を求められると、Orleans は CounterGrain
を選びます。
/// This will refer to an instance of CounterGrain, since that matches the convention.
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");
属性を使って既定のグレインの種類を指定する
次の例のように、Orleans.Metadata.DefaultGrainTypeAttribute 属性をグレイン インターフェイスに追加して、そのインターフェイスに対して既定の実装のグレインの種類を指定できます。
[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");
解決されたグレイン ID を指定してグレインの種類のあいまいさを解消する
IGrainFactory.GetGrain の一部のオーバーロードは、種類 Orleans.Runtime.GrainId の引数を受け入れます。 これらのオーバーロードを使う場合、Orleans はインターフェイスの種類からグレインの種類にマップする必要がないため、解決すべきあいまいさはありません。 次に例を示します。
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"));
.NET