Operaciones de Gridwich para Azure Storage

Azure Storage

El servicio Azure Storage para Gridwich, Gridwich.SagaParticipants.Storage.AzureStorage, proporciona operaciones de blobs y contenedores para las cuentas de Azure Storage configuradas para Gridwich. Crear blob, Eliminar contenedor, Copiar blob o Cambiar nivel de acceso son algunos ejemplos de operaciones de almacenamiento.

Gridwich requiere que sus mecanismos de almacenamiento funcionen tanto para blobs en bloques como para contenedores de Azure Storage. Con clases y operaciones del servicio de almacenamiento distintas para blobs y contenedores, no hay ambigüedad sobre si una operación de almacenamiento determinada se relaciona con un blob o con un contenedor. Este artículo se aplica a blobs y contenedores, excepto en los casos en los que se indique.

Gridwich expone la mayoría de las operaciones de almacenamiento a los sistemas externos dentro del Storage.AzureStorageparticipante de la saga. Otros participantes de la saga usan el servicio de almacenamiento para tareas como la copia de blobs entre diferentes contenedores o cuentas al configurar los flujos de trabajo de codificación.

En este artículo se describe cómo cumple los requisitos de la solución el servicio Azure Storage para Gridwich y cómo se integra con mecanismos como los controladores de eventos. Los vínculos apuntan al código fuente correspondiente, que contiene comentarios más extensos sobre los contenedores, las clases y los mecanismos.

SDK de Azure Storage

Gridwich usa clases del SDK de Azure Storage para interactuar con Azure Storage en lugar de tratar de forma manual con solicitudes REST. En el proveedor de almacenamiento, las clases de SDK BlobBaseClient y BlobContainerClient administran solicitudes de almacenamiento.

Actualmente, estas clases de cliente del SDK solo permiten un acceso indirecto a los dos encabezados HTTP que Gridwich debe manipular, x-ms-client-request-id para el contexto de la operación y ETag para la versión del objeto.

En Gridwich, un par de clases de proveedor dispensa la funcionalidad BlobBaseClientProvider y BlobContainerClientProvider en unidades denominadas sleeves. Para más información sobre las cubiertas, consulte Cubiertas de almacenamiento.

En el diagrama siguiente se muestra la estructura de las clases del SDK y Gridwich, y cómo se relacionan entre sí las instancias. Las flechas indican "tiene una referencia a".

Diagram showing client object instance relationships between the Storage SDK classes.

Directiva de canalización

El enlace se establece para manipular los encabezados HTTP como una instancia de la directiva de canalización cuando se crea la instancia de cliente. Esta directiva solo se puede establecer en el momento de la creación de la instancia de cliente y no se puede cambiar. El código del proveedor de almacenamiento que usa el cliente debe ser capaz de manipular los valores del encabezado durante la ejecución. El reto es hacer que el proveedor de almacenamiento y la canalización interactúen sin problemas.

Para la directiva de canalización de Gridwich, consulte la clase BlobClientPipelinePolicy.

Almacenamiento en caché del servicio de almacenamiento

La autenticación y el establecimiento de la conexión TCP crean una sobrecarga cuando una instancia de objeto de cliente del SDK envía su primera solicitud a Azure Storage. La sobrecarga está compuesta por varias llamadas al mismo blob en una solicitud del sistema externo, por ejemplo Obtener metadatos seguido de Eliminar blob.

Para mitigar la sobrecarga, Gridwich mantiene una memoria caché de una instancia de cliente para cada blob o contenedor de almacenamiento, en función de las clases del SDK que usa el contexto de la operación. Gridwich conserva esta instancia de cliente y puede usarla para varias operaciones de Azure Storage en el mismo blob o contenedor durante el tiempo que dure una solicitud del sistema externo.

Las clases de cliente proporcionadas por el SDK de Azure requieren que las instancias de objeto de cliente del SDK sean específicas para un único blob o contenedor en el momento de la creación. Las instancias tampoco tienen seguridad garantizada para el uso simultáneo en diferentes subprocesos. Dado que un contexto de operación representa una solicitud única, Gridwich basa el almacenamiento en caché en la combinación del nombre del contenedor o el blob con el contexto de la operación.

La reutilización de esta instancia, combinada con la estructura del cliente del SDK de Azure Storage, requiere código de soporte adicional para equilibrar la eficiencia y la claridad del código.

Argumento de contexto

