---
name: domain-driven-design
description: DDD patterns cho complex business logic. Use for Aggregates, Value Objects, Entities, Domain Events, và Rich Domain Model.
compatibility: ".NET 8+, EF Core 8+"
metadata:
  author: Velik Ho
  version: "1.0"
---

# Domain-Driven Design Patterns / Mẫu DDD

DDD patterns cho GoodGo microservices với complex business logic.

## When to Use This Skill / Khi Nào Sử Dụng

Use this skill when:
- Modeling complex business domains / Mô hình hóa domain phức tạp
- Designing aggregates and entities / Thiết kế aggregates và entities
- Implementing business rules in domain / Triển khai business rules trong domain
- Creating value objects / Tạo value objects
- Raising domain events / Raise domain events

## Core Concepts / Khái Niệm Cốt Lõi

### DDD Building Blocks / Các Khối Xây Dựng DDD

```
┌─────────────────────────────────────────────────────────────┐
│                    DOMAIN MODEL LAYER                       │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────┐   │
│  │                 AGGREGATE ROOT                       │   │
│  │  ┌─────────────────┐  ┌─────────────────┐           │   │
│  │  │     Entity      │  │  Value Object   │           │   │
│  │  │   (Identity)    │  │  (No Identity)  │           │   │
│  │  └─────────────────┘  └─────────────────┘           │   │
│  │                                                     │   │
│  │  • Business Rules / Quy tắc nghiệp vụ               │   │
│  │  • Domain Events / Domain Events                    │   │
│  │  • Invariants / Bất biến                           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────┐  ┌─────────────────┐                  │
│  │  Domain Service │  │ Domain Events   │                  │
│  └─────────────────┘  └─────────────────┘                  │
└─────────────────────────────────────────────────────────────┘
```

### Entity vs Value Object / Entity vs Value Object

| Aspect | Entity | Value Object |
|--------|--------|--------------|
| **Identity** | Has unique ID | No identity |
| **Equality** | By ID | By all properties |
| **Mutability** | Mutable (via methods) | Immutable |
| **Lifecycle** | Independent | Belongs to Entity |
| **Example** | Order, User | Address, Money |

### Aggregate Rules / Quy Tắc Aggregate

1. **One repository per aggregate root** / Một repository cho mỗi aggregate root
2. **Reference only by ID** / Chỉ tham chiếu qua ID
3. **Atomic transaction boundary** / Ranh giới transaction atomic
4. **Consistency within aggregate** / Nhất quán trong aggregate

## Key Patterns / Mẫu Chính

### Entity Base Class

```csharp
/// <summary>
/// EN: Base class for all entities.
/// VI: Base class cho tất cả entities.
/// </summary>
public abstract class Entity
{
    private int? _requestedHashCode;
    private List<IDomainEvent>? _domainEvents;

    public virtual Guid Id { get; protected set; }
    
    public IReadOnlyCollection<IDomainEvent> DomainEvents 
        => _domainEvents?.AsReadOnly() ?? Array.Empty<IDomainEvent>().AsReadOnly();

    public void AddDomainEvent(IDomainEvent eventItem)
    {
        _domainEvents ??= new List<IDomainEvent>();
        _domainEvents.Add(eventItem);
    }

    public void RemoveDomainEvent(IDomainEvent eventItem)
    {
        _domainEvents?.Remove(eventItem);
    }

    public void ClearDomainEvents()
    {
        _domainEvents?.Clear();
    }

    public bool IsTransient()
    {
        return Id == default;
    }

    public override bool Equals(object? obj)
    {
        if (obj is not Entity other)
            return false;

        if (ReferenceEquals(this, other))
            return true;

        if (GetType() != other.GetType())
            return false;

        if (IsTransient() || other.IsTransient())
            return false;

        return Id.Equals(other.Id);
    }

    public override int GetHashCode()
    {
        if (!IsTransient())
        {
            _requestedHashCode ??= Id.GetHashCode() ^ 31;
            return _requestedHashCode.Value;
        }
        return base.GetHashCode();
    }

    public static bool operator ==(Entity? left, Entity? right)
    {
        return left?.Equals(right) ?? right is null;
    }

    public static bool operator !=(Entity? left, Entity? right)
    {
        return !(left == right);
    }
}
```

### Aggregate Root

```csharp
/// <summary>
/// EN: Marker interface for aggregate roots.
/// VI: Interface đánh dấu aggregate roots.
/// </summary>
public interface IAggregateRoot { }

/// <summary>
/// EN: Order aggregate root with business rules.
/// VI: Order aggregate root với business rules.
/// </summary>
public class Order : Entity, IAggregateRoot
{
    private readonly List<OrderItem> _orderItems = new();
    
    public string UserId { get; private set; }
    public Address ShippingAddress { get; private set; }
    public OrderStatus Status { get; private set; }
    public decimal TotalAmount { get; private set; }
    public DateTime CreatedAt { get; private set; }
    public DateTime? SubmittedAt { get; private set; }

    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly();

    // EN: Required by EF Core
    private Order() { }

    public Order(string userId, Address shippingAddress)
    {
        Id = Guid.NewGuid();
        UserId = userId ?? throw new ArgumentNullException(nameof(userId));
        ShippingAddress = shippingAddress ?? throw new ArgumentNullException(nameof(shippingAddress));
        Status = OrderStatus.Draft;
        CreatedAt = DateTime.UtcNow;
        TotalAmount = 0;

        // EN: Raise domain event
        // VI: Raise domain event
        AddDomainEvent(new OrderCreatedDomainEvent(Id, userId));
    }

    public void AddItem(Guid productId, int quantity, decimal unitPrice)
    {
        // EN: Business rule: Can only add items to draft orders
        // VI: Quy tắc: Chỉ thêm items vào orders draft
        if (Status != OrderStatus.Draft)
            throw new DomainException("Cannot add items to non-draft order");

        if (quantity <= 0)
            throw new ArgumentException("Quantity must be positive", nameof(quantity));

        if (unitPrice < 0)
            throw new ArgumentException("Price cannot be negative", nameof(unitPrice));

        var existingItem = _orderItems.FirstOrDefault(i => i.ProductId == productId);
        if (existingItem != null)
        {
            existingItem.IncreaseQuantity(quantity);
        }
        else
        {
            _orderItems.Add(new OrderItem(productId, quantity, unitPrice));
        }

        RecalculateTotal();
    }

    public void RemoveItem(Guid productId)
    {
        if (Status != OrderStatus.Draft)
            throw new DomainException("Cannot remove items from non-draft order");

        var item = _orderItems.FirstOrDefault(i => i.ProductId == productId);
        if (item != null)
        {
            _orderItems.Remove(item);
            RecalculateTotal();
        }
    }

    public void Submit()
    {
        // EN: Business rule: Cannot submit empty order
        // VI: Quy tắc: Không thể submit order trống
        if (!_orderItems.Any())
            throw new DomainException("Cannot submit empty order");

        if (Status != OrderStatus.Draft)
            throw new DomainException($"Cannot submit order in {Status} status");

        Status = OrderStatus.Submitted;
        SubmittedAt = DateTime.UtcNow;

        AddDomainEvent(new OrderSubmittedDomainEvent(Id, UserId, TotalAmount));
    }

    public void Cancel(string reason)
    {
        if (Status == OrderStatus.Shipped || Status == OrderStatus.Delivered)
            throw new DomainException("Cannot cancel shipped or delivered order");

        Status = OrderStatus.Cancelled;
        AddDomainEvent(new OrderCancelledDomainEvent(Id, reason));
    }

    private void RecalculateTotal()
    {
        TotalAmount = _orderItems.Sum(i => i.Quantity * i.UnitPrice);
    }
}
```

### Value Object

```csharp
/// <summary>
/// EN: Base class for value objects.
/// VI: Base class cho value objects.
/// </summary>
public abstract class ValueObject
{
    protected abstract IEnumerable<object?> GetEqualityComponents();

    public override bool Equals(object? obj)
    {
        if (obj is null || obj.GetType() != GetType())
            return false;

        var other = (ValueObject)obj;
        return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
    }

    public override int GetHashCode()
    {
        return GetEqualityComponents()
            .Select(x => x?.GetHashCode() ?? 0)
            .Aggregate((x, y) => x ^ y);
    }

    public static bool operator ==(ValueObject? left, ValueObject? right)
    {
        return left?.Equals(right) ?? right is null;
    }

    public static bool operator !=(ValueObject? left, ValueObject? right)
    {
        return !(left == right);
    }
}

/// <summary>
/// EN: Address value object.
/// VI: Value object địa chỉ.
/// </summary>
public class Address : ValueObject
{
    public string Street { get; }
    public string City { get; }
    public string State { get; }
    public string PostalCode { get; }
    public string Country { get; }

    public Address(string street, string city, string state, string postalCode, string country)
    {
        Street = street ?? throw new ArgumentNullException(nameof(street));
        City = city ?? throw new ArgumentNullException(nameof(city));
        State = state ?? throw new ArgumentNullException(nameof(state));
        PostalCode = postalCode ?? throw new ArgumentNullException(nameof(postalCode));
        Country = country ?? throw new ArgumentNullException(nameof(country));
    }

    protected override IEnumerable<object?> GetEqualityComponents()
    {
        yield return Street;
        yield return City;
        yield return State;
        yield return PostalCode;
        yield return Country;
    }
}

/// <summary>
/// EN: Money value object with currency.
/// VI: Value object tiền tệ.
/// </summary>
public class Money : ValueObject
{
    public decimal Amount { get; }
    public string Currency { get; }

    public Money(decimal amount, string currency)
    {
        if (amount < 0)
            throw new ArgumentException("Amount cannot be negative");
        
        Amount = amount;
        Currency = currency?.ToUpperInvariant() 
            ?? throw new ArgumentNullException(nameof(currency));
    }

    public Money Add(Money other)
    {
        if (Currency != other.Currency)
            throw new InvalidOperationException("Cannot add different currencies");
        
        return new Money(Amount + other.Amount, Currency);
    }

    protected override IEnumerable<object?> GetEqualityComponents()
    {
        yield return Amount;
        yield return Currency;
    }
}
```

### Domain Events

```csharp
/// <summary>
/// EN: Domain event interface.
/// VI: Interface domain event.
/// </summary>
public interface IDomainEvent : INotification
{
    Guid Id { get; }
    DateTime OccurredOn { get; }
}

/// <summary>
/// EN: Domain event when order is submitted.
/// VI: Domain event khi order được submit.
/// </summary>
public record OrderSubmittedDomainEvent : IDomainEvent
{
    public Guid Id { get; } = Guid.NewGuid();
    public DateTime OccurredOn { get; } = DateTime.UtcNow;
    public Guid OrderId { get; init; }
    public string UserId { get; init; }
    public decimal TotalAmount { get; init; }

    public OrderSubmittedDomainEvent(Guid orderId, string userId, decimal totalAmount)
    {
        OrderId = orderId;
        UserId = userId;
        TotalAmount = totalAmount;
    }
}
```

## Common Mistakes / Lỗi Thường Gặp

### 1. Anemic Domain Model

```csharp
// ❌ BAD: Anemic model with no behavior
public class Order
{
    public Guid Id { get; set; }
    public string Status { get; set; }
    public List<OrderItem> Items { get; set; }
}

// ✅ GOOD: Rich domain model with behavior
public class Order
{
    private readonly List<OrderItem> _items = new();
    public OrderStatus Status { get; private set; }
    
    public void AddItem(Guid productId, int quantity, decimal price)
    {
        if (Status != OrderStatus.Draft)
            throw new DomainException("Cannot modify non-draft order");
        // ...
    }
}
```

### 2. Direct Property Modification

```csharp
// ❌ BAD: Direct modification bypasses rules
order.Status = OrderStatus.Submitted;

// ✅ GOOD: Use domain methods
order.Submit();
```

### 3. Logic in Application Layer

```csharp
// ❌ BAD: Business logic in handler
public async Task Handle(CreateOrderCommand cmd)
{
    if (cmd.Items.Count == 0)
        throw new Exception("Empty order");
    // ...
}

// ✅ GOOD: Business logic in domain
public void Submit()
{
    if (!_orderItems.Any())
        throw new DomainException("Cannot submit empty order");
}
```

## Quick Reference / Tham Chiếu Nhanh

### Aggregate Design Guidelines

| Guideline | Description |
|-----------|-------------|
| Small aggregates | Keep aggregates focused |
| Reference by ID | 외부 aggregates chỉ tham chiếu qua ID |
| Eventual consistency | Between aggregates |
| Immediate consistency | Within aggregate |

### When to Use Each Pattern

| Pattern | Use When |
|---------|----------|
| Entity | Has identity, lifecycle |
| Value Object | No identity, immutable |
| Aggregate | Group of related entities |
| Domain Event | Side effects needed |
| Domain Service | Logic doesn't fit entity |

## Resources / Tài Nguyên

- [Detailed Examples](./references/REFERENCE.md) - Full code examples
- [Repository Pattern](../repository-pattern/SKILL.md) - Data access
- [CQRS MediatR](../cqrs-mediatr/SKILL.md) - Command handlers
- [Testing Patterns](../testing-patterns/SKILL.md) - Domain testing
