cleanddd-dotnet-coding
8
总安装量
3
周安装量
#33617
全站排名
安装命令
npx skills add https://github.com/netcorepal/cleanddd-skills --skill cleanddd-dotnet-coding
Agent 安装分布
amp
3
opencode
3
kimi-cli
3
codex
3
github-copilot
3
gemini-cli
3
Skill 文档
ä½¿ç¨æ¶æº
- 卿¬ä»åºç¼å/ä¿®æ¹ä¸å¡åè½ãå½ä»¤ãæ¥è¯¢ã端ç¹ãéæäºä»¶ãä»å¨ãå®ä½é ç½®æç¸å ³æµè¯æ¶å è½½ã
åç½®è¾å ¥
- 建模设计ï¼å·²å®æ CleanDDD éæ±åæä¸å»ºæ¨¡ï¼è·å¾èåãå½ä»¤ãæ¥è¯¢ãäºä»¶çè®¾è®¡ææ¡£ã
å¼å§åå¿«éæ£æ¥
- æ¯å¦å·²æ cleanddd-modeling çèå/å½ä»¤/äºä»¶/API 端ç¹ï¼Endpointsï¼æ¸ åï¼ç¼ºå¤±å è¡¥é½ã
- 确认èåè¾¹çä¸ä¸åå¼ï¼å½åç»ä¸ PascalCaseï¼æä¸¾åºå®æ¼åã
- 约å®ï¼å½ä»¤å¤çå¨ä¸æ¾å¼ SaveChangesï¼è·¨èå交äºç¨é¢åäºä»¶/éæäºä»¶ï¼ç¦ç´æ¥è·¨èåæ´æ°ã
éç¨åå
- ä¼å ç¨ä¸»æé 彿°ï¼ææ IO/ä»å¨/EF è°ç¨ä½¿ç¨ async/await å¹¶ä¼ é CancellationTokenã
- ä¸¥æ ¼åå±ï¼Web â Infrastructure â Domainï¼èåä¸å®ä½åå¸é¢åäºä»¶ï¼å½ä»¤å¤çå¨ä¸æ¾å¼ SaveChangesã
- 强类å ID ç± EF å¼çæå¨æä¾ï¼æé 彿°ä¸è®¾ç½® IDï¼ç´æ¥ä½¿ç¨ç±»åå®ä¾ï¼é¿å .Valueã
- ä¸å¡å¼å¸¸ä½¿ç¨ KnownExceptionï¼FastEndpoints ç¨ç¹æ§é ç½®ï¼ä¸ä½¿ç¨ Configure()ï¼IMediator æé æ³¨å ¥ï¼ä½¿ç¨ Send.OkAsync/CreatedAsync/NoContentAsync ä¸ .AsResponseData()ã
æ¨è工使µ
- èåä¸å®ä½ â 2) é¢åäºä»¶ â 3) ä»å¨ä¸å®ä½é ç½® â 4) å½ä»¤+éªè¯å¨+å¤çå¨ â 5) æ¥è¯¢+éªè¯å¨+å¤çå¨ â 6) API 端ç¹ï¼Endpointsï¼ â 7) é¢åäºä»¶å¤çå¨ â 8) éæäºä»¶/转æ¢å¨/å¤çå¨ â 9) æµè¯ã
ç®å½ç»æ
- Domainï¼src/ProjectName.Domain/ï¼AggregatesModel/{Aggregate}Aggregateï¼DomainEventsï¼ã
- Infrastructureï¼src/ProjectName.Infrastructure/ï¼Repositoriesï¼EntityConfigurationsï¼ApplicationDbContextï¼ã
- Webï¼src/ProjectName.Web/Application/ï¼Commandsï¼Queriesï¼DomainEventHandlersï¼IntegrationEventsï¼IntegrationEventConvertersï¼IntegrationEventHandlersï¼ï¼Endpointsã
- Testsï¼test/* 对åºåå±ã
ç»ä¸å½å䏿¾ç½®çº¦å®
- å½å飿 ¼ï¼å ¨é¨ä½¿ç¨ PascalCaseï¼record/class/æ¥å£æ .NET æ¯ä¾ï¼äºä»¶ä½¿ç¨è¿å»å¼ã
- 强类å IDï¼å½å为 {Entity}Idï¼å®ä¹ä¸º
public partial recordï¼ä¸å®ä½/èååæä»¶ã - æä»¶å½åï¼
- å½ä»¤ï¼{Action}{Entity}Command.csï¼åæä»¶å å«éªè¯å¨ä¸å¤çå¨ã
- æ¥è¯¢ï¼{Action}{Entity}Query.csï¼åæä»¶å å«ååº/DTOãéªè¯å¨ä¸å¤çå¨ã
- 端ç¹ï¼{Action}{Entity}Endpoint.csï¼æ¯æä»¶ä¸ä¸ªç«¯ç¹ã
- ä»å¨ï¼{Aggregate}Repository.csï¼åæä»¶æ¾æ¥å£ä¸å®ç°ã
- å®ä½é ç½®ï¼{Entity}EntityTypeConfiguration.csã
- é¢åäºä»¶ï¼{Aggregate}DomainEvents.csï¼å¯å å«è¯¥èåçå¤ä¸ªäºä»¶ã
- éæäºä»¶ï¼{Entity}{Action}IntegrationEvent.csã
- éæäºä»¶è½¬æ¢å¨ï¼{Entity}{Action}IntegrationEventConverter.csã
- éæäºä»¶å¤çå¨ï¼{IntegrationEvent}HandlerFor{Action}.csã
- é¢åäºä»¶å¤çå¨ï¼{DomainEvent}DomainEventHandlerFor{Action}.csã
- DbContextï¼ApplicationDbContext.csã
- æ¾ç½®ç®å½ï¼
- èå/å®ä½/é¢åäºä»¶ï¼src/ProjectName.Domain/AggregatesModel/{Aggregate}Aggregate ä¸ src/ProjectName.Domain/DomainEventsã
- ä»å¨ä¸é ç½®ï¼src/ProjectName.Infrastructure/Repositories ä¸ src/ProjectName.Infrastructure/EntityConfigurationsã
- åºç¨å±ï¼src/ProjectName.Web/Application/{Commands|Queries|DomainEventHandlers|IntegrationEvents|IntegrationEventConverters|IntegrationEventHandlers}ã
- 端ç¹ï¼src/ProjectName.Web/Endpoints/{Module}/ã
- åæä»¶å并约å®ï¼å½ä»¤/æ¥è¯¢å°éªè¯å¨ãå¤çå¨ä¸ååºï¼å¦æï¼åå¹¶è³å䏿件ï¼ä»å¨æ¥å£ä¸å®ç°åæä»¶ï¼å ¶ä½ç±»åä¸ç±»ä¸æä»¶ã
èå
- å½åè§åï¼èåæ ¹æ é âAggregateâ åç¼ï¼åå®ä½éµå¾ªå®ä½å½åã
- ç®å½ï¼src/ProjectName.Domain/AggregatesModel/{Aggregate}Aggregateã
- æä»¶åï¼{Entity}.csï¼ä¸
{Entity}Idåæä»¶ï¼ã - å®ç°è¦ç¹ï¼
- èåæ ¹ç»§æ¿ Entity + IAggregateRootï¼protected æ åæé ã
- 屿§ private setï¼é»è®¤å¼æ¾å¼è®¾ç½®ï¼å å« Deleted ä¸ RowVersionã
- ç¶æåæ´æ¶ä½¿ç¨ this.AddDomainEvent() åå¸é¢åäºä»¶ã
- 强类å ID å®ç° IGuidStronglyTypedIdï¼ä¼å ï¼æ IInt64StronglyTypedIdï¼ç± EF å¼çæå¨çæã
- åå®ä½ç»§æ¿ Entity + IEntityï¼å¹¶æä¾æ åæé ã
示ä¾ï¼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));
}
}
é¢åäºä»¶
- å½åè§åï¼{Entity}{Action}DomainEventï¼è¿å»å¼ï¼ï¼ä½¿ç¨ recordï¼ç±»åå®ç° IDomainEventã
- ç®å½ï¼src/ProjectName.Domain/DomainEventsã
- æä»¶åï¼{Aggregate}DomainEvents.csï¼åèåçå¤ä¸ªäºä»¶å¯åå¹¶ï¼ã
- å®ç°è¦ç¹ï¼ä» ä½ä¸ºè½½ä½ï¼ä¸å«ä¸å¡é»è¾ã
示ä¾ï¼User é¢åäºä»¶
namespace ProjectName.Domain.DomainEvents;
public record UserCreatedDomainEvent(User User) : IDomainEvent;
public record UserEmailChangedDomainEvent(User User) : IDomainEvent;
å½ä»¤
- å½åè§åï¼å½ä»¤ record 为 {Action}{Entity}Commandï¼è¿åç±»åä½¿ç¨ ICommandï¼æ è¿åä½¿ç¨ ICommandã
- ç®å½ï¼src/ProjectName.Web/Application/Commands/{Module}sã
- æä»¶åï¼{Action}{Entity}Command.csï¼åæä»¶å å«éªè¯å¨ä¸å¤çå¨ï¼ã
- å®ç°è¦ç¹ï¼
- å¤çå¨å®ç° ICommandHandlerï¼ææ³åçæ¬ï¼ã
- 使ç¨ä»å¨è¯»å/æä¹ åèåï¼å ¨å¼æ¥å¹¶ä¼ é CancellationTokenã
- ä½¿ç¨ KnownException 表达ä¸å¡å¼å¸¸ï¼ä¸æå¨è°ç¨ SaveChanges/UpdateAsyncã
示ä¾ï¼åå»ºç¨æ·å½ä»¤
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;
}
}
æ¥è¯¢
- å½åè§åï¼{Action}{Entity}Queryï¼è¿å IQuery æ IPagedQueryã
- ç®å½ï¼src/ProjectName.Web/Application/Queries/{Module}sã
- æä»¶åï¼{Action}{Entity}Query.csï¼åæä»¶å å«ååº/DTOãéªè¯å¨ä¸å¤çå¨ï¼ã
- å®ç°è¦ç¹ï¼
- å¤çå¨å®ç° IQueryHandlerï¼ç´æ¥ä½¿ç¨ ApplicationDbContext æ¥è¯¢ã
- å ¨å¼æ¥å¹¶ä¼ é CancellationTokenï¼ä½¿ç¨æå½±ãWhereIf/OrderByIf/ToPagedDataAsyncã
- åé¡µä½¿ç¨ PagedDataï¼æä¾é»è®¤æåºã
- ç¦ç¨ä»å¨ä¸è·¨èå Joinï¼æ å¯ä½ç¨ã
- è¾åºç±»åå¯ä¸º {Entity}Response æ DTOã
示ä¾ï¼æ¥è¯¢ç¨æ·
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);
API 端ç¹ï¼Endpointsï¼
- å½åè§åï¼{Action}{Entity}Endpointï¼è¯·æ±/ååºä¸º {Action}{Entity}Request/{Action}{Entity}Responseï¼ä¸ä½¿ç¨ DTOï¼ã
- ç®å½ï¼src/ProjectName.Web/Endpoints/{Module}ã
- æä»¶åï¼{Action}{Entity}Endpoint.csï¼æ¯æä»¶ä¸ä¸ªç«¯ç¹ï¼ã
- å®ç°è¦ç¹ï¼
- 使ç¨å±æ§è·¯ç±/æéï¼[HttpPost]/[AllowAnonymous]/[Tags]ï¼ã
- HandleAsync éè¿ mediator åéå½ä»¤/æ¥è¯¢ï¼Send.OkAsync/CreatedAsync/NoContentAsync + .AsResponseData()ã
- 请æ±/ååºå¯ç´æ¥ä½¿ç¨å¼ºç±»å IDï¼é¿å è§£å .Valueï¼ä¸ä½¿ç¨ Configure()ã
示ä¾ï¼åå»ºç¨æ· API 端ç¹ï¼Endpointï¼
using ProjectName.Domain.AggregatesModel.UserAggregate;
using ProjectName.Web.Application.Commands.Users;
using Microsoft.AspNetCore.Authorization;
namespace ProjectName.Web.Endpoints.Users;
public record CreateUserRequest(string Name, string Email);
public record CreateUserResponse(UserId UserId);
[Tags("Users")]
[HttpPost("/api/users")]
[AllowAnonymous]
public class CreateUserEndpoint(IMediator mediator)
: Endpoint<CreateUserRequest, ResponseData<CreateUserResponse>>
{
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);
}
}
é¢åäºä»¶å¤çå¨
- å½åè§åï¼{DomainEvent}DomainEventHandlerFor{Action}ã
- ç®å½ï¼src/ProjectName.Web/Application/DomainEventHandlersã
- æä»¶åï¼{Name}.csï¼æ¯æä»¶ä¸ä¸ªå¤çå¨ï¼ã
- å®ç°è¦ç¹ï¼
- å®ç° IDomainEventHandlerï¼æ¹æ³ç¾å Handle(TEvent, CancellationToken)ã
- éè¿ mediator åéå½ä»¤é©±å¨èåï¼ä¸ç´æ¥æ¹ Dbï¼éµå®äºå¡/åæ¶ã
- 主æé 彿°æ³¨å ¥ä¾èµã
示ä¾ï¼é¢åäºä»¶å¤çå¨è§¦åå½ä»¤
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);
}
}
ä»å¨
- å½åè§åï¼æ¥å£ I{Aggregate}Repositoryï¼å®ç° {Aggregate}Repositoryã
- ç®å½ï¼src/ProjectName.Infrastructure/Repositoriesã
- æä»¶åï¼{Aggregate}Repository.csï¼æ¥å£ä¸å®ç°åæä»¶ï¼ã
- å®ç°è¦ç¹ï¼
- æ¥å£ç»§æ¿ IRepository<TEntity, TKey>ï¼å®ç°ç»§æ¿ RepositoryBase<TEntity, TKey, ApplicationDbContext>ã
- DbContext 屿§è®¿é® DbSetï¼ä¼å 使ç¨åºç±»é»è®¤æ¹æ³ï¼é¿å éå¤å®ç°ã
- ç±æ¡æ¶èªå¨å®æä¾èµæ³¨å ¥æ³¨åã
示ä¾ï¼ç¨æ·ä»å¨
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);
}
}
å®ä½é ç½®
- å½åè§åï¼{Entity}EntityTypeConfigurationã
- ç®å½ï¼src/ProjectName.Infrastructure/EntityConfigurationsã
- æä»¶åï¼{Entity}EntityTypeConfiguration.csã
- å®ç°è¦ç¹ï¼
- å®ç° IEntityTypeConfigurationã
- å¿ é¡»é 置主é®ï¼å符串å设置 MaxLengthï¼å¿ å¡«å IsRequiredï¼å段添å HasComment 注éï¼æéè¦æ·»å ç´¢å¼ã
- 强类å IDï¼IGuidStronglyTypedId â UseGuidVersion7ValueGeneratorï¼IInt64StronglyTypedId â UseSnowFlakeValueGeneratorï¼RowVersion æ éé ç½®ï¼ä¸è¦èªå®ä¹è½¬æ¢å¨ã
示ä¾ï¼ç¨æ·å®ä½é ç½®
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();
}
}
DbContext
- å½åè§åï¼ApplicationDbContextã
- ç®å½ï¼src/ProjectName.Infrastructureã
- æä»¶åï¼ApplicationDbContext.csã
- å®ç°è¦ç¹ï¼
- æ·»å èå DbSet 屿§ï¼
public DbSet<T> Name => Set<T>();ï¼ã - éè¿ ApplyConfigurationsFromAssembly èªå¨åºç¨å®ä½é ç½®ã
- æ·»å èå DbSet 屿§ï¼
示ä¾ï¼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>();
}
éæäºä»¶
- å½åè§åï¼{Entity}{Action}IntegrationEventï¼recordï¼ã
- ç®å½ï¼src/ProjectName.Web/Application/IntegrationEventsã
- æä»¶åï¼{Entity}{Action}IntegrationEvent.csã
- å®ç°è¦ç¹ï¼ä¸å¯åï¼ä¸ç´æ¥å¼ç¨èåå®ä¾ï¼é¿å ææä¿¡æ¯ï¼å¤æç±»ååæä»¶ä½¿ç¨ recordã
示ä¾ï¼ç¨æ·å建éæäºä»¶
using ProjectName.Domain.AggregatesModel.UserAggregate;
namespace ProjectName.Web.Application.IntegrationEvents;
public record UserCreatedIntegrationEvent(UserId UserId, string Name, string Email, DateTime CreatedTime);
éæäºä»¶è½¬æ¢å¨
- å½åè§åï¼{Entity}{Action}IntegrationEventConverterã
- ç®å½ï¼src/ProjectName.Web/Application/IntegrationEventConvertersã
- æä»¶åï¼{Entity}{Action}IntegrationEventConverter.csã
- å®ç°è¦ç¹ï¼å®ç° IIntegrationEventConverter<TDomainEvent, TIntegrationEvent>ï¼å°é¢åäºä»¶æ å°ä¸ºéæäºä»¶ï¼ä½¿ç¨ recordã
示ä¾ï¼é¢åäºä»¶å°éæäºä»¶
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);
}
}
éæäºä»¶å¤çå¨
- å½åè§åï¼{IntegrationEvent}HandlerFor{Action}ã
- ç®å½ï¼src/ProjectName.Web/Application/IntegrationEventHandlersã
- æä»¶åï¼{Name}.csï¼æ¯æä»¶ä¸ä¸ªå¤çå¨ï¼ã
- å®ç°è¦ç¹ï¼å®ç° IIntegrationEventHandlerï¼å¨ HandleAsync ä¸éè¿å½ä»¤é©±å¨èåï¼ä¸ç´æ¥ä¿®æ¹ Dbï¼ä¸»æé 彿°æ³¨å ¥ä¾èµã
示ä¾ï¼å¤çéæäºä»¶
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);
}
}
åå æµè¯
- å½åè§åï¼{Method}{Scenario}{Expected}ã
- ç®å½ï¼test/ProjectName.{Layer}.Tests/ã
- æä»¶åï¼{Entity}Tests.csï¼æææ¨¡åæåï¼ã
- å®ç°è¦ç¹ï¼
- éç¨ AAA 模å¼ï¼åæµååºæ¯ï¼è¦çæ£å¸¸/å¼å¸¸ãé¢åäºä»¶ãç¶æ/ä¸åéãè¾¹çã
- ä½¿ç¨ Theory/InlineDataï¼å¼ºç±»å ID ç´æ¥ new æ¯è¾ï¼æ¶é´éç¨ç¸å¯¹æ¯è¾ï¼>= çï¼ã
- ä½¿ç¨ GetDomainEvents() æ ¡éªäºä»¶ç±»å䏿°éï¼å¯ç¨å·¥å/Builder çææµè¯æ°æ®ã
æäº¤åèªæ£
- å½ä»¤/æ¥è¯¢/å¤çå¨å为 asyncï¼ä¼ é CancellationTokenï¼æªåºç° SaveChanges/UpdateAsync æå¨è°ç¨ã
- é¢åäºä»¶åå¸å®æ´ï¼å¤çå¨ä¸ç´æ¥è·¨èåæ¹æ°æ®ï¼éæäºä»¶æ èåå¼ç¨ï¼å å«å¿ è¦å®¡è®¡ä¿¡æ¯ã
- API 端ç¹ä» è°ç¨ mediatorï¼è¯·æ±/ååºä¸å¼ºç±»å ID ä¸è§£å .Valueï¼è·¯ç±/æ ç¾/é´ææ£ç¡®ãæ¥è¯¢è¾åºå è®¸ä½¿ç¨ Response æ DTOã
- EF é ç½®å å«ä¸»é®ãé¿åº¦ãå¿ å¡«ãæ³¨éï¼å¼ºç±»å ID ä½¿ç¨æ åå¼çæå¨ã
示ä¾ï¼èååæµ
public class UserTests
{
[Fact]
public void Constructor_ShouldRaiseCreatedEvent()
{
var user = new User("Alice", "alice@example.com");
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", "old@example.com");
user.ClearDomainEvents();
user.ChangeEmail("new@example.com");
Assert.Equal("new@example.com", user.Email);
Assert.IsType<UserEmailChangedDomainEvent>(user.GetDomainEvents().Single());
}
}
KnownException åè
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();
å¸¸ç¨ using æç¤º
- Web å ¨å±ï¼global using FluentValidation; MediatR; NetCorePal.Extensions.Primitives; FastEndpoints; NetCorePal.Extensions.Dto; NetCorePal.Extensions.Domainã
- Infrastructure å ¨å±ï¼global using Microsoft.EntityFrameworkCore; Microsoft.EntityFrameworkCore.Metadata.Builders; NetCorePal.Extensions.Primitivesã
- Domain å ¨å±ï¼global using NetCorePal.Extensions.Domain; NetCorePal.Extensions.Primitivesã
- Tests å ¨å±ï¼global using Xunit; NetCorePal.Extensions.Primitivesã
- é¢å¤ï¼æ¥è¯¢å¤çå¨éå¼ç¨èåå½å空é´ä¸ Infrastructureï¼å®ä½é ç½®éå¼ç¨å¯¹åºèåï¼ç«¯ç¹éå¼ç¨èåãå½ä»¤ãæ¥è¯¢å½å空é´ã