Talk about 36 tips for interface design

foreword

Hello everyone, as a back-end development, no matter what language it is, Java, Go or C++, the back-end ideas behind it are similar. Later, I plan to publish a technical column on back-end thinking, mainly including some back-end designs, or related to back-end specifications. I hope it will be helpful to everyone’s daily work.

We are back-end development engineers, and our main job is: how to design an interface well. So, today I will introduce to you 36 tips for designing a good interface. This article is the first of the back-end thinking column.


Friends who are interested in knowing the content and more related learning materials, please like and collect + comment and forward + follow me, there will be a lot of dry goods later. I have some interview questions, architecture, and design materials that can be said to be necessary for programmer interviews!
All the materials are organized into the network disk, welcome to download if necessary! Private message me to reply [111] to get it for free

1. Interface parameter verification

Input and output parameter verification is a basic quality that every programmer must have. The interface you design must first verify the parameters. For example, whether the input parameter is allowed to be empty, and whether the input parameter length meets your expected length. This needs to be a habit. In daily development, many low-level bugs are caused by not checking parameters.

For example, if your database table field is set to varchar(16), and the other party passes a 32-bit string, if you do not verify the parameters, the insertion into the database will be abnormal.

The same is true for the output parameters. For example, the interface message you defined, the parameter is not empty, but your interface returns the parameter without verification. For some reason in the program, it returns a null value for others. . .

Add picture annotations, no more than 140 words (optional)

2. When modifying the old interface, pay attention to the compatibility of the interface

Many bugs are caused by modifying the old external interface, but not making it compatible. Most of the key problems are serious, which may directly lead to the failure of the system release. Novice programmers can easily make this mistake~

edit

Add picture annotations, no more than 140 words (optional)

Therefore, if your requirement is to modify the original interface, especially if this interface provides external services, you must consider interface compatibility. For example, the dubbo interface originally only received parameters A and B, but now you add a parameter C, you can consider handling it like this:


//老接口
void oldService(A,B){
  //兼容新接口,传个null代替C
  newService(A,B,null);
}

//新接口,暂时不能删掉老接口,需要做兼容。
void newService(A,B,C){
  ...
}
 
 

//Old interface void oldService(A,B){ //Compatible with the new interface, pass null instead of C newService(A,B,null); } //New interface, the old interface cannot be deleted temporarily, it needs to be compatible. void newService(A,B,C){ ... }

3. When designing the interface, fully consider the scalability of the interface

The interface should be designed according to the actual business scenario, and the scalability of the interface should be fully considered.

For example, you receive a request: when users add or modify employees, they need to scan their faces. Then you backhandedly provide an interface for employee management to submit facial recognition information? Or think about it first: Is submitting facial recognition a general process? For example, if transfer or one-click discount needs to be connected to facial recognition, do you need to re-implement an interface? It is still currently divided into modules according to business types, and it is good to reuse this interface to preserve the scalability of the interface.

If it is divided by module, in the future, if other scenarios such as one-click discounting are connected to facial recognition, there is no need to create a new set of interfaces, just add an enumeration, and then reuse facial recognition through the process interface to realize one-click discounting The differentiation of the face is enough.

Edit toggle to center

Add picture annotations, no more than 140 words (optional)

4. The interface considers whether anti-heavy processing is required

If the front end repeats the request, how does your logic handle it? Whether to consider interface deduplication processing.

Of course, if it is a query request, there is no need to prevent it. If it is an update and modification class, especially a financial transfer class, it is necessary to filter duplicate requests. To put it simply, you can use Redis to prevent repeated requests. The same requester, the same request within a certain time interval, consider whether to filter. Of course, if the concurrency of the transfer interface is not high, it is recommended to use the database anti-duplication table, with the unique serial number as the primary key or unique index.

Edit toggle to center

Add picture annotations, no more than 140 words (optional)

5. For key interfaces, consider thread pool isolation.

For some important interfaces such as login, transfer transaction, and order placement, consider thread pool isolation. If all your businesses share a thread pool, and some businesses have bugs that cause the thread pool to be blocked and full, it will be a disaster, and all businesses will be affected. Therefore, if the thread pool is isolated and more core threads are allocated to important businesses, the important businesses will be better protected.

