概述
分层的目的是为了将某个功能的实现逻辑,根据一定规则拆分到各层次,从而降低各层的复杂度,保证代码的可读性和可维护性。
我当过大量实践总结,设计了如下图所示的分层规范:
该分层规范的核心思想是将业务和技术细节相分离,也即分层架构下的业务逻辑层和服务层的划分。
接下来我说下如何分离。
业务与技术相分离
其分离可通过如下表格来说明。
逻辑 | 数据 | 异常 | |
---|---|---|---|
业务 | 业务逻辑 | 业务数据 | 业务异常 |
技术 | 技术逻辑 | 技术数据 | 技术异常 |
技术逻辑
技术逻辑,即我们的一些技术实现手段性的代码。比如我们要实现一个关闭订单的业务动作,其技术实现手段可能就是将数据库的某个字段值从0更新为1的过程。
技术数据
技术数据,通常情况下可以和业务数据一样。典型有区别的就是状态类的数据。比如我们的订单状态,从技术数据角度,它是0表示正常,1表示关闭。而在业务数据角度,它应该是枚举类型,CLOSED表示关闭,OPEN表示正常的。
技术异常
技术异常,通常也是系统性的异常。例如网络超时,远程服务报错,代码bug导致的空指针等异常。此类异常的特点是不可枚举且无法避免。该类异常一定要打印异常日志用于排查问题。同时该异常最好不要抛给调用方,因为调用方也看不懂。如果调用方是客户端,将异常信息展示给了用户,则体验极差,用户会懵逼。
业务逻辑
业务逻辑,即我们实现某个功能的业务流程。
业务数据
技术数据这一段已做说明,这里不多说了。
业务异常
业务异常,通常是不满足某个业务前提条件而进行某个业务动作时,被拦截终止流程的异常。比如用户要关闭一个订单,但目前订单已经是关闭了的,则可以抛出一个业务异常:订单已经关闭,无需重复关闭!。
以上就是业务和技术相分离的思想。了解了这些我们就可以接下来学习各层的职责划分。
各层职责
数据传输层
用于实现与远程服务的通信。远程服务包括db、rpc服务端、kafka服务端、redis服务端等等
该层比较简单,通常不需要我们开发,我们常见的操作数据库的Mapper、调用http服务的httpClient,以及调用远程RPC服务的client包,都是作为数据传输层来看待。
异常情况:该层可能会因为网络超时,远程服务无响应抛出系统异常。
服务层(Service)
为业务逻辑层提供服务
职责
服务层,也作为防腐层,它用来封装对数据传输层的技术调用细节,为业务逻辑层提供具有业务含义的原子性业务动作
和业务数据
。
其中的方法命名要尽可能的拥有业务含义。例如关闭订单这个场景,其应该提供一个closeOrder(long orderId)
方法,内部技术细节则可能是调用mybatis的Mapper将订单表的status
字段由0更新为1.
异常情况:该层可能发生的异常有,你代码本身的bug导致的异常,以及调用数据传输层产生的系统异常。为了业务逻辑层可以感知到该层的异常,该层的异常通常不需要自己捕获,用默认抛给业务逻辑层即可。
业务逻辑层
该层就是用来实现业务流程。
该层就是用来调用服务层,实现业务流程的编排。该层作为业务逻辑层,要尽可能的保证业务逻辑的可读性强,而不被一些技术细节所干扰。
异常情况:该层抛出的异常,是在不满足某个业务前提条件时,终止业务流程的业务异常。以及调用服务层可能产生的系统异常。
接口层
作为服务对外的通道,要保证确定性的输入和确定性的输出。
职责
-
入参校验
对参数进行校验、解析、以及初始化工作。尽可能保证进入业务逻辑层前,参数是可靠符合预期的。
-
结果返回
业务逻辑执行完后,将结果响应给调用方。通常是将响应数据进行序列化以方便网络传输。
-
异常处理
这是比较重要的一个职责,也是最容易被忽略的。
- 该层在对入参进行校验,可能会有参数异常产生,这时我们需要捕获该异常,将异常告知调用方。
- 该层还会调用业务逻辑层完成业务流程,则可能会收到业务逻辑层抛出来的业务异常和系统异常。我们也需要将异常捕获,通过某个手段告知调用方。
-
日志打印
为了方便问题排查我们通常需要打印日志。在该层我们可以根据自身情况打印如下日志。
- 入参
- 异常
- 异常信息,尤其是系统异常一定要打印,用于排查问题必不可少的依据。对于参数异常和业务异常,可无需打印或者以warn级别打印,因为这不是你的错。
异常情况:该层要对所有异常进行捕获处理。通常我们接口响应数据中定义code
码,来告知调用方本次调用的异常情况。不要再往上抛异常,否则你就无法确定调用方到底得到怎样的响应。
以上就是分层的详细说明。还很抽象对吧,没关系,看个示例感受感受。
示例
数据传输层
由于该层不需要开发,所以就不再贴代码示例了。
服务层
订单服务
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper;
/**
* 订单是否存在。
* @param id
* @return
*/
public boolean isExist(long id) {
//调用mapper查询数据库该订单是否存在
}
/**
* 关闭订单
* @param id
*/
public void closeOrder(long id) {
//调用mapper将状态字段从0更新为1
}
}
消息队列服务
@Service
public class MqService {
public void sendOrderClosedMsg(long orderId,String msg){
//构建消息体
//调用mq客户端发消息
}
}
业务逻辑层
订单关闭的业务逻辑(该处业务流程纯虚构)
@Service
public class OrderBiz {
@Resource
private OrderService orderService;
@Resource
private MqService mqService;
public void closeOrder(long id)throws Exception{
//判断订单是否存在
if(!orderService.isExist(id)){
throw new BizException("订单不存在!");
}
//关闭订单
orderService.closeOrder(id);
//发送订单关闭消息
mqService.sendOrderClosedMsg(id,"订单已关闭");
}
}
接口层
接口层参数校验、异常处理、响应返回
@RestController
@RequestMapping("/order")
public class OrderController {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderController.class);
@Resource
private OrderBiz orderBiz;
@RequestMapping("/close")
public Response closeOrder(Long orderId){
try {
if(orderId == null || orderId < 1){
throw new ParamException("订单id不合法");
}
orderBiz.closeOrder(orderId);
return Response.success(null);
}catch (ParamException e){//参数异常
return new Response<>(ResponseCode.PARAM_ERROR,e.getMessage());
}catch (BizException e){//业务异常
return new Response<>(ResponseCode.BIZ_EXCEPTION,e.getMessage());
}catch (Exception e){//保证任何异常都能被捕获
LOGGER.error("关闭订单时发生异常:",e);
return new Response<>(ResponseCode.SERVER_ERROR,"服务端开小差,请稍后重试!");
}
}
}
响应实体
@Data
public class Response {
protected Integer code;
protected String errMsg;
protected T data;
public Response() {
}
public Response(Integer code, String errMsg) {
this.code = code;
this.errMsg = errMsg;
}
public static Response success(T data){
Response response = new Response();
response.setCode(ResponseCode.SUCCESS);
response.setData(data);
return response;
}
}
以上就是本人关于分层架构的最佳实践。