このコードラボでは、タスクを追加・表示・編集するプログラムを Go で書いて Google Cloud Functions に公開することで、イベント駆動型のサーバレスアプリケーション開発を体験できます。

下記の流れで開発を行います。

  1. Go で書かれたサンプルコードを取得・確認する。
  2. サンプルコードを Google Cloud Functions に公開する。
  3. 公開したアプリケーションを動かしてみる。
  4. サンプルコードを変更して 2. と 3. を行う。

Google Cloud Functions とは

Google Cloud Functions は、Google Cloud Platform 上のサービスの一つです。

また、2019年1月より Go でも書けるようになりました。

Google Cloud Functions には、 HTTPS でアクセスすることで実行できる HTTP 関数と、Google Cloud Platform 内のイベントから呼び出すことができるバックグラウンド関数の2種類があります。ここでは HTTP 関数を作成します。

Go の開発環境は必要でしょうか?

このコードラボでは Google Cloud Shell というクラウド上の開発環境を利用します。お使いのコンピューター上で Go の開発環境を用意する必要はありません。

Google Cloud Shell の詳しい説明と使い方は Google Cloud Shell で Go の開発をはじめよう を参照してください。

プロジェクトにアクセスする

Google Cloud Shell で Go の開発をはじめよう の手順に従って、Google Cloud Console にアクセスしましょう。

GitHub からコードラボで利用するサンプルコードを取得しましょう。

Google Cloud Shell で Go の開発をはじめよう の手順に従って、git clone コマンドを実行します。

$ git clone https://github.com/WomenWhoGoTokyo/codelab.git

codelab/taskmanager というディレクトリが作られていることを Google Cloud Shell のコンソールで確認しましょう。

$ cd codelab/taskmanager
$ ls
function.go  go.mod  go.sum  parameter.go  status.go  task.go

git clone したファイルの中に go.mod があります。Google Cloud Functions で Go のプログラムを動かすためには、Go Modules が必要です。

Google Cloud Shell で Go の開発をはじめよう の手順に従って, Goolge Cloud Shell のエディタで codelab > taskmanager > go.mod を開いてみましょう。

module functions/taskmanager

go 1.11

require (
        cloud.google.com/go v0.38.0
        github.com/pkg/errors v0.8.1
)

このように、Go のランタイム (このコードラボでは 1.11) が指定されています。

また、プログラム内で利用している外部パッケージのバージョンもここで指定します。

このコードラボでは、Go Modules の詳細な仕組みについては触れませんが、興味のある方は調べてみてください。

取得した Go のプログラムを Google Cloud Shell で Go の開発をはじめようGoogle Cloud Shell で Go のプログラムを公開する の手順に従って、Google Cloud Functions に公開してみましょう。

$ gcloud functions deploy {Cloud Functions Name} \
--runtime go111 \
--entry-point TaskManager \
--trigger-http

--entry-point は省略することが可能ですが、その場合は、Cloud Functions Nameと同じになります。

公開コマンドを実行の後、このようにコンソールに表示されたら、うまく公開ができています。

status: ACTIVE
timeout: 60s
updateTime: '2019-05-11T08:11:43Z'
versionId: '1'

このコードラボでは、Cloud Functions Nameを環境変数にセットしてプログラム内で利用します。

環境変数を設定するコマンドを実行しましょう。

$ gcloud beta functions deploy {Cloud Functions Name} \
--set-env-vars MY_CODE={Cloud Functions Name}

公開したアプリケーションを確認しましょう。

アクセスする

https://console.cloud.google.com/?hl=ja にアクセスしましょう。

Google Cloud Platform の Cloud Functions をクリックすると、Cloud Functions の一覧が表示されます。

一覧で、公開した Cloud Functions Nameをクリックします。

トリガーをクリックし、HTTP トリガーの URL を確認しましょう。

タスクを追加する

コンソールでタスクを追加するコマンドを実行しましょう。

1つ目:

$ curl -XPOST -H "Content-Type: application/json" \
-d '{"title":"銀行に行く"}' \
{HTTP トリガー URL}

データを見る

Google Cloud Platform 上でデータを確認します。

データストア > エンティティ をクリックしましょう。

"種類" を Cloud Functions Nameにすると、コマンドで登録した情報が格納されていることが確認できます。

さらにタスクを追加する

何回か curl コマンドを実行し、複数のタスクを登録します。

下記は例です。自由なタスクを登録しましょう。

2つ目:

$ curl -XPOST -H "Content-Type: application/json" \
-d '{"title":"コードラボの準備をする"}' \
{HTTP トリガー URL}

3つ目:

$ curl -XPOST -H "Content-Type: application/json" \
-d '{"title":"VimGolfをする"}' \
{HTTP トリガー URL}

4つ目:

$ curl -XPOST -H "Content-Type: application/json" \
-d '{"title":"リンゴを買って帰る"}' \
{HTTP トリガー URL}

5つ目:

$ curl -XPOST -H "Content-Type: application/json" \
-d '{"title":"車を洗う"}' \
{HTTP トリガー URL}

さらに追加したタスクを見る

さらに追加したタスクが登録されていることを確認しましょう。

登録したタスクを JSON で取得しましょう。

Google Cloud Shell で Go の開発をはじめよう の手順に従って、Goolge Cloud Shell のエディタを立ち上げます。

タスクを取得するプログラムを書く

codelab > taskmanager > task.go を開きましょう。

下記の場所にプログラムを追加します。

書き換える箇所は、このように変更します。

Before:

type Task struct {
    ID        int64     `datastore:"-"`
    Title     string    `datastore:"title"`
    Status    Status    `datastore:"status"`
    CreatedAt time.Time `datastore:"createdAt"`
}

After:

type Task struct {
    ID        int64     `datastore:"-" json:"ID"`
    Title     string    `datastore:"title" json:"title"`
    Status    Status    `datastore:"status" json:"status"`
    CreatedAt time.Time `datastore:"createdAt" json:"createdAt"`
}

また、ファイルの最後に、この関数を追加しましょう。

func getAllTask() ([]Task, error) {
    ctx := context.Background()

    client, err := datastore.NewClient(ctx, "wwgt-codelabs")
    if err != nil {
        return nil, err
    }

var t []Task

    q := datastore.NewQuery(os.Getenv("MY_CODE"))
    keys, err := client.GetAll(ctx, q, &t)
    if err != nil {
        return nil, err
    }

    for i, key := range keys {
        t[i].ID = key.ID
    }
    return t, nil
}

タスクを取得する処理を呼び出すプログラムを書く

codelab > taskmanager > function.go を開きましょう。

下記の場所にプログラムを追加します。

タスクを取得するプログラムの呼び出しは、下記のように書きます。

.
.
.
    // 一覧取得
    case http.MethodGet:
        t, err := getAllTask()
        if err != nil {
            e := errors.Errorf("get error: %v", err)
            responseWrite(w, http.StatusInternalServerError, e.Error(), e)
            return
        }

        if len(t) < 1 {
            responseWrite(w, http.StatusOK, "0 tasks", nil)
            return
        }

        b, err := json.Marshal(t)
        if err != nil {
            e := errors.Errorf("json marshal error: %v", err)
            responseWrite(w, http.StatusInternalServerError, e.Error(), e)
            return
        }

        w.WriteHeader(http.StatusOK)
        w.Write(b)

    // ステータス変更
    case http.MethodPatch:
.
.
.

File > SaveAll を選択して、保存します。

変更したプログラムを公開する

コンソールでコマンドを実行して、Go のプログラムを公開しましょう。

$ gcloud functions deploy {Cloud Functions Name} \
--runtime go111 \
--entry-point TaskManager \
--trigger-http

アプリケーションを確認する

コンソールで curl コマンドを実行して、タスクを取得してみましょう。

$ curl -XGET -H "Content-Type: application/json" \
{HTTP トリガー URL} | jq .

タスクには3つのステータスがあります。

名前

意味

ToDo

これからやるタスク

1

InProgress

着手中のタスク

2

Done

完了したタスク

3

取得したタスクを確認し、ステータスを更新できるようにします。

タスクを更新するプログラムを書く

codelab > taskmanager > task.go を開きましょう。

ファイルの末尾に関数を2つ追加します。

1つ目:

func setTask(id int64, title string, status Status) *Task {
    return &Task{
        ID:        id,
        Title:     title,
        Status:    status,
        CreatedAt: time.Now(),
    }
}

2つ目:

func (t Task) update() error {
    ctx := context.Background()

    client, err := datastore.NewClient(ctx, "wwgt-codelabs")
    if err != nil {
        return err
    }

    key := datastore.IDKey(os.Getenv("MY_CODE"), t.ID, nil)

    _, err = client.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
        var gt Task

        if err := tx.Get(key, &gt); err != nil {
            return err
        }

        if t.Title == "" {
            t.Title = gt.Title
        }

        if err := t.Status.validate(); err != nil {
            t.Status = gt.Status
        }

        t.CreatedAt = gt.CreatedAt

        _, err := tx.Put(key, &t)
        return err
    })
    return err
}

