민스씨의 일취일장

프로젝트 | PaymentService 만들며 익힌 기본기 - PR, 예외처리, Test Code, SRP 본문

Projects/MetaPay

프로젝트 | PaymentService 만들며 익힌 기본기 - PR, 예외처리, Test Code, SRP

읻민스 2024. 7. 24. 17:20
반응형

서비스 하나 만들며 익힌 기본기에 대한 내용이다.

기본 서비스 만들기

F-Lab Project 서비스 만들며 익힌 기본기 썸네일 이미지이다.
F-Lab Project 서비스 만들며 익힌 기본기

프로젝트를 시작하면서 가장 먼저 핵심 기능을 하는 서비스를 하나 만드는 시간을 가졌다. 단 하나의 메서드의 동작을 구현하고 테스트 코드를 작성해 보면서 여러 기본기를 익힐 수 있었다.

기본 서비스 구성

읻민스가 진행중인 현재 프로젝트는 페이 서비스를 만들어 보는 것이다. 페이 서비스의 가장 핵심 기능은 "결제"이다. 결제는 다음과 같은 순서로 이뤄지도록 구성했다.

결제 과정 도식이다.
결제 과정

 

이 "결제" 기능을 수행하기 위해 필요한 것들을 아래와 같이 구성했다.

com.ydmins.paymentservice
│
├── controller
│   └── PaymentController.java
│
├── domain
│   └── payment
│       ├── Payment.java
│       ├── PaymentStatus.java
│       └── PaymentMethod.java
│
├── repository
│   └── PaymentRepository.java
│
├── service
│   ├── PGService.java
│   ├── PaymentService.java
│   └── PaymentServiceImpl.java
│
└── PaymentServiceApplication.java

 

PR 보내기

그 동안엔 PR을 보낼일이 정말 몇번 없었다. 이번 에프랩에선 프로젝트를 진행할 땐 작업한 내용을 main 또는 develop 브랜치로 병합할 때 멘토님에게 PR을 보내고 승인을 받는 식으로 진행한다. 이를 통해서 혼자 연습하기 정말 어려웠던 PR을 정말 아주 많이 시도해 볼 수 있다. (벌써 첫 주에만 5번이 넘는 PR을 보내보았다.) PR을 보내면서 이렇게 하는게 맞는지 여러번 멘토님께 여쭤보면 해당 PR은 어땠는지 PR을 보낼 때 어떤점을 주의해야 하는지 알 수 있었다. 그 중 핵심 메시지는 아래와 같다.

PR을 보낼 땐 리뷰어가 코드 리뷰를 할 때 수월하게 할 수 있도록 해야 한다.

 

PR 규모

PR을 보낼 때에는 리뷰어가 수월하게 리뷰할 수 있도록 하기 위해선 변경된 파일의 수가 너무 많으면 안된다. 보통 5개 내외, 될 수 있으면 5개 이하인 것이 좋다. 리뷰어 조차도 한 사람의 개발자이기 때문에 내 코드만 리뷰하면서 시간을 보낼만큼 한가하지가 않다. 따라서 변경 규모가 너무 크지 않은 선에서 PR을 보내 변경 내용을 빠르게 파악할 수 있도록 해야 한다.

PR 메시지

리뷰어가 코드를 쉽게 리뷰할 수 있도록 가이드하는 역할하는 것이 PR 메시지이다. 따라서 PR은 아래와 같은 형식을 갖추면 좋다.

PR 개요 : 본 PR이 어떤 변경을 하였는지 설명

변경 내용 : 구체적으로 어떤 점이 변경되었는지 설명

체크리스트 : 문제없이 작동되는 것을 테스트 했는지 기술

이후 계획 : 간단하게 이후 어떤 작업을 이어나갈지 기술

 

하나하나 간략하게 살펴보자.

  • PR 개요

해당 PR은 어떤 변경을 다루고 있는지 개략적으로 설명해, 본론을 읽기전에 리뷰어가 의도를 파악할 수 있도록 돕느다.

  • 변경 내용

실제로 어떤 변경이 이뤄졌는지 설명해준다. 코드만으로 변경 내용을 파악하는 데에는 시간이 더 오래 걸린다. 뿐만 아니라 리뷰어는 내 모든 코드 내용을 기억하고 있지 못한다. 따라서 변경 설명과 코드 변경이 올바른지 파악할 수 있는 가이드를 제시하는 것이다.

  • 체크리스트

문제없이 코드가 작동하는지, 테스트 코드들이 문제없이 통과했는지 체크해서 알려주는 것이다. 이 과정은 리뷰어에게 리뷰 요청을 하기 전에 스스로 문제 없는 것을 확인하는 이유가 훨씬 크다고 생각한다. 이런 것을 규칙적으로 하지 않으면 테스트도 해보지 않고, 실제로 작동도 하지 않는 코드를 PR하는 불상사가 발생할 수 있다.

  • 이후 계획

이후 계획을 통해서 리뷰어가 해당 코드가 계획된 방향으로 잘 가는지 파악하는데 도움을 줄 수 있다. 또는 앞으로 어떤 방향을 고민해 보면 좋은지 고민을 나눠줄 수도 있다.

예외케이스

멘토님이 예외처리를 하라는 커멘트를 주셨다. 예외는 일단 나중에 하면 된다 생각하고 생각한 순서대로 코드를 작성했다. 그런데 지속적으로 예외 처리를 말씀해주셨다. 그렇게해서 본격적으로 예외를 다루기 시작했는데, 예외가 왜 중요한지 알게 됐다. 지금 생각해 보면 정말 어처구니 없는 코드였다. 본래의 코드에서는 결제 시도를 하면 결제가 성공하거나 실패하는 딱 두가지 경우를 상정해뒀었다. 그런데 멘토님이 생각해보란 지점에서 예외를 넣어보니 예외가 정말 중요한 사항이었던 걸 알았다.

  • 성공

