Поделиться через


Ссылки на зерна

Прежде чем вызывать метод на зерно, необходимо сначала указать ссылку на это зерно. Ссылка на зерно — это прокси-объект, реализующий тот же интерфейс зерна, что и соответствующий класс зерна. Он инкапсулирует логическое удостоверение (тип и уникальный ключ) целевого зерна. Ссылки на зерна используются для вызова целевого зерна. Каждая ссылка на зерно состоит в одном зерне (один экземпляр класса зерна), но можно создать несколько независимых ссылок на одно и то же зерно.

Поскольку ссылка на зерно представляет логическую идентификацию целевого зерна, она не зависит от физического расположения зерна и остается допустимой даже после полного перезапуска системы. Разработчики могут использовать ссылки на зерна, как и любой другой объект .NET. Его можно передать в метод, использовать в качестве возвращаемого значения метода и даже сохранять в постоянном хранилище.

Ссылку на зерно можно получить путем передачи удостоверения зерна IGrainFactory.GetGrain<TGrainInterface>(Type, Guid) методу, где T находится интерфейс зерна и key является уникальным ключом зерна внутри типа.

Ниже приведены примеры получения ссылки на фрагмент интерфейса, определенного IPlayerGrain выше.

Из класса grain:

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

Ссылки на зерна содержат три фрагмента информации:

  1. Тип зерна, который однозначно идентифицирует класс зерна.
  2. Ключ зерна, который однозначно идентифицирует логический экземпляр этого класса зерна.
  3. Интерфейс, который должен реализовать ссылка на зерно.

Примечание.

Тип зерна и ключ образуют удостоверение зерна.

Обратите внимание, что приведенные выше вызовы IGrainFactory.GetGrain приняли только два из этих трех вещей:

  • Интерфейс, реализованный ссылкой на зерно, IPlayerGrain.
  • Ключ зерна, который является значением playerId.

Несмотря на то, что ссылка на зерно содержит тип зерна, ключ и интерфейс, примеры предоставляются только Orleans ключом и интерфейсом. Это связано с тем, что Orleans поддерживает сопоставление между интерфейсами зерна и типами зерна. При запросе фабрики IShoppingCartGrainOrleans зерна обратитесь к его сопоставлению, чтобы найти соответствующий тип зерна, чтобы он смог создать ссылку. Это работает, если в вызове существует только одна реализация интерфейса зерна, но если существует несколько реализаций, то в вызове потребуется отсознать их GetGrain . Дополнительные сведения см. в следующем разделе, в разделе "Диамбигирование разрешения типа зерна".

Примечание.

Orleans создает типы эталонных реализаций для каждого интерфейса зерна в приложении во время компиляции. Эти эталонные реализации наследуются от Orleans.Runtime.GrainReference класса. GetGrain возвращает экземпляры созданной Orleans.Runtime.GrainReference реализации, соответствующей запрошенной интерфейсу зерна.

Диамбигирование разрешения типа зерна

При наличии нескольких реализаций интерфейса зерна, например в следующем примере, Orleans пытается определить предназначенную реализацию при создании ссылки на зерно. Рассмотрим следующий пример, в котором есть две реализации 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
}

Следующий вызов вызывает GetGrain исключение, так как Orleans не знает, как однозначно сопоставить ICounterGrain один из классов зерна.

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

Сообщение об ошибке сообщает, какая реализация Orleans зерна соответствует запрашиваемому типу интерфейса зерна. ICounterGrain В нем показаны имена типов зерна (upcounter и downcounter) и классы зерна (UpCounterGrain и DownCounterGrain).

Примечание.

Имена типов зерна в предыдущем сообщении upcounter об ошибке и downcounterпроизводные от имен классов UpCounterGrain зерна и DownCounterGrain соответственно. Это поведение Orleans по умолчанию и его можно настроить, добавив [GrainType(string)] атрибут в класс зерна. Например:

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

Существует несколько способов устранения неоднозначности, подробно описанных в следующих подразделах.

Диамбигирование типов зерна с помощью уникальных интерфейсов маркеров

Самый четкий способ диамбигуации этих зерен заключается в том, чтобы дать им уникальные интерфейсы зерна. Например, если мы добавим интерфейс IUpCounterGrain к UpCounterGrain классу и добавим интерфейс IDownCounterGrain в DownCounterGrain класс, например в следующем примере, мы можем разрешить правильную ссылку на зерно, передав IUpCounterGrain или IDownCounterGrain GetGrain<T> вызов вместо передачи неоднозначного 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
}

Чтобы создать ссылку на любое зерно, рассмотрим следующий код:

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

Примечание.

В предыдущем примере вы создали две ссылки на зерна с одинаковым ключом, но разными типами зерна. Первым, хранящимся в переменной myUpCounter , является ссылка на зерно с идентификатором upcounter/my-counter. Второй, хранящийся в переменной myDownCounter , является ссылкой на зерно с идентификатором downcounter/my-counter. Это сочетание типа зерна и ключа зерна, который однозначно идентифицирует зерно. 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 и DownCounterGrainOrleans будет выбрано CounterGrain при запросе ссылкиICounterGrain, как показано в следующем примере:

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

Диамбигирование типов зерна путем предоставления разрешенного идентификатора зерна

Некоторые перегрузки 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"));