title: How to avoid race conditions for duplicate requests?
body: <p>Say I receive two concurrent requests with the same payload. But I must (1) perform a single payment transaction (using a third-party API) and (2) somehow return the same response to both requests. It's this second requirement that complicates things. Otherwise, I could have just returned an error response to the duplicate request.</p>
  <p>I have two entities: <code>Session</code> and <code>Payment</code> (related via <code>@OneToOne</code> relation). <code>Session</code> has two fields to keep track of the overall state: <code>PaymentStatus</code> (<code>NONE</code>, <code>OK</code>, <code>ERROR</code>), <code>SessionStatus</code> (<code>CHECKED_IN</code>, <code>CHECKED_OUT</code>). The initial condition is <code>NONE</code> and <code>CHECKED_IN</code>.</p>
  <p>The request payload does contain a unique session number, which I use to get the relevant session. For now, assume that the payment service is sort of &quot;idempotent&quot; for a unique order id: it performs only one transaction for a given order id. The order id also comes in the request payload (the same value for the twin requests).</p>
  <p>The flow I have in mind is along these lines:</p>
  <ol>
  <li>Get the session</li>
  <li>If <code>session.getPaymentStatus() == OK</code>, find the payment and return success response.</li>
  <li>Perform the payment</li>
  <li>Save the payment to DB. <code>Session</code> has a field with unique constraint generated from the request payload. So if one of the threads tries to insert a duplicate, a <code>DataIntegrityViolationException</code> will be thrown. I catch it, find the already inserted payment, and return a response based on it.</li>
  <li>If no exception is thrown in 4, return the appropriate response.</li>
  </ol>
  <p>In this flow, there seems to be at least one scenario where I might have to return <em>error</em> responses to both requests <em>despite</em> the fact that the payment transaction was successfully completed! For instance, say an error occurs for the &quot;first&quot; request, payment is not done, and an error response is returned. But for the &quot;second&quot; request, which happens to take a bit longer to process, payment is done, but upon insertion to DB, the already inserted payment record is discovered, and an error response is formed on the basis of it.</p>
  <p>I'd like to avoid all these race condition-like situations. And I've a feeling that I'm missing something very obvious here. In essence, the problem is to somehow make one request to wait for another to complete. Is there a way that I can utilize DB transactions and locks to handle this smoothly?</p>
  <p>Above I assumed that the payment service is idempotent for a given order id. What if it wasn't and I had to absolutely avoid sending duplicate requests to it?</p>
  <p>Here's the relevant part of the service method:</p>
  <pre class="lang-java prettyprint-override"><code>Session session = sessionRepo.findById(sessionId)
          .orElseThrow(SessionNotFoundException::new);
  
  Payment payment = paymentManager.pay(session, req.getReference(), req.getAmount());
  
  Payment saved;
  try {
      saved = paymentRepo.save(payment);
  } catch (DataIntegrityViolationException ex) {
      saved = paymentRepo.findByOrderId(req.getReference())
              .orElseThrow(PaymentNotFoundException::new);
  }
  
  PaymentStatus status = saved.getSession().getPaymentStatus();
  PaymentStage stage = saved.getSession().getPaymentStage();
  
  if (stage == COMPLETION &amp;&amp; status == OK)
      return CheckOutResponse.success(req.getTerminalId(), req.getReference(), 
              req.getPlateNumber(), saved.getAmount(), saved.getRrn());
  
  return CheckOutResponse.error(req.getTerminalId(), req.getReference(),
              &quot;Unable to complete transaction.&quot;);
  </code></pre>