api-versioning
API versioning strategies for ASP.NET Core. Covers Asp.Versioning library, URL segment,…
Domain-Driven Design tactical patterns for .NET applications. Covers aggregates, aggregate roots, value objects, domain events, domain services, strongly-typed IDs, and repository patterns for aggregate persistence. Load this skill when implementing DDD, working with aggregates,
$ npx -y skills add codewithmukesh/dotnet-claude-kit --skill ddd --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/dddContext preview
The summary Claude sees to decide when to auto-load this skill.
Domain-Driven Design tactical patterns for .NET applications. Covers aggregates, aggregate roots, value objects, domain events, domain services, strongly-typed IDs, and repository patterns for aggregate persistence. Load this skill when implementing DDD, working with aggregates,
name: ddd description: > Domain-Driven Design tactical patterns for .NET applications. Covers aggregates, aggregate roots, value objects, domain events, domain services, strongly-typed IDs, and repository patterns for aggregate persistence. Load this skill when implementing DDD, working with aggregates, value objects, domain events, bounded contexts, or when the architecture-advisor recommends DDD + Clean Architecture. Pair with the clean-architecture skill.
1. **Aggregates define consistency boundaries** — An aggregate is a cluster of entities and value objects treated as a single unit for data changes. All invariants within an aggregate are enforced in a single transaction. Cross-aggregate consistency is eventual. 2. **Value objects over primitives** — Replace primitive obsession with value objects. `Money`, `EmailAddress`, `OrderNumber` are not strings — they carry validation, equality, and behavior. Use C# records for immutable value objects. 3. **Domain events decouple side effects** — When something meaningful happens in the domain (OrderPlaced, PaymentReceived), raise a domain event. Side effects (send email, update read model, notify another aggregate) subscribe to these events. The aggregate stays focused on its own rules. 4. **Aggregate root is the sole entry point** — External code accesses an aggregate only through its root entity. Child entities are never loaded or modified independently. The root enforces all invariants for the entire aggregate. 5. **Repositories persist aggregates, not entities** — One repository per aggregate root. The repository loads and saves the entire aggregate as a unit. No repository for child entities. The Infrastructure implementation uses `DbContext` internally — this is a DDD tactical pattern for aggregate boundaries, not a generic CRUD wrapper.
The aggregate root owns all access to its children and enforces invariants:
// Domain/Orders/Order.cs
public sealed class Order : AggregateRoot
{
private readonly List<OrderLine> _lines = [];
private Order() { } // EF Core
public OrderNumber Number { get; private set; } = null!;
public CustomerId CustomerId { get; private set; }
public Money Total { get; private set; } = Money.Zero("USD");
public OrderStatus Status { get; private set; }
public DateTimeOffset PlacedAt { get; private set; }
public IReadOnlyList<OrderLine> Lines => _lines.AsReadOnly();
public static Order Place(CustomerId customerId, OrderNumber number, DateTimeOffset now)
{
var order = new Order
{
Id = Guid.CreateVersion7(),
CustomerId = customerId,
Number = number,
Status = OrderStatus.Placed,
PlacedAt = now
};
order.RaiseDomainEvent(new OrderPlaced(order.Id, customerId, now));
return order;
}
public Result AddLine(ProductId productId, int quantity, Money unitPrice)
{
if (Status is not OrderStatus.Placed)
return Result.Failure("Cannot modify a confirmed or cancelled order");
if (quantity <= 0)
return Result.Failure("Quantity must be positive");
var existing = _lines.FirstOrDefault(l => l.ProductId == productId);
if (existing is not null)
{
existing.IncreaseQuantity(quantity);
}
else
{
_lines.Add(new OrderLine(productId, quantity, unitPrice));
}
RecalculateTotal();
return Result.Success();
}
public Result Confirm()
{
if (Status is not OrderStatus.Placed)
return Result.Failure("Only placed orders can be confirmed");
if (_lines.Count == 0)
return Result.Failure("Cannot confirm an order with no lines");
Status = OrderStatus.Confirmed;
RaiseDomainEvent(new OrderConfirmed(Id));
return Result.Success();
}
private void RecalculateTotal()
{
Total = _lines.Aggregate(Money.Zero(Total.Currency), (sum, line) => sum + line.Subtotal);
}
}Use C# records for immutable value objects with structural equality:
// Domain/Common/Money.cs
public sealed record Money
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency)
{
ArgumentOutOfRangeException.ThrowIfNegative(amount);
ArgumentException.ThrowIfNullOrWhiteSpace(currency);
Amount = amount;
Currency = currency.ToUpperInvariant();
}
public static Money Zero(string currency) => new(0, currency);
public static Money operator +(Money left, Money right)
{
if (left.Currency != right.Currency)
throw new InvalidOperationException($"Cannot add {left.Currency} and {right.Currency}");
return new Money(left.Amount + right.Amount, left.Currency);
}
}
// Other value objects (EmailAddress, OrderNumber, etc.) follow the same pattern:
// sealed record, constructor validation, no public settersPrevent mixing up GUIDs from different entities:
// Domain/Common/StronglyTypedId.cs
public readonly record struct CustomerId(Guid Value)
{
public static CustomerId New() => new(Guid.CreateVersion7());
public override string ToString() => Value.ToString();
}
public readonly record struct ProductId(Guid Value)
{
public static ProductId New() => new(Guid.CreateVersion7());
}
public readonly record struct OrderNumber(string Value)
{
public override string ToString() => Value;
}
// Infrastructure/Persistence/Configurations/OrderConfiguration.cs
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.HasKey(o => o.Id);
builder.Property(o => o.CustomerIdAPI versioning strategies for ASP.NET Core. Covers Asp.Versioning library, URL segment,…
Architecture conformance check: verifies an existing codebase against its declared…
Architecture selection advisor for .NET applications. Asks structured questions about domain…
.NET Aspire for cloud-native orchestration. Covers AppHost configuration, service defaults,…
Authentication and authorization for ASP.NET Core. Covers JWT bearer tokens, OpenID Connect,…
Autonomous iteration loops for .NET: drive a broken build or failing test suite to green with…