マイクロサービスにおけるモノレポ vs マルチレポ
概要
マイクロサービスアーキテクチャにおいて、ソースコードのリポジトリ戦略は開発効率・CI/CD・チーム間コラボレーションに大きな影響を与えます。主なアプローチとして モノレポ(Monorepo) と マルチレポ(Multi-repo / Polyrepo) があり、それぞれ異なるトレードオフがあります。
モノレポ(Monorepo)
すべてのマイクロサービスとライブラリを1つのリポジトリで管理するアプローチです。
my-platform/
├── services/
│ ├── OrderService/
│ │ ├── src/
│ │ ├── tests/
│ │ └── OrderService.csproj
│ ├── UserService/
│ │ ├── src/
│ │ ├── tests/
│ │ └── UserService.csproj
│ └── PaymentService/
│ ├── src/
│ ├── tests/
│ └── PaymentService.csproj
├── shared/
│ ├── Common.Contracts/
│ ├── Common.Infrastructure/
│ └── Common.Testing/
├── Directory.Build.props
├── Directory.Packages.props
└── MyPlatform.slnx
メリット
| 項目 | 説明 |
|---|---|
| コードの共有が容易 | 共通ライブラリやコントラクトをプロジェクト参照で直接利用できる |
| アトミックな変更 | 複数サービスにまたがる変更を1つのPRで実施できる |
| 統一されたバージョン管理 | パッケージバージョンを Directory.Packages.props で一元管理 |
| リファクタリングが容易 | API契約の変更を全サービスに一括適用できる |
| 一貫した開発体験 | コーディング規約、linting設定、CI設定を統一できる |
デメリット
| 項目 | 説明 |
|---|---|
| リポジトリの肥大化 | コードベースが大きくなり、クローンやビルドに時間がかかる |
| CIの複雑化 | 変更されたサービスのみをビルド・デプロイする仕組みが必要 |
| 権限管理が難しい | サービス単位での細かなアクセス制御が困難 |
| 独立したデプロイが困難になりがち | 意図せず密結合になるリスクがある |
| ツールへの依存 | 専用のビルドツール(Nx、Bazelなど)が必要になる場合がある |
マルチレポ(Multi-repo)
各マイクロサービスを独立したリポジトリで管理するアプローチです。
# 各リポジトリ
order-service/
├── src/
├── tests/
├── Dockerfile
├── OrderService.csproj
└── OrderService.sln
user-service/
├── src/
├── tests/
├── Dockerfile
├── UserService.csproj
└── UserService.sln
common-contracts/ # NuGetパッケージとして公開
├── src/
├── Common.Contracts.csproj
└── Common.Contracts.sln
メリット
| 項目 | 説明 |
|---|---|
| 独立したデプロイ | 各サービスを完全に独立してビルド・デプロイ可能 |
| 明確なオーナーシップ | チームごとにリポジトリを所有し、責任範囲が明確 |
| シンプルなCI/CD | 各リポジトリのCIパイプラインが小さくシンプル |
| 柔軟なアクセス制御 | リポジトリ単位で権限を設定可能 |
| 技術スタックの自由度 | サービスごとに異なるフレームワークやバージョンを選択可能 |
デメリット
| 項目 | 説明 |
|---|---|
| コード共有が面倒 | 共通ライブラリはNuGetパッケージ化が必要 |
| 横断的変更のコスト | 複数リポジトリへのPRが必要で、調整コストが高い |
| バージョンの不整合 | サービス間で依存パッケージのバージョンがばらつく |
| 重複コード | 共通処理が各リポジトリで重複しがち |
| 新規参画者のオンボーディング | どのリポジトリに何があるか把握しにくい |
比較一覧
| 観点 | モノレポ | マルチレポ |
|---|---|---|
| コード共有 | ✅ プロジェクト参照で容易 | ⚠️ NuGetパッケージ化が必要 |
| アトミックな変更 | ✅ 1 PRで完結 | ❌ 複数リポジトリで調整が必要 |
| 独立デプロイ | ⚠️ CI設定が複雑 | ✅ 自然に独立 |
| チームの自律性 | ⚠️ コンフリクトの可能性 | ✅ 完全に独立 |
| CI/CD の複雑さ | ⚠️ 影響範囲の検出が必要 | ✅ シンプル |
| オンボーディング | ✅ 全体を一箇所で把握 | ⚠️ 分散しがち |
| ツール要件 | ⚠️ 専用ツールが必要な場合あり | ✅ 標準ツールで十分 |
| リファクタリング | ✅ 一括変更が容易 | ❌ 調整コストが高い |
| リポジトリサイズ | ❌ 大きくなる | ✅ 小さく保てる |
| アクセス制御 | ❌ 制限が難しい | ✅ リポジトリ単位で制御可能 |
.NET におけるモノレポのベストプラクティス
1. Directory.Build.props による共通設定
リポジトリルートに Directory.Build.props を配置し、全プロジェクトに共通設定を適用します。
<!-- Directory.Build.props -->
<Project>
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
</Project>
2. Central Package Management (CPM)
Directory.Packages.props で NuGet パッケージのバージョンを一元管理します。
<!-- Directory.Packages.props -->
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="FluentValidation" Version="11.11.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
</ItemGroup>
</Project>
各プロジェクトではバージョンを省略できます。
<!-- OrderService.csproj -->
<ItemGroup>
<PackageReference Include="Serilog" />
<PackageReference Include="FluentValidation" />
</ItemGroup>
3. 変更影響範囲の検出(GitHub Actions)
変更されたサービスのみをビルド・デプロイする CI/CD パイプラインを構築します。
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
order-service: ${{ steps.changes.outputs.order-service }}
user-service: ${{ steps.changes.outputs.user-service }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
order-service:
- 'services/OrderService/**'
- 'shared/**'
user-service:
- 'services/UserService/**'
- 'shared/**'
build-order-service:
needs: detect-changes
if: needs.detect-changes.outputs.order-service == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- run: dotnet build services/OrderService/OrderService.csproj
- run: dotnet test services/OrderService/tests/
4. ソリューションファイルの活用
.NET 9 以降の .slnx 形式で管理し、サービスごとのフィルタリングも活用します。
<!-- MyPlatform.slnx (全体) -->
<Solution>
<Folder Name="/services/OrderService/">
<Project Path="services/OrderService/OrderService.csproj" />
<Project Path="services/OrderService/tests/OrderService.Tests.csproj" />
</Folder>
<Folder Name="/services/UserService/">
<Project Path="services/UserService/UserService.csproj" />
<Project Path="services/UserService/tests/UserService.Tests.csproj" />
</Folder>
<Folder Name="/shared/">
<Project Path="shared/Common.Contracts/Common.Contracts.csproj" />
<Project Path="shared/Common.Infrastructure/Common.Infrastructure.csproj" />
</Folder>
</Solution>
必要に応じてサービス単位のソリューションファイルも用意します。
<!-- services/OrderService/OrderService.slnx (個別開発用) -->
<Solution>
<Project Path="OrderService.csproj" />
<Project Path="tests/OrderService.Tests.csproj" />
<Project Path="../../shared/Common.Contracts/Common.Contracts.csproj" />
</Solution>
5. 共有ライブラリの設計指針
モノレポにおける共有ライブラリは以下の原則に従います。
shared/
├── Common.Contracts/ # DTOs、イベント定義(依存ライブラリなし)
├── Common.Infrastructure/ # 横断的関心事(ログ、認証、ヘルスチェック)
└── Common.Testing/ # テストヘルパー、カスタムファクトリ
- Contracts は依存ライブラリを持たないPOCOのみで構成する
- POCO(Plain Old CLR Object) とは、特定のフレームワークや基底クラスに依存しない、純粋なC#クラス(またはrecord)のことです。外部ライブラリへの参照や属性を持たないため、どのプロジェクトからも軽量に参照でき、シリアライズやバージョン管理のトラブルを回避できます
- Infrastructure は薄い抽象化レイヤーに留め、各サービスで上書き可能にする
- サービス固有のロジックを共有ライブラリに入れない
// ✅ 共有に適している: イベント契約
namespace Common.Contracts.Events;
public record OrderCreatedEvent(
Guid OrderId,
Guid CustomerId,
decimal TotalAmount,
DateTime CreatedAt);
// ❌ 共有に適さない: サービス固有のビジネスロジック
// namespace Common.Services;
// public class OrderPricingCalculator { ... }
.NET におけるマルチレポのベストプラクティス
1. NuGet パッケージによるコード共有
共通ライブラリを NuGet パッケージとして公開し、各サービスで利用します。
<!-- Common.Contracts.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<PackageId>MyCompany.Common.Contracts</PackageId>
<Version>2.1.0</Version>
<Description>Shared contracts and DTOs for microservices</Description>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
</Project>
GitHub Packages や Azure Artifacts をプライベート NuGet フィードとして活用します。
<!-- nuget.config -->
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="MyCompanyFeed"
value="https://pkgs.dev.azure.com/myorg/_packaging/myFeed/nuget/v3/index.json" />
</packageSources>
</configuration>
2. テンプレートリポジトリの活用
新しいマイクロサービスの立ち上げを標準化するため、テンプレートリポジトリを用意します。
service-template/
├── src/
│ ├── Program.cs
│ ├── appsettings.json
│ └── MyService.csproj
├── tests/
│ └── MyService.Tests.csproj
├── .github/
│ └── workflows/
│ └── ci.yml
├── Dockerfile
├── .editorconfig
├── nuget.config
└── README.md
dotnet new テンプレートとしてパッケージ化することも有効です。
dotnet new install MyCompany.ServiceTemplate
dotnet new myservice -n InventoryService
3. Contract Testing(契約テスト)
マルチレポでは、サービス間のAPIコントラクトが壊れないことを保証するために Contract Testing が重要です。
// Pact を利用したコンシューマ駆動テストの例
public class OrderServiceConsumerTests
{
private readonly IPactBuilderV4 _pactBuilder;
public OrderServiceConsumerTests()
{
var pact = Pact.V4("OrderService", "UserService", new PactConfig());
_pactBuilder = pact.WithHttpInteractions();
}
[Fact]
public async Task GetUser_ReturnsExpectedUser()
{
_pactBuilder
.UponReceiving("a request for user details")
.WithRequest(HttpMethod.Get, "/api/users/123")
.WillRespond()
.WithStatus(HttpStatusCode.OK)
.WithJsonBody(new
{
id = "123",
name = "John Doe",
email = "john@example.com"
});
await _pactBuilder.VerifyAsync(async ctx =>
{
var client = new HttpClient
{
BaseAddress = ctx.MockServerUri
};
var response = await client.GetAsync("/api/users/123");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
});
}
}
4. Renovate / Dependabot による依存関係の自動更新
マルチレポでは依存パッケージの管理が分散するため、自動更新ツールを導入します。
// renovate.json
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"nuget": {
"enabled": true
},
"packageRules": [
{
"matchPackagePatterns": ["MyCompany\\.Common\\..*"],
"automerge": true,
"automergeType": "pr"
}
]
}
ハイブリッドアプローチ
実際のプロジェクトでは、モノレポとマルチレポを組み合わせたハイブリッドアプローチが有効な場合もあります。
ハイブリッドパターンの例
| パターン | 説明 |
|---|---|
| プラットフォーム+ドメイン分離 | 共通基盤をモノレポ、ドメインサービスをマルチレポ |
| チーム単位のモノレポ | 各チームが所有するサービス群をモノレポで管理 |
| 共有ライブラリのみモノレポ | 共有コードをモノレポ、個別サービスはマルチレポ |
選定ガイドライン
モノレポが向いているケース
- 少人数(〜20人程度)で密に連携するチーム
- サービス間の依存関係が強く、同時に変更することが多い
- 共通ライブラリの変更頻度が高い
- 組織的に統一された開発規約やツールチェーンを重視する
- .NET Aspire を使って統一的にオーケストレーションしたい
マルチレポが向いているケース
- 大組織で複数の自律的なチームが存在する
- サービスが各チームで独立して開発・デプロイされる
- 異なる技術スタックやリリースサイクルが必要
- 厳密なアクセス制御やコンプライアンス要件がある
- 外部ベンダーやパートナーがサービスを開発する場合
意思決定フローチャート
まとめ
- モノレポ はコード共有とアトミックな変更に優れるが、CI/CDの複雑化とリポジトリ肥大化が課題
- マルチレポ はチームの自律性と独立デプロイに優れるが、横断的変更とバージョン管理が課題
- .NET では
Directory.Build.props、Central Package Management、.slnxなどの仕組みがモノレポ運用を強力に支援する - マルチレポでは NuGet パッケージ化、テンプレートリポジトリ、Contract Testing が鍵となる
- 正解は1つではない:チーム規模、組織構造、サービスの依存関係に基づいて適切な戦略を選択する