DIGGLE開発者ブログ

予実管理クラウドDIGGLEのプロダクトチームが、技術や製品開発について発信します

RubyKaigi 2023 の雰囲気をお届けします #rubykaigi

こんにちは、DIGGLE エンジニアの ito と miyakawa です。

前回のブログ記事でお伝えした通り、DIGGLE は RubyKaigi 2023 スポンサーになりました。

diggle.engineer

今回の記事では RubyKaigi で初現地参加の様子を通して、会場の雰囲気をお届けします。

前日の準備

スポンサーブースの設営は前日から可能だったため、5月10日 15:00 ごろに松本駅に到着しました。

松本駅では RubyKaigi2023 の垂れ幕が下がっており、駅に降り立った段階からイベントの熱気を感じることができました。

会場は松本市民芸術館で、松本駅からは徒歩で15分ほどの場所にあります。

会場内は松本をイメージした意匠が施されていたため、 それらの意匠の邪魔にならないようにブースの配置をしました。

入場・受付

午前に開催される Matz さんの Keynote に参加するため、9:40 ごろに入場しました。

入場の列はそれほど長くなく、スポンサーでの入場ということも相まってすんなり入場することができました。

また、受付のためのチケットは事前に iPhone のウォレットに登録してあったため、入場はそちらの提示ですみました。

会場

会場に入って受付に向かうまでの階段です。

受付周辺の様子です。
たくさんの人で賑わっています。
ノベルティ配布に並ぶ列がありそこに並んだのですが、受付の列より断然長かったです^^;

こちらはセッション会場の様子です。

ブースエリアの様子。セッションの合間は混雑しますが、セッション中は比較的空いています。

Keynote セッション

1日目のセッションは Matz さんの Keynote から始まりました。
Ruby 30年の歴史を振り返るという内容で、Ruby 歴の浅い自分たちにとってはどのエピソードも興味深いものでした。

DIGGLE ブースの様子

こちらは Keynote セッション後、ブースの準備が終わって気合万全!の図。

DIGGLE のブースでは、ruby.wasm を使って作成した弾幕シューティングゲームをプレイできます。
このゲームの制作過程については前回のブログで紹介しています。

diggle.engineer

かなりの鬼難易度でゲームオーバーが続出する中、ついに1名クリアした方が現れました^^

他社様のブースの様子

ブース対応の間に他の企業様のブースにいくつかお邪魔させていただきました。

こちらは食べチョクさんのブースで、長野県名産のリンゴのノベルティ(?)をいただきました。 写真左の方は生産者の方とのことでリアルに産直を感じれた素敵なブースでした。

こちらはマネーフォワードさんのブースで、仕事に大切にしていることアンケートをされていました。みなさんはどれを選びますか??
他にもコードレビュー募集コーナーなど面白い企画をされていて、ブース展示側としてとても勉強になりました。

ご飯

お昼ご飯は会場に用意された山賊焼弁当をいただきました。
左手に写っているのは cookpad さんが配られていたリンゴジュースです(甘くて美味しかったです^^)。

ちなみに前日の夜はホテルの食堂でご飯を食べたのですが、松本のお野菜がどれも美味しく、特にトマトが絶品でした。
みなさん、機会があれば松本のトマト食べてみてください。

おわりに

以上、RubyKaigi 2023 の雰囲気をお届けしました。

明日以降来場される皆様のお役に立てば幸いです。

We're hiring!

私たちと一緒に開発してくれるメンバーを募集しています。少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

herp.careers

【RubyKaigi 2023】スポンサーに当選したので弾幕シューティングゲームを作ってみた

こんにちは。DIGGLEエンジニアリングマネージャーのzakkyです。発売以来スプラトゥーン3のフェスに全敗しているので「zakkyと逆張りすれば勝てる説」が立証されつつあります。

はじめに

早速本題です。

弊社では、今年RubyKaigi 2023Platinum Sponsorsに初めて応募しまして、そして無事に当選することができました。

弊社以外のRubyKaigi 2023スポンサーには、本当に有名な企業が並んでおりまして、そんな有名企業に挟まれる初参加企業ということで、「何かかまさなきゃ! 何か目をひくものを作らなくちゃ!」という事で、弾幕シューティングゲームを作りましたのでご紹介させていただきます。

ちなみに、ruby.wasmに関する調査~実装完了までを週末の空き時間(10hくらい)で済ませましたので、結構ハードルは低いのではないかと思います。

なんで弾幕シューティング?

RubyKaigiへの出展ということで、「Rubyに関する出し物を作ろう!」ということで、色々とメンバーと考えていたのですが、その中で、Ruby 3.2からWASIベースのWebAssemblyサポート(以降ruby.wasm)が行われたので、ruby.wasmを使って何か作れないか。という話が出ました。

作成物の候補としては・・・

  • ゲーム
    • シンプルに目をひきそう
  • アニメーション動画
    • CMっぽいものをアニメーションとして作る

上記が挙がったのですが、「実際触れるものが良いだろう」ということでゲームを作ることになりました。

・・・で、弾幕シューティングに決めた理由ですが、「ノリで実装してたらいつのまにか完成してた ruby.wasmで実装した場合、実際どれくらいのパフォーマンスで動くのかを視覚的に見やすく・面白く表現する方法として考えました」となります。

ruby.wasmってなに?

「そもそもruby.wasmって何?」という方に向けた説明なのですが、一言で言うと、「ブラウザ上でRubyを動かす仕組み」となります。

今までJavaScriptで書いていた部分をRubyを使って書けるようになったので、Railsでerbやhamlを使って開発しつつ、インタラクションなどのDOM操作周りをruby.wasmで実装すれば、「JavaScriptを一切使わずに一気通貫な実装が可能になった」とも言えます。

どうやって実装するの?

ruby.wasmで検索すると色々と出てきますので、詳しくはそちらを参考にしていただくとして、ruby-head-wasm-wasiを読込むだけでOKです。

 <script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/browser.script.iife.js"></script>

rbファイルとして切り出し

