Dapr allows the storage of durable data (key\value pairs) across multiple sessions and services.
With Dapr State Management, you can:
- Save and retrieve state using different stores with 2 levels of consistency (strong and eventual)
- Use a consistent API to perform operations, abstracting away the implementation details.
- Implement caching (with TTL) to improve performance.
- Filter\Sort\Page state entries
Let's see how we can use it in the customer's microservices.
The customers' microservice persists data in MS SQL Server and caches data in MongoDB
Here's the code:
public class CustomersController : ControllerBase
{
private readonly ILogger<CustomersController> logger;
private readonly DaprClient daprClient;
private readonly ICustomersRepository customerRepository;
public CustomersController(ILogger<CustomersController> logger, DaprClient daprClient, ICustomersRepository customerRepository)
{
this.logger = logger;
this.daprClient = daprClient;
this.customerRepository = customerRepository;
}
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<Customer>), (int)HttpStatusCode.OK)]
public async Task<ActionResult<IEnumerable<Customer>>> GetCustomers()
{
var customers = await GetCustomersFromCache();
return Ok(customers);
}
private async Task<IEnumerable<Customer>> GetCustomersFromCache()
{
var customers = await daprClient
.GetStateAsync<IEnumerable<Customer>>(
"mongostore",
"GetCustomers");
if (customers == null)
{
customers = await customerRepository
.GetCustomersAsync();
await daprClient
.SaveStateAsync("mongostore",
"GetCustomers",
customers);
}
return customers;
}
}
We have a standard API Controller where we inject the DaprClient (view the previous article to see the NuGet package required and how to register it).
In the GetCustomersFromCache method, we use the GetStateAsync to check if we previously cached the data. If yes, we just return it; if no, we load it (using the customer repository), cache it using the SaveStateAsync and return it.
Please note that we are specifying where we want to save our state by passing the store name (in our case "mongostore"). But where is it defined?
If you remember in the previous article we configured the sidecar with the component path; that's the folder where we store the configurations of our components.
Therefore, we just have to create under that folder a new YAML file with the following configuration
apiVersion: dapr.io/v1alpha
kind: Component
metadata: name: mongostore
spec: type: state.mongodb version: v1 metadata: - name: host value: "mongo:27017"
The value "mongostore" in the metadata section is the store name we have to use in our code (I set it as "mongostore", but you can follow your preferred naming convention). We also specify how to connect to the MongoDB store.
Where can you find all these settings? In the official documentation, of course!
Now we have all the information to use another state store (i.e. Redis) as I've done in the products microservice.
The products' microservice persists data in PostgreSQL and cache data in Redis
The relevant piece of code is similar to the previous one:
private async Task<IEnumerable<Product>> GetProductsFromCache(
{
var products = await daprClient
.GetStateAsync<IEnumerable<Product>>(
"redisstore",
"GetProducts");
if (products == null)
{
products = await this.productRepository.GetProductsAsync();
await daprClient.SaveStateAsync("redisstore", "GetProducts", products);
}
return products;
})
To configure the Redis state store, we create the following file in the component's folder
apiVersion: dapr.io/v1alpha
kind: Component
metadata: name: redisstore
spec: type: state.redis version: v1 metadata: - name: redisHost value: redis:6379 - name: redisPassword value: ""
To test both customers' and products' microservices, I prepared 2 .http files with the sample GET requests, so we can use Visual Studio.
Just start the solution (using the docker-compose) and executes both requests.
When executing the GET /customers, the result will be cached in Mongo
In the source code on GitHub, you can see also how to delete the state and how to set a TTL of the cached value (otherwise cache will never expire!).
In the next article, we'll see the Configuration and Binding building blocks