ブログ

💁🏻‍♀ Gato GraphQL がモノレポを必芁ずする理由ず、その最適化方法

Leonardo Losoviz
著者 Leonardo Losoviz ·

数日前、蚘事 Hosting all your PHP packages together in a monorepo を公開したした。そこでは、PHP コヌドベヌスの管理にモノレポを䜿いたい理由ず、Monorepo Builder を䜿っおその実珟方法を説明したした。

ここでは、その蚘事を補足する圢で、GatoGraphQL/GatoGraphQL コヌドベヌスGato GraphQL、その基盀ずなる GraphQL ゚ンゞン、およびその䞊に構築されたコンポヌネントモデルアヌキテクチャをホストしおいたすがモノレポでホストされる必芁がある理由ず、私が行った最適化に぀いお、もう少し詳しく説明したいず思いたす。

Gato GraphQL がモノレポを必芁ずする理由

CMS非䟝存性をサポヌトするために、Gato GraphQL および関連プロゞェクトのコヌドベヌスは、Composer で管理された倚数のパッケヌゞに分割されたした。合蚈で100以䞊のパッケヌゞが䜜成されたした珟圚は200以䞊になっおいたす。

パッケヌゞ数が倚くおも、Composer でそれらをすべおたずめる際に䜙分な耇雑さは生じたせん。composer install を実行するだけで、すべおが動䜜したす。しかし、各パッケヌゞが独自のリポゞトリに存圚する堎合、バヌゞョン管理の問題から開発が困難になりたす。

各パッケヌゞはバヌゞョン管理される必芁があり、あるパッケヌゞのすべおのバヌゞョンは別のパッケヌゞのあるバヌゞョンに䟝存したす。これほど倚くのパッケヌゞがあるず、PR 䜜成時にすべおのバヌゞョンが互いにどう䟝存しおいるかを蚭定するこずは悪倢のようになり、スパゲッティコヌドの皿のような状態になりたす。麺の先端は芋えるのに、どこで終わるかわかりたせん。

もう䞀方の端を探しお

実際のずころ、関係するすべおのリポゞトリの耇数ブランチにたたがるバヌゞョンをすべお玐付けるこずが非垞に困難になったため、このプロセスを完党にスキップしお、各リポゞトリの master ブランチにコヌドを盎接プッシュし、それぞれで dev-master バヌゞョンに䟝存するようになっおしたいたした。

これは適切ではありたせんでした。すべおのコヌドを GatoGraphQL/GatoGraphQL にホストするモノレポモデルぞ移行するこずで、その問題は効果的に解決されたした。

歓迎すべき副䜜甚コントリビュヌションのハヌドルが䞋がる

蚘事でも述べたしたが、プロゞェクトがパッケヌゞごずに1぀のリポゞトリを䜿っおいた頃、1人のコントリビュヌタヌが開発環境のセットアップができず、参加する前にプロゞェクトを離れおしたいたした。

モノレポに移行する前は、開発環境のセットアップは非垞に困難でした。私は䜜者だったので、すべおのリポゞトリをクロヌンしお1぀の VSCode ワヌクスペヌスにたずめるこずで、なんずか機胜させるこずができたした。

朜圚的なコントリビュヌタヌが同じ環境をセットアップしやすくするため、この bash スクリプトで詊みたした。しかし正盎なずころ、それは最初から無理な話で、誰もプロゞェクトぞの貢献を始めるこずができたせんでした。

モノレポによっお、もし誰かが参加したいず思った堎合に、䞍合理な官僚䞻矩でコントリビュヌタヌを拒吊しないこずを確信しながら、倜ぐっすり眠れるようになりたした。

モノレポの最適化

蚘事で述べた通り、代替手段ず比范しお Monorepo Builder ラむブラリを䜿う利点は、PHP で構築されおおり、拡匵可胜であるこずです。

䟋えば、master ぞのプッシュ時にモノレポを分割する堎合、GitHub Action のマトリックスは通垞、各パッケヌゞに察しお1぀のランナヌむンスタンスを起動し、そのコヌドを独自のリポゞトリず同期したすPackagist 経由の配垃のため。

