Uno de los puntos que, desde mi experiencia, considero críticos son las respuestas de las API entre los diferentes servicios. 

 

 

1 - Por qué unificar la respuesta de las API?

Es importante una vez llegamos a microservicios y tenemos muchos servicios tener una idea clara y compartida de cómo hacer las cosas es crucial. 

 

Porque si bien es cierto que cuando hacemos microservicios se tiene la idea de que cada equipo haga lo que quiera, en la práctica real, esto puede ser un problema muy grande, lo ideal es tener libertad, pero con ciertas limitaciones.

Por ejemplo, si utilizamos comunicación asíncrona, lo suyo es utilizar el mismo service bus en cada servicio, no el que nos de la gana.

 

Lo mismo sucede con las respuestas de nuestra API. Si respondemos en cada servicio utilizando la misma lógica será mucho más fácil para el resto de servicios que consumen el nuestro, ya sean otros servicios propios o clientes externos.

 

 

2 - Unificar la respuesta de la API en C#

En este post vamos a trabajar sobre el código de Distribt y lo que vamos a aplicar es la lógica de Railway Oriented Programming pero no para hacer la lógica del dominio, sino para únicamente la respuesta de la API.

 

Tengo un vídeo sobre ROP aqui Pero lo único que necesitamos saber es que la respuesta de nuestra api va a ser un ResultDto<T> donde contendrá la siguiente información:

public class ResultDto<T>
{
    public T Value { get; set; }
    public ImmutableArray<ErrorDto> Errors { get; set; }
    public bool Success => this.Errors.Length == 0;
}

Como puedes ver, tenemos una lista de errores, luego T como nuestro tipo genérico que va a cambiar, y luego success que básicamente nos indica si un objeto tiene algún error o no. 

 

Por lo tanto, la API siempre va a devolver la misma estructura y la misma lógica. 

 

Pero esto no acaba ahí ya que con mi librería de ROP podemos especificar también el Status Code de una response

Para ello en nuestro código, debemos importar el siguientes paquete nuget:

El cual nos añadira tambíen NetMentor.ROP. para el que puedes encontrar aquí el código en github.

Esta librería nos permite además de utilizar la estructura Result añadir los status codes para la respuesta de la API.

 

 

2.1 - Qué Status Code utilizar como respuesta de la API

Debemos utilizar el status code que mas nos convenga partiendo de los códigos de estado. Pero, hay unos que son mas comunes que otros y bajo mi experiencia pesonal estos son los mas comunes:

 

A - Respuestas satisfactorias

  • 200 OK: Significa que lo que sea que se ha ejecutado ha tenido éxito, por ejemplo cuando haces un GET, sueles responder con un estado 200.
  • 201 Created: Común cuando haces un POST y creas algo en el sistema, ya sea un registro en una base de datos, un proceso, fichero, etc.
  • 202 Accepted: este está enlazado a los proceso asíncronos, cuando realizamos una llamada el sistema la acepta y la almacena pero la acción como tal, no sea ha completado, por ejemplo cuando indicas “enviar un producto” tu le das al botón, y la respuesta viene al momento, pero tiene que haber una persona en el almacén que coge el pedido lo empaqueta y lo manda, y una vez mandado, habrá sido completado. La misma lógica la debemos seguir si la llamada a una api lleva mucho tiempo. Algunas veces lleva tiempo extra y otras no.

 

B - Respuestas no satisfactorias

  • 400 Bad Request: esta es la que utilizamos cuando el servidor no puede comprender lo que le estás pidiendo. Cuando no se sabe que poner, se suele poner este.
  • 404 Not found: Cuando pedimos una información y esa información no está disponible, por ejemplo un ID que no existe.
  • 409 Conflict: Cuando la petición esta en conflicto con el sistema, por ejemplo creamos dos elementos que comparten el mismo ID o si tienen una propiedad única y ya existe en la base de datos, como el número de pasaporte.
  • 422 Unprocessable Entity: Cuando entendemos lo que nos llega, pero contiene algún error por ejemplo de validación se suele utilizar. (Nota no disponible en NetStandard 2.0).

