Монорепозиторий с C#: как организовать код в .NET проектах
Современная разработка на C# и .NET всё чаще сталкивается с выбором: хранить все связанные проекты в одном репозитории (монорепозиторий) или разделить их по отдельным репозиториям (мультирепозиторий). Монорепозиторий — это подход, при котором код нескольких приложений, библиотек, тестов и конфигураций хранится в одном Git-репозитории. Для больших корпоративных решений, микросервисов или библиотек NuGet этот подход может значительно упростить управление зависимостями, рефакторинг и развёртывание.
В этой статье мы разберём, как правильно организовать монорепозиторий с C# в экосистеме .NET, рассмотрим структуру папок, настройку проектов, CI/CD и лучшие практики.
1. Преимущества и недостатки монорепозитория в .NET
Прежде чем углубляться в технические детали, важно понять, когда монорепозиторий действительно полезен, а когда он может стать проблемой.
Преимущества:
- Единая версия зависимостей. Все проекты внутри репозитория используют одни и те же версии пакетов, что исключает конфликты версий.
- Упрощённый рефакторинг. Изменение общего интерфейса или API библиотеки сразу видно во всех проектах, которые его используют.
- Целостность коммитов. Атомарные коммиты, которые изменяют и библиотеку, и приложение, работают без временных разрывов.
- Общие настройки. Один файл
Directory.Build.propsзадаёт настройки компиляции, анализа кода и версии для всех проектов.
Недостатки:
- Рост размера репозитория. Со временем клонирование и история могут стать очень большими.
- Сложность CI/CD. Требуется умная система сборки, чтобы не пересобирать всё при каждом коммите.
- Права доступа. В одном репозитории сложнее разграничить доступ для разных команд.
Для .NET-проектов монорепозиторий особенно удобен, когда у вас есть общая библиотека классов (например, Domain или Common), несколько микросервисов или веб-API, которые зависят от неё.
2. Структура монорепозитория для C# (.NET)
Правильная структура папок — основа успешного монорепозитория. Рассмотрим типовую организацию для решения, включающего несколько микросервисов, библиотеку и тесты.
MyApp/
├── .github/
│ └── workflows/
│ └── ci.yml
├── src/
│ ├── MyApp.Common/ # общая библиотека
│ │ ├── MyApp.Common.csproj
│ │ └── ...
│ ├── MyApp.Api.Orders/ # микросервис для заказов
│ │ ├── MyApp.Api.Orders.csproj
│ │ └── ...
│ └── MyApp.Api.Users/ # микросервис для пользователей
│ ├── MyApp.Api.Users.csproj
│ └── ...
├── tests/
│ ├── MyApp.Common.Tests/
│ │ ├── MyApp.Common.Tests.csproj
│ │ └── ...
│ └── MyApp.Api.Orders.Tests/
│ ├── MyApp.Api.Orders.Tests.csproj
│ └── ...
├── Directory.Build.props # общие настройки для всех проектов
├── MyApp.sln # решение для Visual Studio
└── README.md
Ключевые моменты:
- Все проекты находятся в папке
src/, тесты — вtests/. Это стандарт, принятый в .NET сообществе. - Файл
Directory.Build.propsзадаёт общие свойства:TargetFramework,Nullable,ImplicitUsings, версии пакетов и т.д. - Один SLN-файл упрощает работу в IDE, но при большом количестве проектов можно разбить на несколько решений.
Пример Directory.Build.props:
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
Этот файл автоматически применяется ко всем .csproj в папке и подпапках репозитория.
3. Настройка CI/CD для монорепозитория C#
Одна из главных сложностей — настроить сборку так, чтобы она пересобирала только изменённые проекты и их зависимости. Для этого можно использовать dotnet test с фильтрацией или инструменты вроде Nuke Build или Microsoft.Build.Traversal.
Пример GitHub Actions workflow (упрощённый):
name: .NET CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Restore
run: dotnet restore MyApp.sln
- name: Build
run: dotnet build MyApp.sln --configuration Release
- name: Test
run: dotnet test MyApp.sln --configuration Release --verbosity normal
Для более продвинутого сценария (сборка только изменённых проектов) можно использовать dotnet test --filter или скрипты, которые анализируют изменённые файлы через git diff. Например, если изменилась только библиотека MyApp.Common, то тесты для неё запускаются, а для остальных — нет.
Совет: используйте пакет Microsoft.Build.Traversal
Этот пакет позволяет создать единый проект, который ссылается на все остальные проекты в решении. Тогда команда dotnet build будет собирать их в правильном порядке.