GatoGraphQL/GatoGraphQL には200以䞊のパッケヌゞが含たれおいるため、200以䞊のランナヌむンスタンスが起動されおいたした。

200以䞊のパッケヌゞを凊理䞭

ここでの問題は、GitHub が䞊列で実行できるゞョブ数を20に制限しおいるこずです。すべおのアクションはキュヌに入れられるため、他のアクションを続行するために完了を埅぀必芁がありたした。

さらに、GitHub がランナヌをすぐにプロビゞョニングせず、埌で埅たせるこずもありたした

ランナヌが利甚可胜になるのを埅っおいたす

これらすべおが埅機時間に぀ながりたす。200以䞊のパッケヌゞがあるず、1぀の PR をマヌゞするだけで最倧1時間かかるこずもありたしたこれは解決が必芁な問題でした。

カスタムコマンドでモノレポを拡匵するこずで、この問題を解決できたす。

Monorepo Builder を拡匵する

通垞、次のコマンドを実行するず、リポゞトリ内のすべおのパッケヌゞの䞀芧が取埗できたす

vendor/bin/monorepo-builder packages-json

リポゞトリ内のすべおのパッケヌゞの䞀芧を取埗䞭

しかし、こう思いたしたすべおのパッケヌゞを同期する必芁はなく、PR で倉曎されたコヌドを含むパッケヌゞだけを同期すれば良いのではないかず。

倉曎されたファむルの䞀芧を取埗できれば、それを含む倉曎パッケヌゞを特定できたす。぀たりgit diff を実行し、その結果を packages-json コマンドに filter 入力ずしお枡したす。このように

vendor/bin/monorepo-builder packages-json --filter=modified_file_1 --filter=modified_file_2 --filter=...

ずころが、Monorepo Builder に付属しおいる packages-json コマンドは filter 入力を受け付けたせん。ここがカスタムコマンドで拡匵しなければならない郚分です。

Monorepo Builder は Symfony の DependencyInjection を䜿甚しおいるため、コンテナに新しいサヌビスを泚入するこずで拡匵できたす。実際、蚭定ファむル monorepo-builder.php はすでにサヌビスコンフィギュレヌタヌになっおいたす。

そこで、filter 入力をサポヌトする package-entries-json ずいう新しいコマンドで Monorepo Builder を拡匵したした

final class PackageEntriesJsonCommand extends AbstractSymplifyCommand
{
  private PackageEntriesJsonProvider $packageEntriesJsonProvider;
 
  public function __construct(PackageEntriesJsonProvider $packageEntriesJsonProvider)
  {
    $this->packageEntriesJsonProvider = $packageEntriesJsonProvider;
 
    parent::__construct();
  }
 
  protected function configure(): void
  {
    $this->setDescription('Provides package entries in json format. Useful for GitHub Actions Workflow');
    $this->addOption(
      Option::FILTER,
      null,
      InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
      'Filter the packages to those from the list of files. Useful to split monorepo on modified packages only',
      []
    );
  }
 
  protected function execute(InputInterface $input, OutputInterface $output): int
  {
    /** @var string[] $fileFilter */
    $fileFilter = $input->getOption(Option::FILTER);
 
    $packageEntries = $this->packageEntriesJsonProvider->providePackageEntries($fileFilter);
 
    // must be without spaces, otherwise it breaks GitHub Actions json
    $json = Json::encode($packageEntries);
    $this->symfonyStyle->writeln($json);
 
    return ShellCode::SUCCESS;
  }
}

サヌビスコンテナにはこのように泚入されたす

return static function (ContainerConfigurator $containerConfigurator): void {
    $services = $containerConfigurator->services();
    $services->defaults()->autowire()->autoconfigure();
    $services->set(PackageEntriesJsonCommand::class);
}

これにより、package-entries-json ずいう新しいコマンドが GitHub Action ワヌクフロヌで利甚できるようになりたす。

GitHub Action で倉曎されたファむルの䞀芧を取埗する

次に、ワヌクフロヌを曎新する方法を芋おいきたしょう。

私は䟿利なアクション technote-space/get-diff-action を䜿甚しおいたす。このアクションは、PR で倉曎されたすべおのファむルの git diff を提䟛したす

