I have made a logic that matches users whom have made requests to donors. I wrote the logic to accept partial donation. Lets say a user wants $5000 and there are two donors looking at give $3000 and $4000, it will take the $3000 and then $2000 from the second user to complete the receivers donation request.
The logic works a fine but the issue sometimes after matching 4k user and 3k user to our recipient it still accept other matches.
Heres my codebase:
async function fetchOpenRequestsAndTransactions(transactionRepository: TransactionRepository, currency: string) {
const [openRequests, availableTransactions] = await Promise.all([
transactionRepository.getOpenRequestsByOldest(currency),
transactionRepository.getAvailableTransactionsByOldest(currency),
]);
return [openRequests, availableTransactions];
}
function prepareUpdates(
updatesRequests: any[],
updatesTransactions: any[],
request: IRequest,
transaction: ITransaction,
isRequestMatchComplete: boolean,
isTxMatchComplete: boolean,
) {
updatesRequests.push({
id: request.id,
matchedAmount: request.matchedAmount,
isMatchComplete: isRequestMatchComplete,
lastUpdatedAt: request.updatedAt,
});
updatesTransactions.push({
id: transaction.id,
unMatchedAmount: transaction.unMatchedAmount,
isMatchComplete: isTxMatchComplete,
lastUpdatedAt: transaction.updatedAt,
});
}
async function handleDonationRequest(job, dbInstance) {
const { currency } = job.data;
// load dependencies
await dependencyInjectorLoader(dbInstance);
const transactionRepository = Container.get<TransactionRepository>(TransactionRepository);
const [openRequests, availableTransactions] = await fetchOpenRequestsAndTransactions(transactionRepository, currency);
const updatesRequests = [];
const updatesTransactions = [];
const matchesToCreate = [];
for (let i = 0; i < openRequests.length; i++) {
const request = openRequests[i] as IRequest;
console.log('Processing request: ', i, 'Request ID:', request.id, 'Transactions: ', availableTransactions.length);
let amountNeeded = request.amount - request.matchedAmount;
let isRequestMatchComplete = false;
for (const transaction of availableTransactions as ITransaction[]) {
if (amountNeeded <= 0 || isRequestMatchComplete) break;
let isTxMatchComplete = false;
if (transaction.unMatchedAmount > 0) {
const contribution = Math.min(amountNeeded, transaction.unMatchedAmount);
request.matchedAmount += contribution;
transaction.unMatchedAmount -= contribution;
amountNeeded -= contribution;
isRequestMatchComplete = amountNeeded === 0;
isTxMatchComplete = transaction.unMatchedAmount === 0;
prepareUpdates(
updatesRequests,
updatesTransactions,
request,
transaction,
isRequestMatchComplete,
isTxMatchComplete,
);
matchesToCreate.push({
amount: contribution,
currency: currency,
request: request.id,
transaction: transaction.id,
});
console.log(`Request ${request.id} received ${contribution} from transaction ${transaction.id}`);
}
}
if (isRequestMatchComplete) {
request.isMatchComplete = true;
}
}
if(!matchesToCreate.length) return;
await transactionRepository.startTxCall(async tx => {
await Promise.all([
transactionRepository.createMatches(tx, matchesToCreate),
transactionRepository.batchUpdateRequests(tx, updatesRequests),
transactionRepository.batchUpdateTransactions(tx, updatesTransactions),
]);
});
}
The batch update calls all implement optimistic locking with the updatedAt field.
It behaves like the checks get skipped sometimes. This mostly happens when I have multiple people testing it