Flexible Caching in Go with Interfaces

Yet another developer, learning new things everyday :)
Search for a command to run...

Yet another developer, learning new things everyday :)
No comments yet. Be the first to comment.
A little story about why I built something that just remembers what a website said last, so it doesn't matter if that website is having a bad day.

Rediscovering RSS Feeds :)
My Long and Winding Road to Becoming a Vim/Neovim User

Mesmerize Your Audience with a Git Visualization Party Trick

How to Use Git Submodules to Manage Shared Code, Tasks, and Development Environments Across Multiple Microservice Repositories

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.
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.
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.
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.
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.
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.
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.