元営業WEBエンジニアのアプリ開発日記

営業出身のWEB系エンジニアが気になったものから作ってはメモを残してくブログ

GAE(Go1.12)でDatastoreに接続してみるぞ

概要

GAE(Go1.12)でDatastoreに接続してみる

構築

Datastoreへ接続

Datastoreに接続するための初期化処理開始

initメソッドで初期化処理を実施
Datastoreを操作するDao的なDBを作成していく!!

internal/config.go

var (
    DB repository.MemberDatabase
)

func init(){
    var err error
    DB, err = configureDatastore("ucwork-ai-000002")
    if err != nil {
        log.Fatal(err)
    }
}

Datastoreに接続するためのclient作成

projectIDを指定してDatastoreに接続するためのclientを作成

internal/config.go

func configureDatastore(projectID string) (repository.MemberDatabase, error){
    ctx := context.Background()
    client, err := datastore.NewClient(ctx, projectID)
    if err != nil {
        return nil, err
    }
    return db.NewDatastoreDB(client)
}

Datastoreへの接続確認と構造体の完成

NoSQLってトランザクションないんちゃったっけ。
トランザクション使ってDatastoreへ接続できるか確認

接続できることを確認したら対象のclientをdatastoreDBへ設定し返却
戻り値の型がrepository.MemberDatabaseなのでこの構造体はDB操作系のメソッドを持つのである。

internal/db/datastore.go

type datastoreDB struct {
    client *datastore.Client
}

var _ repository.MemberDatabase = &datastoreDB{}

func NewDatastoreDB(client *datastore.Client) (repository.MemberDatabase, error) {
    ctx := context.Background()
    // Verify that we can communicate and authenticate with the datastore service.
    t, err := client.NewTransaction(ctx)
    if err != nil {
        return nil, fmt.Errorf("datastoredb: could not connect: %v", err)
    }
    if err := t.Rollback(); err != nil {
        return nil, fmt.Errorf("datastoredb: could not connect: %v", err)
    }
    return &datastoreDB{
        client: client,
    }, nil
}

Datastoreでの操作作成

インターフェース定義

とりあえず登録と一覧表示のメソッドを宣言
プロパティはなんでもいいからIDとNameを定義

internal/repository/member.go

// Member hold metadata about a Membe<ffff>r
type Member struct {
    ID      int64
    Name    string
}

// MemberDatabase provides access to a database od member
type MemberDatabase interface {
    // ListMembers returns member list
    ListMembers() ([]*Member, error)
    // AddMember saves a given member, assigning it a new ID
    AddMember(member *Member) (id int64, err error)
}

実装

client.Putとかclient.GetAllすると登録したり一覧取得できるらしい

internal/db/datastore.go

func (db *datastoreDB) AddMember(member *repository.Member) (id int64, err error){
    ctx := context.Background()
    k := datastore.IncompleteKey("Member", nil)
    k, err = db.client.Put(ctx, k, member)
    if err != nil {
        return 0, fmt.Errorf("datastoredb: could not put Member: %v", err)
    }
    return k.ID, nil
}

func (db *datastoreDB) ListMembers() ([]*repository.Member, error) {
    ctx := context.Background()
    members := make([]*repository.Member, 0)
    q := datastore.NewQuery("Member").
        Order("Name")

    keys, err := db.client.GetAll(ctx, q, &members)

    if err != nil {
        return nil, fmt.Errorf("datastoredb: could not list books: %v", err)
    }

    for i, k := range keys {
        members[i].ID = k.ID
    }

    return members, nil
}

DB操作の呼び出し

Goでルーティング(gorilla/mux) で作成したルーティングに今回の処理を実装してみる。

jsonで受け取ってjsonで返却するAPIのイメージ。とりあえず動けばいいや感。

func createHandler(w http.ResponseWriter, r *http.Request) *appError {
    // json decode
    decoder := json.NewDecoder(r.Body)
    var memberRequest endpoint.MemberRequest
    err := decoder.Decode(&memberRequest)
    if err != nil {
        return appErrorFormat(err, "decode error: %s", err)
    }

    // object convert
    member, err := memberFromJson(&memberRequest)
    if err != nil {
        return appErrorFormat(err, "convert error: %s", err)
    }

    // save member to db
    id, err := internal.DB.AddMember(member)
    if err != nil {
        return appErrorFormat(err, "add db error: %s", err)
    }

    // create response
    response, jsonError := json.Marshal(member)
    if jsonError != nil {
        return appErrorFormat(jsonError, "%s", jsonError)
    }
    _, writeError := w.Write(response)
    if writeError != nil {
        return appErrorFormat(writeError, "%s", writeError)
    }
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Location", "/members/"+string(id))
    w.WriteHeader(201)
    return nil
}

func listHandler(w http.ResponseWriter, r *http.Request) *appError {
    members, err := internal.DB.ListMembers()
    if err != nil {
        return appErrorFormat(err, "%s", err)
    }

    response, jsonError := json.Marshal(members)
    if jsonError != nil {
        return appErrorFormat(jsonError, "%s", jsonError)
    }

    _, writeError := w.Write(response)
    if writeError != nil {
        return appErrorFormat(writeError, "%s", writeError)
    }
    w.Header().Set("Content-Type", "application/json")
    return nil
}

検証

ローカル環境起動

以下コマンド実行

go run cmd/ucwork/main.go

datastoreに登録

POST通信で登録してみると何やら返却された。
きっとうまくいってそう。本当は201だろとかあるけどdatastoreには登録できたっぽい

curl -v -X POST -H "Content-Type: application/json" -d '{"Name":"ucwork"}' http://localhost:8080/members                      +[add_datastore_connection#6]
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /members HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 17
>
* upload completely sent off: 17 out of 17 bytes
< HTTP/1.1 200 OK
< Date: Wed, 25 Sep 2019 08:04:14 GMT
< Content-Length: 24
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
{"ID":0,"Name":"ucwork"}%

datastoreから一覧取得

GETで一覧取得してみるとさっきの取れた!

curl -v http://localhost:8080/members                                                                                         +[add_datastore_connection#6]
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /members HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Wed, 25 Sep 2019 08:05:56 GMT
< Content-Length: 41
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
[{"ID":5631986051842048,"Name":"ucwork"}]%

コンソール上

コンソール上でも登録されてる!!
f:id:shintaro-0112:20190925171502p:plain

というか、ローカルから叩いても本物に登録されるのか...

gcloud beta emulators datastore start使うとローカルで仮想的なdatastoreできるらしい。
とりあえず一通り触ってみるが目的なんで、今度ちゃんと触るときにローカルエミュレートしよー

まとめ

GCPというよりgoの学習に時間割かれてる気がする・・・
とりあえずDatastore触ったから次はCloudSQLかな。