[팀프로젝트]4주차

비즈니스 로직과 외부라이브러리

우리 서비스는 결제모듈이 들어간다. 결제 모듈을 제공하는 서비스는 다양하다. 우리는 그중 '아임포트'라는 결제 모듈을 택했다. 그러나 우리의 비즈니스 로직에 '아임포트' 결제모듈로 특정지어서는 안된다. 외부 라이브러리와 도메인 로직이 깔끔하게 분리되길 원했다.

 

어댑터 패턴

 

Client는 써드파티 라이브러리나 외부 시스템을 사용하려는 쪽 우리 Thank you for다. Adaptee는 써드파티 라이브러리나 외부시스템 쪽 아임포트 결제모듈이다.

 

TargetInterface.. Client는 Adaptee대신 TargetInterface를 바라봐야한다.

 

우리의 도메인 로직은 프론트 엔드에서 우리에게 후원하길 원하는 창작자의 페이지 이름, 후원자의 이메일, 후원 금액을 보내면 Payment 도메인을 생성하고 저장한다. 이 때 Payment는 고유한 값인 merchantId를 가지며 Pending상태이다. 해당 Payment를 DB에 저장하고 merchantId를 프론트에게 응답한다.

 

프론트엔드는 서버로 부터 받은 merchantId로 아임포트 측에 결제 요청을 한다. 이 때 프론트 상에서도 아임포트를 통한 결제 화면이 뜬다. (ex 카카오결제화면) 결제가 완료되면 제어가 우리의 프론트엔드 측으로 돌아오며, 콜백 데이터로 impUid를 받는다. 이것은 아임포트 측에서 거래를 식별하는 아이디이다. 여기까지 완료되면 프론트 측에서 impUid와 merchantUid를 서버로 보낸다.

 

서버 쪽에서는 이미 저장된 결제정보와 아임포트 측에서 얻어온 결제정보가 동일한지 비교해야 한다. 이때 중요한 것이 아임포트를 걷어내고 생각하는 것이다. 외부라이브러리 측에서 얻어온 결제정보가 동일한지 비교해야 한다. 외부라이브러리와 연동하여 결제정보를 얻어와야 했다.

 

public interface PaymentServiceConnector {

    PaymentInfo requestPaymentInfo(UUID merchantUid);
    
}

 

위의 그림에서 TargetInterface의 역할은 우리 소스에서 PaymentServiceConnector이다. requestPaymentInfo 즉, 결제정보를 어딘가에 요청하는 메시지를 처리할 수 있다.

 

아래 PaymentServiceConnector를 구현한 IamPortPaymentServiceConnector는 아임포트에 대한 정보만 가지고 있다. IamPortPaymentServiceConnector가 Adapter의 역할 이고, 그럼 아임포트 API서버쪽이 Adaptee가 되겠다.

 

@Component
public class IamPortPaymentServiceConnector implements PaymentServiceConnector {
    private static final String MODULE_NAME = "아임포트";
    private static final String IAMPORT_API_URL = "https://api.iamport.kr";

    @Value("${iamport.rest_api_key}")
    private String impKey;

    @Value("${iamport.rest_api_secret}")
    private String impSecret;

    @Override
    public PaymentInfo requestPaymentInfo(UUID merchantUid) {
        String accessToken = getAccessToken();
        IamPortPaymentInfo paymentInfo = requestPaymentInfo(merchantUid, accessToken);

        return convertToPaymentInfo(paymentInfo);
    }
    ...
 }

 

현재 아임포트 밖에 사용하지 않아 의존성을 자동으로 주입받아 사용하고 있지만 후에 다른 결제모듈이 추가된다면 completePayment에 인자로 추가하여 결제모듈의 변경을 유연하게 대처할 수 있을 것 같다.

 

public class PaymentService {
    private final PaymentRepository paymentRepository;
    private final MemberRepository memberRepository;
    private final PaymentServiceConnector paymentServiceConnector;


    public Payment completePayment(PaymentCompleteRequest paymentCompleteRequest) {
        UUID merchantUid = UUID.fromString(paymentCompleteRequest.getMerchantUid());
        PaymentInfo paymentInfo = paymentServiceConnector.requestPaymentInfo(merchantUid);

        Payment payment = findPayment(merchantUid);

        payment.complete(paymentInfo);
        return payment;
    }
    ...
}

 

사실 처음에 아임포트 서비스에 대해 이해를 한 뒤 도메인 로직을 생각했다. 도메인 로직을 먼저 생각하고 그것을 가능하게하는 도구(?)인 아임포트를 찾아봤어야 했는데  반대가 되어버렸다. 저렇게 코드를 짜는게 맞는 것인가에 대해 혼란스러웠다. 그러나 팀원 중 한명이 토스페이먼트도 추가해보는 건 어때라고해서 토스 페이먼트 문서를 보았는데 결제모듈의 연동방식이 비슷하였다.

 

게다가 API를 보내는 부분도 제네릭을 사용해서 타입을 유동적으로 변경해서 리턴을 받을 수 있게하였다. 만약 토스페이먼트가 필요하다면

토스 페이먼트 API응답값에서 필요한 정보얻을 수 있는 DTO로 만들고 해당 DTO의 정보를 바탕으로 우리쪽 도메인 PaymentInfo로 만들어 실제 결제정보인 Payment와 유효성 검사를 하면 된다! 결제모듈이 추가되도 도메인로직이 크게 아니 거의 바뀌지 않을 것 같다.

    public static <T, U> T send(String url, HttpMethod method, HttpEntity<U> entity, Class<T> returnType) {
        return REST_TEMPLATE.exchange(url, method, entity, returnType).getBody();
    }

 

