Hello folks, up to now we have configured the service registry, auth enabled API gateway with discovery and routing, Now let’s focus on building real service implementations to achieve our Internet banking requirement. So here I’ll explain the core banking service which we are going to use in this internet banking concept.

Here this is a simple implementation just to demonstrate a core banking system in this article series. there are 1000x more functions and features inside the proper core banking system.

Core Banking Service Deliverables

Here in this microservices based application, core banking service is the main service which communicate with all the other services.

Microservices Architecture For Internet Banking Concept
Microservices Architecture For Internet Banking Concept

So what are the functions that we are going to develop in this concept core bank?

  • Handling bank accounts in multiple account types.
  • User base and user account mappings.
  • Handling transactions in fund transfers and utility payments.
  • Store all the transaction data on local MySQL storage.

Let’s begin building it,

Developing Core Banking Service

Just create another spring boot application using Spring Initializr with the following dependencies,

  • Spring Web
  • Lombok
  • Spring Data JPA
  • Flyway
  • MySQL
Creating Spring Boot Project Using Spring Initializr
Creating Spring Boot Project Using Spring Initializr

Database Models

In this core banking concept we are going to maintain user base and multiple bank accounts for specific user.

Just create UserEntity.java in model.entity package,

package com.javatodev.finance.model.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.List;

@Getter
@Setter
@Entity
@Table(name = "banking_core_user")
public class UserEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;
    private String lastName;
    private String email;
    private String identificationNumber;

    @OneToMany(mappedBy = "user",fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<BankAccountEntity> accounts;

}

Then the BankAccountEntity.java to store accounts under user.

package com.javatodev.finance.model.entity;

import com.javatodev.finance.model.AccountStatus;
import com.javatodev.finance.model.AccountType;
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.math.BigDecimal;

@Getter
@Setter
@Entity
@Table(name = "banking_core_account")
public class BankAccountEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String number;

    @Enumerated(EnumType.STRING)
    private AccountType type;

    @Enumerated(EnumType.STRING)
    private AccountStatus status;

    private BigDecimal availableBalance;

    private BigDecimal actualBalance;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private UserEntity user;

}

Account Type and AccountStatus to maintain status and types as enums,

package com.javatodev.finance.model;

public enum AccountType {
    SAVINGS_ACCOUNT, FIXED_DEPOSIT, LOAN_ACCOUNT
}
package com.javatodev.finance.model;

public enum AccountStatus {
    PENDING,ACTIVE,DORMANT,BLOCKED
}

Then UtilityAccountEntity.java to store third party utility payment numbers, Here this core banking service will send payments to defined number when user send a Utility payment with a reference from the internet banking side.

package com.javatodev.finance.model.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Getter
@Setter
@Entity
@Table(name = "banking_core_utility_account")
public class UtilityAccountEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String number;

    private String providerName;

}

Then we need to make a TransactionEntity.java table to keep all the banking core transactions in a single table.

package com.javatodev.finance.model.entity;

import com.javatodev.finance.model.TransactionType;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.math.BigDecimal;

@Builder
@Getter
@Setter
@Entity
@Table(name = "banking_core_transaction")
public class TransactionEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private BigDecimal amount;

    @Enumerated(EnumType.STRING)
    private TransactionType transactionType;

    private String referenceNumber;

    private String transactionId;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "account_id", referencedColumnName = "id")
    private BankAccountEntity account;

}

Repository Layer With JpaRepository

package com.javatodev.finance.repository;

import com.javatodev.finance.model.entity.BankAccountEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface BankAccountRepository extends JpaRepository<BankAccountEntity, Long> {
    Optional<BankAccountEntity> findByNumber(String accountNumber);
}
package com.javatodev.finance.repository;

import com.javatodev.finance.model.entity.TransactionEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface TransactionRepository extends JpaRepository<TransactionEntity, Long> {}
package com.javatodev.finance.repository;

import com.javatodev.finance.model.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<UserEntity, Long> {
    Optional<UserEntity> findByIdentificationNumber(String identificationNumber);
}
package com.javatodev.finance.repository;

