Design of platform level exception framework

As important as code readability, strict awareness of exception handling is a developer's professional quality and basic skill. In the early era of J2EE technology, the tight coupling of front and back ends often results in the situation that the back end reports an error directly to the front end, that is, the page is a piece of abnormal code. This error display method can not be accepted by customers in enterprise applications, especially in Bank projects, it is necessary to capture all exceptions and be able to escape to the front end. Based on my previous experience in power and banking projects, this paper summarizes and shares the following practical abnormal framework design in the era of front-end and back-end separation:

Design of error code

Error code is the first essential to deal with exceptions of external interface and RPC method. The design of error code does not need to be complicated, but it needs to be unique to avoid conflict or similarity with the error code of docking system. The definition of error code is generally recommended to start with letters and combine 4-7 digits. Error code information should have a brief escape description. It is recommended to write an enumeration class to express it. In my platform code, the error codes are divided into two enumeration, one is the general error code, and the other is the service/dao layer error code. The code example is as follows:

/*
 * Copyright 2020-2030 ALPHA LAB.Inc All Rights Reserved.
 */
package com.alphalab.framework.types;

/**
 * Universal return code
 * @author allen
 * @version 1.0.0 2020 March 2nd 2013
 */
public enum CommonResultCodeEnum {
	
	SUCCESS_HANDLE("M800000", "Successful treatment"), ERROR_HANDLE("M900000", "Processing failure");
	
	/**
	 * Return code.
	 */
	private String code;
	
	/**
	 * Return to description
	 */
	private String msg;
	
	/**
	 * Initialize return code enumeration
	 * @param code Return code
	 * @param msg Return information
	 */
	private CommonResultCodeEnum(String code, String msg) {
		this.code = code;
		this.msg = msg;
	}

	/**
	 * Get the return code
	 * @return String
	 */
	public String getCode() {
		return code;
	}

	/**
	 * Get the return description
	 * @return String
	 */
	public String getMsg() {
		return msg;
	}
	
}
/*
 * Copyright 2020-2030 ALPHA LAB.Inc All Rights Reserved.
 */
package com.alphalab.framework.types;

/**
 * Common service layer / dao layer error codes
 * @author allen
 * @version 1.0.0 2020 February 27th 2013
 */
public enum MoErrorCodeEnum {
	
	ERROR_DATA_ACCESS_EXCEPTION("M900001", "Data acquisition exception"), 
	ERROR_ENTITY_NOTFOUND_EXCEPTION("M900002", "Object get failed empty"), 
	ERROR_FOUND_BUT_REASON_UNKNOWN("M900003", "service Layer unknown exception");

	/**
	 * Error code.
	 */
	private String code;
	
	/**
	 * Error description
	 */
	private String msg;
	
	/**
	 * Initialize error code enumeration
	 * @param code Error code
	 * @param msg error message
	 */
	private MoErrorCodeEnum(String code, String msg) {
		this.code = code;
		this.msg = msg;
	}

	/**
	 * Get error code
	 * @return String
	 */
	public String getCode() {
		return code;
	}

	/**
	 * Get error description
	 * @return String
	 */
	public String getMsg() {
		return msg;
	}
	
}

Return the design of the object

In general, the return object returned by the server to the front end cannot directly return the business data json object, but needs to return the Result object encapsulating the business data / data set, as well as isSuccess, error code and error information. For "error information", it can be divided into "short information" to display the front-end users' view and "exception stack information" to facilitate developers' troubleshooting. Stack information can be selectively not displayed in the front-end. The return object generally designs the list Result object and json common object to inherit the Result object. In addition, the construction method of return success / failure object can meet the development needs. The code example is as follows:

/*
 * Copyright 2020-2030 ALPHA LAB.Inc All Rights Reserved.
 */
package com.alphalab.framework.domain.dto;

import java.io.PrintWriter;
import java.io.StringWriter;
import com.alphalab.framework.domain.IValueObject;
import com.alphalab.framework.types.CommonResultCodeEnum;

