2019年6月の記事一覧

Go+Rails+SQS+S3で社内のオペレーターチェック入稿システムを刷新した話

こんにちは。サーバーサイドエンジニアの荒井です。
ラクスルでデータ入稿〜印刷用データ作成に関わる開発チームに所属しています。

以前、GoのおすすめDIツールについての話がありましたが、今回は、Goを使ってラクスルのオペレーターチェック入稿システムを刷新した話について書きたいと思います。

新システム構成図

新システム構成図

続きを読む

GoのおすすめDIツール

はじめに

これまでラクスルではRubyを開発言語として採用することが多かったのですが、最近はコマンドラインツールやバッチ処理などでGoによる開発も増えています。
私が最近取り組んでいる印刷発注基盤の刷新プロジェクトでもGoを使ってWEBアプリケーションを開発しており、社内では先例がないこともあって色々と苦労しながらも楽しく開発を進めています。
GoによるWEBアプリケーション開発では、RubyにおけるRailsのようなデファクトスタンダードは存在しないため考慮すべき点がたくさんあります。
例えば、「パッケージ構成をどうするか」「WEBアプリケーションフレームワークを使うべきか」などですが今回はちょっと軽めのテーマとしてDIツールについて紹介します。

DIとは

DIとはDependency Injectionの略で「依存性の注入」と訳されます。
Goではインタフェース型の値をコンスタラクタの引数などで渡すことでDIを実現することが多いと思います。
具体的なコードを見てみましょう。

type DogUsecase struct {
	repo DogRepository
}

func NewDogUsecase(dogRepo DogRepository) DogUsecase {
	return DogUsecase{
		repo: dogRepo,
	}
}

func (u DogUsecase) All() []Dog {
	return u.repo.All()
}

type DogRepository interface {
	All() []Dog
}

type dogRepositoryImpl struct{}

func NewDogRepository() DogRepository {
	return dogRepositoryImpl{}
}

func (r dogRepositoryImpl) All() []Dog {
	// DBからDogを取得
}

func main() {
	// インターフェース型の値を生成
	repo := NewDogRepository()
	// コンストラクタの引数として値を渡す
	usecase := NewDogUsecase(repo)
	usecase.All()
}

上記ではDogUsecaseのAll()メソッド内でDogRepositoryを使っています。
そして、コンストラクタであるNewDogUsecaseの引数としてDogRepositoryを渡しています。
ポイントは下記の2点です。

  • DogUsecaseの内部でDogRepositoryを生成しているのではなく外から渡している
  • DogRepositoryをインターフェースにしている

DogUsecaseDogRepositoryというインターフェースにのみ依存しており、その具象型(dogRepositoryImpl)には依存していません。
そのため、DogUsecaseの単体テストを書く時にDogRepositoryの具象型をモックに変更することが可能です。
また、DogRepositoryの具象型の実装に変更(データの取得元をDBではなく外部APIに変えるなど)があった場合でも、インターフェースが変わらない限りはDogUsecaseのコードを修正する必要がありません。
このように、DIを使うことによってDBや外部APIといった実装の詳細を利用者側から隠蔽し、モジュール間を疎結合にすることで保守性やテスタビリティを高めることができます。

DIツール(dig)の紹介

上述したコードではmain関数の中でDIを行なっていました。
今回のようなシンプルなコードの場合はこれでも問題ないのですが、アプリケーションが大きくなっていくとDIのためのコードが肥大化しメンテナンスしづらくなります
そこで、これらのコードを自動化するためのDIツールが役に立ちます。
GoのDIツールはいくつかありますが、現在私たちのプロジェクトではuber-go/digを使っています。
digの特徴は何といってもシンプルで学習コストが低い点です。また、少し複雑なDIもやろうと思えばできる柔軟性も備えています。
先ほどのコードの中でDIしていた箇所をdigを使って書き直すとこのようになります。

