Compartir vía


Acceso a datos remotos

Nota:

Este libro electrónico se publicó en la primavera de 2017 y no se ha actualizado desde entonces. Mucho contenido del libro sigue siendo valioso, pero algunos de los materiales están obsoletos.

Muchas soluciones modernas basadas en web hacen uso de servicios web, hospedados en servidores web, para proporcionar funcionalidad a las aplicaciones cliente remotas. Las operaciones que expone un servicio web constituyen una API web.

Las aplicaciones cliente deben tener la capacidad de usar la API web sin saber cómo se implementan los datos o las operaciones que expone la API. Para ello, se requiere que la API se rija por las normas comunes que permiten que una aplicación cliente y un servicio web acuerden qué formatos de datos se usan y la estructura de los datos que se intercambian entre las aplicaciones cliente y el servicio web.

Introducción a la transferencia de estado representacional

La Transferencia de estado representacional (REST) es un estilo de arquitectura para la creación de sistemas distribuidos basados en hipermedia. Una de las principales ventajas del modelo de REST es que se basa en estándares abiertos y no vincula la implementación del modelo o las aplicaciones cliente que tienen acceso a él con ninguna implementación específica. Por lo tanto, un servicio web REST podría implementarse mediante Microsoft ASP.NET Core MVC y las aplicaciones cliente podrían desarrollarse mediante cualquier lenguaje y conjunto de herramientas que pueda generar solicitudes HTTP y analizar respuestas HTTP.

El modelo REST usa un esquema de navegación para representar objetos y servicios en una red, denominados recursos. Los sistemas que implementan REST normalmente usan el protocolo HTTP para transmitir solicitudes de acceso a estos recursos. En estos sistemas, una aplicación cliente envía una solicitud en forma de un URI que identifica un recurso y un método HTTP (como GET, POST, PUT o DELETE) que indica la operación que se realizará en ese recurso. El cuerpo de la solicitud HTTP contiene todos datos necesarios para realizar la operación.

Nota

REST define un modelo de solicitud sin estado. Por lo tanto, las solicitudes HTTP deben ser independientes y pueden producirse en cualquier orden.

La respuesta de una solicitud REST hace uso de códigos de estado HTTP estándar. Por ejemplo, una solicitud que devuelve datos válidos debe incluir el código de respuesta HTTP 200 (correcto), mientras que una solicitud que no encuentra o elimina un recurso especificado debe devolver una respuesta que incluya el código de estado HTTP 404 (no encontrado).

Una API web RESTful expone un conjunto de recursos conectados y proporciona las operaciones básicas que permiten a una aplicación manipular esos recursos y navegar fácilmente entre ellos. Por este motivo, los URI que constituyen una API web RESTful típica están orientados a los datos que dicha API expone y usan las funciones proporcionadas por HTTP para operar con estos datos.

Los datos incluidos por una aplicación cliente en una solicitud HTTP, y los mensajes de respuesta correspondientes del servidor web, podrían presentarse en una variedad de formatos, conocidos como tipos de medios. Cuando una aplicación cliente envía una solicitud que devuelve datos en el cuerpo de un mensaje, puede especificar los tipos multimedia que puede controlar en el encabezado Accept de la solicitud. Si el servidor web admite este tipo de medio, puede responder con una respuesta que incluya el encabezado Content-Type que especifica el formato de los datos en el cuerpo del mensaje. Es responsabilidad de la aplicación cliente analizar el mensaje de respuesta e interpretar correctamente los resultados en el cuerpo del mensaje.

Para obtener más información sobre REST, consulte Diseño de API y implementación de API.

Consumo de API de RESTful

La aplicación móvil eShopOnContainers usa el patrón Model-View-ViewModel (MVVM) y los elementos del modelo del patrón representan las entidades de dominio usadas en la aplicación. Las clases de controlador y repositorio de la aplicación de referencia eShopOnContainers aceptan y devuelven muchos de estos objetos de modelo. Por lo tanto, se usan como objetos de transferencia de datos (DTO) que contienen todos los datos que se pasan entre la aplicación móvil y los microservicios en contenedores. La principal ventaja de usar DTO para pasar y recibir datos a y desde un servicio web es que, al transmitir más datos en una sola llamada remota, la aplicación puede reducir el número de llamadas remotas que se deben realizar.

