应用层、领域层的核心区分

好的,这是一个非常经典且重要的问题。在领域驱动设计(DDD)中,明确区分**应用层(Application Layer)领域层(Domain Layer)**是构建健壮、可维护软件的核心。

我们用一个常见的电商场景——“用户下单”——来具体说明哪些东西属于应用层,哪些属于领域层。

假设的四层结构:

  • 表示层 (Presentation Layer): 用户的浏览器、手机App界面或API接口。
  • 应用层 (Application Layer): 协调者,处理用例流程。
  • 领域层 (Domain Layer): 核心,包含业务规则和状态。
  • 基础设施层 (Infrastructure Layer): 提供技术支持,如数据库、消息队列等。

## 领域层 (Domain Layer):业务的核心和灵魂

领域层是软件的心脏,它只关心业务本身的规则和状态,完全不关心这些规则被谁、以何种方式使用。它体现了业务的本质。

在“用户下单”这个场景中,领域层包含以下元素:

  • 领域实体 (Entities): 具有唯一标识符并且状态会随时间变化的对象。它们是业务的核心“名词”。
    • Order (订单): 这是核心实体。它包含订单号、订单项列表、总金额、状态(如:待支付、已支付、已发货)、收货地址等。它还有自己的行为,比如 cancel() (取消订单)、markAsPaid() (标记为已支付)。
    • OrderItem (订单项): 订单中的具体商品行。包含商品ID、数量、单价。
    • Customer (客户): 包含客户ID、姓名、客户等级等。它可能有 isVip() (判断是否为VIP) 这样的方法。
    • Product (商品): 包含商品ID、价格、库存数量。它有 hasStock(quantity) (检查库存是否充足) 这样的业务方法。
  • 值对象 (Value Objects): 没有唯一标识符,通常用来度量或描述事物的对象。它们一旦创建就不可变。
    • Address (地址): 由省、市、区、详细街道组成。两个地址对象,如果所有属性都相同,我们就可以认为它们是同一个地址。
    • Money (金额): 包含数值和货币单位(如 199.99, CNY)。它可以封装货币计算的复杂性和精度问题。
  • 领域服务 (Domain Services): 当某个业务操作不适合放在任何一个实体上时,就可以使用领域服务。它封装了跨多个实体的复杂业务逻辑。
    • PriceCalculatorService (价格计算服务): 如果订单价格计算非常复杂,比如需要根据客户等级、促销活动、优惠券等多个因素来决定最终价格,这个逻辑就不适合放在Order实体中,因为它依赖了 CustomerPromotion 等其他实体。这个服务会接收订单和客户信息,然后返回最终价格。

领域层的代码特点:

  • 纯粹的业务逻辑,不包含任何技术实现代码(没有数据库SQL、没有HTTP请求)。
  • 稳定,不随技术(UI、数据库)的改变而改变。
  • 富含业务行为,而不仅仅是数据的get/set方法。例如,Order实体自己负责计算总价,而不是让应用层来计算。

## 应用层 (Application Layer):用例的协调者和指挥官

应用层是领域层的直接客户。它不包含任何业务规则,而是负责协调和编排领域对象来完成一个完整的用户场景(Use Case)。它像一个指挥官,告诉领域层的各个对象“现在该你做什么了”。

继续“用户下单”的场景,应用层包含以下元素:

  • 应用服务 (Application Services): 它的方法通常直接对应一个用户用例。
    • OrderService (订单服务): 注意,这里的服务和领域服务不同。这是一个应用服务

我们来看看 OrderService 的一个典型方法 placeOrder(customerId, productQuantities, shippingAddress) 的内部流程:

  1. 开始事务: 与基础设施层交互,启动一个数据库事务。
  2. 数据获取: 调用基础设施层的仓储接口 (Repository),从数据库中加载必要的领域实体。
    • Customer customer = customerRepository.findById(customerId);
    • List<Product> products = productRepository.findByIds(productQuantities.keys());
  3. 验证和协调:
    • 检查客户状态是否正常 (customer.isActive())。
    • 循环检查所有商品的库存 (product.hasStock(quantity))。
  4. 调用领域对象执行核心逻辑:
    • 创建一个新的 Order 实体:Order newOrder = customer.createOrder(shippingAddress); (这里假设Customer实体有一个创建订单的工厂方法)。
    • 将商品添加到订单中:newOrder.addItems(products, productQuantities);
  5. 持久化: 再次调用仓储接口,将新创建和已修改的实体保存回数据库。
    • orderRepository.save(newOrder);
    • productRepository.updateAll(products); // 更新库存
  6. 触发副作用:
    • 调用基础设施层的服务,发送一个“订单创建成功”的邮件短信
    • 发布一个“订单已创建”的领域事件 (Domain Event) 到消息队列,供其他系统(如物流系统)消费。
  7. 提交事务: 结束数据库事务。
  8. 返回结果: 向表示层返回一个数据传输对象 (DTO),而不是直接返回领域实体。例如,返回 OrderConfirmationDTO,其中包含订单号和总金额。

应用层的代码特点:

  • 流程性强:代码读起来像一个操作步骤列表。
  • “瘦”逻辑:本身不进行复杂的业务计算,而是调用领域对象的方法。
  • 面向用例:每个公开方法都对应一个具体的用户功能。
  • 处理横切关注点:如事务管理、安全认证、日志记录等。

## 总结与对比

特征应用层 (Application Layer)领域层 (Domain Layer)
职责协调、编排、指挥表达和执行业务规则
核心用例 (Use Case)业务模型 (Business Model)
关心什么如何完成一个任务的流程业务的状态规则是什么
典型代码1. 启动事务&lt;br>2. 加载领域对象&lt;br>3. 调用领域对象的方法&lt;br>4. 持久化&lt;br>5. 结束事务&lt;br>6. 发送通知order.cancel()&lt;br>product.hasStock()&lt;br>priceCalculator.calculate()
变化频率较高,随用户需求和交互方式变化较低,只在核心业务规则变化时才改变
依赖关系依赖领域层和基础设施层接口不依赖任何其他层,是系统的核心和独立部分

简单来说,领域层定义了“积木块”(是什么),而应用层负责“搭建积木”(怎么做)。这种分离使得业务核心(领域层)变得非常稳定和可测试,而应用流程(应用层)则可以灵活地适应外部变化。

发表回复