Dev Playbook
Conventions

.NET Conventions

C# and .NET specific coding standards.

Architecture: Clean Architecture

Solution/
├── src/
│   ├── Domain/           ← Entities, value objects, enums, domain events
│   ├── Application/      ← Use cases, CQRS handlers, DTOs, interfaces
│   ├── Infrastructure/   ← EF Core, external services, repositories
│   └── Api/              ← Controllers, middleware, DI configuration
└── tests/
    ├── UnitTests/
    └── IntegrationTests/

Dependency rule: Inner layers never reference outer layers. Domain has zero dependencies.

Patterns

PatternToolNotes
CQRSMediatRSeparate Commands and Queries
ValidationFluentValidationOn every request DTO
Object MappingMapsterNOT AutoMapper (commercial license)
ORMEF CoreRepository pattern on top
LoggingSerilogStructured logging to Seq
Background JobsHangfireFor long-running tasks
Health ChecksAspNetCore.HealthChecksFor every external dependency
OpenAPIMicrosoft.AspNetCore.OpenApi + ScalarNOT Swashbuckle (deprecated)

Naming

ElementConventionExample
Public membersPascalCaseGetCourseById()
Private fields_camelCase_courseRepository
ParameterscamelCasecourseId
InterfacesI-prefixICourseRepository
Async methodsAsync suffixGetCourseByIdAsync()
ConstantsPascalCaseMaxRetryCount
FilesMatch class nameCourseService.cs

Async Rules

  • Async all the way down — No .Result, no .Wait(), no Task.Run() for IO
  • Always pass CancellationToken in async method signatures
  • Use ConfigureAwait(false) in library code only, not in ASP.NET controllers

CQRS Structure

Application/
├── Features/
│   └── Courses/
│       ├── Commands/
│       │   ├── CreateCourse/
│       │   │   ├── CreateCourseCommand.cs
│       │   │   ├── CreateCourseCommandHandler.cs
│       │   │   └── CreateCourseCommandValidator.cs
│       │   └── UpdateCourse/
│       │       └── ...
│       └── Queries/
│           └── GetCourseById/
│               ├── GetCourseByIdQuery.cs
│               ├── GetCourseByIdQueryHandler.cs
│               └── CourseDto.cs

Error Handling

  • Use Result pattern or custom exceptions — not bare try/catch everywhere
  • Domain exceptions: DomainException, NotFoundException, ConflictException
  • Global exception handler middleware maps exceptions to HTTP status codes
  • Never expose stack traces or internal details in API responses

EF Core

  • Fluent API configuration in separate EntityTypeConfiguration classes
  • Always use migrations, never EnsureCreated()
  • Use AsNoTracking() for read-only queries
  • Avoid lazy loading — use explicit Include() or projection

Testing

  • xUnit for test framework
  • NSubstitute or Moq for mocking
  • TestContainers for integration tests (real PostgreSQL, Redis)
  • Test the service/handler layer, not the framework
  • Naming: MethodName_StateUnderTest_ExpectedBehavior
[Fact]
public async Task CreateCourse_WithValidData_ReturnsCourseId()

On this page