.agents/skills/abp-ddd/SKILL.md
ABP DDD patterns - Entities, Aggregate Roots, value objects, Repositories, Domain Services, Domain Events, Specifications. Use when designing domain layer, creating entities, repositories, or domain services in ABP projects.
npx skillsauth add afonsoft/VideoChat abp-dddInstall 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.
Docs: https://abp.io/docs/latest/framework/architecture/domain-driven-design
IGuidGenerator from outside and pass id parameterId only, never add full navigation properties across aggregatesABP promotes Rich Domain Model pattern where entities contain both data AND behavior:
| Anemic (Anti-pattern) | Rich (Recommended) | |----------------------|-------------------| | Entity = data only | Entity = data + behavior | | Logic in services | Logic in entity methods | | Public setters | Private setters with methods | | No validation in entity | Entity enforces invariants |
Encapsulation is key: Protect entity state by using private setters and exposing behavior through methods.
public class OrderLine : Entity<Guid>
{
public Guid ProductId { get; private set; }
public int Count { get; private set; }
public decimal Price { get; private set; }
protected OrderLine() { } // For ORM
internal OrderLine(Guid id, Guid productId, int count, decimal price) : base(id)
{
ProductId = productId;
SetCount(count); // Validates through method
Price = price;
}
public void SetCount(int count)
{
if (count <= 0)
throw new BusinessException("Orders:InvalidCount");
Count = count;
}
}
Aggregate roots are consistency boundaries that:
public class Order : AggregateRoot<Guid>
{
public string OrderNumber { get; private set; }
public Guid CustomerId { get; private set; }
public OrderStatus Status { get; private set; }
public ICollection<OrderLine> Lines { get; private set; }
protected Order() { } // For ORM
public Order(Guid id, string orderNumber, Guid customerId) : base(id)
{
OrderNumber = Check.NotNullOrWhiteSpace(orderNumber, nameof(orderNumber));
CustomerId = customerId;
Status = OrderStatus.Created;
Lines = new List<OrderLine>();
}
public void AddLine(Guid lineId, Guid productId, int count, decimal price)
{
// Business rule: Can only add lines to created orders
if (Status != OrderStatus.Created)
throw new BusinessException("Orders:CannotModifyOrder");
Lines.Add(new OrderLine(lineId, productId, count, price));
}
public void Complete()
{
if (Status != OrderStatus.Created)
throw new BusinessException("Orders:CannotCompleteOrder");
Status = OrderStatus.Completed;
// Publish events for side effects
AddLocalEvent(new OrderCompletedEvent(Id)); // Same transaction
AddDistributedEvent(new OrderCompletedEto { OrderId = Id }); // Cross-service
}
}
AddLocalEvent() - Handled within same transaction, can access full entityAddDistributedEvent() - Handled asynchronously, use ETOs (Event Transfer Objects)id parameterIGuidGenerator externallyIRepository<T, TKey>): Sufficient for simple CRUD operations// Define custom interface only when custom queries are needed
public interface IOrderRepository : IRepository<Order, Guid>
{
Task<Order> FindByOrderNumberAsync(string orderNumber, bool includeDetails = false);
Task<List<Order>> GetListByCustomerAsync(Guid customerId, bool includeDetails = false);
}
AddDefaultRepositories() without includeAllEntities: true to enforce thisCancellationToken automatically; add parameter only for explicit cancellation controlincludeDetails = true by defaultincludeDetails = false by default// ✅ Correct: Repository for aggregate root (Order)
public interface IOrderRepository : IRepository<Order, Guid> { }
// ❌ Wrong: Repository for child entity (OrderLine)
// OrderLine should only be accessed through Order aggregate
public interface IOrderLineRepository : IRepository<OrderLine, Guid> { } // Don't do this!
Use domain services for business logic that:
public class OrderManager : DomainService
{
private readonly IOrderRepository _orderRepository;
private readonly IProductRepository _productRepository;
public OrderManager(
IOrderRepository orderRepository,
IProductRepository productRepository)
{
_orderRepository = orderRepository;
_productRepository = productRepository;
}
public async Task<Order> CreateAsync(string orderNumber, Guid customerId)
{
// Business rule: Order number must be unique
var existing = await _orderRepository.FindByOrderNumberAsync(orderNumber);
if (existing != null)
{
throw new BusinessException("Orders:OrderNumberAlreadyExists")
.WithData("OrderNumber", orderNumber);
}
return new Order(GuidGenerator.Create(), orderNumber, customerId);
}
public async Task AddProductAsync(Order order, Guid productId, int count)
{
var product = await _productRepository.GetAsync(productId);
order.AddLine(productId, count, product.Price);
}
}
*Manager suffix namingGuidGenerator, Clock) instead of injecting these services// In aggregate
AddLocalEvent(new OrderCompletedEvent(Id));
// Handler
public class OrderCompletedEventHandler : ILocalEventHandler<OrderCompletedEvent>, ITransientDependency
{
public async Task HandleEventAsync(OrderCompletedEvent eventData)
{
// Handle within same transaction
}
}
For inter-module/microservice communication:
// In Domain.Shared
[EventName("Orders.OrderCompleted")]
public class OrderCompletedEto
{
public Guid OrderId { get; set; }
public string OrderNumber { get; set; }
}
Reusable query conditions:
public class CompletedOrdersSpec : Specification<Order>
{
public override Expression<Func<Order, bool>> ToExpression()
{
return o => o.Status == OrderStatus.Completed;
}
}
// Usage
var orders = await _orderRepository.GetListAsync(new CompletedOrdersSpec());
development
This skill enables visual inspection of websites running locally or remotely to identify and fix design issues. Triggers on requests like "review website design", "check the UI", "fix the layout", "find design problems". Detects issues with responsive design, accessibility, visual consistency, and layout breakage, then performs fixes at the source code level.
testing
Comprehensive unit testing with xUnit, mocking, test patterns, and best practices for .NET applications
data-ai
Universal SQL performance optimization assistant for comprehensive query tuning, indexing strategies, and database performance analysis across all SQL databases (MySQL, PostgreSQL, SQL Server, Oracle). Provides execution plan analysis, pagination optimization, batch operations, and performance monitoring guidance.
development
Universal SQL code review assistant that performs comprehensive security, maintainability, and code quality analysis across all SQL databases (MySQL, PostgreSQL, SQL Server, Oracle). Focuses on SQL injection prevention, access control, code standards, and anti-pattern detection. Complements SQL optimization prompt for complete development coverage.