Monorepository mit C#: Code-Organisation in der Praxis
Ein Monorepository (Monorepo) ist ein Ansatz zur Verwaltung von Quellcode, bei dem alle Projekte eines Unternehmens oder Teams in einem einzigen Repository gespeichert werden. In der Welt von C# und .NET wird dieser Ansatz aufgrund der Vereinfachung der gemeinsamen Entwicklung, der Vereinheitlichung von Abhängigkeitsversionen und des zentralisierten CI/CD immer beliebter. Ohne eine ordnungsgemäße Code-Organisation kann ein Monorepository jedoch schnell im Chaos enden. In diesem Artikel behandeln wir praktische Techniken zur Organisation von C#-Code in einem Monorepository: von der Ordnerstruktur bis zur Build-Konfiguration.
1. Ordnerstruktur: Die Grundlage der Ordnung
Der erste Schritt zu einem erfolgreichen Monorepository ist eine durchdachte Verzeichnishierarchie. Es wird empfohlen, einer Konvention zu folgen, bei der jedes Projekt in einem eigenen Ordner liegt und gemeinsame Komponenten in src/shared ausgelagert werden. Beispiel einer Basisstruktur:
/monorepo├── src/│ ├── shared/│ │ ├── CommonLib/ # Gemeinsame Hilfsprogramme, DTOs, Schnittstellen│ │ └── LoggingLib/ # Protokollierung│ ├── services/│ │ ├── AuthService/ # Authentifizierungs-Mikroservice│ │ └── OrderService/ # Bestell-Mikroservice│ └── apps/│ ├── WebApi/ # Haupt-Webanwendung│ └── ConsoleApp/ # Konsolentool├── tests/│ ├── unit/│ │ ├── CommonLib.Tests/│ │ └── AuthService.Tests/│ └── integration/│ └── IntegrationTests/├── docs/├── scripts/├── .gitignore├── Directory.Build.props└── global.jsonWichtige Punkte:
- src/ — Quellcode aller Projekte.
- shared/ — Bibliotheken, die von mehreren Projekten verwendet werden.
- services/ — Mikroservices oder Module.
- apps/ — Endanwendungen (Web API, Console, Blazor).
- tests/ — Tests, isoliert vom Quellcode.
Eine solche Struktur erleichtert das Auffinden von Code, vermeidet zyklische Abhängigkeiten und vereinfacht die CI/CD-Konfiguration.
2. Projektverwaltung mit Directory.Build.props
In einem Monorepository besteht oft die Notwendigkeit, gemeinsame Einstellungen für alle Projekte festzulegen: .NET-Versionen, gemeinsame Pakete, Code-Analyse-Regeln. Anstatt diese in jeder .csproj-Datei zu duplizieren, verwenden Sie die Datei Directory.Build.props im Repository-Stammverzeichnis. Beispiel:
<Project> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> </PropertyGroup>
<ItemGroup> <PackageReference Include="SonarAnalyzer.CSharp" Version="9.*" /> </ItemGroup></Project>Jedes untergeordnete Projekt erbt nun automatisch diese Einstellungen. Wenn Sie diese für ein bestimmtes Projekt überschreiben müssen, fügen Sie einfach die Eigenschaften in dessen .csproj hinzu – diese haben Vorrang.
Zusätzlich können Sie Directory.Build.targets für gemeinsame Build-Ziele erstellen (z. B. Kopieren von Dateien nach dem Build).
3. Abhängigkeitsverwaltung: Zentralisierter Ansatz
Eines der Hauptprobleme eines Monorepositorys sind Paketversionskonflikte. In .NET 8 wurde die integrierte Unterstützung für die zentralisierte Paketverwaltung (Central Package Management, CPM) eingeführt. Erstellen Sie dazu die Datei Directory.Packages.props im Stammverzeichnis:
<Project> <PropertyGroup> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> </PropertyGroup> <ItemGroup> <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> <PackageVersion Include="Serilog" Version="3.1.1" /&g