
En el post anterior, hacíamos una introducción a Dapr con el propósito de saber que es y que nos aporta en nuestro día a día. También vimos un primer ejemplo sobre la invocación entre servicios (Service-to-service Invocation), donde el «Discovery» entraba en acción.
En este post continuaremos aprendiendo y profundizando y, lo haremos particularmente con ejemplos sobre State management, y, por supuesto, veremos también como sustituir components locales para usar nuestros propios componentes en Azure.
A continuación, el listado completo de posts relacionados sobre los que iré trabajando y publicando:
- Introducción a Dapr .NET SDK (1/N) y «Service-to-service invocation»
- [ -> ] Introducción a Dapr .NET SDK (2/N): State management
- Introducción a Dapr .NET SDK (3/N): Secrets
- Introducción a Dapr .NET SDK (4/N): Pub/Sub
- Introducción a Dapr .NET SDK (5/N): Bindings y Triggers
- Introducción a Dapr .NET SDK (6/N): Debugging (VSCode y VS) + Sidekick
- Introducción a Dapr .NET SDK (7/N): Docker Compose and HTTPS
- Introducción a Dapr .NET SDK (8/N): Kubernetes mode
- Introducción a Dapr .NET SDK (9/N): Azure Container Apps mode
State management
Como ya sabemos, tras la inicialización de Dapr (dapr init), se instalan algunos componentes, entre ellos, Redis, que es el componente usado para el almacenamiento de estados. Estos estados tiene la forma key/value. Al haberse creado ya este componente podemos usarlo directamente sin tener que configurar nada adicional, por lo que podemos echar un vistazo al Dashboard de Dapr y comprobarlo. Simplemente ejecutando «dapr dashboard«:

Por el momento, nos basta con saber que nuestro redis tiene un nombre predeterminado «statestore» y es del tipo «state.redis».
Veamos entonces, como usarlo a partir de una aplicación sencilla de consola, que tenemos en mi github, y en concreto aquí.
La clase «StateStoreExample.cs» es la siguiente:
public class StateStoreExample : Example
{
private static readonly string stateKeyName = "Widget";
private static readonly string storeName = "statestore";
public override string DisplayName => "Using the State Store";
public override async Task RunAsync(CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
var state = new Widget() { Size = "small", Color = "yellow", };
await client.SaveStateAsync(storeName,
stateKeyName, state,
cancellationToken: cancellationToken);
Console.WriteLine("Saved State!");
state = await client.GetStateAsync<Widget>(storeName,
stateKeyName,
cancellationToken: cancellationToken);
if (state == null)
{
Console.WriteLine("State not found in store");
}
else
{
Console.WriteLine($"Got State: {state.Size} {state.Color}");
}
await client.DeleteStateAsync(storeName,
stateKeyName,
cancellationToken: cancellationToken);
Console.WriteLine("Deleted State!");
}
private class Widget
{
public string? Size { get; set; }
public string? Color { get; set; }
}
}
Resaltado en negrita observamos las 4 instrucciones que realmente nos interesan:
- Inicialización del cliente Dapr.
- SaveStateAsync. Guardado de estado y, en concreto de información basada en la clase «widget».
- GetStateAsync. Obtención del estado (objeto de la clase widget)
- DeleteStateAsync. Borrado del estado.
Ejecutamos el ejemplo, pasando un «0» argumento para indidar al programa la opción que queremos probar.
dapr run --app-id DaprClient -- dotnet run 0
Si a continuación usamos un cliente (este, por ejemplo) para ver la información almacenada localmente en Dapr Redis, este será el resultado:

Gracias al almacenamiento de estados que nos proporciona Dapr podemos contar, además con:
- Concurrencia optimista. La clase «StateStoreETagsExample.cs«, muestra como los métodos: GetStateAndETagAsync, TrySaveStateAsync y TryDeleteStateAsync, hacen uso de un parámetor «ETag«, para conseguir este propósito. Nota: Para usar ETag en stores que no soporten nativamente ETag, tendremos que implementarlo para simularlo.
- Consistencia (eventual valor predeterminado o fuerte(strong)).
- Operaciones en batch (bulk o multi).
- La clase «StateStoreTransactionsExample.cs» del mismo github, donde el método «ExecuteStateTransactionAsync<T>(…)» es el responsable de todo ello.
- Actualmente el tipo multi (multi-item transactions), solo está soportado por: Mongodb, Redis, PostgreSQL, SQLAzure y Azure ComsmosDB.
- Actor State. Permite indicar que componentes serán usados por actores. Bastará con dar el valor true a la propiedad actorStateStore. Nota: Aun no hemos entrado en detalle sobre que son los Actores, no obstante, aquí y también aquí podemos encontrar más detalle.
- Consultas directas al storage, en formato SQL.
State management en Azure

Ya sabemos como se trabaja con los estados, si bien, de momento hemos usado Redis, como componente local proporcionado por Dapr, pero ¿Como lo hacemos para trabajar con otro no local, o incluso uno diferente: Azure Redis, Azure Storage Table, Azure ConsmosDB, Cassandra,…? Pues bien, la respuesta es sencilla y aquí donde reside uno de los trucos mágicos de Dapr.
Jugemos con los componentes, creando el adecuado para cada caso. El código no cambia !!!
Azure Redis
En primer lugar creamos en Azure un nuevo recurso «Azure Cache for Redis». En este caso con el nombre «dapr», y navegamos a la opción «Access keys», donde otenemos la clave para nuestro Redis.

Creamos un nuevo componente «azure-redis.yaml» en la carpeta «components/azure», con la siguiente estructura. El tipo es «state.redis», que es el mismo que utiliza Dapr de manera predeterminada.
Sustituimos el valor para «redisHost» y «redisPassword«, por los proporcionados por Azure Redis.
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
namespace: default
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: dapr.redis.cache.windows.net:6380
- name: redisPassword
value: "<REDIS-KEY>"
- name: enableTLS # <-- This is the important part missing in the docs
value: "true"
Nota: El sufijo «redis.cache.windows.net:6380» nos lo facilita Azure, por lo que generalmente no cambia.
Ejecutamos, en este caso utilizando el argumento «–components-path», indicando así que estamos definiendo uno o más nuevos componentes.
dapr run --app-id DaprClient --components-path ./components/azure -- dotnet run 0
Finalmente consultamos y comprobamos que el valor se guarda correctamente en el Redis de Azure que hemos creado previamente.

Azure Table Storage
De la misma manera que para el caso anterior, creamos en Azure una cuenta de almacenamiento (o Storage Account) y una tabla con el nombre «daprdemotable«.
Creamos un nuevo componente con el mismo nombre «statestorage» y lo almacenamos como «components/azure/azure-tablestorage.yaml«.
Sustituimos el valor de la propiedad «accountKey», por el que nos proporciona Azure que encontramos en la sección «Security + networking / Access keys / key1 / key» en el portal de Azure.
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestorage
namespace: <NAMESPACE>
spec:
type: state.azure.tablestorage
version: v1
metadata:
- name: accountName
value: "daprdemo1"
- name: accountKey
value: <REPLACE-WITH-ACCOUNT-KEY>
- name: tableName
value: "daprdemotable"
Ejecutamos la misma instrucción que para el caso anterior.
Y, una vez más, para finalizar, consultamos los datos almacenados haciendo uso del portal de Azure, para comprobar que efectivamente el componente funciona.

Notas sobre la ejecución de estos ejemplos:
- Para todos ellos se ha usado el mismo nombre de componente (metadata.name), cuyo valor es «statestore. ¡Así no cambiamos nuestro código!
- La carpeta y subcareptas de componentes contienen varios ficheros que se corresponden con el mismo nombre (metadata.name) del componente, por lo que es necesario que solo uno de ellos tenga la extensión «.yaml». De esta manera decimos a Dapr cual es exactamente el que queremos que use.
IMPORTANTE: En el siguiente post, veremos como evitar introducir información sensible (password, keys, etc) en los componentes para que sean los Secret Stores quienes se encarguen de ellos. ¡Recuerda que introducir passwords y otra información sensible en texto plano no es una recomendación de seguridad!
Happy #Dapr coding !!
Referencias:
Reblogueó esto en El Bruno.
Me gustaMe gusta