API开发规范:请求与响应模型应共用还是分离?
这是API设计里非常常见的问题,我的建议是尽量使用分离的请求模型和响应模型,而不是共用同一个类。原因主要有这几点:
1. 符合单一职责原则
每个模型只聚焦一件事:请求模型专门负责接收客户端传入的参数,响应模型专门定义要返回给客户端的数据结构。
拿你的例子来说,CreateUser接口里,客户端根本不需要传入CreatedOn和CreatedBy(这些是服务端生成的),如果共用UserModel,要么客户端会被要求填写这些无关字段,要么你得额外做忽略字段的逻辑;而GetUsers接口的响应,可能客户端只需要UserId、UserName、Name这类信息,没必要把内部的CreatedOn/CreatedBy暴露出去,共用模型就会被迫返回冗余甚至不必要的信息。
2. 避免数据泄露与冗余
共用模型很容易不小心把服务端内部字段(比如创建时间、创建人、数据库层面的私有字段)暴露给客户端。哪怕你用特性(比如[JsonIgnore])来忽略这些字段,随着业务扩展,模型字段越来越多,维护起来会非常麻烦,一不小心就会漏处理。
3. 更灵活的扩展空间
业务变化时,请求和响应的需求往往是独立的。比如后续你可能需要给CreateUser添加Password字段(客户端提交用),但绝对不能在响应里返回这个字段;或者给响应添加LastLoginTime字段,不需要客户端传入。如果模型分离,这些修改完全不会互相影响,代码逻辑也更清晰。
结合你的代码的改进示例
你可以拆分出三个层次的模型,让职责更清晰:
- 内部实体模型(用于数据库操作):
public abstract class BaseEntity { public DateTime CreatedOn { get; set; } public int CreatedBy { get; set; } } public class UserEntity : BaseEntity { public int UserId { get; set; } public string UserName { get; set; } public string FirstName { get; set; } public string MiddleName { get; set; } public string LastName { get; set; } // 内部计算逻辑放在实体层 public string FullName => $"{FirstName} {MiddleName} {LastName}".Trim(); }
- 请求模型(客户端传入):
public class CreateUserRequest { public string UserName { get; set; } public string FirstName { get; set; } public string MiddleName { get; set; } public string LastName { get; set; } }
- 响应模型(返回给客户端):
public class UserResponse { public int UserId { get; set; } public string UserName { get; set; } public string FullName { get; set; } // 如果客户端需要创建信息,再按需添加,不需要就不写 // public DateTime CreatedOn { get; set; } }
然后你的API可以这样实现:
public int CreateUser(CreateUserRequest request) { // 把请求模型转成内部实体,设置服务端生成的字段 var userEntity = new UserEntity { UserName = request.UserName, FirstName = request.FirstName, MiddleName = request.MiddleName, LastName = request.LastName, CreatedOn = DateTime.Now, CreatedBy = CurrentUserId // 假设当前登录用户ID }; // 保存到数据库,返回新用户ID return _userRepository.Save(userEntity); } public List<UserResponse> GetUsers() { var userEntities = _userRepository.GetAll(); // 把内部实体转成响应模型,只返回需要的字段 return userEntities.Select(u => new UserResponse { UserId = u.UserId, UserName = u.UserName, FullName = u.FullName }).ToList(); }
当然,如果是极端简单的场景(比如一个只返回单个ID的接口),共用模型可能暂时没问题,但从长期维护和代码健壮性来看,分离模型是更稳妥的实践。
内容的提问来源于stack exchange,提问作者S D

