Manejo de errores en la Arquitectura de Microservicios

Microservices con Node JS y React

El proyecto contiene un submódulo, clonar con estos parámetros:

git clone –recursive https://github.com/josecho/MicroServicesNatsStreamingBlog.git

¿Qué es la técnica del manejo de errores?

En primer lugar, decir que el manejo de errores se refiere a los procedimientos de respuesta y recuperación de las condiciones de error presentes en una aplicación de software. En otras palabras, es el proceso compuesto por la anticipación, detección y resolución de errores de aplicación, errores de programación o errores de comunicación.

Por lo tanto, se ha creado un Middleware de manejo de errores en este proyecto. El middleware de manejo de errores nos permite separar nuestra lógica de error y enviar respuestas como tal y como corresponde. El manejo de errores es importante porque facilita que los usuarios finales de tu código lopuedan usar correctamente. Otro tema importante es que hace que tu código sea más fácil de mantener.

Por otro lado, en una arquitectura de microservicios debemos asegurarnos de que el error que provenga de nuestros servicios siempre satisfaga la misma estructura. En este proyecto, todas las respuestas de error que enviamos desde cualquier servico deben tener esta estructura:

Common error responses

Estructura de la respuesta común

Cada servicio puede implementarse con un lenguaje de programación diferente y estos crearán una respuesta de error con su propia estructura, particular para cada servicio. En consecuencia, esto obligará a que la aplicación React tenga el conocimiento necesario para manejar muchos errores.

Complexity arround errors

Para evitar esto, en este proyecto, la estructura de la respuesta de error creada es igual en todos los servicios para que la aplicación React sólo necesite saber cómo manejar 1 tipo de error.

same structure

Dificultad en el manejo de errores

  • Debemos de tener una respuesta estructurada consistentemente para todos los servidores, no importará qué salió mal

    Escribir un middleware de manejo de errores para procesar errores, darles una estructura consistente y enviarlos de vuelta al navegador.

  • Miles de millones de cosas pueden salir mal, no solo la validación de las entradas a un controlador de solicitudes. Cada uno de estas cosas necesita ser manejada consistentemente.

    Asegúrate de capturar todos los errores posibles usando el mecanismo de manejo de errores de Express (llamar a la función ‘next’)

No tenemos  idea alguna en dónde puede salir algo mal algo de nuestra aplicación.

Something wrong inside of our application

Para manejar estas situaciones, se creó el Error Handling Middleware en la aplicación. En esta aplicación queremos tener una única estructura para todos los posibles errores que se puedan crear en los diferentes servicios. Por lo tanto, hemos dado los primeros pasos de esta manera.

res.status(400).send({
message: 'Something went wrong',
})

Después de conectar el middleware a nuestra aplicación express.

app.use(errorHandler);

Para asegurarnos de tener una respuesta estructurada muy consistente, pase lo que pase, en lugar de escribir manualmente un código de manejo de errores como el que existía en el archivo signup.ts:

return res.status(400).send(errors.array());

Cada vez que algo salga mal, vamos a lanzar un error.

