Add C# language conventions: one type per file, file-scoped namespaces, sealed/internal types, DTO as records, ban on object-valued dictionaries with the enforcing test, layering, naming and formatting rules.
102 lines
5.7 KiB
Markdown
102 lines
5.7 KiB
Markdown
# AGENTS.md — Założenia i standardy backendu (C#)
|
|
|
|
Dotyczy projektu `backend` oraz testów `tests/TarninaHub.Tests` (ASP.NET Core Web API, .NET 10).
|
|
|
|
## Komunikaty commitów
|
|
|
|
- Komunikaty commitów zapisujemy **wyłącznie w języku angielskim**.
|
|
- Zasada obowiązuje **w obu projektach**: repozytorium `DevHeyah` oraz `20_projects/TarninaHub`.
|
|
- Stosujemy konwencję Conventional Commits (typ: `feat`, `fix`, `docs`, `chore` itd.).
|
|
|
|
## Założenia języka C#
|
|
|
|
- **Platforma:** .NET 10 (`net10.0`), `Nullable enable`, `ImplicitUsings enable`. Nie wyłączaj nullowalności; unikaj operatora `!` (jeśli musisz — uzasadnij).
|
|
- **Jeden typ = jeden plik:** każdy typ najwyższego poziomu (klasa, `record`, interfejs, `enum`, `struct`) w osobnym pliku, nazwa pliku zgodna z nazwą typu. Nie zagnieżdżaj typów pomocniczych poza przypadkami prywatnych szczegółów implementacji.
|
|
- **Przestrzeń nazw:** wyłącznie file-scoped (`namespace X;`). Kolejność: `using` → `namespace` → typy. Grupuj `using` bez zbędnych duplikatów.
|
|
- **Typy:** klasy usług i kontrolerów oznaczaj `sealed`; modele wierszy i typy wewnętrzne jako `internal`. DTO to `public sealed record` (niemutowalne, pozycyjne).
|
|
- **Zakaz map z wartościami `object`:** w DTO, interfejsach, `Domain` i modelach `*Row` nie stosuj `Dictionary<string, object>` ani `IReadOnlyDictionary<string, object>`. Elastyczne dane (`content`, `payload`, `contact`, `media`, `attachments`) to mapy wartości tekstowych (`IReadOnlyDictionary<string, string?>`), materializowane do konkretnych `Dictionary<string, string?>` przed przekazaniem do `RawQuery`. Powód: błąd deserializacji CBOR (Dahomey) i nieobsługiwana serializacja typów interfejsowych. Egzekwuje test `NoObjectValuedDictionariesTests`.
|
|
- **Wzorce:** promuj `record`, pattern matching, wyrażenia (`=>`), kolekcje `[]`; unikaj `null`-owych kolekcji (inicjalizuj puste).
|
|
- **Warstwy:** `Domain` (reguły, enumy), `Application` (DTO, abstrakcje, mapowania), `Infrastructure` (persistence, integracje), `Controllers` (tylko HTTP). Zależności kieruj do wewnątrz (Infrastructure → Application → Domain).
|
|
- **Nazewnictwo:** typy/metody/właściwości `PascalCase`, parametry i zmienne lokalne `camelCase`, pola prywatne `_camelCase`, interfejsy `I...`, typy generyczne `T...`. Metody asynchroniczne z sufiksem `Async`.
|
|
- **Formatowanie:** zgodnie z `.editorconfig` (C#: 4 spacje, LF, final newline, bez trailing whitespace). Weryfikacja: `dotnet format whitespace --verify-no-changes TarninaHub.slnx` i `dotnet format style --verify-no-changes --severity warn TarninaHub.slnx`.
|
|
- **Zmienne:** preferuj `var`, gdy typ jest oczywisty; jawne typy przy niejasności.
|
|
|
|
## Język nazewnictwa
|
|
|
|
- Wszystkie nazwy zmiennych, parametrów, pól, metod, klas i plików są **wyłącznie w języku angielskim**.
|
|
- Zakaz używania polskich nazw w kodzie (np. `uzytkownik`, `zamowienie`), w tym w parametrach tras i modelach.
|
|
- Komentarze i dokumentacja opisowa mogą być w języku polskim, ale identyfikatory w kodzie — tylko po angielsku.
|
|
|
|
## Organizacja kontrolerów
|
|
|
|
- Każdy kontroler w osobnym pliku w katalogu `Controllers/`, nazwa pliku zgodna z nazwą klasy.
|
|
- Nazwa klasy: `<Resource>Controller` (np. `CustomersController`), dziedziczy po `ControllerBase`.
|
|
- Stosuj atrybut `[ApiController]` oraz jawne trasy `[Route("api/<resource>")]` (np. `[Route("api/health")]`) — nie używaj tokenu `[controller]`.
|
|
- Katalog `Controllers/` grupuje wyłącznie kontrolery — logika biznesowa trafia do serwisów.
|
|
|
|
## Konwencje akcji
|
|
|
|
- Metody akcji w czasie teraźniejszym, opisujące operację: `GetById`, `GetAll`, `Create`, `Update`, `Delete`.
|
|
- Używaj atrybutów HTTP: `[HttpGet]`, `[HttpGet("{id}")]`, `[HttpPost]`, `[HttpPut("{id}")]`, `[HttpDelete("{id}")]`.
|
|
- Parametry tras w `{camelCase}` (np. `{customerId}`), zgodnie z nazwami w kodzie.
|
|
- Zwracaj typy wyników `ActionResult<T>` lub `IActionResult` z jawnymi kodami statusu:
|
|
- `200 OK` — pobranie/zaktualizowanie,
|
|
- `201 Created` — utworzenie (`CreatedAtAction`),
|
|
- `204 NoContent` — usunięcie,
|
|
- `400 BadRequest`, `404 NotFound` — błędy.
|
|
- Kontroler nie zawiera logiki biznesowej ani dostępu do bazy — deleguje do serwisów/repozytoriów.
|
|
- Używaj wstrzykiwania zależności przez konstruktor (tylko `readonly` pola).
|
|
|
|
## Modele i DTO
|
|
|
|
- Wejście i wyjście kontrolera stanowią dedykowane DTO, nie modele encji.
|
|
- Nazwy DTO: `<Resource>Request`, `<Resource>Response`.
|
|
- Walidacja wejścia przez adnotacje (`[Required]`, `[StringLength]`, `[Range]`).
|
|
- Nie zwracaj encji bazy bezpośrednio z akcji.
|
|
|
|
## Asynchroniczność
|
|
|
|
- Wszystkie operacje I/O (baza, HTTP) jako `async` z sufiksem `Async` i zwrotem `Task<...>`.
|
|
- Akceptuj `CancellationToken cancellationToken` i przekazuj go dalej.
|
|
|
|
## Przykład
|
|
|
|
```csharp
|
|
[ApiController]
|
|
[Route("api/customers")]
|
|
public class CustomersController : ControllerBase
|
|
{
|
|
private readonly ICustomerService _customerService;
|
|
|
|
public CustomersController(ICustomerService customerService)
|
|
{
|
|
_customerService = customerService;
|
|
}
|
|
|
|
[HttpGet("{customerId:int}")]
|
|
public async Task<ActionResult<CustomerResponse>> GetById(
|
|
int customerId,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var customer = await _customerService.GetByIdAsync(customerId, cancellationToken);
|
|
|
|
return customer is null
|
|
? NotFound()
|
|
: Ok(customer);
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult<CustomerResponse>> Create(
|
|
CreateCustomerRequest request,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var createdCustomer = await _customerService.CreateAsync(request, cancellationToken);
|
|
|
|
return CreatedAtAction(
|
|
nameof(GetById),
|
|
new { customerId = createdCustomer.Id },
|
|
createdCustomer);
|
|
}
|
|
}
|
|
```
|