Clean Architecture – Caching With Interface Adapters

Last time we discovered the versatility of Clean Architecture’s Interface Adapters shell and how it acts as a connecting layer between the central Business Logic and our system’s specific technologies—the Frameworks & Devices. 

In the example, we had business logic that wrote to and read from a SQL Server database. The code that lets us save and retrieve data from the specific database schema belongs in the Interface Adapters. Let’s check out the design we ended up with:

The advantage of this design is that when the database schema changes, our SQL Customer Repository Adapter will reflect those changes yet leave our Business Logic unaffected. Here we have powerful pluggability.

 

OK, let’s make things more interesting.

We want to introduce an optimisation—data caching. Instead of every data read running off to the database, we want first to check whether the data exists in a cache. If it does, return the data and do not read from the database. If not, go to the database, read the data, and put it in the cache for subsequent reads. 

 

Now, where should the logic reading either from the cache or the database live? What about the entirely separate logic connecting us to a specific Redic cache implementation? 

Do these belong to the business logic?

No, our business logic is the wrong place. These are both data concerns. All the business logic is concerned with is a way to read this data—that’s it. How the data is retrieved is not its concern. 

 

OK, does this logic belong to the general SQL Server and Redis caching code? i.e. in the Frameworks & Devices shell?

No, that would also be incorrect. We don’t want to mix specific and generic data access.  

 

On the other hand, it makes sense to have the code connecting our specific data retrieval from Redis cache (i.e. construction of cache keys, etc.) in a module at the same level as our specific SQL Server data retrieval code, SQL Customer Repository Adapter. 

Furthermore, the logic switching between cache and database reads must sit in front of, and connect to, both the cache and database modules.

 

OK, so a picture is forming as to a system design that includes caching:

 

We now have two layers of logic in Interface Adapters—firstly, a module to switch between reading data from cache or database. Secondly, the adapters to retrieve data from Redis and SQL Server. 

The logic to switch between cache and database is abstract. It does not mention Redis or SQL Server as the given cache or database technologies. Why? We get the flexibility to plug in other caching and database technologies.

 

Here is an example implementation of a class managing the Customer data retrieval logic, first from a cache and then from a database:  

  public class CachedCustomerRepository : ICustomerRepository
  {
     private ICache<Customer> Cache { get; set; }
     private ICustomerDatabase Database { get; set; }

     public CachedCustomerRepository(ICache<Customer> cache, ICustomerDatabase database)
     {
        Cache = cache;
        Database = database;
     }

     public Customer GetCustomer(string emailAddress)
     {
        var customer = Cache.Get(emailAddress);
        if (customer != null)
           return customer;
        customer = Database.GetCustomer(emailAddress);
        if (customer == null)
           return null;
        // Put the customer into the cache for future calls.
        Cache.Set(customer.EmailAddress, customer);
        return customer;
     }
  }

What we have here is a decent pluggable design. However, we can improve on it further by making a small change. We’ll look into that next time.

 

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply