首页
/ 告别面条代码:ASP.NET Core领域驱动设计实战指南

告别面条代码:ASP.NET Core领域驱动设计实战指南

2026-02-04 04:06:24作者:吴年前Myrtle

你是否还在为大型ASP.NET Core项目的代码混乱而头疼?业务逻辑与数据访问交织、修改一处牵动全身、新功能迭代举步维艰?本文将带你用领域驱动设计(DDD)重构应用架构,通过分层设计实现代码解耦,让系统随业务增长而优雅演化。读完本文你将掌握:

  • 如何在ASP.NET Core中落地DDD分层架构
  • 领域模型设计与聚合根实现技巧
  • 仓储模式与EF Core的无缝集成
  • 完整的DDD项目结构与最佳实践

DDD架构在ASP.NET Core中的应用

领域驱动设计(DDD)是一种通过将业务领域建模为核心来设计复杂软件的方法论。在ASP.NET Core项目中实施DDD,能够有效解决业务复杂度提升带来的代码维护难题。

经典DDD分层架构

DDD推荐的分层架构包括:

  • 表现层:处理HTTP请求和响应(Controllers)
  • 应用层:协调领域逻辑,不包含业务规则
  • 领域层:核心业务逻辑和领域模型
  • 基础设施层:提供技术支持,如数据库访问

在ASP.NET Core中,我们可以通过项目结构清晰划分这些层次:

src/
├── YourApp.Web/          # 表现层(Controllers、Pages)
├── YourApp.Application/  # 应用层(Services、DTOs)
├── YourApp.Domain/       # 领域层(Entities、Aggregates)
└── YourApp.Infrastructure/ # 基础设施层(Repositories、EF Core)

ASP.NET Core与DDD的契合点

ASP.NET Core的依赖注入系统为DDD分层架构提供了完美支持。通过构造函数注入,我们可以轻松实现各层之间的解耦。例如,表现层的控制器依赖于应用层服务,应用层服务又依赖于领域层和仓储接口。

领域模型设计实践

领域模型是DDD的核心,它捕获业务概念和规则。在ASP.NET Core中,我们通常使用实体(Entity)和值对象(Value Object)构建领域模型。

实体与聚合根设计

实体是具有唯一标识的领域对象,其生命周期可能跨越多个状态变化。聚合根是聚合的根实体,负责维护聚合内的一致性。

以订单系统为例,Order是典型的聚合根,包含OrderItem子实体:

// 领域层实体示例 [参考 src/Mvc/test/WebSites/BasicWebSite/ContactsRepository.cs]
public class Order : Entity
{
    public OrderId Id { get; private set; }
    public CustomerId CustomerId { get; private set; }
    public OrderStatus Status { get; private set; }
    private List<OrderItem> _items = new();
    
    // 业务行为封装
    public void AddItem(Product product, int quantity)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("只能向草稿订单添加商品");
            
        _items.Add(new OrderItem(product.Id, quantity, product.Price));
    }
    
    // 更多领域行为...
}

值对象实现

值对象是用于描述领域特征的对象,没有唯一标识,通常是不可变的。例如,地址、货币金额等:

public record Address(
    string Street,
    string City,
    string State,
    string PostalCode,
    string Country
);

public record Money(
    decimal Amount,
    string CurrencyCode
);

仓储模式与EF Core集成

仓储模式(Repository Pattern)为领域模型提供持久化接口,隔离领域层与数据访问技术。在ASP.NET Core中,我们通常使用EF Core实现仓储接口。

仓储接口设计

在领域层定义仓储接口,保持对具体数据访问技术的无知:

// 领域层仓储接口 [参考 src/Identity/EntityFrameworkCore/src/UserStore.cs]
public interface IOrderRepository
{
    Task<Order?> GetByIdAsync(OrderId id, CancellationToken cancellationToken = default);
    Task AddAsync(Order order, CancellationToken cancellationToken = default);
    Task UpdateAsync(Order order, CancellationToken cancellationToken = default);
    Task DeleteAsync(Order order, CancellationToken cancellationToken = default);
}

EF Core实现仓储

在基础设施层实现仓储接口,利用EF Core进行数据访问:

// 基础设施层仓储实现 [参考 src/Identity/EntityFrameworkCore/src/UserStore.cs]
public class EfCoreOrderRepository : IOrderRepository
{
    private readonly AppDbContext _dbContext;
    
    public EfCoreOrderRepository(AppDbContext dbContext)
    {
        _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
    }
    
    public async Task<Order?> GetByIdAsync(OrderId id, CancellationToken cancellationToken = default)
    {
        return await _dbContext.Orders
            .Include(o => o.Items)
            .FirstOrDefaultAsync(o => o.Id == id, cancellationToken);
    }
    
    // 其他方法实现...
}

数据库上下文配置

正确配置EF Core的DbContext,映射领域模型到数据库表:

// EF Core上下文配置 [参考 src/Identity/testassets/Identity.DefaultUI.WebSite/Data/ApplicationDbContext.cs]
public class AppDbContext : DbContext
{
    public DbSet<Order> Orders { get; set; }
    public DbSet<Product> Products { get; set; }
    
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        
        // 配置聚合根
        modelBuilder.Entity<Order>(b =>
        {
            b.HasKey(o => o.Id);
            b.Property(o => o.Id).HasConversion(
                id => id.Value,
                value => new OrderId(value));
                
            b.OwnsOne(o => o.ShippingAddress);
            
            // 配置子集合
            b.HasMany(o => o.Items)
                .WithOne()
                .HasForeignKey("OrderId");
        });
        
        // 其他实体配置...
    }
}

ASP.NET Core DDD项目完整结构

以下是一个符合DDD规范的ASP.NET Core项目结构示例:

src/
├── YourApp.Web/                  # 表现层
│   ├── Controllers/              # API控制器
│   │   ├── OrdersController.cs   # 订单API
│   │   └── ProductsController.cs # 产品API
│   ├── Models/                   # 视图模型
│   └── Program.cs                # 应用入口点
│
├── YourApp.Application/          # 应用层
│   ├── Services/                 # 应用服务
│   │   ├── OrderService.cs       # 订单应用服务
│   │   └── ProductService.cs     # 产品应用服务
│   └── DTOs/                     # 数据传输对象
│
├── YourApp.Domain/               # 领域层
│   ├── Entities/                 # 实体
│   │   ├── Order.cs              # 订单实体
│   │   └── Product.cs            # 产品实体
│   ├── ValueObjects/             # 值对象
│   │   ├── OrderId.cs            # 订单ID
│   │   └── Address.cs            # 地址
│   ├── Aggregates/               # 聚合根
│   ├── Repositories/             # 仓储接口
│   └── Services/                 # 领域服务
│
└── YourApp.Infrastructure/       # 基础设施层
    ├── Data/                     # 数据访问
    │   ├── AppDbContext.cs       # EF Core上下文
    │   └── Repositories/         # 仓储实现
    └── Services/                 # 外部服务集成

依赖注入配置

在Program.cs中配置依赖注入,将各层组件连接起来:

// Program.cs 依赖注入配置
var builder = WebApplication.CreateBuilder(args);

// 添加应用服务
builder.Services.AddScoped<IOrderService, OrderService>();

// 添加领域服务
builder.Services.AddScoped<IDiscountService, DiscountService>();

// 添加基础设施
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
    
builder.Services.AddScoped<IOrderRepository, EfCoreOrderRepository>();

// 添加控制器
builder.Services.AddControllers();

var app = builder.Build();

// 中间件配置...

app.MapControllers();
app.Run();

实际案例:订单管理系统DDD实现

让我们通过一个简单的订单管理系统,看看DDD在ASP.NET Core中的完整应用。

领域模型实现

首先定义核心领域模型,包括Order聚合根和相关值对象:

// 订单状态枚举
public enum OrderStatus
{
    Draft,
    Confirmed,
    Shipped,
    Delivered,
    Cancelled
}

// 订单ID值对象
public record OrderId(Guid Value);

// 订单项实体
public class OrderItem
{
    public ProductId ProductId { get; private set; }
    public int Quantity { get; private set; }
    public decimal UnitPrice { get; private set; }
    
    public OrderItem(ProductId productId, int quantity, decimal unitPrice)
    {
        ProductId = productId;
        Quantity = quantity > 0 ? quantity : throw new ArgumentException("数量必须大于0");
        UnitPrice = unitPrice >= 0 ? unitPrice : throw new ArgumentException("单价不能为负数");
    }
    
    public decimal TotalPrice => Quantity * UnitPrice;
}

// 订单聚合根
public class Order : Entity<OrderId>
{
    public CustomerId CustomerId { get; private set; }
    public OrderStatus Status { get; private set; }
    public Address ShippingAddress { get; private set; }
    private readonly List<OrderItem> _items = new();
    
    public IReadOnlyList<OrderItem> Items => _items.AsReadOnly();
    public decimal TotalAmount => _items.Sum(item => item.TotalPrice);
    
    public Order(OrderId id, CustomerId customerId, Address shippingAddress)
    {
        Id = id;
        CustomerId = customerId;
        ShippingAddress = shippingAddress;
        Status = OrderStatus.Draft;
    }
    
    public void AddItem(Product product, int quantity)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("只能向草稿订单添加商品");
            
        if (quantity <= 0)
            throw new ArgumentException("商品数量必须大于0");
            
        _items.Add(new OrderItem(product.Id, quantity, product.Price));
    }
    
    public void Confirm()
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("只能确认草稿订单");
            
        if (_items.Count == 0)
            throw new InvalidOperationException("订单必须包含至少一个商品");
            
        Status = OrderStatus.Confirmed;
        // 可以在这里发布领域事件
        AddDomainEvent(new OrderConfirmed(Id));
    }
    
    // 其他领域行为...
}

应用服务实现

应用服务协调领域逻辑,处理事务和跨聚合操作:

// 订单应用服务
public class OrderService : IOrderService
{
    private readonly IOrderRepository _orderRepository;
    private readonly IProductRepository _productRepository;
    private readonly IUnitOfWork _unitOfWork;
    
    public OrderService(
        IOrderRepository orderRepository,
        IProductRepository productRepository,
        IUnitOfWork unitOfWork)
    {
        _orderRepository = orderRepository;
        _productRepository = productRepository;
        _unitOfWork = unitOfWork;
    }
    
    public async Task<OrderDto> CreateOrderAsync(CreateOrderCommand command, CancellationToken cancellationToken)
    {
        // 验证商品存在且有库存
        var products = new List<Product>();
        foreach (var item in command.Items)
        {
            var product = await _productRepository.GetByIdAsync(
                new ProductId(item.ProductId), cancellationToken);
                
            if (product == null)
                throw new NotFoundException($"商品 {item.ProductId} 不存在");
                
            if (product.Stock < item.Quantity)
                throw new InsufficientStockException(product.Id, product.Stock, item.Quantity);
                
            products.Add(product);
        }
        
        // 创建订单
        var order = new Order(
            new OrderId(Guid.NewGuid()),
            new CustomerId(command.CustomerId),
            new Address(
                command.ShippingAddress.Street,
                command.ShippingAddress.City,
                command.ShippingAddress.State,
                command.ShippingAddress.PostalCode,
                command.ShippingAddress.Country));
                
        // 添加订单项
        for (int i = 0; i < products.Count; i++)
        {
            order.AddItem(products[i], command.Items[i].Quantity);
        }
        
        // 保存订单
        await _orderRepository.AddAsync(order, cancellationToken);
        await _unitOfWork.SaveChangesAsync(cancellationToken);
        
        // 返回DTO
        return new OrderDto
        {
            Id = order.Id.Value,
            Status = order.Status.ToString(),
            TotalAmount = order.TotalAmount,
            Items = order.Items.Select(item => new OrderItemDto
            {
                ProductId = item.ProductId.Value,
                Quantity = item.Quantity,
                UnitPrice = item.UnitPrice
            }).ToList()
        };
    }
    
    // 其他应用服务方法...
}

API控制器实现

最后,在表现层实现API控制器,处理HTTP请求:

// 订单API控制器 [参考 src/Identity/samples/IdentitySample.Mvc/Controllers/AccountController.cs]
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly IOrderService _orderService;
    
    public OrdersController(IOrderService orderService)
    {
        _orderService = orderService;
    }
    
    [HttpPost]
    public async Task<ActionResult<OrderDto>> CreateOrder(
        [FromBody] CreateOrderCommand command,
        CancellationToken cancellationToken)
    {
        var order = await _orderService.CreateOrderAsync(command, cancellationToken);
        return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
    }
    
    [HttpGet("{id}")]
    public async Task<ActionResult<OrderDto>> GetOrder(
        Guid id, 
        CancellationToken cancellationToken)
    {
        var order = await _orderService.GetOrderByIdAsync(id, cancellationToken);
        if (order == null)
            return NotFound();
            
        return order;
    }
    
    [HttpPut("{id}/confirm")]
    public async Task<IActionResult> ConfirmOrder(
        Guid id, 
        CancellationToken cancellationToken)
    {
        await _orderService.ConfirmOrderAsync(id, cancellationToken);
        return NoContent();
    }
    
    // 其他API端点...
}

DDD实践中的常见问题与解决方案

在ASP.NET Core中实施DDD时,开发者常遇到一些挑战,以下是解决方案:

1. 领域事件处理

使用中介者模式(如MediatR)处理领域事件:

// 领域事件
public record OrderConfirmed(OrderId OrderId) : IDomainEvent;

// 事件处理器
public class OrderConfirmedHandler : INotificationHandler<OrderConfirmed>
{
    private readonly IEmailService _emailService;
    
    public OrderConfirmedHandler(IEmailService emailService)
    {
        _emailService = emailService;
    }
    
    public async Task Handle(OrderConfirmed notification, CancellationToken cancellationToken)
    {
        // 发送订单确认邮件
        await _emailService.SendOrderConfirmationEmail(notification.OrderId);
    }
}

2. 事务管理

使用工作单元模式确保数据一致性:

public interface IUnitOfWork
{
    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

// EF Core工作单元实现
public class EfCoreUnitOfWork : IUnitOfWork
{
    private readonly AppDbContext _dbContext;
    
    public EfCoreUnitOfWork(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }
    
    public Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        return _dbContext.SaveChangesAsync(cancellationToken);
    }
}

总结与展望

通过将领域驱动设计与ASP.NET Core结合,我们可以构建出业务逻辑清晰、架构稳定的企业级应用。DDD的分层架构使系统各部分职责明确,依赖注入促进了解耦,而EF Core则为领域模型提供了强大的持久化支持。

随着项目的演进,DDD架构的优势会逐渐显现:

  • 业务逻辑集中在领域层,易于理解和维护
  • 各层之间的边界清晰,便于团队协作
  • 领域模型准确反映业务概念,提高沟通效率
  • 系统更具弹性,能够适应业务变化

ASP.NET Core持续发展的特性(如最小API、端点路由等)将进一步简化DDD的实施。未来,随着微服务架构的普及,DDD的限界上下文概念将发挥更大价值,帮助我们构建更灵活的分布式系统。

你是否在ASP.NET Core项目中尝试过DDD?遇到了哪些挑战?欢迎在评论区分享你的经验和问题!关注我们获取更多ASP.NET Core架构实践指南。

登录后查看全文
热门项目推荐
相关项目推荐