func main() {
  container := dig.New()
  container.Provide(NewDogRepository)
  container.Provide(NewDogUsecase)
  container.Invoke(func(usecase DogUsecase) {
    usecase.All()
  })
}

dig.New()でDIコンテナを作成し、Provideでコンテナに型を登録します。
Provideの引数は、「返り値として登録したい型を返す関数」です。
例えば、NewDogRepositoryは返り値としてDogRepository型を返す関数なので、Provideの引数として渡すことによってDogRepository型をコンテナに登録することができます。
登録した型の値を取り出したい場合には、Invokeを使用します。Invokeの引数は、「利用したい型を引数にした関数」です。
ここでは、DogUsecase型を使ってAll()メソッドを実行したいので、DogUsecase型を引数とする関数がInvokeの引数になっています。
注目していただきたいのは、container.Provide(NewDogUsecase)という行です。
NewDogUsecaseは引数としてDogRepositoryを必要としますが、この行ではDogRepositoryを渡していません。
一つ前の行のcontainer.Provide(NewDogRepository)によってDogRepository型をコンテナに登録しているため、Invokeの実行時には自動でNewDogRepositoryの引数であるDogRepository型の値をコンテナから取得することができます。

一見するとDIツールを使わなかったコードの方が短くてシンプルに思えるかもしれませんが、アプリケーションが成長してモジュールが増え依存関係も複雑になっていくと、手動でDIをする必要がなくコンストラクタの引数によって自動的にDIできることのメリットは大きくなります。

digの応用的な使い方

digを使った場合、基本的には型をベースにしてDIが実行されますが、時には同じ型で別の値を使いたいというケースもあると思います。
例として、MasterとSlaveのDBを同じRepository内で使いたいというケースを考えてみます。

func NewMasterConnection() (*sql.DB, error)
func NewSlaveConnection() (*sql.DB, error)

type DBConnection struct {
  dig.In

  MasterConn *sql.DB `name:"master"`
  SlaveConn  *sql.DB `name:"slave"`
}

func NewDogRepository(c DBConnection) DogRepository {
  return dogRepositoryImpl{
    master: c.MasterConn,
    slave:  c.SlaveConn,
  }
}

func main() {
  container := dig.New()
  container.Provide(NewMasterConnection, dig.Name("master"))
  container.Provide(NewSlaveConnection, dig.Name("slave"))
  container.Provide(NewDogRepository)
}

上記では、NewMasterConnectionNewSlaveConnectionProvideの引数として渡す際にdig.Nameを使って名前付けしています。
そして、NewDogRepositoryではDBConnectionという構造体のフィールドの中で`name:”master”``name:”slave”`といったタグを付ける事で同じ型(*sql.DB)の中でdig.Nameに対応する値を取得することができます。
このように、digは少し複雑なDIをする場合も柔軟に対応できます。
今回紹介した機能の他にもオプショナルな型であったり、同じ型の異なる値をグループ化したりといったこともできますので、詳しくは公式ドキュメントをご参照ください。

まとめ

  • ラクスルでもGoで開発している!
  • DIはモジュール間の結合度を下げて保守性やテスタビリティを高める
  • 手動DIはアプリケーションが大きくなるとつらいのでDIツールを使うのがオススメ
  • uber-go/digというDIツールが便利

社内ハッカソンの意義 〜Raksul Hack Week #2 開催から見えてきたこと〜

こんにちは。ハコベル(物流サービス)チームのサーバサイドエンジニア 加藤です。

このブログでもお伝えしていた第2回社内ハッカソン Raksul Hack Week #2。6月第2週に開催しました。今回は開催のレポートをしつつ、そこから見えてきたものについて書いてみたいと思います。

開催概要と今回のテーマ

