Uso de Patrones de Diseño en la nube - Parte I

Patrones
Vs Categorías

Como la gran
mayoría de buenos desarrolladores saben, los patrones de diseño son paradigmas
que aportan soluciones a problemas
típicos y recurrentes que nos podemos encontrar a la hora de
desarrollar una aplicación.  Seguramente
que la aplicación que estamos desarrollando sea exclusiva, pero tendrá partes
comunes con otras aplicaciones: acceso a datos, creación de objetos,
operaciones entre sistemas etc. En lugar de reinventar la rueda, podemos
solucionar problemas utilizando algún patrón, ya que son soluciones probadas y
documentadas por multitud de programadores.

Como comentaba al
principio, las 8 categorías que se han definido para tratar de englobar las áreas
en la nube en las que podemos encontrar problemas comunes son:

  • Disponibilidad:
    Define la proporción de tiempo que el sistema es funcional. Se verá afectado
    por los errores del sistema, problemas de infraestructura, los ataques maliciosos
    y la carga del sistema. Por lo general se mide como un porcentaje del tiempo de
    funcionamiento. Las Aplicaciones deben ser diseñadas e implementadas de manera
    que garantice la máxima disponibilidad.
  • Gestión
    de datos
    :  Es el elemento clave de
    las aplicaciones de la nube, y la mayoría de las influencias de los atributos
    de calidad. Los datos son normalmente alojados en diferentes lugares y en
    varios servidores por razones como el rendimiento, la escalabilidad o la
    disponibilidad, y esto puede presentar problemas. Por ejemplo, la consistencia
    de datos se debe mantener, y los datos normalmente tendrán que ser
    sincronizados a través de diferentes lugares.
  • Diseño e Implementación: Un buen diseño abarca factores como la
    consistencia y la coherencia en el diseño de componentes y sus despliegues,
    facilidad de mantenimiento para simplificar la administración y el desarrollo,
    y para permitir la reutilización de componentes y subsistemas para su uso en
    otras aplicaciones y en otros escenarios. Las decisiones tomadas durante la
    fase de diseño e implementación tienen un gran impacto en la calidad y el costo
    total de las aplicaciones y servicios alojados en la nube.
  • Mensajería:
     La naturaleza distribuida de las aplicaciones
    en la nube requiere una infraestructura de mensajería que conecte los
    componentes y los servicios, idealmente de una manera imprecisa con el fin de
    maximizar la escalabilidad. La Mensajería asíncrona es ampliamente usada, y
    proporciona muchos beneficios, pero también trae desafíos tales como el ordenar
    los mensajes, gestión de mensajes dudosos, etc.
  • Gestión
    y Seguimiento
    :  Las aplicaciones de
    la nube se ejecutan en un centro de datos remoto en el que no tiene el control
    total de la infraestructura o, en algunos casos, el sistema operativo. Esto
    puede hacer que la gestión y el seguimiento sea más difícil que un despliegue on-premises.
    Las solicitudes deben exponer información de tiempo de ejecución para que los
    administradores y los operadores pueden usar para administrar y supervisar el
    sistema, así controlar los requerimientos de cambio del negocio y las personalizaciones
    sin necesidad de que la aplicación sea detenida o redistribuida.
  • Rendimiento
    y Escalabilidad
    :  El rendimiento es
    un indicador de la capacidad de respuesta de un sistema para ejecutar cualquier
    acción dentro de un intervalo de tiempo estimado, mientras que la escalabilidad
    es la capacidad de un sistema, ya sea para manejar los aumentos de carga sin
    impacto en el rendimiento o para que los recursos disponibles incrementen
    fácilmente. Las aplicaciones en la nube generalmente se encuentran con cargas y
    picos de trabajo variables de actividad. La predicción de éstos, especialmente
    en un escenario multi-tenant, es casi imposible. En su lugar, las aplicaciones
    deben ser capaces de escalar dentro de los límites para satisfacer los picos de
    demanda, y la escala en que la demanda disminuye.
  • Resiliencia: La capacidad de un sistema
    para manejar con solvencia los fallos y recuperarse de los mismos. La
    naturaleza de cloud hosting donde las aplicaciones son a menudo multi-tenant,
    el uso compartido de los servicios de la plataforma, la competencia por
    recursos y ancho de banda, y el hecho de estar corriendo en el mismo hardware
    significa que hay una mayor probabilidad de que surjan fallos transitorios y
    más permanentes. La detección de fallos y la recuperación rápida y eficiente es
    clave en este escenario.
  • Seguridad:
    La capacidad de un sistema para evitar acciones maliciosas o accidentales fuera
    del uso diseñado, y para evitar la divulgación o pérdida de información. Las
    aplicaciones en la nube están expuestas en Internet, a menudo son expuestas con
    carácter público. Las aplicaciones deben ser diseñadas y desplegadas de una
    manera que estén protegidas de los ataques maliciosos, restringir el acceso
    sólo a los usuarios sólo permitidos, y proteger los datos sensibles.