import com.javatodev.finance.model.entity.UtilityAccountEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UtilityAccountRepository extends JpaRepository<UtilityAccountEntity, Long> {
    Optional<UtilityAccountEntity> findByProviderName(String provider);
}

Flyway Migrations For Database Versioning and Initial Data

Here I’m going to use Flyway in order to migrate the initial data set for banking core, and If you are really new to flyway refer to our article on database versionning and migrations with flyway in spring boot.

Here I’m going to create the base project database tables and insert initial data using flyway.

to do that, create a folder named as migration in src/resources in this project, and create following files with correct naming.

  • V1.0.20210427174638__create_base_table_structure.sql
-- banking_core_service.banking_core_user definition

CREATE TABLE `banking_core_user`
(
    `id`                    bigint(20) NOT NULL AUTO_INCREMENT,
    `email`                 varchar(255) DEFAULT NULL,
    `first_name`            varchar(255) DEFAULT NULL,
    `identification_number` varchar(255) DEFAULT NULL,
    `last_name`             varchar(255) DEFAULT NULL,
    PRIMARY KEY (`id`)
);

-- banking_core_service.banking_core_account definition

CREATE TABLE `banking_core_account`
(
    `id`                bigint(20) NOT NULL AUTO_INCREMENT,
    `actual_balance`    decimal(19, 2) DEFAULT NULL,
    `available_balance` decimal(19, 2) DEFAULT NULL,
    `number`            varchar(255)   DEFAULT NULL,
    `status`            varchar(255)   DEFAULT NULL,
    `type`              varchar(255)   DEFAULT NULL,
    `user_id`           bigint(20) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY                 `FKt5uqy9p0v3rp3yhlgvm7ep0ij` (`user_id`),
    CONSTRAINT `FKt5uqy9p0v3rp3yhlgvm7ep0ij` FOREIGN KEY (`user_id`) REFERENCES `banking_core_user` (`id`)
);

-- banking_core_service.banking_core_account definition

CREATE TABLE `banking_core_utility_account`
(
    `id`            bigint(20) NOT NULL AUTO_INCREMENT,
    `number`        varchar(255) DEFAULT NULL,
    `provider_name` varchar(255) DEFAULT NULL,
    PRIMARY KEY (`id`)
);
  • V1.0.20210427174721__temp_data.sql
INSERT INTO banking_core_user (id, email, first_name, identification_number, last_name)
VALUES ('1', 'sam@gmail.com', 'Sam', '808829932V', 'Silva');
INSERT INTO banking_core_service.banking_core_user (id, email, first_name, identification_number, last_name)
VALUES ('2', 'guru@gmail.com', 'Guru', '901830556V', 'Darmaraj');
INSERT INTO banking_core_service.banking_core_user (id, email, first_name, identification_number, last_name)
VALUES ('3', 'ragu@gmail.com', 'Ragu', '348829932V', 'Sivaraj');
INSERT INTO banking_core_service.banking_core_user (id, email, first_name, identification_number, last_name)
VALUES ('4', 'randor@gmail.com', 'Randor', '842829932V', 'Manoon');

INSERT INTO banking_core_account
    (actual_balance, available_balance, `number`, status, `type`, user_id)
VALUES (100000.00, 100000.00, 100015003000, 'ACTIVE', 'SAVINGS_ACCOUNT', '1'),
       (100000.00, 100000.00, 100015003001, 'ACTIVE', 'SAVINGS_ACCOUNT', '1'),
       (100000.00, 100000.00, 100015003002, 'ACTIVE', 'SAVINGS_ACCOUNT', '2'),
       (12000.00, 12000.00, 100015003003, 'ACTIVE', 'SAVINGS_ACCOUNT', '2'),
       (12000.00, 12000.00, 100015003004, 'ACTIVE', 'SAVINGS_ACCOUNT', '2'),
       (12000.00, 12000.00, 100015003005, 'ACTIVE', 'SAVINGS_ACCOUNT', '3'),
       (290000.00, 290000.00, 100015003006, 'ACTIVE', 'SAVINGS_ACCOUNT', '3'),
       (290000.00, 290000.00, 100015003007, 'ACTIVE', 'SAVINGS_ACCOUNT', '3'),
       (290000.00, 290000.00, 100015003008, 'ACTIVE', 'SAVINGS_ACCOUNT', '3'),
       (365023.00, 365023.00, 100015003009, 'ACTIVE', 'SAVINGS_ACCOUNT', '3'),
       (365023.00, 365023.00, 100015003010, 'ACTIVE', 'SAVINGS_ACCOUNT', '4'),
       (365023.00, 89456.00, 100015003011, 'ACTIVE', 'SAVINGS_ACCOUNT', '4'),
       (89456.00, 89456.00, 100015003012, 'ACTIVE', 'SAVINGS_ACCOUNT', '4'),
       (889000.33, 889000.33, 100015003013, 'ACTIVE', 'SAVINGS_ACCOUNT', '4');


INSERT INTO banking_core_utility_account (`number`, provider_name)
VALUES ('8203232565', 'VODAFONE');
INSERT INTO banking_core_utility_account (`number`, provider_name)
VALUES ('5464546545', 'VERIZON');
INSERT INTO banking_core_utility_account (`number`, provider_name)
VALUES ('6546456464', 'SINGTEL');
INSERT INTO banking_core_utility_account (`number`, provider_name)
VALUES ('7889987999', 'HUTCH');
INSERT INTO banking_core_utility_account (`number`, provider_name)
VALUES ('2132123132', 'AIRTEL');
INSERT INTO banking_core_utility_account (`number`, provider_name)
VALUES ('61645564646', 'GIO');
  • V1.0.20210429210839__create_transaction_table.sql
-- banking_core_service.banking_core_transaction definition

CREATE TABLE `banking_core_transaction`
(
    `id`               bigint(20) NOT NULL AUTO_INCREMENT,
    `amount`           decimal(19, 2) DEFAULT NULL,
    `transaction_type` varchar(30) NOT NULL,
    `reference_number` varchar(50) NOT NULL,
    `transaction_id`   varchar(50) NOT NULL,
    `account_id`       bigint(20) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY                `FKk9w2ogq595jbe8r2due7vv3xr` (`account_id`),
    CONSTRAINT `FKk9w2ogq595jbe8r2due7vv3xr` FOREIGN KEY (`account_id`) REFERENCES `banking_core_account` (`id`)
);

All done with the database and database related mappings inside the project, Let’s focus on writing DTO, mappers and Request Response classes to use with services.

DTO, Mappers, and REST Request Responses

Here I’m going to use separate DTO layer to bring data from this core service to public over the API. Basically here I’ll not going to expose my entity layer, instead of that I’ll define DTO layer to bring in and out data from this API.

package com.javatodev.finance.model.dto;

import lombok.Data;

import java.util.List;

@Data
public class User {

    private Long id;
    private String firstName;
    private String lastName;
    private String email;
    private String identificationNumber;
    private List<BankAccount> bankAccounts;

}
package com.javatodev.finance.model.dto;

import com.javatodev.finance.model.AccountStatus;
import com.javatodev.finance.model.AccountType;
import lombok.Data;

import java.math.BigDecimal;

@Data
public class BankAccount {

    private Long id;
    private String number;
    private AccountType type;
    private AccountStatus status;
    private BigDecimal availableBalance;
    private BigDecimal actualBalance;
    private User user;

}
package com.javatodev.finance.model.dto;

import lombok.Data;

@Data
public class UtilityAccount {
    private Long id;
    private String number;
    private String providerName;
}
package com.javatodev.finance.model.dto;

import lombok.Data;

import java.math.BigDecimal;

@Data
public class Transaction {

    private Long id;
    private BigDecimal amount;
    private BankAccount bankAccount;
    private String referenceNumber;

}

Now we need to have a common implementation to map things from Entity->DTO and vice versa, Let’s call it BaseMapper.java and extend that when we need on separate aspects.

package com.javatodev.finance.model.mapper;

