こんにちは!ソウゾウのSoftware Engineerの@ogataka50です。連載:「メルカリShops」プレオープンまでの開発の裏側の9日目を担当させていただきます。
1日目の記事「メルカリShops の技術スタックと、その選定理由」にあった通りメルカリShopsではMonorepoで開発を行なっています。この記事では主にSoftware Engineerとして、DX (Developer eXperience)の観点からPJ初期からMonorepo上で開発を進めていった中で経験したこと感じたことを実際に起きた出来事とともに共有したいと思います。
Monorepoとは
Monorepoとは単一のリポジトリに特定のプロジェクトのコードをすべて含めるパターンです。
下記のような利点があると言われています。
- コードの可視性
- 統一されたバージョン管理, Single Source of Truth
- 広範なコード共有と再利用
- シンプルな依存関係の管理
- 大規模なリファクタリングが一度に行うことができる
- 柔軟なチーム境界とコードオーナーシップ
プロジェクトに関わるすべてのコードが含まれているため、その1リポジトリを取得すればプロジェクトのすべてを把握することができます。またインターフェースの変更なども呼び出し元、先を一度に修正することが可能なため大規模なリファクタリングが可能です。リポジトリによる境界がないため、組織変更によるチーム、オーナーシップの変更にも柔軟に対応できるとされています。
メルカリShopsにおけるMonorepo
メルカリShopsのリポジトリ構成は下記のようになっています。
各言語ごとにルートディレクトリが分割されており、それらの配下にそれぞれのapplication codeがあります。BackendとなるMicroservicesにはGoを採用しており、BFF (Backend for Frontend),
FrontendにはTypeScriptを採用しているため、Go以下にMicroservices群、typescript以下にGraphQL, Web Frontendなどが配置されています。
.
├── containers
├── docs
│ ├── adr
│ ├── erd
│ └── sequence
├── go
│ ├── pkg
│ ├── services
│ │ ├── serviceA
│ │ ├── serviceB
│ │ └── serviceC
│ └── tools
├── proto
├── python
│ ├── libs
│ ├── projects
│ │ ├── projectA
│ │ └── projectB
│ └── tools
├── resources
├── terraform
└── typescript
├── apps
│ ├── graphql
│ └── web
├── libs
└── tools
Monorepoでの開発で起きた出来事
ここからは実際に私がMonorepoでの開発中に体験した出来事をもとに事例の共有をしていきたいと思います。
前提として私のこれまでの経験は下記になります。
- Monorepo初体験
- GraphQL初体験
- キャリアのほとんどはBackend Engineer(PHP/Go)
- Frontendは数年に一度社内向けTool開発で触ることがある程度
- TypeScript -> 型のあるJavaScriptらしいぐらいな認識
メルカリShops開発初期の風景
私がSouzohにjoinしたのは設立とほぼ同時の2021/2/1でした。
その時点ではメルカリShopsのコンセプトがある程度固まり、そこから実際の仕様に落とし込む段階で、開発面としては基本的な技術スタックが決まっており、各コンポーネントでサンプルとなる仮実装のみがあるという状態だったと思います。エンジニアの人数としても当時は全体で5人程度の規模感でした。
そこから仕様を詰めつつ、私はコアドメインの1つのMicroservicesを実装し始めました。ある程度実装した後に困ったことが起きます。次の段階としてBFFの実装を進めたいのですが、BFFであるGraphQL ServerはTypeScriptで書かれています。前述の通りGraphQL, TypeScriptは完全に素人だったのですが、周りを見回しても自分以上に忙しそうな状態(加えてこの時点ではBFFをメインに開発するエンジニアはいない状態でした)。
なるほど?これは逃げ切れんな…と悟り、数日かけてGraphQL, TypeScriptの初歩のキャッチアップと既存コードのcommit logをたどりどのように実装されていったかを確認していきました。それらを経て手探りで実装を行い、code review & try & errorを繰り返しどうにかGraphQLの実装していきました。
Monorepoが後押しするカルチャー、カルチャーが後押しするMonorepo
正直この時点では単に人がいない、 待っていても進まないのでやるしかなかったという認識しかしていませんでした。しかしこのような開発スタイルはある程度エンジニアが増えた後になった今でも続いています。それも元々広範囲に知識があった特定のエンジニアだけというわけではない、ほとんどのエンジニアが特定の技術に制限せず開発を進めています。
Backend Engineer、Frontend Engineer、Client Engineer, Site Reliability Engineerが各々の得意分野に軸足を置きつつ必要に応じて半歩隣のドメイン、半歩隣の技術領域も含め、率先して問題解決をしています。このような動きは「越境」と表現されるように一般的にはハードルが高いものなはずです。なぜこのようなことが起きたのか不思議に感じていましたが、私の実感としてはMonorepoであることがもっとも大きく影響していると感じています(※専門的な技術を持つスペシャリストのような動きをするEngineerもいます)。
Monorepoではプロジェクトに関するすべてのコード、変更とそのPull Requestを自然に把握することができます。インクリメンタルなトラッキングができることはプロジェクト全体の理解を大きく促進します。またリポジトリの単位がチーム/責任範囲となることが多いですが、Monorepoでは1リポジトリなので、プロジェクト(=リポジトリ)全体が責任範囲という認識が自然と醸成されるように思います。
また「メルカリShopsの開発を支える組織」にもあった通りSouzohでは「全員ソフトウェアエンジニア」というカルチャーを目指しています。役割に制限を設けず問題解決に集中することを理想像としています。
Monorepoがもつコードの可視性、柔軟なチーム境界と、Souzohが目指す「全員ソフトウェアエンジニア」という制限を設けず、コトに向かうというカルチャーが相互に作用した結果、上述したような各々エンジニアが境界を定めず問題解決をするというスタイルが起きたのだと思っています。どちらかが欠けていたら上述したことは起こらなかったように感じます。
リリース直前QA中でのコアドメインの状態管理変更
もう1つ個人的に印象的だったケースとして、「リリース直前QA中でのコアドメインの状態管理変更」について記載します。
タイトルの通り、リリース直前のQAで発覚した不具合の修正対応する中でコアドメインの状態管理が適切ではないことに気づきました。必ずしも即時問題になるわけではなかったのですが、今後の運用を考慮すると修正が必要でした。正直当時の私としては面倒なものを見つけてしまった、一晩寝かせてからまた考えるか…と思ったのですが、一晩寝かせても修正すべきという気持ちだったので、なるほど?これは逃げ切れんな…と悟り修正対応をすることにしました。
わかりやすくするため、商品を表す「Product」の「Status」の修正を行うと仮定します。
Product.Statusの修正自体は軽微ですが、ProductはメルカリShopsのコアドメインの1つのため、Microservices, BFF, Frontendなど広範囲で参照されており、影響範囲は広範囲に及んでいました。
まずは修正概要をまとめ、チームに共有し修正を始めました。
当初、個人的にはざっくり1週間~2週間程度はかかるのではと予想していました。影響範囲それぞれを主に見ているエンジニアとの修正方針の確認や、スケジュール調整が必要だと考えたからです。
一先ず私でできる範囲で修正を始めてみると、Monorepoなので修正漏れなどの心配もなく、Microservices, BFF, Frontendの参照先を変更し、Pull Requestでレビュー + 既存データのmigrationを行い対応完了することができました。問題に気づいてから2,3日で対応完了することができたのが個人的に大きな驚きでした。
仮にリポジトリが分かれていると対象のチームに修正を依頼 -> 修正完了まで後方互換性を維持などが必要なケースもあると思いますが、Monorepoのため、参照先もまとめて修正することができ、利点の1つである「大規模なリファクタリングを一度に行うことができる」ということを体感できた出来事でした
(これはあくまでリリース前の開発中だったためで、仮にリリース後の場合はMicroservicesのdeploy順などを考慮する必要があります)。
Monorepoで気がかりなこと
現時点ではMonorepoでの利点を多く感じていますが、気がかりなことも記載します。
誰でもできる ≒ 誰かがやってくれる
上述した通り誰もが制限なく、開発できるというのは大きな利点ですが、ひとたびオーナーシップが良からぬ方向に向かうと、「誰かがやってくれる」という裏目になりえることがあると感じる時があります。とはいえ領域を限定してオーナーシップを明確化するというのも違うと思うのでよきバランスを維持できればと思います。
コードベースの保守性
誰でも必要によって広範囲にコードの修正を行うので、図らずも設計思想に沿っていない修正などをしてしまうことが起こり得えます。現在は開発者数も顔がわかる範囲の人数なのでcode reviewでコミュニケーションをとるなどができていますが、今後組織規模が大きくなった時にどうなっていくかは気になる点です。
このようなことを回避するためにこれまで以上にdesign docなどのdocumentationが重要になってくると感じています。
Deploy時の依存関係
仮にあるMicroserviceのインターフェースをリファクタリングしたいとして、Monorepoだと参照先のコードもまとめて1つのPull Requestとしてある種アトミックに変更できますが、Microservices であるがため、アトミックにdeployすることはできず、依存によるDeploy順の考慮や、場合によっては一定期間の後方互換性のサポートなどが必要になるケースが起こり得るので、何もかも一度に変更できるとは限りません。
まとめ
Monorepoでの開発中に起きたこと感じたことを紹介しました。
現時点ではMonorepoの恩恵を受けてとても自由に開発をできていると感じます。それと同時にこの自由さが開発組織の規模が大きくなっても維持できるか、今後どのような問題が起こるかはある種楽しみでもあります。
そのような問題に直面するためには、なによりビジネスとしてメルカリShopsを成長させなければいけません。ということでソウゾウでは現在全方面でエンジニアを募集中です! もし上記のような環境で興味があればこちらよりご連絡いただければと思います。
またカジュアルに話だけでも聞いてみたいという方もこちらの申し込みフォームよりご連絡ください!
また、2021/08/18 から 2021/09/28 にかけて「ソウゾウ TECH TALK」というイベントが開催されます。テーマを分け、技術的な知見を共有しあうことを目的とした勉強会です。興味のある方はぜひご参加ください!
明日は「NestJS を使った GraphQL Server の実装」というタイトルでSoftware Engineerの@mookjpさんが登場予定です。お楽しみに!