You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DDD与整洁架构中值对象与原始类型的边界及应用疑问

值对象与原始类型的边界划分指南

核心原则

领域层必须完全使用值对象/实体,绝对不能直接暴露原始类型——这是保持领域模型纯度、封装业务规则的核心要求。应用层作为领域层的入口,也应该遵循这个原则,仅在适配外部系统的边界层做原始类型和值对象的转换。


1. 应用服务/CQRS命令:优先使用值对象

应该选择第二种实现:

data class RegisterNewUserCommand(
    val username: Username,
    val rawPassword: RawPassword,
    val email: Email)

原因:

  • 提前校验拦截:值对象本身封装了业务规则(比如Username限制长度、禁止特殊字符,Email校验格式),在构造命令时就能触发校验,避免无效数据流入领域层。如果用String,你需要在应用服务或领域层重复编写校验逻辑,容易遗漏或不一致。
  • 语义明确:username: Username比String更清晰地表达了这个参数的业务含义,避免把任意字符串误传为用户名,降低协作时的理解成本。
  • 统一规则入口:所有涉及用户名的校验逻辑都集中在Username类里,后续规则变更只需修改这一处,不用到处找代码修改。

2. 应用层与基础设施层的端口:用值对象定义接口

应该选择第一种端口定义:

interface CouponsRepository {
    fun findByCode(couponCode: CouponCode): Coupon?
}

原因:

  • 保持领域边界清晰:Repository端口是应用层定义的,属于领域模型的一部分,用值对象能保证调用者(领域服务/应用服务)只需要传递符合业务规则的CouponCode,不用关心底层存储的细节。
  • 适配逻辑隔离:基础设施适配器(比如JPA实现类)负责处理值对象到原始类型的转换,示例如下:
class JpaCouponsRepository : CouponsRepository {
    override fun findByCode(couponCode: CouponCode): Coupon? {
        return entityManager.find(Coupon::class.java, couponCode.value)
    }
}

转换逻辑被限制在适配器内部,领域层完全不用感知原始类型的存在。


最终疑问解答:应用层及领域层应仅使用值对象和实体吗?

是的,应用层和领域层应该优先且尽可能使用值对象和实体,仅在以下场景使用原始类型:

  • 完全没有业务规则的通用参数(比如分页的page: Int,但如果有分页大小限制,还是应该封装成PageSize值对象);
  • 与外部系统交互的适配层(比如HTTP控制器接收请求时用原始类型,然后转换为值对象传递给应用服务;数据库适配器把值对象转成原始类型存储)。

本质上,原始类型属于“无业务含义的通用类型”,而值对象是“带有业务规则的领域类型”——在领域和应用层用值对象,才能让代码真正体现业务逻辑,而不是一堆无意义的字符串/数字。

内容的提问来源于stack exchange,提问作者user19111862

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 07:03:09