Charla tecnica · Technitive · Capgemini AD Center

Arquitectura Frontend — Simplificando los mensajes de estado

Charla en Technitive sobre como eliminar el try/catch repetido en cada pantalla llevando los errores y estados al interceptor HTTP.

Arquitectura Frontend — Simplificando los mensajes de estado

Charla impartida en Technitive (marzo de 2024) en representacion de Capgemini AD Center, sobre como dejar de repartir try/catch y toasts por todas las pantallas de una aplicacion frontend.

El planteamiento es simple: las reglas de negocio y los fallos HTTP no deberian ser responsabilidad de la pantalla que los origina. La charla recorre una pequeña app Angular de referencia — un dashboard de Formula 1 con pantallas para pilotos, escuderias, circuitos y temporadas — para mostrar como un cambio arquitectonico minimo elimina cientos de lineas de gestion de errores repetida.

El problema

La mayoria de frontends acaban pareciendose: cada metodo valida parametros, llama a un endpoint, comprueba la respuesta, decide que mensaje mostrar y por fin hace el trabajo real. Ese codigo se duplica en cada pantalla, es dificil de mantener consistente, y la logica que deberia vivir en un unico sitio queda dispersa en todos.

  • Dificil de mantener — cada pantalla es dueña de su propio UX de error.
  • Consume tiempo de desarrollo en boilerplate sin valor de negocio.
  • Aumenta innecesariamente el codigo con un try/catch casi identico decenas de veces.
  • La logica que deberia estar centralizada queda dividida en N copias independientes.
Slide del problema comun: el patron try/catch repetido que ensucia cada pantalla.
Slide del problema comun: el patron try/catch repetido que ensucia cada pantalla.

El problema a escala

Si haces zoom out, el coste se multiplica. Cada feature de la aplicacion — pilotos, escuderias, circuitos, temporadas — posee su propia pantalla, llama a su propio endpoint, gestiona su propio error y monta su propio componente de mensaje. El mismo patron, copia-pegado N veces, con N oportunidades de divergir.

De repente el coste no son tres lineas de try/catch: es una superficie de mantenimiento que crece linealmente con el numero de features y reduce las ganas del equipo de tocar nada.

  • N features × el mismo try/catch — y la misma probabilidad de inconsistencia.
  • Cada feature posee la maquetacion de su propio UX de error y deriva por su cuenta.
  • Añadir un nuevo modo de fallo implica tocar cada feature en lugar de un solo sitio.
Slide con cuatro features (pilotos, escuderias, circuitos, temporadas) repitiendo el patron pantalla → endpoint → mensaje de error.
Slide con cuatro features (pilotos, escuderias, circuitos, temporadas) repitiendo el patron pantalla → endpoint → mensaje de error.

El componente MessageBoard

Un componente tipado que vive en la libreria compartida y renderiza cuatro tipos de mensaje: Error, Warning, Success e Info. Cada mensaje lleva un tipo, un cuerpo, una posicion opcional (default o flotante) y un flag opcional de autoClose.

Lo decisivo es donde se monta: en el bootstrap layout de la aplicacion — fuera del router-outlet — junto al header y al footer. Es global y persistente: importa que ruta este activa, la superficie donde aparecen los mensajes siempre esta ahi.

  • MessageType: Success | Info | Error | Warn.
  • Position: Float | Default — controla si el banner es sticky o inline.
  • MessageBase: { type, message, position?, autoClose? } extendido por MessageBoard con un id interno.
  • Montado como <header /><message-board /><router-outlet /><footer /> en la raiz de la app.
Slide del componente MessageBoard: cuatro tipos de mensaje y la interfaz TypeScript MessageBase.
Slide del componente MessageBoard: cuatro tipos de mensaje y la interfaz TypeScript MessageBase.

Estado reactivo con BehaviorSubject

Como el MessageBoard vive en la raiz del arbol, una pantalla profunda no puede comunicarse con el a traves de inputs y outputs. La charla resuelve esto con una pequeña clase de estado reactivo que expone un BehaviorSubject privado y metodos publicos addMessage / removeMessage.

El MessageBoard se suscribe al observable; cualquier servicio, componente o interceptor inyecta ese mismo estado y empuja mensajes a el. Los operadores de RxJS (filter, tap, catchError) hacen la reactividad explicita y facil de extender.

Slide con la clase MessageBoardState: BehaviorSubject privado y metodos publicos addMessage / deleteMessage.
Slide con la clase MessageBoardState: BehaviorSubject privado y metodos publicos addMessage / deleteMessage.

El interceptor HTTP

El interceptor es el cotilla del trafico: inspecciona codigos de estado y cuerpos en cada request y response, y decide que hacer sin que la pantalla que llamo se entere.

Ante un 4xx (error de negocio) o un 5xx (error de servidor) construye un Message a partir del payload del backend, lo empuja al estado y corta el stream — no queda nada que la pantalla tenga que manejar. Ante un 200 con contrato de exito puede tambien disparar un banner verde automatico. Cualquier otro caso se reenvia tal cual al llamante.

  • 4xx / 5xx → addMessage(error) y corta el flujo. La pantalla nunca ve el fallo.
  • 200 con contrato de exito → addMessage(success) y reenvia el payload.
  • 200 normal → reenvia el payload sin tocarlo.
  • Resultado: las reglas de negocio se dirigen desde el backend, la pantalla solo posee el happy path.
Slide del flujo del interceptor: caminos HTTP 200, 400 y 500 dirigidos al estado de mensajes.
Slide del flujo del interceptor: caminos HTTP 200, 400 y 500 dirigidos al estado de mensajes.

La arquitectura completa

Juntando todas las piezas, la arquitectura deja de parecer 'una pantalla que pide datos' y empieza a parecer un bucle reactivo cerrado. La pantalla llama a su endpoint, la respuesta atraviesa el interceptor, el interceptor decide si empujar un mensaje al estado compartido, el layout re-renderiza el MessageBoard automaticamente, y la llamada original solo reenvia datos — nunca errores — a la pantalla.

Los pasos numerados del diagrama cuentan la historia entera: 1) la pantalla llama. 2) el endpoint pega al backend. 3) llega la respuesta. 4) el interceptor la inspecciona. 5–6) empuja un mensaje al estado compartido. 7) el layout re-renderiza el MessageBoard automaticamente. 8–9) la llamada solo devuelve datos a la pantalla.

Diagrama end-to-end de la solucion: pantalla, interceptor, backend, estado de mensajes y MessageBoard conectados en un unico bucle reactivo.
Diagrama end-to-end de la solucion: pantalla, interceptor, backend, estado de mensajes y MessageBoard conectados en un unico bucle reactivo.

Resultado

En una aplicacion con decenas de pantallas, el patron elimina miles de lineas de gestion de errores duplicada y deja que los componentes se centren en su trabajo real: disparar acciones y renderizar datos. La infraestructura se encarga de que el usuario siempre sepa que esta pasando.

Stack

Angular TypeScript RxJS NgRx HTTP Interceptors WebSockets

Enlaces