Implement and Test the Go Version of Redis Caching Class


golang Implement and Test the Go Version of Redis Caching Class cache database Go Go Programming programming languages Redis Caching

The GoLang

This post writes a go version of caching layer using redis, wrapps it in a class and provides a usage.

Let’s create a Go package that provides a caching layer using Redis. We’ll use the popular go-redis/redis/v8 package to interact with Redis. We’ll define a Cache struct with methods to set and get values, and wrap the functionality in a class-like structure.

First, you need to install the go-redis/redis/v8 package:

go get github.com/go-redis/redis/v8

Redis Cache Layer in Go

Now, let’s create our caching layer in Go.

package cache

import (
	"context"
	"time"

	"github.com/go-redis/redis/v8"
)

// Cache struct wraps the Redis client
type Cache struct {
	client *redis.Client
	ctx    context.Context
}

// NewCache creates a new Cache instance
func NewCache(addr, password string, db int) *Cache {
	rdb := redis.NewClient(&redis.Options{
		Addr:     addr,
		Password: password,
		DB:       db,
	})

	return &Cache{
		client: rdb,
		ctx:    context.Background(),
	}
}

// Set sets a value in the cache with an expiration time
func (c *Cache) Set(key string, value interface{}, expiration time.Duration) error {
	return c.client.Set(c.ctx, key, value, expiration).Err()
}

// Get gets a value from the cache
func (c *Cache) Get(key string) (string, error) {
	val, err := c.client.Get(c.ctx, key).Result()
	if err == redis.Nil {
		return "", nil // Key does not exist
	} else if err != nil {
		return "", err
	}
	return val, nil
}

// Remove deletes a key from the cache
func (c *Cache) Remove(key string) error {
	return c.client.Del(c.ctx, key).Err()
}

// Example usage
func Example() {
	cache := NewCache("localhost:6379", "", 0)

	// Set a value with an expiration of 1 hour
	err := cache.Set("exampleKey", "exampleValue", time.Hour)
	if err != nil {
		panic(err)
	}

	// Get the value
	value, err := cache.Get("exampleKey")
	if err != nil {
		panic(err)
	}
	
	if value == "" {
		println("Key does not exist")
	} else {
		println("Value:", value)
	}
}

Now, let’s use the caching layer in a simple main.go to demonstrate its usage.

package main

import (
	"fmt"
	"time"

	"your_module_name/cache"
)

func main() {
	c := cache.NewCache("localhost:6379", "", 0)

	// Set a value in the cache
	err := c.Set("greeting", "Hello, World!", 10*time.Minute)
	if err != nil {
		fmt.Println("Error setting cache:", err)
		return
	}

	// Get the value from the cache
	val, err := c.Get("greeting")
	if err != nil {
		fmt.Println("Error getting cache:", err)
		return
	}

	fmt.Println("Cached value:", val)
}

Here are some explanations:

  • Cache struct: This wraps a Redis client and provides methods for interacting with Redis.
  • NewCache function: This initializes a new Cache instance.
  • Set method: This sets a key-value pair in the cache with an expiration time.
  • Get method: This retrieves a value from the cache by its key.
  • Example function: This demonstrates setting and getting a value using the cache.

To run this, ensure Redis is running on your local machine or the specified address, and replace “your_module_name” with the actual name of your module.

redis-server Implement and Test the Go Version of Redis Caching Class cache database Go Go Programming programming languages Redis Caching

The Redis Caching Server

Start the Redis Server

To start Redis on your local machine, you have several options depending on your operating system. Here are the steps for the most common operating systems:

Run Redis On Linux

If you’re using a Linux distribution, you can typically install Redis using your package manager. For example, on Ubuntu, you can use:

1
2
sudo apt update
sudo apt install redis-server
sudo apt update
sudo apt install redis-server

Once installed, start the Redis server with:

1
sudo service redis-server start
sudo service redis-server start

Run Redis On macOS

If you have Homebrew installed, you can install Redis using:

1
2
brew update
brew install redis
brew update
brew install redis

Start Redis using:

1
brew services start redis
brew services start redis

Run Redis On Windows

Redis is not natively supported on Windows, but you can use the Windows Subsystem for Linux (WSL) or install a Redis version specifically compiled for Windows from third-party sources.

You can start Redis Using WSL:

  • Install WSL and a Linux distribution (like Ubuntu) from the Microsoft Store.
  • Open the WSL terminal and follow the Linux instructions to install and start Redis.
  • Using a precompiled Redis binary for Windows:
  • Download Redis for Windows from the Microsoft Open Tech GitHub page.
  • Extract the downloaded archive.
  • Open Command Prompt and navigate to the extracted folder.
  • Run the Redis server by executing: redis-server.exe

Run Redis using Docker Image

You can also use Docker to run Redis on any operating system: Make sure Docker is installed and running on your system.

Pull the Redis Docker image by:

1
docker pull redis
docker pull redis

Run the Redis container:

1
docker run --name redis -d -p 6379:6379 redis
docker run --name redis -d -p 6379:6379 redis

Verifying Redis is Running

To verify that Redis is running, you can use the Redis CLI:

1
redis-cli ping
redis-cli ping

You should get a response of PONG, indicating that the Redis server is up and running.

Using the Redis Server

Once Redis is running, you can use the Go code provided earlier to interact with the Redis server. Simply run your main.go and it should be able to connect to Redis and perform the set and get operations as demonstrated.

Tests the Cache Layer in Go

Testing in Go is straightforward using the testing package. Below, I’ll write a test file (Unit Test) for cache.go to test the Set and Get methods of the Cache struct. Make sure your Go module is properly set up to include the cache package, and that Redis is running on your local machine.

