Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC background process

I come from a Perl background and am writing my first Java MVC web application using Spring.

My webapp allows users to submit orders which the app processes synchronously by calling a third-party SOAP service. The next phase of the project is to allow users to submit bulk orders (e.g. a CSV containing 500 rows) and process them asynchronously. Here is a snippet of my existing controller:

@Controller
@Service
@RequestMapping(value = "/orders")
public class OrderController {

    @Autowired
    OrderService orderService;

    @RequestMapping(value="/new", method = RequestMethod.POST)
    public String processNewOrder(@ModelAttribute("order") Order order, Map<String, Object> map) {

        OrderStatus orderStatus = orderService.processNewOrder(order);

        map.put("orderStatus", orderStatus);

        return "new";
    }
}

I plan to create a new @RequestMapping to deal with the incoming CSV and modify the OrderService to be able to break the CSV apart and persist the individual orders to the database.

My question is: what is the best approach to creating background workers in an MVC Spring app? Ideally I would have 5 threads processing these orders, and most likely from a queue. I have read about @Async or submitting a Runnable to a SimpleAsyncTaskExecutor bean and am not sure which way to go. Some examples would really help me.

like image 468
smilin_stan Avatar asked Jul 21 '14 09:07

smilin_stan


1 Answers

I think Spring Batch is overkill and not really what you are looking for. It's more for batch processing like writing all the orders to a file then processing all at once, whereas this seems to be more like asynchronous processing where you just want to have a 'queue' of work and process it that way.

If this is indeed the case, I would look into using a pub/sub model using JMS. There are several JMS providers, for instance Apache ActiveMQ or Pivotal RabitMQ. In essence your OrderService would break the CSV into units of work, push them into a JMS Queue, and you would have multiple Consumers setup to read from the Queue and perform the work task. There are lots of ways to configure this, but I would simply make a class to hold your worker threads and make the number of threads be configurable. The other added benefits here are:

  1. You can externalize the Consumer code, and even make it run on totally different hardware if you like.
  2. MQ is a pretty well-known process, and there are a LOT of commercial offerings. This means you could easily write your order processing system in C# using MQ to move the messages over, or even use Spring Batch if you like. Heck, there is even MQ Series for host, so you could have your order processing occur on mainframe in COBOL if it suited your fancy.
  3. It's stupidly simply to add more consumers or producers. Simply subscribe to the Queue and away they go!
  4. Depending on the product used, the Queue maintains state so messages are not "lost". If all the consumers go offline, the Queue will simply backup and store the messages until the consumers come back.
  5. The queues are also usually more robust. The Producer can go down and the consumers you not even flinch. The consumers can go down and the producer doesn't even need to know.

There are some downsides, though. You now have an additional point of failure. You will probably want to monitor the queue depths, and will need to provision enough space to store the messages when you are caching messages. Also, if timing of the processing could be an issue, you may need to monitor how quick things are getting processed in the queue to make sure it's not backing up too much or breaking any SLA that might be in place.

Edit: Adding example... If I had a threaded class, for example this:

public class MyWorkerThread implements Runnable {
  private boolean run = true;

  public void run() {
    while (run) {
      // Do work here...
    }

    // Do any thread cooldown procedures here, like stop listening to the Queue.
  }

  public void setRunning(boolean runState) {
    run = runState;
  }
}

Then I would start the threads using a class like this:

@Service("MyThreadManagerService")
public class MyThreadManagerServiceImpl implements MyThreadManagerService {
  private Thread[] workers;
  private int workerPoolSize = 5;

  /**
   * This gets ran after any constructors and setters, but before anything else
   */
  @PostConstruct
  private void init() {
    workers = new Thread[workerPoolSize];
    for (int i=0; i < workerPoolSize; i++) {
      workers[i] = new Thread(new MyWorkerThread());  // however you build your worker threads
      workers[i].start();
    }
  }

  /**
   * This gets ran just before the class is destroyed.  You could use this to
   * shut down the threads
   */
  @PreDestroy
  public void dismantle() {
    // Tell each worker to stop
    for (Thread worker : workers) {
      worker.setRunning(false);
    }

    // Now join with each thread to make sure we give them time to stop gracefully
    for (Thread worker : workers) {
      worker.join();  // May want to use the one that allows a millis for a timeout
    }

  }

  /**
   * Sets the size of the worker pool.
   */
  public void setWorkerPoolSize(int newSize) {
    workerPoolSize = newSize;
  }
}

Now you have a nice service class you can add methods to to monitor, restart, stop, etc., all your worker threads. I made it an @Service because it felt more right than a simple @Component, but technically it can be anything as long as Spring knows to pick it up when you are autowiring. The init() method on the service class is what starts up the threads and the dismantle() is used to gracefully stop them and wait for them to finish. They use the @PostConstruct and @PreDestroy annotations, so you can name them whatever you want. You would probably have a constructor on your MyWorkerThread to setup the Queues and such. Also, as a disclaimer, this was all written from memory so there may be some mild compiling issues or method names may be slightly off.

There may be classes already available to do this sort of thing, but I have never seen one myself. Is someone knows of a better way using off-the-shelf parts I would love to get better educated.

like image 67
CodeChimp Avatar answered Sep 20 '22 03:09

CodeChimp