跳到主要内容

マイクロサービスにおけるモノレポ 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つではない:チーム規模、組織構造、サービスの依存関係に基づいて適切な戦略を選択する

参考リンク