ballerina-guides/managing-database-transactions

Name: managing-database-transactions

Owner: ballerina-guides

Description: null

Created: 2018-02-16 13:48:42.0

Updated: 2018-04-30 20:24:06.0

Pushed: 2018-04-30 20:24:06.0

Homepage: null

Size: 98

Language: Ballerina

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Build Status

Database Transactions

A transaction is a small unit of a program that must maintain Atomicity, Consistency, Isolation, and Durability ? commonly known as ACID properties ? in order to ensure accuracy, completeness, and data integrity.

In this guide, you will learn about managing database transactions using Ballerina.

The following are the sections available in this guide.

What you?ll build

To understanding how you can manage database transactions using Ballerina, let?s consider a real-world use case of a simple banking application. This banking application allows users to,

Transferring money from one account to another account involves both operations withdrawal from the transferor and deposit to the transferee.

Let's assume the transaction fails during the deposit operation. Now the withdrawal operation carried out prior to deposit operation also needs to be rolled-back. Otherwise, we will end up in a state where transferor loses money. Therefore, to ensure the atomicity (all or nothing property), we need to perform the money transfer operation as a transaction.

This example explains three different scenarios where one user tries to transfer money from his/her account to another user's account. The first scenario shows a successful transaction whereas the other two fail due to different reasons.

Prerequisites
Optional requirements
Implementation

If you want to skip the basics, you can download the git repo and directly move to the “Testing” section by skipping “Implementation” section.

Create the project structure

Ballerina is a complete programming language that supports custom project structures. Use the following package structure for this guide.

ging-database-transactions
 guide
  ??? banking_application
       ??? account_manager.bal
       ??? application.bal
       ??? tests
            ??? account_manager_test.bal              
Implementation

Let's get started with the implementation of the transferMoney function of account_manager. This function explains how we can use transactions in Ballerina. It comprises of two different operations, withdrawal and deposit. To ensure that the transferring operation happens as a whole, it needs to reside in a database transaction block. Transactions guarantee the 'ACID' properties. So if any of the withdrawal or deposit fails, the transaction will be aborted and all the operations carried out in the same transaction will be rolled back. The transaction will be successful only when both, withdrawal from the transferor and deposit to the transferee are successful.

The below code segment shows the implementation of function transferMoney. Inline comments added for better understanding.

