Please, stop using enumerated types in external interfaces!

Recently, there was a problem in our online environment. The online code threw an IllegalArgumentException during its execution. After analyzing the stack, it was found that the most fundamental exception was the following:

java.lang.IllegalArgumentException: 

No enum constant com.a.b.f.m.a.c.AType.P_M

Probably the above content, it seems very simple, the error message prompted is that the P_M enumeration item is not found in the AType enumeration class.

So after investigation, we found that before the exception occurred online, a downstream system that the application relied on was released. During the release process, an API package changed. The main content of the change is the Response return value class of an RPC interface. The enumeration item P_M is added to an enumeration parameter AType in.

However, when the downstream system was released, we were not notified of the upgrade of the system we were responsible for, so an error was reported.

Let's analyze why this happens.

Reproduce the problem

First, the downstream system A provides a second-party library with a parameter type in the return value of a certain interface is an enumeration type.

First, the downstream system A provides a second-party library with a parameter type in the return value of a certain interface is an enumeration type.

One-party library refers to the dependencies in this project.
Two-party library refers to the dependencies provided by other projects within the
company. The third party library refers to the dependencies from third parties such as other organizations and companies.

public interface AFacadeService {

    public AResponse doSth(ARequest aRequest);

}

public Class AResponse{

    private Boolean success;

    private AType aType;

}

public enum AType{

    P_T,

    A_B

}

Then the B system relies on this second-party library, and will call the doSth method of AfacadeService through RPC remote calls.

public class BService {

    @Autowired

    AFacadeService aFacadeService;

    public void doSth(){

        ARequest aRequest = new ARequest();

        AResponse aResponse = aFacadeService.doSth(aRequest);

        AType aType = aResponse.getAType();

    }

}

At this time, if the A and B systems rely on the same two-party library, the enumeration AType used by the two will be the same class, and the enumeration items inside will be the same. This situation will not happen what is the problem.

However, if one day, the second party library is upgraded and a new enumeration item P_M is added to the AType enumeration class, only system A has been upgraded at this time, but system B has not been upgraded.

Then the AType that the A system depends on is like this:

public enum AType{

    P_T,

    A_B,

    P_M

}

The AType that system B depends on is like this:

public enum AType{

    P_T,

    A_B

}

In this case, when the B system calls the A system through RPC, if the aType in the AResponse returned by the A system is the newly added P_M, the B system will not be able to parse it. Generally at this time, the RPC framework will have a deserialization exception. Cause the program to be interrupted.

Principle analysis

We have analyzed the phenomenon of this problem clearly, so let's look at the principle and why there is such an abnormality.

In fact, this principle is not difficult. Most of these RPC frameworks will use the JSON format for data transmission, that is, the client will serialize the return value into a JSON string, and the server will deserialize the JSON string into a Java Object.

In the process of JSON deserialization, for an enumeration type, it will try to call the valueOf method of the corresponding enumeration class to obtain the corresponding enumeration.

When we look at the implementation of the valueOf method of the enumeration class, we can find that if the corresponding enumeration item is not found in the enumeration class, an IllegalArgumentException will be thrown:

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {

    T result = enumType.enumConstantDirectory().get(name);

    if (result != null)

        return result;

    if (name == null)

        throw new NullPointerException("Name is null");

    throw new IllegalArgumentException(

        "No enum constant " + enumType.getCanonicalName() + "." + name);

}

Regarding this issue, in fact, there is a similar agreement in the "Alibaba Java Development Manual":
Insert picture description here
it stipulates that "enumeration can be used for the parameters of the second party library, but the return value is not allowed to use enumeration". The thinking behind this is the content mentioned above in this article.

Extended thinking

Why can there be enumeration in parameters?

I don’t know if you have ever thought about this issue. In fact, it has something to do with Erfangku’s responsibilities.

In general, when system A wants to provide a remote interface for others to call, it will define a second-party library to tell its caller how to construct parameters and which interface to call.

The caller of this second-party library will call according to the content defined in it. The parameter construction process is completed by the B system. If the B system uses an old two-party library, the enumerations used will naturally be some of the existing ones, and the new ones will not be used, so this There will be no problems.

