본문 바로가기
BackEnd/Project

[Loan] Ch05. 대출 집행 등록 기능 구현

by 개발 Blog 2024. 9. 13.

공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.

지난 시간까지 대출 계약을 체결하는 기능을 구현했고, 이제는 대출금을 실제로 대출 신청자에게 전달하는 과정을 처리하는 "대출 집행" 기능을 구현한다. 대출 계약이 체결된 이후에 대출금이 지급되도록 하는 것이 핵심이며, 이를 통해 대출 프로세스가 완성된다.

1. 대출 집행 API 설계

대출 집행 기능은 InternalController에 새로운 API를 추가하여 구현된다. POST /internal/applications/{applicationId}/entries 경로로 대출 신청 ID와 함께 대출 금액 정보를 받아, 해당 대출에 대해 대출금이 집행되는 로직을 처리한다.

package com.example.loan.controller;


import com.example.loan.dto.ResponseDTO;
import com.example.loan.service.EntryService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import static com.example.loan.dto.EntryDTO.Request;
import static com.example.loan.dto.EntryDTO.Response;

@RequiredArgsConstructor
@RestController
@RequestMapping("/internal/applications")
public class InternalController extends AbstractController{
    private final EntryService entryService;

    @PostMapping("{applicationId}/entries")
    public ResponseDTO<Response> create(@PathVariable Long applicationId, @RequestBody Request request) {
        return ok(entryService.create(applicationId, request));
    }
}
  • 대출 신청 ID를 받아 해당 대출 신청이 실제로 계약이 체결된 상태인지 검증한다.
  • 계약이 체결되지 않았다면 예외를 발생시키고, 계약이 완료된 대출에 대해서만 집행 처리를 진행한다.
  • 대출 금액을 잔고에 반영하기 위해 BalanceService를 호출하여 대출 잔고를 관리한다.

2. DTO 설계

대출 집행과 잔고 관리를 위해서는 데이터를 주고받을 수 있는 DTO(Data Transfer Object)가 필요하다. 이번 기능에서는 EntryDTO와 BalanceDTO를 사용하여 대출 집행 요청 및 응답, 잔고 관리를 처리한다.

 

EntryDTO

대출 집행과 관련된 데이터를 주고받기 위해 사용된다.

package com.example.loan.dto;

import lombok.*;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

public class EntryDTO implements Serializable {

    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    @Getter
    @Setter
    public static class Request{
        private BigDecimal entryAmount;
    }

    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    @Getter
    @Setter
    public static class Response{

        private Long entryId;

        private Long applicationId;

        private BigDecimal entryAmount;

        private LocalDateTime createdAt;

        private LocalDateTime updatedAt;

    }
}
  • Request: 대출 금액을 전달하는 데 사용되며, 대출 신청 ID와 대출 금액(entryAmount)을 포함한다.
  • Response: 대출 집행 후 결과를 반환하며, 집행 ID(entryId), 신청 ID, 집행 금액, 생성 및 수정 시간을 포함한다.

BalanceDTO

대출 잔고 정보를 관리하기 위한 DTO이다.

package com.example.loan.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.math.BigDecimal;

public class BalanceDTO implements Serializable {


    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    @Getter
    @Service
    public static class Request{

        private Long applicationId;

        private BigDecimal entryAmount;
    }

    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    @Getter
    @Service
    public static class Response{

        private Long balanceId;

        private Long applicationId;

        private BigDecimal balance;
    }
}
  • Request: 대출 신청 ID와 대출 집행 금액(entryAmount)을 통해 잔고 정보를 업데이트하는 데 사용된다.
  • Response: 잔고 정보 반환 시, 잔고 ID(balanceId), 신청 ID, 잔고 금액을 포함한다.

3. EntryService를 통한 대출 집행 처리

대출 집행 로직은 EntryService와 EntryServiceImpl에서 구현된다. 

package com.example.loan.service;

import static com.example.loan.dto.EntryDTO.Request;
import static com.example.loan.dto.EntryDTO.Response;

public interface EntryService {

    Response create(Long applicationId, Request request);
}
package com.example.loan.service;

import com.example.loan.domain.Application;
import com.example.loan.domain.Entry;
import com.example.loan.dto.BalanceDTO;
import com.example.loan.exception.BaseException;
import com.example.loan.exception.ResultType;
import com.example.loan.repository.ApplicationRepository;
import com.example.loan.repository.EntryRepository;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;

import java.util.Optional;

import static com.example.loan.dto.EntryDTO.Request;
import static com.example.loan.dto.EntryDTO.Response;

@Service
@RequiredArgsConstructor
public class EntryServiceImpl implements EntryService {

    private final BalanceService balanceService;

    private final EntryRepository entryRepository;

    private final ApplicationRepository applicationRepository;

    private final ModelMapper modelMapper;

    @Override
    public Response create(Long applicationId, Request request) {
        // 계약 체결 여부 검증
        if (isContractedApplication(applicationId)) {
            throw new BaseException(ResultType.SYSTEM_ERROR);
        }

        Entry entry = modelMapper.map(request, Entry.class);
        entry.setApplicationId(applicationId);

        entryRepository.save(entry);

        // 대출 잔고 관리
        balanceService.create(applicationId
                , BalanceDTO.Request.builder()
                                .entryAmount(request.getEntryAmount())
                        .build());

        return modelMapper.map(entry, Response.class);
    }



    private boolean isContractedApplication(Long applicationId) {
        Optional<Application> existed = applicationRepository.findById(applicationId);
        if (existed.isPresent()) {
            return false;
        }
        return existed.get().getContractedAt() != null;
    }
}
  • 계약 체결 여부 검증: 먼저 대출 신청이 계약이 체결되었는지 확인하는 절차가 진행된다. 이 과정에서 대출 신청 ID로 ApplicationRepository를 통해 해당 대출 신청의 상태를 조회한다. 만약 계약이 체결되지 않은 상태라면 예외를 발생시켜 대출 집행이 불가능하도록 한다.
  • 대출 집행 데이터 생성: 계약이 체결된 대출에 대해서는 대출 금액을 저장하는 Entry 엔터티를 생성한다. 이 엔터티는 대출 금액, 대출 신청 ID 등의 정보를 포함하며, 생성된 데이터를 EntryRepository를 통해 저장한다.
  • 대출 잔고 관리: 대출 금액이 실제로 집행되었으므로, 대출 잔고를 관리하기 위한 로직이 함께 실행된다. BalanceService를 호출하여 잔고를 관리하며, 대출 금액이 잔고에 반영되도록 처리한다.

4. Repository 설계

대출 집행 및 잔고 관리를 위해 두 개의 Repository가 필요하다.

 

EntryRepository

대출 집행 정보를 데이터베이스에 저장하고 조회하는 역할을 담당한다. Entry 엔터티와 연결되어 있으며, 대출 금액 집행과 관련된 데이터를 관리한다.

package com.example.loan.repository;

import com.example.loan.domain.Entry;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping
public interface EntryRepository extends JpaRepository<Entry, Long> {

}

 

BalanceRepository

대출 잔고 정보를 관리하며, 대출 신청 ID를 기반으로 잔고 데이터를 저장 및 조회한다. 이 Repository를 통해 대출 잔고 상태를 업데이트하거나 새로운 잔고 정보를 추가할 수 있다.

package com.example.loan.repository;

import com.example.loan.domain.Balance;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface BalanceRepository extends JpaRepository<Balance, Long> {

    Optional<Balance> findByApplicationId(Long applicationId);

}

5. 잔고 관리 로직

대출 집행 후에는 대출 금액이 실제로 잔고에 반영되도록 해야 한다. 이를 위해 BalanceService와 BalanceServiceImpl에서 잔고 관리 로직을 구현한다.

package com.example.loan.service;

import static com.example.loan.dto.BalanceDTO.Request;
import static com.example.loan.dto.BalanceDTO.Response;

public interface BalanceService {

    Response create(Long applicationId, Request request);
}
package com.example.loan.service;

import com.example.loan.domain.Balance;
import com.example.loan.exception.BaseException;
import com.example.loan.exception.ResultType;
import com.example.loan.repository.BalanceRepository;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;

import static com.example.loan.dto.BalanceDTO.Request;
import static com.example.loan.dto.BalanceDTO.Response;

@Service
@RequiredArgsConstructor
public class BalanceServiceImpl implements BalanceService{

    private final BalanceRepository balanceRepository;

    private final ModelMapper modelMapper;

    @Override
    public Response create(Long applicationId, Request request) {
        if (balanceRepository.findByApplicationId(applicationId).isPresent()) {
            throw new BaseException(ResultType.SYSTEM_ERROR);
        }

        Balance balance = modelMapper.map(request, Balance.class);

        BigDecimal entryAmount = request.getEntryAmount();
        balance.setApplicationId(applicationId);
        balance.setBalance(entryAmount);

        Balance saved = balanceRepository.save(balance);

        return modelMapper.map(saved, Response.class);
    }
}
  • 잔고 생성: BalanceServiceImpl은 새로운 대출에 대해 잔고 정보를 생성하고, 대출 금액이 잔고에 반영되도록 한다. 대출 신청 ID와 대출 금액 정보를 받아, 이를 기반으로 잔고를 초기화하고 BalanceRepository를 통해 저장한다.
  • 기존 잔고 확인: 만약 이미 해당 대출에 대해 잔고 정보가 존재한다면 예외를 발생시킨다. 즉, 대출 금액이 중복으로 반영되지 않도록 방지하는 역할을 한다.

6. 실행 테스트

대출 집행 기능을 구현한 후, 정상적으로 동작하는지 실행 테스트를 진행했다. 이번 테스트에서는 entry 테이블에 데이터가 삽입되고, 동시에 balance 테이블에도 대출 금액이 반영되는지 확인하였다.

 

1) 대출 집행 API 호출

Postman을 통해 POST /internal/applications/{applicationId}/entries 경로로 대출 집행 요청을 보냈다. 요청 본문에는 entryAmount(대출 집행 금액)를 입력하여, 해당 금액을 신청자에게 집행하는 과정을 테스트했다.

 

2) 데이터베이스 확인

실행 로그를 통해 데이터베이스에 entry와 balance 테이블에 데이터가 정상적으로 삽입된 것을 확인했다.

  • 위 로그에서 확인할 수 있듯이, 대출 집행 정보가 entry 테이블에 삽입되었고, 동시에 대출 잔고 정보가 balance 테이블에 업데이트되었다. 이를 통해 대출 집행 기능이 정상적으로 동작하고 있음을 확인할 수 있었다.

이와 같은 흐름으로 대출 집행 시 entry 테이블에 집행 내역이 기록되고, 해당 금액이 balance 테이블에 잔고로 반영된다. 이를 통해 대출 집행과 잔고 관리가 자동으로 처리되며, 두 테이블의 데이터 일관성이 유지된다.