import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public abstract class BaseMapper<E, D> {
    public abstract E convertToEntity(D dto, Object... args);

    public abstract D convertToDto(E entity, Object... args);

    public Collection<E> convertToEntity(Collection<D> dto, Object... args) {
        return dto.stream().map(d -> convertToEntity(d, args)).collect(Collectors.toList());
    }

    public Collection<D> convertToDto(Collection<E> entity, Object... args) {
        return entity.stream().map(e -> convertToDto(e, args)).collect(Collectors.toList());
    }

    public List<E> convertToEntityList(Collection<D> dto, Object... args) {
        return convertToEntity(dto, args).stream().collect(Collectors.toList());
    }

    public List<D> convertToDtoList(Collection<E> entity, Object... args) {
        return convertToDto(entity, args).stream().collect(Collectors.toList());
    }

    public Set<E> convertToEntitySet(Collection<D> dto, Object... args) {
        return convertToEntity(dto, args).stream().collect(Collectors.toSet());
    }

    public Set<D> convertToDtoSet(Collection<E> entity, Object... args) {
        return convertToDto(entity, args).stream().collect(Collectors.toSet());
    }
}

Then the implementation for UserEntity to User conversion,

package com.javatodev.finance.model.mapper;

import com.javatodev.finance.model.dto.User;
import com.javatodev.finance.model.entity.UserEntity;
import org.springframework.beans.BeanUtils;

public class UserMapper extends BaseMapper<UserEntity, User>{
    private BankAccountMapper bankAccountMapper = new BankAccountMapper();

    @Override
    public UserEntity convertToEntity(User dto, Object... args) {
        UserEntity entity = new UserEntity();
        if (dto != null) {
            BeanUtils.copyProperties(dto, entity, "accounts");
            entity.setAccounts(bankAccountMapper.convertToEntityList(dto.getBankAccounts()));
        }
        return entity;
    }

    @Override
    public User convertToDto(UserEntity entity, Object... args) {
        User dto = new User();
        if (entity != null) {
            BeanUtils.copyProperties(entity, dto, "accounts");
            dto.setBankAccounts(bankAccountMapper.convertToDtoList(entity.getAccounts()));
        }
        return dto;
    }
}
package com.javatodev.finance.model.mapper;

import com.javatodev.finance.model.dto.BankAccount;
import com.javatodev.finance.model.entity.BankAccountEntity;
import org.springframework.beans.BeanUtils;

public class BankAccountMapper extends BaseMapper<BankAccountEntity, BankAccount> {

    @Override
    public BankAccountEntity convertToEntity(BankAccount dto, Object... args) {
        BankAccountEntity entity = new BankAccountEntity();
        if (dto != null) {
            BeanUtils.copyProperties(dto, entity, "user");
        }
        return entity;
    }

    @Override
    public BankAccount convertToDto(BankAccountEntity entity, Object... args) {
        BankAccount dto = new BankAccount();
        if (entity != null) {
            BeanUtils.copyProperties(entity, dto, "user");
        }
        return dto;
    }
}
package com.javatodev.finance.model.mapper;

import com.javatodev.finance.model.dto.UtilityAccount;
import com.javatodev.finance.model.entity.UtilityAccountEntity;
import org.springframework.beans.BeanUtils;

public class UtilityAccountMapper extends BaseMapper<UtilityAccountEntity, UtilityAccount> {
    @Override
    public UtilityAccountEntity convertToEntity(UtilityAccount dto, Object... args) {
        UtilityAccountEntity entity = new UtilityAccountEntity();
        if (dto != null) {
            BeanUtils.copyProperties(dto, entity);
        }
        return entity;
    }

    @Override
    public UtilityAccount convertToDto(UtilityAccountEntity entity, Object... args) {
        UtilityAccount dto = new UtilityAccount();
        if (entity != null) {
            BeanUtils.copyProperties(entity, dto);
        }
        return dto;
    }
}

Then let’s add pending Request and Response classes that initialize API requests and responses.

package com.javatodev.finance.model.dto.request;

import lombok.Data;

import java.math.BigDecimal;

@Data
public class FundTransferRequest {
    private String fromAccount;
    private String toAccount;
    private BigDecimal amount;
}
package com.javatodev.finance.model.dto.request;

import lombok.Data;

import java.math.BigDecimal;

