Монорепозиторий с C#: организация кода на практике

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Монорепозиторий с 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, который собирает все проекты и

Блоги

Рекомендаций по книгам