Saltar a contenido

Andreani.Arq.Orleans.Core

Esta librería contiene la implementación de Grains comunes.

HealthCheck

Implementación de Grain de HealthCheck.

Collections

En Orleans, es vital estar al tanto de los granos generados en nuestro sistema. Aunque a menudo tenemos la capacidad de asignar manualmente el ID para cada grano, hay situaciones en las que este ID es generado automáticamente por el sistema. En estos casos, se hace necesario tener una estrategia para agrupar estos granos según ciertos criterios.

Además, es importante señalar que Orleans no ofrece una funcionalidad nativa para buscar granos basada en campos del estado, similar a una cláusula "WHERE" en SQL. Por lo tanto, la responsabilidad recae en nosotros para crear nuestros propios mecanismos de agrupación de granos.

Esto es crucial por varias razones:

  1. Gestión del Estado: Sin un mecanismo de agrupación, se vuelve complicado gestionar y acceder al estado de granos específicos de manera eficiente.

  2. Monitoreo y Diagnóstico: Agrupar granos facilita el seguimiento y la identificación de problemas, lo cual es esencial para el mantenimiento y la optimización del sistema.

  3. Optimización de Recursos: Al tener agrupaciones claras, podemos implementar estrategias más eficientes para la colocación de granos, lo que a su vez mejora el rendimiento del sistema.

  4. Personalización de la Lógica de Negocio: Al crear nuestros propios agrupadores, tenemos más flexibilidad para implementar lógicas de negocio complejas que requieran la interacción entre múltiples granos.

Por lo tanto, la implementación de mecanismos propios para agrupar granos se convierte en una práctica esencial para la administración efectiva de aplicaciones distribuidas en Orleans.


Directory

El IDirectoryGrain<T> es un grano diseñado para guardar referencias a un grano específico. Implementa IGrainWithStringKey, lo que significa que es esencial asignarle un nombre programático para poder acceder a él más tarde. El tipo genérico T está restringido a los tipos de ID que Orleans puede manejar.

// Implementaciones específicas
public interface IDirectoryGuidGrain : IDirectoryGrain<Guid>{}
public interface IDirectoryStringGrain : IDirectoryGrain<string>{}
public interface IDirectoryIntegerGrain : IDirectoryGrain<long>{}
public interface IDirectoryGuidCompoundGrain : IDirectoryGrain<(Guid, string)>{}
public interface IDirectoryIntegerCompoundGrain : IDirectoryGrain<(long, string)>{}

Dependiendo del tipo de ID que hayas seleccionado para tu grano, deberás optar por una de estas implementaciones específicas.

Ejemplo:

_grainFactory.GetGrain<IDirectoryGuidGrain>(nameof(IPersonGrains))

En este caso, IPersonGrains tiene un ID de tipo Guid, por lo que elegimos IDirectoryGuidGrain.

Si deseas utilizar estos directorios, tu grano debe implementar la interfaz IGrainWithDirectory<T>.

public interface IGrainWithDirectory<T>
{
    Task<T> GetState();
}

Esta interfaz se usa para recuperar el valor del estado del grano cuando se invoca el método GetAll.

Almacenamiento de Referencias

Los granos de tipo Directory mantienen una lista de IDs en su estado, representada por IPersistentState<List<T>> _index. Esta es una lista estándar de C#.

Para añadir o eliminar un elemento, el directorio primero verifica si el elemento ya existe.

Para comprobar la existencia de un elemento, se utiliza el método Exist de Linq.

Advertencia

Estos directorios están diseñados para almacenar un conjunto de referencias fijas. Dado que las listas en C# crecen con cada nuevo elemento, es vital controlar su tamaño para evitar un aumento en la carga de procesamiento del servidor.

Recuperación de Datos

Los directorios emplean una estrategia de paginación basada en marcadores.

El método GetAll devuelve un objeto ResultFindAll.

public record struct ResultFindAll<T>(string? Next, int Limit, T Content);

Donde: 1. Content: Contiene los datos de los granos almacenados. 2. Limit: Especifica el límite de elementos recuperados. 3. Next: Señala la siguiente posición para continuar iterando la lista.

Este método recorre la lista de IDs almacenados en el estado, rehidrata los granos correspondientes y luego invoca el método GetState() de la interfaz IGrainWithDirectory.

Ejemplo de uso

var offset = "";
var limit = 100;
var grains = await _client.GetGrain<IDirectoryStringGrain>(nameof(IUserGrains))
                                      .GetAll<IUserGrains, UserDto>(offset, limit: limit);


BigDirectory

Los BigDirectory son un caso particular de los Directory. Poseen los mismos metodos que los Directory. Los BigDirectory a diferencia de los Directory son estructuras pensadas para almacenar una gran lista de elementos sin afectar la performance del cluster, ya que utiliza en vez de un lista enlazada de C# una lista enlazada desarrollada por Andreani para Orleans.

Almacenamiento de Referencias

Los BigDirectory Almacenan una referencia a un ICollectionGrain y a IBloomFilterCollectionGrain.