For example, in the previous example, when system B calls system A, when it uses AType when constructing parameters, there are only two options: P_T and A_B. Although system A already supports P_M, system B does not use it.

If system B wants to use P_M, then the second party library needs to be upgraded.

However, the return value is different. The return value is not controlled by the client. What the server returns is determined by the second-party library it relies on.

However, in fact, compared to the regulations in the manual, I prefer not to use enumerations for both input and output parameters in the RPC interface.

Generally, there are several considerations when we use enumeration:
1. Enumeration strictly controls the incoming content of the downstream system to avoid illegal characters.
2. It is convenient for the downstream system to know which values ​​can be transmitted, and it is not easy to make mistakes.

It is undeniable that using enumeration does have some advantages, but I do not recommend using it for the following reasons:
1. If the second party library is upgraded and some enumeration items in an enumeration are deleted, then enumeration is also used in the parameters. There will be a problem, and the caller will not be able to recognize the enumeration.
2. Sometimes there are multiple upstream and downstream systems. For example, the C system indirectly calls the A system through the B system, the parameters of the A system are passed from the C system, and the B system just does a parameter conversion and assembly. In this case, once the second-party library of system A is upgraded, then B and C must be upgraded at the same time, and neither of them will be compatible if they are not upgraded.

I actually recommend that you use strings instead of enumerations in the interface. Compared with the strong type of enumeration, string is a weak type.

If you use a string instead of the enumeration in the RPC interface, you can avoid the two problems we mentioned above. The upstream system only needs to pass the string, and the legality of the specific value only needs to be in the A system. Check it out.

For the convenience of the caller, you can use javadoc's @see annotation to indicate that the value of this string field is obtained from that enumeration.

public Class AResponse{

    private Boolean success;

    /**

    *  @see AType 

    */

    private String aType;

}

For a relatively large Internet company like Ali, an interface provided at random may have hundreds of callers, and interface upgrades are also normal. We simply cannot ask all callers to follow each other after the second party library is upgraded. Upgrade, this is completely unrealistic, and for some callers, he doesn't need new features, and there is no need to upgrade.

There is another situation that looks special, but in fact it is relatively common, that is, sometimes an interface declaration is in package A, and some enumeration constants are defined in package B. The more common is Ali’s transaction-related information , Orders are divided into many levels, each time a package is introduced, dozens of packages need to be introduced.

For the caller, I definitely don't want my system to introduce too many dependencies. On the one hand, too many dependencies will cause the application's compilation process to be slow, and dependency conflicts are prone to occur.

Therefore, when calling the downstream interface, if the type of the field in the parameter is an enumeration, then I have no choice but to rely on his second party library. But if it is not an enumeration, but a string, then I can choose not to rely on it.

Therefore, when we define the interface, we will try to avoid using the strong type of enumeration. The specification stipulates that it is not allowed to be used in the return value, but I am more demanding, that is, I rarely use it even in the input parameters of the interface.

Finally, I just don’t recommend using enumerations in the input and output parameters of the externally provided interfaces. It’s not that you shouldn’t use enumerations at all. As I have mentioned in many previous articles, enumerations have many benefits, and I often use them in my code. . Therefore, you must not give up eating because of choking.

Of course, the opinions in the article represent only me personally. Whether it is applicable to other people, other scenarios, or the practices of other companies, readers need to distinguish by themselves. I suggest you think more about it when you use it.

Reader benefits

Thank you for seeing here!
I have compiled a lot of 2020 latest Java interview questions (including answers) and Java study notes here, as shown below
Insert picture description here

The answers to the above interview questions are organized into document notes. As well as interviews also compiled some information on some of the manufacturers & interview Zhenti latest 2020 collection (both documenting a small portion of the screenshot) free for everyone to share, in need can click to enter signal: CSDN! Free to share~

If you like this article, please forward it and like it.

Remember to follow me!
Insert picture description here

Guess you like

Origin blog.csdn.net/weixin_49527334/article/details/112173820