vendredi 2 août 2019

How to handle state, concurrency and JMS inside Spring controllers?

I'm building a Spring Boot 2.1.6 REST API in which one of the controllers receives frequent requests. Each request contains data pertaining to some client event. The client is a video player which sends events each time a user viewed a video, viewed 25%, 50% etc. of the video, clicked etc. There may around 20 events per each client cycle.

I need to save each event in the database. As far as I understand inserting an event into the database inside the controller is not the best solution because inserting in the db is expensive, especially if this will happen on each call to the controller.

Therefore I thought to accumulate the events inside the controller inside some data structure, perhaps a queue. Then when the queue reached certain size I'd send all the events via JMS service to a consumer which will update the db with all the events (in a separate thread).

Controllers in Spring are singletons by default (and I want to keep them that way). Therefore I will need to synchronize the controller queue because I don't think that even if I'll declare the queue static it will prevent synch issues. In addition if I use synchronized inside the controller then I'm not taking an advantage of the multi-threading model that Tomcat uses.

What is the best practice to save controller state/perform synchronization is such case?

This is my controller:

@RestController
public class TrackingEventController {

    @Autowired
    private DBHandler db;

    @Autowired
    private JmsTemplate jmsTemplate;

    private Queue<TrackingEvent> queue;

    public TrackingEventController() {
        this.queue = new ArrayBlockingQueue<>(30);
    }

    @CrossOrigin(origins = "*")
    @RequestMapping(method=GET, path=trackingEventPath)
    public ResponseEntity<Object> handleTrackingEvent(
            @RequestParam(name = Routes.event) String event,
            @RequestParam(name = Routes.pubId) String pubId,
            @RequestParam(name = Routes.advId) String advId) {

        TrackingEvent tr = new TrackingEvent(event, pubId, advId);

        this.trySendEventsMessage(tr);

        return new ResponseEntity<>(null, new HttpHeaders(), HttpStatus.OK);
    }

    private synchronized void trySendEventsMessage() {
        this.queue.add(tr);
        if (this.queue.size() >= eventsMapMaxMessageSize) {
            jmsTemplate.convertAndSend(jmsTopicName, this.queue);
            queue.clear();
        }
    }
}

Aucun commentaire:

Enregistrer un commentaire