Flexible Caching in Go with Interfaces

Flexible Caching in Go with Interfaces

Caching is a common technique in programming to improve performance by storing expensive computations or IO results for fast lookup. In this post, we'll look at how Go's interfaces enable building flexible and extensible caches.

Defining a Cache Interface

First, let's define an interface specifying cache capabilities:

type Cache interface {
  Get(key string) interface{}
  Set(key string, value interface{})
}

This Cache interface has two methods - Get to retrieve a cached value by key, and Set to store a key-value pair.

By defining an interface, we decouple the cache usage from a specific implementation. Any cache library that implements these methods satisfies the interface.

A Simple Memory Cache

Let's implement a simple in-memory cache conforming to the interface:

type InMemoryCache struct {
  store map[string]interface{} 
}

func (c *InMemoryCache) Get(key string) interface{} {
  return c.store[key] 
}

func (c *InMemoryCache) Set(key string, value interface{}) {
  c.store[key] = value
}

The InMemoryCache uses a Go map to store entries in memory. It implements the Get and Set methods to manage entries in the map.

Using the Cache

We can now easily use the cache:

cache := InMemoryCache{make(map[string]interface{})}

cache.Set("foo", "bar")

value := cache.Get("foo") // "bar"

The interface allows us to call Set and Get without worrying about the implementation.

Swapping Cache Implementations

Now let's say we want to use Redis instead of in-memory. We can create a RedisCache implementing the same interface:

type RedisCache struct {
  client *redis.Client 
}

// Redis Get/Set implementation

And swap it in:

cache := RedisCache{redisClient} 

cache.Set("foo","bar") // Now uses Redis

The client code remains unchanged. This demonstrates the flexibility provided by interfaces.

Benefits of Interface-based Caching

Using interface-based caching gives several benefits:

  • Decoupling - Client code isn't coupled to a specific cache library.

  • Maintainability - The cache implementation can be changed without modifying the client code.

  • Testability - Caches can be stubbed or mocked for testing.

  • Reusability - Generic cache interface enables writing reusable caching logic.

Summary

Interfaces in Go helm in building flexible libraries and applications. Defining simple interfaces makes code more:

  • Modular - Different implementations can be plugged in.

  • Extensible - New implementations can be added without disruption.

  • Maintainable - Components can be swapped for easy maintenance.

  • Testable - Components can be stubbed and mocked.

By providing powerful abstraction with minimal overhead, interfaces are invaluable in Go for creating loosely coupled and scalable systems.

Did you find this article valuable?

Support The Bug Shots by becoming a sponsor. Any amount is appreciated!