mercredi 3 juin 2020

How to refactor this REST API Spring Boot app?

I'm working on a simple financial transaction app in Spring Boot. And I want to know if I could improve it. There are 4 types of transactions and I have 4 different endpoints and 4 request classes. I want to know if I could use a design pattern or something with these 4 request classes. This is the code:

Controller:

@RestController
@AllArgsConstructor
public class TransactionController {

    private final TransactionService transactionService;

    @PostMapping("/ibantoiban")
    public ResponseEntity<String> sendMoneyIbanToIban(@Valid @RequestBody IbanToIbanRequest ibanToIbanRequest) {
        transactionService.sendMoneyIbanToIban(ibanToIbanRequest);

        return ResponseEntity.ok("Transaction Completed Successfully!");
    }

    @PostMapping("/ibantowallet")
    public ResponseEntity<String> sendMoneyIbanToWallet(@Valid @RequestBody IbanToWalletRequest ibanToWalletRequest) {
        transactionService.sendMoneyIbanToWallet(ibanToWalletRequest);

        return ResponseEntity.ok("Transaction Completed Successfully!");
    }

    @PostMapping("/wallettoiban")
    public ResponseEntity<String> sendMoneyWalletToIban(@Valid @RequestBody WalletToIbanRequest walletToIbanRequest) {
        transactionService.sendMoneyWalletToIban(walletToIbanRequest);

        return ResponseEntity.ok("Transaction Completed Successfully!");
    }

    @PostMapping("/wallettowallet")
    public ResponseEntity<String> sendMoneyWalletToWallet(@Valid @RequestBody WalletToWalletRequest walletToWalletRequest) {
        transactionService.sendMoneyWalletToWallet(walletToWalletRequest);

        return ResponseEntity.ok("Transaction Completed Successfully!");
    }

    @PostMapping(path = "/pdfreport", produces = MediaType.APPLICATION_PDF_VALUE)
    public ResponseEntity<InputStreamResource> getStatement(@Valid @RequestBody AccountStatementRequest accountStatementRequest) {
        AccountStatement accountStatement = transactionService.getStatement(accountStatementRequest);

        ByteArrayInputStream bis = GeneratePdfReport.pdfReport(accountStatement);

        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "inline; filename=transactions.pdf");
        return ResponseEntity
                .ok()
                .headers(headers)
                .contentType(MediaType.APPLICATION_PDF)
                .body(new InputStreamResource(bis));
    }
}

Service:

@Service
@AllArgsConstructor
public class TransactionServiceImpl implements TransactionService {


    private final AccountRepository accountRepository;
    private final TransactionRepository transactionRepository;

    @Override
    @Transactional
    public Transaction sendMoneyIbanToIban(IbanToIbanRequest ibanToIbanRequest) {
        String fromIban = ibanToIbanRequest.getFromIban();
        String toIban = ibanToIbanRequest.getToIban();
        BigDecimal amount = new BigDecimal(ibanToIbanRequest.getAmount());
        Account fromAccount = accountRepository.findByIban(fromIban);
        Account toAccount = accountRepository.findByIban(toIban);
        if (fromAccount.getCurrentBalance().compareTo(amount) == 1) {
            String description = ibanToIbanRequest.getDescription();
            return sendMoney(fromAccount, toAccount, amount, description,
                    IBAN_TO_IBAN_TRANSACTION_TYPE);
        }
        return null;
    }

    @Override
    @Transactional
    public Transaction sendMoneyIbanToWallet(IbanToWalletRequest ibanToWalletRequest) {
        String fromIban = ibanToWalletRequest.getFromIban();
        String toWallet = ibanToWalletRequest.getToWalletNumber();
        BigDecimal amount = new BigDecimal(ibanToWalletRequest.getAmount());
        Account fromAccount = accountRepository.findByIban(fromIban);
        Account toAccount = accountRepository.findByAccountNumber(toWallet);
        if (fromAccount.getCurrentBalance().compareTo(amount) == 1) {
            String description = ibanToWalletRequest.getDescription();
            return sendMoney(fromAccount, toAccount, amount, description,
                    IBAN_TO_WALLET_TRANSACTION_TYPE);
        }
        return null;
    }

    @Override
    @Transactional
    public Transaction sendMoneyWalletToIban(WalletToIbanRequest walletToIbanRequest) {
        String fromWallet = walletToIbanRequest.getFromWalletNumber();
        String toIban = walletToIbanRequest.getToIban();
        BigDecimal amount = new BigDecimal(walletToIbanRequest.getAmount());
        Account fromAccount = accountRepository.findByAccountNumber(fromWallet);
        Account toAccount = accountRepository.findByIban(toIban);
        if (fromAccount.getCurrentBalance().compareTo(amount) == 1) {
            String description = walletToIbanRequest.getDescription();
            return sendMoney(fromAccount, toAccount, amount, description,
                    WALLET_TO_IBAN_TRANSACTION_TYPE);
        }
        return null;
    }

    @Override
    @Transactional
    public Transaction sendMoneyWalletToWallet(WalletToWalletRequest walletToWalletRequest) {
        String fromWallet = walletToWalletRequest.getFromWalletNumber();
        String toWallet = walletToWalletRequest.getToWalletNumber();
        BigDecimal amount = new BigDecimal(walletToWalletRequest.getAmount());
        Account fromAccount = accountRepository.findByAccountNumber(fromWallet);
        Account toAccount = accountRepository.findByAccountNumber(toWallet);
        if (fromAccount.getCurrentBalance().compareTo(amount) == 1) {
            String description = walletToWalletRequest.getDescription();
            return sendMoney(fromAccount, toAccount, amount, description,
                    WALLET_TO_WALLET_TRANSACTION_TYPE);
        }
        return null;
    }

