LoginSignup
8
4

More than 3 years have passed since last update.

Docker環境下でGoとRedisでAPIを実装する (Part.2)

Last updated at Posted at 2020-02-01

はじめに

本記事は、GoとRedisでAPIを実装する (Part.1)の続きである。まだ、読んでいない方は事前に読んでいただけると幸いである。

Part.2では、APIの実装から検証を行う。

まず今回の実装に至り、実行環境は以下の通りである。

1. 実行環境

  • macOS Catalina Ver.10.15.2
  • Docker version 19.03.5, build 633a0ea
  • docker-compose version 1.24.1, build 4667896b
  • golang 1.13.6
  • Redis 5.0.7

1.1. ファイル構成

file-tree
sample_project
├── docker
│   ├── api
│   │   └── Dockerfile-api //API用
│   └── database
│       └── Dockerfile-redis //DB用
├── docker-compose.yml
└── src
    └── app
        ├── controller
        │   ├── sender.go
        │   └── receiver.go
        ├── infrastructure
        │   └── database.go
        ├── interface
        │   └── user.go
        └── main.go

2. APIの実装

今回の実装では、JSON形式のデータを扱う事にする。

sampleData.json
{
    // ユーザID
    "ID"    : "A000000",
    // ユーザ名
    "Name"  : "Bob",
    // 年齢
    "Age"   : 18,
}

Sender / Reciever それぞれの処理内容としては、以下の通りである。

  • Sender側
    • JSON形式のデータをRedisに格納する。
      • Redisで使用するキーパターンは以下の通りである
      • 「ユーザID:ユーザ名」例)A000000:Bob
  • Receiver側
    • キーを使用し、edis内からデータを取得する。

また、今回は重複データが存在する場合、データが更新されるようにする。

2.1. main.go

まず、main.goについて説明する。ここで行う事はHTTP通信のみである。gin.Default()でデフォルトのミドルウェアと共にルータを作成し、それぞれのエンドポイントで行う処理を指定する。

main.go
package main

import (
    "app/controller"
    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    // Sender
    router.POST("/sender", controller.Send())

    // Receiver
    router.GET("/receiver", controller.Receiver())

    // PORT環境変数が未指定の場合、デフォルトで8080で待受する
    router.Run()
}

今回は分かりやすい様、エンドポイントを/sender/receiverにする。

2.2. interface/user.go

JSONで渡されるデータを受け取る為に、構造体を用意しておく。

interface/user.go
package _interface

type UserInformation struct {
    // ユーザID
    ID      string  `json:"id"`
    // ユーザ名
    Name    string  `json:"name"`
    // 年齢
    Age     int     `json:"age"`
}

2.3. Redisへの接続 / 切断

Redisへの接続処理を逐一記述するのは少し面倒であるし、複数箇所でこの処理を記述するのも可読性が悪い。その為、接続処理をインスタンス化する事で、処理を集約化した。

database.go
import (
    "github.com/garyburd/redigo/redis"
    "os"
)

type Redis struct {
    connection redis.Conn
}

// Redisへの接続
func NewRedis() *Redis {
    // IPポートの設定
    const ipPort = "redis:6379"
    // redisに接続する
    c, err := redis.Dial("tcp", ipPort)
    if err != nil {
        panic(err)
    }
    // 接続情報をConnインタフェースのconnectionに保存
    r := &Redis{
        connection: c,
    }
    return r
}
// Redisからの切断
func (r *Redis) CloseRedis() {
    // redisとの通信を切断する
    _ = r.connection.Close()
}

ポート番号はデフォルトの6379に設定している。
Connインタフェースは、Redisを使用する為の主要なインターフェースである。
また、切断処理もメソッド化しておく。

2.4. Sender

2.4.1. controller/sender.go

Sender側の処理について説明する。

controller/sender.go
package controller

import (
    "app/infrastructure"
    "app/interface"
    "encoding/json"
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
)

func Send() gin.HandlerFunc {
    return func(context *gin.Context) {
        // Redisに接続する
        redis := infrastructure.NewRedis()
        // Send()処理終了後にRedisとの接続を切断する
        defer redis.CloseRedis()

        // requestInformationをUserInformationで初期化する
        requestInformation := _interface.UserInformation{}

        // 構造体をBINDする
        err := context.Bind(&requestInformation)
        if err != nil{
            context.JSON(http.StatusBadRequest, gin.H{"Status": "BadRequest"})
        }

        // Redisで使用するキーの作成
        key := requestInformation.ID + ":" + requestInformation.Name

        // 作成した構造体requestInformationをJSONに変換する
        payload, err := json.Marshal(requestInformation)
        if err != nil {
            fmt.Println("JSON Marshal Error : ", err)
            return
        }

        // key, payloadを引数にRedisに追加する
        if err := redis.Set(key, payload); err != nil {
            fmt.Println("Failed to store data in Redis. ", err)
        } else {
            context.JSON(http.StatusOK, gin.H{"Status": "Successfully added to redis. "})
        }
    }
}

一度、取得したデータを構造体requestInformationに格納し、その後にjson.Marshal()でJSON文字列に変換する。今回、Redisで使用するキーの生成はこのファイル内で簡素的に生成している。生成したkeypayloadを引数にdatabase.goの関数Set(key, payload)に渡す。

