Redis公式のGoクライアントライブラリrueidisを試してみた

# This 記事 is 何？
Twitterぼんやり見てたらRedis公式のGo用クライアントライブラリが出てたとかで、自身のプロジェクトにどの程度簡単に入れられるのかなーと思い試してみました。

公式によると今使っている[go-redis](https://github.com/redis/go-redis)よりも速い！とのことだったので✨
基本`かんたん`なことしかやってないですが、移植の際に必要になりそうな`HSet/HGetAll`も書いてみています。

公式：https://github.com/redis/rueidis

# 構造
自身のプロジェクトに適用するイメージが湧きやすいよう、Redis操作は`repository`の形で実装しています。
Redis接続とClient生成は`infra`ディレクトリに格納したredis.goですね。
あと、Redisを立ち上げるためにdocker-compose.yamlも書いています。
```
.
├─ main.go
├─ infra
│   ├─ redis.go
├─ repository
│   ├─ cache_repository.go
├─ docker-compose.yaml
├─ .env
```

# コード 
3時間ほど、公式と自身のプロジェクトを見比べながら、あとRedisにも精通してなかったのでRedisコマンド見ながら格闘した結果が下記です。

## main.go
godotenvで`.env`を読んでいるのは、RedisのHost,Portを環境別に管理するイメージのコードにしたかったからで、`infra.Redis()`でClient生成して、RepositoryをNewしています。
あとはRepositoryの`SetEx`や`Get`、`HSet`・`HGetAll`を読んでその結果を`fmt.Println(f)`しているだけですね。
```main.go
package main

import (
	"context"
	"fmt"
	"handson-ruieidis/infra"
	"handson-ruieidis/repository"
	"log"

	"github.com/joho/godotenv"
)

type account struct {
	Name string
	Age  string
}

func main() {
	err := godotenv.Load(".env")
	if err != nil {
		panic("Error loading .env file")
	}

	repo := repository.NewCacheRepository(infra.Redis())
	defer infra.Redis().Close()

	ctx := context.Background()
	// SET key1 val1
	if err := repo.SetEx(ctx, "key1", "val1", 65535); err != nil {
		log.Fatal(err)
	}
	// GET key1
	val, err := repo.Get(ctx, "key1")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Get key1:", val) // "val1"

	hSetKey := "account_1"
	sampleAccount := &account{
		Name: "kojikoji",
		Age:  "41",
	}
	if err := repo.HSet(ctx, hSetKey, sampleAccount); err != nil {
		log.Fatal(err)
	}

	hGetAll, err := repo.HGetAll(ctx, hSetKey)
	if err != nil {
		log.Fatal(err)
	}

	for key, value := range hGetAll {
		fmt.Printf("key:%s\nvalue:%s\n", key, value)
	}
}
```

## `repository/cache_repository.go`
go-redisの書き方とちょっと違ってcommandBuild形式？なので、最初は違和感ありましたが、最後にはスラスラ書けるようになってました。びっくり。

ただ、HSetで構造体を渡すUsecaseで、ループで一度に`FieldValue`に全フィールドと値をセットしてclient.Doするとなぜか怒られてしまったので、今回は1フィールドずつHSetする形にしています。
公式のなかに、おそらく有志が作ったと思われる`Go-redis like API adapter`があったので、そちらも参考にしてみたのですがちょっと施行時間が足りずうまくいかなったので再チャレンジしたいですね。
```repository/cache_repository.go
package repository

import (
	"context"
	"reflect"
	"time"

	"github.com/redis/rueidis"
)

type CasheRepository interface {
	SetEx(ctx context.Context, key string, value string, ttl int64) error
	Get(ctx context.Context, key string) (string, error)
	HSet(ctx context.Context, key string, target interface{}) error
	HGetAll(ctx context.Context, key string) (map[string]string, error)
}

type casheRepository struct {
	client rueidis.Client
}

func NewCacheRepository(client rueidis.Client) CasheRepository {
	repo := &casheRepository{
		client: client,
	}

	return repo
}

func (r *casheRepository) SetEx(ctx context.Context, key string, value string, ttl int64) error {
	return r.client.Do(ctx, r.client.B().Setex().Key(key).Seconds(ttl).Value(value).Build()).Error()
}

func (r *casheRepository) Get(ctx context.Context, key string) (string, error) {
	result := r.client.Do(ctx, r.client.B().Get().Key(key).Build())
	return result.ToString()
}

func (r *casheRepository) HSet(ctx context.Context, key string, target interface{}) error {
	expire := 86400

	val := reflect.ValueOf(target).Elem()

	for i := 0; i < val.NumField(); i++ {
		hSetFieldValue := r.client.B().Hset().Key(key).FieldValue()
		typeField := val.Type().Field(i)
		fieldName := typeField.Name
		fieldValue := val.FieldByName(typeField.Name).Interface()
		hSetFieldValue = hSetFieldValue.FieldValue(fieldName, fieldValue.(string))
		if err := r.client.Do(ctx, hSetFieldValue.Build()).Error(); err != nil {
			return err
		}
	}

	ttl := int64(expire * int(time.Second))
	if err := r.client.Do(ctx, r.client.B().Expire().Key(key).Seconds(ttl).Build()).Error(); err != nil {
		return err
	}
	return nil
}

func (r *casheRepository) HGetAll(ctx context.Context, key string) (map[string]string, error) {
	result := r.client.Do(ctx, r.client.B().Hgetall().Key(key).Build())
	return result.AsStrMap()
}
```
## 出力
結果は下記のように出力されました。
かんたんなことしかしてないので速度とかは比べる必要ないものの、当たり前のように一瞬で表示されます🙌🏻
```
$ go run main.go
Get key1: val1
key:field
value:value
key:Name
value:kojikoji
key:Age
value:41
```
# 感想
普段あまり趣味とか業務外とかでコード書いたりしないのですが、自身のプロジェクトとか業務にどう活かせるかなと考えながら書いていくのは楽しかったです。
Twitterも基本眺めるだけなんですが、また自身のプロジェクトに適用できそうな面白そうなものがあったら、軽く試してみるのも良いな、と思いました。

# 成果物
せっかくなのでGitHubにリポジトリ作ってみました。
https://github.com/bayobayo0324/handson-ruieidis/tree/main