modules/ai-guide/Write-AiGuide-Application.ps1
|
param( [string] $ProjectName, [string] $OutputPath ) . (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Utils.ps1") $guideDir = Join-Path $OutputPath "ai-guide" function Write-Guide($fileName, $content) { Write-CoreFile (Join-Path $guideDir $fileName) $content } $appGuideContent = @' # Application Layer AI Developer Guide You are an AI assistant helping developers build the Application Layer of the {P} solution. This layer contains the application use cases, CQRS commands/queries, MediatR handlers, DTOs, FluentValidation validators, and AutoMapper mappings. --- ## Strict Application Layer Rules 1. **Clean Dependencies:** The Application project depends only on the Domain project. Do not reference WebApi or Persistence projects. 2. **Feature Folder Scoping:** Group CQRS logic by feature (e.g. `Features/Products/Commands/AddProduct/AddProductCommand.cs`). 3. **Single-File CQRS Handlers:** Put the request command/query record, the handler class, and the validator (if applicable) inside a single `.cs` file. 4. **Input Validation:** Use FluentValidation. Place validation rules (`Validator`) in the same file as the command it validates. Do not use Data Annotations. 5. **No Controller Logic:** Controllers only pass queries/commands to MediatR. Do not write business rules in WebApi controllers. 6. **Pagination Rule:** Every entity list endpoint MUST have a `GetPaged` query. Use `GetPagedAsync` for public-facing list endpoints. `GetAll` is for internal use only. --- ## How to Add a Feature When adding a new feature (e.g., `Category`), implement the following files in `{P}-backend/src/{P}.Application`: ### 1. DTO (Data Transfer Object) Define DTOs as C# records inside the feature folder: ```csharp // {P}-backend/src/{P}.Application/Features/Categories/CategoryDto.cs namespace {P}.Application.Features.Categories; public record CategoryDto(int Id, string Name, DateTime CreatedAt); ``` ### 2. AutoMapper Mapping Profile Create profile inside a `Mappings/` subfolder within the feature folder: ```csharp // {P}-backend/src/{P}.Application/Features/Categories/Mappings/CategoryProfile.cs using AutoMapper; using {P}.Domain.Entities; namespace {P}.Application.Features.Categories.Mappings; public class CategoryProfile : Profile { public CategoryProfile() { CreateMap<Category, CategoryDto>(); } } ``` ### 3. Paged Query (Required for list endpoints) ```csharp // {P}-backend/src/{P}.Application/Features/Categories/Queries/GetPagedCategories/GetPagedCategoriesQuery.cs using AutoMapper; using MediatR; using {P}.Domain.Common; using {P}.Domain.Interfaces; namespace {P}.Application.Features.Categories.Queries.GetPagedCategories; public record GetPagedCategoriesQuery(int Page = 1, int Size = 20) : IRequest<PagedResult<CategoryDto>>; public class GetPagedCategoriesQueryHandler : IRequestHandler<GetPagedCategoriesQuery, PagedResult<CategoryDto>> { private readonly ICategoryRepository _repo; private readonly IMapper _mapper; public GetPagedCategoriesQueryHandler(ICategoryRepository repo, IMapper mapper) { _repo = repo; _mapper = mapper; } public async Task<PagedResult<CategoryDto>> Handle(GetPagedCategoriesQuery request, CancellationToken ct) { var pagedResult = await _repo.GetPagedAsync(new PagedQuery { Page = request.Page, Size = request.Size }); return new PagedResult<CategoryDto> { Items = _mapper.Map<IEnumerable<CategoryDto>>(pagedResult.Items), TotalCount = pagedResult.TotalCount, Page = pagedResult.Page, Size = pagedResult.Size }; } } ``` ### 4. Command, Handler, & Validator (Single File) Define the Command record, the MediatR handler, and the FluentValidation rules in the same file: ```csharp // {P}-backend/src/{P}.Application/Features/Categories/Commands/AddCategory/AddCategoryCommand.cs using FluentValidation; using MediatR; using {P}.Domain.Entities; using {P}.Domain.Interfaces; namespace {P}.Application.Features.Categories.Commands.AddCategory; public record AddCategoryCommand(string Name) : IRequest<int>; public class AddCategoryCommandValidator : AbstractValidator<AddCategoryCommand> { public AddCategoryCommandValidator() { RuleFor(x => x.Name) .NotEmpty().WithMessage("Category name cannot be empty.") .MaximumLength(200).WithMessage("Category name cannot exceed 200 characters."); } } public class AddCategoryCommandHandler : IRequestHandler<AddCategoryCommand, int> { private readonly ICategoryRepository _repository; public AddCategoryCommandHandler(ICategoryRepository repository) { _repository = repository; } public async Task<int> Handle(AddCategoryCommand request, CancellationToken ct) { var category = new Category(request.Name); return await _repository.AddAsync(category); } } ``` '@.Replace("{P}", $ProjectName) Write-Guide "application-guide.md" $appGuideContent |