최근 인앱 결제 프로젝트를 진행하고 있습니다.

각각 플랫폼 정책에 맞게 개발 중인데 진행하다 보니 여러 가지 차이점이 보입니다.

구글은 정기결제에 대한 처리할 경우 RTDN을 이용해야하고,

최초 결제 시 앱을 통해 정보를 받아서 검증을 해야합니다.

이 모든 과정을 가이드 문서를 따라가면서 GCP와 Play Console을 오가며 설정을 하고

API를 호출하는 통신도 필요합니다.

그럼에도 불구하고 진행하면서 아쉬운 점은 환불 정책이 너무 미비하다는 점입니다.

사용자가 환불할 경우 RTDN으로 알림이 오지 않기 때문에 결국 폴링 방식 결제 검증 적용하거나

그때그때 검증을 할 수밖에 없는 구조입니다.

힘들게 RTDN으로 구축해놔도 환불이나 일회성 상품의 결제의 경우에는 또 다른 솔루션을 찾아야 하니

울며 겨자 먹기 구현을 하거나 어느 정도의 환불에 대해서는 묻고(?) 가는 비용으로 잡을 수밖에 없지 않나 싶습니다.

'생각정리' 카테고리의 다른 글

블로그 이전  (0) 2023.11.05
코딩 테스트 인터뷰 리뷰  (0) 2023.01.11
리프레시 & 코로나  (0) 2022.11.24
이직  (0) 2022.07.30
코딩테스트  (0) 2022.03.30

이번 글에서는 Spring RestTemplate에 대해 알아보겠습니다.

실무에서 HttpClient(HttpComponent) 또는 HttpUrlConnection 모듈을 사용한 서버 to 서버 통신이 많이

구현되어 있는 경우를 볼 수 있는데요.

일부 시스템들이나 간혹 다른 분들의 코드를 보면 과거 Wrapper 클래스로 만들 모듈을 그대로 재사용하는 경우가

많습니다. 스프링에서 지원하는 RestTemplate으로 기능 구현하는 것이 스프링 프레임워크를 사용하는 입장에서 

좋지 않나 싶어 구조와 사용법에 대해 정리해보겠습니다.

 

Spring RestTemplate 

RestTemplate HTTP 클라이언트 라이브러리를 통해 더 높은 수준의 API를 제공합니다.

Spring 3.x 버전부터 지원했으며, Spring 5.x부터는 WebClient를 쓰기를 권장하고 있습니다.

두 라이브러리 가장 큰 차이는 블로킹과 논블로킹 차이라고 간단하게 설명할 수 있습니다.

RestTemplate 기본 설정을 사용하면 UrlConnection과 최대 5개의 커넥션 풀을 갖게 됩니다.

RestTamplate 생성 시 ClientHttpReqeustFactory 부분을 구현하여 Apache HttpComponents

을 사용할 수도 있으며, 커넥션 풀이나 커넥션, 리드 타임아웃 같은 설정들도 세팅 가능합니다.

이러한 여러 가지 편리한 기능 지원으로 개발자는 비즈니스 구현에 더 집중할 수 있는 것 같습니다.

특징

  •  HTTP 요청 후 JSON, XML, String과 같은 다양한 응답을 받을 수 있는 템플릿 지원.
  •  Blocking I/O 기반의 동기방식을 사용.
  •  Header, Contents-Type, Http Method 등 다양한 설정 지원.

 

서버 to 서버 HTTP 통신에 많이 사용하는 라이브러리

  • URLConnection
    Java.net 패키지에 포함된 라이브러리.
    간단한 HTTP 프로토콜 기반 서비스 기능 구현 시 많이 사용.
  • Apache HttpClient (HttpComponent)
    Apache 재단에서 관리하는 라이브러리로 다양하고 편리한 기능들을 지원하기 때문에
    비교적 HTTP 프로토콜 기반 서비스 기능 구현 시 사용.

 

기존 라이브러리들 사용의 문제점

URLConnection을 빈도가 높은 기능 구현 시 사용하면 커넥션 풀을 기본적을 지원하지 않기 때문에

TIME_WAIT 상태의 연결이 증가하고 어느 시점에 가면 통신 시 hang이 걸리는 케이스가 확인됩니다.

HttpClient를 사용하더라도 매번 객체를 생성/연결 종료하여 사용하는 케이스가 있는데

이렇게 사용하면 URLConnection을 이용해 구현하는 것과 크게 다를 바가 없는 케이스로 

이럴 경우에도 hang이 걸리는 케이스가 확인됩니다.

 

 

기본 설정을 사용한 예제

아래 코드는 헤더 생성, 콘텐츠, Accept 타입 설정하여 uri를 호출하는 간단한 예시입니다.

 // 헤더 객체 생성
HttpHeaders headers = new HttpHeaders();
// 헤더 값 세팅(ex. Authorization)
headers.set(headerName, headerValue); 
// 컨텐츠 타입 세팅
headers.setContentType(MediaType.APPLICATION_JSON); 
// Accept 세팅
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); 
// RestTemplate 객체 생성
RestTemplate restTemplate = new RestTemplate(); 
HttpEntity<String> entity = new HttpEntity<String>(headers); 
// 헤더, 메소드, 응답 타입 세팅하여 uri 호출
ResponseEntity<String> responseEntity = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
// Response Body 리턴
return responseEntity.getBody();

 

위와 같이 사용할 경우

new RestTemplate()로 객체 생성하면 exchange() 메서드 호출 시 사용하는 factory를 라이브러리

코드를 추적해보면 HttpAccessor 클래스에 getRequestFactory() 메서드에 의해

requestFactory 값으로 SimpleClientHttpRequestFactory 객체를 세팅하는 것과 HttpURLConnection을 

사용하는 것을 확인 가능하고 5개의 커넥션 풀을 생성하여 기본 설정으로 사용합니다.

 

 

HttpClient 커넥션 풀 설정을 사용한 예제 

 // 헤더 객체 생성
HttpHeaders headers = new HttpHeaders();
// 헤더 값 세팅(ex. Authorization)
headers.set(headerName, headerValue); 
// 컨텐츠 타입 세팅
headers.setContentType(MediaType.APPLICATION_JSON); 
// Accept 세팅
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); 
// Httpclient 객체 및 커넥션 풀 설정
HttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(50)
.setMaxConnPerRoute(10)
.build();
// factory 및 timeout 설정
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);
factory.setConnectTimeout(4000);
factory.setReadTimeout(5000);

// RestTemplate Fcatory 설정
RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(factory));

// RestTemplate 객체 생성
RestTemplate restTemplate = new RestTemplate(); 
HttpEntity<String> entity = new HttpEntity<String>(headers); 
// 헤더, 메소드, 응답 타입 세팅하여 uri 호출
ResponseEntity<String> responseEntity = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
// Response Body 리턴
return responseEntity.getBody();

HttpURLConnection 대신 HttpClient(HttpComponent)를 사용하고 커넥션 풀과 Timeout 설정을 추가한 예제입니다.

이렇게 Connection을 재사용해서 이점을 얻으려면 Keep-Alive를 지원하는지 반드시 확인하고 적용해야 합니다.

그리고 잘못 값들을 설정하여 사용할 경우 이점을 얻지 못할 수 있으며, 경우에 따라 성능이 저하될 수도 있습니다.

 

 

마치며

다음 글에서는 Spring Boot에서 RestTemplate을 Configuration과 Bean 어노테이션을 사용하여

등록하고 @Autowired를 통한 재사용을 하는 방법을 확인해보겠습니다.

 

 

참고

https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#remoting

https://hc.apache.org/httpcomponents-core-4.4.x/index.html

https://perfectacle.github.io/2021/02/14/simple-client-http-request-factory-connection-pool/

'Spring Boot Framework' 카테고리의 다른 글

Spring Boot Interceptor  (0) 2022.02.18
Spring Rest Docs 와 Swagger (2)  (0) 2022.02.12
Spring Rest Docs 와 Swagger (1)  (0) 2022.02.11
Spring Boot Oauth 2.0 과 JWT Demo (5)  (0) 2022.02.08
Spring Boot Oauth 2.0 과 JWT Demo (4)  (0) 2022.02.06

문제

https://www.acmicpc.net/problem/9095

분석/설계

입력 숫자의 범위가 1~11이지만 다이나믹 프로그래밍으로 풀어봤습니다.

점화식을 아래와 같이 만들어봤습니다. 

N이 1, 2, 3 의 경우 점화식 적용할 경우 index가 음수 값이거나 0이기 때문에

따로 정의 해줬습니다.

 

dp[i] = d[i-1] + d[i-2] + d[i-3]

구현 코드

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int T = scanner.nextInt();
		int N[] = new int[T];
		int dp[] = new int[11];
		
		for(int i=0; i<T; i++) {
			N[i] = scanner.nextInt();
		}
		
		dp[0] = 1;
		dp[1] = 1;
		dp[2] = 2;
		
		for(int i=0; i<N.length; i++) {
			for(int j=3; j<N[i]+1; j++) {
				dp[j] = dp[j-1] + dp[j-2] + dp[j-3];
			}	
		}
		
		for(int i=0; i<N.length; i++) {
			System.out.println(dp[N[i]]);
		}

		scanner.close();
	}
}

정리

다이나믹 프로그램은 탑다운과 바텀업 방식이 있습니다.

이 문제는 점화식을 만들고 바텀업 방식으로 풀었고, 문제만 보고 점화식이 잘 정의가 안되면

예시를 여러 개 만들고 패턴을 찾아내어 점화식을 만들고 알고리즘을 적용하여 코드를 작성하면 

수월합니다.

참고

https://www.acmicpc.net/problem/9095

문제

https://www.acmicpc.net/problem/2839

분석/설계

입력 숫자의 범위가 3~5000 이기때문에 다이나믹 프로그래밍으로 풀지 않고

그리디 알고리즘으로 푸는 방법도 있을 것 같습니다.

하지만 다이나믹을 프로그래밍 사용하여 풀어봤습니다.

점화식을 아래와 같이 만들어봤습니다. 

4, 7 같은 경우 3이나 5로 정확히 나눠서 떨어지지 않기때문에 문제의 조건대로 -1을 출력해야 하므로

이에 대한 예외 처리를 주의해야합니다.

 

dp[i] = min(d[i-3], d[i-5]) + 1 

구현 코드

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();

        int dp[] = new int[N+1];

        dp[3] = 1;
        if(N == 4) {
            System.out.println(-1); 
            return;
        }
        if(N>4) dp[5] = 1;	

        for(int i=6; i<dp.length; i++) {
            if (i % 5 == 0) {
                dp[i] = dp[i-5] + 1;
            } else if (i % 3 == 0) {
                dp[i] = dp[i-3] + 1;
            } else {
                if (dp[i-3] != 0 && dp[i-5] != 0) {
                    dp[i] = Math.min(dp[i-3], dp[i-5]) + 1;
                } else {
                    dp[i] = -1;
                }
            }
        }

       System.out.println(dp[N]);
       sc.close();
    }
}

정리

다이나믹 프로그램은 탑다운과 바텀업 방식이 있습니다.

이 문제는 점화식을 만들고 바텀업 방식으로 풀었고

편한 풀이 방식을 적용하면 될 것 같습니다.

참고

https://www.acmicpc.net/problem/2839

 

영속성 컨텍스트

번역하면 엔티티를 영구 저장하는 환경으로 논리적인 개념입니다.

엔티티 매니저에 의해 관리되며, DB와 애플리케이션 사이에서 엔티티의 일종의 저장소 같은 역할.

 

엔티티의 생명주기

  • 비영속(New/Transient)
    영속성 컨텍스트와 관계가 없는 상태.
  • 영속(Managed)
    영속성 컨텍스트에 저장된 상태
  • 준영속(Detached)
    영속성 컨텍스트에 저장되었다가 분리된 상태

  • 삭제(Removed)
    삭제된 상태

 

EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();

Member member = new Member();
member.setId(11L);
member.setName("Jack");
/* 여기까지는 비영속 */
em.persist(member); // 영속성 컨텍스트 관리 시작
em.clear(); // 준영속 상태로 영속성 컨텍스트 삭제
em.remove(member); // 비영속 상태로 영속성 컨텍스트와 DB 삭제

 

영속성 컨텍스트 관리의 장점

  • 1차 캐시
    영속성 컨텍스트에 저장해놓았다가 트랜잭션이 커밋되기 전까지 DB를 조회하지 않고 캐시에 있는 값을 조회합니다.
    비즈니스 로직이 매우 복잡할 경우 약간의 이득이 있을 수 있지만 트랜잭션 내에서 일어나므로 효과는 크지 않을 수 있습니다.

  • 동일성 보장
    같은 트랜잭션 안에서 엔티티의 비교의 == 연산자를 통해 비교할 수 있습니다.

  • 트랜잭션을 지원하는 쓰기 지연
    트랜잭션 커밋 전까지 INSERT SQL을 쓰기 SQL 저장소에 모았다가 전송할 수 있습니다.

  • 변경 감지
    엔티티를 수정하면 스냅샷과 비교하여 변경된 부분이 있는 경우
    업데이트 SQL을 쓰기 SQL 저장소에 저장했다가 트랜잭션 커밋 시 DB에 전송합니다.

플러시

플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영.

영속성 컨텍스트의 내용들을 DB와 동기화한다고 이해하면 됩니다.

이름 때문에 오해할 수 있지만 삭제하거나 비우는 작업이 아닙니다.

 

스냅샷과 비교해서 수정된 엔티티를 찾고, 수정된 엔티티에 대해 쿼리를 생성하여 쓰기 지연 SQL 저장소에 등록합니다.
쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송합니다.

 

플러시 하는 경우

  • flush() 메소드 명시적 호출.
  • 트랙잭션 커밋할 경우
  • JPQL 쿼리 실행할 경우

참고

자바 ORM 표준 JPA 프로그래밍 / 김영한 저자

'Java > JPA' 카테고리의 다른 글

JPA (Java Persisitence API) - JPA 소개 (1)  (0) 2022.03.21

문제

https://www.acmicpc.net/problem/1463

분석/설계

입력 숫자의 범위가 2~10의 6제곱이기때문에 입력 범위가 크고

이렇게 입력 범위가 큰 경우 완전 탐색 알고리즘을 쓰면 항상 효율성에서 걸리는 문제들이 많았습니다.

다이나믹 프로그래밍을 적용하면 복잡도 면에서 걸리지 않을 수있다고 판단했고 이를 적용했습니다.

보통 다이나믹 프로그래밍은 점화식을 이용하면 쉽게 풀리는 경향이 있습니다.

 

dp[i] = min(d[i-1], d[i//2], d[i//3]) + 1 

구현 코드

import java.util.Scanner;

public class Main {
	
	static int dp[] = new int[10000001]; // 1을 만들기 위한 최소 연산 사용 횟수
	
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int X = scanner.nextInt();
		
		for(int i=2; i<X+1; i++) {
			dp[i] = dp[i-1] + 1;
			
			if(i % 2 == 0) {
				dp[i] = Math.min(dp[i], dp[i/2] + 1);
			}
			
			if(i % 3 == 0) {
				dp[i] = Math.min(dp[i], dp[i/3] + 1);
			}	
		}
		
		System.out.println(dp[X]);
		scanner.close();
	}
}

정리

다이나믹 프로그램은 탑다운과 바텀업 방식이 있습니다.

이 문제는 점화식을 만들고 바텀업 방식으로 풀었고

탑다운로도 풀이한 분들이 많이 있습니다.

편한 풀이 방식을 적용하면 될 것 같습니다.

참고

https://www.acmicpc.net/problem/1463

 

+ Recent posts