Realización de las solicitudes web

La aplicación móvil eShopOnContainers usa la clase HttpClient para realizar solicitudes a través de HTTP, con JSON que se usa como tipo de medio. Esta clase proporciona funcionalidad para enviar solicitudes HTTP de forma asincrónica y recibir respuestas HTTP de un recurso identificado por URI. La clase HttpResponseMessage representa un mensaje de respuesta HTTP recibido de una API REST después de realizar una solicitud HTTP. Contiene información sobre la respuesta, incluido el código de estado, los encabezados y cualquier cuerpo. La clase HttpContent representa el cuerpo HTTP y los encabezados de contenido, como Content-Type y Content-Encoding. El contenido se puede leer mediante cualquiera de los métodos ReadAs, como ReadAsStringAsync y ReadAsByteArrayAsync, según el formato de los datos.

Realización de una solicitud GET

La clase CatalogService se usa para administrar el proceso de recuperación de datos desde el microservicio de catálogo. En el método RegisterDependencies de la clase ViewModelLocator, la clase CatalogService se registra como una asignación de tipos en el tipo ICatalogService con el contenedor de inserción de dependencias de Autofac. A continuación, cuando se crea una instancia de la clase CatalogViewModel, su constructor acepta un tipo de ICatalogService, que Autofac resuelve y devuelve una instancia de la clase CatalogService. Para obtener más información sobre la inserción de dependencias, consulte Introducción a la inserción de dependencias.

En la figura 10-1 se muestra la interacción de las clases que leen los datos de catálogo del microservicio de catálogo para mostrarlos por el CatalogView.

Recuperación de datos del microservicio de catálogo

Figura 10-1: Recuperación de datos del microservicio de catálogo

Cuando se navega al CatalogView, se llama al método OnInitialize de la clase CatalogViewModel. Este método recupera datos de catálogo del microservicio de catálogo, como se muestra en el ejemplo de código siguiente:

public override async Task InitializeAsync(object navigationData)  
{  
    ...  
    Products = await _productsService.GetCatalogAsync();  
    ...  
}

Este método llama al método GetCatalogAsync de la instancia de CatalogService insertada en el CatalogViewModel por Autofac. El siguiente ejemplo de código muestra el método GetCatalogAsync:

public async Task<ObservableCollection<CatalogItem>> GetCatalogAsync()  
{  
    UriBuilder builder = new UriBuilder(GlobalSetting.Instance.CatalogEndpoint);  
    builder.Path = "api/v1/catalog/items";  
    string uri = builder.ToString();  

    CatalogRoot catalog = await _requestProvider.GetAsync<CatalogRoot>(uri);  
    ...  
    return catalog?.Data.ToObservableCollection();            
}

Este método compila el URI que identifica el recurso al que se enviará la solicitud y usa la clase RequestProvider para invocar el método HTTP GET en el recurso, antes de devolver los resultados a CatalogViewModel. La clase RequestProvider contiene una funcionalidad que envía una solicitud en forma de URI que identifica un recurso, un método HTTP que indica la operación que se va a realizar en ese recurso y un cuerpo que contiene los datos necesarios para realizar la operación. Para obtener información sobre cómo se inserta la clase RequestProvider en el CatalogService class, vea Introduction to Dependency Injection.

En el ejemplo de código siguiente se muestra el método GetAsync de la clase RequestProvider:

public async Task<TResult> GetAsync<TResult>(string uri, string token = "")  
{  
    HttpClient httpClient = CreateHttpClient(token);  
    HttpResponseMessage response = await httpClient.GetAsync(uri);  

    await HandleResponse(response);  
    string serialized = await response.Content.ReadAsStringAsync();  

    TResult result = await Task.Run(() =>   
        JsonConvert.DeserializeObject<TResult>(serialized, _serializerSettings));  

    return result;  
}

Este método llama al método CreateHttpClient, que devuelve una instancia de la clase HttpClient con los encabezados adecuados establecidos. A continuación, envía una solicitud GET asincrónica al recurso identificado por el URI, con la respuesta que se almacena en la instancia de HttpResponseMessage. A continuación, se invoca el método HandleResponse, que produce una excepción si la respuesta no incluye un código de estado HTTP correcto. A continuación, la respuesta se lee como una cadena, se convierte de JSON en un objeto CatalogRoot y se devuelve a CatalogService.

El método CreateHttpClient se muestra en el ejemplo de código siguiente:

private HttpClient CreateHttpClient(string token = "")  
{  
    var httpClient = new HttpClient();  
    httpClient.DefaultRequestHeaders.Accept.Add(  
        new MediaTypeWithQualityHeaderValue("application/json"));  

    if (!string.IsNullOrEmpty(token))  
    {  
        httpClient.DefaultRequestHeaders.Authorization =   
            new AuthenticationHeaderValue("Bearer", token);  
    }  
    return httpClient;  
}

Este método crea una nueva instancia de la clase HttpClient y establece el encabezado Accept de las solicitudes realizadas por la instancia de HttpClient en application/json, lo que indica que espera que el contenido de cualquier respuesta tenga el formato JSON. A continuación, si se pasó un token de acceso como un argumento al método CreateHttpClient, se agrega al encabezado Authorization de las solicitudes realizadas por la instancia HttpClient, cuyo prefijo es la cadena Bearer. Para obtener más información acerca de la autorización, vea Autorización.

Cuando el método GetAsync de la clase RequestProvider llama a HttpClient.GetAsync, se invoca el método Items de la clase CatalogController del proyecto Catalog.API, que se muestra en el ejemplo de código siguiente:

[HttpGet]  
[Route("[action]")]  
public async Task<IActionResult> Items(  
    [FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0)  
{  
    var totalItems = await _catalogContext.CatalogItems  
        .LongCountAsync();  

    var itemsOnPage = await _catalogContext.CatalogItems  
        .OrderBy(c=>c.Name)  
        .Skip(pageSize * pageIndex)  
        .Take(pageSize)  
        .ToListAsync();  

    itemsOnPage = ComposePicUri(itemsOnPage);  
    var model = new PaginatedItemsViewModel<CatalogItem>(  
        pageIndex, pageSize, totalItems, itemsOnPage);             

    return Ok(model);  
}

Este método recupera los datos del catálogo de la base de datos SQL mediante EntityFramework y lo devuelve como un mensaje de respuesta que incluye un código de estado HTTP correcto y una colección de instancias de CatalogItem con formato JSON.

Realización de una solicitud POST

La clase BasketService se usa para administrar el proceso de recuperación y actualización de datos con el microservicio de la cesta de la compra. En el método RegisterDependencies de la clase ViewModelLocator, la clase BasketService se registra como una asignación de tipos en el tipo IBasketService con el contenedor de inserción de dependencias de Autofac. A continuación, cuando se crea una instancia de la clase BasketViewModel, su constructor acepta un tipo de IBasketService, que Autofac resuelve y devuelve una instancia de la clase BasketService. Para obtener más información sobre la inserción de dependencias, consulte Introducción a la inserción de dependencias.

La figura 10-2 muestra la interacción de las clases que envían los datos de cesta mostrados por el BasketView, al microservicio de cesta.

Envío de datos al microservicio de cesta

Figura 10-2: Envío de datos al microservicio de cesta

Cuando se agrega un elemento a la cesta de la compra, se llama al método ReCalculateTotalAsync de la clase BasketViewModel. Este método actualiza el valor total de los elementos de la cesta de la compra y envía los datos de la cesta al microservicio de cesta de la compra, como se muestra en el ejemplo de código siguiente:

private async Task ReCalculateTotalAsync()  
{  
    ...  
    await _basketService.UpdateBasketAsync(new CustomerBasket  
    {  
        BuyerId = userInfo.UserId,   
        Items = BasketItems.ToList()  
    }, authToken);  
}

Este método llama al método UpdateBasketAsync de la instancia de BasketService insertada en el BasketViewModel por Autofac. El método siguiente muestra el método UpdateBasketAsync:

public async Task<CustomerBasket> UpdateBasketAsync(CustomerBasket customerBasket, string token)  
{  
    UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint);  
    string uri = builder.ToString();  
    var result = await _requestProvider.PostAsync(uri, customerBasket, token);  
    return result;  
}

