Skip to content

Batch Transactions

Sometimes situations arise when there is a need to make multiple changes to wallets. For example, we need to change the balance of many wallets at once. For example, the system administrator accrues a bonus for participating in some promotion. Previously, the code would look like this:

php
use Bavix\Wallet\Services\AtomicServiceInterface;

app(AtomicServiceInterface::class)->blocks($wallets, function () use ($amount, $wallets) {
    foreach ($wallets as $wallet) {
        $wallet->deposit($amount);
    }
});

The code is working and everything works correctly. But what happens under the hood? Nothing good. For 5 users it will look like this. exm1

Since the operations inside an atomic operation can depend on each other, we will not be able to combine insert queries into a batch. But there is an opportunity to reduce the number of update queries and improve application performance out of the blue.

After small things, the situation will look like this.

exm2

As you can see, things are getting better. Still, I would like to be able to tell the package that the changes are independent of each other. In this case, the package will be able to collapse all insert queries into a single query and insert in a batch.

Here new api handles can help us:

php
// For multiple transactions.
interface TransactionQueryHandlerInterface
{
    /**
     * @param non-empty-array<TransactionQuery> $objects
     * @return non-empty-array<string, Transaction>
     * @throws ExceptionInterface
     */
    public function apply(array $objects): array;
}

// For multiple transfers of funds.
interface TransferQueryHandlerInterface
{
    /**
     * @param non-empty-array<TransferQuery> $objects
     * @return non-empty-array<string, Transfer>
     * @throws ExceptionInterface
     */
    public function apply(array $objects): array;
}

Let's use the API handle.

php
use Bavix\Wallet\External\Api\TransactionQuery;
use Bavix\Wallet\External\Api\TransactionQueryHandlerInterface;

app(TransactionQueryHandlerInterface::class)->apply(
    array_map(
        static fn (Wallet $wallet) => TransactionQuery::createDeposit($wallet, $amount, null),
        $wallets
     )
);

And now look at the result and it is impressive. exm3

But it is worth noting that these are highly efficient api handles and they do not check the balance of the wallet before making changes. If you need it, then you have to do something like this.

php
use Bavix\Wallet\External\Api\TransactionQuery;
use Bavix\Wallet\External\Api\TransactionQueryHandlerInterface;
use Bavix\Wallet\Services\AtomicServiceInterface;
use Bavix\Wallet\Services\ConsistencyServiceInterface;

app(AtomicServiceInterface::class)->blocks($wallets, function () use ($wallets, $amount) {
    foreach ($wallets as $wallet) {
        app(ConsistencyServiceInterface::class)->checkPotential($wallet, $amount);
    }

    app(TransactionQueryHandlerInterface::class)->apply(
        array_map(
            static fn (Wallet $wallet) => TransactionQuery::createWithdraw($wallet, $amount, null),
            $wallets
        )
    );
});

In version 10.x, it became possible to create transactions with a given uuid (generate on the client side). The main thing is to keep uniqueness.

php
use Bavix\Wallet\External\Api\TransactionQuery;

// int version
TransactionQuery::createDeposit($wallet, $amount, null, uuid: '5f7820d1-1e82-4d03-9414-05d0c44da9a1');
TransactionQuery::createWithdraw($wallet, $amount, null, uuid: '6e87dbf2-7be7-48c2-b688-f46ba4e25786');

// float version
TransactionFloatQuery::createDeposit($wallet, $amountFloat, null, uuid: '5f7820d1-1e82-4d03-9414-05d0c44da9a1');
TransactionFloatQuery::createWithdraw($wallet, $amountFloat, null, uuid: '6e87dbf2-7be7-48c2-b688-f46ba4e25786');

It's simple!