Facebook や Google で行われているように、私たちはほぼ全てのプロダクトをただ一つのリポジトリーで管理しています。ここで管理しているものは iOS および Android、Windows、MacOS 版 の SDK と、iOS 版の PDF ビューワー、そしていくつかの内製ツールで、これは PDF 上の情報を簡単に探ることができる PSPDFInspector のようなものを指します。実際には Web 版の PSPDFKit や Android 版の PDF ビューワーといった例外があるのですが、それらを除いて私たちの全プロダクトはモノリポの上に生きています。
必ずしもモノリポを利用していた訳ではありませんでした。しかし時間を経て、モノリポからは多くの恩恵が得られるという結論に至ったのです。欠点も存在しますが、総じてモノリポはそれだけの価値があると断言できます。
不遇な生い立ち
2011 から 2013 年まで、PSPDFKit は iOS でだけ利用可能でした。しかし最終的に私たちは Android を無視することができなくなり、Android 版の PSPDFKit の開発を別リポジトリーで着手したのです。Android 版では完全に新しい UI が必要であるのに加え、Core と呼ばれる PDF ドキュメントの解析とレンダリングを担当するライブラリーについても新しいものが必要でした。iOS 版において私たちは、Apple のコアグラフィック PDF レンダリングエンジンに対し多くのカスタマイズを加えたものを使っていて、それによって PDF を解析して注釈を書き込む機能を実現していました。しかし全て Objective-C のコードだったため Android に流用することはできず、それはつまり新しい2つのリポジトリー(PSPDFKit-Android
と Core
)を一から始めることを意味していたのです。
私たちは双方のコンポーネントを交互に開発していたので、Core への変更の多くで Android 版の JNI ラッパーへの変更が必要になりました。そのため、1つの機能のために2つのプルリクエストを作成することは珍しいことではなかったですし、その2つのマージというのは同時に行う必要がありました。もちろん、Core リポジトリーへの PR のマージだけを行い Android 版の PR を残しておくこともできるのですが、私たちの目標は両 master として常に互換性を保つことであり、これによって多数のブランチを抱えることにより発生する潜在的なコンフリクトが減ったという追加の恩恵を得ることができていました。そうして、Core
リポジトリーを PSPDFKit-Android
へマージするまでに時間はかかりませんでした。これによってワークフローに対する大きな恩恵が得られるためです。他のプラットフォームのために Core
リポジトリーを使うことになったとしても、後から分離することは可能であることがわかっていました。
Android 版のリリースと Core の成長
Android 版がリリースされた後、私たちはすぐに全体をコントロールできる恩恵を目の当たりにすることになりました。Apple の CGPDF はすばらしい PDF レンダラーですが、一部の PDF が正常に描画されなかったり、非常に処理が遅かったり、さらに悪いことには Apple のコードの根深い部分でクラッシュすることが、私たちの顧客の間で常日頃に見つかっていました。Android 上ではこれらの問題をすぐに修正できましたが、iOS 上では radar(訳注:バグ報告機能)を送信するくらいしかできることはありませんでした。(ここは私たちがかなり知見を積んだ部分になります。)
もっと知りたい:Writing Good Bug Reports
iOS 版への新エンジン導入
2014 年、私たちは「iOS 版の Core を利用する」という大規模なプロジェクトを開始しました。私たちには3つの選択肢がありました。
PSPDFKit-Android
を iOS 版のサブモジュールとして含めCore
を再利用する。PSPDFKit-Android
からCore
を抽出して、以前のようなバラバラのプルリクエストを扱う。- iOS 版に加えて Android 版も(!)、新しい master リポジトリーにマージする。
多くの話し合いが持たれましたが、最終的に多数派の意見により一つの大きなリポジトリーへ移動するという案に決まりました。私たちは(まさしく文字通り)ヒストリーを書きかえるという任務に着手し PSPDFKit-Android
を PSPDFKit-iOS
にマージしました。プラットフォームごとのサブフォルダーが用意され、新しいリポジトリーには次の4つのフォルダが含まれました。
- iOS
- android
- core
- documentation
iOS/Android 版 PDF ビューワー
私たちが iOS 版および Android 版の PDF ビューワーの開発を始めた時、モノリポがフィットするとは考えていませんでした。結局のところ、それらは SDK の特定のバージョン(通常は安定版)を見ているので、複数のリポジトリーにまたがった PR というのは必要ありません。一方で、ビジネスパートナーへ新しい挙動や API を紹介する前に、新しい機能や拡張のテスト版としてアプリを使う機会が長年に渡って増えています。自分たちの API をドッグフーディングすることは、インターフェイスを良くする助けにもなりました。一方で否定的側面として、PDF ビューワーが開発中の API を使っていて、master の更新によって PDF ビューワーが突如として壊れるということが定期的にありました。
PSPDFKit SDK の構築と改良のために、さらに多くのチームのメンバーがメインアプリとして PDF ビューワーを使い始めましたが、ソースコードを介しての結合は各バイナリーをビルドするよりずっと簡単でした。私たちはまたもや多数のプルリクエストの組み合わせやサブモジュールの衝突に陥ってしまっていましたが、それも iOS 版の PDF ビューワーをモノリポに移すと決定するまでのことでした。
作業は非常に複雑なものになりました。私たちはヒストリーに関して非常に気にかけていましたし、大きなバイナリ成果物を Git LFS(Large File Storage)へ移行してしまうことで、すでにかなり大きなモノリポがさらに大きくなってしまうことを避けようとしました。自動移行スクリプトを書くのに2週間程度かかりましたが、私たちはついにそれを実施し、全ての GitHub Issue についても移行を行いました。
今月に Android 版 PDF ビューワーで同じ作業を計画しているのですが、ここでは Gradle のビルドスクリプトが非常にシンプルになったという追加の恩恵もありました。
Web とサーバー:特別なケース
Web 版 PSPDFKit (PSPDFKit-Web
) はピュア JavaScript ライブラリーとして 2016 年に開発が始まりました。Core API をラップした Unix デーモンと協調する Elixir アプリで、PSPDFKit サーバーを介してドキュメントを描画します。私たちはモノリポに cli
プロジェクトは追加したものの、その当時モノリポに Web や Server まで追加する理由は思いつきませんでした。つまる所、それらはかなり異なったエンティティです。
1年後、私たちは WebAssembly の調査を始めて、そこから Web スタンドアロン版 PSPDFKit が生まれました。それは Core を直接 WebAssembly にコンパイルして JavaScript モジュールとして呼び出します。この変更の結果として、Core と Web の関係性が急激に近くなり、時々 Core の更新によって Web の機能が壊されてしまうことさえありました。私たちは別々に分かれたリポジトリーを扱うことに対してすでに辛さを感じていましたし、Web 版 PSPDFKit で Core を使うことが多くなってきている状況をかんがみて、私たちは Web (と最終的には Server も)のモノリポへの移行に現在取り組んでおり、これによって私たちの作業はよりシンプルなものになるでしょう。
否定的側面
とは言うものの、モノリポにも否定的な側面があります。リポジトリーは頻繁に変更されるので、pull した時に再コンパイルが必要になることが多くなります。また、Slack に進捗の経過を push するフックのようなツールは、基本的に使うことが出来ません。これは、プラットフォームごとにトラフィックを別にしてチャンネルを分けるような有効な方法がまだ存在しないからです。(しかし、a rather active issue for GitHub’s Slack integration には、まさしくそういった機能を求める人々が参加しています。)
リポジトリーは何年もかけて数ギガバイトにもなっており、Git LFS システムを使ってその成長を緩和させているものの、古い履歴を書き変えることにそれほどの価値があるとは思えません。CI テストは実行時間がかすかに長くなるでしょう。これは最初に新しくチェックアウトし直すためです。(もちろんキャッシュすることは出来ますが、それでも小さなリポジトリーと同じくらい速くとはいきません。)
しかし、git submodule
を扱わなくて良いことは喜ばしいことであり、チームの誰もが Grand Merge (TM) 以前の状況には戻りたくありません。
また、各プルリクエストが現在全てのサポートしているプラットフォームをトリガーするので、CI サーバーのロード時間が急激に増加しました。私たちはカスタム Jenkins スクリプトを記述して、変更されたファイルからその影響のあるプラットフォームのみを賢くトリガーするようにこれを修正するようにしました。
もっと知りたい:Selective Pull Request Testing with Jenkins
結論
総じて、私たちはモノリポを採用して最高に満足していますし、さらに多くのプロジェクトをそこに含めようと計画しています。ワークフローがシンプルになると共に、各プラットフォームチーム間の距離が近づいたことで「深く掘り下げる」ことがしやすくなり、他のチームに事を任せてしまうのではなくプラットフォームをまたいだ問題解決がなされるようになりました。