Skip to main content

Eventos

Visión general

La aplicación publica mensajes de evento en distintos momentos (por ejemplo, cuando un usuario inicia sesión, cuando se desembolsa un préstamo o cuando se registra un pago). Estos mensajes pueden ser de cualquier tipo y se pueden consumir desde cualquier parte, incluidos tus propios módulos.

Hay dos piezas fundamentales:

  • IEventoPublicador (Crono.Eventos): responsable de enviar los mensajes de evento a los suscriptores.
  • IConsumidor (Crono.Eventos): interfaz de marcador que convierte a una clase en consumidor (manejador o suscriptor) de uno o más eventos.

Todo mensaje de evento implementa la interfaz de marcador IEventoMensaje (o IEventoDominio para eventos de dominio).

Consumir eventos

Los métodos manejadores realizan tareas de pre o post procesamiento de un evento. Deben cumplir:

  • Ser públicos
  • No ser estáticos
  • Devolver void o Task
  • Seguir la convención de nombres:
    • Asíncronos: ManejarAsync, ManejarEventoAsync o ConsumirAsync
    • Síncronos: Manejar, ManejarEvento o Consumir

El primer parámetro del método debe ser siempre el mensaje de evento o una instancia de ConsumidorContexto<TMensaje>.

El IConsumidorInvocador decide cómo llamar al método según su firma:

  • Los métodos void se invocan de forma síncrona.
  • Los métodos Task se invocan de forma asíncrona y se esperan.
  • Con el atributo Olvidar (fire-and-forget), el método se ejecuta en segundo plano sin esperar. Útil en procesos largos, porque no bloquea el hilo de la solicitud actual.
warning

Usa el atributo Olvidar con precaución. Una clase con un consumidor fire-and-forget no debe tomar dependencias con ámbito de solicitud, porque la continuación ocurre en otro hilo y se pierde el contexto. Pasa las dependencias necesarias como parámetros del método: el invocador crea un contexto privado para la unidad de trabajo y resuelve las dependencias desde ahí.

Puedes declarar parámetros de dependencia adicionales en el método manejador; el invocador los resuelve automáticamente (el orden no importa):

public async Task ManejarEventoAsync(AlgunEvento mensaje,
CronoDbContexto db,
ICacheAdministrador cache,
CancellationToken cancelToken)
{
// Tu código
}

Todas las clases que implementan IConsumidor se detectan automáticamente al iniciar la aplicación; no es necesario registrarlas en el contenedor de inyección de dependencias.

Ejemplo ilustrativo de un consumidor (los tipos de mensaje son de ejemplo):

internal class NotificarDesembolsoConsumidor : IConsumidor
{
private readonly IComunicacionServicio _comunicacion;

public NotificarDesembolsoConsumidor(IComunicacionServicio comunicacion)
{
_comunicacion = comunicacion;
}

public async Task ManejarEventoAsync(PrestamoDesembolsadoEvento mensaje)
{
// Reaccionar al desembolso: enviar WhatsApp, registrar comunicación, etc.
await _comunicacion.NotificarDesembolsoAsync(mensaje.PrestamoId);
}
}
info

Consejo: si hay varios métodos manejadores en la misma clase consumidora, pasa las dependencias compartidas por el constructor. En caso contrario, usa parámetros del método.

Consumir por tipo base o interfaz

El primer parámetro no tiene que ser el tipo exacto publicado. Si declaras una clase base o interfaz, el manejador se invoca para todos los tipos publicados que deriven de ella. Cuando coinciden varios manejadores (uno para el tipo concreto y otro para el tipo base), se invocan todos, empezando por el más específico.

Para recibir el contexto de la solicitud junto al mensaje, declara el parámetro como ConsumidorContexto<TMensaje>.

Publicar eventos

Para publicar un evento, crea el mensaje, llénalo con los datos necesarios y usa IEventoPublicador.PublicarAsync:

// Crear el mensaje de evento...
var evento = new PrestamoDesembolsadoEvento(prestamo.Id);

// ...y publicarlo
await _eventoPublicador.PublicarAsync(evento);
warning

Evita el método síncrono Publicar. Solo úsalo cuando estés seguro de que todos los manejadores del mensaje son síncronos.

Bus de mensajes

IBusMensajes se activa, por ejemplo, cuando se instala el módulo de Redis (que provee un proveedor de bus). Por defecto usa un bus nulo que no hace nada.

Los mensajes que viajan por el bus deben ser valores string simples (no tipos complejos). Está garantizado que el servidor que publicó un mensaje no lo consumirá: el mensaje solo se reenvía a los demás nodos de la granja de servidores para su procesamiento. Se usa, entre otros, para sincronizar la caché en memoria entre nodos (ver Caché).

Eventos de Negocio (bitácora)

Además del pub/sub anterior, Crono mantiene un registro persistente de Eventos de Negocio (EventosNegocio) para trazar la actividad sobre las entidades clave (préstamos, clientes, transacciones): desembolsos, pagos, cambios de estado, reprogramaciones, cargos, etc.

  • La entidad EventoNegocio almacena cada evento con su categoría, operación, tipo, usuario y canal de origen.
  • IEventoNegocioRegistrador registra los eventos; interceptores como GeneradorEventoEdicionInterceptor y GeneradorEventoTransaccionInterceptor los generan automáticamente al editar entidades o registrar transacciones.
  • EventosNegocioTiposConocidos define el catálogo de tipos de evento reconocidos.

A diferencia del pub/sub (en memoria, para reaccionar en el acto), los Eventos de Negocio quedan guardados en la base de datos y alimentan el historial de actividad que se muestra en el detalle del préstamo y del cliente.