.cursor/skills/cleanddd-dotnet-coding/SKILL.md
在 CleanDDD 项目中落地已建模的需求(聚合/命令/查询/API 端点(Endpoints)/事件/仓储/配置/测试)的编码指南;用于编写或修改业务功能、端点与数据访问时
npx skillsauth add zhouda1fu/Ncp.Admin cleanddd-dotnet-codingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Configure() 内配置路由、Tags、AuthSchemes、Permissions、Description 等。IMediator 构造注入,使用 Send.OkAsync/CreatedAsync/NoContentAsync 与 .AsResponseData()。public partial record,与实体/聚合同文件。{Entity}Id 同文件)。Entity<TChildId>,由聚合根维护集合。多对多:若联结行需要独立标识与生命周期,可用 Entity<TLinkId>;若仅为两 Id 关联,可用联结 POCO/值对象或复合键,按不变式选择。示例:User 聚合根与强类型 ID
namespace ProjectName.Domain.AggregatesModel.UserAggregate;
public partial record UserId : IGuidStronglyTypedId;
public class User : Entity<UserId>, IAggregateRoot
{
protected User() { }
public User(string name, string email)
{
Name = name;
Email = email;
this.AddDomainEvent(new UserCreatedDomainEvent(this));
}
public string Name { get; private set; } = string.Empty;
public string Email { get; private set; } = string.Empty;
public Deleted Deleted { get; private set; } = new();
public RowVersion RowVersion { get; private set; } = new(0);
public void ChangeEmail(string email)
{
Email = email;
this.AddDomainEvent(new UserEmailChangedDomainEvent(this));
}
}
示例:User 领域事件
namespace ProjectName.Domain.DomainEvents;
public record UserCreatedDomainEvent(User User) : IDomainEvent;
public record UserEmailChangedDomainEvent(User User) : IDomainEvent;
示例:创建用户命令
using ProjectName.Domain.AggregatesModel.UserAggregate;
namespace ProjectName.Web.Application.Commands.Users;
public record CreateUserCommand(string Name, string Email) : ICommand<UserId>;
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(50);
RuleFor(x => x.Email).NotEmpty().EmailAddress().MaximumLength(100);
}
}
public class CreateUserCommandHandler(IUserRepository userRepository)
: ICommandHandler<CreateUserCommand, UserId>
{
public async Task<UserId> Handle(CreateUserCommand command, CancellationToken cancellationToken)
{
if (await userRepository.EmailExistsAsync(command.Email, cancellationToken))
throw new KnownException("邮箱已存在");
var user = new User(command.Name, command.Email);
await userRepository.AddAsync(user, cancellationToken);
return user.Id;
}
}
示例:查询用户
using ProjectName.Domain.AggregatesModel.UserAggregate;
using ProjectName.Infrastructure;
using Microsoft.EntityFrameworkCore;
namespace ProjectName.Web.Application.Queries.Users;
public record GetUserQuery(UserId UserId) : IQuery<UserDto>;
public class GetUserQueryValidator : AbstractValidator<GetUserQuery>
{
public GetUserQueryValidator()
{
RuleFor(x => x.UserId).NotEmpty();
}
}
public class GetUserQueryHandler(ApplicationDbContext context)
: IQueryHandler<GetUserQuery, UserDto>
{
public async Task<UserDto> Handle(GetUserQuery request, CancellationToken cancellationToken)
{
return await context.Users
.Where(x => x.Id == request.UserId)
.Select(x => new UserDto(x.Id, x.Name, x.Email))
.FirstOrDefaultAsync(cancellationToken)
?? throw new KnownException($"未找到用户,UserId = {request.UserId}");
}
}
public record UserDto(UserId Id, string Name, string Email);
Configure() 中配置 Post/Put/路由、Tags、AuthSchemes、Permissions、Description 等;HandleAsync 中通过 mediator 发送命令/查询;Send.OkAsync/CreatedAsync/NoContentAsync + .AsResponseData()。.Value;若项目启用 XML 文档/接口说明,Request、Response 与端点类应补充摘要与参数说明。示例:
public class CreateUserEndpoint(IMediator mediator)
: Endpoint<CreateUserRequest, ResponseData<CreateUserResponse>>
{
public override void Configure()
{
Tags("Users");
Post("/api/users");
AuthSchemes(JwtBearerDefaults.AuthenticationScheme);
Permissions(PermissionCodes.AllApiAccess, PermissionCodes.UserCreate);
}
public override async Task HandleAsync(CreateUserRequest req, CancellationToken ct)
{
var id = await mediator.Send(new CreateUserCommand(req.Name, req.Email), ct);
await Send.OkAsync(new CreateUserResponse(id).AsResponseData(), ct);
}
}
示例:领域事件处理器触发命令
using ProjectName.Domain.DomainEvents;
using ProjectName.Web.Application.Commands.Users;
namespace ProjectName.Web.Application.DomainEventHandlers;
public class UserCreatedDomainEventHandlerForSendWelcome(IMediator mediator)
: IDomainEventHandler<UserCreatedDomainEvent>
{
public async Task Handle(UserCreatedDomainEvent domainEvent, CancellationToken cancellationToken)
{
var command = new SendWelcomeEmailCommand(domainEvent.User.Id, domainEvent.User.Email, domainEvent.User.Name);
await mediator.Send(command, cancellationToken);
}
}
示例:用户仓储
using ProjectName.Domain.AggregatesModel.UserAggregate;
namespace ProjectName.Infrastructure.Repositories;
public interface IUserRepository : IRepository<User, UserId>
{
Task<bool> EmailExistsAsync(string email, CancellationToken cancellationToken = default);
}
public class UserRepository(ApplicationDbContext context)
: RepositoryBase<User, UserId, ApplicationDbContext>(context), IUserRepository
{
public async Task<bool> EmailExistsAsync(string email, CancellationToken cancellationToken = default)
{
return await DbContext.Users.AnyAsync(x => x.Email == email, cancellationToken);
}
}
示例:用户实体配置
using ProjectName.Domain.AggregatesModel.UserAggregate;
namespace ProjectName.Infrastructure.EntityConfigurations;
public class UserEntityTypeConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("Users");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id)
.UseGuidVersion7ValueGenerator()
.HasComment("用户标识");
builder.Property(x => x.Name)
.IsRequired()
.HasMaxLength(50)
.HasComment("用户姓名");
builder.Property(x => x.Email)
.IsRequired()
.HasMaxLength(100)
.HasComment("用户邮箱");
builder.HasIndex(x => x.Email).IsUnique();
}
}
public DbSet<T> Name => Set<T>();)。示例:DbSet 注册
using ProjectName.Domain.AggregatesModel.UserAggregate;
namespace ProjectName.Infrastructure;
public partial class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IMediator mediator)
: AppDbContextBase(options, mediator)
{
public DbSet<User> Users => Set<User>();
}
示例:用户创建集成事件
using ProjectName.Domain.AggregatesModel.UserAggregate;
namespace ProjectName.Web.Application.IntegrationEvents;
public record UserCreatedIntegrationEvent(UserId UserId, string Name, string Email, DateTime CreatedTime);
示例:领域事件到集成事件
using ProjectName.Domain.DomainEvents;
using ProjectName.Web.Application.IntegrationEvents;
namespace ProjectName.Web.Application.IntegrationEventConverters;
public class UserCreatedIntegrationEventConverter
: IIntegrationEventConverter<UserCreatedDomainEvent, UserCreatedIntegrationEvent>
{
public UserCreatedIntegrationEvent Convert(UserCreatedDomainEvent domainEvent)
{
var user = domainEvent.User;
return new UserCreatedIntegrationEvent(user.Id, user.Name, user.Email, DateTime.UtcNow);
}
}
示例:处理集成事件
using ProjectName.Web.Application.Commands.Users;
using ProjectName.Web.Application.IntegrationEvents;
namespace ProjectName.Web.Application.IntegrationEventHandlers;
public class UserCreatedIntegrationEventHandlerForSendWelcomeEmail(
ILogger<UserCreatedIntegrationEventHandlerForSendWelcomeEmail> logger,
IMediator mediator)
: IIntegrationEventHandler<UserCreatedIntegrationEvent>
{
public async Task HandleAsync(UserCreatedIntegrationEvent integrationEvent, CancellationToken cancellationToken)
{
logger.LogInformation("发送欢迎邮件:{UserId}", integrationEvent.UserId);
var command = new SendWelcomeEmailCommand(integrationEvent.UserId, integrationEvent.Email, integrationEvent.Name);
await mediator.Send(command, cancellationToken);
}
}
示例:聚合单测
public class UserTests
{
[Fact]
public void Constructor_ShouldRaiseCreatedEvent()
{
var user = new User("Alice", "[email protected]");
Assert.Equal("Alice", user.Name);
Assert.Single(user.GetDomainEvents());
Assert.IsType<UserCreatedDomainEvent>(user.GetDomainEvents().First());
}
[Fact]
public void ChangeEmail_ShouldRaiseChangedEvent()
{
var user = new User("Bob", "[email protected]");
user.ClearDomainEvents();
user.ChangeEmail("[email protected]");
Assert.Equal("[email protected]", user.Email);
Assert.IsType<UserEmailChangedDomainEvent>(user.GetDomainEvents().Single());
}
}
if (Paid) throw new KnownException("Order has been paid");
var order = await orderRepository.GetAsync(request.OrderId, cancellationToken)
?? throw new KnownException($"未找到订单,OrderId = {request.OrderId}");
order.OrderPaid();
development
Follows Vben Admin patterns when developing Ncp.Admin frontend (Vue 3 + Vite + TypeScript + Ant Design Vue). Use when adding or modifying views, API modules, routes, locales, or components in src/frontend/apps/admin-antd.
research
需求澄清与拆解,识别干系人并将需求条目标注所属对象与业务实体;仅产出结构化需求描述,不做建模定义,可作为后续建模输入
development
基于 cleanddd-requirements-analysis 结果,输出符合 CleanDDD 的聚合、命令、事件、查询、API 端点(Endpoints)、定时任务模型;用于从已拆解的需求快速得到建模蓝图
tools
交互式 CleanDDD 教练技能,循序讲解并与用户互动练习 CleanDDD 核心原则与方法;包含微课、测验与检查清单,可承接到需求分析/建模/代码实现技能