@Data
public class UtilityPaymentRequest {

    private Long providerId;
    private BigDecimal amount;
    private String referenceNumber;
    private String account;

}
package com.javatodev.finance.model.dto.response;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Builder
@Getter
@Setter
public class FundTransferResponse {

    private String message;
    private String transactionId;

}
package com.javatodev.finance.model.dto.response;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Builder
public class UtilityPaymentResponse {
    private String message;
    private String transactionId;
}

Done, now we have all the components which need to have inside this core banking system in order to access DB and read/write data from that. Let’s add service implementation and controller implementation to complete this API.

Service Implementation

Let’s start with UserService implementation which should have capability on reading users by identification and read all registered users as a paginated list.

Here I’m using the mapper classes I’ve written earlier to map entity into a DTO in service level and Repositories to read data from the DB.

package com.javatodev.finance.service;

import com.javatodev.finance.model.dto.User;
import com.javatodev.finance.model.entity.UserEntity;
import com.javatodev.finance.model.mapper.UserMapper;
import com.javatodev.finance.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class UserService {

    private UserMapper userMapper = new UserMapper();

    private final UserRepository userRepository;

    public User readUser(String identification) {
        UserEntity userEntity = userRepository.findByIdentificationNumber(identification).get();
        return userMapper.convertToDto(userEntity);
    }

    public List<User> readUsers(Pageable pageable) {
       return userMapper.convertToDtoList(userRepository.findAll(pageable).getContent());
    }
}
package com.javatodev.finance.service;

import com.javatodev.finance.model.dto.BankAccount;
import com.javatodev.finance.model.dto.UtilityAccount;
import com.javatodev.finance.model.entity.BankAccountEntity;
import com.javatodev.finance.model.entity.UtilityAccountEntity;
import com.javatodev.finance.model.mapper.BankAccountMapper;
import com.javatodev.finance.model.mapper.UtilityAccountMapper;
import com.javatodev.finance.repository.BankAccountRepository;
import com.javatodev.finance.repository.UtilityAccountRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class AccountService {

    private BankAccountMapper bankAccountMapper = new BankAccountMapper();
    private UtilityAccountMapper utilityAccountMapper = new UtilityAccountMapper();

    private final BankAccountRepository bankAccountRepository;
    private final UtilityAccountRepository utilityAccountRepository;

    public BankAccount readBankAccount(String accountNumber) {
        BankAccountEntity entity = bankAccountRepository.findByNumber(accountNumber).get();
        return bankAccountMapper.convertToDto(entity);
    }

    public UtilityAccount readUtilityAccount(String provider) {
        UtilityAccountEntity utilityAccountEntity = utilityAccountRepository.findByProviderName(provider).get();
        return utilityAccountMapper.convertToDto(utilityAccountEntity);
    }

    public UtilityAccount readUtilityAccount(Long id){
        return utilityAccountMapper.convertToDto(utilityAccountRepository.findById(id).get());
    }

}

As I written above code snippets both services are doing simple readings on user inputs and returning those from the service layer. Now let’s focus on writing the core transaction service.

In this service, I’ll manage all the transactions including fund transfer and utility payments. Additionally, there will be a unique identification UUID number as transaction ID returning from service for all the successful transactions.

package com.javatodev.finance.service;

import com.javatodev.finance.model.TransactionType;
import com.javatodev.finance.model.dto.BankAccount;
import com.javatodev.finance.model.dto.UtilityAccount;
import com.javatodev.finance.model.dto.request.FundTransferRequest;
import com.javatodev.finance.model.dto.request.UtilityPaymentRequest;
import com.javatodev.finance.model.dto.response.FundTransferResponse;
import com.javatodev.finance.model.dto.response.UtilityPaymentResponse;
import com.javatodev.finance.model.entity.BankAccountEntity;
import com.javatodev.finance.model.entity.TransactionEntity;
import com.javatodev.finance.repository.BankAccountRepository;
import com.javatodev.finance.repository.TransactionRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.math.BigDecimal;
import java.util.UUID;

@Service
@Transactional
@RequiredArgsConstructor
public class TransactionService {