タスクを更新する処理を呼び出すプログラムを書く

codelab > taskmanager > function.go を開きましょう。

下記の場所にプログラムを追加します。

タスクを更新するプログラムの呼び出しは、下記のように書きます。

.
.
.
    // ステータス変更
    case http.MethodPatch:
        param, code, err := getJSON(r.Header.Get("Content-Type"), r.Body)
        if err != nil {
            responseWrite(w, code, err.Error(), err)
            return
        }

        t := setTask(param.ID, param.Title, param.Status)
        if err := t.update(); err != nil {
            e := errors.Errorf("patch error: %v", err)
            responseWrite(w, http.StatusInternalServerError, e.Error(), e)
            return
        }
        msg := fmt.Sprintf("%v updated", t)
        responseWrite(w, http.StatusOK, msg, nil)

    // 削除
    case http.MethodDelete:
.
.
.

File > SaveAll を選択して、保存します。

変更したプログラムを公開する

コンソールでコマンドを実行して、Go のプログラムを公開しましょう。

$ gcloud functions deploy {Cloud Functions Name} \
--runtime go111 \
--entry-point TaskManager \
--trigger-http

アプリケーションを確認する

コンソールで curl コマンドを実行して、任意のタスクのステータスを更新してみましょう。

指定している ID は、エンティティの "名前 / ID" の数字になるため、各々異なります。

1つ目:

$ curl -XPATCH -H "Content-Type: application/json" \
-d '{"ID":5632499082330112, "status":3}' \
{HTTP トリガー URL}

2つ目:

$ curl -XPATCH -H "Content-Type: application/json" \
-d '{"ID":5642368648740864, "status":2}' \
{HTTP トリガー URL}

データストア > エンティティ をクリックして、status が書き換わっていることを確認しましょう。

ID を指定して削除する処理を追加しましょう。

タスクを削除するプログラムを書く

codelab > taskmanager > task.go を開きましょう。

ファイルの末尾にプログラムを追加します。

func (t Task) delete() error {
    ctx := context.Background()

    client, err := datastore.NewClient(ctx, "wwgt-codelabs")
    if err != nil {
        return err
    }

    return client.Delete(ctx, datastore.IDKey(os.Getenv("MY_CODE"), t.ID, nil))
}

タスクを削除する処理を呼び出すプログラムを書く

codelab > taskmanager > function.go を開きましょう。

下記の場所にプログラムを追加します。

タスクを削除するプログラムの呼び出しは、下記のように書きます。

.
.
.
    // 削除
    case http.MethodDelete:
        param, code, err := getJSON(r.Header.Get("Content-Type"), r.Body)
        if err != nil {
            responseWrite(w, code, err.Error(), err)
            return
        }

        t := setTask(param.ID, "", 0)
        if err := t.delete(); err != nil {
            e := errors.Errorf("delete error: %v", err)
            responseWrite(w, http.StatusInternalServerError, e.Error(), e)
            return
        }
        msg := fmt.Sprintf("%v deleted", t)
        responseWrite(w, http.StatusOK, msg, nil)

    default:
.
.
.

File > SaveAll を選択して、保存します。

変更したプログラムを公開する

コンソールでコマンドを実行して、Go のプログラムを公開しましょう。

$ gcloud functions deploy {Cloud Functions Name} \
--runtime go111 \
--entry-point TaskManager \
--trigger-http

アプリケーションを確認する

コンソールで curl コマンドを実行して、任意のタスクを削除してみましょう。

指定している ID は、エンティティの "名前 / ID" の数字になるため、各々異なります。

$ curl -XDELETE -H "Content-Type: application/json" \
-d '{"ID":5644004762845184}' \
{HTTP トリガー URL}

データストア > エンティティ をクリックして、指定したタスクが削除されていることを確認しましょう。

このコードラボでは、タスクを追加・表示・編集するプログラムを Go で書いて Google Cloud Functions に公開することで、イベント駆動型のサーバレスアプリケーション開発を体験しました。

ここで触れたことをきっかけに、他のアプリケーションと連携をしてみたり、Google Cloud Platform 内の他のサービスと連携させてみたりと、Google Cloud Functions と Go がもっと身近に、もっと楽しくなることを願っています。