自己紹介

金城秀希氏(以下、金城):それでは「Rails Engineをフル活用した複数アプリの機能共通化の勘所」と題して発表します。よろしくお願いします。

今日はこんな流れでお話します。まずは自己紹介と事業紹介、Rails Engineを活用の背景。そしてRails Engineとは、活用事例、勘所、メッセージというような流れです。

改めまして、金城秀希と言います。ざっくりと経歴をお話しすると、沖縄出身で、大学を出て新卒で機械部品メーカーの営業を2年していました。2020年5月に、メドピアにRailsエンジニアとして入社しました。Twitterは金城だけに、“ゴールデンキャッスル”っていう感じの名前に最近変えてみたので(笑)。よかったらフォローしてください。

Rails Engineを使うようになった背景

では始めていきます。突然ですがみなさん、歩いていますか?

ライフログプラットフォーム事業部では、歩くのが楽しくなるアプリを開発しています。左から「スギサポwalk」「日経歩数番」「サツドラウォーク」「aminoステップ™」という、計4つの歩数計アプリを開発・リリースしてきました。今表示されているのがそれぞれのホーム画面で、バーチャルウォークラリーになっています。

バーチャルウォークラリーとは、リアルで歩いた歩数がアプリ上でカウントされ、歩けば歩くほどこの歩数が積み上がり、6,000歩、12,000歩、18,000歩とチェックポイントを通過していき、30,000歩でゴールというウォーキングイベントで。ゴールすると次のラリーが始まる仕組みです。

アプリには歩数計として、歩くのが楽しくなる機能だけではなく、店舗にチェックインできたり、医師に健康相談できたり、クイズや質問に回答する「ミッション」という機能があったり、またそれらのイベントに応じてポイントを貯めることができたりできたり。多くの機能があります。

もともとバーチャルウォークラリーを提供するパートナー毎に、アプリの各機能を個別開発していました。しかし、パートナーが増えてきたこともあり、コンテンツ投入だけで素早くパートナーのオーダーに応える基盤構築を目指しためRails Engineを使うようになりました。

Rails Engineについて

Rails Engineについて、詳しく見ていきます。Rails Engineとは、「ホストとなるRailsアプリケーションに機能を提供する、ミニチュア版Railsアプリケーション」です。こちらのイラストは弊社のキャラクターのメドベアといいますが、これを1つのレポジトリと思ってもらい、真ん中にあるのがホストアプリです。Engineをまた別のレポジトリで開発をして、例えば読む機能、書く機能、PCを触る機能などをホストアプリに提供できる。

それぞれのEngineも、ミニチュア版のRailsアプリケーションになっています。これからの話では、このメドベアに運転する機能を与えるということで、medbear_driveというEngineを開発するつもりで話を進めていきたいと思います。

Rails Engineを作成するには、rails plugin new medbear_drive --mountableというコマンドを実行します。実行すると(スライドの)左の図のようなディレクトリが生成されます。App配下にassetsやcontrollersがあり、一見すると普通のRailsです。開発しているときもEngineであると意識する必要はそんなになくて、普通のRailsと同じように開発できます。

特徴的なのは、spec、dummy配下にdummyアプリというものが存在します。このディレクトリもRailsサーバーを動かせるディレクトリで、app配下で開発した機能を、dummyアプリで試しながら開発できます。

開発がいったん終わると、弊社ではGithub Packageにpublishします。Github Packageとは何かというと、詳細は割愛しますが、レポジトリと同じようなソースコードのホスティングサービスではあります。

RubyGemsやnpmパッケージなどのパッケージとしてホスティングして、アプリ側で依存関係として取り込めるようにするサービスになっています。

publishしたあとは、gemファイルに記載してbundle installするだけで、他のgemと同様に取り込めます。

Rails Engineの活用法

これからgem、Rails Engineをどのように活用しているのか紹介したいと思います。4月に「aminoステップ™」という、味の素とコラボレーションした歩数計アプリをリリースしました。「aminoステップ™」では、なんと18個ものEngineを使っています。総テーブル数75のうち、71個はEngine関連のテーブルです。残りの4つのテーブルだけが、「aminoステップ™」のホストアプリで生成されたテーブルになっています。

4つしかないというと「スカスカなんじゃないか」という感じですが、それでもEngineによって多くの機能が提供されており、アプリとして成立しています。