2.4.2. infrastructure/database.go (SET)

先程の、database.goにRedisへデータを追加する処理を追記する。

database.go
import (
    "fmt"
    "github.com/garyburd/redigo/redis"
    "os"
)

// ...省略

// Redisへのデータ追加
func (r *Redis) Set(key string, payload []byte) error {
    // 生成したキーが既に存在するかチェックする
    if r.keyExist(key) == true {
        fmt.Println("Delete the key because it was already registered in redis.")
        fmt.Println("Update an existing key.")
        // 存在する場合、データを更新する
        r.update(key, payload)
    } else {
        // キーをRedisに追加する
        if _, err := r.connection.Do("SET", key, payload); err != nil {
            fmt.Println("infrastructure/database/Set() : ", err)
            os.Exit(1)
            return err
        }
    }
    return nil
}

// キーチェック
func (r *Redis) keyExist(key string) bool {
    // キーが既にRedis内に存在するかチェックする
    result, err := redis.Bool(r.connection.Do("EXISTS", key))
    if err != nil {
        fmt.Println("infrastructure/database/keyExist() : ", err)
    }
    return result
}

// データの更新
func (r *Redis) update(key string, payload []byte) {
    // キーから値を取得後、新たなデータを登録する
    _, err := r.connection.Do("GETSET", key, payload)
    if err != nil {
        fmt.Println("infrastructure/database/update() : ", err)
    }
}

今回は、重複したデータを削除する処理を入れているので、一度keyExist()でRedis内にキーが存在するかチェックする。存在する場合は、update()で既存のキーからデータを取得し、新たなデータを更新する。redisにはUPDATEはないので、代わりにGETSETを使用し、更新を行う。

これでSender側の処理は終了である。

2.5 Receiver

2.5.1. controller/receiver.go

次にReceiver側の処理について説明する。ここでは、パラメータから取得したkeyを使用し、redisからデータを取得する。

controller/receiver.go
package controller

import (
    "app/infrastructure"
    _interface "app/interface"
    "encoding/json"
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
)

func Receive() gin.HandlerFunc {
    return func(context *gin.Context) {
        // Redisに接続
        redis := infrastructure.NewRedis()
        // Receive()処理終了後にRedisとの接続を切断する
        defer redis.CloseRedis()

        // クエリストリングパラメーターを取得
        key := context.Query("key")

        // responseInformationをUserInformationで初期化する
        responseInformation := _interface.UserInformation{}

        // Redisからデータを取得する
        if payload, err := redis.Get(key); err != nil {
            fmt.Println("Failed to get data from Redis. :", err)
        } else {
            // Redisから取得したpayloadをGo Object(構造体)に変換する
            if err := json.Unmarshal(payload, &responseInformation); err != nil {
                fmt.Println("Could not Unmarshal the retrieved json. :", err)
            }
            context.JSON(http.StatusOK, responseInformation)
        }
    }
}

Receiverでは、Redisに格納されているデータをクエリストリングパラメータ(URLパラメータとも言う)から、データを取得する。取得後は、json.Unmarshal()を行い、Go Objectとして構造体UserInformationに変換する。

2.5.2. infrastructure/database.go (GET)

また先程の、database.goにGET処理を追記する。

database.go
import (
    "fmt"
    "github.com/garyburd/redigo/redis"
    "os"
)

// ...省略

func (r *Redis) Get(key string) ([]byte, error) {
    // キーを使用し、Redisからデータを追加する
    payload, err := redis.Bytes(r.connection.Do("GET", key))
    if err != nil {
        fmt.Println("infrastructure/database/Set() : ", err)
        return payload, err
    }
    return payload, err
}

以上で、Sender / Receiverの処理は終了である。

では、最後に検証を行う。

3. 検証

検証では、Talend API Testerや、Postmanと言ったツールを使用すると良い。今回は、Postmanを使用して検証を行う。

3.1. Sender

スクリーンショット 2020-02-01 17.02.03.png

以下のURLにJSONをPOST通信で渡している。正常に渡せていれば結果として"Status": "Successfully added to redis. "が返ってくる。

  • URL

    • http://localhost:8080/sample_project/sender
  • 結果

Status-Success
{
    "Status": "Successfully added to redis. "
}

また、Redisに正しくデータが登録されているかどうかを確認する。確認はsample_project/redis:0.1.0のコンテナに入り、以下のコマンドを確認すると良い。

redis-cli
$ docker exec -it [CONTAINER ID] sh
/data # redis-cli
127.0.0.1:6379> GET key(自分で設定したキー)
"{\"id\":\"sample001\",\"name\":\"test001\",\"age\":99}"

IDEなどでDBが確認できるプラグインもあるが、その際はlocalhost:6379を指定して確認すると良い。

3.2. Receiver

スクリーンショット 2020-02-01 17.18.26.png

GET通信なので、URL内にパラメータとしてkeyを渡す。データが取得できれば、結果として登録したデータが返ってくる。

検証としては、以上である。

4. まとめ

今回は、2つの記事に渡り、APIの実装について記述した。今回の内容を参考にしていただければ幸いである。

また、この実装は簡素的なものであるため、懸念点なども多い。今後は、その点の改善を行っていこうと思う。

追記

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4