Edit toggle to center

Add picture annotations, no more than 140 words (optional)

6. Calling third-party interfaces should consider exception and timeout handling

If you call third-party interfaces or distributed remote services, you need to consider:

  • exception handling

For example, if you call someone else's interface, if there is an exception, how to deal with it, whether to retry or treat it as a failure or alarm.

  • interface timeout

It is impossible to predict how long it will take for the other party's interface to return. Generally, a timeout disconnection time is set to protect your interface. I have seen a production problem before, that is, the http call does not set a timeout period, and finally the responder process freezes, and the request keeps occupying the thread and does not release it, dragging down the thread pool.

  • number of retries

Your interface call failed, do you need to try again? How many times to retry? Need to think about it from a business perspective

edit

Add picture annotations, no more than 140 words (optional)

7. Interface implementation considers fusing and degradation

Current Internet systems are generally deployed in a distributed manner. In a distributed system, a certain basic service is often unavailable, which eventually leads to the unavailability of the entire system. This phenomenon is called the service avalanche effect.

For example, the distributed call link A->B->C...., as shown in the figure below:

Edit toggle to center

Add picture annotations, no more than 140 words (optional)

If there is a problem with service C, for example, the call is slow due to slow SQL, it will cause B to be delayed, and A will also be delayed. The blocked A request will consume resources such as threads and IO of the system. When more and more services are requested from A, more and more computer resources are occupied, which will eventually lead to system bottlenecks, causing other requests to be unavailable as well, and finally causing the business system to crash.

To deal with the service avalanche, the common practice is circuit breaker and downgrade. The simplest is to add switch control. When the downstream system has problems, the switch will be degraded and the downstream system will no longer be called. You can also choose the open source component Hystrix.

8. After the log is printed, the key code of the interface must be escorted by the log.

No matter where the business-critical code is, there should be enough logs to protect it. For example: you realize the transfer business, transfer a few million, and then the transfer fails, and then the customer complains, and then you have not printed the log. Think about the dire situation, but you have nothing to do. . .

So, what log information do you need for your transfer business? At least, before the method is called, the input parameters need to be printed. After the interface is called, the exception needs to be caught, and the exception-related logs should be printed at the same time, as follows:

public void transfer(TransferDTO transferDTO){
    log.info("invoke tranfer begin");
    //打印入参
    log.info("invoke tranfer,paramters:{}",transferDTO);
    try {
      res=  transferService.transfer(transferDTO);
    }catch(Exception e){
     log.error("transfer fail,account:{}",
     transferDTO.getAccount())
     log.error("transfer fail,exception:{}",e);
    }
    log.info("invoke tranfer end");
    }

9. The function definition of the interface should have a single

Singleness means that what the interface does is relatively single and specific. For example, a login interface, what it does is to verify the account name and password, and then return the login success and userId. But if you put some registration, some configuration queries, etc. in the login interface in order to reduce interface interaction, it is not appropriate.

In fact, this is also some ideas of microservices, the function of the interface is single and clear. For example, interfaces related to order service, points, and product information are all divided. Will it be easier to split microservices in the future?

10. In some scenarios of the interface, it is more reasonable to use asynchrony

To give a simple example, say you implement an interface for user registration. When the user registers successfully, send an email or text message to notify the user. This email or text message is more suitable for asynchronous processing. Because there can never be a failure of a notification class, it will cause the registration to fail.

As for the asynchronous way, simply use the thread pool. You can also use the message queue, that is, after the user registers successfully, the producer generates a message of successful registration, and the consumer sends a notification after receiving the message of successful registration.

Edit toggle to center

Add picture annotations, no more than 140 words (optional)

Not all interfaces are suitable for designing as synchronous interfaces. For example, if you want to do a transfer function, if you are a single transfer, you can synchronize the interface design. When the user initiates the transfer, the client just waits quietly for the transfer result. If you are transferring money in batches, one batch of one thousand, or even ten thousand, you can design the interface to be asynchronous. That is, when the user initiates a batch transfer, if the persistence is successful, the acceptance success will be returned first. Then the user waits every ten or fifteen minutes to check the transfer result. Or, after the batch transfer is successful, call back to the upstream system.

Edit toggle to center