Cuando se almacena un elemento, se valida que no exista previamente utilizando BloomFilter y luego es agregado a ambas collecciones. Para remover un elemento, se valida que exista y luego solo se remueve de la CollectionGrain, esto es debido a que no es posible eliminar un elemento de BloomFilter.

Recuperación de Datos

Al igual que los Directory, los BigDirectory tienen el mismo input y output en el metodo GetAll

Su funcionamiento en vez de iterar una lista de Id, itera sobre la ICollectionGrain obteniendo los valores almacenados con estrategia Latest

ICollectionGrain

El ICollectionGrain es un grain que tiene por estado la referencia el primer y último grano de la cadena.

CollectionGrain

El ICollectionGrain implementa el patron de comportamiento "Iterator" para recorrer la estructura de los IStorageGrain

Los IStorageGrain son los elementos de esta colección, estos almacenan el valor que queremos almacenar y la referencia al anterior y al posterior en la cadena.

El grain IStorageGrain tiene la siguiente definición:

public interface IStorageGrain<T> : IGrainWithGuidKey
{
    Task<Guid?> Previus();
    Task<Guid?> Next();
    Task<T> Value();
    Task SetNext(Guid? next);
    Task SetPrevius(Guid? previus);
    Task SetValue(T value);
    Task Save(T value, Guid? previus, Guid? next);
}

En resumen el StoregaGrain no es más que un grain que no tiene comportamiento, solo almacena información que es explotada por el ICollectionGrain.

La defición de la interface de ICollectionGrain es:

public interface ICollectionGrain<T> : IGrainWithGuidKey
{
    Task<IStorageGrain<T>?> Current(); 
    Task AddItem(T item);
    Task RemoveItem(T item);
    Task ChangeDirection(IteratorDirection direction);
    Task<bool> MoveNext();
    Task Skip(Guid id);
    Task Reset();
    Task<Guid?> HowIsNext();
}

  1. Current: Devuelve el Grain de la posición actual.
  2. AddItem: Agrega un nuevo item a la lista. El proceso de agregado de item es: se crea el grain de StorageGrain con el valor que queremos almacenar, se setea el next en null y el previus al id del LastGrain de la cadena y este nuevo Grain pasa a ser el nuevo LastGrain de la cadena. Por ultimo se apunta el valor de next del viejo LastGrain al nuevo LastGrain
  3. RemoveItem: Remueve un item de la cadena. Para el proceso de remove, se busca el elemento a eliminar en la cadena, y se modifican los grain que estaban antes y despues en la cadena, quitando la referencia del grain a eliminar y apuntandose entre estos respectivamente.
  4. ChangeDirection: Cambia la dirección de recorrido de la lista. Por defecto es Latest | Opciones: Earliest,Latest
  5. MoveNext: Si existe una siguiente posición en la dirección de recorrido, mueve el puntero en la dirección necesaria y devuelve true, en caso de no tener una siguiente posición devuelve false
  6. Skip: Posiciona el Current en la referencia solicitada.
  7. Reset: Reinicia el Current.
  8. HowIsNext: Devuelve la referencia al siguiente valor. Esto depende de la dirección en la que se este recorriendo.

Diagrama AddItem

AddItem

Diagrama RemoveItem

RemoveItem

Ejemplo de uso:

do
{
    var element = await _collection.Current();
    if (element == null)
        break;
    Console.WriteLine(await element.Value());
}
while (await _collection.MoveNext());

IBloomFilterCollectionGrain

El componente IBloomFilterCollectionGrain se fundamenta en la aplicación del patrón de diseño BloomFilter.

Este componente, IBloomFilterCollectionGrain, mantiene una referencia a una ICollectionGrain. En esta colección se guarda el arreglo de bytes que el BloomFilter utiliza.

La interfaz se define de la siguiente manera:

public interface IBloomFilterCollectionGrain<T> : IGrainWithGuidKey
{
    Task AddAsync(T id);
    Task<bool> Exist(T id);
}

Estrategia de Almacenamiento

Los parámetros estándar para el BloomFilter son los siguientes:

  1. Número de elementos (n) = 100000
  2. Algoritmo de Hash = Murmur32BitsX86
  3. Cantidad de Funciones Hash (k) = 17
  4. Tamaño del BitArray (m) = 2396272 (aproximadamente 290 KB)
  5. Tasa de Error (P) = 0.00001

Nota

El número de elementos se estableció en este valor para minimizar el uso excesivo de memoria. Siguiendo la misma lógica que con los BigDirectory, el arreglo de bits se guarda en una ICollectionGrain y se crea uno nuevo cada vez que se alcanza la meta de 100,000 registros.

Estrategia de Búsqueda

Para determinar si un elemento existe, se realiza una búsqueda en la lista de BloomFilters. Si el elemento no se encuentra en el lote actual, se carga el siguiente lote de la lista hasta llegar al final. Si el elemento no se encuentra en ninguno de los lotes, se devuelve false.

Ejemplo de Uso

Guid guid = Guid.NewGuid();
_bloomFilter = _grainFactory.GetGrain<IBloomFilterCollectionGrain<long>>(guid);

// Add
var id = 123456;
await _bloomFilter.AddAsync(id);

// Exist
bool exist = await _bloomFilter.Exist(id)