DEV Community

Renicius Pagotto
Renicius Pagotto

Posted on • Updated on

Cache-Aside using Azure Cache for Redis

In the software development cycle, we often need to focus on improving software performance. There are many ways to improve this and a caching strategy is one of the best options.

To understand what is Azure Cache for Redis, visit Azure Cache for Redis - Overview

What is Cache-Aside Pattern

This pattern is very simple and straightforward. When we need a specific data, first we try to get it from the cache, if the data is not in the cache, we need to get it from the source, add to the cache and return. Due to this, in a next query, the data will be retrieved from the cache.

When adding data to the cache, we also need to determine how long the data should be stored or whether it will be permanent.

When to use this pattern

Use this pattern when:

  • This pattern can be used when the demand is unpredictable, enabling load data on demand.

  • In scenarios where data will be accessed frequently and will not have frequent updates.

Cache Invalidation

Cache invalidation is a process where data is removed from cache storage. This process should only happen when data is updated or deleted from the database, thus avoiding inconsistency of the cached data.

Image description

Cache-Aside Implementation

Implementing this pattern in .NET is very simple and straightforward and for that, we will use .NET 6, in-memory database to simulate a real database and an instance of the Azure Cache For Redis resource.

First, let's create the Customer entity that will be used in the database and also in the cache.

public class Customer
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public string Email { get; set; }
    public string Source { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

The next step is to create the context that will be used by in-memory database.

public class CustomerContext : DbContext
{
    public CustomerContext(DbContextOptions<CustomerContext> options) : base(options)
    { }

    public DbSet<Entity.Customer> Customers { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Now, let's configure the in-memory database and the Azure Cache resource on our application. On the Program.cs, add the following lines of code.

using Customer.Api.Repository;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<CustomerContext>(opt => opt.UseInMemoryDatabase("CustomerDB"));

builder.Services.AddStackExchangeRedisCache(options => {
    options.Configuration = "your primary or secondary connection string";
});

var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseAuthorization();
app.MapControllers();
app.Run();
Enter fullscreen mode Exit fullscreen mode

On builder.Services.AddDbContext line, we are configuring the in-memory database and on builder.Services.AddStackExchangeRedisCache line, we are configuring connection with Azure Cache For Redis using a primary or a secondary connection string that is provided by Azure Portal.

Image description

And finally, we need to implement the endpoint

[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
        var customerCached = await _cache.GetStringAsync($"customer:{id}");

        if (customerCached is not null)
        {
            var customer = JsonSerializer.Deserialize<Entity.Customer>(customerCached);
            customer.Source = "cache";
            return Ok(customer);
        }

        var customerDb = await _customerContext.Customers.FirstOrDefaultAsync(p => p.Id == id);

        if (customerDb is not null)
        {
            await _cache.SetStringAsync($"customer:{customerDb.Id}", JsonSerializer.Serialize(customerDb), 
                new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30)
                });

            customerDb.Source = "database";
            return Ok(customerDb);
        }

        return Ok("Customer not found");
}
Enter fullscreen mode Exit fullscreen mode

This method is very simple to understand, we are first trying to get data from cache, if data is not in cache, so we'll retrieve from database, save in cache and return for the user.

Now, let's implement the cache invalidation

[HttpPut("{id}")]
public async Task<IActionResult> Update(int id, Entity.Customer customer)
    {
        var customerDb = await _customerContext.Customers.FirstOrDefaultAsync(p => p.Id == id);
        if (customerDb is not null)
        {
            customerDb.FullName = customer.FullName;
            customerDb.Email = customer.Email;

            _customerContext.Update(customerDb);
            await _customerContext.SaveChangesAsync();

            await _cache.RemoveAsync($"customer:{id}");
            return Ok();
        }

        return Ok("Customer not found");
}
Enter fullscreen mode Exit fullscreen mode

In this flow, firstly we are retrieving the information from database to update the informations and once the entity has been updated, we are going to remove the data from cache.

With that, we have a simple and easy-to-understand solution that will increase the performance of our application and reduce the overhead of database operations.

GitHub Repository: https://github.com/reniciuspagotto/azure-cache-for-redis-customer

Did you like this post? Do you want to talk a little more about it? Leave a comment or get in touch through LinkedIn.

Top comments (0)