memoirでPush通知の実装を行ったので、その構成について紹介
Pull Request
使用する技術
- GAE: Cloud Tasksのタスクを作成
- Cloud Tasks: Push通知のタスクを管理
- Cloud Functions: Push通知送信
- expo-notifications: Push通知送信に使用するライブラリ
Push通知はAPIと同期させる実行する必要が無かったので、以下の手順で実行させる - GAEからCloud Tasksのタスクを作成 → Cloud TasksでCloud Functionsの実行を管理 → Cloud FunctionsでPush通知を送信

実装
まず、Push通知を送信する Cloud Functions を作成
■ memoir-notification/function.go
package memoirnotification
import (
"encoding/json"
"net/http"
expo "github.com/oliveroneill/exponent-server-sdk-golang/sdk"
)
type NotificationRequest struct {
Token []string `json:"token"`
Title string `json:"title"`
Body string `json:"body"`
URLScheme string `json:"urlScheme"`
}
func SendNotification(w http.ResponseWriter, r *http.Request) {
param := NotificationRequest{}
if err := json.NewDecoder(r.Body).Decode(¶m); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
to := []expo.ExponentPushToken{}
for _, token := range param.Token {
pushToken, err := expo.NewExponentPushToken(token)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
to = append(to, pushToken)
}
client := expo.NewPushClient(nil)
_, err := client.Publish(
&expo.PushMessage{
To: to,
Body: param.Body,
Data: map[string]string{"urlScheme": param.URLScheme},
Sound: "default",
Title: param.Title,
Priority: expo.DefaultPriority,
},
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
Push通知は以下のExpo NotificationのGoのSDKを使用
実装はシンプルにNotificationのトークンとメッセージ内容とメッセージのパラメータをRequestパラメータに設定してPostするのみです。
こちらを以下のコマンドデプロイして、Cloud Functionsに関しては完了
$ gcloud functions deploy SendNotification --runtime go113 --trigger-http --region asia-northeast1
次にCloud Tasksの設定していきます。以下のコマンドでタスクのキューを作成
$ gcloud tasks queues create sendNotification
以下のコマンドで作成タスクの情報を確認
$ gcloud tasks queues describe sendNotification
以下みたいに表示されます
rateLimits: maxBurstSize: 100 maxConcurrentDispatches: 1000 maxDispatchesPerSecond: 500.0 retryConfig: maxAttempts: 100 maxBackoff: 3600s maxDoublings: 16 minBackoff: 0.100s state: RUNNING
設定はこちらから確認できます。
デフォルトだと、maxAttempts(最大試行回数): 100回、maxBackoff(リトライ間隔の最小の時間):0.1秒、maxBackoff(リトライ間隔の最大の時間):1時間、maxDoublings(最大倍数回数): 16回と、結構手厚い設定になっているので、今回のPush通知は必須の処理では無いので、以下のコマンドで設定を変更しました。
$ gcloud tasks queues update sendNotification --max-attempts 6 --min-backoff 5s --max-doublings 3
これでCloud Tasksの設定も完了です。 次はGAEからタスクを作成を実装していきます。
package task
import (
"context"
"encoding/json"
"fmt"
"os"
cloudtasks "cloud.google.com/go/cloudtasks/apiv2"
taskspb "google.golang.org/genproto/googleapis/cloud/tasks/v2"
)
type NotificationRequest struct {
Token []string `json:"token"`
Title string `json:"title"`
Body string `json:"body"`
URLScheme string `json:"urlScheme"`
}
type HTTPTaskInterface interface {
PushNotification(r NotificationRequest) (*taskspb.Task, error)
}
type HTTPTask struct {
ProjectID string
LocationID string
QueueID string
URL string
}
func NewNotificationTask() HTTPTaskInterface {
return &HTTPTask{
ProjectID: os.Getenv("GCP_PROJECT_ID"),
LocationID: os.Getenv("GCP_LOCATION_ID"),
QueueID: os.Getenv("NOTIFICATION_QUEUE_ID"),
URL: os.Getenv("NOTIFICATION_URL"),
}
}
func (t *HTTPTask) PushNotification(r NotificationRequest) (*taskspb.Task, error) {
ctx := context.Background()
client, err := cloudtasks.NewClient(ctx)
if err != nil {
return nil, fmt.Errorf("NewClient: %v", err)
}
defer client.Close()
body, err := json.Marshal(r)
if err != nil {
return nil, err
}
// Build the Task queue path.
queuePath := fmt.Sprintf("projects/%s/locations/%s/queues/%s", t.ProjectID, t.LocationID, t.QueueID)
req := &taskspb.CreateTaskRequest{
Parent: queuePath,
Task: &taskspb.Task{
MessageType: &taskspb.Task_HttpRequest{
HttpRequest: &taskspb.HttpRequest{
HttpMethod: taskspb.HttpMethod_POST,
Url: t.URL,
Body: []byte(body),
},
},
},
}
createdTask, err := client.CreateTask(ctx, req)
if err != nil {
return nil, fmt.Errorf("cloudtasks.CreateTask: %v", err)
}
return createdTask, nil
}
まず、Cloud Tasksで使用するGCPの情報を設定します。(URLには 上で作成したCloud FunctionsのURLを設定)
return &HTTPTask{
ProjectID: os.Getenv("GCP_PROJECT_ID"),
LocationID: os.Getenv("GCP_LOCATION_ID"),
QueueID: os.Getenv("NOTIFICATION_QUEUE_ID"),
URL: os.Getenv("NOTIFICATION_URL"),
}
そして、以下でタスクを作成しています。
req := &taskspb.CreateTaskRequest{
Parent: queuePath,
Task: &taskspb.Task{
MessageType: &taskspb.Task_HttpRequest{
HttpRequest: &taskspb.HttpRequest{
HttpMethod: taskspb.HttpMethod_POST,
Url: t.URL,
Body: []byte(body),
},
},
},
}
createdTask, err := client.CreateTask(ctx, req)
if err != nil {
return nil, fmt.Errorf("cloudtasks.CreateTask: %v", err)
}
これにより、タスクが作成されPush通知が送信されるようになります。 以下、動作確認の動画です