博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
微服务接口设计规范和统一异常处理策略
阅读量:6371 次
发布时间:2019-06-23

本文共 7468 字,大约阅读时间需要 24 分钟。

hot3.png

背景

公司内部服务架构越来越趋向微服务,有着大量接口在相互调用。时间推移接口越来越多,服务的规模数量越急剧增加,同时每个服务的接口设计杂乱无章。如名称不同、判断逻辑不同、错误码不同、字段数量或多或少等等,这在一个分布式系统中是非常头疼的事情,往往一个实现需要对接多个服务(甚至7-8个服务调用)。

公司的Dubbo微服务架构,很多公司都搭建在内部产品中去使用,越来越趋向于阿里的大中台架构。针对这样的背景我们需要进行接口返回规则统一设计,以达到公司内部所有服务都统一的输出规则。

也类似于开放平台返回参数设计,如微信、支付宝等都是统一的JSON格式加上返回码的策略。这块的定义大多数公司思路是相似的,归宗来看主要如下:

public class Result{	Code code;	String msg;	Object data;}

data用来返回数据,可以是对象也可以是列表;msg用来返回错误的描述;code返回的是规定格式的错误码,枚举是最为合适;再加上分页结果集的设计基本涵盖到所有场景。

我们设计的思路就是:

要规范返回参数字段的名字和数量,约定所有的接口返回是一套标准,尽可能是简单字段越少越好。如:统一封装到Result对象。

详细的接口设计思路和例子

详细的接口返回类设计思路,主要考虑enum用来作为消息类型,Object或T作为数据类型来使用。

public class Result{	Code code;	String msg;	Object data;		protected Result(){}		private Result(Code code,String msg,Object data){		this.code = code;		this.msg = msg;		this.data = data;	}		public static Result success(){		return new Result(Code.success,Code.success.getDesc(),null);	}	public static Result error(){		return new Result(Code.system_error,Code.system_error.getDesc(),null);	}	// 这里针对异常处理封装	public static Result error(Throwable e){		if(e instanceof ResultException){			ResultException ex = (ResultException)e;			return new Result(ex.getCode(),e.getMsg(),null);		}		return new Result(Code.system_error,Code.system_error.getDesc(),null);	}	//省略很多代码success(..),error(..)复制方法}
public enum Code{	success(0,"成功"),	system_error(-1,"系统错误"),	paramter_invalid(1,"请求参数不合法"),	;	private int num;	private String desc;// 省略contruct \ getXX \setXX}

如考虑严格限制返回类型,可以考虑将Object data换成范型 T data,这样可以限制接口返回必须是规定的类型。参考如下:

public class Result
{ Code code; String msg; T data; //类似上面Result设计}

这里的BaseModel是空对象,返回的数据对象需要继承它。

public abstract class BaseModel implements Serializable{}

UserInfo是具体的业务对象,参考具体的业务场景来定义。

public class UserInfo extends BaseModel{	Long id;	String name;	//省略代码}

针对分页返回结果集设计重点是分页信息类,这点和Mybatis的PageHelper的分页类思路相似,如下格式:

public class PageInfo{	int size;	int number;	int total;	//省略代码}
public class ResultPage{	PageInfo page;		private ResultPage(){}	private ResultPage(int size,int number,int total){		super();		this.page = new PageInfo(size,number,total);	}	public static ResultPage success(){		//代码省略	}		public static ResultPage error(){		return new ResultPage(0,0,0);	}	//省略很多代码success(..)和error(..)}

统一异常处理设计

一般的业务思路下使用Result.success()和Result.error()基本涵盖需求。针对事务的回退要求,需要我们进行throw exception操作。常规写法如下:

public class IDemoServiceImpl implements IDemoService{	@Override	@Transcational	public Result searchDemoInfo(){		//具体业务逻辑	}}

在每个方法里面写try..catch来单独处理异常,这样虽能能解决问题但代码冗余太重也很笨。新定义一个方法来实现事务的throw exception,如下:

public class IDemoServiceImpl implements IDemoService{	@Override	public Result searchDemoInfo(){		try{			this.doOne();		} catch (Exception e){			//省略		}	}	@Transcational	private void doOne(){		//具体业务逻辑	}}

我们需要全局来统一处理,而不是对业务进行侵入;只有分离解藕后续我们才能灵活的进行迭代改造。目前使用最多的Http Rest和Dubbo Rpc协议接口,分别使用Spring MVC和Dubbo这两种框架。统一异常处理核心的思想是Spring AOP的aspect,Dubbo比较特别一点可以抛出异常到service customer端处理。

Dubbo接口的异常统一策略

深入聊一下dubbo异常抛出的策略,查看源码类:ExceptionFilter.class。dubbo异常抛出策略主要有以下几种:

  • RuntimeException和Exception异常可以抛出;
  • 接口上申明了异常类的,可以直接抛出到服务调用者;
  • 异常类和接口在一个jar包内,已可以直接抛出到调用者。
  • 若异常类的package前缀是java.*或javax.*也可以直接抛出;
  • dubbo本身的RcpException可以直接抛出。
@Activate(group = Constants.PROVIDER)public class ExceptionFilter implements Filter {//省略很多代码public Result invoke(Invoker
invoker, Invocation invocation) throws RpcException {try { Result result = invoker.invoke(invocation); if (result.hasException() && GenericService.class != invoker.getInterface()) { try { Throwable exception = result.getException(); // 如果是checked异常,直接抛出 if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) { return result; } // 在方法签名上有声明,直接抛出该申明异常 try { Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); Class
[] exceptionClassses = method.getExceptionTypes(); for (Class
exceptionClass : exceptionClassses) { if (exception.getClass().equals(exceptionClass)) { return result; } } } catch (NoSuchMethodException e) { return result; } // 未在方法签名上定义的异常,在服务器端打印ERROR日志 logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception); // 异常类和接口类在同一jar包里,直接抛出 String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){ return result; } // 是JDK自带的异常,直接抛出 String className = exception.getClass().getName(); if (className.startsWith("java.") || className.startsWith("javax.")) { return result; } // 是Dubbo本身的异常,直接抛出 if (exception instanceof RpcException) { return result; } // 否则,包装成RuntimeException抛给客户端 return new RpcResult(new RuntimeException(StringUtils.toString(exception))); } catch (Throwable e) { logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); return result; } } return result;} catch (RuntimeException e) { logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); throw e;}}}

我们采用自定义异常类来统一封装处理,在接口包里面定义异常类:ResultException extend RuntimeException。这样可以对异常进行统一封装处理返回Result或者直接抛出自定义异常ResultException,这里推荐采用Aspect进行处理后返回给调用者Result,通过Code状态码判断即可。

public class ResultException extends RuntimeException{	Code code;	String msg;	public ResultException(Code code){		this.code = code;		this.msg = code.system_error.getDesc();	}	public ResultException(Code code,String msg){		this.code = code;		this.msg = msg;	}	//省略很多代码}

统一异常处理,AOP思想的around方式包裹整个method进行异常捕获,转换成标准输出给调用者,如下:

@Aspect@Componentpublic class DubboResultExceptionHandler{	@Around("execution(public * com.xxx.xx.xx.service.I*Impl.*(..))")	public Result aroudResult(ProceedingJoinPoint pjp){	try{		Object result = pjp.proceed();		if(!(result instanceof Result))			return Result.error();		return (Result)result;	} catch(Throwable e){		// 这里请参考前面Result.error(..)的设计		return Result.error(e);	}	}}

接口设计思路,听过抛出异常来滚回事务,如下:

public class IDemoServiceImpl implements IDemoService{	@Override	@Transcational	public Result searchDemoInfo(Long id){	this.doOne();	if(id < 10)	throw new ResultException(Code.paramter_invalid,"id不能小于10");	return Result.success();	}		private void doOne(){	//其他业务实现	}}

SpringMVC接口异常统一策略

SpringMVC异常处理依赖@ControllerAdvice和ResponseEntityExceptionHandler,可以拦截Controller层抛出的指定异常处理统一返回Result。

@ControllerAdvicepublic class GlobalExceptionHandler extends ResponseEntityExceptionHandler {	@ExceptionHandler({Exception.class, RuntimeException.class})	@ResponseBody	public Result doHandler(Exception e){		Result error;		if(e instanceof ResultException){		ResultException me = (ResultException) e;		error = Result.error(me.getCode(),me.getMsg())		} else {		e.printStackTrace();		error = Result.error(Code.system_error,Code.system_error.getDesc());		}		return error;	}}

技术的路上我们风雨同行,感谢你们的支持。


作者:Owen jia,推荐关注他的博客: 。

转载于:https://my.oschina.net/timestorm/blog/2964221

你可能感兴趣的文章
剑指offer--3
查看>>
堆排序C++实现
查看>>
[ JS 基础 ] JS 浮点数四则运算精度丢失问题 (3)
查看>>
如何在ABAP Netweaver和CloudFoundry里记录并查看日志
查看>>
Sublime Text 3.2 发布首个维护版本 3.2.1
查看>>
新人学习之IDEA中常用的git操作
查看>>
MySQL:Innodb Handler_read_* 变量解释
查看>>
规则引擎在公安系统中的应用及作用
查看>>
Android桌面小部件AppWidget:音乐播放器桌面控制部件Widget(3)
查看>>
09 - JavaSE之线程
查看>>
云界十年群雄论剑,第十届中国云计算大会开幕
查看>>
Xshell用鼠标选中一段文字后自动换行的问题
查看>>
英特尔与Voke合作,用VR直播纽约时装周
查看>>
第135天:移动端开发经验总结
查看>>
Dozer 使用小结
查看>>
第153天:关于HTML标签嵌套的问题详解
查看>>
可通过区块链实现去中心化的三大网络巨头
查看>>
vue-element-admin 4.0.1 发布,后台集成方案
查看>>
一位耶鲁教授,在和大公司比谁最快造出第一台量子计算机
查看>>
TMS云应邀参加第六届西部国际物流博览会
查看>>