Back to skills
SKILL.md
Easyplatform Backend
ASecurityComplete Easy.Platform backend development for EasyPlatform. Covers CQRS commands/queries, entities, validation, migrations, background jobs, and message bus. Use for any .NET backend task in this monorepo.
- 666 stars
- 0 votes
- 0 copies
- 2 views
- Added February 8, 2026
Works with
Security analysis
100/100npx -y skills add majiayu000/claude-skill-registry --skill easyplatform-backend --agent claude-codeAre you the author of Easyplatform Backend?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/majiayu000-easyplatform-backend)---
name: easyplatform-backend
description: Complete Easy.Platform backend development for EasyPlatform. Covers CQRS commands/queries, entities, validation, migrations, background jobs, and message bus. Use for any .NET backend task in this monorepo.
infer: true
allowed-tools: Read, Write, Edit, Grep, Glob, Bash
---
# Easy.Platform Backend Development
Complete backend development patterns for EasyPlatform .NET 9 microservices.
## Quick Decision Tree
```
[Backend Task]
├── API endpoint?
│ ├── Creates/Updates/Deletes data → CQRS Command (§1)
│ └── Reads data → CQRS Query (§2)
├── Business entity? → Entity Development (§3)
├── Side effects (notifications, emails)? → Entity Event Handler (§4) - NEVER in command handlers!
├── Data transformation/backfill? → Migration (§5)
├── Scheduled/recurring task? → Background Job (§6)
└── Cross-service sync? → Message Bus (§7) - NEVER direct DB access!
```
## File Organization
```
{Service}.Application/
├── UseCaseCommands/{Feature}/Save{Entity}Command.cs # Command+Handler+Result
├── UseCaseQueries/{Feature}/Get{Entity}ListQuery.cs # Query+Handler+Result
├── UseCaseEvents/{Feature}/*EntityEventHandler.cs # Side effects
├── BackgroundJobs/{Feature}/*Job.cs # Scheduled tasks
├── MessageBusProducers/*Producer.cs # Outbound events
├── MessageBusConsumers/{Entity}/*Consumer.cs # Inbound events
└── DataMigrations/*DataMigration.cs # Data migrations
{Service}.Domain/
└── Entities/{Entity}.cs # Domain entities
```
## Critical Rules
1. **Repository:** `IPlatformQueryableRootRepository<T>` -- NEVER throw exceptions for validation
2. **Validation:** `PlatformValidationResult` fluent API
3. **Side Effects:** Entity Event Handlers -- NEVER in command handlers
4. **DTO Mapping:** DTOs own mapping via `PlatformEntityDto<T,K>.MapToEntity()`
5. **Cross-Service:** Message bus -- NEVER direct database access
---
## §1. CQRS Commands
**File:** `UseCaseCommands/{Feature}/Save{Entity}Command.cs` (Command + Result + Handler in ONE file)
```csharp
public sealed class SaveEmployeeCommand : PlatformCqrsCommand<SaveEmployeeCommandResult>
{
public string? Id { get; set; }
public string Name { get; set; } = "";
public override PlatformValidationResult<IPlatformCqrsRequest> Validate()
=> base.Validate().And(_ => Name.IsNotNullOrEmpty(), "Name required");
}
public sealed class SaveEmployeeCommandResult : PlatformCqrsCommandResult
{
public EmployeeDto Entity { get; set; } = null!;
}
internal sealed class SaveEmployeeCommandHandler :
PlatformCqrsCommandApplicationHandler<SaveEmployeeCommand, SaveEmployeeCommandResult>
{
protected override async Task<SaveEmployeeCommandResult> HandleAsync(
SaveEmployeeCommand req, CancellationToken ct)
{
var entity = req.Id.IsNullOrEmpty()
? req.MapToNewEntity().With(e => e.CreatedBy = RequestContext.UserId())
: await repository.GetByIdAsync(req.Id, ct)
.EnsureFound().Then(e => req.UpdateEntity(e));
await entity.ValidateAsync(repository, ct).EnsureValidAsync();
await repository.CreateOrUpdateAsync(entity, ct);
return new SaveEmployeeCommandResult { Entity = new EmployeeDto(entity) };
}
}
```
**⚠️ MUST READ:** [references/cqrs-patterns.md](references/cqrs-patterns.md) — full CQRS command patterns
---
## §2. CQRS Queries
**File:** `UseCaseQueries/{Feature}/Get{Entity}ListQuery.cs`
```csharp
public sealed class GetEmployeeListQuery : PlatformCqrsPagedQuery<GetEmployeeListQueryResult, EmployeeDto>
{
public List<Status> Statuses { get; set; } = [];
public string? SearchText { get; set; }
}
internal sealed class GetEmployeeListQueryHandler :
PlatformCqrsQueryApplicationHandler<GetEmployeeListQuery, GetEmployeeListQueryResult>
{
protected override async Task<GetEmployeeListQueryResult> HandleAsync(
GetEmployeeListQuery req, CancellationToken ct)
{
var qb = repository.GetQueryBuilder((uow, q) => q
.Where(e => e.CompanyId == RequestContext.CurrentCompanyId())
.WhereIf(req.Statuses.Any(), e => req.Statuses.Contains(e.Status))
.PipeIf(req.SearchText.IsNotNullOrEmpty(), q =>
searchService.Search(q, req.SearchText, Employee.DefaultFullTextSearchColumns())));
var (total, items) = await (
repository.CountAsync((uow, q) => qb(uow, q), ct),
repository.GetAllAsync((uow, q) => qb(uow, q)
.OrderByDescending(e => e.CreatedDate).PageBy(req.SkipCount, req.MaxResultCount), ct)
);
return new GetEmployeeListQueryResult(items.SelectList(e => new EmployeeDto(e)), total, req);
}
}
```
**⚠️ MUST READ:** [references/cqrs-patterns.md](references/cqrs-patterns.md) — full CQRS query patterns
---
## §3. Entity Development
**File:** `{Service}.Domain/Entities/{Entity}.cs`
```csharp
[TrackFieldUpdatedDomainEvent]
public sealed class Employee : RootAuditedEntity<Employee, string, string>
{
[TrackFieldUpdatedDomainEvent] public string Name { get; set; } = "";
public string CompanyId { get; set; } = "";
[ComputedEntityProperty]
public string DisplayName { get => $"{Code} - {Name}"; set { } }
public static Expression<Func<Employee, bool>> OfCompanyExpr(string companyId) => e => e.CompanyId == companyId;
public static Expression<Func<Employee, bool>> UniqueExpr(string companyId, string code) => e => e.CompanyId == companyId && e.Code == code;
public static Expression<Func<Employee, object?>>[] DefaultFullTextSearchColumns() => [e => e.Name, e => e.Code];
public async Task<PlatformValidationResult> ValidateAsync(IRepository<Employee> repo, CancellationToken ct)
=> await PlatformValidationResult.Valid()
.And(() => Name.IsNotNullOrEmpty(), "Name required")
.AndNotAsync(() => repo.AnyAsync(e => e.Id != Id && e.Code == Code, ct), "Code exists");
}
```
**⚠️ MUST READ:** [references/entity-patterns.md](references/entity-patterns.md) — full entity patterns
---
## §4. Entity Event Handlers (Side Effects)
**CRITICAL:** NEVER call side effects in command handlers!
**File:** `UseCaseEvents/{Feature}/Send{Action}On{Event}{Entity}EntityEventHandler.cs`
```csharp
internal sealed class SendNotificationOnCreateEmployeeEntityEventHandler
: PlatformCqrsEntityEventApplicationHandler<Employee>
{
public override async Task<bool> HandleWhen(PlatformCqrsEntityEvent<Employee> @event)
=> !@event.RequestContext.IsSeedingTestingData()
&& @event.CrudAction == PlatformCqrsEntityEventCrudAction.Created;
protected override async Task HandleAsync(PlatformCqrsEntityEvent<Employee> @event, CancellationToken ct)
=> await notificationService.SendAsync(@event.EntityData.Id);
}
```
**⚠️ MUST READ:** [references/side-effects-patterns.md](references/side-effects-patterns.md) — side effects patterns
## §5-7. Migrations, Jobs, Message Bus
**⚠️ MUST READ:** [migration-patterns.md](references/migration-patterns.md) | [job-patterns.md](references/job-patterns.md) | [messaging-patterns.md](references/messaging-patterns.md)
---
## Anti-Patterns
| Don't | Do |
| --------------------------------- | ----------------------------------------------- |
| `throw new ValidationException()` | `PlatformValidationResult` fluent API |
| Side effects in command handler | Entity Event Handler in `UseCaseEvents/` |
| Direct cross-service DB access | Message bus |
| DTO mapping in handler | `PlatformEntityDto.MapToEntity()` |
| Separate Command/Handler files | ONE file: Command + Result + Handler |
| `protected bool HandleWhen()` | `public override async Task<bool> HandleWhen()` |
## Checklist
- [ ] Service-specific repository, fluent validation (`.And()`, `.AndAsync()`)
- [ ] No side effects in command handlers, DTO mapping in DTO class
- [ ] Cross-service uses message bus, jobs have `maxConcurrent`, migrations use `dismissSendEvent: true`
## IMPORTANT Task Planning Notes
- Always plan and break many small todo tasks
- Always add a final review todo task to review the works done at the end to find any fix or enhancement needed
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.