2回目となる今回も、Raksul Hack Week #1から基本的な開催概要は変えずに企画しました。

  • 参加者はエンジニア、プロダクトマネージャー、デザイナーとする
  • ハッカソンの期間は1週間とし、期間中はハッカソン100%コミット、普段の開発業務はやらない(※ ただし緊急対応系は最優先で)
  • チーム制とする
  • 最終日に各チームは成果発表する
  • ラクスルの事業、ステークホルダーに関わることであれば何に取り組んでも良い

結果として、4日間の作業期間+1日の発表会&表彰式という日程で、2〜5名の16チームが参加する大イベントとなりました。運営は各事業部から集まったエンジニア・PM・デザイナーの4人で行いました。

そして今回のテーマは、

 

みんなで創る次の世界

〜好きなことから始まる挑戦〜

課題 ✖️チーム✖️技術

 

第一回の開催の後、様々な背景を持ったメンバーが増えて続けているプロダクトチーム。普段は業務に集中していて、視野を広げたり、チームを超えてお互いを知り合う時間が十分にとれないのが現状です。今回は、普段の仕事の範囲を超えて、お客様や社内の課題を共有し、チームと技術の力を使って、次の世界を実現するきっかけにしようという気持ちを込めました。

開催までの取り組み

今年は、以下のように開催2ヶ月前から次ごとにテーマを区切り準備を進めてきました。

  • 4月:アイデア月間
  • 5月:チーミング月間
  • 6月:Raksul Hack Week #2

この期間に、各事業部のビジネスメンバーからお客様の課題、自分たちの業務の課題やこんなのがあれば嬉しいといった声を聞くランチイベント Hack Week Lunch や、チームビルディングイベント Hack Week Pre-event を開催しました。

詳しくは、今年も Raksul Hack Week を開催します! や いよいよRaksul Hack Weekが始まります!をご覧ください。

いよいよ本番

まずは、初日のキックオフ。毎週の全社朝会の後、スケジュールや注意事項の説明を聞き、くじで発表順を決めていざ開始!

キックオフの様子

初日は、それぞれのチームで、4日間をどんな風に進めるか、課題を整理したりプランニングしたりする様子が見受けられました。

ホワイトボードを前にチームでディスカッション

進め方を決めたあとはそれぞれで作業を進めたり、ラクスルでもすっかり定番になった、ペアプロやモブプログラミングを取り入れて進めるチームやユーザ調査に出かけるチームもありました。

モブプロで作業を進めるチーム

連日、いつもより遅くまで作業を続ける姿が見られました。すごいコミットメントですね。

バイキングデスクで仲良く作業する2人

ベトナムからは今回は2チームが参加。

作戦を練り直すベトナムチームのメンバー

発表会

発表会は、参加メンバーはもちろん、弁理士の先生や、ボードメンバー、ビジネスメンバーも参加して行われました。持ち時間は、各チーム7分。お昼を挟んでの2部構成で行いました。

ネットワークがつながりにくいというトラブルがある中、それぞれ、デモまで行って聞き応えのある発表となりました。1週間の熱量が発表にもこもります。

チームみんなで発表

技術的にも、AI、IoT、モバイルアプリやフロント技術を使ったものや、組み合わせ方に工夫を凝らしたものなど、様々な取り組みが発表されました。どのチームも課題設定が素晴らしく、ボードメンバーからも賞賛の声がありました。

発表を聞き入るCEO 松本とCTO 泉

投票&表彰式

前回は、シールをボードに貼る形式で行われましたが、今回は集計のことも考えて Google Form を利用。一人2チームを選ぶ形式で投票を行いました。また、どこが素晴らしいと思って投票したのか、投票コメントも記入してもらうようにし、翌週に全チームにフィードバックしました。

受賞したチームや受賞は逃したものの評価が高かったチームを3チームほどご紹介します。

技術負債の可視化ツールに取り組んだのは、以前からこのブログにも度々投稿している、エンジニアマネージャーの二串とチームメンバー。ラクスルは以前から技術負債解消に果敢に取り組んでいますが、その取り組みをダイエットに見立てて、楽しく可視化しようとしたアイデアがみんなの心を掴みました。