Casi todas las operaciones del servicio de almacenamiento de Gridwich requieren un argumento de contexto especial de tipo StorageClientProviderContext. Este argumento de contexto cumple los siguientes requisitos:

  • Proporciona respuestas al sistema externo, entre las que se incluye el valor del contexto de la operación basado en JSON único para cada solicitud que el sistema externo especificó en la solicitud a Gridwich. Para más información, consulte Contexto de la operación.

  • Permite a los autores de llamadas al servicio de almacenamiento, como los controladores de eventos de Gridwich, controlar qué respuestas son visibles para el sistema externo. Este control evita que el servicio sature al sistema externo con eventos de notificación que no corresponden. Para más información, consulte Silenciamiento del contexto.

  • Cumple con las convenciones de Azure Storage para garantizar solicitudes y respuestas coherentes en un entorno que permite una combinación de lectores y escritores en paralelo. Por ejemplo, admite el seguimiento de ETag. Para más información, consulte ETags.

Contexto de almacenamiento

El contexto para los tipos de almacenamiento del blob y el contenedor es el elemento StorageClientProviderContext, que tiene el siguiente aspecto:

    string  ClientRequestID { get; }
    JObject ClientRequestIdAsJObject { get; }
    bool    IsMuted { get; set; }
    string  ETag { get; set; }
    bool    TrackingETag { get; set; }

Las dos primeras propiedades son representaciones diferentes del contexto de la operación que se usó para inicializar la instancia de StorageClientProviderContext. La clase tiene varios constructores, incluido un constructor de copia. Entre los métodos adicionales se incluyen ResetTo, para permitir la duplicación de estado local y el método estático CreateSafe para asegurarse de que las inicializaciones problemáticas no inician excepciones.

La clase también contiene un control especial para crear contextos basados en GUID y cadenas vacías. Los controladores de notificación de Azure Storage para blobs creados y eliminados, que también procesan las notificaciones que proceden de agentes externos, requieren el formato GUID.

Silenciamiento del contexto

La propiedad IsMuted controla si la aplicación espera que el servicio publique las notificaciones resultantes de vuelta al autor de llamada, por ejemplo, al sistema externo. En una operación silenciada, el servicio no publica los eventos resultantes.

Un ejemplo son las copias de blobs que ejecuta un codificador para organizar los blobs en Azure Storage como entrada para una tarea de codificación. El sistema externo no se preocupa de estos detalles, sino solo sobre el estado del trabajo de codificación y dónde puede recuperar las salidas codificadas. Para reflejar estos problemas, el codificador:

  1. Crea un contexto de almacenamiento sin silenciar basado en el contexto de la operación de solicitud, por ejemplo ctxNotMuted.

  2. Crea un contexto de almacenamiento silenciado, por ejemplo ctxMuted, mediante el uso del constructor de copia de la clase de contexto o la creación de una nueva instancia. Ambas opciones tendrán el mismo valor de contexto de la operación.

  3. Especifica ctxMuted para las operaciones de almacenamiento implicadas en la configuración de la codificación. El sistema externo no ve ninguna indicación de que estas operaciones se llevan a cabo.

  4. Especifica el contexto ctxNotMuted para las operaciones de almacenamiento que reflejan la finalización de la codificación, por ejemplo, la copia a un archivo de salida en un contenedor de destino. Los controladores de Gridwich publican los eventos de notificación de Azure Storage resultantes al sistema externo.

El autor de llamada controla la visibilidad final de las operaciones. Tanto las operaciones silenciadas como las no silenciadas se basan en un valor de operationContext equivalente. La intención del silenciamiento del contexto es facilitar el diagnóstico de problemas a partir de los registros de seguimiento de eventos, ya que es posible ver las operaciones de almacenamiento relacionadas con una solicitud, independientemente del estado de silenciamiento de la operación.

El elemento ResponseBaseDTO tiene la propiedad booleana DoNotPublish, que el evento que envía utiliza para dictar la decisión final sobre si se debe publicar. El evento que envía, a su vez, establece la propiedad DoNotPublish en función de la propiedad IsMuted del contexto.

El servicio transmite la configuración de silenciamiento a Azure Storage, que establece el elemento clientRequestId en los eventos de notificación de almacenamiento que presenta a los dos controladores de Gridwich, creados y eliminados. Estos dos controladores establecen DoNotPublish para reflejar el silenciamiento solicitado por el autor de llamada.

ETags para la coherencia de destino

Azure Storage utiliza el encabezado HTTP ETag para las secuencias de solicitudes que deben tener coherencia de destino. Un ejemplo es asegurarse de que un blob no ha cambiado entre las operaciones de almacenamiento Recuperar metadatos y Actualizar metadatos.

Para su alineación con el uso estándar de HTTP, este encabezado tiene un valor opaco cuya interpretación es que, si el valor del encabezado cambia, el objeto subyacente también ha cambiado. Si una solicitud envía su valor de ETag actual para el objeto y no coincide con el valor de ETag actual del servicio de almacenamiento, se produce inmediatamente un error en la solicitud. Si la solicitud no incluye un valor de ETag, Azure Storage omite esa comprobación y no bloquea la solicitud.