Add picture annotations, no more than 140 words (optional)

11. Optimize the interface time-consuming, consider changing the remote serial call to parallel call

Suppose we design an interface for the home page of an APP, which needs to check user information, banner information, pop-up window information, and so on. So do you call the interface serially or in parallel?

Edit toggle to center

Add picture annotations, no more than 140 words (optional)

If it is checked one by one in series, for example, it takes 200ms to check user information, 100ms to check banner information, and 50ms to check pop-up window information, then it will take a total of 350ms. If you also check other information, it will take even more time. This scenario can be changed to parallel calls. That is to say, checking user information, checking banner information, and checking pop-up window information can be initiated at the same time.

Edit toggle to center

Add picture annotations, no more than 140 words (optional)

There is an asynchronous programming tool in Java: CompletableFuture, which can realize this function very well.

12. Interface merging or consider the idea of ​​batch processing

For database operations or remote calls, do not call for loops if batch operations are possible.

edit

Add picture annotations, no more than 140 words (optional)

A simple example, when we usually insert a list of detailed data into the database, do not insert one by one in the for loop, it is recommended to perform batch insertion of hundreds of items in a batch. In the same way, remote calls are similar. For example, if you check whether a marketing tag is hit, you can check it tag by tag, or you can check tags in batches. If you do it in batches, the efficiency will be higher.

//反例
for(int i=0;i<n;i++){
  remoteSingleQuery(param)
}

//正例
remoteBatchQuery(param);

Have you guys understood why kafka is so fast? In fact, one of the reasons is that Kafka uses batch messages to improve the processing capacity of the server.

13. In the process of implementing the interface, use the cache properly

What scenarios are suitable for caching? Scenarios with more reads and fewer writes and lower data timeliness requirements.

If the cache is used well, it can carry more requests, improve query efficiency, and reduce the pressure on the database.

For example, some product information that usually changes little or hardly changes can be placed in the cache. When a request comes in, first query the cache, if not, then check the database, and update the data in the database to the cache. However, the use of cache increases the need to consider these points: how to ensure cache and database consistency, clustering, cache breakdown, cache avalanche, cache penetration and other issues.

  • Guarantee database and cache consistency: cache delay double delete, delete cache retry mechanism, read biglog asynchronously delete cache

  • Cache breakdown: set data to never expire

  • Cache Avalanche: Redis Cluster High Availability, Evenly Set Expiration Time

  • Cache penetration: interface layer verification, setting a default null flag for empty queries, Bloom filter.

Generally use Redis distributed cache, of course, sometimes you can also consider using local cache, such as Guava Cache, Caffeine, etc. There are some disadvantages of using local cache, that is, it is impossible to store large data, and the cache will become invalid when the application process is restarted.

14. The interface considers hot data isolation

Instantaneous high concurrency may destroy your system. Some hot data isolation can be done. Such as business isolation, system isolation, user isolation, data isolation, etc.

  • Business isolation, such as 12306's time-segmented ticket sales, disperses hot data processing and reduces system load pressure.

  • System isolation: For example, the system is divided into three sections: users, products, and communities. These three blocks use different domain names, servers, and databases to achieve complete isolation from the access layer to the application layer to the data layer.

  • User Isolation: Focus on user requests to better configured machines.

  • Data isolation: Use a separate cache cluster or database to serve hot data.

15. Variable parameter configuration, such as red envelope skin switching, etc.

If the product manager puts forward a demand for red envelopes, during Christmas, the red envelope skin will be related to Christmas, and during the Spring Festival, it will be the red envelope skin for the Spring Festival.

If the control is hard-coded in the code, there can be code similar to the following:


if(duringChristmas){
   img = redPacketChristmasSkin;
}else if(duringSpringFestival){
   img =  redSpringFestivalSkin;
}

If it’s the Lantern Festival, the operating lady suddenly has another idea, and it’s related to changing the red envelope skin to a lantern. At this time, should I modify the code and re-release it?

From the beginning of the interface design, it is possible to implement a red envelope skin configuration table, and make the red envelope skin configurable? To change the red envelope skin, just modify the table data.

Of course, there are still some scenarios that are suitable for some configurable parameters: how many pages to control, how long does it take for a certain red packet to expire, etc., all of which can be entered into the parameter configuration table. This is also a manifestation of the idea of ​​expansion.