技術負債可視化チーム

そして今回一位は、古参の岡田と新卒からラクスルにジョインしている3人のメンバーのチームでした。

印刷マッチングプラットフォームであるラクスル。その重要なテーマの1つである、発注周りの改善提案に取り組みました。

ビジネスメンバーが思いつかなかった課題にテクノロジーを使って切り込み、見事な改善提案で、みんなをあっと言わせました。

リーダーシップをとったのは、3年目の岸野。「以前から個人的にやるべきだと思っていた課題。Hack Week という機会に取り組むことができました。」とコメント。ジョブローテーションをしながら、確実に成長している3人の今後がますます楽しみになりました。

CEO松本と1位のチーム

ちなみに私のチームは、CEO賞を受賞しました。ハコベル(物流サービス)の技術スタックを横展開し、他事業で数年前からの課題を解決するWebサービスとアプリを開発しました。運営しつつも、今回のテーマ「みんなで創る次の世界」を体現したいと思い、CTOの泉、アプリエンジニアのライアン、デザイナーの武末とともに、トライアル運用を目指して取り組みました。

未来を見つめるCEO賞受賞チーム

ここで紹介しきれませんが、本当にどのチームの成果も素晴らしく、今後どのようにするのかは継続議論がされることになりました。

懇親会で締めくくり

みんな全力投球したHack Weekを終えての懇親会は、それぞれ取り組んだテーマの話で盛り上がりました。また、期間中のエピソードもたくさん聞くことができました。

懇親会で挨拶するCTO

Raksul Hack Week #2 開催から見えてきたもの

Hack Week後のアンケートを通じて、参加メンバーからたくさんの声をいただきました。

「Hack Week Lunchもラクスル自体の理解度解像度が上がり良かった」

「普段あまり関わりの無いメンバーと開発でき、充実していた」

「ゼロからのプロダクトに少人数で考えて向かうという、実は普段あまりチャンスが無いようなことができたことにとても価値があったと思う」

「新しい技術にトライできて勉強になったし楽しかった」

「自分自身の課題も見えたり、メンバーとの交流できて、良い経験が得られたなと思った」

 

これらの声や運営している中での気づきから、ラクスルにおける社内ハッカソンの意義を私なりにまとめてみました。

  1. 学びの機会
    – 技術だけでなく、ビジネス課題に視野を広げる
    – 課題設定、プランニング、ゼロイチの開発を自分たちで行う力を養う(広義の開発力の向上)
  2. コミュニケーションの機会
    – 成長しているエンジニア組織でのチームを超えたコミュニケーションの促進
    – お互いを応援・賞賛しあう文化づくり
  3. 挑戦の機会
    – 新しい技術への挑戦
    – 結果が見えないテーマに挑戦(失敗できる機会)
  4. ビジョンの実現の加速の機会
    – 動くものを見せることで未着手のテーマに本格的に取組むきっかけにする

Hack Week はさながら冒険の旅のようでした。出発前の戸惑いあったり、チームで課題を乗り切る場面があったり。翌週出会ったみんなは、心なしか一回り力強くなったように感じました。

表彰式後の集合写真。何のポーズかな??

1週間業務を止めて行う社内ハッカソンの開催は、それなりの投資。決定するのにも勇気が要りますし、運営チームや参加者を含めて準備もあります。でも、上記にまとめたように、今回それ以上に意義のあるイベントだと思いました。

エンジニア組織の活性化をお考えの方は、社内ハッカソンに取り組んでみてはいかがでしょうか。

ラクスルでは事業をつくっていきたいエンジニアを絶賛募集しています!

自分の書くコードで、印刷・広告・物流といった大きな産業の課題を解決するようなインパクトを作り出してみませんか。技術・業界・業務など多角的に理解を深めながら、自らアイデアを出し、主体的に事業をつくっていく。そんな働き方をしたい方をお待ちしています!