Para cada una de
estas categorías, veremos patrones comunes diseñados para ayudar a los
desarrolladores a resolver los problemas a los que se enfrentan con
regularidad.

Nosotros veremos,
a lo largo de varios artículos, en que categorías se pueden utilizar, el
contexto y problema que resuelve, la solución, consideraciones, cuando usarlo y
un ejemplo de su uso. En este primer artículo veremos el primer patrón, para
seguir en los siguientes artículos con los varios patrones más.

Patrón
Cache-Aside

Categorías donde
aplicarlo: Gestión de Datos y Rendimiento y Escalabilidad

​Cargar los datos
mayoritariamente demandados en una memoria caché de un almacén de datos. Este
patrón puede mejorar el rendimiento y también ayuda a mantener la coherencia
entre los datos almacenados en la memoria caché y los datos en el almacén de
datos subyacente.

Contexto y Problema

Las aplicaciones
utilizan una caché para optimizar el acceso repetido a la información en un
almacén de datos. Sin embargo, para ello hemos de estar seguros que los datos
almacenados en caché tienen que ser consistentes con los datos en el almacén de
datos. Las aplicaciones deben implementar una estrategia que ayuda a asegurar
que los datos en la caché estén actualizados en la manera de lo posible, pero
también pueden detectar y manejar situaciones que surjan cuando los datos de la
caché estén obsoletos.

Solución

Muchos de los
sistemas de almacenamiento en caché comerciales proporcionan lectura y
escritura a través de las operaciones de lectura / escritura de la base de
datos subyacente. En estos sistemas, una aplicación recupera los datos de la
memoria caché. Si los datos no están en la caché, se recuperan de forma
transparente del almacén de datos subyacente y se agrega a la caché. Cualquier
modificación de los datos almacenados en la memoria caché se escriben
automáticamente al almacén de datos también.

Para cachés que
no proporcionan esta funcionalidad, es responsabilidad de las aplicaciones que
utilizan la memoria caché para mantener los datos en la caché.

Una aplicación
puede emular esta funcionalidad implementando el patrón Cache-Aside. Este
patrón carga de forma efectiva datos en la caché bajo demanda.

Consideraciones

Hay que tener en
cuenta los siguientes puntos en el momento de decidir la forma de aplicar este
patrón:

•         Vida útil de los
datos almacenados en caché
. Muchos cachés implementan una directiva de caducidad que
hace que los datos sean invalidados y se eliminan de la memoria caché si no se
accede por un período determinado. Para que este patrón sea efectivo, asegúrate
de que la política de vencimiento coincide con el patrón de acceso para las
aplicaciones que utilizan los datos. Recuerda que el almacenamiento en caché es
más eficaz para los datos relativamente estáticos o datos que se leen con
frecuencia.

•         El vaciado de Datos. La mayoría de los
cachés tienen un tamaño limitado en comparación con el almacén de datos de
donde se originan los datos, y van a vaciar a los datos si es necesario. La
mayoría de los cachés adoptan una política de uso de utilización más reciente
para seleccionar los elementos a vaciar, pero esto se puede personalizar.
Configure la propiedad global de vencimiento y otras propiedades de la memoria
caché, y la propiedad de vencimiento de cada elemento almacenado en caché, para
ayudar a asegurar que el caché es rentable. No siempre puede ser adecuado
aplicar una política de vaciado global para todos los elementos de la caché.
Por ejemplo, si un elemento de la caché es muy costoso de recuperar del almacén
de datos, puede ser beneficioso mantenerlo en la memoria caché a expensas de
otros elementos a los que son accedidos con mayor frecuencia, pero son menos
costosos.

•         Precargar la Caché. Muchas soluciones
rellenan previamente la memoria caché con los datos que es probable que
necesite como parte del proceso de inicio de una aplicación. Este patrón puede
ser útil si algunos de estos datos expiran o son eliminados.

•         Consistencia. Implementar
este patrón no garantiza la coherencia entre el almacén de datos
y la memoria caché. Un elemento en el almacén de datos se puede cambiar en cualquier momento por
un proceso externo, y este cambio
no se refleja en la memoria caché
hasta la próxima vez que el elemento se
cargue en la memoria caché. En un sistema que replica datos a través de almacenes de datos,
este problema puede llegar a ser especialmente
grave si se produce la
sincronización con mucha frecuencia.

•         Almacenamiento en
caché local (In-Memory)
. Una caché podría ser local a una
instancia de la aplicación y ser almacenada en memoria. El patrón puede ser
útil en este entorno si una aplicación accede repetidamente a los mismos datos.
Sin embargo, si una caché local es privada y las diferentes instancias de la
aplicación tienen una copia de los mismos datos en la caché, los datos podrían
convertirse rápidamente inconsistentes entre cachés, por lo que puede ser
necesario revisar las políticas de cuando expiran los datos almacenados en una
memoria caché privada y actualizarlos con mayor frecuencia. En estos escenarios
puede ser apropiado el uso de un mecanismo de caché distribuida.