/**
 * Result object base class
 * @author allen
 * @version 1.0.0 2020 March 2nd 2013
 */
public class ResultVO implements IValueObject {

	/**
	 * Return code.
	 */
	protected String code;
	
	/**
	 * Return to description
	 */
	protected String msg;
	
	/**
	 * Error details / exception stack information
	 */
	protected String errorDetail;
	
	/**
	 * Whether the execution is successful
	 */
	protected boolean success;
	
	public ResultVO() {
	}
	
	/**
	 * Construct the result object in case of exception
	 * @param throwable Throwable abnormal
	 */
	public ResultVO(Throwable throwable) {
		if (throwable instanceof MoRuntimeException) {
			MoRuntimeException moex = (MoRuntimeException)throwable;
			this.code = moex.getErrorCode();
			this.msg = moex.getMessage();
		} else {
			this.code = CommonResultCodeEnum.ERROR_HANDLE.getCode();
	    	this.msg = CommonResultCodeEnum.ERROR_HANDLE.getMsg();
		}
    	this.errorDetail = getStackTrace(throwable);
    	this.success = false;
	}
	
	/**
	 * Construct the result object in case of exception
	 * @param errorCode Error code
	 * @param errorMsg error message
	 */
	public ResultVO(String errorCode, String errorMsg) {
		this.code = errorCode;
    	this.msg = errorMsg;
    	this.success = false;		
	}
	
	/**
	 * Construct the result object in case of exception
	 * @param errorCode Error code
	 * @param errorMsg error message
	 * @param throwable Throwable abnormal
	 */
	public ResultVO(String errorCode, String errorMsg, Throwable throwable) {
		this(errorCode, errorMsg);
		this.errorDetail = getStackTrace(throwable);
	}

	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}

	public boolean getSuccess() {
		return success;
	}

	public void setSuccess(boolean success) {
		this.success = success;
	}
	
	public String getErrorDetail() {
		return errorDetail;
	}

	public void setErrorDetail(String errorDetail) {
		this.errorDetail = errorDetail;
	}
	
	/**
	 * Gets the exception stack information in the form of a string
	 * @param throwable Throwable abnormal
	 * @return String
	 */
	private static String getStackTrace(Throwable throwable) {
	    StringWriter stringWriter= new StringWriter();
	    PrintWriter writer= new PrintWriter(stringWriter);
	    throwable.printStackTrace(writer);
	    StringBuffer buffer= stringWriter.getBuffer();
	    return buffer.toString();
	}
	
	/**
	 * Set processing success result
	 */
	public void setSuccessResult() {
		this.code = CommonResultCodeEnum.SUCCESS_HANDLE.getCode();
    	this.msg =CommonResultCodeEnum.SUCCESS_HANDLE.getMsg();
    	this.success = true;
    }

	@Override
	public String toString() {
		return "ResultVO [code=" + code + ", msg=" + msg + ", success="
				+ success + "]";
	}
	
}
/*
 * Copyright 2020-2030 ALPHA LAB.Inc All Rights Reserved.
 */
package com.alphalab.framework.domain.dto;

/**
 * Single result set object / operation result object
 * @author allen
 * @version 1.0.0 2020 March 2nd 2013
 */
public class SingleResultVO<T> extends ResultVO {

	/**
	 * Result object
	 */
	private T data;
	
	public SingleResultVO() {}
	
	/**
	 * Construct the normal result object
	 * @param data Single result set
	 */
	public SingleResultVO(T data) {
		super.setSuccessResult();
		this.data = data;
	}
	
	/**
	 * Construct the result object in case of exception
	 * @param throwable Throwable abnormal
	 */
	public SingleResultVO(Throwable throwable) {
		super(throwable);
	}

	/**
	 * Construct the result object in case of exception
	 * @param errorCode Error code
	 * @param errorMsg error message
	 */
	public SingleResultVO(String errorCode, String errorMsg) {
		super(errorCode, errorMsg);
	}
	