16. The interface considers idempotency

The interface needs to consider idempotence, especially the important interfaces such as grabbing red envelopes and transferring money. The most intuitive business scenario is that the user clicks twice consecutively, whether your interface is held or not. Or in the case of repeated consumption in the message queue, how do you control your business logic?

Recall, what is idempotent?

In computer science, idempotent means that requesting a resource once and multiple times should have the same side effects, or that the impact of multiple requests is the same as that of one request execution.

Don't confuse everyone, there is actually a difference between anti-heavy and idempotent design. Anti-duplication is mainly to avoid duplicate data, just intercept duplicate requests. In addition to intercepting the processed requests, the idempotent design also requires the same effect to be returned every time the same request. However, in many cases, their processing procedures and solutions are similar.

edit

Add picture annotations, no more than 140 words (optional)

There are mainly 8 kinds of interface idempotent implementation schemes:

  • select+insert+primary key/unique index conflict

  • direct insert + primary key/unique index conflict

  • State machine is idempotent

  • Extract the anti-heavy table

  • token token

  • pessimistic lock

  • optimistic lock

  • distributed lock

17. Read and write separation, give priority to read-slave library, pay attention to master-slave delay

Our databases are all deployed in clusters, with master databases and slave databases. Currently, read and write are generally separated. For example, if you write data, it must be written to the main library, but for reading data that does not require high real-time performance, priority should be given to reading from the slave library, because it can share the pressure of the main library.

If you read from the library, you need to consider the master-slave delay.

18. The interface pays attention to the amount of data returned, if the amount of data is large, pagination is required

A packet returned by an interface should not contain too much data. Excessive data volume is not only complicated to deal with, but also the pressure of data volume transmission is very high. Therefore, the number is relatively large, and it can be returned in pages. If it is a message with irrelevant functions, then interface splitting should be considered.

19. Good interface implementation is inseparable from SQL optimization

We do the backend and write an interface, which is inseparable from SQL optimization.

SQL optimization thinks from these dimensions:

  • explain Analyze SQL query plans (focus on type, extra, filtered fields)

  • Show profile analysis to understand the state of the thread executed by SQL and the time consumed

  • Index optimization (covering index, leftmost prefix principle, implicit conversion, order by and group by optimization, join optimization)

  • Optimization of large paging problems (delayed association, recording the maximum ID of the previous page)

  • The amount of data is too large (separate database and table, synchronize to es, use es to query)

20. The granularity of the code lock is well controlled

What is lock granularity?

In fact, it is how big the scope you want to lock. For example, if you go to the bathroom at home, you only need to lock the bathroom. You don’t need to lock the whole house to prevent family members from entering. The bathroom is your lock granularity.

When we write code, if shared resources are not involved, there is no need to lock. It's like when you go to the bathroom, you don't need to lock the whole house, just lock the bathroom door.

For example, in the business code, there is an ArrayList that needs to be locked because it involves multi-threaded operations. Suppose there happens to be a time-consuming operation (the slowNotShare method in the code) that does not involve thread safety issues. How would you add it? What about the lock?

Counter example:

 
 

Positive example:

 
 

21. Interface status and errors need to be unified and clear

Provide the necessary interface call status information. For example, you need to clearly tell the client whether a transfer interface call is successful, failed, in process, or accepted successfully. If the interface fails, what is the specific reason for the failure. These necessary information must be told to the client, so it is necessary to define a clear error code and corresponding description. At the same time, try to encapsulate the error information as much as possible, and don't completely throw the exception information from the backend to the client.

Edit toggle to center

Add picture annotations, no more than 140 words (optional)

22. The interface should consider exception handling

Implementing a good interface is inseparable from elegant exception handling. For exception handling, here are ten tips:

  • Try not to use e.printStackTrace(), but use log printing. Because the e.printStackTrace() statement may cause the memory to be full.

  • When catching an exception, it is recommended to print out the specific exception to better locate the problem

  • Don't use one Exception to catch all possible exceptions

  • Remember to use finally to close the stream resource or use try-with-resource directly

  • The caught exception and the thrown exception must be an exact match, or the caught exception is the parent class of the thrown exception

  • The caught exception cannot be ignored, at least log it

  • Watch out for exceptions' infestation of your code hierarchy

  • Customize the package exception, do not discard the information of the original exception Throwable cause

  • Runtime exception RuntimeException should not be handled by catch, but pre-checked first, such as: NullPointerException handling

  • Pay attention to the order of exception matching, and capture specific exceptions first

23. Optimize program logic

It is still very important to optimize the logic of the program. That is to say, if the business code you implement is more complicated, it is recommended to write the comments clearly. Also, the code logic should be as clear as possible, and the code should be as efficient as possible.

For example, if you want to use the attributes of user information, you have obtained the userId according to the session, and then query the user information from the database. After using it, you may need to use the attributes of user information later. Some friends don’t think too much about it. If there are too many, pass the userId in backhand, and check the database again. . . I have seen this kind of code in my project. . . It's not good to pass down the user object directly. .

Counter-example pseudocode:

 
 

 

Positive example:

 
 

 

Of course, these are just some small examples, and there are many similar examples, which require everyone to think more during the development process.

24. In the process of interface implementation, pay attention to large files, large transactions, and large objects

  • When reading large files, do not read Files.readAllBytes directly into the memory, as this will cause OOM. It is recommended to use BufferedReader line by line.

  • Large transactions may lead to problems such as deadlock, long rollback time, and master-slave delay. Try to avoid large transactions during development.

  • Pay attention to the use of some large objects, because large objects directly enter the old age, which may trigger fullGC

25. Your interface needs to consider current limiting

If your system handles 1,000 requests per second, what if 100,000 requests come in a second? From another perspective, when the concurrency is high, the traffic peak comes and exceeds the carrying capacity of the system. What should I do?

If no measures are taken, all the requests will come, and the system CPU, memory, and load will be very high. In the end, the requests cannot be processed, and all requests cannot be responded normally.

For this scenario, we can use a current limiting scheme. Just to protect the system, redundant requests are discarded directly.

Current limit definition:

In computer networks, throttling is to control the rate at which a network interface sends or receives requests, which prevents DoS attacks and limits web crawlers. Current limiting, also known as flow control. It means that when the system is faced with high concurrency or large traffic requests, it restricts the access of new requests to the system, so as to ensure the stability of the system.

You can use Guava's RateLimiter stand-alone version to limit current, you can also use Redis distributed current limit, you can also use Ali open source component sentinel current limit

26. When the code is implemented, pay attention to runtime exceptions (such as null pointers, subscripts out of bounds, etc.)

In daily development, we need to take measures to avoid runtime errors such as array boundary overflow, division by zero, and null pointer. Similar codes are more common:

 
 

 

Measures should be taken to prevent overflow of the array bounds. For example:

 
 

 

edit

Add picture annotations, no more than 140 words (optional)

27. Ensure interface security

If your API interface is provided externally, you need to ensure the security of the interface. To ensure the security of the interface, there are token mechanisms and interface signatures.

The token mechanism authentication scheme is relatively simple, that is

Edit toggle to center

Add picture annotations, no more than 140 words (optional)

  1. The client initiates a request to apply for a token.

  2. The server generates a globally unique token, saves it in redis (usually an expiration time is set), and then returns it to the client.

  3. The client initiates a request with the token.

  4. The server goes to redis to confirm whether the token exists. Generally, redis.del(token) is used. If it exists, it will be deleted successfully, that is, the business logic will be processed. If the deletion fails, the business logic will not be processed, and the result will be returned directly.

The interface signature method is to add the relevant information of the interface request (request message, including the request timestamp, version number, appid, etc.), the client private key to sign, and then the server uses the public key to verify the signature, and the verification is considered legal A request that has not been tampered with.

In addition to the signature verification and token mechanisms, interface packets are generally encrypted. Of course, the HTTPS protocol will encrypt the message. If it is our service layer, how to encrypt and decrypt it?

You can refer to the principle of HTTPS, that is, the server sends the public key to the client, and then the client generates a symmetric key, and then the client encrypts the symmetric key with the server’s public key, and then sends it to the server, and the server uses its own private key Decrypt to get the client's symmetric key. At this time, the message can be transmitted happily. The client encrypts the request message with the symmetric key, and the server decrypts the message with the corresponding symmetric key.

