2022年4月21日に、メルペイ・メルコインのエンジニアたちによる「Merpay Tech Talk〜Monorepo開発におけるツール選定〜」を開催しました。
Monorepo開発を進めるにあたって、メルコインではNx、メルペイではTurborepoを利用しています。Monorepo開発を導入した背景やツール選定の理由、実際に導入した所感などをご紹介します。
アーカイブはこちら! https://www.youtube.com/watch?v=I0Wxr1JCvhs
登壇者紹介
@tokuda109 / 株式会社メルペイ Frontend Engineer
2019年10月に入社し、クーポン機能のフロントエンド開発を担当後、パフォーマンスやテスト等のフロントエンドの品質改善に取り組む。
@Solazzi Marco / 株式会社メルペイ Frontend Engineer
イタリア出身。UIとA11yの開発が大好き。趣味はアメリカドラマとイタリア料理。
@ryotah / 株式会社メルペイ Frontend Engineer
2005年に株式会社サイバーエージェントに入社、広告コンテンツ制作、アバターサービス開発、ゲーム開発を担当。2014年から株式会社トレタでBtoBのSasS事業開発を担当。2018年にメルペイに入社、Webフロントエンドエンジニアとして各種サービスの開発を行う。
@shiruco / 株式会社メルコイン Frontend Engineer
フロントエンドエンジニアとして様々なゲーム開発やアプリケーション開発に従事。2017年頃からブロックチェーン技術に興味を持ちはじめ、2021年10月にメルコインへ入社。新規サービスのローンチに向け日々奮闘中。
メルコインフロントエンドにおけるNx導入について
@ryotah:モノレポとは、一つのリポジトリの中に複数の異なるプロジェクトが入った構成をいいます。プロジェクトの依存関係が明確に定義されていることが特徴です。
モノレポのメリットは、次の四つです。
一つ目は、「Shares code and visibility」です。コードが見やすく、検索や共有が簡単になります。npmやyarn、pnpmのWorkspaceを使ってモジュールをインストールしたり、TypeScriptのパスのエイリアスを使って簡単にインポートしたりできます。
二つ目は、「Atomic changes」です。複数プロジェクトにまたがる変更が容易になることです。バックエンドとフロントエンドが一つのリポジトリに入っている場合、一つのPRでAPIやコードの修正ができます。
三つ目の「Developer mobility」は、一貫性のあるツールを提供することで、別チームのプロジェクトの手伝い、あるいは開発者の異動がしやすくなることを意味します。
四つ目の「Single set of Dependencies」は、依存ライブラリを単一バージョンにすることをいいます。一般的なモノレポのメリットというよりかは、JavaScript関連にフォーカスした話になります。例えば、Reactの17.5が入ったプロジェクトとReactの17.8が入ったプロジェクトが混在しないようにすることです。
モノレポをメルコインフロントエンドに導入した理由は、メルペイでの開発における課題を解消するためです。
メルペイのフロントエンドにはたくさんのアプリケーションがあるため、リポジトリの数が多くなっています。エンジニアはプロジェクトを兼務・異動することもあり、横断的にプロジェクトに触れなければいけません。
これらのアプリケーションは、いずれもメルペイのドメインの中で動かし、Nuxtを使用して作成しているので、必要な機能は似通っています。そのため、社内向けの共通ライブラリを使用しているのですが、実際に運用してみると、これが非常に面倒です。
依存ライブラリの更新作業も大変です。Renovateを使って、更新作業をストレスなく実行しようとしているのですが、それでも多くて、ほぼ同じ作業を各リポジトリでしなければいけません。
リポジトリを横断した変更も大変です。例えば社内の共通ライブラリをセキュリティ対応のために更新した場合、ライブラリを利用している全リポジトリで個別にパッケージ更新作業が必要になります。
一貫性のない開発環境にも課題を感じていました。npmコマンドがプロジェクトごとに異なると、別のプロジェクトから移ってきた人は動かし方がわからずストレスになります。
メルコインでプロジェクトを作っていくにあたって、同じようなことが起こりそうだと思ったので、モノレポの導入を決めました。モノレポの導入によってコードは同一リポジトリに置かれるので、コードの重複を解消できます。1PRで複数プロジェクトの変更ができるため、リポジトリを横断した変更も簡単です。
いくつかのプロジェクトは、すでにメルコインフロントエンドのモノレポ環境で動いています。これはダミーですが、同じような構成になっています。
モノレポツールとしてNxを選定する際に注意したことは、次の通りです。
一つのリポジトリに全部のプロジェクトを入れることは、多数のチーム・プロジェクトが存在していることを意味します。速度を落とさず安心・安全に開発を進めるような機能や仕組がなければ逆に大変になってしまいます。
例えば、コードの変更内容や依存関係から必要なbuildやtestを自動判定し、必要な部分のみ実行できる機能があります。タスクを並列で実行したり、実行結果をキャッシュする機能もあります。
最終的にNxを選んだ理由は、メルコインでのモノレポ運用のために必要な機能を満たしているためです。
開発が安定していることも理由のひとつです。Nxは元Angularの開発メンバーによって設立されたNrwlによって開発され、2018年にリリースされています。現在はバージョン13で、ユーザー数も多く、安心して使えると思います。わかりやすいAPIや、どのようなパッケージマネージャーでも利用できる柔軟性もポイントです。
最後に、Nxについて説明します。
Nxとは、拡張可能なモノレポツールです。ドキュメントが多く、一見したところ複雑で学習コストが高いツールに見えますが、実は柔軟性があって簡単に利用できるツールだったりします。
基本的にはNx Coreがあり、必要に応じてNx pluginsを導入する流れです。実際にはNx Coreのみでモノレポを導入することもできます。その場合、かなりシンプルに導入できます。Nx pluginsを一方のプロジェクトでは使用し、もう一方のプロジェクトでは使用しない、という利用方法も可能です。
Nx Coreは、文字通りNxのコア機能です。少ない依存パッケージと設定ファイルで利用できます。nx.jsonとworkspace.jsonの二つの設定ファイルさえあれば、Nx Coreの機能を使えます。Nx Coreはモノレポ内の依存環境を解析したり、変更内容の影響範囲を解析したり、タスクの並列実行や計算結果のキャッシュを行ったりしています。
モノレポ内の依存環境を解析すると、依存関係の可視化や、依存関係から必要なタスクの実行ができるようになります。
また、変更内容から必要なタスクを実行できるので、複数のプロジェクトにまたがる変更をPRで投げたときに、変更された内容を見て必要なプロジェクトのタスクを実行できます。さらに依存関係の情報があれば、触っていないプロジェクトやコードについてもテストを自動実行できるのです。
Nx Pluginsは、Nx Coreと一緒に使うことで機能を拡張できるもので、generator、executerの二つにわかれます。generatorは、テンプレートから新しいファイルを作成するための設計図です。新規プロジェクトを同一フォーマットで簡単に作成できるなどの特徴があります。executerは、generatorから生成されたプロジェクトを実行するためのものです。通常、作ったプロジェクトをnpm-scriptで動かすところを、代わりにしてくれます。一定のルールに沿ったコマンドがすでに用意されているので、別のフレームワークを使っているプロジェクトであっても、統一されたコマンドで実行できます。
@shiruco:ありがとうございました。私もNxを開発で使っているのですが、単一のpackage.jsonでの運用はもちろん、モジュール単位でpackage.jsonを持つこともできるので、その辺の柔軟性が良いと思います。
メルペイフロントエンドにおけるTurborepo導入について
@tokuda109:続いて、メルペイフロントエンドチームが抱えている現状の問題、それを解決するためにTurborepoをどのように使っているかを紹介します。
これは、メルペイのフロントエンドチームの歴史です。
2019年2月にメルペイをリリースして、しばらくは機能追加やバグ修正をしていました。
2020年には新規開発が落ち着いて、ソフトウェアの品質をあげていくフェーズに移りました。パフォーマンス、アクセシビリティ、テストを導入し、改善ループを回せるよう可視化しました。
2021年には、セキュリティも品質の一つに加わりました。リリースしてから今までに、これだけのものが追加されたことは、コードベースがとても大きくなったことを意味します。
具体的な問題は、npmスクリプトが多く、どの順番で実行すべきかわからないことです。npmスクリプトには実行順を明示的に記述できないので、各スクリプトが行う内容を理解し、正しい順番で実行する必要があります。
また、インストールしているパッケージ数が増えてきて、バージョンアップに苦戦することも多くなりました。このパッケージがなぜ入っているのかわからないが、除外すると動かなくなりそうなので残しておくというケースが増えています。
メルペイフロントエンドのpackage.jsonは、次のようになっています。
上からdev、start、buildと、Nuxtのアプリケーションを起動・buildするためのスクリプトがあります。その下にlint、testが記載されている状態です。下の方には、cypressを使ったテストがあります。ブラウザが対象のアプリケーションにアクセスしてテストを行うので、事前にアプリケーションをビルドする必要があります。
一つ目がNuxt buildをするスクリプト、二つ目がNuxt generateをするスクリプトです。この二つを実行して初めてテスト対象のサイトが作られ、三つ目のtestを実行するスクリプトを実行できます。
npmスクリプトをもっと適切に実行したい、package.jsonをさらに小さく分割したいと考え、Turborepoの導入を視野に入れました。
Turborepoの特徴は、次の通りです。
Turborepoは、フロントエンド開発用のシンプルかつ余分な機能がほとんどないツールです。コマンドを通常npm run buildと記述するところ、turbo run build という形で実行します。
Turborepo自体にはWorkspace機能がなく、npmやyarnなどのパッケージマネージャーで分割されたWorkspaceと共存する形で使います。Workspace機能を使うとpackage.jsonを複数に分割できるので、各Workspaceにあるpackage.jsonの依存やスクリプトはシンプルになります。
タスクの依存を解決するという特徴もあります。先ほどのような三つのコマンドを順番に実行する必要がなく、cypressを実行するとbuildやgenerateも一緒に実行できるのです。
実行内容が同じであれば、スクリプトを実行せず結果だけを返してくれるので、実行がすぐに終わります。cypressを続けて実行するとき、build結果が変わらない場合はcypressのテストのみを実行できます。個人的にはとてもいい機能だと思います。
続いて、他のビルドツールの導入検証についてお話しします。類似ツールはいくつかあり、メルペイに合うものを検討した結果、Turborepoを採用することになりました。理由は次の通りです。
Turborepoはシンプルで学習コストが低く、ツール自体の依存も少ないので、初めてであっても負担なく使うことができると思います。
また、GitHubのDiscussionsとDiscordを通して、ユーザーとコミュニケーションを活発に行い、プロダクトを改善している点も特徴です。
例えば、バージョン1.2から導入された「フィルター機能」は、Discussionsで提案・議論されていたものです。Discordはさまざまな方が参加しており、「うまく動かないから助けてほしい」「現在こういうアプリ開発をしている」などのカジュアルなコミュニケーションがなされています。
ここからは、メルペイでどのようにTurborepoを使っているかを説明します。メルペイでは、Turborepoと同時にpnpmというパッケージマネージャーを使っています。Workspaceのディレクトリ構成は、次のようになっています。
プロダクトコードが格納されているappや、各種テストが格納されているe2e、integration、unitなどに分割されています。
ディレクトリを分割することで、各Workspaceのpackage.jsonに記載されているdependenciesやscriptsは少なくなります。分割されたWorkspaceの依存を解決・実行するのが、Turborepoの役割です。
これはTurborepoのタスクと依存を定義し、turbo.jsonという名前でプロジェクトのルートに置いたファイルです。pipeline中のapp#buildとapp#generateが、Turborepoから実行するタスクです。
app#generateのハッシュの前が、各Workspaceのpackage.jsonのネームに記載されている値で、ハッシュの後ろはスクリプトに記載されています。
このファイルには、全体の依存関係が記述されています。例えば一番下のcypress#runは、依存関係としてdependsOnにapp#generateが記載されています。つまり、app#generateに依存していることが定義されています。同様に、app#generateではdependsOnにapp#buildが記載されており、app#buildへの依存が定義されています。
app#generateとapp#buildをたどり、二つのbuildが終わった後にcypress#runが実行されます。2回目以降の実行ではapp#generateとapp#buildはキャッシュされ、cypressのテストのみが実行される仕組みです。
以上が、Turborepoの基本的な使い方です。Turborepoはシンプルで既存のプロジェクトに導入しやすく、動作も安定しています。メルペイでは、モノレポ導入のためというより、テストやパフォーマンスなどをWorkspaceに分割して保守性を上げるために使っていますね。今は一つのプロジェクトで試しているのですが、問題がなければ他のプロジェクトにも展開する予定です。
メルペイ・メルコインのフロントエンドチーム体制とプロダクトについて
@shiruco:ここからはメルペイ・メルコインのフロントエンドチーム体制と運用しているプロダクトについて説明させてください。
まずは、体制についてです。メルコインは会社として立ち上がって間もないため、メルペイとメルコインでフロントエンドチームはわかれていません。エンジニアは11名、うち外国籍の方が2名在籍しており、コミュニケーションは基本的に英語で行っています。
続いて、メルペイとメルコインで扱っているプロダクトについてです。
メルペイでは、お客さま向け、加盟店さま向け、社内オペレーター向けの3つのサービスがあります。メルコインでは、最近ローンチしたNFT(パ・リーグ Exciting Moments)というお客さま向けサービスや、メルペイ同様社内オペレーター向けのサービス、その他のサービス開発も進めています。
モノレポを取り入れるために検討したツール
@shiruco:ここからは、パネルディスカッションに入ります。今回メルペイではTurborepo、メルコインではNxを採用しました。モノレポを取り入れるために、他にどのようなツールを検討しましたか?
@ryotah:主にメルペイでもともと使用していたLernaや、マイクロソフトのRushも調べていました。
メルコインのモノレポ環境を作ったのが2021年12月頭、TurborepoがVercelに買収されたのが12月末だったのです。Turborepoも気になってはいましたが、すでにNxを導入し、開発を進めていたので、そのまま使用しました。
@tokuda109:メルペイでは、Nx、Turborepo、Rushの3つを比較しました。
@shiruco:モノレポを導入するにあたって、パッケージマネージャもpnpmに変更した理由はありますか?
@tokuda109:package.jsonのメンテナンスで保守性を担保するのが難しくなってきたため、分割してTurborepoで依存を解決しようと考えたからです。そこでpnpmのWorkspaceとTurborepoになりました。
モノレポを導入した感想と課題
@shiruco:実際にモノレポを導入してみてどうですか?よかったことや今後の課題を聞かせてください。
@ryotah:現状はメリットしかないですね。強いて言うなら、今後チーム数やプロジェクト数が増えたときの対応方法を考えていく必要があります。
例えば以前cypressでテストを書いていて、共通UIライブラリのアクセシビリティのタグを変更したいときがありました。今までであれば別のリポジトリで更新・パブリッシュして手元のバージョンをアップデートしていたのが、今では横のディレクトリを更新するだけで完了できるので快適です。
@Solazzi Marco:Nxの場合はいろいろなプロジェクトが入って、リリーススケジュールが異なる分問題があるかもしれません。これはモノレポのデメリットだと思います。スケジュールを綺麗にまとめられれば、問題ないと思いますよ。
モノレポへの移行で気をつけたことや今後の計画
@shiruco:今あるプロダクトをモノレポに移行していく中で、大変だったことや気をつけたことはありますか。
@tokuda109:他のプロジェクトに展開するにあたって、事前知識がないエンジニアでも移行作業ができるようにドキュメントを残す、commitの分割の仕方を考える、などの対策を取りました。Turborepoは小さいツールなので、作業自体はさほど苦戦しなかったと思います。
@shiruco:全員の合意を取りながら進めていくのが大変かと思います。モノレポはこれから本格運用する予定かと思いますが、今後の計画や理想の形について聞いてみたいです。
@ryotah:むずかしい質問ですね(笑)以前感じていた課題は概ね解決できていると思うので、さらにプロジェクトが増えてきたとき、必要に応じてNxの細かい設定などを考えたいと思います。
Nxの操作には、意外と困りませんでした。Nx pluginsにはいろいろと制約があると思っていたのですが、意外と標準機能だけで事足ります。困ったらカスタマイズもできるので便利です。
@shiruco:僕の場合、モノレポでの開発が初めてだった上に、Nxはドキュメントが多いので、キャッチアップは大変でした。ただ、コマンドの癖がない点、VS Codeからコマンドを実行できる点は便利だと思います。バージョンアップに追随する際にタイムラグがあるとは思うのですが、Nxはしっかりメンテナンスされているので、今のところ不都合は感じていません。
最近気になっている技術/モノレポのCIについて
@shiruco:最後に、モノレポに関わらず皆さんが気になっている技術があれば教えてください。
@tokuda109:Goで作られたツールが気になります。Turborepoもそうですが、Goなど、node.jsのエコシステムではない作られ方をしているものもありますよね。Nuxt をそのような作り方をするのは難しいのかなと思うのですが、ツールチェーンの一部として使うならうまく機能すると思います。
@Solazzi Marco:最近は、JavaScriptを使用していないツールがたくさんありますよね。一点心配なのが、おかしな挙動があったとき、別の言語なので対応しにくくなる可能性があることです。例えば不具合が起こったとき、Goがわからなくて対応できないとか。
@shiruco:ありがとうございます。ここで、「モノレポのCIは何を使っていますか?工夫点があれば知りたいです。」という質問がありました。メルペイではCircleCI、メルコインではGitHub Actionsを使用しています。
@ryotah:GitHub Actionsに限った話ではありませんが、NxやTurborepoで変更したコードに対してタスクを実行する機能があるので、Nxであればaffectedコマンド、Turborepoであれば–sinceを利用して、CI上では変更が影響する箇所のみをテストするようにしています。
また、リリースではリリースタグの命名ルールを作って、それに合わせたプロジェクトをデプロイするという使い方をしています。
@shiruco:ありがとうございました。
最後に
merpay Tech Talkは、定期的にエンジニア向けのイベントを開催しています。イベント開催案内を受け取りたい方は、connpassグループのメンバーになってくださいね!