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