Skip to main content

dotnet-cqrs-api

Guide for building .NET APIs using CQRS pattern with MediatR, FluentValidation, and Carter modules Use when this capability is needed.

الانتقال إلى التثبيت

معلومات المصدر

المستودع
tomevault-io/skills-registry
آخر نشاط في المصدر
٢٨ أبريل ٢٠٢٦ في ٢٢:٥٣
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٠
التفرعات
٠

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
2 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
dotnet-cqrs-api
description
Guide for building .NET APIs using CQRS pattern with MediatR, FluentValidation, and Carter modules Use when this capability is needed.
metadata
{"author":"alanben"}
# .NET CQRS API Development Skill ## Purpose This skill guides the specification and creation of .NET APIs using the CQRS pattern with MediatR, FluentValidation, and Carter modules. It provides a structured approach to building feature-based, vertically-sliced APIs with robust validation and clear separation of concerns. ## Core Principles ### Architectural Philosophy - **Vertical Slice Architecture**: Features are self-contained units organized by business capability - **CQRS Separation**: Commands (write operations) and Queries (read operations) are explicitly separated - **Result Pattern**: All operations return `Result<T>` for explicit success/failure handling - **Functional Composition**: Prefer declarative, immutable approaches over imperative mutations ### Technology Stack - **MediatR**: Implements mediator pattern for decoupling endpoint handlers from business logic - **FluentValidation**: Provides declarative, strongly-typed validation rules - **Carter**: Enables minimal API-style endpoint definition with module organization - **Mapster** (inferred): Used for object mapping (`.Adapt<T>()` pattern) ## Project Structure ``` /Features /{FeatureName} - Add{Feature}.cs # Command endpoint + handler - Update{Feature}.cs # Command endpoint + handler - Get{Feature}.cs # Single item query endpoint + handler - Get{Feature}s.cs # Collection query endpoint + handler - {Feature}AddRequest.cs # Command DTO - {Feature}UpdateRequest.cs # Command DTO - {Feature}Response.cs # Query response DTO ``` ## Feature Specification Process ### Step 1: Define the Feature Domain Before writing code, establish: 1. **Feature Name**: Clear, business-focused identifier (e.g., "Order", "Customer", "Invoice") 2. **Business Context**: What problem does this feature solve? 3. **Operations Needed**: Which CQRS operations are required? - Create (Add) - Read Single (Get) - Read Collection (GetAll/List) - Update - Delete (not in templates, but may be needed) 4. **Domain Model**: What properties define this entity? 5. **Validation Rules**: What business rules must be enforced? 6. **Authorization Requirements**: Who can access these endpoints? ### Step 2: Design the Data Contract Define three types of DTOs: **Request DTOs** (for commands): - Contain only the data needed to perform the operation - Exclude computed or system-managed fields (IDs, timestamps) - Should be immutable or follow init-only patterns **Response DTOs** (for queries): - May inherit from domain models - Include data needed by consumers - Can include computed/derived properties **Domain Models** (internal): - Represent the entity in the data layer - Should not be directly exposed via API ### Step 3: Specify Validation Rules For each operation, define: - Required fields and null checks - Range validations (GreaterThan, LessThan) - Format validations (Regex patterns, date formats) - Business rule validations (cross-field rules) - Custom validation logic requirements ### Step 4: Define Routing Strategy Establish URL patterns following RESTful conventions: - Collection routes: `/{feature-plural}` or `/Features/{FeatureName}` - Item routes: `/{feature-plural}/{id}` or `/Features/{FeatureName}/{id}` - Consider route parameters vs query strings - Maintain consistency across features ## Implementation Templates ### Command Pattern (Add/Create) **Use when**: Creating new entities **Structure**: ```csharp namespace {ProjectName}.Features.{FeatureName}; // 1. Carter Endpoint Module public class Add{Feature}Endpoint : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapPost("{route}", async ({Feature}AddRequest request, ISender sender) => { var command = new Add{Feature}.Command { {Feature} = request }; var result = await sender.Send(command); if (result.IsFailure) { return Results.BadRequest(result.Error); } return Results.Ok(result.Value); }) .Produces<int>() // Or appropriate return type .RequireAuthorization("{PolicyName}") .WithMetadata(new RouteMetadata { Tags = new[] { "{TagName}" } }); } } // 2. Command Definition public static partial class Add{Feature} { public class Command : IRequest<Result<int>> { // Or TResult public {Feature}AddRequest? {Feature} { get; set; } } // 3. Validation Rules public class Validator : AbstractValidator<Command> { public Validator() { RuleFor(x => x.{Feature}) .NotNull() .WithMessage("No {feature} data provided."); // Add specific field validations RuleFor(x => x.{Feature}!.PropertyName) .GreaterThan(0) .WithMessage("PropertyName cannot be zero."); } } // 4. Command Handler internal sealed partial class {Feature}Handler : IRequestHandler<Command, Result<int>> { private readonly ILogger<{Feature}Handler> _logger; private readonly IValidator<Command> _validator; private readonly I{Feature}Data _data; public {Feature}Handler( IValidator<Command> validator, ILogger<{Feature}Handler> logger, I{Feature}Data data ) { _validator = validator; _logger = logger; _data = data; } public async Task<Result<int>> Handle(Command request, CancellationToken cancellationToken) { // Validate var validationResult = _validator.Validate(request); if (!validationResult.IsValid) { return Result.Failure<int>(new Error("Add{Feature}.Validation", validationResult.ToString())); } try { // Map and execute {Feature}Model new{Feature} = request.{Feature}!.Adapt<{Feature}Model>(); var {feature} = await _data.Add{Feature}(new{Feature}); if ({feature} is null) { return Result.Failure<int>(new Error("Add{Feature}", "Failed to add {feature}")); } return {feature}.ID; } catch (Exception ex) { return Result.Failure<int>(new Error("Add{Feature}.Exception", ex.Message)); } } } } ``` **Key Patterns**: - Endpoint delegates to MediatR command via `ISender` - Validation happens in handler, not endpoint - Result pattern for explicit error handling - Exception handling wraps unexpected failures - Returns entity ID on success ### Query Pattern (Get Single) **Use when**: Retrieving a specific entity by identifier **Structure**: ```csharp namespace {ProjectName}.Features.{FeatureName}; // 1. Carter Endpoint Module public class Get{Feature}Endpoint : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapGet("{route}/{id:int}", async (int id, ISender sender) => { var query = new Get{Feature}.{Feature}Query { ID = id }; var result = await sender.Send(query); if (result.IsFailure) { return Results.NotFound(result.Error); } return Results.Ok(result.Value); }) .Produces<{Feature}Response>() .RequireAuthorization("{PolicyName}") .WithMetadata(new RouteMetadata { Tags = new[] { "{TagName}" } }); } } // 2. Query Definition public static class Get{Feature} { public class {Feature}Query : IRequest<Result<{Feature}Response>> { public int ID { get; set; } = 0; // Additional filter properties } // 3. Validation Rules public class Validator : AbstractValidator<{Feature}Query> { public Validator() { RuleFor(x => x.ID) .GreaterThan(0) .WithMessage("ID cannot be zero."); } } // 4. Query Handler internal sealed class Handler : IRequestHandler<{Feature}Query, Result<{Feature}Response>> { private readonly ILogger<Handler> _logger; private readonly IValidator<{Feature}Query> _validator; private readonly I{Feature}Data _data; public Handler( ILogger<Handler> logger, IValidator<{Feature}Query> validator, I{Feature}Data data ) { _logger = logger; _validator = validator; _data = data; } public async Task<Result<{Feature}Response>> Handle({Feature}Query request, CancellationToken cancellationToken) { // Validate var validationResult = _validator.Validate(request); if (!validationResult.IsValid) { return Result.Failure<{Feature}Response>(new Error("Get{Feature}.Validation", validationResult.ToString())); } // Retrieve and map var {feature} = await _data.Get{Feature}(request.ID); if ({feature} is null) { return Result.Failure<{Feature}Response>(new Error( "Get{Feature}.Null", "The {feature} with the specified ID was not found")); } var response = {feature}.Adapt<{Feature}Response>(); // Optional: Enrich response with additional data return response; } } } ``` **Key Patterns**: - Returns `NotFound` for missing entities - Validation ensures query parameters are valid - Response mapping allows projection/transformation - Separate method for complex retrieval logic ### Query Pattern (Get Collection) **Use when**: Retrieving multiple entities with filtering **Structure**: ```csharp namespace {ProjectName}.Features.{FeatureName}; // 1. Carter Endpoint Module public class Get{Feature}sEndpoint : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapGet("{route}", async (int param1, string param2, ISender sender) => { var query = new Get{Feature}s.{Feature}Query { Param1 = param1, Param2 = param2 }; var result = await sender.Send(query); if (result.IsFailure) { return Results.NotFound(result.Error); } return Results.Ok(result.Value); }) .Produces<IEnumerable<{Feature}Response>>() .RequireAuthorization("{PolicyName}") .WithMetadata(new RouteMetadata { Tags = new[] { "{TagName}" } }); } } // 2. Query Definition public static class Get{Feature}s { public class {Feature}Query : IRequest<Result<IEnumerable<{Feature}Response>>> { public int Param1 { get; set; } = 0; public string Param2 { get; set; } = string.Empty; // Filter/pagination properties } // 3. Validation Rules public class Validator : AbstractValidator<{Feature}Query> { public Validator() { RuleFor(x => x.Param1) .GreaterThan(0) .WithMessage("Param1 cannot be zero."); // Date format validation example RuleFor(x => x.DateParam) .Matches(@"\d{4}-\d{2}-\d{2}") .WithMessage("DateParam must be in the format yyyy-MM-dd."); } } // 4. Query Handler internal sealed class Handler : IRequestHandler<{Feature}Query, Result<IEnumerable<{Feature}Response>>> { private readonly ILogger<Handler> _logger; private readonly IValidator<{Feature}Query> _validator; private readonly I{Feature}Data _data; public Handler( ILogger<Handler> logger, IValidator<{Feature}Query> validator, I{Feature}Data data ) { _logger = logger; _validator = validator; _data = data; } public async Task<Result<IEnumerable<{Feature}Response>>> Handle({Feature}Query request, CancellationToken cancellationToken) { // Validate var validationResult = _validator.Validate(request); if (!validationResult.IsValid) { return Result.Failure<IEnumerable<{Feature}Response>>(new Error("Get{Feature}s.Validation", validationResult.ToString())); } // Retrieve collection var {feature}s = await _data.List{Feature}s(/* filter params */); if ({feature}s is null) { return Result.Failure<IEnumerable<{Feature}Response>>(new Error( "Get{Feature}s.Null", "Failed to get {feature}s")); } var response = {feature}s.Adapt<List<{Feature}Response>>(); return response; } } } ``` **Key Patterns**: - Query parameters come from route or query string - Returns collections (IEnumerable<T>) - Validation includes format checks (dates, etc.) - Consider pagination for large datasets ### Command Pattern (Update) **Use when**: Modifying existing entities **Structure**: ```csharp namespace {ProjectName}.Features.{FeatureName}; // 1. Carter Endpoint Module public class Update{Feature}Endpoint : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapPut("{route}", async ({Feature}UpdateRequest request, ISender sender) => { var command = new Update{Feature}.Command { {Feature} = request }; var result = await sender.Send(command); if (result.IsFailure) { return Results.BadRequest(result.Error); } return Results.Ok(result.Value); }) .Produces<int>() .RequireAuthorization("{PolicyName}") .WithMetadata(new RouteMetadata { Tags = new[] { "{TagName}" } }); } } // 2. Command Definition public static partial class Update{Feature} { public class Command : IRequest<Result<int>> { public {Feature}UpdateRequest? {Feature} { get; set; } } // 3. Validation Rules public class Validator : AbstractValidator<Command> { public Validator() { RuleFor(x => x.{Feature}) .NotNull()
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub