すきま風

勉強したことのメモとか

golangでRedisの参照と更新をする

golangを勉強することにしました。頑張るぞ

初回は A Tour of Go すら読んでいない状態で、勢いと感性のままにRedis Access をするCLI を作ります。

  • json fileからデータを読み込んでEntityを作成する
  • redigoを使ってRedis参照と更新ができる
  • Interface を作って複数のEntityを同じコンテキストで扱えるようにする

Redigoを選んだのは、Redis公式でrecommendされていたからです 🙃

main

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "github.com/gomodule/redigo/redis"
    "io/ioutil"
    "os"
    "redis-cli/entity"
)

func main() {
    // parameterを受け取る
    var (
        t = flag.String("t", "", doc())
        u = flag.Bool("u", false, "update mode enabled?")
    )
    flag.Parse()

    fmt.Printf("type: %s\n", *t)
    fmt.Printf("update: %t\n", *u)

    // entityType object
    et := entity.NewEntityType(*t)
  
    // entityType validation
    err := et.ValidateEntityType()
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }

    // json file read && unmarshal
    raw, err := ioutil.ReadFile(et.JsonFilePath())
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }

    ec := unmarshal(raw, *et)

    // redis connect
    conn, err := redis.Dial("tcp", os.Getenv("REDIS_HOST"))
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }
    defer conn.Close()

    // 更新前の情報を取得して出力する
    for _, e := range ec {
        fmt.Println("HGETALL Key:", e.Key())
        val, _ := redis.StringMap(conn.Do("HGETALL", e.Key()))
        if val != nil {
            fmt.Println("Value:", val)
        }

        // 更新モードではない場合次へ
        if !*u {
            continue
        }

        // set value if update mode true
        fmt.Println("Set Value")

        // interfaceでポリモーフィズム的なことをする
        for k, v := range e.Values() {
            // skip empty value
            if len(v) == 0 {
                continue
            }

            fmt.Println("[HSET]", "SubKey:", k, "Value:", v)

            r, err := conn.Do("HSET", e.Key(), k, v)
            if err != nil {
                fmt.Println(err.Error())
                os.Exit(1)
            }
            if r == 1 {
                fmt.Println("set new sub key:", k)
            }
        }

        fmt.Println("")
    }

    fmt.Println("Script end successfully.")
}

func unmarshal(raw []byte, t entity.Type) []entity.IEntity {
    switch t.V {
    case "book":
        var ec entity.BookCollection
        if err := json.Unmarshal(raw, &ec); err != nil {
            fmt.Println(err.Error())
            os.Exit(1)
        }

        return ec.Collection()
    case "publisher":
        var ec entity.PublisherCollection
        if err := json.Unmarshal(raw, &ec); err != nil {
            fmt.Println(err.Error())
            os.Exit(1)
        }

        return ec.Collection()
    default:
        panic("error")
    }
}

func doc() string {
    return `
set redis table type
book
publisher
`
}

Entity

  • Book と Publisher を用意
  • InterfaceにIEntityを用意
package entity

type IEntity interface {
    // keyを返す
    Key() string

    // HSETするvaluesをmapで返す
    Values() map[string]string
}
package entity

import "strconv"

type BookCollection struct {
    Books []Book `json:"books"`
}

type Book struct {
    Isbn          string   `json:"isbn"`
    Price         string   `json:"price"`
    PublisherCode string   `json:"publisherCode"`
    Authors       []Author `json:"authors"`
}

type Author struct {
    Name string `json:"name"`
}

func (b Book) Key() string {
    return b.Isbn
}

func (b Book) Values() map[string]string {
    m := map[string]string{
        "price":         b.Price,
        "publisherCode": b.PublisherCode,
    }

    // authorは複数所持
    for i, a := range b.Authors {
        m["author_"+strconv.Itoa(i)] = a.Name
    }

    return m
}

// object arrayをinterface arrayにして返す
func (bc BookCollection) Collection() []IEntity {
    var entities []IEntity

    for _, b := range bc.Books {
        // objectをInterfaceにする
        var e IEntity = b
        entities = append(entities, e)
    }

    return entities
}
package entity

type PublisherCollection struct {
    Publishers []Publisher `json:"publishers"`
}

type Publisher struct {
    PublisherCode string `json:"publisherCode"`
    Name          string `json:"name"`
}

func (p Publisher) Key() string {
    return p.PublisherCode
}

func (p Publisher) Values() map[string]string {
    return map[string]string{
        "Name": p.Name,
    }
}

func (pc PublisherCollection) Collection() []IEntity {
    var entities []IEntity

    for _, p := range pc.Publishers {
        var e IEntity = p
        entities = append(entities, e)
    }

    return entities
}
package entity

import "errors"

type Type struct {
    V string
}

func NewEntityType(v string) *Type {
    return &Type{V: v}
}

func (t Type) JsonFilePath() string {
    switch t.V {
    case "book":
        return "./json/book.json"
    case "publisher":
        return "./json/publisher.json"
    default:
        panic("error")
    }
}

func (t Type) ValidateEntityType() error {
    switch t.V {
    case "book", "publisher":
        return nil
    default:
        return errors.New("error")
    }
}

json

book.json

{
  "books" :[
    {
      "isbn": "4000000000",
      "price": "1000",
      "publisherCode": "1000",
      "authors": [
        {
          "name": "foo"
        }
      ]
    }
  ]
}

まとめ

自分が正しい方向に進んでいるのかよくわからなくなったので基礎から勉強することにします 😑
目標はgoroutineを使いこなせるようになることです!