Cuando usar este patrón:

•         La caché no facilita operaciones nativas de lectura y escritura.

•         La demanda de recursos es impredecible. Este modelo permite
a las aplicaciones cargar datos bajo demanda. Se
predicen qué datos de una
aplicación requerirá de antemano.

Este patrón podría no ser adecuado:

•         Cuando el conjunto de datos en
caché es estático

•         Para el
almacenamiento en caché de la
información de estado de sesión en una aplicación web
alojada en unagranja de servidores web. En
este entorno, se debe evitar la
introducción de dependencias basadas en
la afinidad de
cliente-servidor.

Ejemplo:

En Windows Azure se puede usar Windows Azure Caché para crear una memoria caché distribuida que puede ser compartida por varias instancias de una aplicación. El método GetMyEntityAsync en el siguiente ejemplo de código muestra una implementación de este patrón basado en Windows Azure caché. Un objeto se identifica mediante el uso de un ID con valor de  número entero como clave. El método GetMyEntityAsync genera un valor de cadena con base en esta clave (la API de Windows Azure caché utiliza cadenas de valores de clave) y trata de recuperar un elemento con esta clave de la caché. Si se encuentra un elemento coincidente, lo devuelve. Si no hay ninguna coincidencia en la caché, el método GetMyEntityAsync recupera el objeto de un almacén de datos, lo agrega a la caché, y luego la devuelve (el código que realmente recupera los datos del almacén de datos se ha omitido debido a que es el almacén de datos dependiente). Hay que tener en cuenta que el elemento en caché está configurado para expirar a fin de evitar de que se quede obsoleto si se actualiza en cualquier otro lugar.

1private DataCache cache;
2...
3public
4async Task<MyEntity> GetMyEntityAsync(int id) {
5
1// Define una
2clave única para este método y sus parámetros.
3
1var key = string.Format("StoreWithCache_GetAsync_{0}", id);
2
1var expiration = TimeSpan.FromMinutes(3);
2
1bool cacheException = false;
2
1try
2
1{
2
1// Intentamos
2obtener la clave de la caché.
3
1var cacheItem = cache.GetCacheItem(key);
2
1if (cacheItem != null)
2
1{
2
1return cacheItem.Value as MyEntity;
2
1}
2
1}
2
1catch (DataCacheException)
2
1{
2
1// si hay
2algún problema lanzará una excepción
3
1//  y evitará el uso de la caché para el resto de
2la llamada.
3
1cacheException
2= true;
3
1}
2
1// Si hay un
2error de caché, obtenemos el id del almacén original y // lo almacenaremos en
3la caché.
4
1// el código
2ha sido omitido por ser de un almacén dependiente este // ejemplo.
3
1var entity = ...;
2
1if (!cacheException)
2
1{
2
1try
2
1{
2
1// Evitamos
2el almacenamiento en caché de un valor nulo
3
1if (entity != null)
2
1{
2
1// Ponemos el
2elemento con tiempo de caducidad dependiendo de la
3
1// criticidad
2que pueda ser tener datos obsoletos
3
1cache.Put(key, entity, timeout: expiration);
2
1}
2
1}
2
1catch (DataCacheException)
2
1{
2
1// If there is a cache related issue, ignore it
2
1// and just return the entity.
2
1}
2
1}
2
1return entity;
2
1 }​
2

El método UpdateEntityAsync
que voy a mostrar a continuación muestra
cómo invalida un objeto en la caché cuando el valor es modificado por la aplicación. Este es un ejemplo de un enfoque de escritura simultánea. El código actualiza el almacén de datos original y luego elimina el elemento de la caché de la caché mediante una llamada al método Remove, especificando la clave (el
código para esta parte de la
funcionalidad se ha omitido, ya que será el almacén de datos dependiente).

1public async Task UpdateEntityAsync(MyEntity entity) {
2
1// Actualizar
2el objeto en el almacén de datos original.
3
1await this.store.UpdateEntityAsync(entity).ConfigureAwait(false);
2
1// Obtener la
2clave correcta para el objeto en caché.
3
1var key = this.GetAsyncCacheKey(entity.Id);
2
1// Entonces,
2Eliminamos el actual objeto de la caché
3
1this.cache.Remove(key); }
2
1private string GetAsyncCacheKey(int objectId) {
2
1return string.Format("StoreWithCache_GetAsync_{0}", objectId); }​
2

En el siguiente artículo continuaremos viendo patrones de diseño, que a buen seguro nos serán de gran utilidad a la hora de diseñar e implementar soluciones en la nube.

Francisco
Ricardo Gil González
MVP CLUSTER | Especialista en SharePoint &
Office 365 francisco.gil@fiveshareit.es Linkedin http://www.mvpcluster.es