    private Transaction sendMoney(
            Account fromAccount, Account toAccount, BigDecimal amount,
            String description, String transactionType) {

            fromAccount.setCurrentBalance(fromAccount.getCurrentBalance().subtract(amount));
            accountRepository.save(fromAccount);
            toAccount.setCurrentBalance(toAccount.getCurrentBalance().add(amount));
            accountRepository.save(toAccount);
            String cnpFrom = fromAccount.getCnp();
            String cnpTo = toAccount.getCnp();
            Transaction transactionFrom = transactionRepository.save(
                    Transaction.builder()
                            .transactionId(0L)
                            .transactionType(transactionType)
                            .cnp(cnpFrom)
                            .transactionAmount(amount)
                            .payment(true)
                            .description(description)
                            .build());

            transactionRepository.save(
                    Transaction.builder()
                            .transactionId(0L)
                            .transactionType(transactionType)
                            .cnp(cnpTo)
                            .transactionAmount(amount)
                            .description(description)
                            .build());

            return transactionFrom;
    }

    @Override
    public AccountStatement getStatement(AccountStatementRequest accountStatementRequest) {
        String cnp = accountStatementRequest.getCnp();
        Account account = accountRepository.findByCnp(cnp);
        return AccountStatement.builder()
                .name(account.getName())
                .cnp(account.getCnp())
                .iban(account.getIban())
                .currentBalance(account.getCurrentBalance())
                .transactionHistory(transactionRepository.findByCnp(cnp))
                .build();
    }
}

IbanToIbanRequest:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class IbanToIbanRequest {

    @NotBlank(message = "Please provide a valid IBAN")
    @Pattern(regexp = "[a-zA-Z]{2}\\d{2}[ ]\\d{4}[ ]\\d{4}[ ]\\d{4}[ ]\\d{4}[ ]\\d{2}|DE\\d{20}",
            message = "IBAN validation failed")
    private String fromIban;

    @NotBlank(message = "Please provide a valid IBAN")
    @Pattern(regexp = "[a-zA-Z]{2}\\d{2}[ ]\\d{4}[ ]\\d{4}[ ]\\d{4}[ ]\\d{4}[ ]\\d{2}|DE\\d{20}",
            message = "IBAN validation failed")
    private String toIban;

    @NotBlank(message = "Please provide an amount")
    private String amount;

    @NotBlank(message = "Please provide a description")
    private String description;
}

IbanToWalletRequest:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class IbanToWalletRequest {

    @NotBlank(message = "Please provide a valid IBAN")
    @Pattern(regexp = "[a-zA-Z]{2}\\d{2}[ ]\\d{4}[ ]\\d{4}[ ]\\d{4}[ ]\\d{4}[ ]\\d{2}|DE\\d{20}",
            message = "IBAN validation failed")
    private String fromIban;

    @NotBlank(message = "Please provide a valid wallet number")
    @Pattern(regexp = "\\d{26}",
            message = "Wallet number validation failed")
    private String toWalletNumber;

    @NotBlank(message = "Please provide an amount")
    private String amount;

    @NotBlank(message = "Please provide a description")
    private String description;
}

WalletToIbanRequest:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class WalletToIbanRequest {

    @NotBlank(message = "Please provide a valid wallet number")
    @Pattern(regexp = "\\d{26}",
            message = "Wallet number validation failed")
    private String fromWalletNumber;

    @NotBlank(message = "Please provide a valid IBAN")
    @Pattern(regexp = "[a-zA-Z]{2}\\d{2}[ ]\\d{4}[ ]\\d{4}[ ]\\d{4}[ ]\\d{4}[ ]\\d{2}|DE\\d{20}",
            message = "IBAN validation failed")
    private String toIban;

    @NotBlank(message = "Please provide an amount")
    private String amount;

    @NotBlank(message = "Please provide a description")
    private String description;
}

WalletToWalletRequest:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class WalletToWalletRequest {

    @NotBlank(message = "Please provide a valid wallet number")
    @Pattern(regexp = "\\d{26}",
            message = "Wallet number validation failed")
    private String fromWalletNumber;

    @NotBlank(message = "Please provide a valid wallet number")
    @Pattern(regexp = "\\d{26}",
            message = "Wallet number validation failed")
    private String toWalletNumber;

    @NotBlank(message = "Please provide an amount")
    private String amount;

    @NotBlank(message = "Please provide a description")
    private String description;
}

AccountStatementRequest:

@Getter
@Setter
@NoArgsConstructor
public class AccountStatementRequest {

    @NotBlank(message = "Please provide a valid CNP")
    @Pattern(regexp = "^[12](0[1-9]|[1-9][0-9])(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])([0-9]{6})$",
            message = "CNP validation failed")
    private String cnp;
}

So I want to ask you if I could refactor this code, especially in the service, to use a design pattern, or how can I improve the implementation. I don't want to add a new functionality, just to refactor the code and improve it. Thank you!

Aucun commentaire:

Enregistrer un commentaire