Skip to content

Commit

Permalink
feat : 주문취소 feign retryer bean 추가 (#564)
Browse files Browse the repository at this point in the history
* feat : 주문취소 retry 추가

* chore : 불필요 dependency 삭제

* feat : 500번대 retry 먼저 시도

---------

Co-authored-by: 이찬진 <[email protected]>
  • Loading branch information
gengminy and ImNM authored Jul 9, 2023
1 parent 1e0dbf0 commit 5478555
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@


import band.gosrock.common.validator.PhoneValidator;
import java.lang.annotation.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;

@Target({ElementType.FIELD})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

@FeignClient(
name = "PaymentsCancelClient",
url = "https://api.tosspayments.com",
url = "${feign.toss.url}",
configuration = {PaymentsCancelConfig.class})
public interface PaymentsCancelClient {
@PostMapping("/v1/payments/{paymentKey}/cancel")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,47 @@
import band.gosrock.infrastructure.outer.api.tossPayments.exception.PaymentsCancelErrorCode;
import band.gosrock.infrastructure.outer.api.tossPayments.exception.PaymentsUnHandleException;
import band.gosrock.infrastructure.outer.api.tossPayments.exception.TossPaymentsErrorDto;
import feign.FeignException;
import feign.Response;
import feign.RetryableException;
import feign.Retryer;
import feign.codec.ErrorDecoder;
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;

public class PaymentCancelErrorDecoder implements ErrorDecoder {
private static final long PERIOD = 500L;
private static final long MAX_PERIOD = TimeUnit.SECONDS.toMillis(3L);
private static final int MAX_ATTEMPTS = 3;

@Bean
Retryer.Default retryer() {
return new Retryer.Default(PERIOD, MAX_PERIOD, MAX_ATTEMPTS);
}

@Override
public Exception decode(String methodKey, Response response) {
TossPaymentsErrorDto body = TossPaymentsErrorDto.from(response);
try {
PaymentsCancelErrorCode paymentsCancelErrorCode =
FeignException exception = feign.FeignException.errorStatus(methodKey, response);
int status = response.status();
// 500번대는 기본적으로 리트라이
if (HttpStatus.valueOf(status).is5xxServerError()) {
throw new RetryableException(
status,
exception.getMessage(),
response.request().httpMethod(),
exception,
null,
response.request());
}
// payments 에러 코드 Decode
TossPaymentsErrorDto body = TossPaymentsErrorDto.from(response);

final PaymentsCancelErrorCode paymentsCancelErrorCode =
PaymentsCancelErrorCode.valueOf(body.getCode());
ErrorReason errorReason = paymentsCancelErrorCode.getErrorReason();
final ErrorReason errorReason = paymentsCancelErrorCode.getErrorReason();

throw new DuDoongDynamicException(
errorReason.getStatus(), errorReason.getCode(), errorReason.getReason());
} catch (IllegalArgumentException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package band.gosrock.infrastructure.outer.api.tossPayments.client;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.catchThrowable;

import band.gosrock.infrastructure.InfraIntegrateProfileResolver;
import band.gosrock.infrastructure.InfraIntegrateTestConfig;
import band.gosrock.infrastructure.outer.api.tossPayments.dto.request.CancelPaymentsRequest;
import feign.RetryableException;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.spec.internal.HttpStatus;
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;

@ContextConfiguration
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = InfraIntegrateTestConfig.class)
@AutoConfigureWireMock(port = 0)
@ActiveProfiles(resolver = InfraIntegrateProfileResolver.class)
@TestPropertySource(properties = {"feign.toss.url=http://localhost:${wiremock.server.port}"})
class PaymentsCancelClientTest {
private static final String IDEMPOTENCY_KEY = "idempotency-key";
private static final String PAYMENT_KEY = "1234";
private static final CancelPaymentsRequest REQUEST =
CancelPaymentsRequest.builder().cancelReason("test").build();
@Autowired private PaymentsCancelClient paymentsCancelClient;

@Test
public void 주문취소_실패시_멱등성_테스트() {
final String URL = "/v1/payments/" + PAYMENT_KEY + "/cancel";
// given
stubFor(
post(urlEqualTo(URL))
.willReturn(aResponse().withStatus(HttpStatus.SERVICE_UNAVAILABLE)));

// when
Throwable exception =
catchThrowable(
() -> paymentsCancelClient.execute(IDEMPOTENCY_KEY, PAYMENT_KEY, REQUEST));

// then
assertThat(exception).isInstanceOf(RetryableException.class);
verify(3, postRequestedFor(urlEqualTo(URL)));
}
}

0 comments on commit 5478555

Please sign in to comment.