- 村田靖拓
- 約 4,400 文字
- 500 View
目次
はじめに
こんにちは、TIG所属の村田です。
サーバレス連載企画の第5回はCloudEventsを取り上げたいと思います。
※本記事は2020.03.31時点の情報を元に執筆しています。
CloudEventsとは?
CloudEventsは様々存在するイベントを統一的に扱いたいとの思いから登場した統一仕様です。
2019.10.24にCNCFのIncubatorプロジェクトに昇格したらしく、同時にv1.0のSpecificationがリリースされています。
私のチームで開催している勉強会でも取り上げたことがあり、以下はその時の資料です。
https://speakerdeck.com/mura123yasu/cloudevents
今回は実際に公開されているSDKを使って一連のイベントデータのやり取りを実現してみようと思うのですが、SDKはいくつかの言語で公開されています。
CloudEvents provides SDKs for Go, JavaScript, Java, C#, Ruby, and Python
GitHubを見る限りではGoのSDKが一番開発進んでいるのかなと思ったのと個人的にGo書きたい思いが強いので、今回はGoのSDKを使っていきます。
環境とターゲット
| 項目 | バージョン等 |
|---|---|
| OS | macOS Mojave 10.14.6 |
| go | 1.14 |
| Google Cloud SDK | 286.0.0 |
| CloudEvents sdk-go | v1.1.2 |
今回は『CloudEvents仕様のメッセージをローカル端末からCloud Pub/Sub経由でCloud Functionsに渡し、個々の値を取り出しプログラムで扱える形にすること』を目的とします。
以下のような形を目指します。
SDKバージョンについての補足
現在v2は work in progress とのことで、今回は雰囲気を掴むためにLatest Releaseの v1.1.2 のソースコードをいじってみます。
※v1のREADMEには 2020.03.27 を目処にv2リリースを目指すとの記載がありますが、いまも絶賛開発中と思われます。
We will target ~2 months of development to release v2 of this SDK with an end date of March 27, 2020.
https://github.com/cloudevents/sdk-go/blob/master/README_v1.md
やってみる
今回書いたソースコードはすべてGitHubにあげていますので必要に応じて参照して頂ければと思います。
- https://github.com/mura123yasu/cloudevents-go-helloworld
- https://github.com/mura123yasu/cloudevents-cloudpubsub-receiver
まずは、シンプルにローカルで繋げる
まずはローカル端末内で完結する形で実装します。
公式のリポジトリにしっかりサンプル実装があるので、それを参考にしつつ進めることができました。
まずはReceiver側の実装です。
package main |
実処理は Receive にて行われていますが、今回は受け取ったイベントを標準出力するのみです。
次にSender側の実装です。
package main |
event.SetData がいわゆるpayloadにあたるデータを詰め込んでいる箇所です。
Receiverを起動してSenderからメッセージを投げてみると…
go run main.go |
ちゃんと届きました! 簡単ですね。
次に、Cloud Pub/Sub経由の形に変えてみる
実際にはイベントデータの受け渡しはキューを経由するなどして非同期なやりとりになるかと思います。
というわけで、私が普段GCPを利用しているということもありGCPのCloud Pub/Subを経由する形で実装したいと思います。
まずはReceiverの実装です。
package main |
GCPサービスへアクセスする都合上環境変数からの値の取得やその取り回しがありますが、大枠は先程の実装と同じです。
先程はクライアント作成時に NewDefaultClient を呼んでいましたが、今回はPub/Subを利用するため専用のtransportを作成してそれを引数に渡す形でクライアントを New(transport) しています。
ちょうど該当するソースコードは以下になります。
t, err := cepubsub.New(context.Background(), |
次にSender側の実装です。
package main |
こちらも大枠は先程のパターンと変わりませんが、今回ひとつ先程のパターンと異なるのは、やりとりするデータの形式について明示的にstructを定義している点です。(念のため補足ですが、Pub/Subを利用するからそうしているというわけではありません。あくまで変化点という意味です。)
各々ファイルにそれぞれ Model を定義してしまっていますが、別ファイルで定義してReceiverとSenderの両方からimportする形で実装するのが望ましい形かなと思います。
では動かしてみましょう。
まずはCloud Pub/SubのTopicおよびSubscriptionを作成します。
gcloud pubsub topics create <YOUR PUBSUB TOPIC> |
次に、アプリケーションの実行に必要な環境変数を設定します。
# required |
アプリケーションの認証情報については詳しくはこちらを参照してください。
準備が整ったので動かします!
go run main.go |
期待通りにメッセージを受け取ることができました。
GCPコンソールからもメッセージがしっかりPub/Subに届いていたことが確認できます。
成功です。
最後に、Cloud Pub/Subから先をCloud Functionsに切り替える
さて、Pub/SubキューはCloud Functionsに渡してあげたいと思うのは私だけでしょうか?(求ム、同志)。
というわけでラストは先程Cloud Pub/Subに到達したメッセージをCloud Functionsで受け取りたいと思います。
先程までのReceiverをCluod Functions仕様に書き換えてあげます。また、さっきまでは受け取ったメッセージの中に含まれるpayloadをプログラム上で扱える形にまでparseしきってなかったのでそこも一緒にやりたいと思います。
ということで実装がこちら。
package receiver |
Cloud Pub/Subの Message 型で受け取った電文を、CloudEventsの Message 型に変換し、データの中身を Model 型へ変換しています。これにより Sequence および Message という個々の値を扱える状態にできました。
CloudEventsのメッセージの取り扱いについての考察
ReceiverとSenderで同一の Model を準備してデータをやりとりし合うこと自体は一般的な実装ですが、今回ひとつミソになるのは pubsub.Message ⇔ cepubsub.Message の変換だと思います。
※Cloud Pub/SubのMessageを pubsub.Message 、CloudEventsのMessageを cepubsub.Message と表現しています。(以下、同様)
CloudEventsのSDK上ではCloud Pub/Subとやりとりする際の Message を以下のように定義しています。
type Message struct { |
https://github.com/cloudevents/sdk-go/blob/v1.1.2/pkg/cloudevents/transport/pubsub/message.go
つまり、CloudEventsの定義する Event データがCloud Pub/Subを通過する際には pubsub.Message.Data と pubsub.Message.Attributes に情報が集約されます。
実際にPub/Subから受け取った電文をそのまま標準出力したものを見てみると、以下のような形になっていました。
&{ |
pubsub.Message.Attributes を標準出力したログでは以下のようにMap情報が確認できました。
[INFO] message.Attributes: map[ce-datacontenttype:application/json ce-id:89142958-bdb1-4fc9-979a-5c45f6590207 ce-source:github.com/cloudevents/sdk-go/cmd/samples/pubsub/sender/ ce-specversion:1.0 ce-time:2020-03-30T16:25:40.473793Z ce-traceparent:00-a31200e4a4b2a5a0d41b5710b5f350bb-a348823d4ab87c62-00 ce-type:com.cloudevents.sample.sent] |
また、 pubsub.Message.Data を標準出力したログでは以下のようにデータの中身が確認できました。
[INFO] message.Data: {"id":0,"message":"HELLO"} |
私は今回Cloud Pub/Subを利用しましたが、同様な形でCloudEventsが各イベント型の情報とのIFを定義してくれて、実装者はCloudEventsとのIFだけを気にすれば良くなっていくんだろうなと思います。
現在v2は絶賛開発中ですが、たとえば pubsub.Message ⇔ cepubsub.Message の変換をやってくれるutility的なものがSDKの中に登場するとすごく便利だろうなと思いました。
また、もう一点気になったポイントは以下のように実装されている CloudEventsVersion の存在です。
func (m Message) CloudEventsVersion() string { |
https://github.com/cloudevents/sdk-go/blob/v1.1.2/pkg/cloudevents/transport/pubsub/message.go
これは cepubsub.Message の Attributes の中から specversion を取り出しており、ちょうど以下の ce-specversion:1.0 にあたる情報を取り出していることになります。
map[ |
v1.1.2のSDKでは specversion のみの実装ですが、同様の形で Attributes からいわゆるメタデータを取り出して処理を行うかあるいは後続にイベントを伝播させるために再度なにかしらのオブジェクトに詰めるかといったことを行うことになるかなと思います。
ということで動かしてみる
ちょこっと考察を挟みましたが、肝心のプログラム実行がまだでした。
まずはReceiver関数をCloud Functionsにデプロイします。
gcloud functions deploy <YOUR FUNCTION NAME> --project <YOUR GCP PROJECT> \ |
Senderは同じものを利用するだけなので新しい準備は不要です。
というわけで実行してみると…
Cloud Functionsのログにてメッセージが届いていることが確認できました!
無事に『CloudEvents仕様のメッセージをローカル端末からCloud Pub/Sub経由でCloud Functionsに渡し、個々の値を取り出しプログラムで扱える形にすること』という目的を達成できました。
さいごに
今回は主にCloud Pub/Subにフォーカスする形でCloudEventsの実装について紹介させて頂きました。SDKは絶賛開発中なステータスですが、世に蔓延る様々なイベント形式に悩まされる実装者が幸せになれる未来が待っていると思うと非常に楽しみですし、CloudEventsの動向からますます目が離せませんね。
サーバレス連載の5本目でした。次はAWSのStep FunctionsとLambdaでServelessなBatch処理を実現するです。