Sometimes, the security of the interface also includes the desensitization of information such as mobile phone numbers and ID cards. That is to say, the user's private data cannot be exposed casually.

28. Distributed transactions, how to guarantee

Distributed transaction: It means that the participants of the transaction, the server supporting the transaction, the resource server and the transaction manager are respectively located on different nodes of different distributed systems. Simply put, a distributed transaction refers to a transaction in a distributed system, and its existence is to ensure the data consistency of different database nodes.

Several solutions for distributed transactions:

  • 2PC (two-phase submission) plan, 3PC

  • TCC(Try、Confirm、Cancel)

  • local message table

  • best effort notification

  • set

29. Some classic scenarios of transaction failure

In our interface development process, we often need to use transactions. Therefore, some classic scenarios of transaction failure need to be avoided.

  • The access permission of the method must be public, other private and other permissions, and the transaction will be invalid

  • The method is defined as final, which will cause the transaction to fail.

  • The method in the same class is directly called internally, which will cause the transaction to fail.

  • If a method is not handed over to spring management, no spring transaction will be generated.

  • Multi-threaded calls, the two methods are not in the same thread, and the obtained database connections are different.

  • The table's storage engine does not support transactions

  • If you swallow an exception by yourself in try...catch, the transaction will fail.

  • Error Propagation Properties

30. Master common design patterns

To write the code well, you still need to be proficient in commonly used design patterns, such as strategy pattern, factory pattern, template method pattern, observer pattern, and so on. Design pattern is a summary of code design experience. Using design patterns can reusable code, make the code easier for others to understand, and ensure code reliability.

31. When writing code, consider linear safety issues

In the case of high concurrency, HashMap may have an infinite loop. Because it is non-linear safe, you can consider using ConcurrentHashMap. So try to develop a habit of this, don't come up with a backhand, just a new HashMap();

  • Hashmap, Arraylist, LinkedList, TreeMap, etc. are linearly insecure;

  • Vector, Hashtable, ConcurrentHashMap, etc. are linearly safe

edit

Add picture annotations, no more than 140 words (optional)

32. The interface definition is clear and easy to understand, and the naming standard.

We write code not only to achieve current functions, but also to facilitate subsequent maintenance. When it comes to maintenance, the code is not only written for yourself, but also for others. Therefore, the interface definition should be clear and easy to understand, and the naming convention should be standardized.

33. Interface version control

The interface should be under version control. That is to say, the request basic message should include the version interface version number field to facilitate future interface compatibility. In fact, this point can also be regarded as a manifestation of the scalability of the interface.

For example, if a certain function of the client APP is optimized, the old and new versions will coexist. At this time, our version number will come in handy to upgrade the version and do a good job of version control.

34. Pay attention to code specification issues

Watch out for some common code smells:

  • A lot of repetitive code (extracting shared methods, design patterns)

  • Too many method parameters (can be encapsulated into a DTO object)

  • The method is too long (small function)

  • Too many judgment conditions (optimize if...else)

  • Do not handle unused code

  • Do not pay attention to code formatting

  • avoid over-engineering

35. To ensure the correctness of the interface is actually to ensure fewer bugs

To ensure the correctness of the interface, from another perspective, it is to ensure fewer bugs, or even no bugs. Therefore, after the interface is developed, it is generally necessary to develop a self-test. Then, the correctness of the interface is also reflected in ensuring the correctness of the data when multiple threads are concurrent, and so on. For example, when you make a transfer transaction and deduct the balance, you can use the CAS optimistic lock to ensure that the balance deduction is correct.

If you are implementing the seckill interface, you have to prevent overselling. You can use Redis distributed locks to prevent overselling issues.

36. Learn to communicate, communicate with the front end, communicate with the product

I put this point to the end, learning to communicate is very, very important. For example, when you develop and define an interface, you must not come up and immerse yourself in the definition of the interface. You need to align the interface with the client first. When encountering some difficulties, align the solution with the technical leader. In the process of realizing the requirements, if there is any problem, communicate with the product in time.

In short, in the process of developing the interface, we must communicate well~

Guess you like

Origin blog.csdn.net/m0_69424697/article/details/125104393