Skip to content

Latest commit

 

History

History
170 lines (152 loc) · 9.92 KB

copilot-instructions.md

File metadata and controls

170 lines (152 loc) · 9.92 KB

GitHub Copilot — Project Architecture Instructions

Stack

  • 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 + SpanId via Serilog.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 — :80 API only (backend-only), :9080 MinIO CDN with proxy_cache
  • Testing: xUnit + FluentAssertions + NSubstitute + Testcontainers

Layer Dependency Rules

  • Domain → no project-level dependencies on other layers. May reference Mediator.Abstractions (for INotification domain events) and Microsoft.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.

Folder Structure

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

Naming Conventions

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);

Universal Code Style

  • sealed on all Commands, Queries, Handlers, Validators, Services — unless inheritance is needed.
  • record for Commands, Queries, DTOs, Domain Events, Value Objects, Integration Events.
  • Primary constructors for DI — no manual field assignment boilerplate.
  • required on mandatory DTO and settings properties.
  • File-scoped namespaces — namespace MyApp.Domain.Entities; no braces.
  • Collection expressions [...] — never new List<T> { }.
  • async/await all the way — never .Result or .Wait().
  • CancellationToken on every async method, always last parameter.
  • ArgumentNullException.ThrowIfNull / ArgumentException.ThrowIfNullOrWhiteSpace for guard clauses.
  • TimeProvider injection — never DateTime.UtcNow directly.
  • No magic strings — use nameof(), constants, or static error classes.
  • XML doc comments on public interfaces only.
  • Use Guid.CreateVersion7() instead of Guid.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 resolve IConfiguration directly outside of Program.cs / DependencyInjection.cs.
  • Collection return types — return IReadOnlyList<T> or IReadOnlyCollection<T> from public methods; never expose List<T> directly. Use IEnumerable<T> only for lazy sequences.
  • Pattern matching — prefer switch expressions and is type patterns over if + 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 no SynchronizationContext.
  • 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 Development environment (if (app.Environment.IsDevelopment())). Never expose in Production.

Result Pattern (universal)

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".

Response Envelope

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.


Cross-Cutting Rules

  • Structured logging always — never string interpolation in log templates.
  • CorrelationId propagated via X-Correlation-Id header through all requests and events.
  • TraceId included in every API response.
  • Health checks registered for all external dependencies.
  • No sensitive data in logs: passwords, tokens, API keys, PII.

Secrets Management

Full Docker, Dockerfile, and secrets reference: docker-instructions.md

  • appsettings.json must 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 with echo "value" (trailing newline breaks AddKeyPerFile).
  • Program.cs loads .secrets/ first (local dev at repository root), then /run/secrets (Docker). See docker-instructions.md for the exact loading order.