package cache

import (
	"testing"
	"time"
)

// Helper function to create a new Cache for testing
func newTestCache() *Cache {
	return NewCache("localhost:6379", "", 0)
}

func TestCache_SetAndGet(t *testing.T) {
	cache := newTestCache()

	// Test data
	key := "testKey"
	value := "testValue"
	expiration := 10 * time.Minute

	// Test setting a value in the cache
	err := cache.Set(key, value, expiration)
	if err != nil {
		t.Fatalf("Failed to set value in cache: %v", err)
	}

	// Test getting the value from the cache
	retrievedValue, err := cache.Get(key)
	if err != nil {
		t.Fatalf("Failed to get value from cache: %v", err)
	}

	if retrievedValue != value {
		t.Errorf("Expected value %s, but got %s", value, retrievedValue)
	}

	// Clean up
	err = cache.Remove(key)
	if err != nil {
		t.Fatalf("Failed to delete key from cache: %v", err)
	}
}

func TestCache_GetNonexistentKey(t *testing.T) {
	cache := newTestCache()

	// Test getting a value for a nonexistent key
	key := "nonexistentKey"
	value, err := cache.Get(key)
	if err != nil {
		t.Fatalf("Failed to get value from cache: %v", err)
	}

	if value != "" {
		t.Errorf("Expected empty value for nonexistent key, but got %s", value)
	}
}

func TestCache_SetWithExpiration(t *testing.T) {
	cache := newTestCache()

	// Test data
	key := "expiringKey"
	value := "value"
	expiration := 1 * time.Second

	// Test setting a value in the cache with expiration
	err := cache.Set(key, value, expiration)
	if err != nil {
		t.Fatalf("Failed to set value in cache: %v", err)
	}

	// Wait for the key to expire
	time.Sleep(2 * time.Second)

	// Test getting the value from the cache after expiration
	retrievedValue, err := cache.Get(key)
	if err != nil {
		t.Fatalf("Failed to get value from cache: %v", err)
	}

	if retrievedValue != "" {
		t.Errorf("Expected empty value for expired key, but got %s", retrievedValue)
	}
}

func TestCache_Remove(t *testing.T) {
	cache := newTestCache()

	// Test data
	key := "removeKey"
	value := "value"
	expiration := 10 * time.Minute

	// Test setting a value in the cache
	err := cache.Set(key, value, expiration)
	if err != nil {
		t.Fatalf("Failed to set value in cache: %v", err)
	}

	// Test removing the key from the cache
	err = cache.Remove(key)
	if err != nil {
		t.Fatalf("Failed to remove key from cache: %v", err)
	}

	// Test getting the value from the cache after removal
	retrievedValue, err := cache.Get(key)
	if err != nil {
		t.Fatalf("Failed to get value from cache: %v", err)
	}

	if retrievedValue != "" {
		t.Errorf("Expected empty value for removed key, but got %s", retrievedValue)
	}
}

Here are some explanations:

  • newTestCache Function: This is a helper function to create a new instance of the Cache for testing purposes.
  • TestCache_SetAndGet:
    • This test checks the Set and Get methods
    • Sets a value in the cache.
    • Retrieves the value from the cache and compares it to the original value.
    • Cleans up by deleting the key from Redis.
  • TestCache_GetNonexistentKey:
    • This test checks the behavior of the Get method when the key does not exist.
    • Tries to get a value for a nonexistent key and checks that the returned value is empty.
  • TestCache_SetWithExpiration:
    • This test checks that a key-value pair set with an expiration is correctly removed from the cache after the expiration time.
    • Sets a value with a short expiration time.
    • Waits for the expiration time to pass.
    • Attempts to get the value from the cache and checks that it has been removed.
  • TestCache_Remove: This test checks that the Remove function is correctedly implemented.

Running the Tests

To run the tests, use the go test command in the terminal:

1
go test -v ./...
go test -v ./...

The -v flag makes the output more verbose, so you can see which tests are running and their results. If everything is set up correctly, the tests should pass, indicating that the Cache struct is working as expected.

Example Output if Redis is Running

If Redis is running properly, you should see an output indicating that the tests have passed successfully:

=== RUN   TestCache_SetAndGet
--- PASS: TestCache_SetAndGet (0.05s)
=== RUN   TestCache_GetNonexistentKey
--- PASS: TestCache_GetNonexistentKey (0.01s)
=== RUN   TestCache_SetWithExpiration
--- PASS: TestCache_SetWithExpiration (1.05s)
PASS
ok      your_module_name/caching  1.124s

This confirms that your Cache implementation is working correctly with Redis.

Project Structure of Cache Layer in Go

If you see this error: The error undefined: Cache typically occurs when the test file cannot find the Cache struct. This might be due to incorrect package naming or placement of the test file.

Project Structure: Ensure your project structure is correctly set up. It should look something like this:

your_module_name/
├── cache/
│   ├── cache.go
│   └── cache_test.go
├── go.mod
└── main.go

Package Declaration: Make sure the cache.go and cache_test.go files declare the package cache.
go.mod: Ensure your go.mod file is properly set up. Replace your_module_name with your actual module name:

module your_module_name

go 1.21

require (
    github.com/go-redis/redis/v8 v8.11.5
)

–EOF (The Ultimate Computing & Technology Blog) —

GD Star Rating
loading...
2012 words
Last Post: Why and How You Should Stop the ChatGPT and Bytedance Bots Crawling Your Pages?
Next Post: Teaching Kids Programming - The @cache Function Decorator in Python

The Permanent URL is: Implement and Test the Go Version of Redis Caching Class

Leave a Reply