A Quick view of Cached Repository Pattern
Tags: decorator, repository, csharp, dotnet
Published: 2022-01-31 14:15:26

In this article, we will see how to use the Cached Repository pattern with asp.net core web api and entity framework core.

## The idea

In this exemple we will create an aspnet core webapi that fetches a list of cutomers stored in the database and the result will be a Data transfert object (DTO) that contains the list of customers and the time taken to fetch the data.

The database will be initialized with 10000 record for testing.

You can find the full source code of this sample in the link below:
https://github.com/kbourzayq/CachedRepository

### The Repository Class before Applying cache

The repository class looks like :

```csharp
public class CustomerRepository : IReadOnlyRepository<Customer>
{
    private readonly AppDbContext _context;

    public CustomerRepository(AppDbContext context)
    {
        _context = context;
    }

    public Customer GetById(int id)
    {
        return _context.Cutomers.First(x => x.Id == id);
    }

    public List<Customer> List()
    {
        return _context.Cutomers.Take(1000).ToList();
    }
}
```

The Api Controller class :

```csharp
[Route("api/[controller]")]
[ApiController]
public class CustomerController : ControllerBase
{
    private readonly IReadOnlyRepository<Customer> _repository;

    public CustomerController(IReadOnlyRepository<Customer> repository)
    {
        _repository = repository;
    }
    [HttpGet]
    public ActionResult<CustomerListDto> GetAll()
    {
        var timer = Stopwatch.StartNew();
        var customers = _repository.List();
        timer.Stop();
        CustomerListDto customerList = new CustomerListDto { 
            Customers = customers, 
            ElapsedTimeMilliseconds = timer.ElapsedMilliseconds
        };
        return Ok(customerList);
    }
}
```

Time to inject dependencies and lunch our app:

```csharp

Builder.Services.AddScoped<IReadOnlyRepository<Customer>, CustomerRepository>();

```

As you can see the loading of 1000 records takes some time...

```json
{
  "elapsedTimeMilliseconds": 1226,
  "customers": [
    {
      "name": "Customer 0",
      "lastName": "Customer Lastname 0",
      "address": "Address 0",
      "id": 1
    },
    {
      "name": "Customer 1",
      "lastName": "Customer Lastname 1",
      "address": "Address 1",
      "id": 2
    },
    {
      "name": "Customer 2",
      "lastName": "Customer Lastname 2",
      "address": "Address 2",
      "id": 3
    },
    ....
```
## Let's apply cache

In this example, we want to add caching behavior without modifying the existing code.
In other words, we should add caching to the application without touching any code in the repository implementation shown above or the webapi that uses it. 
See: [Open closed principle](https://dev.to/bourzayq_khalid/be-cautious-when-implementing-the-open-closed-principle-24bh).
To do that, we will use the Decorator pattern. 
The Decorator pattern is used to add some behaviors to an existing type without affecting the behavior of other types.

The Cached Repository class looks like the code below :

```csharp
public class CachedCustomerRepository : IReadOnlyRepository<Customer>
{
    private readonly CustomerRepository _repository;
    private readonly IMemoryCache _cache;
    private const string CustomersCacheKey = "Sample::Customers";
    private MemoryCacheEntryOptions cacheOptions;

    
    public CachedCustomerRepository(CustomerRepository repository,
        IMemoryCache cache)
    {
        _repository = repository;
        _cache = cache;
        cacheOptions = new MemoryCacheEntryOptions()
            .SetAbsoluteExpiration(relative: TimeSpan.FromSeconds(20));
    }

    public Customer GetById(int id)
    {
        string key = CustomersCacheKey + "-" + id;

        return _cache.GetOrCreate(key, entry =>
        {
            entry.SetOptions(cacheOptions);
            return _repository.GetById(id);
        });
    }

    public List<Customer> List()
    {
        return _cache.GetOrCreate(CustomersCacheKey, entry =>
        {
            entry.SetOptions(cacheOptions);
            return _repository.List();
        });
    }
}
```

The next setp is to change the injected dependencies :

```csharp
Builder.Services.AddScoped<IReadOnlyRepository<Customer>, CachedCustomerRepository>();
Builder.Services.AddScoped<CustomerRepository>();
Builder.Services.AddSingleton<IMemoryCache, MemoryCache>();
```

Now, after implementing our cache, we will launch our application and see what it will give us

First call :
```json
{
  "elapsedTimeMilliseconds": 1224,
  "customers": [
    {
      "name": "Customer 0",
      "lastName": "Customer Lastname 0",
      "address": "Address 0",
      "id": 1
    },
    {
      "name": "Customer 1",
      "lastName": "Customer Lastname 1",
      "address": "Address 1",
      "id": 2
    },
    {
      "name": "Customer 2",
      "lastName": "Customer Lastname 2",
      "address": "Address 2",
      "id": 3
    },
    ....
```

Second call :
```json
{
  "elapsedTimeMilliseconds": 0,
  "customers": [
    {
      "name": "Customer 0",
      "lastName": "Customer Lastname 0",
      "address": "Address 0",
      "id": 1
    },
    {
      "name": "Customer 1",
      "lastName": "Customer Lastname 1",
      "address": "Address 1",
      "id": 2
    },
    {
      "name": "Customer 2",
      "lastName": "Customer Lastname 2",
      "address": "Address 2",
      "id": 3
    },
    ....
```

As you can see, loading a large number of records takes some time (between 150 to 1224 ms on my machine) on the first load, but then drops to 0 ms for subsequent calls because we put the data in our cache.
 
In our app example, we set a cache expiration value of 20 seconds, so once the 20 seconds are up, we will reload the data from the database and cache it for the next 20 seconds.

## Conclusion

In the example we just saw, it was a small application that tries to retrieve a large number of data that we are going to put in a memory cache. 
In the case of distributed applications you will have to use a distributed cache such as Redis or memcached.
You can find in the link below a very small application that uses Redis :
https://github.com/kbourzayq/RedisDemo 

The idea of this example is to explain how we can implement the Repository pattern by caching the data to have high performance in the case where we have data that has a very minimal change frequency.
I hope this article was clear for you and do not hesitate to leave your comments if you see that it needs improvements.

See you next post in the next few days.