分层架构最佳实践

概述

分层的目的是为了将某个功能的实现逻辑,根据一定规则拆分到各层次,从而降低各层的复杂度,保证代码的可读性和可维护性。

我当过大量实践总结,设计了如下图所示的分层规范:


该分层规范的核心思想是将业务和技术细节相分离,也即分层架构下的业务逻辑层和服务层的划分。

接下来我说下如何分离。

业务与技术相分离

其分离可通过如下表格来说明。

逻辑 数据 异常
业务 业务逻辑 业务数据 业务异常
技术 技术逻辑 技术数据 技术异常

技术逻辑

技术逻辑,即我们的一些技术实现手段性的代码。比如我们要实现一个关闭订单的业务动作,其技术实现手段可能就是将数据库的某个字段值从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<Void> 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<T> {

    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 <T> Response<T> success(T data){
        Response  response = new Response();
        response.setCode(ResponseCode.SUCCESS);
        response.setData(data);
        return response;
    }

}

以上就是本人关于分层架构的最佳实践。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章