そこそこ大きいコードを書こうとすると、ファイルの分割が必要になってきますが、JavaScriptファイルの読込みのように、scriptタグで書いてあげればOKです。(type="text/ruby"という書き方、新鮮ですね!

<script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/browser.script.iife.js"></script>
<script type="text/ruby" src="ruby/game/point.rb"></script>
<script type="text/ruby" src="ruby/game/box.rb"></script>

rbファイルの読込みは上記で完了しているので当たり前ではありますが、ファイル間(=クラス間)のアクセスに関して、requireなどの記述は不要となります。

※ただし、コードの読込むrbファイルの順番には気を付ける必要があります。

class Box < Point # requireをすることなくBoxクラスからPointクラスを参照できる
  class Area
    attr_reader :top, :right, :bottom, :left
    def initialize(x, y, size_x, size_y)
      @left = x
      @top = y
      @right = x + size_x
      @bottom = y + size_y
    end

    # NOTE: シンプルに現在位置同士で衝突を判定し、移動経路上での衝突は見ない
    def hit?(area)
      left <= area.right && right >= area.left && top <= area.bottom && bottom >= area.top
    end
  end

  attr_writer :size
  attr_reader :size
  def initialize(x, y, size_x, size_y)
    super(x, y)
    @size = Point.new(size_x, size_y)
  end

  (省略)
end

初期セットアップ周りの覚書

ローカルで検証をする際に何も考えずにrbファイルを切り出すとCORSエラーになる

ruby.wasmに限らず遭遇する問題ですが、ローカルファイルをそのまま開くとrbファイルがCORSエラーとなって読込めない問題が発生しました。

何かしらの方法で http://localhost/でアクセスできるようにさえしてあげれば良いので、今回はnpmのreloadを使ってローカルでの動作検証を行いました。(他にもgemのhttpdやnpmのhttp-serverなども試してみましたが、どこかでキャッシュするらしく、うまく動かないので不採用にしました)

修正したrbファイルが更新されない

上記のreloadを使っても、まだキャッシュされてしまうようで、rbファイルを修正&ブラウザをリロードしても上手く更新されない問題がありました。 結論としては、reloadを使用したうえで、更にmetaタグの指定が必要でした。

  <head>
    <title>DIGGLE rubykaigi 2023</title>
    <link rel="icon" href="favicon.ico" />
    <meta charset="utf-8" />
    <meta http-equiv="Pragma" content="no-cache" /> <!-- ここの記述が必要 -->
    <meta http-equiv="Cache-Control" content="no-cache" /> <!-- ここの記述が必要 -->
  </head>

実装まわりの覚書

JavaScriptに絡むパラメータや関数の呼び出し方法

Rubyには、他言語と違いメソッド呼び出しの末尾の()が不要となる仕様があります。そのため、パラメータを参照したい場合、[:parameter]として呼び出す必要があります。

    @canvas = JS.global[:document].querySelector('#canvas')
    @context = canvas.getContext("2d")

    body = JS.global[:document].querySelector('body')
    @display_size = Point.new(body[:clientWidth].to_i, body[:clientHeight].to_i)

上記ですと、documentはパラメータなので、[:document]として取得し、querySelectorは関数なので、JS.global[:document].querySelector('#canvas')という記述になります。ここは、普段JavaScriptで書くお作法とは結構違うので最初は戸惑いました。

型変換が面倒

JavaScriptから取得したオブジェクトは、そのまま文字列や数字として使用することができません。

例えば、下記のようなコードを書くとエラーが発生します。

button[:textContent] = button[:textContent] + "_suffix"

型変換をしない場合にはエラーが発生

そのため、下記のようにto_sやto_iなどで変換する必要があります。

button[:textContent] = button[:textContent].to_s + "_suffix"

同じく、JavaScript側へ想定外の型で渡そうとするとエラーになります。

button[:width] = 100.0

想定外の型でJavaScriptへ渡すとエラー

このエラーに関してはエラー内容からは原因が類推できないため、かなり悩まされました。今回のゲームの特性上、floatで弾の移動ベクトルを持たせていたのですが、どこか1か所でもto_iを書き漏れていると↑のエラーが発生してしまい、泣きそうになりました。

また、nullやundefinedがnilとは違う扱いになっており、ruby.wasmから別途定義&提供されているJS::NullやJS::Undefinedと比較する必要がある点も注意が必要です。*1*2

if tbody[:firstChild]) == JS::Null
  puts 'null'
end

ActiveSupportが使えない

当たり前ではありますが、ActiveSupportは使えません。普段Railsを使っていてActiveSupportのことを意識せず恩恵を受けている私は「delegate_missing_toってActiveSupportに入ってたのか~」などなど、改めてActiveSupportのありがたさを嚙み締めながら実装することになりました。

実際のゲーム画面

ということで、スプラトゥーンで負けが込んで気分転換をしているうちにこんな感じのゲームになりました。実際に触りたい方は是非ブースまでお越しください!

実際のゲーム画面

まとめ

ruby.wasmを使ってみましたが、結構簡単に実装できるので、Railsを使ってお手軽に小さくサービスを作る際には重宝するかもしれません。ただ、性能はそこまで良くないので、注意が必要となります。(画面左上にFPSを出しているのですが、画面に表示している弾数が多くなるとFPSがガクッと下がります)

宣伝

最後に宣伝なのですが、RubyKaigi 2023ではスポンサーブースを出しておりますので、ご来場予定の方がいらっしゃいましたら、是非お立ち寄りください。(もちろん私も参加します)

ブースでは、今回ご紹介する弾幕シューティングゲームのURL配布や、撃破タイムのランキング掲示などを行おうと思っておりますので、RubyKaigi 2023に参加する予定の方は、一度お立ち寄りいただけますと幸いです。

追記

ソースコードの公開はRubyKaigi終了後を予定しておりましたが、想像以上に希望される方がいらっしゃいましたので、当初予定よりかなり早いですが公開します。

github.com

We're hiring!

こんな私と一緒に開発してくれるメンバーを募集しています。少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

herp.careers

【Ruby 3.2 新機能】るりまの Data クラスリファレンス作成 PR をレビューした

こんにちは。にくといいます。github だと niku です。2022 年 12 月に DIGGLE に入社しました。

さて Ruby 3.2 からは Data というクラスが使えるようになりました。 Ruby の日本語リファレンスるりま*1(以下「るりま」)にも class Data (Ruby 3.2 リファレンスマニュアル) というページができていますね。すごい!

「るりま」は GitHub の Pull Request を用いてメンテナンスされています。今回 Data のリファレンスも

github.com

で作られた PR が元になっています。 この PR のレビュアーとして私も参加したので、その時の様子も交えて「るりま」のドキュメントのメンテナンスの雰囲気をお伝えしたいと思います。

Data クラス

この記事を楽しむために、まずは Data クラスというのがどういうものなのかを大まかに説明します。 Data クラスは、Strcut クラスに似たインターフェースを持っています。

~ ()
irb
irb(main):001:0> SMeasure = Struct.new(:amount, :unit)
=> SMeasure
irb(main):002:0> DMeasure = Data.define(:amount, :unit)
=> DMeasure
irb(main):003:0> s_one_km = SMeasure.new(1, :km)
=> #<struct SMeasure amount=1, unit=:km>
irb(main):004:0> d_one_km = DMeasure.new(1, :km)
=> #<data DMeasure amount=1, unit=:km>

