【架构】----为什么有了PO VO DTO 为什么还需要拆分 UserAdd(新增入参)、UserQuery(查询入参)、UserUpdate(更新入参)对象?

前言

作为Java架构师,建议拆分出UserAdd(新增入参)、UserQuery(查询入参)、UserUpdate(更新入参)对象,核心原因是遵循“职责单一原则”和“数据边界隔离”,避免UserVO/UserPO承担过多角色导致的维护隐患。以下从架构设计角度详细说明。
在这里插入图片描述

1. 先明确核心对象的职责边界(避免混淆是前提)

在拆分前,必须先清晰定义已有UserVOUserPO的职责,再判断是否需要新增对象:

对象类型核心职责字段特点适用场景
UserPO数据库表映射(ORM实体)与表结构1:1对应,包含主键、冗余字段(如version乐观锁、isDeleted逻辑删除)、数据库字段类型(如DateDAO层操作(新增/更新/查询数据库)
UserVO前端视图展示(数据输出)包含前端需要的展示字段(如deptName部门名称,而非deptId)、格式化字段(如createTimeStr: "2024-05-01")、隐藏敏感字段(如不返回password接口返回给前端(如列表查询、详情查询)

2. 为什么必须新增UserAdd/UserUpdate/UserQuery

核心问题:UserPO/UserVO无法满足“入参”的专属需求,强行复用会导致以下架构隐患:

(1)UserAdd:解决“新增入参”的3个核心问题

新增操作(如createUser)的入参需求,UserPO/UserVO无法覆盖:

  • 字段冗余UserPO包含id(主键,新增时由数据库/雪花ID生成,前端无需传)、createTime(系统自动填充)、isDeleted(默认false),若用UserPO接收前端参数,会导致前端传无关字段,甚至误传id篡改数据。
  • 校验规则专属:新增时部分字段必填(如username/password),但UserVOpassword是隐藏字段,UserPO的校验规则可能与新增冲突(如UserPOupdateTime允许为null)。UserAdd可单独定义新增专属校验(如@NotBlank)。
  • 数据类型适配:前端传的deptName(用于展示),新增时实际需要deptId(用于存库),UserAdd可只包含deptId,避免UserVOdeptName的冗余传递。

示例

// 新增入参:只包含前端需传、后端需校验的字段
public class UserAdd {
    @NotBlank(message = "用户名不能为空") // 新增专属校验
    private String username;
    
    @NotBlank(message = "密码不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9]{6,16}$", message = "密码需6-16位字母数字组合")
    private String password;
    
    @NotNull(message = "部门ID不能为空") // 只传deptId,无需deptName(前端展示用)
    private Long deptId;
    
    // 无需包含id、createTime、isDeleted等PO专属字段
    // getter/setter
}
(2)UserUpdate:解决“更新入参”的2个核心问题

更新操作(如updateUser)的入参需求,与新增完全不同:

  • 字段可选性:更新时用户可能只改nickname,不改passwordUserUpdate的字段需支持“部分更新”(用@Nullable@Blankable),而UserAdd的字段是“全量必填”,UserPO的字段可能包含不允许更新的字段(如createTime)。
  • 主键必传+防篡改:更新必须传id(定位数据),但UserPOid若允许前端修改,可能导致篡改他人数据;UserUpdate可强制id为必填(@NotNull),且只用于定位,不允许业务逻辑修改。

示例

// 更新入参:支持部分字段更新,强制传主键
public class UserUpdate {
    @NotNull(message = "用户ID不能为空") // 必须传,用于定位数据
    private Long id;
    
    @Nullable // 可选更新:用户可改可不改
    private String nickname;
    
    @Nullable // 可选更新:密码可传可不传(传则更新,不传则不处理)
    @Pattern(regexp = "^[a-zA-Z0-9]{6,16}$", message = "密码需6-16位字母数字组合")
    private String password;
    
    @Nullable // 可选更新:部门ID
    private Long deptId;
    
    // 禁止包含createTime(不允许更新)、isDeleted(逻辑删除单独处理)
    // getter/setter
}
(3)UserQuery:解决“查询入参”的3个核心问题

查询操作(如pageQueryUser)的入参需求,UserPO/UserVO完全不匹配:

  • 查询条件专属字段:查询可能需要“模糊匹配用户名”(usernameLike)、“部门ID列表”(deptIds)、“时间范围”(createTimeStart/createTimeEnd),这些字段在UserPO/UserVO中不存在。
  • 分页/排序参数:查询通常需要分页(pageNum/pageSize)、排序(sortField/sortDir),这些是查询通用参数,与UserPO(数据库映射)、UserVO(前端展示)的职责无关。
  • 条件校验:查询时可能需要限制“时间范围不能跨月”“pageSize不超过100”,这些校验规则只适用于查询,不应混入UserPO/UserVO

示例

// 查询入参:包含查询条件、分页、排序
public class UserQuery {
    @Nullable // 模糊查询:用户名包含该值
    private String usernameLike;
    
    @Nullable // 精确查询:部门ID列表
    private List<Long> deptIds;
    
    @Nullable // 时间范围:创建开始时间
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTimeStart;
    
    @Nullable // 时间范围:创建结束时间
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTimeEnd;
    
    // 分页参数(默认值:第一页,每页10条)
    private Integer pageNum = 1;
    private Integer pageSize = 10;
    
    // 排序参数(默认按创建时间降序)
    private String sortField = "createTime";
    private String sortDir = "desc";
    
    // 校验:pageSize不超过100
    @AssertTrue(message = "每页条数不能超过100")
    public boolean isPageSizeValid() {
        return pageSize <= 100;
    }
    // getter/setter
}

3. 不拆分的架构隐患(为什么不能偷懒?)

若强行用UserPO/UserVO代替UserAdd/UserUpdate/UserQuery,会导致以下问题,后期维护成本极高:

  • 字段污染UserPO中加入pageNum/sortField等查询参数,违背“ORM实体与数据库1:1”的原则,DAO层操作时需手动过滤无关字段,容易出错。
  • 校验混乱UserVO中同时存在“新增必填”(@NotBlank)和“更新可选”(@Nullable)的校验注解,导致校验逻辑冲突,接口调用时频繁出现校验异常。
  • 安全风险:前端误传id/isDeleted/version等字段,若后端未过滤,可能导致数据篡改(如修改他人用户信息)、乐观锁失效(篡改version)。
  • 扩展性差:后续新增“查询时按角色过滤”需求,需在UserVO中加roleId字段,导致UserVO越来越臃肿,成为“万能对象”,任何改动都可能影响多个接口。

4. 架构师视角:拆分的长期价值

虽然拆分会多写3个类,但从架构设计的“高内聚、低耦合”角度,长期价值远大于短期成本:

  1. 边界清晰:每个对象只负责一类职责(新增入参/更新入参/查询入参/数据库映射/前端展示),团队协作时无需猜“这个字段是干嘛的”。
  2. 改动可控:新增字段时,只修改对应对象(如新增“用户类型”字段,只需在UserAdd/UserUpdate/UserVO中加,不影响UserQuery/UserPO),降低回归测试范围。
  3. 易于维护:校验规则、字段含义都在专属对象中,新人接手时能快速理解“新增需要什么参数”“查询支持什么条件”,无需通读所有代码。

总结

必须拆分。正确的对象分工如下:

  • 入参层UserAdd(新增)、UserUpdate(更新)、UserQuery(查询)→ 负责“接收前端参数+专属校验”;
  • 数据层UserPO → 负责“与数据库映射”;
  • 出参层UserVO → 负责“给前端展示数据”。

这种分层设计是Java架构中“DTO模式”的经典应用,能有效隔离数据边界,降低系统复杂度,是中大型项目的必备规范。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值