ETags en el servicio de almacenamiento

En el caso de Gridwich, el elemento ETag es un detalle interno entre el servicio de almacenamiento de Gridwich y Azure Storage. Ningún otro código necesita tener en cuenta el elemento ETag. El servicio de almacenamiento utiliza el elemento ETag para secuencias como las operaciones Obtener metadatos del blob seguida de Eliminar blob para procesar una solicitud BlobDelete Event. El uso del elemento ETag garantiza que la operación Eliminar blob tiene como destino exactamente la misma versión del blob que la operación Obtener metadatos.

Para usar el elemento ETag para el ejemplo anterior:

  1. Envíe la solicitud Obtener metadatos con un elemento ETag en blanco.
  2. Guarde el valor del elemento ETag de la respuesta.
  3. Agregue el valor del elemento ETag guardado a la solicitud Eliminar blob.

Si los dos valores de ETag son diferentes, se produce un error en la operación de eliminación. El error implica que alguna otra operación ha cambiado el blob entre los pasos 2 y 3. Repita el proceso desde el paso 1.

ETag es un parámetro de los constructores y una propiedad de cadena de la StorageClientProviderContext class. Solo el elemento BlobClientPipelinePolicy específico de Gridwich manipula el valor de ETag.

Control del uso de ETag

La propiedad TrackingETag controla si se va a enviar el valor de ETag en la siguiente solicitud. El valor true significa que el servicio envía un elemento ETag si hay uno disponible.

Una solicitud de Azure Storage con un valor de ETag que no coincide con el blob o el contenedor del asunto produce un error en la operación. Este error se produce por diseño, porque es la forma HTTP estándar de expresar "la versión exacta a la que se va a ETag dirigir la solicitud". Las solicitudes pueden incluir la propiedad TrackingETag para indicar que ETags debe coincidir o no incluir la propiedad TrackingETag para indicar que los valores ETag no importan.

La canalización siempre recupera un valor de ETag de una operación de Azure Storage si hay uno presente en esa respuesta REST. La canalización siempre actualiza la propiedad ETag del contexto, si es posible, a partir de la última operación. La marca TrackingETag solo controla si la siguiente solicitud de la misma instancia de cliente envía el valor de la propiedad ETag. Si el valor de ETag es null o está vacío, la solicitud actual no establece ningún valor HTTP de ETag, independientemente del valor de TrackingETag.

Cubiertas de almacenamiento

Gridwich requiere que sus mecanismos de almacenamiento funcionen tanto para blobs en bloques como para contenedores de Azure Storage. Hay clases y operaciones del servicio de almacenamiento distintas para blobs y contenedores, por lo que no hay ambigüedad sobre si una operación de almacenamiento determinada se relaciona con un blob o con un contenedor.

Un par de clases de proveedor, una para blobs y otra para contenedores, dispensan los dos conjuntos de funciones en unidades llamadas cubiertas. Las cubiertas contienen instancias de clases auxiliares de almacenamiento que forman parte de Azure SDK. La inicialización del servicio de almacenamiento crea los proveedores y los pone directamente a disposición de los métodos del servicio de almacenamiento.

Estructura de la cubierta

La cubierta es un contenedor para la instancia de objeto de cliente del SDK y un contexto de almacenamiento. Las funciones del proveedor de almacenamiento hacen referencia a la cubierta mediante dos propiedades: Client y Context. Hay un tipo de funda para blobs y otro para contenedores, que tienen Client propiedades de tipo BlobBaseClient y BlobContainerClient, respectivamente.

La estructura general de la cubierta para blobs tiene el siguiente aspecto:

    BlobBaseClient Client { get; }
    BlobServiceClient Service { get; }
    StorageClientProviderContext Context { get; }

La propiedad Service de la cubierta es por comodidad. Algunas de las operaciones finales relacionadas con el codificador que usan la clase BlobServiceClient del SDK requieren las claves de la cuenta de almacenamiento. Este requisito llevó a agregar una instancia de cliente de servicio a los dos tipos de cubierta existentes, en lugar de producir un proveedor independiente.

Uso de la cubierta

Los proveedores de almacenamiento de cliente dispensan instancias de cubiertas. El código del servicio de almacenamiento es similar a la siguiente secuencia de código anotado, con los tipos destacados para mayor claridad:

    public bool DeleteBlob(Uri sourceUri, StorageClientProviderContext context)
    {
        . . .
        StorageBlobClientSleeve sleeve = _blobBaseClientProvider.GetBlobBaseClientForUri(sourceUri, context); // Line A
        BlobProperties propsIncludingMetadata = sleeve.Client.GetProperties(); // Line B
        sleeve.Context.TrackingETag = true;   // Send ETag from GetProperties()
        var wasDeleted = sleeve.Client.DeleteBlob(); // Line C
        sleeve.Context.TrackingETag = false;
        var someResult = sleeve.Client.AnotherOperation(); // Line D
        . . .
    }
  1. Gridwich rellena automáticamente el contexto de la operación en el contexto de la cubierta en la línea A. El valor predeterminado de TrackingETag es false.
  2. Después de la línea B, sleeve.Context contiene el valor de ETag de la línea A y conserva el mismo valor de ClientRequestID.
  3. La línea C envía el valor de ETag de la línea B y el valor de ClientRequestId.
  4. Después de la línea C, el contexto tiene un nuevo valor de ETag, tal y como se devuelve en la respuesta de Delete().
  5. La línea D no envía un valor de ETag en la solicitud para AnotherOperation().
  6. Después de la línea D, el contexto tiene un nuevo valor de ETag, tal y como se devuelve en la respuesta de AnotherOperation().

El servicio de almacenamiento está establecido actualmente como Transient en la configuración de inserción de dependencias, lo que implica que el almacenamiento en caché basado en cubiertas realiza para cada solicitud. Consulte Servicio de almacenamiento e inserción de dependencias para más información.

Alternativas del servicio de almacenamiento

En las secciones siguientes se describen enfoques alternativos que no forman parte de la solución de almacenamiento de Gridwich actual.

Clase AzureStorageManagement de Gridwich

Junto con el miembro Service de la cubierta, que es una instancia de la clase BlobServiceClient de Azure SDK, Gridwich también tiene la clase AzureStorageManagement. El método GetConnectionStringForAccount del servicio de almacenamiento y el método GetStoreByNameAsync de la codificación de Telerek usan esa clase para obtener las claves de la cuenta de almacenamiento. La clase se basa actualmente en el marco de trabajo Fluent. Las adiciones a la clase BlobServiceClient del SDK deberían reemplazar esta clase, lo que permite una recuperación de información más específica que la amplia variedad de la interfaz IAzure de Fluent.

Ocultar la directiva de canalización mediante subclases

La subclase de los tipos de cliente del SDK agrega dos propiedades simples al cliente, una para cada valor de encabezado HTTP, para ocultar completamente la interacción con la directiva de canalización. Pero debido a un error de Moq, no es posible crear pruebas unitarias mediante mock para estos tipos derivados. Gridwich usa Moq, por lo que no utiliza este enfoque de subclases.

El error de Moq se relaciona con un control incorrecto de las subclases entre ensamblados en presencia de funciones virtuales de ámbito interno. Las clases de cliente del SDK hacen uso de funciones virtuales de ámbito interno que implican tipos de ámbito interno que son invisibles para los usuarios externos normales. Cuando Moq intenta crear un mock de la subclase, que se encuentra en uno de los ensamblados de Gridwich, se produce un error en tiempo de ejecución de la prueba, ya que no puede encontrar las clases virtuales de ámbito interno en las clases de cliente del SDK de las que se derivan las clases de Gridwich. No hay ninguna solución alternativa sin realizar cambios en la generación del servidor proxy Castle de Moq.

Servicio de almacenamiento e inserción de dependencias

Gridwich registra actualmente el servicio Storage como un servicio de inserción de dependencias de tipo Transient. Es decir, cada vez que se solicita la inserción de dependencias para el servicio, se crea una nueva instancia. El código actual también debe funcionar correctamente si el registro cambia a Scoped, lo que implica una instancia por solicitud; por ejemplo, la solicitud del sistema externo.

Sin embargo, habrá problemas si el registro cambia a Singleton, una instancia para toda la aplicación de funciones de Gridwich. El mecanismo de almacenamiento en caché de Gridwich para las cubiertas y los intervalos de bytes de datos no distinguirá entre las distintas solicitudes. Además, el modelo de almacenamiento en caché no es de extracción, por lo que Gridwich no elimina la instancia de la memoria caché mientras está en uso. Puesto que no se garantiza que las clases de cliente del SDK sean seguras para subprocesos, la coordinación requiere una serie de cambios.

Por estos motivos, no cambie el servicio de almacenamiento de Gridwich, tal y como está, al registro de inserción de dependencias Singleton. Gridwich sigue esta regla en el registro de inserción de dependencias e incluye una prueba unitaria, CheckThatStorageServiceIsNotASingleton, para aplicarla.

Pasos siguientes

Documentación del producto:

Módulos de Microsoft Learn: