浅谈如何写好一个接口
前言从工作至今,见过 N 多个接口。那接口,叫一个百花齐放,奇形怪状,怎么写都有。或许 公司没有一套约定俗成的后端编码规范吧。下面分享一下我 “多年” 编写接口经验。返回数据格式在和前端对接接口时,我们一般会返回 三大件数据给到前端,如下代码所示:@Datapublic class Response<T> {private int code;private String message
前言
从工作至今,见过 N 多个接口。
那接口,叫一个百花齐放,奇形怪状,怎么写都有。
或许 公司没有一套约定俗成的后端编码规范吧。
下面分享一下我 “多年” 编写接口经验。
返回数据格式
在和前端对接接口时,我们一般会返回 三大件
数据给到前端,如下代码所示:
@Data
public class Response<T> {
private int code;
private String message;
private T data;
}
- Code:接口状态码
- 明确接口请求成功返回时返回,如返回 0,即表示成功,返回 -1 表示失败
- 配合
全局异常状态码
输出,便于前端逻辑判断处理,通过状态码,也方便自己定位接口异常。
- Message:错误信息
- 如果请求成功可以返回,“成功”
- 如果请求失败时,可以返回对应错误信息,以便前端对接
- Data: 定义为范型,兼容多种返回数据类型,返回接口所需数据。
Controller
有的接口,入参,出参,Map 一把梭。
有的接口,Domain对象,从头传到尾。
有的接口,没有注释,返回参数一个 “R”
横批:什么玩意?
上代码看看:
// 垃圾代码
@PostMapping("/add")
public R add(Map map) {
return R.success(service.add(map));
}
// 建议
@PostMapping("/add")
public R<UserAddVO> add(@Valid @RequestBody UserAddDTO dto) {
return R.success(service.add(dto))
}
-
移除Map 入参
- 建议改为 Json 或 Form-data 等类型入参,明确入参。
- 团队内部定义好
入参后缀
,如示例代码 DTO(Data Transfer Object),明确入参值
,以及类型
,便于后续维护。
-
重命名R(可选),建议命名规范。可以叫
Response
,KResponse
,Result
等等 -
声明 R 返回类型:如代码所示:UserAddVO,以及
返回参数后缀
,如示例代码:VO(View Object) -
@Valid
参数校验(可选),使用好@Valid 注解可以减少一些参数校验代码。
Java 是
强类型
语言,别写成弱类型语言,该明确的得明确,定义好,形成规范。不然新人接手项目,看到不得问候你全家。
AppService
在我们刚刚开始学Java 时,大部人网上教程,都会教到,先定义一个 AService 接口,然后在写 AServiceImpl 实现类。如下代码所示:
public interface AService {
void a();
}
public class AServiceImpl {
@Override
public void a () {
// dosomething...
}
}
可能大家这样写,挺好的。面向接口编程。
但是写了好几年代码,发现这种 AService 接口,根本有点多余,后面业务代码基本没有扩展可能性,没啥用。
后续我改成直接 AService 类得了,舒服多了。如下所示:
// 建议
public class AService {
public void a () {
// dosomething...
}
}
在回过头来看看,我在标题上写的是 AppService。
为啥这样写呢?
一是受限于传统的三层架构,Controller,Service、Repository / Mapper
,Service层随着业务不断发展,代码会逐渐膨胀,不易于维护。
二是学习 DDD 架构模式,从中学了一个 新命名 ApplicationService
,简写为 AppService。
简单来说,AppService 是门面,就是在 AppService层,不做复杂业务逻辑
,只做 资源编排
,具体业务细节,在 对应 Service
中处理。
我们直接看看代码吧:
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class BAppService {
private final BRepository bRepository;
// 省略.. service
public Boolean do(Long userId) {
User user = userRepository.findByUserId(userId);
if (user.isMan()) {
throws new RuntimeException("男人不能做!");
}
List<GirlFriend> girlFriends = girlFriendRepository.findByUserId(userId);
for (GirlFriend girlFriend : girlFriends) {
// 业务处理..
}
B b = new B();
// 省略一些set
bRepository.save(b);
return true;
}
}
如 BAppService 所示,在 do 方法里,做了很多复杂的业务逻辑。
相信大家也见过几百上千行的 Service 业务逻辑代码,恐怖如斯。
appService 关注太多业务细节,违背单一职责
原则。同时写单测
时也不好测试。
我们来优化一下 Service代码。
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class BAppService {
private final BRepository bRepository;
// 省略.. service
public Boolean do(Long userId) {
/*User user = userRepository.findByUserId(userId);
if (user.isMan()) {
throws new RuntimeException("男人不能做!");
}*/
// 替换为如下代码:
womenUseService.check(user);
/*List<GirlFriend> girlFriends = girlFriendRepository.findByUserId(userId);
for (GirlFriend girlFriend : girlFriends) {
// 业务处理..
}*/
// 替换为如下代码:
girlFriendDoService.do(userId);
B b = new B();
// 省略一些set
bRepository.save(b);
return true;
}
}
在 BAppService类,我们抽取 womenUseService,girlFriendDoService 类,把复杂业务逻辑抽离出去,在 do 方法里,只做 womenUseService和 girlFriendDoService 的调用,即编排业务
。
ApplicationService更多详情也可以参考 滕云大佬文章:后端开发实践系列——领域驱动设计(DDD)编码实践
Repository / Mapper
Repository / Mapper 后续简称为:RM
,我们通常写代码时,会通过 RM 查询出接口,然后进行非空校验,如下代码所示:
User user = userRepository.findByUserId(userId);
if (user == null) {
throws new RuntimeException("用户不存在")
}
每次需要根据 userId 查询用户时,都会校验用户是否存在的操作,啊,CService 需要,又写一遍…
其实我们增加在 RM 基础上,增加多一层 Dao
,隔离持久化框架,如抽取一个 UserDao 类,后续直接可以复用。
@Repository
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UserDao {
private final UserRepository userRepository;
public User findByUserId() {
User user = userRepository.findByUserId(userId);
if (user == null) {
throws new RuntimeException("用户不存在")
}
return user;
}
}
编写接口文档
和前端对接,前端主要看的就是接口文档。
这里我以 Swagger 为例:
@Api(tags = "用户相关接口") // 增加 swagger 接口文档注解
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UserController {
@PostMapping("/add")
@ApiOperation("添加用户") // 增加 swagger 接口文档注解
public R<UserAddVO> add(@Valid @RequestBody UserAddDTO dto) {
return R.success(service.add(dto))
}
}
这里在对应接口 Controller 类上添加 @Api
注解,标明这是用户相关接口集合。
然后在每个 Controller 方法,即接口,@ApiOperation
注解,标明这个接口干啥的,如上代码所示是添加用户接口。
@Data
public class UserAddVO {
@ApiModelProperty("姓名") // 增加 swagger注解,标明字段意思
private String name;
@ApiModelProperty(value = "年龄", example = "20") // 增加 swagger注解,标明字段意思
private int age;
//...省略其他
}
写完接口后,需要还需要在返回参数,增加swagger注解,标明这些字段是啥意思。
swagger 文档,默认会根据类型生成对应默认值,如上代码所示 age 字段,会生成默认为 0,如果有需要自定义值,可以通过 example
属性标明。这样可以方便接口调试,不用每次手动构造数据。
这样前端才好对接,不然靠猜吗?
自测
写完接口后,我们还需要自己测试一下,不要急急忙忙提测
,对接前端
自己都没测过,你敢保证自己写的代码没有bug吗?
一般通过 Postman 自测,当然也可以通过自己写集成测试保证接口质量
至于如何集成测试可以看我另一篇文章Java 集成测试
最后
如果是老项目代码,还是建议先别动吧 ,出事不负责 ~
但新写代码,按照如上所示编写,那么恭喜你,你已经超过很多很多
程序猿了~
以上就是全部内容,希望能帮助到你~
如有不妥,欢迎指出,大家一起交流学习。
感谢阅读,下次再见。
更多推荐
所有评论(0)