In a project, payment is usually related to the business, and when it comes to payment, there must be transfer operation. The transfer step is a relatively key part, and the design ability of this interface also roughly reflects a person’s level.
I met a problem yesterday:
Try to write a transfer interface in Java, pass in the main business parameters including transfer account, transfer account, transfer amount, complete the transfer and transfer account funds processing, the service to ensure that the balance of the transfer account will not be overdrawn in the process of funds, the amount is calculated accurately.
design
-
First of all, there will not be so few parameters in the system. In general, the request parameters will also have some common information, such as the source of the request (request IP and system), request serial number, request time, and other information. The gateway usually blocks some illegal requests
-
If a result is returned, it usually contains the processing result, response time, status after processing, and the original request information is also returned
-
See if the requirement has a strong consistency requirement, and if not, whether to return the result in a timely manner. According to the requirements, if you do not need to return the results in real time, you can continue to retry at the back end until the final results are available and strong consistency is required, some special processing needs to be done. If the final consistency is not guaranteed, you can.
-
Idempotent design, a unique request number for only one payment, to prevent repeated deductions
-
Some other remote services may be involved, and some operations will be handled according to the negotiation with other systems. Of course, the input parameters of this interface will also be reported to the caller of the system
-
So they say, you have to make a judgment about the balance, and you have to make an internal judgment about whether the user has enough money. At the database level, the user balance can not be less than 0. The amount is calculated accurately, usually using BigDecimal.
-
There are a few conventions to keep in mind when writing code
-
Internally note some restrictions on whether the account is valid
code
I didn’t do anything idempotent in my code. There may be other issues with the code, but feel free to point out if there are points you haven’t taken into account
package me.aihe.demo;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.util.HashMap;
/** * Try to write a transfer interface in Java, pass in the main business parameters including the transfer account, * transfer account, transfer amount, complete the transfer and transfer account funds processing, * the service to ensure that the balance of the transfer account will not be overdrawn, the amount is calculated accurately. * /
public class FirstProblem {
/** * suppose this thing is a remote service name */
private String checkAmoutisEnoughRemoteService = "A remote service that verifies that a user's balance is sufficient.";
/* * Title analysis: * Define interface: * Input parameters: Transfer out account Transfer amount * Requirements: To determine whether the balance is sufficient * the amount is accurate use BigDeceimal * * doubt: * Is it necessary to return a value? Or is it just a one-time process? * Log key operations * Add distributed locks if there is concurrency * Others According to the demand, whether to do some extra control, such as limiting, rollback, retry * If there may be a remote call wait state, retry, as synchronous as possible, * if it can be asynchronous, Background add scheduled tasks for asynchronous data query and update * */
// I am writing the interface directly here, instead of defining the name of the class, I can define the name of the class if necessary
// Since it is not an interface, I leave the method blank
/** * Transfer interface, try to write a transfer interface in Java, pass in the main business parameters including transfer account, * transfer account, transfer amount, complete transfer and transfer account funds processing, * the service to ensure that the balance of the transfer account will not be overdrawn, the amount is calculated accurately. * *@author he.ai 2019-04-18 20:16
*
* @paramWho gets the money out from *@paramDestAccout who the money was transferred to *@paramIf we were to return a Result, we would normally use a common Result class to encapsulate the code, the Result, the data and so on from a remote call
void transfer( String sourceAccount, String destAccout, String amout ){
// Suppose we can get the sourceAccout balance here
// The source can be: call the service remotely, fetch it directly from the database, or get the current account balance
// 1. Check whether the parameters are valid
// If there is an exception, where should the exception be handled
checkParam(sourceAccount, destAccout, amout);
// You can also do a check to see if the account exists
// 2. Verify whether the balance of the transferred account is enough. This step depends on whether we have permission.
// If we do not have the permission to obtain the user's balance information, we need to call the department with the permission to judge
// I assume that we do not have permission to know the user's balance, so we need to judge
// Call remote parameters, which need to be negotiated with other systems
HashMap<String, String> map = new HashMap<>();
map.put("account",sourceAccount);
map.put("amount",amout);
Result result = callRemoteService(checkAmoutisEnoughRemoteService, map);
// Process result
// Determine whether the user balance is sufficient, I will not write the judgment logic
// 3. Carry out the transfer operation, the code can run here, indicating that the user account is OK, the balance is ok
// As for what risk control, do users have security risks, depending on requirements, and other systems
// If the data consistency requirement is very high, then we can do global transaction control
// If the data consistency requirement is not high, then we can do it first, and then write the scheduled task, or use asynchronous notification mode
// It can even be locked
doTransfer(sourceAccount,destAccout,amout);
// At this point, assuming the money has cleared, see if the request should be notified to other business systems
sendNotifytoOthers();
}
private void sendNotifytoOthers(a) {}/** ** If there is a global transaction, the local transaction is also ok */
@Transactional(rollbackFor = Exception.class)
public void doTransfer(String sourceAccount, String destAccout, String amout) {
// We should have access to the balance
// If we get the user's balance, we can call another system
// But we still need a backup
// The balance of the user, this step according to the system requirements, check where to obtain
BigDecimal sourceLeftMoney = new BigDecimal("1000");
BigDecimal destLeftMoney = new BigDecimal("200");
// If we had the permission, we would update it directly, and we would not have to call the remote service
// The general ORM framework here can help us with the transformation
// update userDatabase set rest_money = (sourceLeftMoney - amout) where rest_money = sourceLeftMoney and accout = sourceAccount
// Remember to log.
updatesourceAccount(sourceAccount,amout);
// The remaining amount here is the remaining amount transferred into the account
// update userDatabase set rest_money = (destLeftMoney + amout) where rest_money = destLeftMoney and accout = destAccout
updatedestAccout(destAccout,amout);
}
private void updatedestAccout(String destAccout, String amout) {}private void updatesourceAccount(String sourceAccount, String amout) {}/** * tool method, which can directly call the remote service *@param checkAmoutisEnoughRemoteService
* @param map
*/
private Result callRemoteService(String checkAmoutisEnoughRemoteService, HashMap<String, String> map) {
// Remote service processing logic
return null;
}
static class Result{}/ * * * actually here multiple parameters can be encapsulated as an object, you don't have to mail * so many parameters@param sourceAccount
* @param destAccout
* @param amout
*/
private void checkParam(String sourceAccount, String destAccout, String amout) {
if (StringUtils.isEmpty(sourceAccount)){
// This is a business exception. Projects usually have their own business exceptions
// As for exception replenishment, depending on the project choice, either catch at present or throw global exception to catch
// For convenience, I will not catch exceptions here
// If it is critical business, try to send an alarm
throw new RuntimeException("Abnormal Service" + "Transfer out account is empty");
}
// Different exceptions can be thrown according to different parameters
if (StringUtils.isEmpty(destAccout)){
throw new RuntimeException("Abnormal service:" + "Transfer account is empty");
}
if (StringUtils.isEmpty(amout)){
throw new RuntimeException("Transfer amount is empty"); }}}Copy the code
The last
Welcome to discuss, the above is just a bit of my train of thought, pool wisdom can we progress together.