An application | system can not exist in isolation, even if is the enterprise internal system in the development process also proves the tripartite system (capital, SMS, data aggregation, etc.) for docking. Our company is an N-party payment system, and we often need to connect with banks and enterprises (Alipay, wechat, ZHONGnongjian, etc.). After going through countless pits, we have the following points of practical coding experience, which we think is more practical to share and exchange with you.

1. Use generics + design patterns for class organization

A common system cannot have only one service, but provides only a few service routes, such as THE URL route or commandCode route. URL routing is typically represented by wechat Pay, while the rival Alipay uses commandCode routing. They’re both giants, so there’s no one that’s better than the other, they’re just different practices, so if you’re designing a system, you don’t have to worry too much about it, and both can be used for very large applications. Nevertheless the individual is more inclined to pay the practice of treasure.

All right, back to this article. Now suppose there are use external application of commandCode routing (behind the URL routing mode), for example the following discussions: address: https://127.0.0.1:1234/openapi/json, interface list as follows:

- ** query **Request: {"Type" : "query", "McHId" : ""," outTradeNo ":" "} response: a little
- ** Pay **Request: {"Type" : "pay", "McHId" : ""," outTradeNo ":" ", "amount" : ""," userId ":" "} response: a little
- ** Refund **Request: {"Type" : "refund", "McHId" : ""," oriOutTradeNo ":" ", "amount" : ""} response: a littleCopy the code

Abstraction + encapsulation + inheritance

By analyzing the interfaces, it can be seen that the request packets of each interface have field Type and McHId, so it can be naturally associated with inheritance. Create a request base class, BaseRequest, and move the common fields under it. However, different business types have different types and how to return different values. Here we can take the abstract base class, define the getType method as abstract, hand over the logic to the inherited subclasses, and use the overwrite method when the call actually occurs. Here is an example:

public abstract class BaseRequest {
    private String type;
    private String mchId;
    public abstract String getType(a);
    // omit the rest of get and set
}

public class QueryRequest extends BaseRequest {
    private String outTradeNo;
    @Override
    public String getType(a) {
        return "query"; }}// Omit PayRequest, RefundRequest class definitions
Copy the code

For the response, we will do much the same for the Request class without expanding it because of space.

