Монорепозиторий с C#: организация кода на практике
В современной разработке на C# (.NET) всё чаще встаёт вопрос выбора между монорепозиторием (monorepo) и мультирепозиторием (multirepo). Монорепозиторий — это подход, при котором весь код нескольких проектов, библиотек и сервисов хранится в одном Git-репозитории. В этой статье мы разберём, как организовать монорепозиторий для C#-решений, рассмотрим практические примеры структуры, настройки сборки и управления зависимостями.
Преимущества монорепозитория для C#
Монорепозиторий особенно полезен, когда у вас есть несколько связанных проектов: веб-API, консольные приложения, библиотеки классов и тесты. Основные плюсы:
- Единая версия зависимостей — все проекты используют одну и ту же версию NuGet-пакетов и внутренних библиотек.
- Упрощённый рефакторинг — изменение API в одной библиотеке сразу видно во всех потребителях.
- Централизованная CI/CD — один pipeline для сборки, тестирования и деплоя всех компонентов.
- Снижение дублирования — общие модели, утилиты и конфигурации выносятся в shared-проекты.
Однако стоит учитывать и минусы: рост размера репозитория, увеличение времени клонирования (если не использовать sparse checkout), а также необходимость продуманной системы прав доступа.
Структура монорепозитория на C#
Рассмотрим типовую структуру монорепозитория для .NET-проекта. Предположим, у нас есть микросервисы AuthService, OrderService и общая библиотека Shared.
MyMonorepo/
├── .github/
│ └── workflows/
│ └── ci.yml
├── src/
│ ├── AuthService/
│ │ ├── AuthService.Api/
│ │ │ ├── Controllers/
│ │ │ ├── Program.cs
│ │ │ └── AuthService.Api.csproj
│ │ └── AuthService.Tests/
│ │ └── AuthService.Tests.csproj
│ ├── OrderService/
│ │ ├── OrderService.Api/
│ │ │ └── OrderService.Api.csproj
│ │ └── OrderService.Tests/
│ │ └── OrderService.Tests.csproj
│ └── Shared/
│ ├── Shared.Models/
│ │ └── Shared.Models.csproj
│ └── Shared.Utils/
│ └── Shared.Utils.csproj
├── tests/
│ └── IntegrationTests/
│ └── IntegrationTests.csproj
├── MyMonorepo.sln
├── Directory.Build.props
├── Directory.Packages.props
├── .gitignore
└── README.md
Ключевые элементы:
- src/ — основная папка с исходным кодом.
- tests/ — интеграционные тесты, если они не привязаны к конкретному сервису.
- Directory.Build.props — общие настройки MSBuild для всех проектов.
- Directory.Packages.props — централизованное управление версиями NuGet-пакетов (режим Central Package Management).
- .sln — файл решения, который объединяет все проекты.
Настройка централизованного управления зависимостями
В .NET 6+ появилась встроенная поддержка Central Package Management (CPM). Это идеально подходит для монорепозитория. Создайте файл Directory.Packages.props в корне:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.App" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageVersion Include="xunit" Version="2.7.0" />
</ItemGroup>
</Project>
Теперь в каждом .csproj достаточно указать только имя пакета без версии:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" />
</ItemGroup>
</Project>
Это гарантирует, что во всём монорепозитории используется одна и та же версия Entity Framework Core, и вы не столкнётесь с конфликтами при сборке.
Практический пример: общая библиотека и сервисы
Допустим, у нас есть общая модель пользователя в Shared.Models:
// Shared.Models/User.cs
namespace Shared.Models;
public class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
И сервис AuthService, который использует эту модель. Добавим ссылку на проект в AuthService.Api.csproj:
<ItemGroup>
<ProjectReference Include="..\..\Shared\Shared.Models\Shared.Models.csproj" />
</ItemGroup>
Теперь в контроллере мы можем напрямую использовать модель:
// AuthService.Api/Controllers/AuthController.cs
using Microsoft.AspNetCore.Mvc;
using Shared.Models;
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
[HttpPost("register")]
public IActionResult Register(User user)
{
// Логика регистрации
return Ok(new { Message = $"User {user.Name} registered" });
}
}
Если мы изменим модель User (например, добавим поле Phone), компилятор сразу укажет на все места, где нужно внести изменения — в том же репозитории. Это огромное преимущество перед мультирепозиторием, где пришлось бы синхронизировать версии пакетов.
Сборка и CI/CD для монорепозитория
Для автоматизации сборки можно использовать GitHub Actions. Пример простого pipeline, который собирает все проекты и