	/**
	 * Construct the result object in case of exception
	 * @param errorCode Error code
	 * @param errorMsg error message
	 * @param throwable Throwable abnormal
	 */
	public SingleResultVO(String errorCode, String errorMsg, Throwable throwable) {
		super(errorCode, errorMsg, throwable);
	}

	public T getData() {
		return data;
	}

	public void setData(T data) {
		this.data = data;
	}

	@Override
	public String toString() {
		return "SingleResultVO [data=" + data + ", code=" + code 
				+ ", msg=" + msg + ", success=" + success + "]";
	}
	
}
/*
 * Copyright 2020-2030 ALPHA LAB.Inc All Rights Reserved.
 */
package com.alphalab.framework.domain.dto;

import java.util.List;

/**
 * Multiple result set objects
 * @author allen
 * @version 1.0.0 2020 March 2nd 2013
 */
public class ListResultVO<T> extends ResultVO {
	
	/**
	 * list Result set.
	 */
	private List<T> list;
	
	public ListResultVO() {}
	
	/**
	 * Construct the normal result object
	 * @param data Result set
	 */
	public ListResultVO(List<T> list) {
		super.setSuccessResult();
		this.list = list;
	}
	
	/**
	 * Construct the result object in case of exception
	 * @param throwable Throwable abnormal
	 */
	public ListResultVO(Throwable throwable) {
		super(throwable);
	}

	/**
	 * Construct the result object in case of exception
	 * @param errorCode Error code
	 * @param errorMsg error message
	 */
	public ListResultVO(String errorCode, String errorMsg) {
		super(errorCode, errorMsg);
	}
	
	/**
	 * Construct the result object in case of exception
	 * @param errorCode Error code
	 * @param errorMsg error message
	 * @param throwable Throwable abnormal
	 */
	public ListResultVO(String errorCode, String errorMsg, Throwable throwable) {
		super(errorCode, errorMsg, throwable);
	}

	public List<T> getList() {
		return list;
	}

	public void setList(List<T> list) {
		this.list = list;
	}
	
	@Override
	public String toString() {
		return "ListResultVO [list=" + list + ", code=" + code 
				+ ", msg=" + msg + ", success=" + success + "]";
	}

}

Design of exception base class

The purpose of designing exception base class is to unify the standard handling of exceptions. For example, all exceptions in constraint platform can have corresponding error codes, constraint runtime and exception classes corresponding to each layer. Generally, two base classes are defined, the checked base class and the unchecked base class (runtime exception class) for the platform. The service/dao layer also needs to customize an exception class that inherits the runtime exception base class. These custom exception classes are all the same methods and parameter columns, only the error codes are different. Take the exception base class at runtime and the exception class code at service layer as examples, as follows:

/*
 * Copyright 2020-2030 ALPHA LAB.Inc All Rights Reserved.
 */
package com.alphalab.framework.exception;

import java.text.MessageFormat;

/**
 * Unchecked base class exception
 * @author allen
 * @version 1.0.0 2020 February 27th 2013
 */
public class MoRuntimeException extends RuntimeException {
	
	/**
	 * Error code.
	 */
	private String errorCode;
	
	/**
	 * Initialize exception instance
	 * @param newErrorCode Error code
	 * @param message Wrong description
	 */
	public MoRuntimeException(String newErrorCode, String message) {
		super(message);
		errorCode = newErrorCode;
	}

	/**
	 * Initialize exception instance
	 * @param newErrorCode Error code
	 * @param message Wrong description
	 * @param messageParams Error description placeholder value
	 */
	public MoRuntimeException(String newErrorCode, String message, Object... messageParams) {
		super(MessageFormat.format(message, messageParams));
		errorCode = newErrorCode;
	}
		
	/**
	 * Initialize exception instance
	 * @param newErrorCode Error code
	 * @param cause Exception information stack
	 */
	public MoRuntimeException(String newErrorCode, Throwable cause) {
		super(cause);
		errorCode = newErrorCode;
	}
	