good Now that we have created the BaseRequest, BaseResponse classes, we have the data carrier, and we are ready to write the main logic. My custom is to call the class xxxClient (why not xxxManager? More on that later. In keeping with abstract-oriented programming principles, parameters and return values naturally need to use base classes for unified calls:

public class xxxClient {
    public BaseResponse execute(BaseRequest request) {
        // 1. Signature (if required)
        // 2, RequestBean to JSON
        // 3
        // 4. Verification (if necessary)
        // go to ResponBean}}Copy the code

The comments outline what this method needs to do, omits the logic of signature verification, and each system has different requirements for signatures. The important thing is to convert these two steps. When requesting, you need to convert the RequestBean to the format required by the interconnecting application. When responding, you need to convert the returned content into an internal ResponBean sample.

The format is JSON (XML), and there is no nesting. It is easy to use existing JSON libraries (FastJSON, Jackson, Gson, etc.). In the fastjson example, when you need to convert a RequestBean to a JSON string, you can call json.tojsonString (). If you need to ignore a field or rename a field, you can also use the annotations provided by it.

When you need to convert a returned message to a ResponBean, there is a question: how do you know which ResponBean to convert? The ResponBean Class type is the ResponBean Class type, and the ResponBean Class type is the ResponBean Class type. The pseudocode is as follows:

private Class<? extends BaseResponse> getTypeByCommandCode(String code) {
    if ("query".equals(code)) {
        return QueryResponse.class;
    }
    else if ("pay".equals(code)) {
        return PayResponse.class;
    }
    / /...
}
Copy the code

You can do that, but this whole if else thing is a little bit inelegant isn’t it? Good code should be open-close, open for extension and closed for modification.

As you can see, the ResponseBean is based on Type, which is unique to each RequestBean. In this case, we can treat the ResponseBean class as if it were the type value. Also define an abstract method in BaseRequest that returns the ResponseBean class, eliminating the need for if else judgment. The code is as follows:

public abstract class BaseRequest {
    private String type;
    private String mchId;
    public abstract String getType(a);
    // Get the ResponseBean Class
    public abstract Class<? extends BaseResponse> getResponseType();
    // omit the rest of get and set
}

public class QueryRequest extends BaseRequest {
    private String outTradeNo;

    // Return the class of QueryResponse
    @Override
    public Class<? extends BaseResponse> getResponseType() {
        return QueryResponse.class;
    }

    @Override
    public String getType(a) {
        return "query"; }}Copy the code

The code is also made more robust by fixing the return value range of the getResponseType method to the Class of the BaseResponse subclass with generic qualification. When called from xxxClient, it looks like this:

public class xxxClient {
    public BaseResponse execute(BaseRequest request) {
        / /... Omit the previous steps
        String response = "Suppose this is a tripartite system returning content.";
        BaseResponse responseBean = JSON.parseObject(response, request.getResponseType());
        returnresponseBean; }}Copy the code

Join the generic

Code written here, has done unified call, and follow the open and close principle. The client creates a different RequestBean, calls the execute method, and gets an instance of the BaseResponse base class that returns the value, and then strong-rolls the concrete subclass response sample for the corresponding business logic. For example, the caller code of the query is roughly as follows:

public static void main(String[] args) {
    QueryRequest request = new QueryRequest();
    QueryResponse response = (QueryResponse)new xxxClient().execute(request);
    // Do other business logic
}
Copy the code

You may ask, forcing this is a bit of a stretch, unfriendly to the caller, but could there be a more elegant way? Of course there is, and that’s where generics come in.

On closer inspection, one RequestBean corresponds to one ResponseBean, so can we introduce generics and use T instead of the generic qualifier? Define it directly at the class level, not the method level, and determine the type of BaseResponse while defining the RequestBean. The following code looks like this:

public abstract class BaseRequest<T extends BaseResponse> {
    private String type;
    private String mchId;
    public abstract String getType(a);
    // Get the ResponseBean Class
    public abstract Class<T> getResponseType(a);
    // omit the rest of get and set
}
Copy the code

The QueryRequest class definition needs to look like this:

public class QueryRequest extends BaseRequest<QueryResponse> {
    private String outTradeNo;

    // Return the class of QueryResponse
    @Override
    public Class<QueryResponse> getResponseType(a) {
        return QueryResponse.class;
    }

    @Override
    public String getType(a) {
        return "query"; }}Copy the code

After the change, you’ll find that the RequestBean and ResponseBean are more closely related, and subsequent extensions are more friendly. Of course, this foreshadows is not for close connection and set up, the important thing is in the xxxClient class, see how to change:

public class xxxClient {
    public <T extends BaseResponse> T execute(BaseRequest<T> request) {
        / /... Omit the previous steps
        String response = "Suppose this is a tripartite system returning content.";
        T responseBean = JSON.parseObject(response, request.getResponseType());
        returnresponseBean; }}Copy the code

Because T is used instead of the specific ResponseBean subclass type, you can simply change the execute method return value to T, using the generic qualifier, whereas the corresponding BaseRequest class requires a T type. At this point, the caller code does not need to perform the strong action, which is very reasonable and elegant:

public static void main(String[] args) {
    QueryRequest request = new QueryRequest();
    QueryResponse response = new xxxClient().execute(request);
    // Do other business logic
}
Copy the code

By now, the module’s class organization has been basically finalized, and it is also very simple to transform or improve based on this shelf, such as the following two cases:

If the XML

JSON is just another form of presentation. It’s just a different library, usually Xstream, dom4j, etc. That’s not what I’m talking about here. If the docking application supports multiple formats, how should the code be organized?

The class organization remains largely the same, becoming just a piece of Bean transformation. So why don’t you take the transformation out and let the client specify it. Define the conversion interface as follows:

public interface Converter { <A extends BaseRequest<? >>String bean2String(A request);
    <B extends BaseResponse> B string2Bean(String responStr, Class<B> clazz);
}
Copy the code

Pass the Converter as an argument to the xxxClient#execute method

public <T extends BaseResponse> T execute(BaseRequest<T> request, Converter converter) {
    String requestStr = converter.bean2String(request);
    String response = "Suppose this is a tripartite system returning content.";
    T responseBean = converter.string2Bean(response, request.getResponseType());
    return responseBean;
}
Copy the code

The conversion action group has been abstracted. When the format extension is needed, the xxxClient#execute business logic code does not need to be changed. Just a new Converter is needed.

public class XmlConverter implements Converter {
    @Override
    public<A extends BaseRequest<? >>String bean2String(A request) {
        // omit the XmlUtils implementation here...
        return XmlUtils.toString(request);
    }
    @Override
    public <B extends BaseResponse> B string2Bean(String responStr, Class<B> clazz) {
        // omit the XmlUtils implementation here...
        returnXmlUtils.toBean(responStr, clazz); }}Copy the code

If it is a URL route

CommandCode is used for routing. CommandCode is used for routing.

With this shelf, it’s a no-brainer. A brief interpretation is that the URL of each business is different, so we can handle it in the way of type and BeanResponse class.

public abstract class BaseRequest<T extends BaseResponse> {
    private String type;
    private String mchId;
    private String url;
    // Different services have different request addresses
    public abstract String getUrl(a);
    public abstract String getType(a);
    // Get the ResponseBean Class
    public abstract Class<T> getResponseType(a);
    // omit the rest of get and set
}

public class QueryRequest extends BaseRequest<QueryResponse> {
    private String outTradeNo;
    @Override
    public String getUrl(a) {
        return "https://127.0.0.1:1234/openapi/query";
    }
    @Override
    public Class<QueryResponse> getResponseType(a) {
        return QueryResponse.class;
    }
    @Override
    public String getType(a) {
        return "query"; }}Copy the code

XxxClient to obtain the url logic, roughly as follows:

public <T extends BaseResponse> T execute(BaseRequest<T> request, Converter converter) {
    String requestStr = converter.bean2String(request);
    
    HttpUtils.doPost(request.getUrl(), requestStr, 5.15);

    String response = "Suppose this is a tripartite system returning content.";
    return converter.string2Bean(response, request.getResponseType());
}
Copy the code

Of course, these are just two examples. Real businesses are more complex than this, such as multiple signatures, multiple responses to one request, and so on, which can be extended based on this shelf.

2. Minimize external dependence

In my opinion, the boundaries between modules are clear. When you write modules that interconnect with third-party systems, you should not rely on persistence or web classes, which is consistent with the single responsibility principle.

Spring’s dependency handling

The first is that you can’t rely on Spring-related classes. Once you rely on them, they will be contaminated. What if you need to port your module to a project that doesn’t use Spring? It is up to the caller to decide whether to host Spring or not. As FAR as I am aware, there will also be an xxxManager class to handle the interface with Spring, which can also handle flow limiting, fusing, etc.

Provides SDK dependency handling

Other peer applications will “take the liberty” of suggesting the provided SDK (in jar form)! Be careful! Use it if you can. If you choose to believe them, the late jar management (upload private server) and feature expansion will drive you crazy. Take a simple example. Assume that all configuration files in your project are stored in the database, which can achieve the function of dynamic refresh. However, most SDKS provided by docking applications store configuration in the form of files.

If you really want to change the SDK and can only compromise, please use the source code dependency approach rather than maven-style dependency. Is to decompile other people’s SDK to get.java files, and then copy to their application source directory.

Of course, you will face the headache of providing a.jar file containing hundreds of classes, one by one decompiling. Fortunately, there is a tool like JD-GUI, which can open.jar and save all sources.

3. Parameterization of configuration variables

Configuration variables, such as request URL, network request timeout, character encoding, and so on, are exposed parameters. Assuming that the other application has multiple environments, once written in code, the flexibility of the application is greatly reduced. There is also timeout time, which should be determined by the business, such as query timeout time and transaction timeout time should be different. Therefore, it is recommended to make arguments and let the caller decide.

4. Wrap the return value

Suppose there is a case where the signature fails before sending the three-party system. How do you return the error to the caller? There is no guarantee that if you enter this method, you will get a response from the three-party system. Even if you send it, it is possible that the response will be empty. For these cases we can define a wrapper class as follows:

public class ManagerResultWrapper<T> {
    /** Prompt message */
    private String info;
    /** Success message */
    private boolean success;
    /** Return error code */
    private String code;
    /** Data to return */
    private T data;

    private ManagerResultWrapper(String info, boolean success, String code, T data) {
        this.info = info;
        this.success = success;
        this.code = code;
        this.data = data;
    }

    public static <T> ManagerResultWrapper<T> success(T data) {
        return new ManagerResultWrapper<>("".true."", data);
    }

    public static <T> ManagerResultWrapper<T> fail(String code, String info) {
        return new ManagerResultWrapper<>(info, false, code, null);
    }
    // omit get and set
}
Copy the code

You can specify that the SUCCESS field is used to indicate whether the message was successfully sent to the bank, that the code and INFO fields hold information on failure, and that the generic T holds the entity (BaseResponse as defined above) to which the bank responds. The specific return rule can be customized according to the actual situation.

5. Exception handling

I don’t like to use exceptions to control logic very much, but tripartite system interaction is bound to involve network IO, which cannot escape and must be dealt with. My practice is not to catch IO exceptions, so that the caller knows that THERE is IO interaction, and it is not recommended to wrap and return IO exceptions at this time. If external sensitivity to IO exceptions is high (ConnectTimeoutException, ReadTimeoutException may lead to different business results), then it is not appropriate to wrap an exception here.

It’s a programming habit. Wrapping works, but you need to identify the different exceptions with a field (for example, adding a code field to a custom exception class) so that the caller’s need for exception refinement can be satisfied.

6, input and output need to have complete log records

This should not be said more, but it is mentioned. Log is a powerful weapon when it comes to serious business issues and confrontations. Use your log. Do not worry about the log volume is too large, what time, morning ELK is not it?

7. Relatively complete use examples and unit tests

Do unit testing, both to improve your work order and for your teammates. Your team may not know about your design, but it’s much more productive when you have examples or unit tests. (Documents also work)

Again, it’s not hard to write code that machines can understand, but it’s really good to write code that people can understand.