Como celebración por el día del libro, puedes conseguir un 15% de descuento en todos los libros de la web:

Diseña un sistema de notificaciones

18 Apr 2025 10 min (0) Comentarios

¿Cansado de despertarte a mitad de la noche porque has recibido una notificación? En este post vamos a ver el diseño de un sistema de notificaciones que tiene en cuenta a nuestros queridos usuarios. 

 

 

A la hora de diseñar sistemas de notificaciones no solo no tenemos una solución única y perfecta, sino que tenemos varios escenarios. No tiene el mismo diseño una notificación del whatsapp de que has recibido un mensaje, o una notificación de un nuevo vídeo se ha subido a youtube por un youtuber con millones de personas. Pese a que la idea es la misma, implementar la segunda de la misma forma que la primera no es recomendable debido al coste. Hoy nos vamos a centrar en las notificaciones uno a uno. 

 

Esto lo hago de forma intencional para mantener este post en el grupo de los fáciles. 

 

 

1 - Requerimientos de un sistema de notificaciones 

 

Un sistema de notificaciones tiene unos requerimientos sencillos:

  • El sistema debe saber que notificación queremos mandar.
  • Tipo de notificación, aquí entra si queremos mandar un email, un SMS, o una notificación push a los teléfonos.
  • Escalable y disponible, como en cada post de esta serie, la idea es que escale sin problemas y que esté disponible todo el tiempo. Además los tiempos de escalada son claros, durante las horas del día, van a ser mucho mayores que durante la noche, y depende de si es un sistema empresarial o uno de ocio los picos serán a unas horas u otras. 

Adicionalmente, podemos mencionar las prioridades del usuario. Aquí podemos ir desde que tipos de notificaciones queremos permitir al usuario que pueda recibir notificaciones, hasta horarios a los que no quiere recibir cierto tipo de notificaciones. 

 

Si esto fuera una entrevista real podrías preguntar que tipo de sistema estás construyendo, o donde se va a utilizar, ya que si es una notificación de whatsapp quieres recibirla cuando sucede, pero si es una notificación de un foro, quizá no quieras enviar notificaciones durante la noche, o incluso agrupar varias juntas. 

 

 

2 - Diseño del contrato de las notificaciones

 

Vamos a pasar a definir como los productores de los mensajes, que van a ser aplicaciones internas, generan las notificaciones. En este punto estamos definiendo un contrato, no estoy mencionando una API como hice en otros vídeos. El motivo es simple, la gran mayoría de sistemas de notificaciones funcionan con el patrón productor consumidor, por lo que generamos un evento, no una llamada a una API. 

 

El contrato consta de varias partes;

 

  • Metadatos: En esta sección especificamos el identificador único del mensaje, la fecha y hora a la que se ha generado así como por qué canales o que tipo de notificación queremos enviar. En resumen, información de la notificación. 
{
    "metadata": {  👈
        "uniqueIdentifier": string,
        "timestamp": datetime,
        "channels": [sms, email, push]
    }
}

 

  • Receptor: La segunda parte de nuestro mensaje es el receptor, normalmente únicamente contendrá el ID del usuario, ya que es sistema de notificaciones tiene acceso a todo el sistema interno. 
{
    "metadata": { 
        "uniqueIdentifier": string,
        "timestamp": datetime,
        "channels": [sms, email, push]
    },
    "recipient": { 👈
        "userId": string
    },
}

 

  • Contenido de la notificación: aquí tendremos toda la información que el receptor va a recibir. El objeto a enviar suele ser un objeto que contiene todas las propiedades o campos de todos los canales a enviar, por lo que suele ser un objeto grande. En caso de que un tipo específico no se envíe, esos campos estarán vacíos. 
{
    "metadata": { 
        "uniqueIdentifier": string,
        "timestamp": datetime,
        "channels": [sms, email, push]
    },
    "recipient": { 
        "userId": string
    },
    "content": { 👈
        "email": {
            "subject": string,
            "body": string,
            "attachments": [ (url, filename, mimeType) ],
        },
        "sms": {
            "content": string
        },
        "push": {
            "title": string,
            "body": string,
            "action": string
        }
    }
}

