Search code examples
javaspring-bootspring-transactions

How to avoid parallel processing in async in Spring, or how to dispatch web request


Giving the following code which takes a request with a web shop order as JSON in Spring Boot. The order should be handed over to the method processJsonOrder on which the order will be processed. After handing over, the web service should return OK to the client. Processing in processJsonOrder should be async without blocking the return to the client.

@RestController
public class WebShopOrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping(value = "/wc-order")
    public void getWcOrder(@RequestBody String jsonOrder, @RequestHeader Map<String, String> headers) {
        log.info("order received");
    
        // should be done async
        processJsonOrder(jsonOrder);

        return;
    }
}

However, jsonOrder processed in processJsonOrder should be processed one by one. Maybe by a queue, or there is a nice solution in Spring to do so. The idea is that jsonOrders are waiting when an order is executed within processJsonOrder. Currently, when two order updated arrive within seconds, order will be inserted into the database by processJsonOrder twice, as the thread processing the jsonOrder has not reached the commit point yet. There is some code in the orderService to check for duplicates on database level, but that doesn't help always.

The OrderService.java

@Service
public class OrderService {
    
    @Async
    @Transactional
    public void processJsonOrder(WoocommerceOrder wcOrder) {
        // performing business logic
    }

}

I expect that there is an elegant solution for that, or maybe doing the processJson order Async is the wrong approach. Important is that the calling client gets back an answer immediately, but the order is processed afterwards asynchronously, but one by one.


Solution

  • What you're trying to do with your code is called reach a mutual exclusion to avoid a race condition - two or more processes or threads simultaneously reach a critical segment of your code and try to read and/or update the same piece of data, potentially causing a bug or error. In java , there is a built in feature to avoid this kind of problem: The synchronized keyword.

    First, on your Controller, configure the Thread(s):

    @RestController
    public class WebShopOrderController {
    
        @Autowired
        private OrderService orderService;
    
        @PostMapping(value = "/wc-order")
        public void getWcOrder(@RequestBody String jsonOrder, @RequestHeader Map<String, String> headers) {
            log.info("order received");
    
            // Will be done async
            new Thread(() -> processJsonOrder(jsonOrder)).start();
    
            return;
        }
    }
    

    Then, just use the synchronized keyword on your @Service method:

    @Service
    public class OrderService {
        
        @Transactional
        public synchronized void processJsonOrder(WoocommerceOrder wcOrder) {
            // Only one Thread at a time will be allowed to enter this area.
            // performing business logic
        }
    
    }