본문 바로가기
📕 Spring Framework/Spring Project

2022.06.07 「프로젝트 중간 점검」

by GroovyArea 2022. 6. 7.
이번 4일 간 프로젝트를 집중 있게 하느라 블로그 글 작성도 못하고 코테 준비도 제대로 못했다 ㅜㅜ
프로젝트 초기에는 속도에 대한 반성을 많이 했었는데, 시간이 지나고 프로젝트의 틀이 잡힐수록 그 반성은 큰 오산이라는 것을 알게 되었다. 퀄리티 있고, 클린 한 코드를 작성하려면 꽤나 공들이며 시간을 투자해야 한다는 것을 깨달았다. 즉, 더 효율적인 시간 분배가 관건이다. 
이제 프로젝트에서 계획한 기능은 거의 구성이 된 상황이다. 거진 1달이 걸렸다. 계획한 것에 비해 늦었다고 생각하지만 본격적인 리팩터링을 적용해볼 생각이다. 처음으로 혼자서 제대로 된 프로젝트를 하기 때문에 더 애착이 가기도 한다. 이제 반도 안 왔고, 갈 길이 험난할 테지만 이뤄보자~

 

지난 4일 간 구현 목록

  1. 카카오 페이 REST API를 이용한 단건 결제, 장바구니 결제, 결제 조회, 결제 취소
  2. 결제, 카드 정보, 결제 금액 테이블 생성
  3. 결제 프로세스에서 트래픽 몰렸을 때의 대처 (레코드 락 이용)
  4. git ignore 수정 (yml 파일)

 


결제 조회

카카오 페이 API를 이용하면 결제한 내역의 상세 내용을 조회할 수 있다. API를 통해 받은 데이터중 내게 필요한 데이터를 추려서 해당 테이블들에 저장을 했다. 

https://developers.kakao.com/docs/latest/ko/kakaopay/payment-detail

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

컨트롤러 레이어

@GetMapping("/{userId}")
public Message getDBOrderInfo(@PathVariable String userId) {
    return new Message
            .Builder(orderService.getOrderInfoList(userId))
            .message(MEMBER_ORDER_LIST)
            .httpStatus(HttpStatus.OK)
            .mediaType(MediaType.APPLICATION_JSON)
            .build();
}

@GetMapping("/detail")
public Message getOrderDetail(@RequestParam String tid, @RequestParam String cid) {
    return new Message
            .Builder(kakaoPayService.getOrderDetail(tid, cid))
            .message(ORDER_INFO)
            .httpStatus(HttpStatus.OK)
            .mediaType(MediaType.APPLICATION_JSON)
            .build();
}

- 첫 번째 핸들러는 DB에 저장된 추려진 주문 데이터를 Select 하는 동작이다. 서비스 레이어를 통해 매핑된 SQL을 실행해 주문 목록을 가져온다. 

 

- 두 번째 핸들러는 카카오페이 API를 통해 직접 데이터를 얻는 동작이다. JSON 데이터를 객체로 받아 반환한다. 

 

서비스 레이어

@Transactional(readOnly = true)
public List<OrderInfoDTO> getOrderInfoList(String userId) {
    return orderMapper.selectOrderList(userId).stream()
            .map(a -> modelMapper.map(a, OrderInfoDTO.class))
            .collect(Collectors.toList());
}

- DB를 통해 select를 하는 서비스이다. 

 

@Transactional
public OrderInfoDTO getOrderDetail(String tid, String cid) {

    /* 서버로 요청할 헤더*/
    HttpHeaders headers = new HttpHeaders();
    setHeaders(headers);

    /* 서버로 요청할 Body */
    MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
    params.add("cid", cid);
    params.add("tid", tid);

    HttpEntity<MultiValueMap<String, String>> body = new HttpEntity<>(params, headers);

    try {
        return restTemplate.postForObject(HOST + KAKAP_PAY_ORDER, body, OrderInfoDTO.class);
    } catch (RestClientException e) {
        log.error(e.getMessage());
    }
    return null;
}

- kakao api를 통해 직접 요청 후 조회를 한다. 

 

 

주문 취소

주문 취소도 Kakao API를 이용한 요청을 통해 쉽게 진행할 수 있다. 요청을 할 경우 주문 취소에 대한 데이터를 여러 JSON으로 반환한다. 이를 객체로 받아 활용하면 될 것이다. 필요한 데이터를 받아 설계한 테이블에 UPDATE를 했다.

https://developers.kakao.com/docs/latest/ko/kakaopay/cancellation

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

컨트롤러 레이어

