STORES でエンジニアをしている片桐です。
STORES では店舗運営に関するさまざまなプロダクトを提供しています。これらのプロダクトは元々別の会社で運営されてきた完全に異なるプロダクト群で、アカウント体系から全く異なるシステムになっていました。近年はこれらのシステムを本格的に統合する取り組みを進めてきており、その中で統合のためにいくつかのシステムが新たに作成されてきました。
ある程度統合が進み、うまくいったところ・いかなかったところが見えてきた中で、これまでに作ったシステムの技術選定・システムの役割に対する課題感が見えてきました。
現在弊社ではこの課題を解決していくプロジェクトを進めています。その中の1つで、Goで作られたシステムをRuby on Railsで作られたシステムに移植する作業を行なっているので、今回はそれについて紹介させていただきます。
移植元のシステムの課題
今回別システムに移植する「Goで作られたシステム」の課題感は、以下のようなものでした。
- 社内でのバックエンド開発における、goとrubyでの習熟度・エコシステムへのリソース投入の差が大きくなってきた。
- 社内のgolang開発に対する開発基盤が成熟しておらず、一般的なweb frameworkであれば最初から提供されている機能が存在しない。
- そのため、もし必要になった場合はそのタイミングで工数を割いて実装するか、なんとか回避する方法を検討する必要がある。
- そして、弊社の実態としてはほとんどの場面で無理やり回避する方針となってしまっている。
- 高パフォーマンス・省メモリであることのメリットを享受できていない。
- いくら省メモリだとしても、別サーバーを立てて運用するコストはさすがに回収できない。
- 一般的なweb applicationにおいては、極端な高負荷にならない限り言語の遅さがボトルネックになるケースはあまり見られない
- 静的型付けの恩恵を享受できていない。
- 開発環境の未成熟な現状では、railsに比べて極端にテストを書く負荷が高い。
- 結果としてテストがきちんと書けていない部分が出てきてしまっており、型による安全性よりもテスト不足によるリスクの方が大きくなってしまっている。
- 型のメリットが強く出るリファクタリングなどに関しても、railsに精通するエンジニアが多く型の無い言語でも問題なく進める知見のある弊社では、型無しでもデメリットが大きいとはいえない。
- 移植先システムとの役割の境界が曖昧になってきている。
- 移植先システムから移植元システムの情報にアクセスしたい場面が非常に多く、毎回API経由でアクセスしなければならないことがネックになってきている。
- 単純なラウンドトリップが増えることによるレイテンシーの増加もあるし、機能開発時に複数のサーバーに手を入れなければならないことによる開発効率の低下も起きている。
上記の課題感に対して、以下の方針で改善を進めることにしました。
- 役割の境界が曖昧になっている2システムを統合すること。
- 弊社の環境においてより高い生産性が発揮できる、Ruby on Railsのシステムに機能を統合し、移植元のシステムは廃止すること。
移植の進め方
移植にあたっては以下の方針で進めることにしました。
- 移植にあたってサービスの停止は行わない。
- プロダクト統合に関連するシステムということもあり、社内の多くのプロダクトから参照されている。そのため停止すると影響範囲が大きく、調整が大変。
- できるだけ細かい単位で移植する。
- 対象を細かく区切ることで、問題があったときの影響範囲・動作確認の範囲を狭くできる。
- 万が一問題が発生した場合も、原因の切り分けが容易になる。
- 動作確認の精度向上も期待できる。確認範囲が広くなると隅々まで目が行き届かなくなりがち。
移植における課題
移植元と移植先がそれぞれ別々のデータベースを持っている
通常、1システムにつきデータベースは1つであるのが普通なので、今回の移植においても移植元のDBを移植先のDBに統合できるのが理想です。しかしながら、すでに稼働しているシステムのデータベースを統合するのは非常に大掛かりな作業になります。加えて双方で同じ名前のテーブルも存在しており、統合は一層難しい状況でした。
今回システムを移植する目的は、端的に言えば「生産性を上げる」ことです。それを踏まえると、複数のデータベースを扱う複雑性を踏まえても、まずRails上にアプリケーションコードが統合された状態にすることが望ましいと判断しました。
現在のRuby on Railsには複数のデータベースを扱う仕組みが存在するので、この仕組みを利用して移植元のデータベースを扱うことにしました。
エンドポイントがGraphQLで、REST APIのようなエンドポイント単位の移行が困難
通常のREST APIであれば、リソース x アクションごとに異なるエンドポイントURLが存在し、個別に新旧のシステムへ向き先を切り替えることが可能です。しかし、移植元システムのAPIは全面的にGraphQLを採用している1ため、単一のGraphQLエンドポイント(URL)に対してすべてのquery・mutationが送られます。このため、REST APIのようにエンドポイント単位で段階的に移行することが困難な状況です。
すべてのquery・mutationを実装し終わってから向き先を変えるということも可能ではあります。しかし、このやり方ですと一度に広範囲に影響が及んでしまう、いわゆるビッグバンリリースとなってしまうため、現実的ではありません。
これの対応策として、「GraphQL stitching」という技術を採用することにしました。これは、1つのGraphQL Schemaを複数のサーバーによって提供することを可能にする技術です。
詳細は以前の記事で紹介しているので、もし興味があればご覧ください。
これを利用することで、GraphQLのquery・mutationを部分的にあたらしいサーバーに置き換えていくことが可能になります。この技術はすでに弊社内で採用実績があり、私自身もこの機能を利用したAPIの開発に関わっていました。そのため、この技術の使い勝手はもちろんのこと、制約などについてもある程度の肌感があったので、この技術を利用しての移行は現実的と判断しました。
移植作業の進捗
本記事の執筆時点では、ここで紹介させていただいた複数データベースを扱う設定や、GraphQL Stitchingの導入のようなベース部分の実装はすでに完了しています。現在は個別のmutationやqueryの移植作業を粛々と進めている状況です。
ベース部分の実装では色々とはまりどころや工夫した点があったのでぜひ紹介させていただきたいところなのですが、長くなってしまうためこれらは別の記事で紹介させていただきます。
もし興味を持っていただけましたらぜひこの後の記事も読んでいただけると嬉しいです。
- 一部を除く。この「一部」はREST APIとして実装されており、移行に特段難しい点はないのでここでは触れません。↩