Open4

マイクロサービスのアプリケーションを管理するのにyarn workspace v1は向いていないという話(何がベストプラクティスなのか知りたい)

tkowtkow

経緯

  • 管理画面のフロントエンド
  • 管理画面用のバックエンド
  • アプリケーションのフロントエンド
  • アプリケーションのバックエンド
  • スマホアプリ
  • 共通で使うAPIライブラリ

を全てnodejsで開発するためそれらの共通データのTypescript型定義ファイルをそれぞれのプロジェクト内で使い回すためにモノレポ管理で構築したが、かなり苦い経験となった。
結果としてyarn workspace v1からの移行先を探している状態。
自分の検証不足の面も多々あると思いますので、もしどなたか運用方法の課題を解決する方法を知っている方や良い移行先があれば紹介していただけると嬉しいです。

何が良くなかったか

  1. 管理画面のフロントエンドとアプリケーションのreactのバージョンが競合する。当初これは同じバージョンの運用をしていれば問題ないと考えていたが、@typesの型定義が微妙に変更があり、局所的にバージョンを上げることにした。この時no hoistに設定したreact利用ライブラリとワークスペースにinstallしたreactが両ケースを使ったものが存在しているとhoist先のライブラリを使用したケースでhoist先の@types/reactを参照し、buildに失敗することがある。この結果react関連のライブラリをほぼ全てno hoistに設定しなければならなくなり、このような設定をするのであればバージョンを全て追従させることがメリットであるモノレポでの管理を行う意味を失ってくる。
  2. privateパッケージをデプロイしない場合に、workspace毎にモジュールをインストールすることができない。yarn focusという機能があるが、これを使った場合依存ライブラリが全てnode_moduleをregistryからインストールすることを前提にした動きをするため、ローカルワークスペースのライブラリのbuild生成物を開発環境で使い回したい場合には不向き。結果として個別のプロジェクトのデプロイ時にも他のプロジェクトのライブラリのインストールを巻き込んでしまうのでCIが激重になり非常にDXが下がってしまった。該当のプロジェクトではなぜかworkspaceの中の依存するパッケージを全てデプロイしてもyarn focusでどこにも依存していないアプリケーションや管理画面のworkspaceパッケージをインストールしようとしてこける。基本的に共通で使うAPIライブラリ以外をregistryにデプロイする必要がないため、この仕様と相性が悪い。
  3. package毎のlockファイルが作れない。これが致命的で多くの場合後述(binaryのインストールが必要)のようにデプロイ先でnode moduleをインストールする必要があるためバージョンを固定していないとデプロイ先での動作の保証が難しくなる。モノレポ毎デプロイするという豪快な解決策を取れないこともないが、react-native等のプロジェクトを含んでいる場合storageやinstall時間やインストールサイズを不要に嵩ませてお財布に優しくない。これを回避するにはpackage.jsonでの指定が全て固定バージョンにしてlockファイルの代わりにする必要が出てくる。そのため、update時に常にバージョン指定を行う必要があり、DXが悪くなる。

よかったところ

  1. private npmをインストールする場合、ローカルではpackage.jsonのlinkプロトコルと同じような開発を行いながらデプロイサーバでpublishしたprivate npmのインストールに切り替えるのはpakcage.jsonの中身を変えずに出来るので楽だった。そうでない場合は、デプロイ時にlinkプロトコルを差し替えるか、deployするスクリプトにnode moduleまでbundleしたbuildをデプロイするのがよいが、binaryに依存したライブラリなどをインストールする場合なのでpackage.jsonからnode moduleをインストールが必要になるケースが存在するので、完全にnode_moduleをbundle出来ることを想定しない方が良い。
  2. lernaのパッケージのバージョンアップのスクリプトが優秀。バージョンを統一的に扱うことでプロジェクト間でどのパッケージのバージョンを使うべきかと追従が簡単に行えるようになった。

なぜ移行先に悩んでいるか

移行するにあたって

  1. 前述のlockファイルの生成問題
  2. デプロイ先でのパッケージマネージャーのinstallができるか
  3. workspace毎のnode moduleのインストールに対応しているのか

という課題がある。今候補に上がっているnpm, pnpm, yarn v3などの対応状況が不透明(詳しい方いたら教えてください)。

これは、GCPのApp Engine Standard Editionを使いたいため。現在のCIで既にyarn workspaceを前提で動いているCloud Buildのコードがいるが、GAEのデプロイ先で適切なインストーラーを選んでインストールできるかどうかが不明(pnpmなどに変更した場合、lockファイルやpnpmのインストールを軌道の前に走らせることができるのかなど)

現在考えている応急的な対応

今のworkspaceの共通部分のみをyarn workspaceにして、プロジェクトは別directoryで切り分ける。
ライブラリをhoistしてしまうとworkspaceのscopeで依存が見つけられなかったものは上部のnode_modulesを読みに行ってしまうことでバグが起きやすく、また、この現象に気づきにくいため、完全に切り離しておいた方が混乱が少ないようだ。

この場合プロジェクト側からworkspaceのパッケージを読み出す場合、linkプロトコルを使った方法で依存ライブラリを管理しても良いが、デプロイ時の変更が面倒なのでprivate registryに毎回チームでnpm publishしてしまう方がよいだろう。属人化した仕組みでpackage.jsonなどを書き換えると開発に混乱を招きかねない。そもそも同じgithub repositoryで管理しているのであれば、deployするほどのものでなければsymlink(npm link)で解決する方法が一番良いと思われる。

tkowtkow

まとめ

モノレポでworkspace管理を行う場合はあくまでnode_modulesをpublishする必要があるものだけを管理の対象にしましょう。どうしてもという時はお互いのパッケージの構成が他のパッケージのデプロイを妨げないほど近しい場合のみに限定して、lockファイルの管理をどうするかチームで決まりを作る必要があります。
同じ構成ではないマイクロサービスを管理する場合には使わないようにしましょう。(ただし、yarn workspace v1での話なので、他ではどうかわかりません。)

tkowtkow

知人から共有。どうやらpnpmには
https://pnpm.io/ja/workspaces#shared-workspace-lockfile
なるものが存在しているらしい。
しかしpnpmはホスティングするサービスによってはnpm, yarnをデフォルトで選択させるためpnpmのinstallを挟める事で帰って効率を悪くしてしまう懸念がある。
このあたりは未だ過渡期である事は否めない

tkowtkow

【朗報】
pnpmのshared-workspace-lockfile=falseを設定し、

pnpm i --filter rootPackageName,targetPackageName

とすることによって、rootとデプロイしたいパッケージの依存モジュールのみをインストールすることが可能。

これによりpnpmを使える環境ではデプロイ時に不必要なpackageのinstallを避けることができ、かつ、pnpm-lock.yamlによってデプロイ先で安全なインストールができるようになる。