Como vemos, contiene toda la información de las 3 formas que tenemos para mandar mensajes, tanto el contenido del SMS, como de la notificación push, como del Email.

En caso de contener un adjunto, en la gran mayoría de casos se envía la URL de dicho adjunto, no se envía el fichero como tal, ya que así evitamos tener problemas con el tamaño de los ficheros, aún así, depende de cada escenario específico. Si tu sistema envía facturas es posible que incluyas el adjunto en el email, si envías libros no es lo más recomendado.

 

 

3 - Diseño de la arquitectura de un sistema de notificaciones

 

Como me gusta hacerlo y recomiendo hacerlo en una entrevista es empezar sencillo e ir evolucionando. En este caso empezamos por algo sencillo.

 

Tenemos uno o varios sistemas que generan notificaciones, estas notificaciones se publican en un sistema productor consumidor, como puede ser un eventbus, una cola, etc. el cual tiene un subscriptor que lee el mensaje y lo envía al servicio de terceros correspondiente para enviar emails, notificaciones o SMSs, por supuesto habiendo leído la información de contacto de la API del usuario.

 

arquitectura simple notificaciones

En el mundo real, dependiendo del tamaño de tu sistema y el uso o las implementaciones de la empresa donde trabajas es posible que esta implementación te sirva, pero en una entrevista de diseño tenemos que ir más allá. 

 

 

En nuestro caso vamos a continuar con el escalado. Para ello lo primero que vamos a necesitar son más instancias de nuestro consumidor.

Puedes pensar que estamos detrás de un sistema productor/consumidor y que no lo necesitamos ya que podemos consumir al ritmo que queramos, y esta afirmación es cierta, pero no siempre funciona ya que si tenemos muchas notificaciones se puede montar una backlog importante. Por ejemplo, si podemos procesar 10mil mensajes por segundo, pero recibimos 12mil, cada segundo estamos dejando de procesar 2mil que se van añadiendo al backlog.

 

Para ello necesitamos escalar nuestro consumidor con más instancias. Y dependiendo que sistema productor consumidor lo harás de una forma u otra, una aplicación muy común para implementar Productor Consumidor es Kafka, si quieres saber más sobre su funcionamiento interno te recomiendo comprar mi libro Construyendo sistemas distribuidos.

arquitectura de notificaciones v2

NOTA: En caso de que hubieras elegido ir por el camino de la comunicación directa a través de una API, también necesitarías más instancias, pero delante de ellas necesitarías un load balancer.

 

La cosa no acaba aquí, en vez de enviar los mensajes desde esta aplicación lo que hacemos es utilizar esta APP para distribuir los mensajes de forma correcta, donde irán a una cola específica de cada tipo de notificación posible. E idealmente después una función nativa de la nube (Lambda Function, Azure function, etc) procesa el mensaje y lo envía al sistema de terceros que vayamos a utilizar. 

sistema de notificaciones v3

Como punto adicional, si usas Azure o AWS, puedes crear conectores para que estas acciones (enviar email, notificaciones, etc) se puedan hacer directamente desde la cola, sin necesidad de crear una función entre medias. 

 

En la gran mayoría de entrevistas donde os pidan un sistema de notificaciones, esto es todo lo que os van a pedir. Ya que los avisos de whatsapp, o una alerta si tienes un sistema de monitorización funciona de esta manera.

 

 

 

3.1 - Incluir preferencias de usuario en un sistema de notificaciones

 

Para nuestro escenario vamos a incluir las preferencias de usuario, algo que por cierto, debería tener en cuenta yo en mi propio blog… 

 

Para este punto tenemos dos preguntas clave:

  • La primera, ¿en qué capa introducimos las preferencias de usuario?. En la capa de la APP que redistribuye las notificaciones. 
  • La segunda, ¿qué hacemos con las notificaciones que no deben suceder aún? Para responder a esta pregunta tenemos varias opciones. 

 

Aunque antes de responder, tenemos que saber el motivo por el que introducimos esa lógica en la capa de redistribución, el motivo es muy simple y es que en muchos sistemas va a ser la única app, ya que el resto de infraestructura se puede hacer por configuración. Luego la idea de la Función nativa es asegurarse de que todo mensaje que reciba es enviado. No realizar funcionalidad extra ya que la idea principal de este tipo de funciones nativas de la nube es que hagan una única funcionalidad y tener que comprobar la lógica de usuario y actuar sobre ella como veremos ahora es una funcionalidad extra.

 

Ahora viene el qué hacer con las notificaciones

 

Supongamos que tenemos un usuario que solo quiere ser contactado entre las 8 de la mañana y las 4 de la tarde, el resto del tiempo, no. 

Para ello tenemos una API con la información de los usuarios que nos devuelve dicha información. Y si  queremos enviar una notificación debemos comprobar el horario del usuario. 

 

Si nuestra notificación está dentro del horario del usuario, la propagamos y ya. Pero si no lo está tenemos dos opciones, la primera, es ignorarla y perderla. Puedes pensar que esto es una locura, pero si tenemos una tienda por ejemplo, y queremos poner productos en oferta flash durante las próximas dos horas, no la vamos a enviar porque el usuario no quiere y no la vamos a almacenar pues cuando el usuario la lea ya no tiene validez. O mismamente las notificaciones de youtube de que alguien está haciendo directo, si recibes la notificación después de que la persona termine, pues no tiene sentido tampoco. 

 

El caso que de verdad nos concierne es esas notificaciones útiles que debemos almacenar y propagar cuando el usuario está disponible. Para ello podemos hacer la solución donde tenemos una cola para cada hora donde los usuarios quieren esas notificaciones. Por ejemplo tenemos una cola (o stream) para las notificaciones que van a ser ejecutadas a la 8am, otra cola diferente para las notificaciones que se ejecutan a las 9 am y así para todas las horas del día.

 

Luego tenemos una aplicación que lee dicha cola y propaga los mensajes al sistema productor consumidor original el cual procesa los mensajes como si acabaran de llegar. 

flujo completo de un sistema de notificaciones

El flujo sería el siguiente:

1 - Una app de nuestro sistema genera un evento.

2 - Nuestro consumidor consume el mensaje.

3 - Comprobamos las preferencias de usuario y leemos su información (email, numero de teléfono)

4 - Si está fuera de hora, pasamos el mensaje a la cola de la hora correspondiente.

Nota: si está en hora, saltamos al punto 9.

5 - Un timer o un cron job ejecuta una aplicación cada hora (o con diferentes parámetros).

6 - Leemos el mensaje de la cola correspondiente.

7 - Propagamos el mensaje a la cola/Stream de notificación original.

8 - recibimos el mensaje y leemos la información del usuario.

9 - Le propagamos el mensaje a la cola de la notificación correspondiente.

10 - Leemos el mensaje y lo enviamos a la aplicación de terceros que se encarga de enviar ese tipo de notificaciones.

 

Si un mensaje no fuera capaz de ser enviado en cualquiera de los puntos, podríamos implementar una Dead-letter-queue para una revisión de los mismos. 

 

 

3.2 - Mensajes con prioridad

 

Pese a que esta es toda la arquitectura nos queda un último punto de diseño, ya que ahora tenemos notificaciones con horarios, es posible que alguna notificación tenga que saltarse dicha norma. Por ejemplo, si un sistema entero está caído, nos da igual qué tipo de configuración tenga el usuario, debemos ser capaces de mandar la notificación al usuario. Por ello, el contrato del evento debe tener un apartado de prioridad, donde si es la máxima, nos saltamos todo el tema de comprobar la información del usuario. 

{
    "metadata": { 
        "uniqueIdentifier": string,
        "timestamp": datetime,
        "channels": [sms, email, push],
	"priority": low | medium | high 👈
    },
    "recipient": { 
       ...
    },
    "content": { 
        ...
    }
}

Para ello simplemente introducimos el valor en los metadatos, de esta forma, saltamos cualquier posible validación en nuestra aplicación consumidora y lo enviamos a la cola correspondiente. Para este caso en concreto podemos usar prioridad 1, 2, 3 o alta, media, baja, en verdad como queramos.

 

 

 

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café