Design Pattern 101: 领域驱动设计在.NET中的实践

领域驱动设计在.NET中的实践

领域驱动设计(Domain-Driven Design, DDD)是一种软件开发方法,它强调将业务领域作为软件设计的核心。下面我将介绍如何在.NET中应用DDD,并提供相应的代码示例。

核心概念

1. 领域模型 (Domain Model)

public class Order
{
    public int Id { get; private set; }
    public DateTime OrderDate { get; private set; }
    public Address ShippingAddress { get; private set; }
    private readonly List<OrderItem> _items = new();
    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();

    public Order(Address shippingAddress)
    {
        OrderDate = DateTime.UtcNow;
        ShippingAddress = shippingAddress ?? throw new ArgumentNullException(nameof(shippingAddress));
    }

    public void AddItem(Product product, int quantity)
    {
        if (product == null) throw new ArgumentNullException(nameof(product));
        if (quantity <= 0) throw new ArgumentException("Quantity must be positive");

        var item = new OrderItem(product, quantity);
        _items.Add(item);
    }
}

2. 值对象 (Value Object)

public class Address : ValueObject
{
    public string Street { get; }
    public string City { get; }
    public string ZipCode { get; }

    public Address(string street, string city, string zipCode)
    {
        Street = street;
        City = city;
        ZipCode = zipCode;
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Street;
        yield return City;
        yield return ZipCode;
    }
}

3. 仓储模式 (Repository Pattern)

public interface IOrderRepository
{
    Task<Order> GetByIdAsync(int orderId);
    Task AddAsync(Order order);
    Task UpdateAsync(Order order);
}

public class OrderRepository : IOrderRepository
{
    private readonly OrderDbContext _context;

    public OrderRepository(OrderDbContext context)
    {
        _context = context;
    }

    public async Task<Order> GetByIdAsync(int orderId)
    {
        return await _context.Orders
            .Include(o => o.Items)
            .FirstOrDefaultAsync(o => o.Id == orderId);
    }

    public async Task AddAsync(Order order)
    {
        await _context.Orders.AddAsync(order);
        await _context.SaveChangesAsync();
    }

    public async Task UpdateAsync(Order order)
    {
        _context.Orders.Update(order);
        await _context.SaveChangesAsync();
    }
}

4. 领域服务 (Domain Service)

public class OrderService
{
    private readonly IOrderRepository _orderRepository;
    private readonly IProductRepository _productRepository;

    public OrderService(
        IOrderRepository orderRepository,
        IProductRepository productRepository)
    {
        _orderRepository = orderRepository;
        _productRepository = productRepository;
    }

    public async Task PlaceOrder(int customerId, Address shippingAddress, Dictionary<int, int> productQuantities)
    {
        var order = new Order(shippingAddress);

        foreach (var (productId, quantity) in productQuantities)
        {
            var product = await _productRepository.GetByIdAsync(productId);
            order.AddItem(product, quantity);
        }

        await _orderRepository.AddAsync(order);
    }
}

5. 应用层 (Application Layer)

public class OrderApplicationService
{
    private readonly OrderService _orderService;
    private readonly IOrderRepository _orderRepository;

    public OrderApplicationService(
        OrderService orderService,
        IOrderRepository orderRepository)
    {
        _orderService = orderService;
        _orderRepository = orderRepository;
    }

    public async Task<OrderDto> PlaceOrder(PlaceOrderDto dto)
    {
        var shippingAddress = new Address(
            dto.Street, 
            dto.City, 
            dto.ZipCode);

        await _orderService.PlaceOrder(
            dto.CustomerId, 
            shippingAddress, 
            dto.ProductQuantities);

        // 返回订单结果
        var order = await _orderRepository.GetLatestForCustomerAsync(dto.CustomerId);
        return OrderDto.FromOrder(order);
    }
}

在.NET中实现DDD的最佳实践

  1. 使用清晰的层次结构:

    • 应用层 (Application)
    • 领域层 (Domain)
    • 基础设施层 (Infrastructure)
    • 表现层 (Presentation/API)
  2. 使用MediatR实现CQRS模式:

public class CreateOrderCommand : IRequest<OrderDto>
{
    public int CustomerId { get; set; }
    public AddressDto ShippingAddress { get; set; }
    public Dictionary<int, int> ProductQuantities { get; set; }
}

public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, OrderDto>
{
    private readonly OrderService _orderService;

    public CreateOrderCommandHandler(OrderService orderService)
    {
        _orderService = orderService;
    }

    public async Task<OrderDto> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
    {
        var address = new Address(
            request.ShippingAddress.Street,
            request.ShippingAddress.City,
            request.ShippingAddress.ZipCode);

        await _orderService.PlaceOrder(
            request.CustomerId,
            address,
            request.ProductQuantities);

        // 返回订单DTO
    }
}
  1. 使用领域事件 (Domain Events):
public interface IDomainEvent
{
    DateTime OccurredOn { get; }
}

public class OrderPlacedEvent : IDomainEvent
{
    public int OrderId { get; }
    public int CustomerId { get; }
    public DateTime OccurredOn { get; } = DateTime.UtcNow;

    public OrderPlacedEvent(int orderId, int customerId)
    {
        OrderId = orderId;
        CustomerId = customerId;
    }
}

// 在领域模型中触发事件
public class Order
{
    private readonly List<IDomainEvent> _domainEvents = new();
    public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();

    public void ClearDomainEvents() => _domainEvents.Clear();

    private void AddDomainEvent(IDomainEvent domainEvent) => _domainEvents.Add(domainEvent);

    // 在适当的时候触发事件
    private void OnOrderPlaced()
    {
        AddDomainEvent(new OrderPlacedEvent(Id, CustomerId));
    }
}

总结

在.NET中实施DDD需要:

  1. 专注于核心领域和领域逻辑
  2. 使用清晰的架构分层
  3. 正确区分实体、值对象和聚合根
  4. 使用仓储模式隔离持久化逻辑
  5. 通过领域服务处理跨聚合的业务逻辑
  6. 使用领域事件处理领域内的副作用

通过遵循这些原则,可以构建出更可维护、更符合业务需求的.NET应用程序。

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *