EasyPlatform Backend Development .NET

VerifiedCaution

Complete backend development guide for .NET 9 microservices using CQRS patterns, entities, validation, migrations, and message bus for Easy.Platform.

Sby Skills Guide Bot
DevelopmentAdvanced
3/11/2026
Claude Code
#dotnet#cqrs#backend#microservices#easy-platform

Recommended for

Automated summary

Guides .NET 9 backend development in the EasyPlatform monorepo using CQRS commands and queries, entities, validation, migrations, background jobs, and the message bus.

Strengths

  • Decision tree that routes each backend task to the correct pattern (command, query, event handler, migration, job, or message bus).
  • Enforces strong architecture rules: side effects belong in Entity Event Handlers, cross-service communication goes through the message bus, and DTOs own their mapping.
  • Specifies file organization and naming conventions for Application and Domain layers.
  • Points to detailed reference files for deeper CQRS patterns.

Limitations

  • Tightly coupled to the EasyPlatform monorepo and framework, so it is not reusable in other codebases.
  • Depends on external reference documents (e.g., cqrs-patterns.md) that are not included in the SKILL.md.
  • Assumes familiarity with in-house abstractions such as PlatformValidationResult and IPlatformQueryableRootRepository.
When to use it

Use when implementing or modifying a .NET backend feature inside the EasyPlatform monorepo.

When not to use it

Do not use for a generic .NET project or a different framework outside the EasyPlatform context.

Security analysis

Caution
Quality score82/100

The skill is a conventional .NET/CQRS development guide. It contains no obfuscated payloads, no curl|sh, no credential or token exfiltration, no destructive filesystem commands, and no instructions to disable safety checks. The only elevated risk is inherent to its declared toolset: Bash plus Write/Edit allows shell execution and broad source modification, which is appropriate for backend scaffolding but warrants the 'caution' rating rather than 'safe'.

Findings
  • •Declared allowed-tools include Bash, Write, and Edit, granting broad filesystem and shell capability within the repository
  • •Skill instructs agents to scaffold/modify CQRS handlers, entities, and migrations without any explicit guardrails on file scope or migration execution
How this analysis is made

Examples

Add a CQRS command
Create a SaveEmployeeCommand in the EasyPlatform backend that handles both create and update, with validation requiring a non-empty Name and returning an EmployeeDto.
Add a paged query
Add a GetEmployeeListQuery that supports paging, filters by a list of statuses and an optional search text, and returns EmployeeDto results.
Add an entity event handler
Add an entity event handler that sends a notification when an employee is updated, following the EasyPlatform side-effect pattern instead of putting it in the command handler.

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)

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 — full CQRS command patterns


§2. CQRS Queries

File: UseCaseQueries/{Feature}/Get{Entity}ListQuery.cs

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 — full CQRS query patterns


§3. Entity Development

File: {Service}.Domain/Entities/{Entity}.cs

[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 — 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

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 — side effects patterns

§5-7. Migrations, Jobs, Message Bus

⚠️ MUST READ: migration-patterns.md | job-patterns.md | 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

From the file to a first result

The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.

View the source and supporting files

This download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.

1. Download the file

Download SKILL.md

From the root of a test project, save the file as .skill-downloads/easyplatform-backend-development-net-mmlj2o1j/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.

Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.

mkdir -p '.skill-downloads/easyplatform-backend-development-net-mmlj2o1j' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/easyplatform-backend-development-net-mmlj2o1j/download' --output '.skill-downloads/easyplatform-backend-development-net-mmlj2o1j/SKILL.md.part' && mv '.skill-downloads/easyplatform-backend-development-net-mmlj2o1j/SKILL.md.part' '.skill-downloads/easyplatform-backend-development-net-mmlj2o1j/SKILL.md'

2. Check prerequisites

Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.

Tools declared by the author (check availability in your agent): Read, Write, Edit, Grep, Glob, Bash

No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.

The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.

3. Load the skill

After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.

Claude Code
test -f '.skill-downloads/easyplatform-backend-development-net-mmlj2o1j/SKILL.md' && test -s '.skill-downloads/easyplatform-backend-development-net-mmlj2o1j/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/easyplatform-backend' && cp -R '.skill-downloads/easyplatform-backend-development-net-mmlj2o1j' '.claude/skills/easyplatform-backend'

OpenSkills

OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.

Official OpenSkills installation
openskills install './.skill-downloads/easyplatform-backend-development-net-mmlj2o1j'

openskills read 'easyplatform-backend-development-net-mmlj2o1j'

Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.

For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.

openskills sync

Manual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.

4. Try a first task

Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:

Read .skill-downloads/easyplatform-backend-development-net-mmlj2o1j/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.

Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.

Related skills