ハコベルチームはHack Week後から五反田の新オフィスで勤務。新オフィスにもぜひ遊びに来てください!

いよいよRaksul Hack Weekが始まります!

こんにちは。エンジニアマネージャーの石川です。普段は、印刷ECサイトで提供している集客支援(ダイレクトメール、新聞折込、ポスティング等)の開発に携わってます。

前回、ハコベルの加藤よりHack Weekの紹介をさせていただきましたが、いよいよHack Week #2の開催が来週となりました。

Hack Weekは、エンジニア、デザイナ、PM、普段ラクスルの事業を支えているTECHチームが参加するハッカソンイベントです。自分たちでアイディアを出し合い、1週間かけて開発を行います。

本日は、前回の記事以降に行われたチームビルディングイベントの様子をご紹介したいと思います!

チームビルディングイベントを開催しました

Hack Week開催まで3週間を切った5/22、Hack Week pre-eventと称して、チームビルディングを目的としたイベントを開催しました。

前回の記事では、 Hack Week Lunch を実施の様子をご紹介しましたが、今回はチームビルディングイベントということもあり、美味しい食事とお酒を楽しみながら、自由な雰囲気でコミュニケーションを取りやすいイベントを目指しました。

まずはIceBreak

Hack Weekでは、普段は違うチームで開発をしているメンバーと一緒に開発ができる良いチャンスでもあります。

今回のイベントでは、普段のチーム以外のメンバーともぜひ交流を深めてほしい。

そこで、冒頭の時間をIceBreakタイムとして簡単なゲームを実施しました。

まずは隣にいる人と話すきっかけを持ってもらうこと、声を出して話して見ることで、緊張をほぐしてもらうことが目的です。

2人ペアを組んでもらい、簡単なゲームを通して15分ほど会話を楽しんでもらいました。

会場の雰囲気も和んできたところで…ラクスルビールで乾杯をしてイベントスタートです!

チームビルディングタイム

「こういう面白いことやろうと思ってるんだけど、フロントやってくれませんか?」

と、熱いプレゼンをするメンバー

「まだ何をやるか決めてないんですが、何やるか決めました?」

と、すこし不安な思いを抱えながらの参加となったメンバー

それぞれの想いが入り混じりながら、チームビルディングタイムがスタートしました。

それぞれの想いを胸に、イベントスタートです!

最初はみんな手探りでしたが、それぞれが持ち寄ったアイディアを語り合いながら食事とお酒を楽しみ、それぞれのテーブルで様々な議論が交わされました。

身振り手振りを交えて熱い議論を交わします。

チームビルディングイベントの結果は?

イベント開催をきっかけに、16のチームが結成されました!

チームビルディングイベント中に結成されたチームも合ったようですが、イベントの翌日以降に改めて話して結成したチームも多く見られたようです。

チームビルディングイベントに参加してみて

私自身も、ラクスルに入社して4ヶ月。

社内にまだまだ知り合いが少ないので少し不安な気持ちでの参加でしたが、自己紹介から始まり、お互いの事業の紹介をしたり、温めていたアイディアの話をしたり、Hack Weekに対する不安が解消され「いよいよ始まるんだな!」と期待が膨らむイベントになりました。

いよいよRaksul Hack Weekが始まります!

いよいよ来週からRaksul Hack Weekが始まります。

それぞれの16チームのテーマも出揃いました。

テーマは、普段関わっている事業に関するもの、最新の技術を使ったもの、様々です。

次回は、Hack Week開催後に結果をお届けしようと思います。

ラクスルではエンジニアを引き続き募集しております!

ラクスルでは、一緒に事業を盛り上げてくれる仲間を引き続き募集しております。

エンジニア一人一人が事業に対して高い解像度を持ち、事業に関わることができる。全職種一丸となって事業を成長させていける、そんな職場です。

ただ開発をするだけではなく、事業を一緒に創っていきたい方、ぜひ弊社オフィスに遊びにきてください!

 

 