현재 우리의 PaymentInfo이다. 아임포트에 익숙하지 않아 용어가 헷갈려서 merchantId, impUid로 우선 개발을 진행하고있다. 추후 결제모듈이 추가된다면 네이밍을 조금 더 추상적으로 바꾸고 싶다. 🥲

@Getter
public class PaymentInfo {
    private UUID merchantUid;
    private PaymentStatus status;
    private Long amount;
    private String pageName;
    private String impUid;
    private String module;
    
    ...  
 }

 

https://yaboong.github.io/design-pattern/2018/10/15/adapter-pattern/

 

디자인패턴 - 어댑터 패턴

개요 어댑터 패턴에 대해서 알아본다. Coursera 의 디자인패턴 강의 를 기반으로 작성했다. Rectangle vs LegacyRectangle, Duck vs Turkey 같은 터무니 없는 예제가 아닌 조금 더 실질적인 예제를 사용해본다. J

yaboong.github.io

 

 

 

Nginx 서브도메인 설정

 

porkburn이라는 곳에서 도메인을 구입했다. 로고가 상당히 귀엽다. 부끄럽게도 서브도메인에 대해 팀프로젝트하면서 배웠다. www.google.com도 google.com이 도메인이고 www.google.com은 서브도메인이라것을.... 실제로 지금 우리 서비스의 www.우리도메인은 접근안된다. 등록을 안해놨기 때문이다.

 

 

porkbun.com | An oddly satisfying experience.

Porkbun is an amazingly awesome ICANN accredited domain name registrar based out of the Pacific Northwest. We're different, we're easy, and we're affordable. Use us, you won't be sorry. If you don't use us we'll be sad, but we'll still love you.

porkbun.com

 

 

서브도메인이란 도메인에 보조적인 역할을 한다. 다중의 사이트를 만들고 연결을 하고자 할 때 독립적으로 접속할 수 있게 도와준다. 이번 스프린트에서 api.우리도메인, api-dev.우리도메인 이라는 서브도메인을 만들었다. api는 실제 배포환경에서 접근하는 서버를 가르키고, api-dev는 개발환경에서 접근하는 서버를 가르킨다. 그리고 이것들은 모두 TLS설정을 해주어야 한다.

 

기존 인프라가 NGINX에서 TLS설정을 담당하도록 하였기에, 서브도메인에 따라 NGINX에서 라우팅을 시켜주는 방법을 찾아야 했다.

 

그 중 server_name이라는 속성을 사용하였다.

 

server_name 디렉티브는 하나의 IP 주소에 대해 여러개의 도메인(domains)을 사용할 수 있게 한다. 서버는 전달받은 요청 헤더(request header)에 기반하여 어떤 도메인을 서브할지 결정한다.

 

NGINX로 접근을 한다. (하나의 IP주소) server_name속성에 각각 서브도메인을 지정하였다. 80포트로 들어올 경우 443으로 리다이렉팅을 시켜주었다. 443포트는 TLS 체크를 하고 각 server_name에 맞는 api 서버로 요청을 전달한다. (proxy_pass)

 

https://architectophile.tistory.com/12

 

[Nginx] 기본 설정 방법

[Nginx] 기본 설정 방법 Nginx는 가벼운 고성능의 웹서버로서 높은 트래픽 처리를 위해 디자인되었다. Nginx의 가장 강력한 기능 중 하나는 HTML이나 미디어 파일 같은 정적 컨텐츠를 효율적으로 서브

architectophile.tistory.com

 

 

삽질기) 일단 NGINX로 들어왔는데 맞는 server_name이 없다면?

예를들어 test1.우리도메인, test2.우리도메인, test3.우리도메인을 NGINX서버를 가르키도록 등록했다고 가정하자. 그러나 NGINX설정에 server_name으로 test1.우리도메인과 test2.우리도메인만 특정지어져 있다. (nginx.conf에서 디폴트 설정안하고 있고 순서는 test1.우리도메인 > test2.우리도메인 순으로 설정되어있다. ) 그러면 test3.우리도메인은 nginx.conf 최상단의 test1.우리도메인의 설정을 타게 된다. nginx.conf의 최상단을 test2.우리도메인으로 바꾸면 test3.우리도메인이 들어왔을 때 test2.우리도메인의 설정을 탄다.

 

한줄요약

Nginx 요청에서 server를 찾을 수 없다면 첫번째 server로 매핑이된다.

 

스프링 슬라이싱 테스트 관련 궁금한점

팀원이 짠 테스트 코드를 보고있던 도중 @WebMvcTest에서 Intercepter와 ArgumentResolver도 Mock으로 설정해 준것이 궁금했다.  Intercepter와 ArgumentResolver를 사용하지 않는데도 해주었다. 왜왜왜?????? 물어보려고 하다가 테코블의 글을 봤는데 거기 이렇게 적혀있었다.

 

@WebMvcTest 어노테이션을 사용하면 웹 레이어 테스트를 하는 데 필요한 @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver 등만 Bean으로 등록한다.

 

즉 WebMvcTest에서 WebMvcConfigurer를 빈등록하려는데 HandlerMethodArgumentResolver, HandlerInterceptor가 없으니 에러를 뱉는 것이었다. 

 

https://woowacourse.github.io/tecoble/post/2021-05-18-slice-test/

 

Spring Boot 슬라이스 테스트

Spring Boot…

woowacourse.github.io

 

 

'우아한테크코스' 카테고리의 다른 글

[팀프로젝트] 5,6주차  (0) 2021.08.12
배포인프라 수업 다시보기  (0) 2021.08.04
[팀프로젝트]3주차  (1) 2021.07.22
[팀프로젝트] 2주차  (0) 2021.07.13
[팀프로젝트] 1주차  (5) 2021.07.09

댓글



Designed by JB FACTORY