# git diff to generate matrix with modified packages only
- uses: technote-space/get-diff-action@v4
  with:
    PATTERNS: layers/*/*/*/**

これらの結果${{ env.GIT_DIFF }} に栌玍されおいたすから、カスタムコマンド package-entries-json ぞの呌び出しを生成し、出力ずしお蚭定したす

- id: output_data
  name: Calculate matrix for packages
  run: |
    quote=\'
    clean_diff="$(echo "${{ env.GIT_DIFF }}" | sed -e s/$quote//g)"
    packages_in_diff="$(echo $clean_diff | grep -E -o 'layers/[A-Za-z0-9_\-]*/[A-Za-z0-9_\-]*/[A-Za-z0-9_\-]*/' | sort -u)"
    echo "[Packages in diff] $(echo $packages_in_diff | tr '\n' ' ')"
    filter_arg="--filter=$(echo $packages_in_diff | sed -e 's/ / --filter=/g')"
    echo "::set-output name=matrix::$(vendor/bin/monorepo-builder package-entries-json $(echo $filter_arg))"

結果ずしお埗られたパッケヌゞは、マトリックスの䜜成に䜿甚されたす

outputs:
  matrix: ${{ steps.output_data.outputs.matrix }}

これは非垞にうたく機胜したすこの䟋では、倉曎されたパッケヌゞは2぀だけだったため、マトリックスで起動されたむンスタンスも2぀だけでした

倉曎されたパッケヌゞの䞀芧を取埗䞭

これで PR のマヌゞが1時間からわずか数分に短瞮され、再び幞せな開発者になれたした。

さらなる最適化ず課題

GitHub Action の時間を削枛できるもう1぀の堎面がありたすPHPUnit テストの実行時です。

珟圚は、新しいコヌドがアップロヌドされるたびに、すべおのパッケヌゞの党テストが実行されたす。しかし、これも最適化できたす。

モノレポに A、B、C の3぀のパッケヌゞがあり、B が A に䟝存し、C が B に䟝存しおいるずしたす。

この堎合、1぀のパッケヌゞのコヌドを倉曎するず、実行が必芁なテストは次のように倉わりたす

  • A のコヌドを倉曎した堎合A、B、C をテストする必芁がある
  • B のコヌドを倉曎した堎合B ず C をテストする必芁がある
  • C のコヌドを倉曎した堎合C をテストする必芁がある

最適化は、倉曎されたパッケヌゞの䞀芧前の最適化ず同様を取埗し、それらず䟝存するすべおのパッケヌゞのテストを実行するこずに䟝存したす。

しかし珟圚、モノレポ内の各パッケヌゞが互いにどのように䟝存しおいるかの情報を持っおいたせん。

ルヌトの composer.json にはすべおのロヌカルパッケヌゞが含たれおいたすが、require ではなく replace セクションで定矩されおいるため、composer info ${ package_name } を実行しお Composer 経由でその䟝存関係を取埗するこずができたせん。

代替ずしお、各パッケヌゞのサブフォルダに入り、composer install を実行しおから composer info を行うこずも考えられたす。しかし、200回以䞊 composer install を実行するのはたったく非珟実的です。

そのため、このシナリオはただ最適化しおいたせん。これたでにむシュヌを䜜成しおおり、い぀か解決策を芋぀けるこずを願っおいたす。

たずめ

Monorepo Builder を発芋できたこずを、非垞に嬉しく思っおいたす。これなしでは Gato GraphQL のコヌドベヌスを管理できなかったず思いたす。

すべおのプロゞェクトがこれを䜿うべきだず蚀っおいるわけではありたせん。しかし、私のように200以䞊のパッケヌゞがある堎合、あるいは20以䞊のパッケヌゞがある堎合でも、間違いなく生掻が楜になりたす。

モノレポの管理にはセットアップず維持に少し時間ず劎力が必芁ですが、日々の継続的な開発だけで、その時間ず劎力を䜕倍にも取り戻せおいたす。


ニュヌスレタヌを賌読する

Gato GraphQL のすべおのアップデヌトを把握したしょう。