	/**
	 * Initialize exception instance
	 * @param newErrorCode Error code
	 * @param message Wrong description
	 * @param cause Exception information stack
	 */
	public MoRuntimeException(String newErrorCode, String message, Throwable cause) {
		super(message, cause);
		errorCode = newErrorCode;
	}

	/**
	 * Get error code
	 * @return String
	 */
	public String getErrorCode() {
		return errorCode;
	}
	
	/**
	 * Get exception description
	 * @return String
	 */
	public String getMessage() {
		return super.getMessage();
	}
	
}
/*
 * Copyright 2020-2030 ALPHA LAB.Inc All Rights Reserved.
 */
package com.alphalab.framework.exception;

import com.alphalab.framework.types.MoErrorCodeEnum;

/**
 * service Layer unifies the highest exception. Inherits the unchecked exception base class
 * @author allen
 * @version 1.0.0 2020 March 1st 2013
 */
public class MoServiceException extends MoRuntimeException {
	
	/**
	 * Default error code
	 */
	public static final String DEFAULT_ERROR_CODE = 
			MoErrorCodeEnum.ERROR_FOUND_BUT_REASON_UNKNOWN.getCode();
	
	/**
	 * Default error description
	 */
	public static final String DEFAULT_ERROR_MSG = 
			MoErrorCodeEnum.ERROR_FOUND_BUT_REASON_UNKNOWN.getMsg();

	/**
	 * Initialization service layer exception
	 * @param cause Exception information stack
	 */
	public MoServiceException(Throwable cause) {
		super(DEFAULT_ERROR_CODE, DEFAULT_ERROR_MSG, cause);
	}

	/**
	 * Initialize exception instance
	 * @param newErrorCode Error code
	 * @param message Wrong description
	 */
	public MoServiceException(String newErrorCode, String message) {
		super(newErrorCode, message);
	}

	/**
	 * Initialize exception instance
	 * @param newErrorCode Error code
	 * @param message Wrong description
	 * @param messageParams Error description placeholder value
	 */
	public MoServiceException(String newErrorCode, String message, Object... messageParams) {
		super(newErrorCode, message, messageParams);
	}
		
	/**
	 * Initialize exception instance
	 * @param newErrorCode Error code
	 * @param cause Exception information stack
	 */
	public MoServiceException(String newErrorCode, Throwable cause) {
		super(newErrorCode, cause);
	}
	
	/**
	 * Initialize exception instance
	 * @param newErrorCode Error code
	 * @param message Wrong description
	 * @param cause Exception information stack
	 */
	public MoServiceException(String newErrorCode, String message, Throwable cause) {
		super(newErrorCode, message, cause);
	}

}

Layered exception handling code example

In the dao layer, the methods with small fine-grained range generally do not capture exception handling. Exceptions are usually captured and handled by the service layer at the upper layer and thrown to the controller layer at the top layer. In this layer, exceptions need not be thrown continuously, but are translated into result objects for display by the front end. The code example is as follows:

package com.test.exception;

import com.alphalab.framework.domain.dto.SingleResultVO;
import com.alphalab.framework.exception.MoDaoException;
import com.alphalab.framework.exception.MoRuntimeException;
import com.alphalab.framework.exception.MoServiceException;
import com.alphalab.framework.log.IMoLog;
import com.alphalab.framework.log.MoLogFactory;

/**
 * Exception, result object programming example
 * @author allen
 * @version 1.0.0 2020 March 22nd 2013
 */
public class ResultVOTest {
	
	private static final IMoLog molog = MoLogFactory.getLog(ResultVOTest.class);
	
	public static void main(String[] args) {
		//Impersonate client call
		ResultVOTest test = new ResultVOTest();
		SingleResultVO importResultDTO = test.getBaseInfo();
		molog.info(importResultDTO);
		molog.info("\n" + importResultDTO.getErrorDetail());
	}
	