throw new Error('Invalid email or password');
throw new Error('Error connecting to database..

Todos estos cambios se pueden ver en el repositorio de código indicado en el enlace de arriba.

Por otro lado, la imagen de arriba dice que necesitamos adjuntar, de alguna manera, un mensaje de error personalizado al objeto error. Eso se hace en este paso.

Después de lo visto hasta ahora hemos dado una serie de pasos.

Pero nos vamos a centrar en los últimos pasos dados, donde creo que hay algo importante que debemos aprender.

En este momento estamos manejando el RequestValidationError y el DatabaseConnectionError dentro del  Error Handling Middleware.
Hemos codificado en su interior los conocimientos muy complejos que debe tener de cómo extraer información de cada tipo de error que existe dentro de nuestra aplicación.

Errors

Imaginemos un escenario futuro en el que tal vez tengamos muchas cosas diferentes que puedan salir mal dentro de nuestra aplicación y que creemos un error personalizado para todo lo que puede salir mal. Finalmente terminamos teniendo y debiendo manejar todos estos diferentes errores personalizados tal y como mostramos en la siguiente imagen:

Erros

En consecuencia, nuestro Middleware de manejo de errores va a crecer gigantescamente. Tendremos que codificar alguna lógica en el Middleware para comprender todos los posibles errores y comprender cómo extraer información de ellos para poder enviarla a el usuario.

Por otro lado, debemos asegurarnos de que la resuesta que van a dar tenga el mismo tipo de estructura, una respuesta común a todos. Si continuamos por este camino,  nuestro Error Handling Middleware seguirá creciendo y alcanzando un tamaño extremadamente grande.

Método SerializeError y propiedad Status Code

¿Cómo podemos resolver este problema?

Bueno, vamos a invertir la relación mostrada anteriormente.

Logic in Errors

Con este nuevo enfoque, Error Handler Middleware solo se asegurará de que comprende todos los errores que se le presentan y de que pueda acceder al método implementado en ellos llamado serializeError. Este método devuelve un array que contiene elementos del tipo CommonErrorStructure. Al mismo tiempo, también podrá consultar la propiedad statusCode para saber qué código de estado de error usar en la respuesta.

Ahora bien, si se repite el escenario mencionado anteriormente en el que se producen muchos errores en la aplicación:

serializeError method

Podremos crear tantas clases personalizadas de error como queramos. Cada una podrá tener su propia implementación. El Middleware de manejo de errores no crecerá en complejidad porque, sin importar cual sea el error, siempre nos referiremos al mismo método serializeError y a la misma propiedad statusCode. Veamos este paso en el código.

Verificación de nuestros errores personalizados

En la actualidad, no hay absolutamente nada dentro de nuestro código TypeScript para verificar y asegurar que el método serializeError se implemente correctamente y la propiedad statusCode esté presente siempre. Sería importante que tuviéramos algún tipo de verificación dentro de nuestro código que comprobase esto. Algo para asegurarse también de que el método serializeError siempre devuelva el mismo tipo de estructura de error.

Check Errors

Necesitamos implementar un código para asegurarnos de que todos nuestros diferentes servicios, que vamos a crear con el tiempo, siempre devolverán exactamente la misma estructura de error. Entonces, nuevamente, sería genial asegurarnos de que tuviéramos algo dentro del mundo de TypeScript para verificar esto.

Check Errors

Veamos dos enfoques posibles.

Opción #1

Una interfaz de TypeScript que cree un contrato con RequestValidationError y DataBaseConnectionError para garantizar que estos errores personalizados implementen correctamente el método serializeError y la propiedad statusCode.

CustomError Interface

TypeScript Interface

Opción #2

Vamos a crear una nueva clase abstracta llamada CustomError. La clase abstracta tendrá exactamente el mismo propósito que la interfaz que acabamos de ver.

Abstract class

Lo bueno de una clase abstracta es que cuando traducimos esto a JavaScript para ejecutarlo, terminamos teniendo una definición de clase. Cuando traducimos las interfaces de TypeScript a JavaScript, todas las interfaces desaparecen. En realidad, no existen en el mundo de JavaScript, pero las clases abstractas sí.

Solución final

El compilador de TypeScript no convierte la interfaz a JavaScript. Utiliza la interfaz para la verificación de tipos. Esto también se conoce como «duck typing» o «structural subtyping». Si continuamos con el mismo patrón que hemos seguido hasta este punto, acabaremos teniendo un montón de sentencias if diferentes para todos los diferentes tipos de errores.

ton of if statements

En lugar de definir una interfaz y hacer que nuestras clases la implementen, vamos a definir una clase abstracta llamada CustomError y vamos a hacer que RequestValidation y DatabaseConnectionError extiendan de esta clase abstracta. Como resultado, en el Error Handler Middleware solo acabaremos teniendo una declaración if para todos los diferentes tipos de error.

abstract class solution

En conclusión, debemos asegurarnos de que el error que provenga de que nuestros servicios siempre tenga la misma estructura y es haciendo cosas como esta que acabamos de ver lo que nos permitirá aseguraremos de que todas las diferentes áreas que creemos dentro de nuestro aplicación tengan una estructura idéntica.

Tal vez sea un buen momento para ver cómo se manejan las excepciones en Spring Boot Microservices y compararlo con Node Microservices.

0 comentarios

Dejar un comentario

¿Quieres unirte a la conversación?
Siéntete libre de contribuir!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *