I'll try to do it in a simple way instead of complicating things.
I will focus on the real problem and not on the beauty of the code.
My approach, which I have tested, would be as follows:
I created a main class, in which two CompletableFuture simulate two simultaneous calls for the same 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();
}
The method operation is the key. I have synchronized the map by clientId, which means that for other clientId it will not be locked, for each clientId it will let pass a thread simultaneously.
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);
}
}
}
The following methods simulate insertions, db searches and remote searches, but I think the concept is understood, I could do it with repositories but that is not the focus.
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;
}
You can test it with another clientId and another CompletableFuture more and you will see that it works for each client separately in a correct way.
I hope it helps you.