Otro de los bloques que te conté cuando empecé con Dapr era el destinado al patrón de publicador suscriptor. Con él podrás construir una arquitectura basada en eventos asíncronos donde los suscriptores no tienen por qué conocer a los publicadores. Hoy quiero contarte cómo configurarlo con Dapr en un sencillo ejemplo.
El publicador
Lo primero que voy a mostrarte es qué pinta tiene el que publica los eventos, o publicador. En este caso voy a hacer uso del mismo ejemplo que utilicé cuando te hablé del bloque Service-to-service invocation, por lo que el publicador va a ser la API tour of villains.
Para ello, he modificado el método que da de alta nuevos villanos en la base de datos, y ahora notifico que un nuevo villano ha sido creado:
// POST: /villain
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
public async Task<ActionResult<Villain>> PostHero(Villain villain)
{
_context.Villains.Add(villain);
await _context.SaveChangesAsync();
// Add a publication with the new villain
using var client = new DaprClientBuilder().Build();
//Using Dapr SDK to publish a topic
await client.PublishEventAsync("villain-pub-sub", "villains", villain);
return CreatedAtAction(nameof(GetVillain), new { heroName = villain.Hero }, villain);
}
Como ves, utilizo DaprClient junto con el método PublishEventAsync para publicar, usando el componente llamado villain-pub-sub, en el topic llamado villains, la información del nuevo villano.
¿Pero como sabe el publicador cuál es ese componente villain-pub-sub? Pues de la misma forma que vimos con los bindings, a través de una configuración como la siguiente:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: villain-pub-sub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
En este caso me estoy apoyando en la instancia de redis que genera dapr init, cuando empiezas con Dapr, pero tienes disponibles otras tecnologías como se detalla aquí. Por otro lado, para que este proyecto tenga en cuenta esta configuración, tengo asociada a la tarea dapr-debug, en .vscode/tasks.json, el directorio components, que es donde he guardado esta configuración, a la propiedad componentsPath:
{
"appId": "tour-of-villains-api",
"appPort": 5111,
"label": "dapr-debug",
"type": "dapr",
"dependsOn": "build",
"httpPort": 3501,
"grpcPort": 50001,
"logLevel": "debug",
"componentsPath": "./components"
},
El suscriptor
Por otro lado, siempre que se publica algo es porque queremos que alguien o algo al otro lado esté dispuesto a escuchar lo que pasa. En este caso, la API tour of heroes es la que está a la escucha del topic villains, que es donde el publicador da de alta eventos/nuevos villanos.
En cuanto a la configuración, en este caso tenemos que tener tanto la configuración del componente:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: villain-pub-sub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
Como la de la suscripción al topic:
apiVersion: dapr.io/v2alpha1
kind: Subscription
metadata:
name: villain-pub-sub
spec:
topic: villains
routes:
default: /newvillain
pubsubname: villain-pub-sub
scopes:
- tour-of-heroes-api #The scopes field enables this subscription for app with IDs tour-of-heroes-api
Aunque esta última también se puede hacer a través de código usando el atributo Topic, como se ve comentado en el siguiente fragmento.
//Subscribe to a topic
// [Topic("villain-pub-sub", "villains")]
[HttpPost("/newvillain")]
public ActionResult NewVillain([FromBody] object villain)
{
_logger.LogInformation($"A new villain is in the city!");
_logger.LogInformation(villain.ToString());
return Ok();
}
Como ves, lo bueno de todo esto es que ni el publicador sabe quién escucha ni el suscriptor sabe quién publica, lo cual hace que ambos estén totalmente desacoplados entre sí y sea más fácil su reemplazo en caso necesario.
¡Saludos!