分层模型以及代码工程化规范

       工作也有好几年了,追本溯源,从刚工作开始,我们就一直在分层的路上。我们为什么要分层?这需要衍生出一个词,工程化,分层本身是为了让代码更清晰,是工程化的一种手段。而在分布式发展至今,系统间调用越来越多,我们同样离不开分层。
       但同时我也发现一些问题,工作中很多人虽然用了分层,但是却存在了很多问题,比如:

  1. service层返回统一结果封装,平级调用无法直接使用,使用前需要判断code码,code码不应该是当前系统需要关注的东西,而是对外提供的,外部调用需要关注的东西;同时,code码出现在service层说明一个问题,层级界限不分明,层级职责不明确,且编码规范不统一。虽然编码风格可以多种多样,因为涉及到个人习惯,但是例如分层规范等工程化规范还是必须遵守。
  2. rpc服务没有provider层,直接暴露service。需要在service层之上有一层provider层,用来对错误码等信息作统一处理,提高service层的复用性。
  3. 对外部服务调用的code码处理不应该分散在各个层级,会增加编码的复杂度,我们不希望面向报文编程,我们要的是清晰的业务逻辑,所以,code码处理统一下沉,为系统多提供一层client层,统一以当前系统的异常的形式处理code码,如果业务中需要对code进行特殊处理,单独捕获处理即可,详细可以参考下面的异常规范。

       这篇文章的目的,主要就是希望规范这些问题,得到一个工程化的最终目标,业务和领域相应得隔离开来,让每一层关注各自的关注点,利于编程,也利于维护。

正文

一、分层说明

controller层:
  1. 只做参数校验,Request/Response转化内部参数,以及其他参数转化功能,作为薄薄的一层门面模式。只有这一层以及Facade层才会出现Request和Response对象,确保service层的纯粹性。
  2. 入参只接受VO/DTO/QUERY,出参可以是VO或DTO。
  3. 需要加入统一结果封装和统一异常处理,将结果封装为需要的结果,所以VO转化也可以以转化器的形式出现在这里,特别是当没有Facade层时。
provider层:
  1. 和controller层属于同一层,基本与controller层一致,只是分工不同。
  2. 用于对外提供rpc等服务,进行统一结果封装,统一处理等。
    注:dubbo官方提倡的是抛异常,而阿里规约提倡封装统一结果,所以如果是dubbo,采用官方方案时可以不需要做统一结果封装,同时在这一层之前所有的层级都不允许提前封装成一个统一结果对象。(阿里规约中的开放接口层,个人认为是与controller层同一层的东西)
Facade层:
  1. 可选层,业务编排层,用于参数转化,部分前置逻辑校验,Request/Response转化内部参数,小业务拼装编排,好处是项目当项目逐渐变大时,需要做服务的拆分,这个时候因为Facade层的存在,可以很方便的进行代码迁移,同时让service颗粒度减小,更方便业务的开发和维护,实现业务编排的功能。
  2. 领域对象的转化,当业务进行到一定的程度,出现DDD时,如果不直接改造成DDD分层,这一层可以用来将正常的DTO在业务编排后拼装产生一个新的领域对象。(对DDD了解得不多,这一点可能不是很准确,后期发现问题再进行更新)
  3. Facade只承接Service层,且同层不接受通信。入参出参与service基本保持一致,VO转化可以放在这一层。
service层:
  1. 于Facade层、Controller层、Provider层之下
  2. 主要作用是较合适的业务纬度的颗粒
  3. 同层允许通信,当调用的维度可以到实体维度时不允许调用业务维度的颗粒。
  4. service颗粒度不应该太小,小到单个业务即可,也不要太大。
  5. 出参必须都是DTO,入参必须是DTO。
manage层:
  1. 可选层,于service层与Dao层之间
  2. 用于为service层增加缓存,消息发送等能力,做能力增强用,并且下沉一些批量Dao操作(如事务颗粒等),以及衔接外部接口调用,做统一处理,包括报文转换,异常(码)转换等。
  3. 套用阿里规约定义的manage层定义:1) 对第三方平台封装的层,预处理返回结果及转化异常信息; 2) 对Service层通用能力的下沉,如缓存方案、中间件通用处理; 3) 与DAO层交互,对DAO的业务通用能力的封装。
client层:

主要用于衔接外部接口调用,做统一处理,利用这一层进行接受外来报文的转换,异常处理,结果处理等。
       注:个人规范未参考阿里规约前的设计,使用了这一层作为外部接口的统一处理,现归并到Manage层,做ClientManage用。

mapper(DAP)层:
  1. 于service层之下,主要作用是建立实体维度的颗粒,即数据层。出入参只接受DO

分层模型以及代码工程化规范_第1张图片

       在分布式情形下不同的分层需要分离到不同的系统中,具体如下图:
分层模型以及代码工程化规范_第2张图片

       可以看出,如果有facade的存在,当服务进行分割时可以搬运代码,可以不改动原有代码结构。

异常规范说明

       异常存在的形式:大的分可以分为系统异常和业务异常,业务异常需要继续进行细分,分为必须捕获的Exception —— BusinessRequiredException,可捕获的统一的RuntimeException(也可以不捕获)——BusinessException,自定义的允许下游做特殊捕获的自定义异常——继承BusinessException的异常。

       dubbo需要例外处理,进行Filter的编写,以便错误码/异常在不同系统间传输。
分层模型以及代码工程化规范_第3张图片

​       这里有一个点需要说明,阿里规约和Dubbo官方推荐不一致,该如何选择需要自己决定
Dubbo官方:
分层模型以及代码工程化规范_第4张图片
阿里规约:
分层模型以及代码工程化规范_第5张图片
       个人更倾向于抛异常的形式,这要求整个公司规范异常类或者规范Manage必须对外部接口调用进行异常类转换,比如:

  1. 统一的基础异常类BusinessException,这种方案不需要转化,但是依旧建议保留Manager层,只是方法会偏门面。
  2. 如下:
try {
	rpcInterface.method();
} catch (BusinessException e) {
	// 转化为内部异常
	throw new InnerBusinessException(e.getCode(), e.getMessage());
}

       使用统一结果时,处理方式如下:

Result result = rpcInterface.method();
if (isBizError(result)) {
	throw new BusinessException(result.getCode(), result.getMessage());
}
if (isException(result)) {
	throw new SystemException(result.getMessage());
}
return result.getData();

错误码规范说明

       错误码对于下游系统来说有几种处理方式,一种是接到以后直接包成异常类,或者直接使用,一种是经过当前系统的错误码系统转换,但是尽量不要引入别的系统的错误码枚举类型。

       错误码定义在当前系统需要同时定义namespace,要避开重复的namespace;对于调用方系统处理,提供三套方案,可直接透传/使用;可以在外部系统的错误码前加入特定前缀,然后继续向下传输;也可以通过翻译错误码过滤掉别的系统的错误码。其中,第一第二种更适用于内部系统间,第三种更适用于对接第三方系统。

       错误对象数据结构如下:

{
  "result":"failed", // 备选项,用于多成功码(用处极小,没必要且不建议,当前系统中有类似情况才加的)
  "code":"OTS5001",
  "msg":"message",
  "extMsg":"ext message", // 扩展消息,保存不翻译的错误信息等
  "namespace":"OTS",	// namespace可以不要,给code加前缀
  "data":
}

你可能感兴趣的:(rpc,网络协议,网络)