Este método compila el URI que identifica el recurso al que se enviará la solicitud y usa la clase RequestProvider para invocar el método HTTP POST en el recurso, antes de devolver los resultados a BasketViewModel. Tenga en cuenta que se requiere un token de acceso obtenido de IdentityServer durante el proceso de autenticación para autorizar las solicitudes al microservicio de cesta. Para obtener más información acerca de la autorización, vea Autorización.

En el ejemplo de código siguiente se muestra uno de los métodos PostAsync de la clase RequestProvider:

public async Task<TResult> PostAsync<TResult>(  
    string uri, TResult data, string token = "", string header = "")  
{  
    HttpClient httpClient = CreateHttpClient(token);  
    ...  
    var content = new StringContent(JsonConvert.SerializeObject(data));  
    content.Headers.ContentType = new MediaTypeHeaderValue("application/json");  
    HttpResponseMessage response = await httpClient.PostAsync(uri, content);  

    await HandleResponse(response);  
    string serialized = await response.Content.ReadAsStringAsync();  

    TResult result = await Task.Run(() =>  
        JsonConvert.DeserializeObject<TResult>(serialized, _serializerSettings));  

    return result;  
}

Este método llama al método CreateHttpClient, que devuelve una instancia de la clase HttpClient con los encabezados adecuados establecidos. A continuación, envía una solicitud POST asincrónica al recurso identificado por el URI, con los datos de la cesta de la compra serializados que se envían en formato JSON y la respuesta que se almacena en la instancia HttpResponseMessage. A continuación, se invoca el método HandleResponse, que produce una excepción si la respuesta no incluye un código de estado HTTP correcto. A continuación, la respuesta se lee como una cadena, se convierte de JSON en un objeto CustomerBasket y se devuelve al BasketService. Para obtener más información sobre el método CreateHttpClient, vea Realizar una solicitud GET.

Cuando el método PostAsync de la clase RequestProvider llama a HttpClient.PostAsync, se invoca el método Post de la clase BasketController del proyecto Basket.API, que se muestra en el ejemplo de código siguiente:

[HttpPost]  
public async Task<IActionResult> Post([FromBody]CustomerBasket value)  
{  
    var basket = await _repository.UpdateBasketAsync(value);  
    return Ok(basket);  
}

Este método usa una instancia de la clase RedisBasketRepository para conservar los datos de la cesta de la compra en la caché de Redis y los devuelve como un mensaje de respuesta que incluye un código de estado HTTP correcto y una instancia CustomerBasket con formato JSON.

Realización de una solicitud DELETE

En la figura 10-3 se muestran las interacciones de las clases que eliminan los datos de cesta del microservicio de cesta, para el CheckoutView.

Eliminación de datos del microservicio de cesta

Figura 10-3: Eliminación de datos del microservicio de cesta

Cuando se invoca el proceso de finalización de la compra, se llama al método CheckoutAsync de la clase CheckoutViewModel. Este método crea un pedido, antes de vaciar la cesta de la compra, como se muestra en el ejemplo de código siguiente:

private async Task CheckoutAsync()  
{  
    ...  
    await _basketService.ClearBasketAsync(_shippingAddress.Id.ToString(), authToken);  
    ...  
}

Este método llama al método ClearBasketAsync de la instancia de BasketService insertada en el CheckoutViewModel por Autofac. El método siguiente muestra el método ClearBasketAsync:

public async Task ClearBasketAsync(string guidUser, string token)  
{  
    UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint);  
    builder.Path = guidUser;  
    string uri = builder.ToString();  
    await _requestProvider.DeleteAsync(uri, token);  
}

Este método compila el URI que identifica el recurso al que se enviará la solicitud y usa la clase RequestProvider para invocar el método HTTP DELETE en el recurso. Tenga en cuenta que se requiere un token de acceso obtenido de IdentityServer durante el proceso de autenticación para autorizar las solicitudes al microservicio de cesta. Para obtener más información acerca de la autorización, vea Autorización.

En el ejemplo de código siguiente se muestra el método DeleteAsync de la clase RequestProvider:

public async Task DeleteAsync(string uri, string token = "")  
{  
    HttpClient httpClient = CreateHttpClient(token);  
    await httpClient.DeleteAsync(uri);  
}

Este método llama al método CreateHttpClient, que devuelve una instancia de la clase HttpClient con los encabezados adecuados establecidos. A continuación, envía una solicitud ELIMINAR asincrónica al recurso identificado por el URI. Para obtener más información sobre el método CreateHttpClient, vea Realizar una solicitud GET.

Cuando el método DeleteAsync de la clase RequestProvider llama a HttpClient.DeleteAsync, se invoca el método Delete de la clase BasketController del proyecto Basket.API, que se muestra en el ejemplo de código siguiente:

[HttpDelete("{id}")]  
public void Delete(string id)  
{  
    _repository.DeleteBasketAsync(id);  
}

Este método usa una instancia de la clase RedisBasketRepository para eliminar los datos de la cesta de la compra de la caché de Redis.

Almacenar datos en caché

El rendimiento de una aplicación se puede mejorar almacenando en la caché los datos a los que se accede con frecuencia a un almacenamiento rápido que se encuentra cerca de la aplicación. Si el almacenamiento rápido se encuentra más cerca de la aplicación que la fuente original, el almacenamiento en caché puede mejorar significativamente los tiempos de respuesta al recuperar datos.

La forma más común de almacenamiento en caché es el almacenamiento en caché de lectura, donde una aplicación recupera datos haciendo referencia a la memoria caché. Si los datos no se están en la caché, se recuperan del almacén de datos y se agregan a la caché. Las aplicaciones pueden implementar el almacenamiento en caché de lectura con el patrón cache-aside. Este patrón determina si el elemento está actualmente en la memoria caché. Si los datos no se están en la caché, se recuperan del almacén de datos y se agregan a la caché. Para obtener más información, consulte el patrón Cache-Aside.

Sugerencia

Almacenar en caché los datos que se leen con frecuencia y que cambian con poca frecuencia. Estos datos pueden agregarse a la caché a petición la primera vez que los recupera una aplicación. Esto significa que la aplicación debe capturar los datos una sola vez del almacén de datos y que el posterior acceso se puede satisfacer mediante la caché.

Las aplicaciones distribuidas, como la aplicación de referencia eShopOnContainers, deben proporcionar una de las siguientes memorias caché o ambas:

  • Una caché compartida a la que pueden acceder varios procesos o máquinas.
  • Una caché privada, donde los datos se mantienen localmente en el dispositivo que ejecuta la aplicación.

La aplicación móvil eShopOnContainers usa una caché privada, donde los datos se mantienen localmente en el dispositivo que ejecuta una instancia de la aplicación. Para obtener información sobre la memoria caché usada por la aplicación de referencia eShopOnContainers, vea Microservicios de .NET: Arquitectura para aplicaciones .NET en contenedor.

Sugerencia

Considere la caché como un almacén de datos transitorios que podrían desaparecer en cualquier momento. Asegúrese de que los datos se mantienen tanto en el almacén de datos original como en la memoria caché. Las posibilidades de perder datos se minimizan si la memoria caché deja de estar disponible.

Administración de la expiración de datos

No es práctico esperar que los datos almacenados en la caché siempre sean coherentes con los datos originales. Los datos del almacén de datos original pueden cambiar después de haberse almacenado en caché, lo que hace que los datos almacenados en la memoria caché se vuelvan obsoletos. Por tanto, las aplicaciones deben implementar una estrategia que ayude a garantizar que los datos de la caché estén lo más actualizados posible, pero que también pueda detectar y tratar situaciones que surgen cuando los datos de la caché se han vuelto obsoletos. La mayoría de los mecanismos de almacenamiento en caché permiten configurar la memoria caché para que expiren los datos y, por tanto, reducir el período durante el que los datos podrían quedar obsoletos.

Sugerencia

Establezca una hora de expiración predeterminada al configurar una memoria caché. Muchas de las memorias caché implementan una expiración que invalida los datos y los quita de la caché si no se accede a ellos durante un período especificado. Sin embargo, se debe tener cuidado al elegir el período de expiración. Si es demasiado corto, los datos expirarán demasiado rápido y se reducirán las ventajas del almacenamiento en caché. Si es demasiado largo, existe el riesgo de que los datos se queden obsoletos. Por lo tanto, la hora de expiración debe coincidir con el patrón de acceso a las aplicaciones que usan los datos.

Cuando los datos almacenados en caché expiran, se deben quitar de la memoria caché, y la aplicación debe recuperar los datos del almacén de datos original y volver a almacenarlos en la memoria caché.

También es posible que una caché se rellene si se permite que los datos permanezcan almacenados durante mucho tiempo. Por lo tanto, es posible que se requieran solicitudes para agregar nuevos elementos a la memoria caché para quitar algunos elementos en un proceso conocido como expulsión. Normalmente, los servicios de almacenamiento en caché expulsan los datos menos usados recientemente. Sin embargo, hay otras directivas de expulsión, incluidas las usadas más recientemente y la primera en salir. Para obtener más información, consulte Guía de almacenamiento en caché.

Almacenamiento en caché de imágenes

La aplicación móvil eShopOnContainers consume imágenes de producto remotas que se benefician de almacenarse en caché. Estas imágenes se muestran mediante el control Image y el control CachedImage proporcionado por la biblioteca de FFImageLoading.

El control Xamarin.FormsImage admite el almacenamiento en caché de imágenes descargadas. El almacenamiento en caché está habilitado de forma predeterminada y almacenará la imagen localmente durante 24 horas. Además, la hora de expiración se puede configurar con la propiedad CacheValidity. Para obtener más información, consulte Almacenamiento en caché de imágenes descargados.

El control CachedImage de FFImageLoading es un reemplazo del control Xamarin.FormsImage, lo que proporciona propiedades adicionales que permiten la funcionalidad complementaria. Entre esta funcionalidad, el control proporciona almacenamiento en caché configurable, al tiempo que admite marcadores de posición de imagen de error y carga. En el ejemplo de código siguiente se muestra cómo la aplicación móvil eShopOnContainers usa CachedImageel control en el ProductTemplate, que es la plantilla de datos que usa el control ListView en el CatalogView:

<ffimageloading:CachedImage
    Grid.Row="0"
    Source="{Binding PictureUri}"     
    Aspect="AspectFill">
    <ffimageloading:CachedImage.LoadingPlaceholder>
        <OnPlatform x:TypeArguments="ImageSource">
            <On Platform="iOS, Android" Value="default_campaign" />
            <On Platform="UWP" Value="Assets/default_campaign.png" />
        </OnPlatform>
    </ffimageloading:CachedImage.LoadingPlaceholder>
    <ffimageloading:CachedImage.ErrorPlaceholder>
        <OnPlatform x:TypeArguments="ImageSource">
            <On Platform="iOS, Android" Value="noimage" />
            <On Platform="UWP" Value="Assets/noimage.png" />
        </OnPlatform>
    </ffimageloading:CachedImage.ErrorPlaceholder>
</ffimageloading:CachedImage>