「aminoステップ™」で先に導入した機能を、導入工数1日で「サツドラウォーク」というまた別のアプリに導入できる事例もありました。質問やクイズに答える「ミッション」という機能ですが、普通に開発すると数ヶ月かかった機能が、別のアプリに1日で導入できたという、Rails Engineの共通化できる強みが現れた事例でした。

Rails Engineを活用する中で見えてきた勘所ポイント

Rails Engineを活用する中で見えてきた勘所ポイントを3つ紹介したいと思います。まず1つ目は、Engineの設定はConfigで行うということです。Config アクセッサというものが用意されていて、config.user_class_nameみたいな感じで、定義を受けつける仕組みがあります。“::Bear”と設定をすることで、値をEngine側で使いまわせます。

こうすることで、例えば「他のアプリだとuser_classを使っているけど、このホストアプリではBearというクラスを使いたい」という場合に、config.user_class_name = のかたちで、Config、設定できます。2つ目、ホストアプリ側の拡張は、専用モジュールを作成して行うということです。例えばホストアプリ側にBearというクラスがあったとして、そこにメソッドを定義したり、has_many関連を定義したいとなったときに、そのメソッドを専用のモジュールを1つ作って機能を実装していき、includeするかたちで拡張します。

それ以外の変なところでBearクラスをオープンしたり、class evalしたりして拡張することは基本的にしない。以上の2つの方法でしか、お互いの設定や拡張はしないようにすることが重要です。

2つ目は、バージョン管理を行うということです。Publishすると、タグやバージョンも一緒につけることになります。そのときに、更新内容を紐づけたり、アプリ側でなにか対応が必要な場合には、Breaking Changesとして記載します。Publishすると、dependabotを使っていれば(スライド中央のように)知らせてくれて、バージョン単位でbundle updateしていきます。

3つ目、デメリットを理解するという点です。4つデメリットを紹介したいと思います。1つ目はホストアプリ側での機能拡張が制限される点です。先ほどお話した2つの拡張方法でしか拡張はしないので、それ以外のことをしようとすると、どうしても難しいです。それでも拡張するとなった場合、他のアプリへの影響も出てくるので、アプリを跨いだ意思決定が必要になってきます。

また、後方互換性を保つ開発も必要になります。最後に、ホストアプリ開発より時間がかかります。私の体感では、1.2から1.5倍ぐらいかかってしまう感覚です。

最後にメッセージをちょっと1つだけ。最初に、その機能をEngineにすべきか検討してみるといいかもしれません。普通、なにか機能を実装しようとなった場合、わりとなにも考えずにホストアプリに機能を実装していくかと思いますが、ちょっと踏みとどまって、ホストアプリのドメインに特化した内容ではない、他のアプリに共通化できそうな内容であれば、ぜひEngineでの開発を検討してみるとよいのではないでしょうか。以上で発表を終わります。ご清聴ありがとうございました。

質疑応答

司会者:発表ありがとうございました。質問がきているので、さっそくQAいっちゃいますね。「Rails Engine使うと、ダックタイピングとか、黒魔術っぽい感じの実装になってしまうんですか」という質問がきていますが、実際、どんな感じになっちゃうんですか? 

金城:正直、メタプログラミングみたいな技術というか、知識は必要になります。ただ、好き勝手にホストを拡張したり、オープンしたり、変にprependしたりすると、もう収拾がつかなくなるので。そこはちょっと厳しく制限をして、先ほどお話したようにConfigで設定するか、専用のモジュールをホストアプリにincludeする。基本この2つ以外は、メタプログラミング的な方法はあまり取らないようにはしています。

司会者:基本ルールを決めて、それをちゃんと順守する感じですね。Rails Engine採用して、「設計ミスったな」という点とかありますか?「このへん苦労してます」とか、言える範囲で(笑)。

金城:あります。例えば、歩数計でチェックポイントなどを通過すると、ポイントを発行するのですが、ポイントを発行するEngineが別のEngineにあるという設計です。Engineからまた他のEngineに依存してしまっている感じになってしまっていて、ポイントを発行するメソッドを実行したいときに、他のEngine側のメソッドを直接書いてしまったりすると、依存しているのであまりよくないということがありました。この設計改善は、Configにpoint issuerみたいな感じの変数で、procでConfigで設定して、そのポイントを発行するっていうprocを、歩数計アプリ側のEngineでcallする感じで乗り越えています。こんな感じで、後から出てきた失敗と改善はあります。

司会者:なるほど。わかりました。依存とか絡んでくると大変そうですよね。では、登壇ありがとうございました。

金城:ありがとうございました。