@PostMapping("/cancel")
public Message payCancel(@RequestBody Map<String, Object> map){
    return new Message
            .Builder(kakaoPayService.cancelKakaoPay(map))
            .mediaType(MediaType.APPLICATION_JSON)
            .httpStatus(HttpStatus.OK)
            .message(CANCEL_PAY)
            .build();
}

- 필요한 Json 데이터를 Map 객체로 받아 서비스 레이어로 전달한다.

 

서비스 레이어

@Transactional
public OrderCancelDTO cancelKakaoPay(Map<String, Object> map) {

    /* 서버로 요청할 헤더*/
    HttpHeaders headers = new HttpHeaders();
    setHeaders(headers);

    /* 서버로 요청할 Body */
    MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
    params.add("tid", String.valueOf(map.get("tid")));
    params.add("cancel_amount", String.valueOf(map.get("cancelAmount")));
    params.add("cancel_tax_free_amount", String.valueOf(map.get("cancelTaxFreeAmount")));
    params.add("cid", TEST_CID);

    HttpEntity<MultiValueMap<String, String>> body = new HttpEntity<>(params, headers);

    try {
        OrderCancelDTO responseDTO =
                restTemplate.postForObject(HOST + KAKAO_PAY_CANCEL, body, OrderCancelDTO.class);
        orderMapper.updateOrder(responseDTO.getTid());
        return responseDTO;

    } catch (RestClientException e) {
        log.error(e.getMessage());
    }

    return null;
}

- map 객체를 통해 얻어온 파라미터를 가지고 API에 요청을 해 결제 취소 데이터를 객체로 받아온다. 

그 후 해당 레코드를 업데이트한다. 

 


테이블 생성

결제에 필요한 데이터를 DB에 저장하기 위해 테이블을 설계했다. 

최대한 필요한 데이터를 추려서 설계해보았다. 결제 금액과 카드 정보는 주문 테이블과 1:1로 매칭 되고, 주문과 상품의 관계를 주지 않은 이유는 여러 개 주문이 있을 경우가 있기 때문이다. ERD 모델을 설계해야 하기 때문에 1:1 관계를 주었고, 실제로 FK를 설정하지 않고 인덱스를 정의했다.

CREATE TABLE CARDINFO
(
    tid                   VARCHAR(30) NOT NULL,
    issuer_corp           VARCHAR(40) NULL,
    issuer_corp_code      VARCHAR(30) NULL,
    bin                   VARCHAR(30) NULL,
    card_type             VARCHAR(5)  NULL,
    install_month         VARCHAR(5)  NULL,
    interest_free_install VARCHAR(2)  NULL,

    INDEX `idx_tid` USING BTREE (tid) COMMENT '결제 고유 번호 인덱스' VISIBLE
)
    ENGINE = InnoDB
    DEFAULT CHARACTER SET = utf8
    COLLATE = utf8_unicode_ci
    COMMENT = '카드 정보 테이블';
CREATE TABLE amount
(
    tid      VARCHAR(30) NOT NULL,
    total    INT(10)   NULL,
    tax_free INT(10)    NULL,
    vat      INT(10)    NULL,
    point    INT(10)    NULL,
    discount INT(10)    NULL,

    INDEX `idx_tid` USING BTREE (tid) COMMENT '결제 고유 번호 인덱스' VISIBLE
)
    ENGINE = InnoDB
    DEFAULT CHARACTER SET = utf8
    COLLATE = utf8_unicode_ci
    COMMENT = '결제 금액 테이블';

SQL과 인덱스 설정

 


트래픽

앞선 포스팅에서 정리했다시피 레코드에 락을 거는 방법을 택했다. 하지만 이 같은 방법은 속도가 현저히 느려진다. 실제 서비스에서는 성능이 매우 중요하다 들었기 때문에, 다른 방법을 모색해봐야겠다.

레코드 자체에 락을 거는 것 대신 를 통해 주문 요청 데이터를 쌓아둔 후 순차적으로 처리하는 방법도 있을 것 같다. 실행해보자!!

 


▶ 이메일 서비스

회원가입 시 이메일로 인증번호를 전송하고, redis에 저장하며 인증번호를 검증하는 작업을 진행했다. 하지만 MSA 환경에서 이 같은 작업은 데이터 정합성에 문제를 일으킬 수 있다. 느슨한 결합으로 분리되어 있는 앱이므로, 잘 생각을 해봐야 하는 문제이다. 계속 알아보고 생각을 한번 더 해보며 개선해보자

 

진행중인 프로젝트

https://github.com/GroovyArea/MyChickenBreastShop

 

반응형