transferMoney function
unction to transfer money from one account to another.
ic function transferMoney(int fromAcc, int toAcc, int amount) returns (boolean) {
boolean isSuccessful;
log:printInfo("Initiating transaction");
// Transaction block - Ensures the 'ACID' properties.
// Withdraw and deposit should happen as a transaction when transferring money
// from one account to another.
// Here, the reason for switching off the 'retries' option is, in failing scenarios
// almost all the time transaction fails due to erroneous operations by the users.
transaction with retries = 0, oncommit = commitFunc, onabort = abortFunc {
    // Withdraw money from transferor's account.
    match withdrawMoney(fromAcc, amount) {
        error withdrawError => {
            log:printError("Error while withdrawing the money: " +
                withdrawError.message);
            // Abort transaction if withdrawal fails.
            log:printError("Failed to transfer money from account ID " + fromAcc +
                " to account ID " + toAcc);
            abort;
        }
        () => {
            match depositMoney(toAcc, amount) {
                error depositError => {
                    log:printError("Error while depositing the money: " +
                        depositError.message);
                    // Abort transaction if deposit fails.
                    log:printError("Failed to transfer money from account ID " +
                        fromAcc + " to account ID " + toAcc);
                    abort;
                }
                () => isSuccessful = true;
            }
        }
    }
    // If transaction successful.
    log:printInfo("Successfully transferred $" + amount + " from account ID " +
        fromAcc + " to account ID " + toAcc);
}
return isSuccessful;


rinted oncommit
tion commitFunc(string transactionId) {
log:printInfo("Transaction: " + transactionId + " aborted");


rinted onabort
tion abortFunc(string transactionId) {
log:printInfo("Transaction: " + transactionId + " committed");

Let's now look at the implementation of the account_manager, which includes the account management related logic. It consists of functions to create an account, verify an account, check account balance, withdraw money from an account, deposit money to an account, and transfer money from one account to another. Skeleton of the account_manager.bal file attached below.

account_manager.bal
mports

mysql:Client' endpoint.
oint mysql:Client bankDB {
host: config:getAsString("DATABASE_HOST", default = "localhost"),
port: config:getAsInt("DATABASE_PORT", default = 3306),
name: config:getAsString("DATABASE_NAME", default = "bankDB"),
username: config:getAsString("DATABASE_USERNAME", default = "root"),
password: config:getAsString("DATABASE_PASSWORD", default = ""),
dbOptions: { useSSL: false }


unction to add users to 'ACCOUNT' table of 'bankDB' database
ic function createAccount(string name) returns (int) {
// Implemetation
// Return the primary key, which will be the account number of the account


unction to verify an account whether it exists or not
ic function verifyAccount(int accId) returns (boolean) {
// Implementation
// Return a boolean, which is true if account exists; false otherwise


unction to check balance in an account
ic function checkBalance(int accId) returns (int|error) {
// Implementation
// Return the balance or error


unction to deposit money to an account
ic function depositMoney(int accId, int amount) returns (error|()) {
// Implementation
// Return error or ()


unction to withdraw money from an account
ic function withdrawMoney(int accId, int amount) returns (error|()) {
// Implementation
// Return error or ()


unction to transfer money from one account to another
ic function transferMoney(int fromAcc, int toAcc, int amount) returns (boolean) {
// Implementation
// Return a boolean, which is true if transaction is successful; false otherwise


rinted oncommit
tion commitFunc(string transactionId) {
log:printInfo("Transaction: " + transactionId + " aborted");


rinted onabort
tion abortFunc(string transactionId) {
log:printInfo("Transaction: " + transactionId + " committed");

To check the complete implementation of the above, refer to the account_manager.bal.

Let's next focus on the implementation of application.bal file, which includes the main function. It consists of three possible scenarios to check the transfer money operation of our banking application to explain the database transaction management using Ballerina. Skeleton of application.bal file attached below.

application.bal
mports

tion main (string[] args) {
// Create two new accounts
int accIdUser1 = createAccount("Alice");
int accIdUser2 = createAccount("Bob");

// Deposit money to both new accounts
_ = depositMoney(accIdUser1, 500);
_ = depositMoney(accIdUser2, 1000);

// Scenario 1 - Transaction expected to be successful
_ = transferMoney(accIdUser1, accIdUser2, 300);

// Scenario 2 - Transaction expected to fail due to insufficient ballance
// 'accIdUser1' now only has a balance of 200
_ = transferMoney(accIdUser1, accIdUser2, 500);

// Scenario 3 - Transaction expected to fail due to invalid recipient account ID
// Account ID 1234 does not exist
_ = transferMoney(accIdUser2, 1234, 500);

// Check the balance in Bob's account
_ = checkBalance(accIdUser2);

To check the complete implementation of the above, refer to the application.bal.

Testing
Before you begin

NOTE : You can find the SQL script here.

 ballerina run banking_application
Response you'll get

We created two user accounts for users 'Alice' and 'Bob'. Then initially we deposited $500 to Alice's account and $1000 to Bob's account. Later we had three different scenarios to check the money transfer operation, which includes a database transaction.

Let's now look at some important log statements we will get as the response for these three scenarios.

-------------------------------- Scenario 1---------------------------------------- 
  [banking_application] - Transfer $300 from Alice's account to Bob's account 
  [banking_application] - Expected: Transaction to be successful 
  [banking_application] - Initiating transaction 
  [banking_application] - Transfering money from account ID 1 to account ID 2 
  [banking_application] - Withdrawing money from account ID: 1 
  [banking_application] - $300 has been withdrawn from account ID 1 
  [banking_application] - Depositing money to account ID: 2 
  [banking_application] - $300 has been deposited to account ID 2 
  [banking_application] - Transaction committed 
  [banking_application] - Successfully transferred $300 from account 1 to account 2 
-------------------------------- Scenario 2---------------------------------------- 
  [banking_application] - Try to transfer $500 from Alice's acc to Bob's acc 
  [banking_application] - Expected: Transaction to fail as Alice now only has $200
  [banking_application] - Initiating transaction 
  [banking_application] - Transfering money from account ID 1 to account ID 2 
  [banking_application] - Withdrawing money from account ID: 1 
  [banking_application] - Checking balance for account ID: 1 
  [banking_application] - Available balance in account ID 1: 200 
R [banking_application] - Error while withdrawing the money: Not enough balance 
-------------------------------- Scenario 3---------------------------------------- 
  [banking_application] - Try to transfer $500 from Bob's acc to a non-existing acc
  [banking_application] - Expected: Transaction to fail as recipient ID is invalid 
  [banking_application] - Initiating transaction 
  [banking_application] - Transfering money from account ID 2 to account ID 1234 
  [banking_application] - Withdrawing money from account ID: 2 
  [banking_application] - $500 has been withdrawn from account ID 2 
  [banking_application] - Depositing money to account ID: 1234 
  [banking_application] - Verifying whether account ID 1234 exists 
R [banking_application] - Error while depositing the money: Account does not exist 
  [banking_application] - Check balance for Bob's account 
  [banking_application] - Available balance in account ID 2: 1300 
  [banking_application] - You should see $1300 balance in Bob's account (NOT $800) 
  [banking_application] - Explanation: 
 trying to transfer $500 from Bob's account to account ID 1234, initially $500
drew from Bob's account. But then the deposit operation failed due to an invalid
pient; Hence, the TX failed and withdraw operation rollbacked as it is in same TX
Writing unit tests

In Ballerina, the unit test cases should be in the same package inside a folder named as 'tests'. When writing the test functions the below convention should be followed.

This guide contains unit tests for each method available in the account_manager.

To run the unit tests, navigate to managing-database-transactions/guide and run the following command.

 ballerina test

To check the implementation of the test file, refer to the account_manager_test.bal.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.