Who goes there?

Exception Handling and Database Transaction in Laravel — Part 2

Farhat Shahir Zim
5 min readFeb 29, 2020

--

Hello good folks! let’s get a machine gun and handle sum bugs. Also, our Deadpool is killing bugs instead of bad guys.

In our previous blog, we had created these files.

Models:
-------
1. app/User.php
2. app/Wallet.php
Custom Exceptions:
------------------
3. app/Exceptions/CantTransferToSameWalletException.php
4. app/Exceptions/ReceiverWalletNotFoundException.php
5. app/Exceptions/SenderWalletNotFoundException.php
6. app/Exceptions/WalletDoesNotHaveEnoughBalanceException.php
New Migration:
--------------
7. database/migrations/2020_xx_xx_xxxxxx_create_wallets_table.php
Controller:
-----------
8. app/Http/Controllers/WalletController
Service:
--------
9. app/Services/WalletService.php

We have also completed all the files except 8 and 9. Now it is the most important part. We will write our WalletService.php and WalletController.php. These are the methods we will have in our service and controller

  • transfer (senderWalletId, receiverWalletId, transferAmount) method in WalletService.php.
  • hasEnoughBalanceToTransfer (walletId, transferAmount) method in WalletService.php.
  • transferBalance() method in WalletController.php

Let’s talk about the objectives of these methods:

hasEnoughBalanceToTransfer() method: [In service]
-------------------------------------------------
parameters:
-----------
1. int walletId,
2. float transferAmount
Operations:
-----------
[operation 00]
Check sender's wallet balance and if it is greater than or equal to transferAmount.
If it is not => return false
If it is => return true
--------------------------------------------------------------------transfer() method: [In service]
-------------------------------
parameters:
-----------
1. int senderWalletId,
2. int receiverWalletId,
3. float transferAmount
Operations:
-----------
[operation 00]
Begin DB transaction
[operation 01]
senderWalletId and receiverWalletId should not be same.
If it is => throw exception and go to [operation 08]
If not => continue
[operation 02]
Check if sender's wallet exists.
It doesn't exist => throw exception and go to [operation 08]
If exists => continue
[operation 03]
call hasEnoughBalanceToTransfer() method with required parameters and
If it returns false => throw exception and go to [operation 08]
If it returns true => continue
[operation 04]
Decrease transferAmount from sender's wallet.
[operation 05]
Check if receiver's wallet exists.
It doesn't exist => throw exception and go to [operation 08]
If exists => continue
[operation 06]
Increase receiver's wallet balance transferAmount.
[operation 07]
Commit DB transaction
[operation 08]
If any exception raise => Rollback the transaction
--------------------------------------------------------------------transferBalance () method: [In controller]
------------------------------------------
parameters:
-----------
1. Request object, where there will be these parameters:
a. int senderWalletId,
b. int receiverWalletId,
c. float transferAmount
Operations:
-----------
[operation 00]
call transfer() method from WalletServie.php and pass the required parameters
[operation 01]
If there is no exception then return json respose with
[
'success' => true,
'message' => message
]
[operation 02]
If exception occurs then return json respose with
[
'success' => false,
'message' => message
]

All the operations are explained and Let’s see our WalletService.php.

WalletService.php

And here is our WalletController.php.

WalletController.php

One good thing to remember here. We could initialize the WalletService inside of our transferBalance() method of WalletController. But In many cases, we might need our service in many methods. That’s why we initialized our WalletService inside the constructor.

Let’s break all the things down!

Now, you see that we have thrown our own custom exceptions in many different unholy situations here. This is one way. We are going to see another way within a few minutes. So what we have done so far?

  • We have created a Wallet.php (model), wallets table (migration), WalletController.php (controller), WalletService.php (service), and some custom exceptions.
  • Then inside of the WalletService.php, we have created 2 new methods. The transfer() method and hasEnoughBalanceToTransfe() method.
  • hasEnoughBalanceToTransfe() method will validate if the sender has enough balance or not and will return true or false accordingly.
  • transfer() method will transfer money from the sender’s wallet to the receiver’s wallet.
  • We have used database transactions, did all the exception handling and wrote business logic in WalletService’s transfer() method.
  • And used transferBalance() method in WalletController.php and returned JSON response accordingly.

Well, now let’s see the example code of DB transaction with exception handling.

try {
// begin transaction
DB::beginTransaction();

// write your dependent quires here
...

if ($anythingGoesWrong) {
throw new CustomException('Custom exception!');
}

// Happy ending :)
DB::commit();
return $goodStuffs;} catch (\Exception $e) {
// May day, rollback!!! rollback!!!
DB::rollback();
return $sorryDudeYouAreFucked;
}

Let’s see a “Different Approach” as I Promised

However, I said we have a different way to solve the same problem. Let’s see our new WalletService.php

Now Let’s Break Our New Service Down!

We have 2 methods here.
1. transferInADifferentWay()
2. balanceTransferValidation()

Our balanceTransferValidation() method will validate if all the wallets exist and the sender has enough balance to transfer. If anything goes missing here, this method is responsible for sending the exception messages according to the validation.

Our transferInADifferentWay() method will check the validation method’s response and if everything is ok this method will transfer the balance from the sender’s wallet to the receiver’s wallet. If not this will send a response accordingly.

And we haven’t used any custom Exception class here. We have managed our DB transaction in our transferInADifferentWay() method this way.

Let’s see the example code for this approach.

try {
// begin transaction
DB::beginTransaction();

// write your dependent quires here
...

if ($anythingGoesWrong) {
// May day, rollback!!! rollback!!!
DB::rollback();
return $sorryDudeYouAreFucked;
}

// Happy ending :)
DB::commit();
return $goodStuffs;} catch (\Exception $e) {
// May day, rollback!!! rollback!!!
DB::rollback();
return $sorryDudeYouAreFucked;
}

Please! please! please! remember to commit or rollback before leaving if you start transaction once otherwise YOUR DATABASE WILL BE LOCKED!!!!!!!! 😐

And also this way is more modular and clean. Now, where is our Deadpool? You have already forgotten him right. Well, he is now thinking which way is better to implement to keep his lovely Monday motivation alive. What do you guys think? Let me know in the comment section.

Ow, wait, the enemy of Deadpool right now thinking about concurrency attack! But don’t worry. We will cover it in our next blog using Laravel’s Job & Queue Feature. Till then stay healthy and try to keep your everyday motivation.

I pray to dear Lord to keep you guys away from every unexpected and unholy bug. May God bless your application in production.

Ameen!

--

--

Farhat Shahir Zim

[Software Engineer] — | Java | Python | Spring Boot | Django | C/C++ | Assembly | RISC-V | SystemC | Computer Architecture | Self taught programmer |