Restful API development tool - RestPack project tutorial

content

  • Background of the project
  • Introduction to RestPack
  • Introduce RestPack dependencies
  • Enable RestPack
  • @RestPackController annotation
  • RestPack exception handling
  • log output
  • Resource sharing and technical exchange

Background of the project

Today, with the prosperity of the Internet, mobile Internet, Internet of Vehicles, and Internet of Things, various client devices emerge in an endless stream. In order to use the same set of server programs to handle the access of various clients, HTTP Restful API has become popular.

However, when the client interacts with the server, there are often some common requirements, such as:

  • The message returned by the server has a set of unified standards, which is conducive to development and maintenance.
  • When an API request is processed by the server, if an exception occurs, it always hopes to give a clear error code in the returned result of the request, and the client can perform further processing according to the error code.
  • In order to facilitate troubleshooting, it is always hoped that for each request, the server will return a requestId, and the background can associate the log generated by this request with this requestId. In this way, once a problem is found during the front-end and back-end joint debugging, the front-end engineer only needs to give the requestId, and the back-end engineer can quickly find the relevant log with this requestId, which is convenient for analysis and troubleshooting. ......

In order to meet these non-functional requirements, the author summarizes the development experience of many previous projects, and summarizes a unified data return format, as follows (in two cases, success and failure):

Successful response content:

{
  "requestId" : "d56c24d006aa4d5e9b8903b3256bf3e3",
  "serverTime" : 1502592752449,
  "spendTime" : 5,
  "resultCode" : "success",
  "data" : {
    "key1": "value1",
    "key2": "value2"
  }
}
  • requestId : The unique ID number of the request generated by the server. When there is a problem with the request, you can take this ID number and quickly query the log information of this request in the massive logs to facilitate troubleshooting.
  • serverTime : server time. In many scenarios, the current time value needs to be used, but the local time of the client may be inaccurate, because the server time is returned here for the client to use.
  • spendTime : The time spent processing this request on the server side, which is displayed here to facilitate the diagnosis of problems related to slow requests.
  • resultCode : the result code, "success" means success, other means an error error code, the value and specific meaning of the error code are agreed by the client and the server in the project.
  • data : Actual business data, the content is determined by the business logic of each API.

Error response content:

{
  "requestId" : "d7ab68ac513e4549896aa33f0cda3518",
  "serverTime" : 1502594589673,
  "spendTime" : 8,
  "resultCode" : "name.duplicate",
  "message" : "昵称重复: terran4j,请换个昵称!",
  "props" : {
    "name": "terran4j"
  }
}

Similar to a successful response, there are fields such as requestId, serverTime, and spendTime. The difference is that resultCode is a custom error code with two additional fields: message and props:

  • message : The description of the error message, which is an easy-to-understand string of information, which is convenient for developers to know the cause of the error.
  • props : Error context-related properties, this item is optional, some error codes may require further processing by the front end in the program, so the backend can provide some key-value property values ​​in props to facilitate the program to read (instead of letting the front end The program obtains these values ​​by parsing the text content from the message).

Introduction to RestPack

To make the implementation of each API in the project follow this unified data specification, it is undoubtedly necessary to write some repetitive code in each API method. Therefore, based on the actual project experience, the author developed a set of toolkit called RestPack , which can help the developers of Restful API to automatically package the returned results of the API into messages in a unified format.

In the word RestPack, Rest stands for Http Restful API, and Pack means "packaging, wrapping", which together means wrapping the returned data in another layer on the basis of the original Http Restful API to conform to the previous data specification.

The main goal of this article is to introduce the usage of RestPack.

Introduce RestPack dependencies

Then, you can reference the restpack jar in your project's pom.xml file as follows:

		<dependency>
			<groupId>terran4j</groupId>
			<artifactId>terran4j-commons-restpack</artifactId>
			<version>${restpack.version}</version>
		</dependency>

The entire pom.xml content looks like:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>terran4j</groupId>
	<artifactId>terran4j-demo-restpack</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>terran4j-demo-restpack</name>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.9.RELEASE</version>
	</parent>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>terran4j</groupId>
			<artifactId>terran4j-commons-restpack</artifactId>
			<version>${restpack.version}</version>
		</dependency>
	</dependencies>

</project>

If it is gradle, please add dependencies in build.gradle as follows:

compile "com.github.terran4j:terran4j-commons-restpack:${restpack.version}"

${restpack.version} The latest stable version, please refer to here

Enable RestPack

In order to enable RestPack in the application, you need to @EnableRestPackannotate the SpringBootApplication class, the entire main program code, as follows:

package com.terran4j.demo.restpack;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.terran4j.commons.restpack.EnableRestPack;

@EnableRestPack
@SpringBootApplication
public class RestPackDemoApp {

	public static void main(String[] args) {
		SpringApplication.run(RestPackDemoApp.class, args);
	}

}

Add @EnableRestPack to enable the function of RestPack, otherwise the effects described below in this article will not work.

@RestPackController annotation

In the past, the implementation of the HTTP Restful API was to use Spring Boot MVC to write a Controller class, and add the @RestController annotation to the class (for readers who are not clear about this, please read the book " Spring Boot Quick Start " written by the author before. , which is described in detail in the Spring Boot MVC chapter).

To enable the RestPack function on the original Controller class, just change the annotation on the class from @RestController to @RestPackController. The code is as follows:

package com.terran4j.demo.restpack;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.terran4j.commons.restpack.HttpResultPackController;
import com.terran4j.commons.util.error.BusinessException;

@RestPackController
@RequestMapping("/demo/restpack")
public class RestPackDemoController {
    
    private static final Logger log = LoggerFactory.getLogger(RestPackDemoController.class);

	@RequestMapping(value = "/echo", method = RequestMethod.GET)
	public String echo(@RequestParam(value = "msg") String msg) throws BusinessException {
	    log.info("echo, msg = {}", msg);
		return msg;
	}
	
}

After writing this class, we start the main program and enter the URL in the browser:

http://localhost:8080/demo/restpack/echo?msg=abc

The result displayed in the browser is:

{
  "requestId" : "2141d927f1de453ba3edd83306ecdf3e",
  "serverTime" : 1502597485688,
  "spendTime" : 21,
  "resultCode" : "success",
  "data" : "abc"
}

If we remove @EnableRestPack (or restore @RestPackController to @RestController), the result of revisiting is only:

abc

It means that RestPack can automatically package the original returned data into the data specification format we defined.

RestPack also works for methods with no return value. For example, we add the following method to the RestPackDemoController class above:

@RequestMapping(value = "/void", method = RequestMethod.GET)
public void doVoid(@RequestParam(value = "msg") String msg) throws BusinessException {
    log.info("doVoid, msg = {}", msg);
}

After restarting the program, enter the URL in the browser:

http://localhost:8080/demo/restpack/void?msg=abc

The results displayed are as follows:

{
  "requestId" : "2df4aa14dfab46e196ebf7e79b2b35d6",
  "serverTime" : 1502627058784,
  "spendTime" : 35,
  "resultCode" : "success"
}

Since the method has no return value, the "data" field does not appear, but the other fields do.

If the return value is a custom complex object, RestPack can also convert it into json format and put it in the "data" field. For example, we add the following code:

@RequestMapping(value = "/hello", method = RequestMethod.GET)
public HelloBean hello(@RequestParam(value = "name") String name) throws BusinessException {
    log.info("hello, name = {}", name);
    HelloBean bean = new HelloBean();
    bean.setName(name);
    bean.setMessage("Hello, " + name + "!");
    bean.setTime(new Date());
    return bean;
}

The class HelloBean is defined as follows:

package com.terran4j.demo.restpack;

import java.util.Date;

public class HelloBean {
	
	private String name;
	
	private String message;
	
	private Date time;

    // 省略 getter /setter 方法。
	
}

After restarting the program, enter the URL in the browser:

http://localhost:8080/demo/restpack/hello?name=neo

The results displayed are as follows:

{
  "requestId" : "ab5c43c3415042b682b290e17fad1358",
  "serverTime" : 1502957833154,
  "spendTime" : 30,
  "resultCode" : "success",
  "data" : {
    "name" : "neo",
    "message" : "Hello, neo!",
    "time" : "2017-08-17 16:17:13"
  }
}

It is found that the fields in "data" correspond to the properties of HelloBean.

RestPack exception handling

When the server throws an exception, RestPack will wrap the exception into an error message and return it.