	/**
	 * Simulate the controller layer
	 * @return SingleResultVO
	 */
	public SingleResultVO getBaseInfo() {
		SingleResultVO importResultDTO = new SingleResultVO();
		try {
			ResultVOTest test = new ResultVOTest();
			String res = test.getImportMsg();
			importResultDTO = new SingleResultVO(res);
		} catch (MoRuntimeException ex) {
			importResultDTO = new SingleResultVO(ex);
		}
		return importResultDTO;
	}
	
	/**
	 * Simulate the service layer
	 * @return String
	 * @throws MoRuntimeException
	 */
	public String getImportMsg() throws MoRuntimeException {
		String result = "";
		try {
			int sData = 0;
			try {
				sData = crudDao();
			} catch (Exception e) {
				//Catch dao layer exception and throw MoDaoException
				throw new MoDaoException(e);
			}
			result = String.valueOf(sData);
			if ("0".equals(result)) {
				//The unexpected exception directly throws the runtime exception, and the user-defined error code and error description
				throw new MoServiceException("90002", "Error in obtaining business data");
			}
		} catch (MoDaoException ex) {
			throw ex;
		} catch (MoServiceException ex) {
			throw ex;
		} catch (Exception ex) {
			//Finally, unexpected exceptions are caught, encapsulated as runtime exceptions and thrown
			molog.error("Other anomalies", ex);
			throw new MoRuntimeException("90001", ex);
		}
		//Return is placed outside of catch to avoid conflict with return in try
		return result;
	}
	
	/**
	 * Simulate dao layer
	 */
	public int crudDao() {
		int rst = 8/0;
		return rst;
	}
	
}

Operation result:

2020-03-22 17:56:42,286 [main] INFO  com.test.exception.ResultVOTest.main(ResultVOTest.java:23)  - SingleResultVO [data=null, code=M900001, msg=Data acquisition exception, success=false]
2020-03-22 17:56:42,286 [main] INFO  com.test.exception.ResultVOTest.main(ResultVOTest.java:24)  - 
com.alphalab.framework.exception.MoDaoException: Data acquisition exception
	at com.test.exception.ResultVOTest.getImportMsg(ResultVOTest.java:56)
	at com.test.exception.ResultVOTest.getBaseInfo(ResultVOTest.java:35)
	at com.test.exception.ResultVOTest.main(ResultVOTest.java:22)
Caused by: java.lang.ArithmeticException: / by zero
	at com.test.exception.ResultVOTest.crudDao(ResultVOTest.java:80)
	at com.test.exception.ResultVOTest.getImportMsg(ResultVOTest.java:53)
	... 2 more

Exception handling protocol

Finally, according to the past practical project experience, the following key exception handling specifications are sorted out:

1) The error response cannot expose sensitive information.

During exception handling, do not expose the information of the server to the end user in the error description of the returned result object, such as the IP address of the server, the type and version of the operating system, session identifier, account information, database, middleware information, etc., so as to avoid increasing the possibility of the server being attacked by hackers. Therefore, when exceptions such as FileNotFoundException, SQLException, BindException are thrown to the front end, escape processing should be done well.

2) Catch all program exceptions.

Can not appear program exception is not caught directly to the foreground. Therefore, both the service layer and the controller layer should try to catch exceptions in the whole method body. In addition, if there is any additional processing in catch/finally, you also need to catch exceptions.

3) Do not use return in a finally block.

Many people are used to writing return results at the end of a try. In fact, return should be placed outside the try catch. Even if there is an exception, return will be executed at the end.

4) Do not throw a RuntimeException, Exception, or Throwable directly.

These exceptions are not easy to check the real cause of the error, and they cannot be caught for targeted processing. Because there are too many reasons for this exception, you should use a custom exception with business meaning.

Keywords: Programming Java JSON Session

Added by rastem on Sun, 22 Mar 2020 13:01:55 +0200