Interview question
Implement a persisted idempotent POST with Spring and JPA
Uses a scoped idempotency key, request hash, unique constraint, and one transaction to create an order exactly once from the API perspective.
TL;DR
Uses a scoped idempotency key, request hash, unique constraint, and one transaction to create an order exactly once from the API perspective.
Idempotency semantics, database uniqueness, transaction boundaries, replay, payload conflicts, concurrency, and crash outcomes.
Practice the problem like a real interview: restate, reason, implement, and test.
Implement an idempotent order creation service. The key is scoped by authenticated actor and operation. The first request atomically stores both the order and a completed idempotency record. A repeat with the same payload replays the original response; the same key with a different payload returns 409. Concurrent duplicates must not create two orders.
key K + payload A -> 201, order 42
key K + payload A -> replay order 42
key K + payload B -> 409 Idempotency key reused with different request
@org.springframework.stereotype.Service
class IdempotentOrderService {
private static final String OPERATION = "create-order";
private final IdempotencyRepository keys;
private final OrderRepository orders;
private final org.springframework.transaction.support.TransactionTemplate tx;
CreateOrderResult create(java.util.UUID actorId, java.util.UUID key, CreateOrder command) {
String hash = RequestHash.sha256(command.productId() + ":" + command.quantity());
var existing = keys.findByActorIdAndOperationAndKey(actorId, OPERATION, key);
if (existing.isPresent()) return replay(existing.get(), hash);
try {
return tx.execute(status -> {
var order = orders.save(new Order(actorId, command.productId(), command.quantity()));
var result = new CreateOrderResult(order.getId(), 201);
keys.saveAndFlush(IdempotencyRecord.completed(
actorId, OPERATION, key, hash, result.orderId(), result.status()));
return result;
});
} catch (org.springframework.dao.DataIntegrityViolationException race) {
if (!DatabaseErrors.isConstraint(race, "ux_idempotency_scope_key")) {
throw race;
}
var winner = keys.findByActorIdAndOperationAndKey(actorId, OPERATION, key)
.orElseThrow(() -> race);
return replay(winner, hash);
}
}
private CreateOrderResult replay(IdempotencyRecord record, String hash) {
if (!record.requestHash().equals(hash)) throw new IdempotencyConflict();
return new CreateOrderResult(record.orderId(), record.status());
}
}
// Database migration:
// unique (actor_id, operation, idempotency_key)
The application performs indexed O(log n) idempotency lookups plus one bounded transaction. Storage grows with retained keys, so production design also needs an expiry policy longer than the advertised retry window.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Answer a similar question in a different context.