Instruction file imported from pavanthakur/XYDataLabs.OrderProcessingSystem (
.github/instructions/clean-architecture.instructions.md). Copyright stays with the author.
Clean Architecture Rules — XYDataLabs.OrderProcessingSystem
These rules are MANDATORY for all code changes. Violations must be fixed before committing.
Dependency Flow (strict)
Domain (zero dependencies)
↑
Application (→ Domain, → SharedKernel only)
↑
Infrastructure (→ Application, → Domain, → SharedKernel)
↑
API (→ Application, → Infrastructure, → SharedKernel) ← composition root
Layer Rules
Domain Layer
- Contains: Entities, Value Objects, Domain Events, Enums
- ZERO project references — no dependencies on any other project
- ZERO NuGet packages except
Microsoft.Extensions.*abstractions - Never import EF Core, Azure SDK, or any infrastructure namespace
- Pure business rules only — no framework or database concerns
Application Layer
- Contains: Use Cases (Commands, Queries, Handlers), DTOs, Validators, Mapper Profiles, Abstractions
- References: Domain, SharedKernel ONLY
- NEVER reference Infrastructure — no
<ProjectReference>to Infrastructure.csproj - NEVER import Infrastructure namespaces — no
using ...Infrastructure.* - NEVER use concrete DbContext — use
IAppDbContextinterface only - No DI registration code — DI wiring belongs in the API composition root
- CQRS pattern: Commands for writes, Queries for reads, Handlers for orchestration
- All handlers return
Result<T>for business outcomes (not exceptions)
Infrastructure Layer
- Contains: DbContext, Migrations, Repository implementations, External service clients, Caching
- References: Application, Domain, SharedKernel
- Implements interfaces defined in Application (e.g.,
IAppDbContext) - ALL EF Core, Azure SDK, and database concerns live here ONLY
Presentation Layer (API)
- Contains: Controllers, Middleware, Filters, Program.cs (composition root)
- References: Application, Infrastructure, SharedKernel
- IS the composition root — ALL DI registrations happen here (Program.cs or DependencyInjection.cs)
- Controllers are thin — delegate to
IDispatcher(CQRS) or service interfaces only - No business logic in controllers
- All endpoints return
ApiResponse<T>standard envelope
SharedKernel (cross-cutting)
- Contains: Result, Error, ApiResponse, Constants, configuration helpers, observability extensions
- Referenced by all layers — keep it lightweight
- No business logic — only shared primitives and cross-cutting utilities
Patterns & Conventions
Data Access
- Use
IAppDbContext(defined in Application) — never concreteOrderProcessingSystemDbContext - No Repository/Unit of Work wrapper —
IAppDbContextis the abstraction - Infrastructure's DbContext implements
IAppDbContext
CQRS (Hand-Rolled, no MediatR)
ICommand<TResult>/IQuery<TResult>— marker interfacesICommandHandler<TCommand, TResult>/IQueryHandler<TQuery, TResult>— handlersIPipelineBehavior<TRequest, TResult>— cross-cutting (validation, logging, caching)IDispatcher— resolves and invokes handlers from DI- Feature folder structure:
Application/Features/{Feature}/Commands/,Queries/
Error Handling
- Use
Result<T>for business outcomes — not exceptions - Exceptions are for truly exceptional situations (infrastructure failures, bugs)
- Controllers map
Result<T>→ApiResponse<T>→ HTTP status codes PaymentProviderCustomerActionException(SharedKernel) = terminal provider decline — map toPaymentAttemptStatus.Failed, no retry. All other gateway exceptions →UnknownNeedsReconciliation.
API Responses
- ALL endpoints return
ApiResponse<T>with{ Success, Data, Message, Errors } - URL-segment API versioning:
/api/v{version}/[controller]
Caching
ICacheServiceinterface in Application, Redis implementation in InfrastructureCachingBehavior<TRequest, TResult>CQRS pipeline — queries opt in viaICacheable- Commands always bypass cache (and can invalidate)
Observability
- OpenTelemetry for distributed tracing via
AddObservability(serviceName)in SharedKernel - Custom
ActivitySourceper bounded context for business operation spans - Structured logging only:
Log.Information("{Key}", value)— never string interpolation
Multi-Tenancy (Hybrid — SharedPool + Dedicated)
- Two tiers:
SharedPool(default, shared DB with TenantId discriminator) andDedicated(separate physical DB per tenant). See ADR-007. - Tenant-owned entities inherit
BaseAuditableEntityorBaseAuditableCreateEntity— providesTenantId+ audit columns. Never redeclareTenantIdon derived entities. - ConfigureTenantOwnership(): Single method in
OnModelCreating()that configures FK to Tenants + global query filter + DeleteBehavior.Restrict. Every new tenant-owned entity MUST be registered here. - IAppDbContext parity: Every
DbSet<T>onOrderProcessingSystemDbContext(exceptTenant) must appear onIAppDbContext. Architecture tests enforce this. - IgnoreQueryFilters rule: Bypasses tenant isolation — usage is restricted to an architecture-test allow-list. Any new usage must be reviewed for tenant safety and added to the allow-list explicitly.
HeaderTenantProviderresolvesX-Tenant-Codeto canonical tenant context (TenantId,TenantCode,TenantExternalId,ConnectionString,IsSharedPool)- Missing or unknown tenant code returns HTTP 400; suspended or decommissioned tenant returns HTTP 403
Tenantsis a system table — excluded from query filters,IAppDbContext, and tenant stamping- Controllers must never accept TenantId, TenantCode, or TenantExternalId as parameters or in request DTOs. Tenant context comes from middleware only.
- Non-request operations must set
TenantIdexplicitly instead of relying on ambient tenant context
Payment Configuration Safety
IsProduction = falseis the required default in allsharedsettings.{env}.jsonfiles. Startup logs"TEST mode"or"LIVE mode".RazorpayConfigValidatorcross-checks key prefix (rzp_test_*/rzp_live_*) againstIsProductionat startup — mismatch fails startup immediately.- Never put per-tenant runtime config (
Use3DSecure, provider routing) in sharedsettings. DB is the source of truth.
Red Flags — Reject These in Code Review
using XYDataLabs.OrderProcessingSystem.Infrastructurein Application layerOrderProcessingSystemDbContextused directly outside Infrastructure<ProjectReference>from Application to Infrastructure in any .csproj- DI registrations (
services.Add*) in Application project - Business logic in Controllers
- Raw data returned without
ApiResponse<T>envelope - Exception-based flow control for expected business outcomes
client-secret:in any workflow YAML- String-interpolated log messages instead of structured logging