    private final AccountService accountService;
    private final BankAccountRepository bankAccountRepository;
    private final TransactionRepository transactionRepository;

    public FundTransferResponse fundTransfer(FundTransferRequest fundTransferRequest) {

        BankAccount fromBankAccount = accountService.readBankAccount(fundTransferRequest.getFromAccount());
        BankAccount toBankAccount = accountService.readBankAccount(fundTransferRequest.getToAccount());

        //validating account balances
        validateBalance(fromBankAccount, fundTransferRequest.getAmount());

        String transactionId = internalFundTransfer(fromBankAccount, toBankAccount, fundTransferRequest.getAmount());
        return FundTransferResponse.builder().message("Transaction successfully completed").transactionId(transactionId).build();

    }

    public UtilityPaymentResponse utilPayment(UtilityPaymentRequest utilityPaymentRequest) {

        String transactionId = UUID.randomUUID().toString();

        BankAccount fromBankAccount = accountService.readBankAccount(utilityPaymentRequest.getAccount());

        //validating account balances
        validateBalance(fromBankAccount, utilityPaymentRequest.getAmount());

        UtilityAccount utilityAccount = accountService.readUtilityAccount(utilityPaymentRequest.getProviderId());

        BankAccountEntity fromAccount = bankAccountRepository.findByNumber(fromBankAccount.getNumber()).get();

        //we can call third party API to process UTIL payment from payment provider from here.

        fromAccount.setActualBalance(fromAccount.getActualBalance().subtract(utilityPaymentRequest.getAmount()));
        fromAccount.setAvailableBalance(fromAccount.getActualBalance().subtract(utilityPaymentRequest.getAmount()));

        transactionRepository.save(TransactionEntity.builder().transactionType(TransactionType.UTILITY_PAYMENT)
                .account(fromAccount)
                .transactionId(transactionId)
                .referenceNumber(utilityPaymentRequest.getReferenceNumber())
                .amount(utilityPaymentRequest.getAmount().negate()).build());

        return UtilityPaymentResponse.builder().message("Utility payment successfully completed")
                .transactionId(transactionId).build();

    }

    private void validateBalance(BankAccount bankAccount, BigDecimal amount) {
        if (bankAccount.getActualBalance().compareTo(BigDecimal.ZERO) < 0 || bankAccount.getActualBalance().compareTo(amount) < 0) {
            throw new RuntimeException();
        }
    }

    public String internalFundTransfer(BankAccount fromBankAccount, BankAccount toBankAccount, BigDecimal amount) {

        String transactionId = UUID.randomUUID().toString();

        BankAccountEntity fromBankAccountEntity = bankAccountRepository.findByNumber(fromBankAccount.getNumber()).get();
        BankAccountEntity toBankAccountEntity = bankAccountRepository.findByNumber(toBankAccount.getNumber()).get();

        fromBankAccountEntity.setActualBalance(fromBankAccountEntity.getActualBalance().subtract(amount));
        fromBankAccountEntity.setAvailableBalance(fromBankAccountEntity.getActualBalance().subtract(amount));
        bankAccountRepository.save(fromBankAccountEntity);

        transactionRepository.save(TransactionEntity.builder().transactionType(TransactionType.FUND_TRANSFER)
                .referenceNumber(toBankAccountEntity.getNumber())
                .transactionId(transactionId)
                .account(fromBankAccountEntity).amount(amount.negate()).build());

        toBankAccountEntity.setActualBalance(toBankAccountEntity.getActualBalance().add(amount));
        toBankAccountEntity.setAvailableBalance(toBankAccountEntity.getActualBalance().add(amount));
        bankAccountRepository.save(toBankAccountEntity);

        transactionRepository.save(TransactionEntity.builder().transactionType(TransactionType.FUND_TRANSFER)
                .referenceNumber(toBankAccountEntity.getNumber())
                .transactionId(transactionId)
                .account(toBankAccountEntity).amount(amount).build());

        return transactionId;

    }

}

Now we have completed implementing all the necessary service classes for our little requirement. So let’s finalize this API with adding controller layer.

Controller Layer For API Endpoints

