これはなに?
先日、Google Cloud FunctionsにRubyのサポートが入ったので開発者体験を見ながら検証してみました。なお、私は普段AWS Lambdaを使っておりCloud Functions自体に慣れていないので何かお気づきがあればお知らせください。
Functionの実装
Function Frameworkというgemを使って実装をします。HTTPのハンドラーの他に各種イベントハンドラーやテストを便利にできるモジュールが揃っています。AWSでもこういうツールがほしかったですね。SAMとかServerless Frameworkもありますが、Rubyから利用できてデプロイツールから独立しているところが好印象です。AWSさん、お願いできませんかね〜
さっそくgemも入れて最小限の実装をしてみましょう。
app.rb
とGemfile
が必要だそうです。あと、Lambdaで必要なvendor/bundle
は入れてもいいですが、デプロイ時にインストールされるので今回はなしにします。
# frozen_string_literal: true
require 'functions_framework'
# 一度初期化して何回も呼び出しをしたくないものはon_startupのブロック内に記述する
# https://googlecloudplatform.github.io/functions-framework-ruby/v0.7.1/file.writing-functions.html#startup-tasks
FunctionsFramework.on_startup do
require 'json'
require 'rack'
end
# ここのhandlerはCloud Functionのentrypoint名になる
FunctionsFramework.http 'handler' do |request|
# requestはRack::Requestのインスタンス
body_hash = JSON.parse(request.body.read, symbolize_names: true)
raise StandardError, 'hoge not supported' if body_hash.key? :hoge
{ body: body_hash }
# 何もしなくても500エラーが返るが、bodyをカスタマイズする場合はRack::Responseのインスタンスを返す
rescue StandardError => e
Rack::Response.new(
JSON.generate({ error: e }), 500, { 'Content-Type' => 'application/json' }
)
end
source "https://rubygems.org"
gem "functions_framework", "~> 0.7"
gem "rack"
# 現仕様ではdevelopment, testグループのgemはインストールされないそう
# https://sue445.hatenablog.com/entry/2021/01/24/000623
group :development, :test do
gem "rspec"
end
テスト
テスト、書きますよね?
今回はRSpecで書きましたがminitestも対応されています。Function Frameworkさまざまですね。Rubyコードではなく実際のリクエストとレスポンスが使えるのも嬉しいところです。
RSpecのボイラープレートは長いので割愛します。
require 'rspec'
require 'functions_framework/testing'
describe 'CloudFunction handler' do
include FunctionsFramework::Testing
subject(:response) do
load_temporary 'app.rb' do # => spec/../app.rbというパスに解釈される
request = make_post_request('https://example.com/foo', request_body, ['Content-Type: application/json'])
call_http('handler', request)
end
end
context '正常系' do
let(:request_body) { { piyo: true }.to_json }
let(:expected_response_body) { { 'body' => { 'piyo' => true } } }
let(:response_body) { JSON.parse(response.body[0]) }
let(:response_status) { response.status }
it '成功する' do
expect(response_body).to eq expected_response_body
expect(response_status).to eq 200
end
end
context '異常系' do
let(:request_body) { { hoge: true }.to_json }
let(:expected_response_body) { { 'error' => 'hoge not supported' } }
let(:response_body) { JSON.parse(response.body[0]) }
let(:response_status) { response.status }
it '失敗する' do
expect(response_body).to eq expected_response_body
expect(response_status).to eq 500
end
end
end
ローカルで実行する
テストが通ったところで必要ないかもしれませんが、SAMたちと違ってDockerでなくてもできるということで試してみましょう。
$ bundle exec functions-framework-ruby --target=handler --port 8888
I, [2021-01-29T00:40:57.630286 #41870] INFO -- : FunctionsFramework v0.7.0
I, [2021-01-29T00:40:57.630348 #41870] INFO -- : FunctionsFramework: Loading functions from "foo.rb"...
I, [2021-01-29T00:40:57.631502 #41870] INFO -- : FunctionsFramework: Looking for function name "handler"...
I, [2021-01-29T00:40:57.631530 #41870] INFO -- : FunctionsFramework: Starting server...
I, [2021-01-29T00:40:57.763187 #41870] INFO -- : FunctionsFramework: Serving function "handler" on port 8888...
# 別ターミナルにて
$ curl localhost:8888 -d '{ "foo": "bar" }'
{"body":{"foo":"bar"}}%
I, [2021-01-29T00:41:19.113264 #41870] INFO -- : FunctionsFramework: Handling HTTP POST
I, [2021-01-29T00:41:32.663198 #41870] INFO -- : FunctionsFramework: Caught SIGINT; shutting down server...
デプロイする
一旦コンソールからやります。ちゃんとやるときはGCSにzipを上げる運用でもよさそうです。
アップロード手順は割愛しますが、注意するところは以下のとおりです。
- entrypointが入っているファイルの名前が
app.rb
であること - entrypoint (本記事では
handler
)を指定すること - private gemをインストールする場合は、当然ながら
BUNDLE_GITHUB__COM
を環境変数に設定するなど認証を通すこと。ドキュメントではローカルに配置するやりかたも提示されています
Lambdaと同様にzipに入れてアップロードしましょう。デプロイが終わるまで2分くらいかかって体感的に遅いです。今のところ、デプロイのスピードに関してはLambdaが完全に上ですね。
zip -x '*.git*' -r function.zip app.rb Gemfile Gemfile.lock
デプロイされたFunctionを試す
アップロードしたらさっそくリクエストを投げてみましょう。Triggerタブに呼び出し用リンクが記載されています。Lambdaと同様に、コンソールからテスト実行がしたり、cliから直接実行したりすることもできます。標準的ですね。
さいごに
これでひと通り試すことができました。Function Framework、褒めても褒めたりないですね!
さっそく本番運用を始めたいところですが、まだBetaでした。
Ruby使いとしてプラットフォームが1つ増えて嬉しい限りです。
みなさまも快適なRubyライフを!