C# / .NET/04core10 min

Struktura solution i projektów

Dobrze podzielona solucja .NET to taka, w której zależności płyną w jedną stronę, a warstwa domenowa nie wie nic o bazie ani o HTTP. Ta lekcja pokazuje, jak strukturyzujemy projekty w ProfessNet.

Podział na projekty

Typowa solucja serwisu dzieli się na warstwy jako osobne projekty:

Zeus.Inventory.sln
├── src/
│   ├── Zeus.Inventory.Api/          # kontrolery, Program.cs, DI
│   ├── Zeus.Inventory.Application/  # logika, serwisy, interfejsy
│   ├── Zeus.Inventory.Domain/       # encje, reguły domenowe (zero zależności)
│   └── Zeus.Inventory.Infrastructure/ # EF Core, klienci HTTP, repozytoria
└── tests/
    ├── Zeus.Inventory.UnitTests/
    └── Zeus.Inventory.IntegrationTests/

Kierunek zależności

Zależności wskazują do wewnątrz, ku domenie. Domena nie zależy od niczego.

Api  →  Application  →  Domain
              ↑
       Infrastructure

Standard ProfessNet: projekt Domain nie ma referencji do EF Core, ASP.NET ani żadnej infrastruktury. Reguły biznesowe są niezależne od technologii dostępu do danych. Interfejsy repozytoriów żyją w Application, a ich implementacje w Infrastructure.

Centralne zarządzanie pakietami

Wersje pakietów NuGet trzymamy w jednym pliku Directory.Packages.props, żeby wszystkie projekty używały tych samych wersji.

<!-- Directory.Packages.props -->
<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
    <PackageVersion Include="Serilog.AspNetCore" Version="8.0.0" />
  </ItemGroup>
</Project>

W .csproj podajesz już tylko nazwę pakietu, bez wersji:

<ItemGroup>
  <PackageReference Include="Microsoft.EntityFrameworkCore" />
</ItemGroup>

Wspólne ustawienia projektów

Powtarzalne ustawienia (Nullable, LangVersion, warnings as errors) wynosimy do Directory.Build.props w korzeniu repo.

<!-- Directory.Build.props -->
<Project>
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>
</Project>

Standard ProfessNet: Nullable i TreatWarningsAsErrors włączone globalnie w Directory.Build.props. Żaden projekt nie obniża tych ustawień bez uzasadnienia w PR.

ProjektZnaNie zna
DomainnicEF, HTTP, DI
ApplicationDomainkonkretnej bazy/HTTP
InfrastructureDomain, Applicationkontrolerów
Apiwszystkie

Wskazówka: testy jednostkowe celują w Application i Domain (bez infrastruktury), a testy integracyjne w Infrastructure i Api z prawdziwą bazą testową.


Warstwy jako osobne projekty, zależności do wewnątrz, wersje i ustawienia scentralizowane. Taka solucja rośnie bez zamieniania się w plątaninę referencji, a domena pozostaje czysta i testowalna.