- Runtime: .NET 9 — use .NET 9 language features and APIs wherever applicable.
- Architecture: Clean Architecture — Domain → Application → Infrastructure → API
- Database: PostgreSQL 16 (via Npgsql + EF Core)
- Messaging: MassTransit + RabbitMQ
- Caching: Redis (StackExchange.Redis)
- Search: Elasticsearch 8 (
Elastic.Clients.Elasticsearch) - Logging: Serilog → Console + Elasticsearch sink (enriched with
TraceId+SpanIdviaSerilog.Enrichers.Span) - Log Visualization: Kibana 8 (dashboard for Elasticsearch logs + APM UI)
- Tracing: OpenTelemetry → Elastic APM Server 8 (OTLP/HTTP) — auto-instruments ASP.NET Core, EF Core, Redis, HttpClient, MassTransit
- ORM: Entity Framework Core 9
- CQRS: Mediator (Martinothamar) — MIT-licensed, source-generator based, MediatR API compatible
- Validation: FluentValidation
- Mapping: Mapster
- API Docs: Scalar (OpenAPI)
- API Versioning: Asp.Versioning.Mvc
- Health Checks: AspNetCore.HealthChecks.UI (NpgSql, Redis sink adapters included)
- Resilience: Polly v8
- Auth: ASP.NET Core Identity + JWT Bearer + API Key (custom scheme)
- Object Storage: MinIO (S3-compatible) — presigned URL pattern; two clients: management (
minio:9000) + CDN presigned (CdnBaseUrl) - Reverse Proxy / CDN: Nginx —
:80API only (backend-only),:9080MinIO CDN withproxy_cache - Testing: xUnit + FluentAssertions + NSubstitute + Testcontainers
- Domain → no project-level dependencies on other layers. May reference
Mediator.Abstractions(forINotificationdomain events) andMicrosoft.Extensions.Identity.Core(for identity primitives). Never reference Application, Infrastructure, or API. - Application → depends only on Domain. No EF Core, Redis, or RabbitMQ references.
- Infrastructure → depends on Application + Domain. Implements all interfaces.
- API → depends on Application + Infrastructure (DI wiring only). Controllers are thin dispatchers.
src/
├── Domain/
│ ├── Common/ AggregateRoot, BaseEntity, IDomainEvent
│ ├── Entities/
│ ├── Enums/
│ ├── Events/
│ ├── Exceptions/
│ ├── Repositories/ Interfaces: IRepository, IUnitOfWork, etc.
│ └── ValueObjects/
├── Application/
│ ├── Common/
│ │ ├── Behaviors/ Pipeline behaviors: ValidationBehavior, LoggingBehavior, etc.
│ │ ├── Exceptions/
│ │ ├── Interfaces/ ICacheService, ICurrentUser, IEmailService, etc.
│ │ └── Models/ Result<T>, Error, PagedList<T>, ApiResponse<T>
│ └── Features/
│ └── {FeatureName}/
│ ├── Commands/
│ ├── Queries/
│ ├── EventHandlers/
│ └── DTOs/
├── Infrastructure/
│ ├── Caching/ Redis implementation of ICacheService
│ ├── Identity/ JWT generation, API Key validation, user management
│ ├── Messaging/
│ │ ├── Consumers/ MassTransit consumers
│ │ ├── Events/ Integration event definitions
│ │ └── Publishers/ IEventPublisher implementations
│ ├── Persistence/
│ │ ├── Configurations/ EF Core IEntityTypeConfiguration<T> classes
│ │ ├── Migrations/
│ │ ├── Repositories/ IRepository implementations
│ │ └── Seeders/ Role, ApiKey and other startup data seeders
│ ├── Search/ Elasticsearch client wrapper
│ ├── Settings/ Typed settings classes (JwtSettings, StorageSettings, etc.)
│ └── Storage/ MinIO IStorageService implementation (dual-client pattern)
└── API/
├── Common/ ApiResponse<T> envelope
├── Controllers/ Thin dispatchers — send command/query, return ApiResponse
├── Extensions/ IServiceCollection extensions, ClaimsPrincipal extensions
├── Filters/ Action filters
└── Middleware/ CorrelationIdMiddleware, ApiKeyMiddleware, GlobalExceptionHandler
tests/
├── CleanArcStack.Unit.Tests/
│ ├── Auth/
│ └── Common/
└── CleanArcStack.Integration.Tests/
└── Infrastructure/ Testcontainers-based infrastructure setup
| Artifact | Pattern | Example |
|---|---|---|
| Command | {Action}{Entity}Command |
CreateOrderCommand |
| Command Handler | {Action}{Entity}CommandHandler |
CreateOrderCommandHandler |
| Query | {Get|List|Search}{Entity}Query |
GetOrderByIdQuery |
| Domain Event | {Entity}{Action}DomainEvent |
OrderCreatedDomainEvent |
| Integration Event | {Entity}{Action}IntegrationEvent |
OrderCreatedIntegrationEvent |
| Consumer | {Entity}{Action}IntegrationEventConsumer |
OrderCreatedIntegrationEventConsumer |
| Repository Interface | I{Entity}Repository |
IOrderRepository |
| Response DTO | {Entity}Response |
OrderResponse |
| Request DTO | {Action}{Entity}Request |
CreateOrderRequest |
| Validator | {Command|Query}Validator |
CreateOrderCommandValidator |
| Strongly typed ID | {Entity}Id record |
public sealed record OrderId(Guid Value); |
sealedon all Commands, Queries, Handlers, Validators, Services — unless inheritance is needed.recordfor Commands, Queries, DTOs, Domain Events, Value Objects, Integration Events.- Primary constructors for DI — no manual field assignment boilerplate.
requiredon mandatory DTO and settings properties.- File-scoped namespaces —
namespace MyApp.Domain.Entities;no braces. - Collection expressions
[...]— nevernew List<T> { }. async/awaitall the way — never.Resultor.Wait().CancellationTokenon every async method, always last parameter.ArgumentNullException.ThrowIfNull/ArgumentException.ThrowIfNullOrWhiteSpacefor guard clauses.TimeProviderinjection — neverDateTime.UtcNowdirectly.- No magic strings — use
nameof(), constants, or static error classes. - XML doc comments on public interfaces only.
- Use
Guid.CreateVersion7()instead ofGuid.NewGuid(). - Nullable reference types enabled — always express nullability with
?. Never suppress with the null-forgiving operator!unless genuinely impossible to avoid (add a comment explaining why). - Settings injection — always inject
IOptions<TSettings>in services. Never inject or resolveIConfigurationdirectly outside ofProgram.cs/DependencyInjection.cs. - Collection return types — return
IReadOnlyList<T>orIReadOnlyCollection<T>from public methods; never exposeList<T>directly. UseIEnumerable<T>only for lazy sequences. - Pattern matching — prefer
switchexpressions andistype patterns overif+ cast chains. ConfigureAwait(false)— Infrastructure layer: required on every async call (runs in library/background context). Application, Domain, and API layers: do not add — ASP.NET Core has noSynchronizationContext.- Mapster mapping — mapping logic lives in the Application layer (static extension methods next to the DTO). Never map inside a constructor, Infrastructure, or API layer.
- Scalar API docs — only mapped in
Developmentenvironment (if (app.Environment.IsDevelopment())). Never expose in Production.
Always return Result<T> from Application layer. Never throw for expected failures.
// Implicit conversions allow: return order.Id; or return OrderErrors.NotFound;
public static implicit operator Result<T>(T value) => Success(value);
public static implicit operator Result<T>(Error error) => Failure(error);- Error codes:
"{Entity}.{Reason}"— e.g."Order.NotFound","Auth.InvalidCredentials". - API response messages (
Error.Description, validation messages,ProblemDetails.Detail) must be written in Turkish. Error codes (Error.Code) remain in English — e.g."Order.NotFound".
Every endpoint returns ApiResponse<T>. No naked Ok(data) anywhere.
{ "success": true, "data": { ... }, "traceId": "..." }
{ "success": false, "error": { "code": "...", "message": "...", "details": [] }, "traceId": "..." }Paged responses add "pagination": { "page", "pageSize", "totalCount", "totalPages", "hasNextPage", "hasPreviousPage" }.
Validation errors → HTTP 422 with field-level details array.
- Structured logging always — never string interpolation in log templates.
CorrelationIdpropagated viaX-Correlation-Idheader through all requests and events.TraceIdincluded in every API response.- Health checks registered for all external dependencies.
- No sensitive data in logs: passwords, tokens, API keys, PII.
Full Docker, Dockerfile, and secrets reference: docker-instructions.md
appsettings.jsonmust never contain real passwords, tokens or connection strings — leave them empty ("").- Sensitive values live only in
.secrets/files (one value per file, no trailing newline) — never in.env. - Required
.secrets/files:Jwt__Secret,ConnectionStrings__DefaultConnection,RabbitMQ__Password,Elasticsearch__Password,MinIO__AccessKey,MinIO__SecretKey,postgres_password,kibana_system_password,kibana_encryption_key,kibana_security_encryption_key,kibana_reporting_encryption_key. - Create a secret file:
echo -n "value" > .secrets/SecretName— never withecho "value"(trailing newline breaksAddKeyPerFile). Program.csloads.secrets/first (local dev at repository root), then/run/secrets(Docker). Seedocker-instructions.mdfor the exact loading order.