package com.javatodev.finance.controller;

import com.javatodev.finance.model.dto.request.FundTransferRequest;
import com.javatodev.finance.model.dto.request.UtilityPaymentRequest;
import com.javatodev.finance.service.TransactionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/api/v1/transaction")
public class TransactionController {

    private final TransactionService transactionService;

    @PostMapping("/fund-transfer")
    public ResponseEntity fundTransfer(@RequestBody FundTransferRequest fundTransferRequest) {

        log.info("Fund transfer initiated in core bank from {}", fundTransferRequest.toString());
        return ResponseEntity.ok(transactionService.fundTransfer(fundTransferRequest));

    }

    @PostMapping("/util-payment")
    public ResponseEntity utilPayment(@RequestBody UtilityPaymentRequest utilityPaymentRequest) {

        log.info("Utility Payment initiated in core bank from {}", utilityPaymentRequest.toString());
        return ResponseEntity.ok(transactionService.utilPayment(utilityPaymentRequest));

    }

}
package com.javatodev.finance.controller;

import com.javatodev.finance.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping(value = "/api/v1/user")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping(value = "/{identification}")
    public ResponseEntity readUser(@PathVariable("identification") String identification) {
        return ResponseEntity.ok(userService.readUser(identification));
    }

    @GetMapping
    public ResponseEntity readUser(Pageable pageable) {
        return ResponseEntity.ok(userService.readUsers(pageable));
    }

}
package com.javatodev.finance.controller;

import com.javatodev.finance.service.AccountService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping(value = "/api/v1/account")
@RequiredArgsConstructor
public class AccountController {

    private final AccountService accountService;

    @GetMapping("/bank-account/{account_number}")
    public ResponseEntity getBankAccount(@PathVariable("account_number") String accountNumber) {
        log.info("Reading account by ID {}", accountNumber);
        return ResponseEntity.ok(accountService.readBankAccount(accountNumber));
    }

    @GetMapping("/util-account/{account_name}")
    public ResponseEntity getUtilityAccount(@PathVariable("account_name") String providerName) {
        log.info("Reading utitlity account by ID {}", providerName);
        return ResponseEntity.ok(accountService.readUtilityAccount(providerName));
    }

}

Now we are ready with our service implementation, But we have few more things to do in order to use this API as a service client inside this microservices application and finally consume these API endpoints over the API gateway.

First just go to the main class in this service API, and add @EnableEurekaClient along with following configurations in the application.yml to register with Eureka service registry.

package com.javatodev.finance;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@SpringBootApplication
public class CoreBankingServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(CoreBankingServiceApplication.class, args);
    }

}
spring:
  application:
    name: core-banking-service
  datasource:
    url: jdbc:mysql://localhost:3306/banking_core_service
    username: root
    password: password
  jpa:
    hibernate:
      ddl-auto: none

server:
  port: 8092

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8081/eureka

info:
  app:
    name: ${spring.application.name}

Finally, we can add our API gateway configurations to manage routes into the core banking API.

Just open application yml in API gateway project and add the following route definition into the spring.cloud.gateway.routes definition.

## CORE BANKING SERVICE
- id: core-banking-service
  uri: lb://core-banking-service
  predicates:
    - Path=/banking-core/**
  filters:
    - StripPrefix=1

All done now we have our working core banking API service registered and consumable over API gateway.

Just start all the components including service registry, API gateway and banking core service and test API over gateway to check your implementation.

Core Banking Service Is Up and Running with Eureka Service Registry
Core Banking Service Is Up and Running with Eureka Service Registry

Here are a few screenshots while test the core banking API using Postman, and you can access the same collection using the below link.

Run in Postman
Reading Utility Service provider data from banking core over API gateway
Reading Utility Service provider data from banking core over API gateway
Reading account details
Reading account details

Now we have fully implemented the basis Core banking solution for this series and let’s focus on completing internet banking core user service in our next article.

Conclusion

Thanks for reading our latest article on Microservices – Core Banking Service Implementation with practical usage.

If you are looking for spring boot practical application development tutorials, just check our article series.

You can get the source code for this tutorial from our GitHub repository.