フロントエンドコードのMonoreposガイド
公開: 2022-03-11モノレポは議論のホットトピックです。 最近、プロジェクトでこのタイプのアーキテクチャを使用する必要がある理由と使用すべきでない理由について多くの記事がありますが、それらのほとんどは何らかの形で偏っています。 このシリーズは、モノリポジトリをいつどのように使用するかを理解するために、可能な限り多くの情報を収集して説明する試みです。
モノリポジトリはアーキテクチャの概念であり、基本的にそのタイトルにすべての意味が含まれています。 複数のリポジトリを管理する代わりに、分離されたすべてのコード部分を1つのリポジトリ内に保持します。 分離という言葉を覚えておいてください。これは、monorepoがモノリシックアプリと共通点がないことを意味します。 1つのリポジトリ内にさまざまな種類の論理アプリを保持できます。 たとえば、WebサイトとそのiOSアプリ。
この概念は比較的古く、約10年前に登場しました。 Googleは、コードベースを管理するためにこのアプローチを採用した最初の企業の1つでした。 それが10年間存在していたのなら、なぜ今だけそんなにホットな話題なのかと疑問に思うかもしれません。 ほとんどの場合、過去5〜6年の間に、多くのことが劇的な変化を遂げました。 ES6、SCSSプリプロセッサ、タスクマネージャー、npmなど。現在、小さなReactベースのアプリを維持するには、プロジェクトバンドラー、テストスイート、CI / CDスクリプト、Docker構成、および他に何を知っているかを処理する必要があります。 そして今、小さなアプリの代わりに、多くの機能領域で構成される巨大なプラットフォームを維持する必要があると想像してください。 アーキテクチャについて考えている場合は、主に2つのことを実行する必要があります。関心の分離とコードの重複の回避です。
これを実現するには、大きな機能をいくつかのパッケージに分離してから、メインアプリの単一のエントリポイントを介してそれらを使用することをお勧めします。 しかし、これらのパッケージをどのように管理しますか? 各パッケージには独自のワークフロー環境構成が必要です。つまり、新しいパッケージを作成するたびに、新しい環境を構成したり、すべての構成ファイルをコピーしたりする必要があります。 または、たとえば、ビルドシステムで何かを変更する必要がある場合は、各リポジトリを確認し、コミットを実行し、プルリクエストを作成して、ビルドごとに待機する必要があります。これにより、処理速度が大幅に低下します。 このステップでは、モノリポジトリに対応しています。
独自の構成を持つ多くのリポジトリを用意する代わりに、信頼できる唯一の情報源、つまり、1つのテストスイートランナー、1つのDocker構成ファイル、およびWebpackの1つの構成のみを使用します。 そして、スケーラビリティ、関心の分離、一般的なパッケージとのコード共有、および他の多くの長所がまだあります。 いいですね。 そうですね。 しかし、いくつかの欠点もあります。 野生でモノレポを使用することの正確な長所と短所を詳しく見てみましょう。
モノレポの利点:
- すべての構成とテストを保存する1つの場所。 すべてが1つのリポジトリ内にあるため、CI / CDとバンドラーを一度構成してから、構成を再利用してすべてのパッケージをビルドしてから、リモートに公開できます。 ユニット、e2e、および統合テストについても同じことが言えます。CIは、追加の構成を処理しなくても、すべてのテストを起動できます。
- アトミックコミットを使用してグローバル機能を簡単にリファクタリングします。 リポジトリごとにプルリクエストを実行して、変更をビルドする順序を特定する代わりに、対象の機能に関連するすべてのコミットを含むアトミックプルリクエストを作成する必要があります。
- 簡素化されたパッケージの公開。 共有コードを持つ別のパッケージに依存するパッケージ内に新しい機能を実装する場合は、1つのコマンドで実装できます。 これは、いくつかの追加の構成が必要な機能です。これについては、この記事のツールレビューの部分で後で説明します。 現在、Lerna、Yarn Workspaces、Bazelなどの豊富なツールがあります。
- より簡単な依存関係管理。 package.jsonは1つだけです。 依存関係を更新するたびに、各リポジトリに依存関係を再インストールする必要はありません。
- 共有パッケージを分離したまま、コードを再利用します。 Monorepoを使用すると、パッケージを互いに分離したまま、他のパッケージからパッケージを再利用できます。 リモートパッケージへの参照を使用して、単一のエントリポイントを介してそれらを使用できます。 ローカルバージョンを使用するには、ローカルシンボリックリンクを使用できます。 この機能は、bashスクリプトを介して、またはLernaやYarnなどの追加ツールを導入することで実装できます。
モノレポのデメリット:
- アプリの一部にのみアクセスを制限する方法はありません。 残念ながら、モノリポジトリの一部のみを共有することはできません。コードベース全体へのアクセスを許可する必要があり、セキュリティ上の問題が発生する可能性があります。
大規模なプロジェクトで作業する場合のGitのパフォーマンスが低い。 この問題は、100万を超えるコミットと数百の開発者が同じリポジトリで毎日同時に作業を行っている巨大なアプリケーションでのみ発生し始めます。 Gitは有向非巡回グラフ(DAG)を使用してプロジェクトの履歴を表すため、これは特に厄介になります。 コミット数が多いと、履歴が深くなるにつれて、グラフをたどるコマンドが遅くなる可能性があります。 参照の数(つまり、ブランチやタグ、不要になった参照を削除することで解決可能)と追跡されるファイルの量(および、重いファイルの問題は次の方法で解決できますが、その重み)により、パフォーマンスも低下します。 Git LFS)。
注:現在、FacebookはMercurialにパッチを適用することで、VCSのスケーラビリティに関する問題を解決しようとしていますが、おそらくすぐに、これはそれほど大きな問題にはなりません。
- ビルド時間が長くなります。 1つの場所に多くのソースコードがあるため、すべてのPRを承認するために、CIがすべてを実行するのに時間がかかります。
ツールレビュー
モノリポジトリを管理するためのツールのセットは絶えず成長しており、現在、モノリポジトリのさまざまな構築システムのすべてで迷子になるのは非常に簡単です。 このリポジトリを使用すると、人気のあるソリューションをいつでも知ることができます。 しかし、今のところ、JavaScriptで現在頻繁に使用されているツールを簡単に見てみましょう。
- Bazelは、Googleのモノレポ指向のビルドシステムです。 Bazelの詳細:awesome-bazel
- Yarnは、ワークスペースを介したモノリポジトリをサポートするJavaScript依存関係管理ツールです。
- Lernaは、Yarn上に構築された複数のパッケージを使用してJavaScriptプロジェクトを管理するためのツールです。
ほとんどのツールは非常によく似たアプローチを使用していますが、微妙な違いがあります。

