I wrote two microservices in Springboot and tried to follow the best practices. I would appreciate any suggestions about any improvement that I can make on the whole codebase. What are the parts of the code that I can improve on the whole project?
Project Link: https://github.com/forhadmethun/mbank
Technology used:
Java 11+
SpringBoot
JPA / Hibernate
MyBatis
Gradle
Postgres
RabbitMQ
JUnit
Thank you very much.
Directory Structure
.
├── main
│ ├── java
│ │ └── com
│ │ └── forhadmethun
│ │ └── accountservice
│ │ ├── config
│ │ │ └── RabbitMQConfiguration.java
│ │ ├── controller
│ │ │ ├── AccountController.java
│ │ │ ├── ExceptionController.java
│ │ │ ├── HealthController.java
│ │ │ └── TransactionController.java
│ │ ├── db
│ │ │ ├── entity
│ │ │ │ ├── Account.java
│ │ │ │ ├── Balance.java
│ │ │ │ ├── Customer.java
│ │ │ │ └── Transaction.java
│ │ │ ├── repository
│ │ │ │ ├── AccountCommandRepository.java
│ │ │ │ ├── AccountQueryRepository.java
│ │ │ │ ├── BalanceCommandRepository.java
│ │ │ │ ├── BalanceQueryRepository.java
│ │ │ │ ├── CustomerRepository.java
│ │ │ │ ├── TransactionQueryRepository.java
│ │ │ │ └── TransactionRepository.java
│ │ │ └── services
│ │ │ ├── AccountService.java
│ │ │ ├── BalanceService.java
│ │ │ ├── bean
│ │ │ │ ├── AccountServiceBean.java
│ │ │ │ ├── BalanceServiceBean.java
│ │ │ │ ├── CustomerServiceBean.java
│ │ │ │ ├── MessageQueueBean.java
│ │ │ │ └── TransactionServiceBean.java
│ │ │ ├── CustomerService.java
│ │ │ ├── MessageQueueService.java
│ │ │ └── TransactionService.java
│ │ ├── MbankAccountServiceApplication.java
│ │ └── utility
│ │ ├── constant
│ │ │ └── PersistenceConstant.java
│ │ ├── CurrencyUtil.java
│ │ ├── dto
│ │ │ ├── mapper
│ │ │ │ ├── AccountMapper.java
│ │ │ │ ├── BalanceMapper.java
│ │ │ │ ├── CustomerMapper.java
│ │ │ │ └── TransactionMapper.java
│ │ │ └── model
│ │ │ ├── AccountDto.java
│ │ │ ├── BalanceDto.java
│ │ │ ├── CustomerDto.java
│ │ │ ├── DirectionOfTransaction.java
│ │ │ ├── mq
│ │ │ │ └── BalanceMQDto.java
│ │ │ └── TransactionDto.java
│ │ ├── exception
│ │ │ ├── PersistenceException.java
│ │ │ └── RequestException.java
│ │ ├── io
│ │ │ ├── AccountOperationResponse.java
│ │ │ ├── ErrorResponse.java
│ │ │ └── mq
│ │ │ ├── AccountCreationInfo.java
│ │ │ └── TransactionCreationInfo.java
│ │ ├── TransactionUtil.java
│ │ └── validation
│ │ ├── AccountServiceValidation.java
│ │ ├── ValidationImpl.java
│ │ ├── Validation.java
│ │ └── ValidationResult.java
│ └── resources
│ ├── application.properties
│ ├── db
│ │ └── migration
│ │ └── V1__create_table.sql
│ ├── static
│ └── templates
└── test
└── java
└── com
└── forhadmethun
└── accountservice
├── controller
│ ├── AccountControllerTest.java
│ └── TransactionControllerTest.java
└── MbankAccountServiceApplicationTests.java
Some of the Code(Please see the Github repo for all the files)
AccountController.java
package com.forhadmethun.accountservice.controller;
/**
* @author Md Forhad Hossain
* @since 01/10/20
*/
import com.forhadmethun.accountservice.db.services.AccountService;
import com.forhadmethun.accountservice.db.services.CustomerService;
import com.forhadmethun.accountservice.utility.CurrencyUtil;
import com.forhadmethun.accountservice.utility.dto.model.CustomerDto;
import com.forhadmethun.accountservice.utility.exception.PersistenceException;
import com.forhadmethun.accountservice.utility.exception.RequestException;
import com.forhadmethun.accountservice.utility.io.AccountOperationResponse;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@AllArgsConstructor
public class AccountController {
private final CustomerService customerService;
private final AccountService accountService;
@PostMapping("/accounts")
public ResponseEntity<AccountOperationResponse> createAccount(
@RequestBody CustomerDto customerDto
) throws RequestException {
CurrencyUtil.checkCurrencyValidity(customerDto.getCurrencies());
AccountOperationResponse accountCreationResponse =
customerService.createCustomer(customerDto);
return new ResponseEntity<>(accountCreationResponse, HttpStatus.OK);
}
@GetMapping("/accounts/{accountId}")
public ResponseEntity<AccountOperationResponse> getAccount(
@PathVariable Long accountId
) throws PersistenceException {
AccountOperationResponse accountOperationResponse =
accountService.findByAccountId(accountId);
return new ResponseEntity<>(accountOperationResponse, HttpStatus.OK);
}
}
AccountServiceBean.java
package com.forhadmethun.accountservice.db.services.bean;
/**
* @author Md Forhad Hossain
* @since 01/10/20
*/
import com.forhadmethun.accountservice.db.entity.Account;
import com.forhadmethun.accountservice.db.entity.Balance;
import com.forhadmethun.accountservice.db.repository.AccountQueryRepository;
import com.forhadmethun.accountservice.db.repository.AccountCommandRepository;
import com.forhadmethun.accountservice.db.repository.BalanceQueryRepository;
import com.forhadmethun.accountservice.db.services.AccountService;
import com.forhadmethun.accountservice.db.services.BalanceService;
import com.forhadmethun.accountservice.utility.constant.PersistenceConstant;
import com.forhadmethun.accountservice.utility.dto.mapper.BalanceMapper;
import com.forhadmethun.accountservice.utility.dto.model.AccountDto;
import com.forhadmethun.accountservice.utility.exception.PersistenceException;
import com.forhadmethun.accountservice.utility.io.AccountOperationResponse;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@AllArgsConstructor
@Service
public class AccountServiceBean implements AccountService {
private final AccountCommandRepository accountCommandRepository;
private final AccountQueryRepository accountQueryRepository;
private final BalanceService balanceService;
@Override
public Account createAccount(Account account) {
return accountCommandRepository.save(account);
}
@Override
public AccountOperationResponse findByAccountId(Long accountId) throws PersistenceException {
AccountDto account = accountQueryRepository.findByAccountId(accountId);
if (account == null)
throw new PersistenceException(PersistenceConstant.ACCOUNT_NOT_FOUND);
List<Balance> balances = balanceService.findByAccountId(accountId);
AccountOperationResponse accountOperationResponse =
AccountOperationResponse.createAccountCreationResponse(
account.getAccountId(),
account.getCustomerId(),
BalanceMapper.toBalanceDtoList(balances)
);
return accountOperationResponse;
}
}
AccountControllerTest.java
package com.forhadmethun.accountservice.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.forhadmethun.accountservice.db.services.CustomerService;
import com.forhadmethun.accountservice.utility.dto.model.CustomerDto;
import com.forhadmethun.accountservice.utility.io.AccountOperationResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.math.BigDecimal;
import java.util.Arrays;
import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class AccountControllerTest {
@Autowired
CustomerService customerService;
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
void createAccount() throws Exception {
MvcResult mockMvcResult = mockMvc.perform(MockMvcRequestBuilders
.post("/accounts", 42L)
.contentType("application/json")
.content(objectMapper.writeValueAsString(
CustomerDto.builder()
.customerId(1L)
.country("Bangladesh")
.currencies(Arrays.asList("EUR"))
.build()
)))
.andExpect(status().isOk())
.andReturn();
String contentAsString = mockMvcResult.getResponse().getContentAsString();
AccountOperationResponse response = objectMapper.readValue(contentAsString, AccountOperationResponse.class);
assertEquals(1L, response.getCustomerId());
assertEquals(1, response.getBalances().size());
assertEquals("EUR", response.getBalances().get(0).getCurrency());
assertEquals(BigDecimal.ZERO, response.getBalances().get(0).getBalance());
}
@Test
void getAccount() throws Exception {
AccountOperationResponse createdAccountObject =
customerService.createCustomer(
CustomerDto.builder()
.customerId(36L)
.country("Bangladesh")
.currencies(Arrays.asList("EUR"))
.build()
);
MvcResult mockMvcResult =
mockMvc.perform(get("/accounts/" + createdAccountObject.getAccountId()))
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.balances[0].currency", is("EUR"))
).andReturn();
String contentAsString = mockMvcResult.getResponse().getContentAsString();
AccountOperationResponse response = objectMapper.readValue(contentAsString, AccountOperationResponse.class);
assertEquals(response.getAccountId(), createdAccountObject.getAccountId());
assertEquals(response.getCustomerId(), createdAccountObject.getCustomerId());
assertEquals(response.getBalances().size(), createdAccountObject.getBalances().size());
assertEquals("EUR", response.getBalances().get(0).getCurrency());
}
}