From the client's point of view, there are two types of exceptions:

  • One is a business exception, such as: the user name already exists during registration, user input error, etc. In this case, the client needs to specify the abnormal cause and key field data so that the client program knows how to prompt the user on the interface.
  • The other is system abnormality, such as: database cannot be accessed, program BUG, ​​etc. This kind of exception needs to be obfuscated on the client side (try to avoid exposing the problems of the system itself), for example, a prompt such as "Sorry, the system has deserted", or "System maintenance, please try again later" pops up.

RestPack provides an exception class called BusinessException to represent business exceptions. If the exception class thrown by the method is the BusinessException class or its subclasses, RestPack will handle it as a business exception, if not, it will handle it as a system exception. To see the effect in action, we add a new method:

@RequestMapping(value = "/regist", method = RequestMethod.GET)
public void regist(@RequestParam(value = "name") String name) throws BusinessException {
    log.info("regist, name = {}", name);
    if (name.length() < 3) {
        String suggestName = name + "123";
        throw new BusinessException("name.invalid")
                .setMessage("您输入的名称太短了,建议为:${suggestName}")
                .put("suggestName", suggestName);
    }
    log.info("regist done, name = {}", name);
}

In the BusinessException class, the parameter in the constructor (such as "name.invalid" above) is the error code, the put(String, Object)method is used to set some exception context properties, which will appear in the props field of the returned message, and the setMessage(String)method is used to set the exception information , which can be used ${}to refer putto fields where the method appears.

Restart the program and visit the URL in your browser:

http://localhost:8080/demo/restpack/regist?name=ne

The result is as follows:

{
  "requestId" : "22e5651199f645628fdf724e9f0826a3",
  "serverTime" : 1502627761012,
  "spendTime" : 1,
  "resultCode" : "name.invalid",
  "message" : "您输入的名称太短了,建议为:ne123",
  "props" : {
    "suggestName" : "ne123"
  }
}

log output

When RestPack starts to process a request, it will generate a unique requestId. This requestId will not only appear in the returned message, but will also be placed in the MDC of the log at the beginning. For log4j or logback (both support MDC), you can Configure the requestId information to be output to the log, so that each log is associated with the requestId.

For example, in the project, configure logback.xml as follows:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="1000">

	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>%date %level requestId=%X{requestId} -- %-40logger{35}[%line]: %msg%n</pattern>
		</encoder>
	</appender>

	<appender name="file" class="ch.qos.logback.core.FileAppender">
		<file>./restpack.log</file>
		<encoder>
			<pattern>%date %level requestId=%X{requestId} -- %-40logger{35}[%line]: %msg%n</pattern>
		</encoder>
	</appender>

	<root level="info">
		<appender-ref ref="stdout" />
		<appender-ref ref="file" />
	</root>

</configuration>

The point is the log output format, that is, add requestId=%X{requestId} to <pattern> :

%date %level requestId=%X{requestId} -- %-40logger{35}[%line]: %msg%n

%X{} is a field in MDC. For the usage of MDC in logback / log4j, readers who are not clear, please search Baidu by yourself.

After logback.xml is configured, restart the service and enter the URL in the browser:

http://localhost:8080/demo/restpack/echo?msg=abc

The resulting console output is as follows:

2017-08-17 16:34:08,570 INFO requestId=ca2a12a0031f493db97856a3300b917a -- c.t.commons.restpack.RestPackAspect     [120]: request '/demo/restpack/echo' begin, params:
{
  "msg" : "abc"
}
2017-08-17 16:34:08,571 INFO requestId=ca2a12a0031f493db97856a3300b917a -- c.t.d.r.RestPackDemoController          [29]: echo, msg = abc
2017-08-17 16:34:08,572 INFO requestId=ca2a12a0031f493db97856a3300b917a -- c.t.commons.restpack.RestPackAdvice     [63]: request '/demo/restpack/echo' end, response:
{
  "requestId" : "ca2a12a0031f493db97856a3300b917a",
  "serverTime" : 1502958848570,
  "spendTime" : 2,
  "resultCode" : "success",
  "data" : "abc"
}

You can see this in the log requestId=ca2a12a0031f493db97856a3300b917a.

The advantage of this is that it is convenient to check the log. For example, in a linux environment, execute a command similar to the following to the log file:

grep -n "requestId=ca2a12a0031f493db97856a3300b917a" xxx.log

(xxx.log is the name of the log file generated by the program), the log of this request can be quickly filtered out of a large number of log contents.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325476979&siteId=291194637