In recent years, I have been in touch with several new projects intermittently, all of which were designed by myself for coding framework. The interface design forms of these projects are similar, such as HTTP, nested JSON, unified gateway path, command code distribution, etc. Gradually, I gained some insights and experience in the framework design and coding of back-end projects in this form.
The following is the general form of the interface:
/ / request
{
"mchId" : "xxx"."version" : "1.0"."signType" : "SHA256"."timeStamp" : "xxx"."bizType": ""."bizContent" : {
"xxx":"xxx"."xxx":"xxx"
},
"sign":""
}
/ / response
{
"gateCode":""."gateMsg":""."bizCode":""."bizMsg":""."sign":""
}
Copy the code
The bizType field is used to distinguish different services, such as Query and pay. Different bizTypes correspond to different bizContent.
The Command class design
public abstract class AbsCommand<T extends OpenApiBizContentVo> {
public OpenApiBaseResponse progress(String ori) {
// The original parse request packet is a JavaBean
OpenApiBaseRequestVo<T> reqInstance = null;
try {
reqInstance = JSON.parseObject(ori, new TypeReference<OpenApiBaseRequestVo<T>>(this.getBizContentClass()) {});
}
catch (Exception e) {
//
}
// omit the verification
// Invoke the business processing method
return this.progress(reqInstance);
}
public abstract OpenApiBaseResponse progress(OpenApiBaseRequestVo<T> context);
public abstract Class<T> getBizContentClass(a);
}
Copy the code
This is the most basic skeleton, unified request operations, unified command distribution, and the virtual method getBizContentClass, let the subclass return the actual bizContent type, plus the FastJSON framework for generic support to solve the problem of nested JSON parsing.
If bizType is query, we can do this:
public class Query extends AbsCommand<OpenApiQueryBizContentVo>{
@Override
public OpenApiBaseResponse progress(OpenApiBaseRequestVo<OpenApiQueryBizContentVo> context) {
// do something...
return null;
}
@Override
public Class<OpenApiQueryBizContentVo> getBizContentClass(a) {
returnOpenApiQueryBizContentVo.class; }}Copy the code
Command processor and entity class are type strongly associated, eliminating the strong transfer operation, very convenient and elegant.
With the Spring
To put it simply, you map the bizType value to the AbsCommand instance hosted in the SpringIOC container. Spring provides a way to get all examples of a type.
It is easy to add a method to the virtual base class that returns all bizTypes supported by the current command processor, which the subclass implements. Listen to the initialization event of the Spring container, get all the processor instances, and also get the list of commands supported by that instance, and finally do a mapping.
The conversion code is as follows:
The AbsCommand class adds methods to get the list of supported command codes
Public abstract class AbstractCommand<T extends OpenApiBizContentVo> public abstract class AbstractCommand<T extends OpenApiBizContentVo> public abstract Set<OpenApiCommandCodeEnum> supports(); }Copy the code
Create a new CommandFactory class that maps command codes to instances of the AbsCommand type hosted in Spring
@Slf4j
@Component
public class CommandFactory implements ApplicationListener<ContextRefreshedEvent> {
private staticMap<String, AbstractCommand<? >> commandInstanceMapping =new HashMap<>();
public void register(String command, AbstractCommand
instance) {
commandInstanceMapping.put(command, instance);
}
publicAbstractCommand<? > getCommandInstance(String command) {return commandInstanceMapping.get(command);
}
// Remove native type warnings
@SuppressWarnings("rawtypes")
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
Map<String, AbstractCommand> payerBeanMap = applicationContext.getBeansOfType(AbstractCommand.class);
for(Map.Entry<String, AbstractCommand> entry : payerBeanMap.entrySet()) { AbstractCommand<? > command = entry.getValue(); Set<OpenApiCommandCodeEnum> supports = command.supports();if (CollectionUtils.isEmpty(supports)) {
log.error("Type: {} Support empty command code, no registration", command.getClass().getSimpleName());
}
for (OpenApiCommandCodeEnum commandCodeEnum : supports) {
log.info("Command code: {} Register instance: {}", commandCodeEnum.getValue(), command.getClass().getSimpleName()); register(commandCodeEnum.getValue(), command); }}}}Copy the code
OpenApiCommandCodeEnum is the set of all bizType values. Listen to the ContextRefreshedEvent event of the Spring container, and then customize the mapping for each AbsCommand instance after the container is initialized. When you need to get an instance, call the getCommandInstance method.
At this point, the Controller layer (caller) can write:
@Slf4j
@Controller
@RequestMapping("/")
public class OpenApiController {
@Autowired
private CommandFactory commandFactory;
@PostMapping(value = {"gateway"}, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public OpenApiBaseResponse open(HttpServletRequest request) throws BindException {
String ori = super.readAsString(request);
// ...
String commandCode = JSONObject.parseObject(ori).getString("bizType");
if (StringUtils.isEmpty(commandCode)) {
return OpenApiBaseResponse.gatewayFail(OpenApiResultCodeEnum.PARAM_ERROR, "Command code is empty"."");
}
//
AbstractCommand<? extends OpenApiBizContent> abstractCommand = commandFactory.getCommandInstance(commandCode);
if (abstractCommand == null) {
return OpenApiBaseResponse.gatewayFail(OpenApiResultCodeEnum.PARAM_ERROR, "Command code illegal"."");
}
returnabstractCommand.progress(ori); }}Copy the code
Optimization 1: Template approach
Since the AbsCommand class already does a code-level gateway processing, template methods are easy to do, for example if a business wants to do some initialization before or after the actual method is executed:
@Slf4j
public abstract class AbstractCommand<T extends OpenApiBizContent> {
public OpenApiBaseResponse progress(String ori) throws BindException {
/ /... omit
if (!this.before(reqInstance)) {
return OpenApiBaseResponse.gatewayFail(OpenApiResultCodeEnum.OTHER, "Interception failed", mchId);
}
OpenApiBaseResponse response = this.progress(reqInstance);
return this.after(reqInstance, response);
}
/** * do some specific operations after the subclass can choose to copy */
private OpenApiBaseResponse after(OpenApiBaseRequestVo<T> req, OpenApiBaseResponse response) {
return response;
}
/** * Do some operations before specific services, subclasses can choose to override * return false do not go down, return gateway failure; Returns true to go down */
protected boolean before(OpenApiBaseRequestVo<T> req) {
return true;
}
public abstract OpenApiBaseResponse progress(OpenApiBaseRequestVo<T> req);
/ /... omit
}
Copy the code
Create before and after methods so that each command code processor has the right to customize the operation before and after the business.
Optimization 2: Parametric packaging
Sometimes a handler needs more than just the requested OpenApiBaseRequestVo class. It may also need some common local configurations to get. Under the current framework, both need to be implemented in their own command handlers.
For convenience, we can wrap the OpenApiBaseRequestVo class with another layer, like this:
@Data
public class AppContext<T extends OpenApiBizContentVo> {
private OpenApiBaseRequestVo<T> request;
private MchConfig mchConfig;
private SysConfig sysConfig;
public AppContext(OpenApiBaseRequestVo<T> request, MchConfig mchConfig, SysConfig sysConfig) {
this.request = request;
this.mchConfig = mchConfig;
this.sysConfig = sysConfig; }}Copy the code
Then obtain the configuration uniformly in the AbsCommand as follows:
@Slf4j
public abstract class AbstractCommand<T extends OpenApiBizContentVo> {
public OpenApiBaseResponse progress(String ori) throws BindException {
/ /... omit
// Get operation
MchConfig mchConfig = null;
SysConfig sysConfig = null;
AppContext<T> context = new AppContext<>(reqInstance, mchConfig, sysConfig);
if (!this.before(context)) {
return OpenApiBaseResponse.gatewayFail(OpenApiResultCodeEnum.OTHER, "Interception failed", mchId);
}
OpenApiBaseResponse response = this.progress(context);
return this.after(context, response);
}
private OpenApiBaseResponse after(AppContext<T> context, OpenApiBaseResponse response) {
return response;
}
protected boolean before(AppContext<T> context) {
return true;
}
public abstract OpenApiBaseResponse progress(AppContext<T> context);
/ /... omit
}
Copy the code
Optimization 3: Parameter verification
This doesn’t fit the title, but I feel the need to mention it. At this time, we do not have a better method for field joint verification. But now that we’ve agreed on bizContent as OpenApiBizContentVo, we can add the check virtual method here and let subclasses implement it as follows:
public interface OpenApiBizContentVo {
void check(a) throws BindException;
}
Copy the code
Check is also added to the wrapper class OpenApiBaseRequestVo
@Data
public class OpenApiBaseRequestVo<T extends OpenApiBizContentVo> {
/ /... omit
@Length(max = 32)
@NotBlank
@SpecialCharacter
private String timeStamp;
@Valid
private T bizContent;
@JSONField(serialize = false)
public void check(a) throws BindException {
ValidationUtil.validate(this); bizContent.check(); }}Copy the code
This can then be called uniformly in the AbsCommand.
public abstract class AbsCommand<T extends OpenApiBizContentVo> {
public OpenApiBaseResponse progress(String ori) {
OpenApiBaseRequestVo<T> reqInstance = null;
try {
reqInstance = JSON.parseObject(ori, new TypeReference<OpenApiBaseRequestVo<T>>(this.getBizContentClass()) {});
}
catch (Exception e) {
//
}
reqInstance.check();
/ /... omit}}Copy the code
If you’re not familiar with SpringValidation, take a look at my other article: Better practices for Parameter validation based on SpringValidation
summary
If you have three team members at this time, if you follow this framework development, you can achieve mutual influence. Because the common operations are already done by the framework, you just need to fill in some business code. (If return requires a signature: Use Spring’s ControllerAdvice technology)