backend-feature
Scaffolds backend features including new domain entities, new properties on existing entities, and new business operations. Use when creating a new domain entity, extending an existing entity with new
backend
feature
SKILL.md.claude/skills/backend-feature
GitHubBackend Feature Scaffolding
This skill handles three scenarios:
- New Entity - Create a complete vertical slice for a new domain entity
- New Property - Add properties to an existing entity
- New Feature - Add business operations (commands/queries) to an existing entity
Scenario 1: New Entity
For a new entity named [EntityName] (e.g., Customer, Order, Product), this skill generates:
Domain/
└── [EntityName]s/
├── [EntityName].cs # Rich domain entity
├── DomainEvents/
│ ├── [EntityName]Created.cs
│ └── [EntityName]Updated.cs
├── Dtos/
│ ├── [EntityName]Dto.cs
│ ├── [EntityName]ForCreationDto.cs
│ ├── [EntityName]ForUpdateDto.cs
│ └── [EntityName]ParametersDto.cs
├── Features/
│ ├── Add[EntityName].cs
│ ├── Update[EntityName].cs
│ ├── Delete[EntityName].cs
│ ├── Get[EntityName].cs
│ └── Get[EntityName]List.cs
├── Mappings/
│ └── [EntityName]Mapper.cs
└── Models/
├── [EntityName]ForCreation.cs
└── [EntityName]ForUpdate.cs
Databases/
└── EntityConfigurations/
└── [EntityName]Configuration.cs # EF Core entity configuration
Controllers/
└── v1/
└── [EntityName]sController.cs
Each entity needs public const string IdPrefix with 1 to 17 lower-case ASCII letters. The prefix must be unique. Each constructor must call base(IdPrefix) with the IdPrefix of its own type. An entity must derive from BaseEntity, or from an abstract base class that has no IdPrefix, never from another entity. The build fails when an entity breaks one of these rules (FSTID001-005).
An abstract base entity has no IdPrefix. Its constructor must take the prefix as a parameter and give that parameter to BaseEntity, for example protected AuditedEntity(string idPrefix) : base(idPrefix) { }. The prefix parameter must have the name idPrefix. Each concrete entity then calls base(IdPrefix) with its own IdPrefix. The build fails when the abstract constructor gives BaseEntity a different value, or when the abstract class has no constructor (FSTID004). It also fails when an entity derives from an abstract class that has an IdPrefix (FSTID005).
Scenario 2: New Property on Existing Entity
When adding properties to an existing entity, update these files:
| File | Changes |
|---|---|
[EntityName].cs |
Add property with private setter |
[EntityName]ForCreation.cs |
Add property if set at creation |
[EntityName]ForUpdate.cs |
Add property if updatable |
[EntityName]ForCreationDto.cs |
Add property if set at creation |
[EntityName]ForUpdateDto.cs |
Add property if updatable |
[EntityName]Dto.cs |
Add property for response |
[EntityName]Mapper.cs |
Add custom mapping if value object |
[EntityName]Configuration.cs |
Configure property constraints, value objects |
Scenario 3: New Feature on Existing Entity
When adding a business operation to an existing entity:
| Component | Files to Create/Modify |
|---|---|
| Domain method | [EntityName].cs - Add behavior method |
| Domain event | DomainEvents/[EntityName][Action].cs - New event |
| Feature | Features/[Action][EntityName].cs - New command/query |
| Controller | [EntityName]sController.cs - Add endpoint |
| DTOs (if needed) | Dtos/[Action][EntityName]Dto.cs - Request/response |
Usage Examples
New Entity
Create a backend feature for managing Products with name, price, and category
New Property on Existing Entity
Add an email property to the Customer entity
Add a status property with Draft, Active, and Archived states to the Product entity
New Feature on Existing Entity
Add a Submit feature to the Order entity that changes status and validates requirements
Add a GetOrdersByCustomer query that returns orders for a specific customer
Process: New Entity
Gather Requirements
- Entity name (PascalCase, singular:
Customer,Order,Product) - Properties and their types
- Any special business rules or value objects needed
- Entity name (PascalCase, singular:
Generate Domain Entity
- Create rich domain entity following DDD principles
- Private setters, factory methods, encapsulated behavior
- Value objects for complex concepts
- See:
docs/backend/domain-entities.md
Generate Domain Events
- Created and Updated events (no deleted event, because deletes are soft)
- Additional business-specific events as needed
- See:
docs/backend/domain-events.md
Generate DTOs and Models
- Read DTO for responses
- Creation/Update DTOs for requests
- Parameters DTO for list queries
- Internal models for domain method contracts
- See:
docs/backend/dtos-and-mappings.md
Generate Mapperly Mapper
- Entity to DTO mappings
- DTO to internal model mappings
- IQueryable projection support
- See:
docs/backend/dtos-and-mappings.md
Generate CQRS Features
- Add, Update, Delete commands
- Get single and Get list queries
- MediatR handlers with proper patterns
- See:
docs/backend/features-and-cqrs.md
Generate Controller
- Thin controller delegating to MediatR
- Proper API versioning
- OpenAPI documentation
- See:
docs/backend/controllers.md
Generate Entity Configuration
- Create
IEntityTypeConfiguration<T>class - Configure property constraints and indexes
- Configure value object mappings with
ComplexProperty - See:
docs/backend/entity-configurations.md
- Create
Update Infrastructure
- Add DbSet to AppDbContext
- Ensure
ApplyConfigurationsFromAssemblyis called in OnModelCreating
Generate the API Contract
- Build the Server, then run
pnpm generate:apiinfrontend/ - Commit the generated files with the change. Do not edit them by hand
Process: New Property
Read Existing Entity
- Understand current entity structure and patterns
- Identify if property is a simple type or value object
Add Property to Domain Entity
// Simple property public string PhoneNumber { get; private set; } // Value object property public EmailAddress Email { get; private set; } = default!;Update Factory Method (if set at creation)
public static Customer Create(CustomerForCreation forCreation) { var entity = new Customer { // ... existing properties PhoneNumber = forCreation.PhoneNumber, // Add new property }; // ... }Update Update Method (if updatable)
public Customer Update(CustomerForUpdate forUpdate) { // ... existing updates PhoneNumber = forUpdate.PhoneNumber; // Add new property // ... }Update Internal Models
- Add to
[EntityName]ForCreation.csif set at creation - Add to
[EntityName]ForUpdate.csif updatable
- Add to
Update DTOs
- Add to
[EntityName]Dto.csfor responses - Add to
[EntityName]ForCreationDto.csif set at creation - Add to
[EntityName]ForUpdateDto.csif updatable
- Add to
Update Mapper (for value objects)
// Add custom mapping for value objects private static string? MapEmail(EmailAddress? email) => email?.Value;Update Entity Configuration
// Simple property builder.Property(e => e.PhoneNumber).HasMaxLength(20); // Value object builder.ComplexProperty(e => e.Email, email => { email.Property(e => e.Value).HasColumnName("email").HasMaxLength(320); });Create Migration
dotnet ef migrations add Add[PropertyName]To[EntityName]Generate the API Contract
- Build the Server, then run
pnpm generate:apiinfrontend/ - Commit the generated files with the change. Do not edit them by hand
Process: New Feature
Read Existing Entity
- Understand current entity structure
- Identify what state changes are needed
- Determine guard conditions
Add Domain Method to Entity
public Order Submit() { GuardIfNotDraft("Cannot submit"); Status = OrderStatus.Submitted(); QueueDomainEvent(new OrderSubmitted(Id, DateTimeOffset.UtcNow)); return this; }Create Domain Event (if needed)
// DomainEvents/OrderSubmitted.cs public sealed record OrderSubmitted(string OrderId, DateTimeOffset SubmittedAt) : DomainEvent;Create Feature (Command or Query)
// Features/SubmitOrder.cs public static class SubmitOrder { public sealed record Command(string Id) : IRequest<OrderDto>; public sealed class Handler(AppDbContext dbContext) : IRequestHandler<Command, OrderDto> { public async Task<OrderDto> Handle(Command request, CancellationToken ct) { var order = await dbContext.Orders.GetById(request.Id, ct); order.Submit(); await dbContext.SaveChangesAsync(ct); return order.ToOrderDto(); } } }Add Endpoint to Controller
/// <summary> /// Submits an order for processing. /// </summary> [Authorize] [HttpPost("{id}/submit", Name = "SubmitOrder")] [ProducesResponseType(typeof(OrderDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)] public async Task<ActionResult<OrderDto>> SubmitOrder(string id) { var command = new SubmitOrder.Command(id); var result = await mediator.Send(command); return Ok(result); }Create Additional DTOs (if needed)
- Request DTO if the action needs input parameters
- Custom response DTO if different from standard entity DTO
Generate the API Contract
- Build the Server, then run
pnpm generate:apiinfrontend/ - Commit the generated files with the change. Do not edit them by hand
- Build the Server, then run
Important Guidelines
Domain Entity Design
- Use private setters for all properties
- Create static factory method
Create()for entity creation - Use
Update()method for modifications - Implement guard methods for business rules
- Use value objects for complex domain concepts
- Queue domain events on state changes
Property Naming
- Entity properties: PascalCase (
FirstName,Email) - DTO properties: PascalCase with
{ get; init; } - Route parameters: camelCase in URLs, PascalCase in code
Value Objects to Consider
EmailAddress- validated emailPhoneNumber- formatted phoneMoney- currency with amountAddress- street, city, state, zip- Status enums with SmartEnum pattern
Common Patterns
Status with Behavior:
public class OrderStatus : ValueObject
{
private OrderStatusEnum _status;
public bool IsFinalState() => _status.IsFinalState();
public bool CanBeCancelled() => !IsFinalState();
public static OrderStatus Draft() => new("Draft");
public static OrderStatus Submitted() => new("Submitted");
}
Encapsulated Collections:
private readonly List<LineItem> _lineItems = [];
public IReadOnlyCollection<LineItem> LineItems => _lineItems.AsReadOnly();
public LineItem AddLineItem(Product product, int quantity)
{
var item = LineItem.Create(product, quantity);
_lineItems.Add(item);
return item;
}
Checklists
New Entity Checklist
- Review generated entity for business logic completeness
- Add DbSet to
AppDbContext.cs - Verify entity configuration is in
Databases/EntityConfigurations/and properly applied - Each foreign-key column to an entity ID has
HasMaxLength(EntityId.MaxLength)andUseCollation(EntityId.Collation) - Each foreign key to an entity ID has a prefix check with
EntityId.IsValidin the entity validation - Install MediatR if not already present
- Install Mapperly if not already present
- Run
dotnet buildto verify compilation - Put validation in the entity, and put database checks such as unique values in the handler
- Consider what additional business methods the entity needs
- Run
dotnet ef migrations add Add[EntityName]to create migration - Run
pnpm generate:apiinfrontend/and commit the generated contract
New Property Checklist
- Property added with private setter in entity
- Factory method updated (if set at creation)
- Update method updated (if updatable)
- Internal models updated (ForCreation/ForUpdate)
- DTOs updated (Dto, ForCreationDto, ForUpdateDto as needed)
- Mapper updated (custom mapping for value objects)
- Entity configuration updated (constraints, value object mapping)
- A new foreign-key column to an entity ID has
HasMaxLength(EntityId.MaxLength)andUseCollation(EntityId.Collation) - A new foreign key to an entity ID has a prefix check with
EntityId.IsValidin the entity validation - Run
dotnet buildto verify compilation - Run
dotnet ef migrations add Add[PropertyName]To[EntityName] - Run
pnpm generate:apiinfrontend/and commit the generated contract
New Feature Checklist
- Domain method added to entity with proper guards
- Domain event created (if state change is significant)
- Feature file created with Command/Query and Handler
- Controller endpoint added with proper attributes
- Additional DTOs created if needed
- Run
dotnet buildto verify compilation - Run
pnpm generate:apiinfrontend/and commit the generated contract
Reference Files
These docs give the detailed patterns:
docs/backend/domain-entities.md- Rich domain entitiesdocs/backend/features-and-cqrs.md- CQRS with MediatRdocs/backend/dtos-and-mappings.md- DTOs and Mapperlydocs/backend/controllers.md- API controllersdocs/backend/domain-events.md- Domain eventsdocs/backend/entity-configurations.md- EF Core entity configurations