gRPC Client Interceptor入門 with Ruby

こんにちは。サーバサイドエンジニアの三瓶です。印刷ECサイトの ラクスル の開発保守を担当するチームに所属しています。

ラクスルでは Raksul Platform Project (RPP) と称して技術負債の返済活動を継続的に行っているのですが、その流れの一環として印刷ECチームでは巨大化したアプリケーションから商品仕様に関わる部分を別サービスとして切り出す、という作業を最近行いました。

この切り出したサービスでは通信方式として gRPC を、言語としてはRubyを採用しています。

実際に導入してみて、gRPCはまだドキュメントも多くはないと感じたので、本記事ではgRPCが備える機能の1つである Interceptor についてチュートリアル形式でご紹介したいと思います。言語はもちろんRubyを使います。

※ gRPC導入に至った背景や経緯について興味のある方は弊社エンジニアの二串のスライドをご参照ください

Interceptorとは何か

Interceptorとは、RPCコールの前後に任意の処理を差し込める機能・レイヤーのことです。Railsアプリケーションに馴染みがある方は Rack Middleware のようなものを想像してもらうと理解しやすいかと思います。

Interceptorを使えば、認証やロギング、データのフィルタリングなど各通信で共通の処理をひとまとめにできます。
実際に、我々のプロジェクトではリクエストを一意に特定するためのIDをmetadataに付与するInterceptorやリクエストしたメソッド・パラメータ・処理時間をログに残すInterceptor、例外をCatchしてSentryに通知するInterceptor等を自作して、本番環境で稼働しています。

以下はClient → Server間の通信に2つのInterceptorが介在したときのイメージ図です。

作成するClient Interceptorの仕様を決める

ClientのInterceptorと一口に言っても、以下のパターンが考えられます。

  • リクエスト処理の「前」に差し込む。リクエストデータに何か手を入れたい場合など
  • リクエスト処理の「後」に差し込む。レスポンスデータに何か手を入れたい場合など
  • リクエスト処理の「前後」に差し込む。両方に手を入れたい場合や処理全体に何かをしたい場合など

このうち、今回はリクエストの「前」に差し込んでリクエストデータを大文字化する UpcaseInterceptor と、リクエストの「前後」に差し込んで処理時間を計測する PerformanceLoggingInterceptor の2つを作ってみることにします。

以下、それぞれのインターセプターの簡単な仕様です。

  • UpcaseInterceptor
    • リクエストの前に差し込む
    • リクエストオブジェクトに name というキー名のデータあればその値を大文字化する
  • PerformanceLoggingInterceptor
    • リクエストの前後に差し込む
    • リクエスト全体にかかった時間を計測し、標準出力へ出力する
    • 時間は小数点第3位以下を四捨五入する

また、動作の流れは次の図のようになります。赤い線の箇所でインターセプターの処理を挟みこむイメージです。

Client Interceptorを実装する

※ 注意:gRPCのRuby実装では、IntercptorのAPIインターフェースは EXPERIMENTAL という扱いのようです。そのため、今後のバージョンアップで今回紹介するものとはAPIインターフェースが変わっている、ということがありえますのでお気をつけください。

Interceptorを実装するのはとても簡単で、今回の仕様であれば数行で作れてしまいます。

UpcaseInterceptor

UpcaseInterceptorの実装は次のようになりました。

# upcase_interceptor.rb
class UpcaseInterceptor < GRPC::ClientInterceptor
  def request_response(request: nil, call: nil, method: nil, metadata: nil)
    request.name = request.name.upcase
    yield
  end
end

いくつかポイントとなる部分を説明します。

(1) GRPC::ClientInterceptor を継承する

インターセプタークラスは GRPC::ClientInterceptor クラスを継承する必要があります。
継承しない場合、GRPC::InterceptorRegistry::DescendantError 例外が発生します。

