preface

Recently, I am taking over the business related to inventory. Due to the arrival of the golden three silver four job-hopping season, some partners in the company finally chose to leave. So handover and development has become the main work for a while. After looking at the code for a few days, WHILE getting familiar with the business, I also found some defects in the module development, so I thought I could share them with you as a kind of introspection.

background

The inventory service system is mainly connected with the inventory uploading function of various e-commerce platforms, that is, the inventory in the own system is uploaded to the platform according to certain strategies. Different platforms will adopt different strategies, so the system inventory corresponding to each platform will display different inventory quantity, which will involve a process of inventory calculation, and this strategy is dynamic change, which leads to real-time calculation.

The above diagram is a simple process: when receiving events affecting inventory changes such as trading behaviors or purchasing behaviors, the platform associated with commodities should be acquired at this time and the uploading function is enabled. Then the inventory is calculated and the final settlement result is uploaded to the platform.

This is essentially a simple process (although I’ve omitted some of the steps, I’ve just taken the simplest ones here), and it’s not too hard to understand. However, when I saw the process of inventory calculation, I felt that such writing method seemed very weak, so I thought I could optimize it.

Maybe for students with non-e-commerce business background, the concept of inventory may not be so profound. The inventory you can see is the number of items left after you select them on Taobao and Jd.com. However, for e-commerce merchants, this inventory may be composed of several parts:

Actual inventory = available inventory + purchasing inventory in transit + selling inventory in transit +… + XX inventory

For example, we will define the following inventory data structure

public class Stock {

    /** * itemId is the itemId * skuId is the skuId * two ids determine the most fine-grained item */
    private Long itemId;
    private Long skuId;

    private Long availableStock;
    private Long purchaseStock;
    private Long returnStock;

    /** * Final display of inventory */
    private Long totalShowStock;

    // omit the get and set methods
    
    public Long getTotalShowStock(a) {
        return this.availableStock + this.purchaseStock + this.returnStock; }}Copy the code

Analysis of the

So before I describe the problem, I want to ask you to think about it this way: if you, how would you do inventory calculation? Isn’t that a silly question, isn’t that a common scenario, or maybe even a problem, that might be written like this:

public Long stockCalc(Stock stock) {
    Long finalStock = 0L; finalStock += stockCalcWithAvailableStock(stock); finalStock += stockCalcWithPurchaseStock(stock); finalStock += stockCalcWithReturnStock(stock); . . .return finalStock;
}

private Long stockCalcWithAvailableStock(Stock stock) {
    // Get the available inventory logic
    return 0L;
}

private Long stockCalcWithPurchaseStock(Stock stock) {
    // Get the purchase in-transit inventory logic
    return 0L;
}

private Long stockCalcWithReturnStock(Stock stock) {
    // Get the inventory logic in transit
    return 0L;
}
Copy the code

It is written roughly like this, and there is nothing wrong with it, because the original inventory service is also written like this. But did you notice that the stockCalc method is filled with a lot of inventory calculations. Because the inventory information for these parts is not directly stored in a single record of the table, it has to be queried elsewhere (RPC, other library tables, etc.). Here, although the method is divided into various methods, in pursuit of the beauty of the code in form, it is still very bloated. Here I only show the calculation of three components of the inventory, but in reality there may be a dozen components. So if there is a new inventory concept, like adjuststock, then I need to write a stockCalcWithAdjustStock method in stockCalc method, which will obviously break the code design specification.

expand

Because the current work I encounter is inventory calculation, in fact, there are many similar places in our daily development, for example, the configuration information of a user, I use UserConfig to represent. In order to fill in the UserConfig information, we will get the information step by step as above, and then perform the assignment, resulting in a very bloated method. If the user information has a new attribute, we will continue to write code in this method, as in the scenario of rich inventory information above.

Train of thought

So what can be done? It can be observed that inventory calculation is actually carried out step by step in order, so my first thought is the responsibility chain development mode. In terms of chain of responsibility, I think you can think of struts 2, SpringMVC interceptors, Tomcat FilterChain and so on. Like the inventory calculation above, can we calculate one step and then pass it along the link until there are no related processing classes. So let’s give it a shot!

implementation

Now that the chain of responsibility development model is established, let’s draw a class diagram to make things clear:

Let’s look at how StockCalcBusiness and StockCalcManager are implemented:

public abstract class StockCalcBusiness {

    private StockCalcBusiness next;

    public final void handle(Stock stock) {
        // Process the inventory in the current environment first
        this.stockCalc(stock);
        // Process the inventory in the next responsibility chain
        if (this.next ! =null) {
            this.next.handle(stock); }}public void setNext(StockCalcBusiness stockCalcBusiness) {
        this.next = stockCalcBusiness;
    }

    public abstract void stockCalc(Stock stock);

}

Copy the code
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.util.List;

@Component
@Slf4j
public class StockCalcManager {

    @Autowired
    List<StockCalcBusiness> stockCalcBusinessList;

    @PostConstruct
    public void build(a) {
        if (CollectionUtils.isEmpty(stockCalcBusinessList)) {
            stockCalcBusinessList = Lists.newArrayList(new StockCalcBusiness() {
                @Override
                public void stockCalc(Stock stock) {
                    log.info("nothing to deal"); }}); }else {
            for (int i = 0; i < stockCalcBusinessList.size() - 1; i++) {
                stockCalcBusinessList.get(i).setNext(stockCalcBusinessList.get(i + 1)); }}}public void stockCalc(Stock stock) {
        this.stockCalcBusinessList.get(0).handle(stock);
        log.info("End of calculation."); }}Copy the code