성공은 모든 동작이 올바르게 작동한 결과이다. 딱 하나의 경우만 존재한다.

  • 실패

실패의 경우를 잘 생각했어야 한다. 왜 실패인지를 살펴보아야 한다. 

  1. PG 요청이 올바르게 갔는데, 그곳에서 실패 결과를 받은 경우 : 이 경우는 내 코드의 메커니즘에는 문제가 없는 것이다.
  2. PG 요청에서 예외가 발생한 경우 : 어떤 이유에서든지 PG사 요청을 보내거나 받는 과정에서 문제가 발생할 수 있다. 이 경우 결제 실패이지만, 비정상적으로 종료된 실패인 경우이다.
  3. DB에 결과를 저장할 때 발생한 경우 : 이 경우가 정말 치명적인데, 결제는 올바르게 처리됐는데, DB에 결과를 저장할 때만 문제가 발생한 경우이다.

예외를 처리해 보니 정말 다양한 경우의 수가 있는 것을 알게 됐다.

외부 로직을 사용할 땐 항상 예외 상황을 염두해 둬야 한다.

Test Code

평소에는 항상 쫓기는 마음에 구현하는데 정신이 팔려 차마 테스트 코드까지 작성할 여력이 없었다. 이는 반대로 테스트 코드 작성 습관이 잡혀있지 않았다는 말이기도 하다. 이번엔 딱 하나의 서비스 메서드를 구현하는 것이기 때문에 테스트 코드 작성도 부담없고, 이참에 제대로 배우고 연습하자는 마음이었다. 비즈니스 로직을 완성하고 문제없이 작동하는 것을 확인한 다음 테스트 코드 작성을 시작했다. (엄밀히 말해 TDD는 아니었다. 하지만 이후를 위해서 Test를 작성할 필요가 있다고 생각했다.

Case Coverage

아직 테스트 코드와 친하지 않아서, 테스트 코드를 작성할 때면 생각되는 모든 경계 케이스를 테스트 해야 할 지 의문이 들었다. 하지만 해왔던 대로 이번에도 생각되는 모든 케이스를 테스트 하였다. 그리고 멘토님과 같이 코드를 리뷰하는 시간에 다시 한 번 이 점에 대해서 여쭤보았다. 그런데 "많은 케이스를 테스트 해서 나쁠건 없지 않나"라고 답해주셨다. 무조건 많은 테스트 케이스를 만들라는 말씀은 아닐 것이다. 지금처럼 생각되는 경계 케이스는 모두 만들 계획이다. 그리고 내공이 쌓이면 조금 더 잘 짤 수 있게 되지 않을까 생각한다.

given 객체

하나의 메서드를 테스트 하기 위해 총 4개의 메서드를 만들었다.

  1. success : 성공한 케이스
  2. failure : 실패한 케이스
  3. serviceException : 결제 요청시 예외 발생한 케이스
  4. repositoryExcpetion : DB에 저장시 예외 발생한 케이스

given 객체라고 말했지만, given 단계에서 테스트를 위해 사용되는 객체를 이렇게 부르려고 한다. 처음엔 케이스 케이스를 올바르게 작성하고 작동하는지에 대해서만 신경쓰다 보니, 중복된 코드가 눈에 들어오지 않았다. 멘토님과 함께 코드를 살펴보면서 같거나 비슷한 코드가 정말 많이 반복되는 것을 느꼈다. 이 것들을 모듈화하면 테스트 코드 로직을 이해하는데도 훨씬 빠를 것이란 생각이 들었다.

SRP - 단일책임원칙

Java나 C++과 같은 객체 지향 언어를 다룬다면 꼭 한 번은, 아니 정말 많이 들어봤을 원칙이 단일책임원칙이다. 객체 지향 설계 원칙인 SOLID 원칙의 첫 번째 원칙이기도 하다.

하나의 모듈은 하나의 책임을 지어야 한다.

 

너무나 직관적이라 쉽게 적용할 수 있고, 언제나 적용하고 있다고 생각했다. 그런데 멘토님과 코드 리뷰를 진행해 보니, 결제를 처리하는 과정에서 두 번의 외부 로직을 사용하고 각각 예외처리를 하는데 이 과정이 하나의 프로세스라고 말하기 어렵다는 것이다.

public boolean processPayment(PaymentRequest request) {
        Payment payment = createPaymentFromRequest(request);
        PGResponse pgResponse = null;
        try {
            pgResponse = pgService.requestPayment(request);
        } catch (Exception e){
            log.error("Error processing payment", e);
            payment.setStatus(PaymentStatus.FAILED);
        }
        if (pgResponse != null){
            if(pgResponse.isSuccess()){
                payment.setStatus(PaymentStatus.SUCCESSFUL);
            } else {
                payment.setStatus(PaymentStatus.FAILED);
            }
        }
        try {
            paymentRepository.save(payment);
        } catch (Exception e){
            log.error("Failed to save payment",e);
        }
        if(pgResponse == null){
            return false;
        }
        return pgResponse.isSuccess();
    }

 

processPayment이기 때문에 PG서비스를 이용하고, 결과를 DB에 저장하는 것을 관장하는 게 당연하다고 생각했는데 그 처리 하나하나는 또 별개의 작업이라고 생각할 수 있다는 것을 느꼈다. 다시 말해서, 얼마나 쉽게 SRP를 위반할 수 있는지 목격하는 시간이었다. 앞으로 좀 더 면밀하게 SRP를 신경써야 겠다.

728x90
반응형