(2) 通信方式に合わせてメソッドをオーバーライドする

メソッドは利用する通信方式に合わせてオーバーライドします。gRPCには以下4つの通信方式があり、それぞれに対応するメソッドがあります。

  • Unary RPC : #request_response
  • Server streaming RPC : #server_streamer
  • Client streaming RPC : #client_streamer
  • Bidirectional streaming RPC : #bidi_streamer

今回は、1リクエストに対して1レスポンスが返ってくる一番シンプルな通信方式であるUnary RPCを使うため、request_response メソッドをオーバーライドしています。

※ 各通信方式の詳細が気になる方は https://grpc.io/docs/guides/concepts/ をご参照ください

(3) yieldで次の処理を呼び出す

次にメソッドの中ですが、yield を呼び出すことで次のインターセプターまたはリクエスト処理の本体を呼び出すことになります。
UpcaseInterceptorはリクエスト実行の前に呼び出したいので、yield をコールする前に変換処理をしています。

PerformanceLoggingInterceptor

同様に、処理時間を計測して出力するPerformanceLoggingInterceptorは次のようになりました。

# performance_logging_interceptor.rb
class PerformanceLoggingInterceptor < GRPC::ClientInterceptor
  def request_response(request: nil, call: nil, method: nil, metadata: nil)
    start_time = Time.now.to_f
    resp = yield
    request_time = Time.now.to_f - start_time

    puts "duration: #{request_time.round(3)} sec"
    resp
  end
end

PerformanceLoggingInterceptorはリクエスト処理の全体を計測したいため、リクエストの前後に処理を差し込みます。
前述のとおり、リクエスト処理は yield を呼び出すことで伝播されていくため、その前後を囲う形で時間を取れば良いということになります。

返り値は yield で受け取ったレスポンスオブジェクトをそのまま返します。

Interceptorを使う

それでは、作成した2つのInterceptorを実際に使ってみましょう。
gRPC公式のチュートリアル Ruby Quick Start にあるHelloWorldアプリケーションに、今回作成したインターセプターを組み込んで動かしてみます。

Client Interceptorは、Stub と呼ばれるクライアントオブジェクトを初期化するときに引数として渡します。

# greeter_client.rb
require 'grpc'
require 'helloworld_services_pb'

# ★ 作成したインターセプターをrequireする
require_relative './performance_logging_interceptor'
require_relative './upcase_interceptor'

def main
  stub = Helloworld::Greeter::Stub.new(
    'localhost:50051',
    :this_channel_is_insecure,
    interceptors: [UpcaseInterceptor.new, PerformanceLoggingInterceptor.new] # ★ ここでインターセプターを設定する
  )

  # 省略..
end

main

interceptors 引数に渡す順序でインターセプターの実行順序が決まります。
配列の末尾から先頭へと順に実行されるため、上の書き方の場合、PerformanceLoggingInterceptorUpcaseInterceptor の順で実行されることになります。当初のイメージ図の通りの順序ですね。

それでは実行してみましょう。
まず、比較対象としてインターセプターを使わなかった場合の結果を見てみます。

$ bundle exec ruby greeter_client.rb
"Greeting: Hello world"

チュートリアルの通りであれば、上のように “Greeting: Hello world” という結果が出力されると思います。

次にインターセプターを有効化した状態で実行してみます。

$ bundle exec ruby greeter_client.rb
duration: 0.002 sec
"Greeting: Hello WORLD"

UpcaseInterceptor によって “world” が大文字化されて、PerfomanceLoggingInterceptor によって実行にかかった時間が出力されました!

まとめ

以上、簡単ではありますが、RubyでのgRPC Client Interceptorの仕組みや使い方についてのご紹介でした。

アプリケーションを本格的に運用するにつれて様々な要求が発生してくるかと思いますが、Interceptorが役に立つ・解決手段となるケースもあるのではないかと思います。そのようなときにこの記事が参考になれば幸いです。