Of course, here we integrate in the Spring container by default. Due to @postconstruct, the build method declared here is executed when the Spring Bean is initialized. During initialization, the StockCalcBusiness class from the container (since StockCalcBusiness is an abstract class, classes here refer to inherited subclasses) is injected into the collection. The build method here is essentially creating a link between subclasses of StockCalcBusiness. Therefore, when stockCalc is called, the Handle method is dropped along the first StockCalcBusiness subclass, and the Handle method continues along the link after calculating the current inventory. As you’ll notice, I’ve also used the template design pattern, which allows each subclass to implement StockCalcBusiness#stockCalc. So what are the benefits of that? As we said above, if I were to implement a configurable inventory again, the old way of doing it would be that that method would get longer and longer and fatter, and here we just need to implement a subclass of StockCalcBusiness.

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Order(4)
public class AdjustStockCalcBusiness extends StockCalcBusiness {

    @Override
    public void stockCalc(Stock stock) {
        log.info("Calculate assignable adjusted inventory");
        if (StockType.checkAdjustStock(stock.getBit4Stock())) {
            // We omit the process of retrieving the adjusted inventory here
            stock.setAdjustStock(20L);
        } else {
            stock.setAdjustStock(0L); }}}Copy the code

AdjustStockCalcBusiness is added to the AdjustStockCalcBusiness chain due to Spring’s auto-injection mechanism. This way we can only care about this part of the business calculation in this class, so that the original business method does not become bloated.

subsequent

After we optimized the inventory calculation in this way, sure enough, there was a subsequent product demand and another inventory domain concept needed to be added into the inventory calculation, so I no longer needed to add code to the previous bloated method.

thinking

Of course, in fact, this development mode is good, but there are certain bottlenecks:

  1. If there are many subclasses, the link will be deep and stack overflow may occur in severe cases.
  2. In some cases, we want to acquire data in a concurrent mode, not necessarily in a synchronous mode.

For the first consideration, it’s really about business analysis and estimation. We can see that this chain might be in a manageable range, so we can do it this way. Personally, I have reservations about the second consideration. First of all, since it is the chain of responsibility development mode, it has been literally stated that the processing here is linear execution in the way of link, which is called synchronous execution. But in many cases, we want to say that we can execute concurrently, and I don’t think I need to go into the benefits of concurrency, but the main thing is to improve response speed, improve efficiency. We can also do a simple process, where we will use CountDownLatch under JUC. Let’s add the CountDownLatch to the Stock object and make a simple transformation of the StockCalcManager:

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.*;

@Component
@Slf4j
public class StockCalcManager {

    @Autowired
    List<StockCalcBusiness> stockCalcBusinessList;

    public static ExecutorService threadPool = new ThreadPoolExecutor(10.10.0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(10000));

    @PostConstruct
    public void build(a) {
        if (CollectionUtils.isEmpty(stockCalcBusinessList)) {
            stockCalcBusinessList = Lists.newArrayList(new StockCalcBusiness() {
                @Override
                public void stockCalc(Stock stock) {
                    log.info("nothing to deal"); }}); }else {
            for (int i = 0; i < stockCalcBusinessList.size() - 1; i++) {
                stockCalcBusinessList.get(i).setNext(stockCalcBusinessList.get(i + 1)); }}}public void stockCalc(Stock stock) {
        stock.setCountDownLatch(new CountDownLatch(stockCalcBusinessList.size()));
        this.stockCalcBusinessList.get(0).handle(stock);
        try {
            stock.getCountDownLatch().await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("End of calculation."); }}Copy the code

AdjustStockCalcBusiness (AdjustStockCalcBusiness) : AdjustStockCalcBusiness

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Random;

@Component
@Slf4j
@Order(4)
    public class AdjustStockCalcBusiness extends StockCalcBusiness {

    @Override
    public void stockCalc(Stock stock) {
        StockCalcManager.threadPool.submit(() -> {
            log.info("Calculate adjusted inventory");
            if (StockType.checkAdjustStock(stock.getBit4Stock())) {
                // We omit the process of retrieving the adjusted inventory here
                stock.setAdjustStock(20L);
            } else {
                stock.setAdjustStock(0L);
            }
            try {
                Thread.sleep(1000 * new Random().nextInt(10));
            } catch(InterruptedException e) { e.printStackTrace(); } stock.getCountDownLatch().countDown(); }); }}Copy the code

Here we use a combination of thread pools and countdownlatches for concurrency control, which means that when we transfer the link, we just commit the task until the last node commits the task. The link appears to be complete, but because CountDownLatch exists, the main thread waits for all the processing classes holding CountDownLatch to complete and countDown the state value of the AQS to 0. What is the state of AQS? What are you talking about? OK, I think you are in a dangerous state to interview! . Of course, if the emphasis is on sequential processes, such as crafts only do process and so on project development, still use the true chain of responsibility development model.

The last

Color {red}{thumbnail}{thumbnail}{thumbnail}{thumbnail}{thumbnail}{thumbnail}{thumbnail}{thumbnail}{thumbnail} Also attached here is my Github address :github.com/showyool/ju…

Welcome to follow my wechat official account: [a code with temperature]