Я постараюсь сделать это простым способом вместо усложнения вещей.
Я сосредоточусь на реальной проблеме, а не на красоте кода.
Мой подход, который я протестировал, был бы следующим:
Я создал основной класс, в котором два CompletableFuture имитируют два одновременных вызова для одного и того же clientId.
//Simulate lines of db debts per user
static List<Debt> debts = new ArrayList<>();
static Map<String, Object> locks = new HashMap<String, Object>();
public static void main(String[] args) {
String clientId = "1";
//Simulate previous insert line in db per clientId
debts.add(new Debt(clientId,50));
//In a operation, put in a map the clientId to lock this id
locks.put(clientId, new Object());
final ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletableFuture.runAsync(() -> {
try {
operation(clientId, 50);
} catch (Exception e) {
}
}, executorService);
CompletableFuture.runAsync(() -> {
try {
operation(clientId, 50);
} catch (Exception e) {
}
}, executorService);
executorService.shutdown();
}
Метод операции является ключевым. Я синхронизировал карту по clientId , что означает, что для другого clientId он не будет заблокирован, для каждого clientId он пропустит поток одновременно.
private static void operation(String clientId, Integer amount) {
System.out.println("Entra en operacion");
synchronized(locks.get(clientId)) {
if(additionalDebtAllowed(clientId, 50)) {
insertDebt(clientId, 50);
}
}
}
Следующие методы имитируют вставки, поиск в БД и удаленный поиск, но я думаю, что концепция понятна, я мог бы сделать это с репозиториями, но это не главное.
private static boolean additionalDebtAllowed(String clientId, Integer amount) {
List<Debt> debts = debtsPerClient(clientId);
int sumDebts = debts.stream().mapToInt(d -> d.getAmount()).sum();
int limit = limitDebtPerClient(clientId);
if(sumDebts + amount <= limit) {
System.out.println("Debt accepted");
return true;
}
System.out.println("Debt denied");
return false;
}
//Simulate insert in db
private static void insertDebt(String clientId, Integer amount) {
debts.add(new Debt(clientId, amount));
}
//Simulate search in db
private static List<Debt> debtsPerClient(String clientId) {
return debts;
}
//Simulate rest petition limit debt
private static Integer limitDebtPerClient(String clientId) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
}
Вы можете протестировать его с другим clientId и другим CompletableFuture, и вы увидите, что он работает для каждого клиента в отдельности правильным образом.
Надеюсь, это поможет вам.