この記事のパート2はかなり大きなトピックであるため、Lernaワークフローと他のツールについて詳しく説明します。 とりあえず、中身の概要を見てみましょう。
レルナ
このツールは、セマンティックバージョンの処理、ワークフローの構築の設定、パッケージのプッシュなどに非常に役立ちます。Lernaの背後にある主なアイデアは、プロジェクトに、分離されたすべてのコード部分を含むパッケージフォルダーがあることです。 また、パッケージの他に、たとえばsrcフォルダーに配置できるメインアプリがあります。 Lernaのほとんどすべての操作は、単純なルールを介して機能します。つまり、すべてのパッケージを反復処理し、それらに対していくつかのアクションを実行します。たとえば、パッケージバージョンの増加、すべてのパッケージの依存関係の更新、すべてのパッケージのビルドなどです。
Lernaでは、パッケージの使用方法について2つのオプションがあります。
- それらをリモート(NPM)にプッシュせずに
- パッケージをリモートにプッシュする
最初のアプローチを使用している間、パッケージにローカル参照を使用することができ、基本的にそれらを解決するためにシンボリックリンクを気にする必要はありません。
ただし、2番目のアプローチを使用している場合は、リモートからパッケージをインポートする必要があります。 (たとえば、 import { something } from @yourcompanyname/packagename;
)。これは、パッケージのリモートバージョンを常に取得することを意味します。 ローカル開発の場合、 node_modules/
内にあるパッケージを使用する代わりに、フォルダーのルートにシンボリックリンクを作成して、バンドラーにローカルパッケージを解決させる必要があります。 そのため、Webpackまたはお気に入りのバンドラーを起動する前に、すべてのパッケージを自動的にリンクするlerna bootstrap
を起動する必要があります。
糸
Yarnは当初、モノリポジトリをサポートするために構築されたものではないNPMパッケージの依存関係マネージャーです。 しかし、バージョン1.0では、Yarn開発者はWorkspacesと呼ばれる機能をリリースしました。 リリース時にはそれほど安定していませんでしたが、しばらくすると本番プロジェクトで使用できるようになりました。
ワークスペースは基本的にパッケージであり、独自のpackage.jsonがあり、特定のビルドルールを含めることができます(たとえば、プロジェクトでTypeScriptを使用する場合は別のtsconfig.json )。 実際には、bashを使用してYarn Workspacesなしで管理でき、まったく同じセットアップが可能ですが、このツールは、パッケージごとの依存関係のインストールと更新のプロセスを容易にするのに役立ちます。
一目で、Yarnとそのワークスペースは、次の便利な機能を提供します。
- すべてのパッケージのルートにある単一の
node_modules
フォルダー。 たとえば、packages/package_a
とpackages/package_b
があり、独自のpackage.json
がある場合、すべての依存関係はルートにのみインストールされます。 これは、YarnとLernaの動作の違いの1つです。 - ローカルパッケージ開発を可能にする依存関係シンリンク。
- すべての依存関係に対する単一のロックファイル。
- 1つのパッケージのみの依存関係を再インストールする場合に備えて、依存関係の更新に焦点を当てています。 これは、
-focus
フラグを使用して実行できます。 - Lernaとの統合。 Yarnにすべてのインストール/シンボリックリンクを簡単に処理させ、Lernaに公開とバージョン管理を任せることができます。 これは、手間がかからず、操作が簡単なため、これまでで最も人気のあるセットアップです。
便利なリンク:
- 糸のワークスペース
- TypeScriptモノリポジトリプロジェクトを構築する方法
バゼル
Bazelは、多言語の依存関係を処理し、多くの最新言語(Java、JS、Go、C ++など)をサポートできる大規模アプリケーション用のビルドツールです。 ほとんどの場合、中小規模のJSアプリケーションにBazelを使用するのはやり過ぎですが、大規模な場合は、そのパフォーマンスのために多くのメリットが得られる可能性があります。
その性質上、Bazelは、ビルドルールとプロジェクトの依存関係の説明を含むファイルに基づいてプロジェクトビルドを可能にするMake、Gradle、Maven、およびその他のツールに似ています。 Bazelの同じファイルはBUILDと呼ばれ、Bazelプロジェクトのワークスペース内にあります。 BUILDファイルは、Pythonによく似た人間が読める高レベルのビルド言語であるStarlarkを使用します。
通常、 BUILDを扱うことはあまりありません。これは、Webで簡単に見つけることができ、すでに構成されて開発の準備ができている定型文がたくさんあるためです。 プロジェクトをビルドするときはいつでも、Bazelは基本的に次のことを行います。
- ターゲットに関連するBUILDファイルをロードします。
- 入力とその依存関係を分析し、指定されたビルドルールを適用して、アクショングラフを作成します。
- 最終的なビルド出力が生成されるまで、入力に対してビルドアクションを実行します。
便利なリンク:
- JavaScriptとBazel–JS用のBazelプロジェクトを最初から設定するためのドキュメント。
- BazelのJavaScriptおよびTypeScriptルール–JSのボイラープレート。
結論
モノレポは単なるツールです。 未来があるかどうかについては多くの議論がありますが、真実は、場合によっては、このツールがその役割を果たし、効率的な方法でそれを処理するということです。 過去数年の間に、このツールは進化し、柔軟性が大幅に向上し、多くの問題を克服し、構成の面で複雑なレイヤーを削除しました。
Gitのパフォーマンスの低下など、理解すべき問題はまだたくさんありますが、近い将来、これが解決されることを願っています。
アプリ用の堅牢なCI/CDパイプラインの構築方法を学びたい場合は、GitLabCIを使用して効果的な初期デプロイパイプラインを構築する方法をお勧めします。