no he añadido 401 que es unauthorized, 403 forbidden u otros códigos como los 50x ya que todos esos errores nos los proporciona el propio framework automáticamente. 

 

 

2.2 - Añadir HttpStatuscode a la respuesta de la API

La forma en la incluimos un HTTPStatusCode en la respuesta de la API es modificando la request, pero a la vez en C# podemos acceder al objeto ObjectResult el cual nos permite modificar el status code. 

Pero si utilizamos la librería NetMentor.ROP.ApiExtensions podemos hacerlo todo mucho mas fluido.

  • Este post no consiste en ver el funcionamiento de dicha librería sino del resultado final para el usuario, así que si vas al código puedes ver una implementación completa de la librería, pero para simplificar únicamente vamos a ver cómo responder con un status code. 

 

A - Respuestas Satisfactoria

El primero es ver la respuesta satisfactoria, para ello, cuando devolvemos un Result<T> de la api, debemos hacer la llamadada a .ToActionResult() el cual crea un IActionResult (por detrás es un ResultDto<T>) con el HTTPStatusCode de 202; pero claro, qué pasa si lo que queremos hacer es devolver otro código diferente?, por ejemplo 200.

 

Lo que tenemos que hacer es muy sencillo, en la antes de llamar a .ToActionResult() realizamos la llamada a .UseSuccessHttpStatusCode

 el cual nos deja indicar un status code, en caso de que el proceso sea correcto:

public async Task<IActionResult> CreateOrder(CreateOrderRequest createOrderRequest,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        return await _createOrderService.Execute(createOrderRequest, cancellationToken)
            .UseSuccessHttpStatusCode(HttpStatusCode.Created)
            .ToActionResult();
    }

Y como vemos en la respuesta, contiene el status code y la estructura que necesitamos.

respuesta rop api

 

B - Respuestas no satisfactorias

En el caso de las respuestas no satisfactorias el proceso es similar, Debemos tener nuestro happy path igual que lo hemos hecho para una respuesta satisfactoria, la diferencia aquí es que la respuesta no satisfactoria se genera si a lo largo de la cadena hemos generado algún error.

 

Por lo tanto, cuando hacemos .ToActionResult() la librería comprobará si tenemos algún error en nuestro objeto y de ahí sacará el HttpStatusCode dependiendo de qué tipo de error hayamos creado:

private async Task<Result<OrderDetails>> GetOrderDetails(Guid orderId,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        OrderDetails? orderDetails = await _orderRepository.GetById(orderId, cancellationToken);
        if (orderDetails == null)
            return Result.NotFound<OrderDetails>($"Order {orderId} not found");

        return orderDetails;
    }

Y como vemos nos genera la respuesta con el Codigo 404 al utilzar not found

non-happy path rop

 

2.3 - beneficios de unificar las respuestas de una API

El gran beneficio que obtenemos es que los consumidores de dicha API saben como funciona, tanto esa como todas ya que TODAS funcionan igual, esto es algo en lo que pecan muchas empresas, en dejar hacer lo que sea en cada momento y al final la configuración de las api es muy importante.

 

 

3 - Decorar la respuesta de la api con OpenAPI

Por supuesto no olvides decorar los endpoints con las diferentes opciones para que así swagger sea capaz de reconocer las diferentes posibilidades y lo añade al fichero de OpenApi.

[HttpGet("{orderId}")]
[ProducesResponseType(typeof(Result<OrderResponse>), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(Result<OrderResponse>), (int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetOrder(Guid orderId)
    => await _getOrderService.Execute(orderId)
        .UseSuccessHttpStatusCode(HttpStatusCode.OK)
        .ToActionResult();

 

Y este es el resultado visible en swagger el cual se genera a través de open api:

open api swagger response

 

 

Conclusión

En este post hemos visto la importancia de unificar la respuesta de las API

Como crear la unificación de APIs en C#