一方で Struct クラスは値を書き換えることができるのに対し、Data クラスは値を書き換えることができません。 Struct クラスは構造体クラスと呼ばれる概念を表すものに対し、Data クラスは値オブジェクトと呼ばれる概念を表すもので、値オブジェクトというものの性質の一つに「変わらない(不変)」があるためです。

irb(main):005:0> s_one_km.amount = 2
=> 2
irb(main):006:0> s_one_km
=> #<struct SMeasure amount=2, unit=:km>
irb(main):007:0> d_one_km.amount = 2
(irb):7:in `<main>': undefined method `amount=' for #<data DMeasure amount=1, unit=:km> (NoMethodError)
  from /Users/niku/.asdf/installs/ruby/3.2.1/lib/ruby/gems/3.2.0/gems/irb-1.6.2/exe/irb:11:in `<top (required)>'
    from /Users/niku/.asdf/installs/ruby/3.2.1/bin/irb:25:in `load'
  from /Users/niku/.asdf/installs/ruby/3.2.1/bin/irb:25:in `<main>'

ちょっと横道にそれますが「プログラムでできないことが増える」ことを導入するのに、それが嬉しい理由というのはあるんでしょうかね?私は以下の記事で覚えたことを大事にしていて「できないことが増える」のは便利なことだと感じています。

上が力が強く、下へ行くほど力が弱くなります。力が強いと何でもできてしまうので、コードの意図が不明瞭となり、また間違いが入り込みやすくなります。力が弱いとできることは限られるのでコードの意図は明確となり、間違いが入りにくくなります。 「目的に合う一番力の弱い手段を使う」のがよいプログラムを書くための大原則です。たとえば、草を刈るのにチェーンソーは使うべきではありません。もちろん草も切れますが、大切な植木も傷つけてしまうかもしれませんから。

Haskellの文法(再帰編) - あどけない話

「るりま」 Data クラス PR との出会い

私は Ruby 3.0 から実験的に導入された並列処理機能 Ractor https://techlife.cookpad.com/entry/2020/12/26/131858 に興味を持っていて、Ractor 間のデータのやりとりには値の変わらないオブジェクトがあると便利であることを、Erlang や Elixir といった他のプログラミング言語を通じて知っていました。Data 導入の issue https://bugs.ruby-lang.org/issues/16122 か PR https://github.com/ruby/ruby/pull/6353 をみてから、Ruby に導入できるのを心待ちにしていました。

Ruby の英語リファレンス である ruby-doc には Ruby 3.2 リリース (2022/12/25) 時点から Data クラスのリファレンス https://ruby-doc.org/3.2.0/Data.html がありました。一方で、日本語のリファレンスはその時点では存在していませんでした。kakutani さんが Ruby3.2.0 の新機能のほうの Data のリファレンスマニュアルがあってほしいという issue を rurema に立てた https://github.com/rurema/doctree/issues/2764 のをみて、私は「るりま」でリファレンスを書いたことがなかったので、そのうちやってみようと思っていました。

2023/1/7 に時間がとれたので、Data クラスのリファレンスを作ろうとしました。念のため「るりま」の PR 一覧を眺めてみると、なんと前日の 2023/1/6 に Data クラスを「るりま」に載せるための PR が kyanagi さんによって作られていました。そこで PR を書くのではなく、PR のレビューで貢献することとしました。

そのときの私の喜びのコメントです

欲しいなあと思っていたので PR があって嬉しく思いました✨ぜひマージされてほしい。 通りすがりですが、助けになればと思い一通りの変更をみました。コメントを残させてください。

https://github.com/rurema/doctree/pull/2777#pullrequestreview-1239634811

PR レビューの様子

結局、他の方のレビューも含めて、PR が作られたのは 2023/1/7 マージされたのは 2023/2/9 と Data クラスの記事作成には約一ヶ月かかったようです。私がレビューしたのは 2 回くらい、軽微なものを含めると数回といったところで、PR の出だしから完成度が高かったので、やりとりの回数はそれほど多くなく完成となりました。

「構造体」という言葉は Struct とは強く結びついていますが、Data とはそうでもなさそうに感じます。 別の言い回しだとさらに読者が理解しやすかったりしないでしょうか。

https://github.com/rurema/doctree/pull/2777#discussion_r1063972590

API は Struct に似ているので、Struct のリファレンスを元にしてくださったのかなと想像しています。Struct には構造体という言葉が似合うのですが、私は Data のリファレンスには似合わないと思ったので、コメントで問い掛けていました。

この後に記載されているサンプルは全てエラーになっているようです。また私の手元にある Ruby3.2 で試しても同じ結果になりました

https://github.com/rurema/doctree/pull/2777#discussion_r1063974100

例示されているサンプルを実行してみても、エラーになるので、どういうことかよくわからず相談したコメントです。結果としてこのふるまいは正しく、狙いのある API なのでした。どういうことなのかは完成系の https://docs.ruby-lang.org/ja/3.2/class/Data.html#S_--5B--5D 「new に渡す引数の数がメンバの数より多い場合は new でエラーになります。new に渡す引数の数がメンバの数より少ない場合は new ではエラーにならず、そのまま initialize に渡されます。ユーザが initialize のオーバーライドを通して、少ない引数のときの適切な振舞いを実装可能とするためです。」のあたりをご覧ください。例も含めて kyanagi さんがたいへん伝わりやすくしてくれました。

https://docs.ruby-lang.org/en/3.2/Data.html#method-c-define だと、るりまの定義とは異なり(略)こちらは単に記法の違いによるものになるでしょうか?

https://docs.ruby-lang.org/en/3.2/Data.html#method-c-define にある、 引数を取らずに Data.define() と定義して、パターンマッチングに使う便利なやり方はるりまにも記載があると嬉しそうです。

https://github.com/rurema/doctree/pull/2777#discussion_r1063975629

先行して実装されていた ruby-doc の Data と照らしあわせながらコメントしていました。この質問の回答で知ったのですが kyanagi さんは私が参照していた ruby-doc のリファレンス訂正 PR https://github.com/ruby/ruby/pull/7038 も作られていました。素敵です。

細かいですが、Object#hash の定義をみると、hash が必ず満たさなければいけないのは「メンバの値を eql? で比較して同じなら、hash の値も同じでなければならない」ということで、「メンバの値を eql? で比較して異なるときに hash の値が異ならなければならない」ではなさそうに思います。

https://github.com/rurema/doctree/pull/2777#discussion_r1063978482

Object#hashObject#eql? は異なるメソッドですけれどいい感じに協調するように実装しないと望ましい動作が得られない性質を持っているので、リファレンスの表現も(書くなら)気をつかいますよね。これは Ruby 特有ではなくて Java などでもそういうものですよね。結局この表現は省くことになりました。実装詳細に踏み込まないのは穏当な判断かなと感じました。 参考までに Java の場合のドキュメントも貼っておきます

常に、このオブジェクトに対するequalsの比較で使用される情報が変更されていなければ、hashCodeメソッドは常に同じ整数を返す必要があります

https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/lang/Object.html#hashCode()

「るりま」 PR レビューの感想

現状「るりま」に深くかかわっているわけではない第三者の私がふらっときてレビューさせてもらっても特に困ることはありませんでした。「るりま」をメンテされているみなさん、kyanagi さん、親切にしてくださってありがとうございます。

私はレビューするときに強く確信を持っている場合をのぞくと、だいたい「私には状況がaのように見えていて、その場合私ならxと書くところyと書かれていて違いを感じました。ここの意図を詳しく教えていただけますか?」といった問い掛け形式でレビューしていることを再発見しました。PR の書き手が見ている状況と読み手が認知している状況が異なることはよくあるので、認知している状況を自ら開示するのは、すれ違いによってやりとりがややこしくなるのを防ぐために重要ですね。

DIGGLE は RubyKaigi2023 スポンサーになりました

DIGGLE は今年開催される RubyKaigi2023 の Platinum スポンサー になりました。よろしくおねがいします! RubyKaigi 現地でブースも出展し、私も居ますので、遊びにいらしてください。 この記事のことや、Ractor や、よいレビュー、その他について雑談しましょう〜

*1:たぶん びー ふぁれんす にゅあるの略

DBとDWHの技術選定調査について

こんにちは。DIGGLEのエンジニアのchikugoです。

はじめに

弊社プロダクトのDIGGLEの成長に伴い、先々も見据えてパフォーマンス改善を行う必要性が生じました。そのパフォーマンス改善の一貫で、DBやDWHの技術選定を行っています。今回はDBやDWHの技術選定の際に行った、一次調査についてお話できればと思います。

パフォーマンス改善の要件としては特定処理で数倍程度の速度改善が必要で、参照だけでなく追加など更新系の速度改善も求められます。 既存DBがAurora PosgreSQLになりますので、それに比べての改善効果や改善・移行コストが主な選定基準となります。

DB選定

Azure Cosmos DB for PostgreSQL vs AlloyDB で比較を行います。

Azure Cosmos DB for PostgreSQL

citusdataをMicrosoftが買収後、Azure Cosmosとしてリリースされました。マルチテナント型のSaasに最適と謳われています。弊社DIGGLEもマルチテナント型のSaasなのでプロダクト形態としてはマッチします。

citusdataの利用実績としてはSmartHRさんの事例をご存知の方もいるかもしれません。
つらくないマルチテナンシーを求めて: 全て見せます! SmartHR データベース移行プロジェクトの裏側 / builderscon 2018 - Speaker Deck

citusdataの方ではクエリを20倍から300倍 (またはそれ以上) 高速化可能と謳われており、この性能アピールは魅力的です。

Azure Cosmos DB for PostgreSQL のアーキテクチャの概要としては以下のような概念図となります。

概念図

  • コーディネーターノード
    • 分散テーブルのメタデータを格納し、分散計画を担当します
  • ワーカーノード
    • 実際のデータが格納されます
    • 分散テーブルが複数のワーカーノードに分散されて格納されます
    • ノード数を指定してスケーリング可能です

こちらは分散テーブルという概念があり、性能担保する為には以下のようなSQLで分散テーブルに対して分散列を指定してやる必要があります。

SELECT create_distributed_table(
    'table_name',
    'distribution_column');

また性能劣化させない為に分散テーブルの参照には分散列の指定(Where句)が必要になります。

こういった特徴により、Azure Cosmos DB for PostgreSQLはベンダーロックイン要素が多少有り、移行コストはそれなりにあると思われます。

料金については以下で算出可能です。
価格 - Azure Cosmos DB | Microsoft Azure

1ノード辺り、8vCPUs、64 GiBメモリで ¥130,139/月 の料金となっています。
(2023/2/21現在)

AlloyDB

AlloyDBはGoogleが昨年(2022)リリースしたカラム型エンジンを備えたDBになります。トランザクションは4倍、分析クエリは最大100倍高速になると謳われており、こちらの性能アピールも魅力的です。

AlloyDB のアーキテクチャの概要としては以下のような概念図となります。

概念図

  • Cluster
    • ルートリソースになります
  • Primary instance
    • クラスター内のデータベースの読み取り/書き込み接続ポイントを提供します
  • Read pool instance
    • クラスター内のデータベースの読み取り接続ポイントを提供します
    • ノード数を指定してスケーリング可能です
    • 参照系性能が必要でなければRead pool instanceがなくても動作します

AlloyDBはPostgreSQLとの完全互換が謳われており、Aurora PostgreSQLからの移行コストは低そうです。

ただAlloyDBの売りの一つであるカラム型エンジンを有効にした場合は、以下のようにjson型などはサポートされなくなるようなので注意が必要です。
About the AlloyDB columnar engine  |  AlloyDB for PostgreSQL  |  Google Cloud

カラム型エンジンについては以下で詳しく紹介されていますので、興味がある方は参照してみてください。
AlloyDB for PostgreSQL の仕組み: カラム型エンジン | Google Cloud 公式ブログ

料金についてはvCPUsやメモリによって決まっています。
料金  |  AlloyDB for PostgreSQL  |  Google Cloud

月単位で1vCPUが$61.7434、メモリ1GBが$10.4682 の料金になっています。
(2023/2/21現在)

DWH選定

Redshift vs SnowFlake vs BigQuery で比較を行います。

DWHのそれぞれの性能面の比較については今回は一旦おいています。その理由としては以下の記事で紹介されているようにそれぞれのベンダー間でベンチマーク戦争が行われているのと、利用のユースケースによって性能が変わってくると思われるためです。
Redshift vs. BigQuery - 選択ガイド | Integrate.io

DB選定にも言えることですが、弊社プロダクトのユースケースに沿った性能検証は別途行います。

またDWHのコスト比較についてもSnowFlakeやBigQueryは従量課金で算出しずらく、ある所ではRedshiftが安いと言われていたり、ある所ではBigQueryが安いと言われていたりするので、こちらも今回は比較対象外にしています。

Redshift

Redshiftは以下の特徴があります。

  • フェデレーテッドクエリを使用するとRedshiftからAurora PostreSQLのデータが参照可能
  • Aurora PostreSQL側からもリンクテーブルでRedshitを参照可能
  • 月額制の通常プランと従量課金のサーバーレスプランがある
  • PostgreSQL8.xに基づいて作られている

他のDWHにいえることだと思いますが、更新系はそこまで早くないのとデータ量が少ないと検索速度の優位性は小さくなります。逆にデータ量が多くなれば爆速が期待できます。

また同時実行についても同時実行増加オプションはありますが、デフォルトの同時実行推奨数が15とそちら方面も強くありません。

尚、Redshift Serverlessについては同時実行スケーリングがデフォルトで含まれています。

SnowFlake

SnowFlakeの特徴は以下になります。

  • PostgreSQLとの互換性はない
  • 従量課金制
  • WebUIは洗練されている
  • データはAmazon S3に保存
  • リンクテーブルは使えない

SnowFlakeはAurora PostgreSQLでのリンクテーブルが使えないのと、PostgreSQLとの互換性はないことから移行コストは高そうです。

また、弊社ではRubyがメイン言語で使われているのですが、SnowflakeではRuby用のネイティブコネクタが用意されていないのでそちらはマイナス要因となります。
Snowflake Community

BigQuery

BigQueryの特徴は以下になります。

  • PostgreSQLとの互換性はない
  • 従量課金制
  • GCP関連のDBサービスならほぼリアルタイムにレプリケーションできると謳われている
  • 内部的なサーバーリソースの振り分けはBigQueryが自動でやってくれ、DWHの中ではよりマネージドなサービスに分類される
  • リンクテーブルは使えない

BigQueryもAurora PostgreSQLからの移行コストは高そうですが、AlloyDBを選定した場合にはほぼリアルタイムにレプリケーションできると謳われているのは魅力的です。

おわりに

今回はDBとDWHの技術選定調査についてお話ししました。

今回の調査の結果、弊社開発チームとしてはAlloyDBとRedshift Serverlessに対して、弊社プロダクトのユースケースに沿った性能検証を行うこととなりました。

理由としてはAlloyDBは移行コストが低そうで性能改善が見込まれること。Redshift Serverlessも性能改善が見込まれることと、フェデレーテッドクエリなどが利用できる所から移行コストが比較的低そうだというのが主な理由となります。

弊社プロダクトのユースケースに沿った性能検証については現在行っているところですので、また機会があれば記事にしたいと思います。

We're hiring!

今あるものを更により良くするための方法を、我々と一緒に模索してくれる開発メンバーを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

herp.careers

プロダクトマネジメント、時代の変化

DIGGLEのベルマです。DIGGLEではプロダクトマネジメントチームとして6月に組織化しました。立ち上げにあたってどのようにプロダクトマネジメントしていくべきかを考えるため、過去の文献や資料をリサーチしました。
今回のブログでは、リサーチを自分の考察も含めてまとめたものを下記にアウトプットすることにしました。

 

DISCLAIMER: 本記事は元が英語であるため、一部ニュアンスが正しく翻訳できていない場合があります

デジタル化の始まり、SaaSが台頭する前の時代

この頃の特徴
  • エンジニアのリソースが少なく、地理的にアクセスしにくい

  • コンピュータサイエンスに特化した教育・研究を行っている国が少ない

                             areppim AG, 2008, https://stats.areppim.com/stats/stats_unitopnbrxengx09.htm

  • 初期開発コストが高い(特に、サーバーの調達コストが高い)

                            2009年10月14日.https://www.rbbtoday.com/article/2009/10/14/62995.html 

  • 大規模な手動テストの実施やバージョン管理システムの未発達のため、開発の所要期間が長い

  • サーバーは物理的で、クラウドは一部の国でしか利用できず、技術スタックも柔軟性に欠ける

  • 製品はパッケージ商品であり、すべての機能が一緒に開発され、ユーザーからのフィードバックは最後にあり、カスタマーサポートが製品の成功の中心でない

  • マーケットはグローバルが対象であり、参入企業は少ない

 

            Scott Brinker, https://chiefmartec.com/2020/04/marketing-technology-landscape-2020-martech-5000/ 

 

この時代のプロダクトマネジメント

 

 

 

以前は2種類のコミュニケーションが行われていました。

マーケット・コミュニケーション

  1. 競合分析、社内SWOT
  2. 市場の展望、それに基づく
    1. インパクトマップ
    2. ステークホルダーマップ
  3. ユーザーのニーズ
    1. ユーザー要件
    2. ユーザージャーニーマップ
  4. 営業コミュニケーションをリードするためのプレイブックを作成する
    1. 営業パイプラインをベースに、お客様にサービスを提供するための契約書を作成する
    2. 営業はCRM、オンライン広告、パイプライン管理ツールを使用する
  5. マーケターは以下の情報を収集する
    1. 上記の情報をもとに、お客様へのセールスピッチを行い
    2. カスタマーサポートは、マーケティングや製品コミュニケーションに問題がある場合、以下の方法で修正します
  6. お客様のオンボーディング
    1. 機能のリリースをサポートする
    2. 既存顧客からのバグ、問題、要求の収集
  7. このコミュニケーションに関わるすべての人はプロフィットセンターである
  8. このコミュニケーションに参加する全員が、お客様(マーケット)と向き合います

プロダクトコミュニケーション

  1. CTOが率いるプロダクトチーム
    1. エンジニアが多いチーム構成
    2. 要件は技術的なもの
    3. CTOとシニアエンジニアは、営業やマーケティングから要件を聞き、迅速かつ効率的に生産することを目的としています
    4. 製品管理ツールはない
  2. DEVチームはCTOが率いる
    1. チームは製品ごとにチームまたは部門に分かれています
    2. プロダクトをまたいで人が動くが、タイムラグが長い
    3. DEVの要件は、シニアエンジニアからチームに伝えられ、実行される
    4. DEVは課題管理ツール、Github、Wikiを使って日々制作している
  3. QAチームはシニアエンジニアが中心
    1. CTOが設定したリリースの基準に合致しているかどうかを確認する
    2. 常にスコープクリープに対抗する
    3. チェック不足でリリースの妨げになったり、品質を低下させたりしている
    4. エクセルシートで行われる
  4. このコミュニケーションに参加する全員がコストセンターである
  5. このコミュニケーションに参加する全員が、顧客と向き合えていないことが多い

デジタル時代、SaaSの時代

特徴
  • エンジニアリングリソースが豊富で、地理的な問題が業務に影響しない

   


                              

                                                       ARWU (Academic Ranking of World Universities), 08/30/2019

  • 開発の初期費用が安い、サーバー、採用、マーケティングがすべてクラウド上にある

  • CI/CDによる自動テストやデプロイの自動化、バージョン管理システムの進化により、現在では開発プロセスは高度にコラボレーションできるようになりました

  • 開発プラットフォームはすべてデジタルで、プラグアンドプレイスタイル。多くの競合他社からオンラインのクラウドベースのサーバーを複数選択でき、技術スタックは市場の需要に追従する

  • プロダクトスタイルはアプリ重視で、各アプリはエンドユーザーの1つの「ジョブ」を解決することに焦点を当てる。カスタマージャーニーを通して常にお客様からのフィードバックを求める。カスタマーサクセスは、プロダクトを通じたカスタマージャーニーの実現を目指して行動する

  • 競争は広範囲に及び、常に進化しローカルである。アプリは簡単に複製でき、ニッチ市場を獲得するために展開できる

 

画像元

この時代のプロダクトマネジメント

                   

 

  1. 社内全員が同じコミュニケーションチャネルで、同じ方向に向かって話している

  2. PdMは製品チームの一員ではありませんが、すべての部門のフィードバックと市場洞察を促進するために

    1. アイデア

    2. ストーリーからサブタスクに

    3. エピックは、ストーリーを組み合わせて大きな画にしたもの

    4. エピックを各機能に落とし込む

  3. PdMは、そのタスクを管理するための独自のツールを持っている

    1. PdMはマーケティング-セールス-CSのマーケティング・サイクルと連動しています

    2. PdMはDEV-QAと連携し、生産サイクルを回す

  4. どのメンバーも収益の創出を担う

    1. DEVとQAは、ユーザーデータとユーザー行動からアイデアを生み出し、ユーザーエクスペリエンスに付加価値を与えることもできます。

    2. DEVは常にシステムパフォーマンスを管理し、それが顧客維持、ひいてはMRR/ARRに影響を与える。

  5. どのメンバーも様々な情報を元にマーケットと対峙する

    1. DEVとQAは、マーケット側からの情報をすべて把握し、プロダクトの議論に積極的に参加します

* 実際の部門役割の定義は、各社で異なります。例:呼び方や分解されることもある。PdM/PMM、商品企画、事業開発など。

 

過去と現在の対比



                          これまで

                       現在

email

チャットツール

paid feedback

instant free feedback

高価なマーケティング

低価格なマーケティング

競合比較は難しい

競合比較サイトが無料でみられる

ストーリー 引き継ぎ時間が長く、面倒くさい

idea >> story >> epic >> feature

フィードバックは1つの大きなカテゴリー

既存顧客の声と潜在顧客の声を分離

全てのアプリケーションを内製化

3rd partyによる積極的な自動化



その結果、このような変化が生まれました。

 

  1. より迅速で効果的なコミュニケーション*を実現します。

  2. マーケットの変化に機敏に対応するプロダクトマネジメント

    1. CS/Salesからのインプットが重要に

      1. 既存顧客とのベータテスト

    1. マーケットベンチマーク

    2. マーケットに合わせた機能

  3. 最小限のアプリをプロダクトスイートとして開発することができる

    1. 市場シェアと普及率の向上

    1. サブスクリプションモデルに移行できる価格が目立つ

 

* コミュニケーションに深みがない、つまり返信は早いけれども、積極的なコミュニケーションに欠けることもあります。

 

この記事で使用している用語について
  • SaaS - サービスとしてのソフトウェア
  • テスト/QA - 顧客にリリースする前にソフトウェアをテストすること
  • リポジトリ - 一連のソースコードと、それらのファイルに対する変更の履歴
  • Git - リポジトリの分散バージョンコントロールのためのソフトウェア
  • バージョン管理システム - ソフトウェアチームがソースコードの変更を長期的に管理するためのソフトウェアツール。
  • デプロイ - ソフトウェアシステムを一般顧客が使用できるようにするためのすべての活動。
  • 技術スタック - ソフトウェアを構築し実行するために使用する技術の組み合わせ。
  • ユーザージャーニーマップ - ソフトウェアを利用するユーザの流れを視覚的に表現した図
  • CTO - 最高技術責任者
  • 課題管理ツール - チケットトラッキングとアジャイルプロジェクトマネジメントを可能にする課題追跡製品
  • スコープクリープ - プロジェクト開始後に、プロジェクトのスコープが変更されることを指します。
  • コストセンター - 利益を直接的に増やさないが、それでも組織のコストがかかる組織内の部門や機能
  • ジョブ - 人々がなぜそのような行動をとるのかを理解するための、ジョブ理論というフレームワークにおける用語
  • ハイパーローカル - 明確に定義されたコミュニティとその住民に向けられた主要な焦点
  • Ideas-Story-Epic-Feature - エンドユーザーの視点で書かれた、ソフトウェアシステムの機能に関する非形式的な自然言語による記述
  • PdM - プロダクトマネージメントまたはプロダクトマネージャー
  • MRR/ARR - 月間定期収入/年間定期収入

 

DIGGLEの考える開発効率の上げ方

こんにちは。DIGGLEエンジニアリングマネージャーのzakkyです。 先月は埼玉県民の日(11/14)で学校が休みだったので、有給を取って家族写真を撮りに行ってきました。娘も息子も可愛かったです。

ということで、開発生産性 Advent Calendar 2022の23日目の記事となります。

前回のブログ投稿から約半年立ちましたが、その間に役職が変わりまして、エンジニアリングマネージャーになりました。 今回は、マネージャーになって最初の記事という事で、技術的な話からは少し離れて、DIGGLEの開発効率向上施策について書きたいと思います。

そもそも開発効率ってどうやったら向上するんだろう

開発効率向上と一言で言っても色々な視点や施策があるかと思います。

ざっと思いつくだけでも以下があるかと思います。

  • CI/CDの導入・高速化
  • IaC化
  • よく使うコマンドをaliasとして展開
  • 自動生成系のツールの利用(無ければ作成)

上記はDIGGLE内で実施している一例なのですが、今回は先ほどお伝えした通り、上記のような技術的な話からは少し離れ、開発チームとして行っている開発効率向上施策にフォーカスして書いていきたいと思います。

差し込み作業の発生頻度を減らす

皆さんも経験があるかと思いますが、今の作業に差し込みで新しい作業が発生するとガクッと作業効率が落ちることがあると思います。 他の業種に比べてエンジニアは特にここの部分が顕著で、いかに作業に集中してもらえるかが肝になると考えています。

弊社では、そんなエンジニアの効率を下げる差し込み対応を、スクラムマスターに集約することで、他のメンバーが作業に集中できる状況を作るようにしています。

スクラムマスターには負荷になってしまいますが、トータルとしてチームの生産性は向上することとなります。 また、余談となりますが、スクラムマスターを輪番にすることで、各メンバーの自律化を促す施策も行っています。 こちらについて、詳しくは下記の記事で語っておりますので参照ください。

www.wantedly.com

自律したフルスタックエンジニアで構成する

弊社エンジニアはバックエンドやフロントエンドの垣根なく全員フルスタックに開発を行っています。 また、全員が仕様の確認&調整、CSなど他チームとの連携なども含めて自律して行うことができるので、開発要件1件につきエンジニア1人を割り振れば事足りることが多いです。(開発工数次第で複数人で開発することもあります)

これにより、作業工程間での連携で引継ぎ工数が掛かってしまったり、バックエンド(またはフロントエンド)側のタスクだけが積みあがって滞留してしまうといった事を防げます。

また、自律したエンジニアで構成されたチームとすることで、一般的にメンバーが増えれば増えるほど増加していくマネジメントコストの増加を抑えることができます。 そしてマネージャーはマネジメントに追われることなく、開発へリソースを回すことができるようになります。

実際、今回マネージャーとなりましたが、マネジメントに関する工数はほぼ掛かっておりません。優秀なエンジニアに囲まれて嬉しい限りです。

チューター制度を採用することにより素早い立ち上がりをサポートする

せっかく新しいエンジニアの方に入社いただいても、実際に開発できるようになるまでに時間が掛かってしまっては意味がありません。 ということで、弊社では新規参画エンジニアの方にはチューターを付けて、立ち上げをサポートする仕組みがあります。

チューターには、業務内容の説明や、各作業方法の伝達など細かなサポートをお願いしています。 どのようにサポートするかについては、それぞれのエンジニアの裁量にお任せしているので、チューターとなるエンジニアごとに多少の違いはありますが、だいたい1カ月ほどで上記の自律したフルスタックエンジニアになれるようにサポートをお願いしています。

もちろん個人差もありますし、1カ月で「はいおしまい」ということではなく、チーム全体として新規参画エンジニアを受け入れる土壌を作る。ということに重きを置いています。

開発効率向上施策が打てる土壌を持つ

日々の開発の中で、「開発者として実施したいけど、目先の作業に押し流されてできないこと」があると、それが積み重なって負債となり、開発効率を低下させる要因となります。

例えば、以下のようなものがあるかと思います。

  • ライブラリやミドルウェアを最新バージョンに上げたい
  • CIが遅いから高速化したい
  • リファクタリングがしたい
    • unusedなコードがあるから消したい
    • 冗長なロジックを見つけたから共通化したい など

弊社では、上記のような開発効率向上施策に全体工数の何%を使用して良いかを事前に経営層と相談し、工数を確保しておく仕組みを取っています。(およそ10~20%程度)

これによりプランニング時に、「次のSprintではRailsのバージョンを上げたい」であったり、「CIを高速化しないと作業効率が悪すぎるから今やってしまおう」といった事が行いやすくなっています。

※無論、予定していた機能リリースが遅延したりしては元も子もないので、杓子定規で測るのではなく、都度都度柔軟に対応しています。

メンテナンスする資料をむやみに増やさない

README、新規参画者向けのキャッチアップ資料、各種議事録など、ドキュメント化は率先して行っています。

ただ、エンジニアの本分はコードを書くことですので、資料を作成する際には必ず以下を確認するようにしています。

  • なぜ必要なのか
  • 用途は何なのか
  • 似たような資料、もしくは代替しうる資料は他に存在しないか
  • メンテナンスし続けることができるか(※議事録や調査資料など揮発性の高い資料は除く)

特に「メンテナンスし続けることができるか」が重要だと思っています。 作ってはみたけど、メンテナンスせずに放置してしまうと、新規参画者へ間違った情報を与えてしまったり、逆に悪影響を及ぼすことすらありますし、メンテナンスするトリガーが明確か、工数を確保できるかも考えていくと、そこまで重要ではない資料は作成しない方向へ進むことが多いです。

そして、作ることに決めた資料は、チームとしてメンテナンスする意識を持ち、メンテナンスコストを認識・許容するようにしています。

環境面(PCや開発環境)でストレスを与えない

ディスプレイが小さかったり、PCのスペックが低かったりすると開発効率は当然落ちます。 また、開発環境(OSやIDEなど)を制限されることで慣れない環境を余儀なくされるケースもあります。 私も前職や前々職では貸与PCのスペックが低かったり、ディスプレイが貸与されなかったりと不便を感じたこともありました。

弊社では、ハイスペックPCの貸与はもちろん、OSやIDEの縛りもありませんので自分の好きなように開発環境を作ってパフォーマンスを出して貰うことが可能です。 実際、私はwindows + WSLで開発していますし、MacやUbuntuを使って開発しているメンバーもいます。

さいごに

上記のような開発効率向上施策は、その場その場で我々が考えて作り上げたものになります。そのため、「これが最適解である」ということではなく、あくまで現時点(2022年末時点)での形となります。 そして、今後も色々と改善が行われて変わっていくものだと思います。

また良いものが見つかったらご紹介させていただきますので続報お待ちください。

We're hiring!

DIGGLEでは、現在の仕組みを改善してより良いものを一緒に作っていけるメンバーを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

herp.careers

Meetyによるカジュアル面談も行っていますので、この記事の話をもっと聞きたい!という方がいらっしゃいましたら、お気軽にお声がけください。

meety.net

Ruby on Rails で Google API を利用するための承認フローを実装する

こんにちは!DIGGLE エンジニアの miyakawa です。
この記事は Ruby on Rails Advent Calendar 2022 の22日目の記事です。

qiita.com

はじめに

弊社のプロダクト DIGGLE には、Google ドライブ や Google スプレッドシートからデータをインポートする機能があり、この機能を実現するために Google Drive API を利用しています。

Google Drive API 等の Google API を利用して各種 Google サービス上のユーザーのデータを取得・操作する場合、OAuthによってアクセストークンを発行・利用する必要があります。

↓の画像のやつです

今回は Ruby on Rails のアプリにこちらの OAuth 承認フローを組み込む方法についてお話しします。

※この記事では触れないこと

  • OAuth について
  • Google API 個別の利用方法
  • Google OAuth の検証・公開の手順

ステップ1: OAuth 同意画面の設定

まず Google Cloud の Web コンソールから OAuth 同意画面の設定を行います。
OAuth 同意画面とはユーザーが OAuth の承認をする際に表示される画面のことを指します。

下記のURLからアプリ情報等を入力していきます。

https://console.cloud.google.com/apis/credentials/consent

ひとまずは開発環境向けなので適当に項目を埋めていって問題ありません。
(本番環境向けに公開する際には正しく情報を入れる必要があります。利用する OAuth スコープによっては Google によるアプリの検証が必要になります。)

ステップ2: OAuth クライアント ID の作成

OAuth 同意画面の設定が終わったら下記 URL から OAuth クライアント ID を作成します。

https://console.cloud.google.com/apis/credentials

アプリケーションの種類は「ウェブ アプリケーション」を選択し、承認済みのリダイレクト URI に次のステップで作成する OAuth 用 Controller の callback アクションの URI を入力します。

OAuth クライアント ID を作成するとクライアント ID とクライアントシークレットが発行されるので控えておきます。

ステップ3: Rails アプリに OAuth 承認機能を実装する

※ 既に Rails アプリの土台があることを前提にしています。

gem のインストール

今回は Google 公式の OAuth クライアントライブラリである googleauth gem を使用します。

github.com

余談

googleauth gem では signet という汎用的な OAuth 2.0 クライアントライブラリを利用しています。
もし Google 以外のサービス向けに OAuth のクライアント実装が必要な場合はこちらの利用を検討してみてください。

OAuth アクセストークンの Model 作成

今回の実装では、取得した OAuth アクセストークン(アクセストークン、リフレッシュトークン)を RDB(PostgreSQL)に保存します。

マイグレーションと Model は下記のようになります。

class CreateGoogleOauthTokens < ActiveRecord::Migration[7.0]
  def change
    create_table :google_oauth_tokens do |t|
      t.references :user, null: false, foreign_key: true
      t.string :access_token, null: false
      t.string :refresh_token, null: false
      t.timestamps
    end
  end
end
class GoogleOauthToken < ApplicationRecord
  belongs_to :user

  validates :access_token, presence: true
  validates :refresh_token, presence: true
end

※ 上記コードは説明用のため省略していますが、実際は access_tokenrefresh_token は暗号化して保存するように実装するべきです。

Token Store の作成

Token Store とは googleauth gem における OAuth アクセストークンの保存場所です。先の通り、今回は RDB です。

googleauth gem では以下二つの Token Store が実装されていますが、RDB 向けの実装は用意されていません。

  • Google::Auth::Stores::FileTokenStore
  • Google::Auth::Stores::RedisTokenStore

そのため、自アプリの RDB 向けの Token Store を実装します。

require 'googleauth/token_store'

class DBTokenStore < Google::Auth::TokenStore
  def load id
    token = GoogleOauthToken.find_by(user_id: id)
    return nil if token.nil?
    JSON.dump({
      access_token: token.access_token,
      refresh_token: token.refresh_token
    })
  end

  def store id, token
    token_hash = JSON.parse(token)
    token = GoogleOauthToken.find_or_initialize_by(user_id: id)
    token.update!(
      access_token: token_hash['access_token'],
      refresh_token: token_hash['refresh_token'],
    )
  end

  def delete id
    token = GoogleOauthToken.find_by(user_id: id)
    token&.destroy!
  end
end

内容はシンプルで、loadstoredelete の各メソッドに DB への保存、更新、削除の処理を定義したものとなっています。

OAuth 用 Controller の作成

最後に Controller の作成です。

class GoogleOauthController < ApplicationController
  before_action :set_authorizer

  def authorize
    credentials = authorizer.get_credentials(current_user.id)
    if credentials.nil?
      redirect_to authorizer.get_authorization_url(request: request)
    else
      redirect_back fallback_location: root_path
    end
  end

  def callback
    cred, = authorizer.handle_auth_callback(current_user.id, request)
    redirect_to root_path
  rescue Signet::AuthorizationError
    render :authorization_error
  end

  private

  def set_authorizer
    # クライアント ID とクライアントシークレットを環境変数で渡しておく
    client_id = Google::Auth::ClientId.new(ENV.fetch('GOOGLE_OAUTH_CLIENT_ID'), ENV.fetch('GOOGLE_OAUTH_CLIENT_SECRET'))
    # Google API の OAuth スコープのリスト。例として Google Drive の読み込み専用スコープを指定。
    scopes = ['https://www.googleapis.com/auth/drive.readonly']
    token_store = DBTokenStore.new
    # callback アクションの URL
    callback_url = 'http://localhost:3000/google_oauth/callback'

    @authorizer = Google::Auth::WebUserAuthorizer.new(client_id, scopes, token_store, callback_url)
  end
end

routes.rb の設定もお忘れなく。

...
resources :google_oauth, only: [] do
  collection do
    get :authorize
    get :callback
  end
end
...

※ callback アクションの URL が、ステップ2の 承認済みのリダイレクト URI に設定したものと異なる場合はリダイレクト時にエラーとなるため再度設定を確認しておいてください。

これで、authorize アクションのURLにアクセスすることで Google OAuth の認可プロセスを行うことが可能になります。

コードの説明

まず authorizer についてですが、これによってアクセストークンの取得・保存、認証先リダイレクトURLの作成などを行うことができます。
そして、今回利用している Google::Auth::WebUserAuthorizer は authorizer の Rack アプリケーション向けアダプタです。

@authorizer = Google::Auth::WebUserAuthorizer.new(client_id, scopes, token_store, callback_url)


authorize アクションは、Google OAuth の認証/認可の画面へリダイレクトする役割をもちます。

callback アクションは、ユーザーが Google OAuth の認可を終えた後のリダイレクト先であり、リクエストに付与された認証情報を Token Store に保存します。

(参考)Google API の OAuth スコープ

Google API のサービス個別に OAuth のスコープが用意されており、下記ページでスコープの一覧を確認できます。

OAuth 2.0 Scopes for Google APIs  |  Authorization  |  Google Developers

ステップ4: アクセストークンを利用して Google API を使う

アクセストークンを取得できたら、それを利用して Google API を使うことができます。

以下は Google 製の Google ドライブ用クライアントライブラリ google-apis-drive_v3 を使う例です。

require 'google/apis/drive_v3'

drive = Google::Apis::DriveV3::DriveService.new
# 先述の @authorizer を利用して認証情報をセット
drive.authorization = @authorizer.get_credentials(current_user.id)

# ドライブトップ上のファイルを取得
files = drive.list_files()
files.items.each do |file|
  puts file.title
end

おわりに

今回は Ruby on Rails のアプリに Google の OAuth 承認フローを実装する方法についてお話ししました。

他にも関連するトピックとして、Drive API の小話や Google OAuth 利用のためのアプリの検証の方法などあるので、また機会があればお話しできればと思います。

We're hiring!

今あるものを更により良くするための方法を、我々と一緒に模索してくれる開発メンバーを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

herp.careers

Meetyによるカジュアル面談も行っていますので、この記事の話をもっと聞きたい!という方がいらっしゃいましたら、お気軽にお声がけください。

meety.net