El control CachedImage establece las propiedades LoadingPlaceholder y ErrorPlaceholder en imágenes específicas de la plataforma. La propiedad LoadingPlaceholder especifica la imagen que se va a mostrar mientras se recupera la imagen especificada por la propiedad Source y la propiedad ErrorPlaceholder especifica la imagen que se va a mostrar si se produce un error al intentar recuperar la imagen especificada por la propiedad Source.

Como indica el nombre, el control CachedImage almacena en caché las imágenes remotas en el dispositivo durante el tiempo especificado por el valor de la propiedad CacheDuration. Cuando este valor de propiedad no se establece explícitamente, se aplica el valor predeterminado de 30 días.

Aumento de la resistencia

Todas las aplicaciones que se comunican con los recursos y servicios remotos deben ser sensibles a errores transitorios. Los errores transitorios incluyen la pérdida momentánea de conectividad de red a los servicios, la indisponibilidad temporal de un servicio o tiempos de espera que surgen cuando un servicio está ocupado. Estos errores suelen ser de corrección automática y, si la acción se repite después de un intervalo adecuado, es probable que se realice correctamente.

Los errores transitorios pueden tener un gran impacto en la calidad percibida de una aplicación, incluso si se ha probado exhaustivamente en todas las circunstancias previsibles. Para asegurarse de que una aplicación que se comunica con servicios remotos funciona de forma confiable, debe poder hacer todo lo siguiente:

  • Detectar errores cuando se produzcan y determinar si es probable que los errores sean transitorios.
  • Reintentar la operación si se determina que es posible que el error sea transitorio y realizar un seguimiento de la cantidad de veces que se vuelve a intentar la operación.
  • Usar una estrategia de reintento apropiada, que especifica el número de reintentos, el intervalo entre cada intento y las acciones a realizar después de un intento fallido.

Este control de errores transitorios se puede lograr ajustando todos los intentos de acceder a un servicio remoto en el código que implementa el patrón de reintento.

Patrón Retry

Si una aplicación detecta un error al intentar enviar una solicitud a un servicio remoto, puede tratar el error de alguna de las siguientes formas:

  • Reintentar la operación. La aplicación podría reintentar la solicitud con errores inmediatamente.
  • Reintentar la operación después de un retraso. La aplicación debe esperar el tiempo adecuado antes de reintentar la solicitud.
  • Cancelar la operación. La aplicación debe cancelar la operación e informar de una excepción.

La estrategia de reintento se debe ajustar para que coincida con los requisitos empresariales de la aplicación. Por ejemplo, es importante optimizar el número y el intervalo de reintentos en la operación que se está intentando. Si la operación forma parte de una interacción del usuario, el intervalo de reintento debe ser corto, y solo unos pocos reintentos intentan evitar que los usuarios tengan que esperar una respuesta. Si la operación forma parte de un flujo de trabajo de larga duración, donde cancelar y reiniciar el flujo de trabajo resulta lento o costoso, conviene esperar más tiempo entre intentos y efectuar más reintentos.

Nota

Una estrategia de reintentos agresiva con un retraso mínimo entre intentos, y un gran número de reintentos, podría degradar un servicio remoto que se ejecuta en su capacidad o próximo a esta. Además, dicha estrategia de reintento también podría afectar a la capacidad de respuesta de la aplicación si continuamente intenta realizar una operación con errores.

Si una solicitud sigue sin funcionar después de una serie de reintentos, lo mejor para la aplicación es impedir que las solicitudes adicionales vayan al mismo recurso y notificar un error. A continuación, después de un período establecido, la aplicación puede realizar una o varias solicitudes al recurso para ver si se realizan correctamente. Para más información, consulte Circuit Breaker Pattern (Patrón Circuit Breaker).

Sugerencia

Nunca implemente un mecanismo de reintento infinito. Use un número finito de reintentos o implemente el patrón de Disyuntor para permitir que un servicio se recupere.

La aplicación móvil eShopOnContainers no implementa actualmente el patrón de reintento al realizar solicitudes web RESTful. Sin embargo, el control CachedImage, proporcionado por la biblioteca de FFImageLoading admite el control de errores transitorio al reintentar la carga de imágenes. Si se produce un error en la carga de imágenes, se realizarán más intentos. El número de intentos se especifica mediante la propiedad RetryCount y los reintentos se producirán después de un retraso especificado por la propiedad RetryDelay. Si estos valores de propiedad no se establecen explícitamente, sus valores predeterminados se aplican – 3 para laRetryCount propiedad y 250 ms para la propiedad RetryDelay. Para obtener más información acerca del control de CachedImage, consulte Almacenamiento en caché de imágenes.

La aplicación de referencia eShopOnContainers implementa el patrón de reintento. Para obtener más información, incluida una explicación de cómo combinar el patrón de reintento con la clase HttpClient, consulte microservicios de .NET: Arquitectura para aplicaciones .NET en contenedor.

Para obtener más información sobre el patrón de reintento, consulte el patrón Reintentos.

Patrón de disyuntor

En algunas situaciones, pueden producirse errores debido a eventos anticipados que tardan más tiempo en corregirse. Estos errores pueden ir desde una pérdida parcial de conectividad hasta el fallo total del servicio. En estas situaciones, no tiene sentido que una aplicación reintente una operación que es poco probable que se realice correctamente y, en su lugar, debe aceptar que la operación ha provocado errores y tratarlos en consecuencia.

El patrón de disyuntor puede impedir que una aplicación intente ejecutar repetidamente una operación que es probable que produzca un error, al tiempo que permite a la aplicación detectar si se ha resuelto el error.

Nota

El propósito del patrón de disyuntor es diferente del patrón de reintento. El patrón Retry permite a una aplicación reintentar una operación esperando que se podrá ejecutar correctamente. El patrón de disyuntor impide que una aplicación realice una operación que es probable que tenga errores.

Un disyuntor actúa como un proxy para las operaciones que podrían producir errores. El proxy debe supervisar el número de errores recientes que se han producido y utilizar esta información para decidir si permitir que continúe la operación, o devolver de inmediato una excepción.

La aplicación móvil eShopOnContainers no implementa actualmente el patrón de disyuntor. Sin embargo, eShopOnContainers sí. Para más información, consulte Microservicios de .NET: arquitectura para aplicaciones .NET en contenedor.

Sugerencia

Combine los patrones de reintento y disyuntor. Una aplicación puede combinar estos dos patrones utilizando el patrón de reintento para invocar una operación mediante un disyuntor. Sin embargo, la lógica de reintento debe tener en cuenta las excepciones devueltas por el disyuntor y dejar de reintentar la operación si este indica que un error no es transitorio.

Para obtener más información sobre el patrón de disyuntor, consulte el patrón Circuit Breaker.

Resumen

Muchas soluciones modernas basadas en web hacen uso de servicios web, hospedados en servidores web, para proporcionar funcionalidad a las aplicaciones cliente remotas. Las operaciones que expone un servicio web constituyen una API web, y las aplicaciones cliente deben poder usar la API web sin saber cómo se implementan los datos o las operaciones que la API expone.

El rendimiento de una aplicación se puede mejorar almacenando en la caché los datos a los que se accede con frecuencia a un almacenamiento rápido que se encuentra cerca de la aplicación. Las aplicaciones pueden implementar el almacenamiento en caché de lectura con el patrón cache-aside. Este patrón determina si el elemento está actualmente en la memoria caché. Si los datos no se están en la caché, se recuperan del almacén de datos y se agregan a la caché.

Al comunicarse con las API web, las aplicaciones deben ser sensibles a errores transitorios. Los errores transitorios incluyen la pérdida momentánea de conectividad de red a los servicios, la indisponibilidad temporal de un servicio o tiempos de espera que surgen cuando un servicio está ocupado. Estos errores suelen ser de corrección automática y, si la acción se repite después de un intervalo adecuado, es probable que se realice correctamente. Por lo tanto, las aplicaciones deben ajustar todos los intentos de acceder a una API web en el código que implementa un mecanismo de control de errores transitorios.