Mastering Redis Client Integration in Go with the Adapter Pattern

Learn how to use the Adapter pattern in Go to unify disparate Redis clients—*redis.Client* and *redis.ClusterClient*—by defining a common interface, implementing concrete adapters, and employing a factory method, resulting in cleaner, maintainable code that adheres to the open/closed principle.

Ops Development & AI Practice
Ops Development & AI Practice
Ops Development & AI Practice
Mastering Redis Client Integration in Go with the Adapter Pattern

Adapter Pattern Overview

The Adapter pattern is a structural design pattern that converts one interface into another expected by the client, allowing incompatible components to work together. It is useful during system upgrades or feature extensions where new and old interfaces differ.

Applying the Adapter Pattern to Redis Clients in Go

Go applications often interact with Redis using two main client types: *redis.Client and *redis.ClusterClient. Although their usage is similar, their interfaces are not identical, necessitating an adapter to manage them uniformly.

Defining a Common Interface

First, define a shared RedisClient interface that includes the common Redis operations needed by the application, such as Set and Get:

type RedisClient interface {
    Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error
    Get(ctx context.Context, key string) (string, error)
}

Creating Concrete Adapters

Implement the interface for each concrete Redis client by creating two adapter structs: StandardClientAdapter for *redis.Client and ClusterClientAdapter for *redis.ClusterClient.

type StandardClientAdapter struct {
    client *redis.Client
}

func (a *StandardClientAdapter) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
    return a.client.Set(ctx, key, value, expiration).Err()
}

func (a *StandardClientAdapter) Get(ctx context.Context, key string) (string, error) {
    return a.client.Get(ctx, key).Result()
}

type ClusterClientAdapter struct {
    clusterClient *redis.ClusterClient
}

func (a *ClusterClientAdapter) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
    return a.clusterClient.Set(ctx, key, value, expiration).Err()
}

func (a *ClusterClientAdapter) Get(ctx context.Context, key string) (string, error) {
    return a.clusterClient.Get(ctx, key).Result()
}

Factory Method for Adapter Creation

Introduce a factory function RedisClientFactory that receives a generic Redis configuration and returns the appropriate adapter instance, hiding the creation details from the caller.

func RedisClientFactory(redisConfig interface{}) (RedisClient, error) {
    switch client := redisConfig.(type) {
    case *redis.Client:
        return &StandardClientAdapter{client: client}, nil
    case *redis.ClusterClient:
        return &ClusterClientAdapter{clusterClient: client}, nil
    default:
        return nil, fmt.Errorf("unsupported redis client type")
    }
}

Practical Usage Example

With the factory in place, application code can obtain a RedisClient without worrying about the underlying client type, then perform operations via the unified interface.

package main

import (
    "context"
    "fmt"
    "time"
    "github.com/redis/go-redis/v9"
)

func main() {
    // Example: create a cluster client (standard client creation omitted for brevity)
    clusterClient := redis.NewClusterClient(&redis.ClusterOptions{/* config */})

    // Use the factory to get an adapter
    client, _ := RedisClientFactory(clusterClient)

    // Perform Redis operations through the adapter
    client.Set(context.Background(), "key1", "value1", 0)
    value, _ := client.Get(context.Background(), "key1")
    fmt.Println("Got value:", value)
}

Conclusion

The Adapter pattern provides a flexible and efficient solution for handling different Redis client types in Go. By introducing a common interface and concrete adapters, developers can extend system functionality and integrate new components without modifying existing code. Encapsulating adapter creation with a factory further hides implementation details, leading to cleaner, more maintainable code that respects the open/closed principle.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsBackend DevelopmentredisGoAdapter Pattern
Ops Development & AI Practice
Written by

Ops Development & AI Practice

DevSecOps engineer sharing experiences and insights on AI, Web3, and Claude code development. Aims to help solve technical challenges, improve development efficiency, and grow through community interaction. Feel free to comment and discuss.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.