Skip to content

Commit 3036535

Browse files
authored
Merge pull request #133 from TicketPing/feature/retry
feignclinet, webclient에 retry 설정
2 parents d3821a7 + 0b8064e commit 3036535

File tree

19 files changed

+154
-31
lines changed

19 files changed

+154
-31
lines changed

common/circuit-breaker/src/main/java/circuit/config/CircuitBreakerEventConfig.java renamed to common/resilience4j/src/main/java/resilience4j/config/CircuitBreakerEventConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package circuit.config;
1+
package resilience4j.config;
22

33
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
44
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnErrorEvent;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package resilience4j.config;
2+
3+
import io.github.resilience4j.retry.RetryRegistry;
4+
import io.github.resilience4j.retry.event.RetryOnRetryEvent;
5+
import jakarta.annotation.PostConstruct;
6+
import lombok.RequiredArgsConstructor;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.context.annotation.Configuration;
9+
10+
@Slf4j
11+
@Configuration
12+
@RequiredArgsConstructor
13+
public class RetryEventConfig {
14+
15+
private final RetryRegistry retryRegistry;
16+
17+
@PostConstruct
18+
public void registerRetryEventListeners() {
19+
retryRegistry.getAllRetries().forEach(retry -> {
20+
retry.getEventPublisher()
21+
.onRetry(this::logRetry);
22+
});
23+
}
24+
25+
private void logRetry(RetryOnRetryEvent event) {
26+
log.info("Retry for '{}' attempt number: {}",
27+
event.getName(),
28+
event.getNumberOfRetryAttempts());
29+
}
30+
}

common/circuit-breaker/src/main/resources/application-circuit-breaker.yml renamed to common/resilience4j/src/main/resources/application-circuit-breaker.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@ spring:
44
circuitbreaker: enabled
55

66
resilience4j:
7+
retry:
8+
retry-aspect-order: 2
9+
configs:
10+
default:
11+
max-attempts: 3
12+
wait-duration: 500ms
13+
retry-exceptions:
14+
- feign.RetryableException
15+
- feign.FeignException.ServiceUnavailable
16+
717
circuitbreaker:
18+
circuit-breaker-aspect-order: 1
819
configs:
920
default:
1021
registerHealthIndicator: true
@@ -18,10 +29,6 @@ resilience4j:
1829
permittedNumberOfCallsInHalfOpenState: 3
1930
recordSlowCalls: true
2031
record-exceptions:
21-
- java.util.concurrent.TimeoutException
22-
- java.net.SocketTimeoutException
23-
- java.net.UnknownHostException
24-
- java.net.ConnectException
2532
- feign.RetryableException
2633
- feign.FeignException.GatewayTimeout
2734
- feign.FeignException.BadGateway

gateway/src/main/java/com/ticketPing/gateway/infrastructure/client/AuthWebClient.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
import auth.UserCacheDto;
44
import com.ticketPing.gateway.application.client.AuthClient;
55
import com.ticketPing.gateway.common.exception.CircuitBreakerErrorCase;
6-
import com.ticketPing.gateway.common.exception.SecurityErrorCase;
76
import exception.ApplicationException;
87
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
98
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
9+
import io.github.resilience4j.retry.annotation.Retry;
1010
import io.netty.channel.ChannelOption;
11-
import lombok.extern.slf4j.Slf4j;
1211
import org.springframework.core.ParameterizedTypeReference;
1312
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
1413
import org.springframework.stereotype.Component;
@@ -20,7 +19,6 @@
2019

2120
import java.time.Duration;
2221

23-
@Slf4j
2422
@Component
2523
public class AuthWebClient implements AuthClient {
2624

@@ -35,20 +33,15 @@ public AuthWebClient(WebClient.Builder webClientBuilder) {
3533
.build();
3634
}
3735

36+
@Retry(name = "authServiceRetry")
3837
@CircuitBreaker(name = "authServiceCircuitBreaker", fallbackMethod = "validateTokenFallback")
3938
public Mono<UserCacheDto> validateToken(String token) {
4039
return webClient.post()
4140
.uri("http://auth/api/v1/auth/validate")
4241
.header("Authorization", token)
4342
.retrieve()
4443
.bodyToMono(new ParameterizedTypeReference<CommonResponse<UserCacheDto>>() {})
45-
.flatMap(response -> {
46-
if (response.getData() != null) {
47-
return Mono.just(response.getData());
48-
} else {
49-
return Mono.error(new ApplicationException(SecurityErrorCase.USER_CACHE_IS_NULL));
50-
}
51-
});
44+
.flatMap(response -> Mono.just(response.getData()));
5245
}
5346

5447
private Mono<UserCacheDto> validateTokenFallback(String token, Throwable ex) {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.ticketPing.gateway.infrastructure.config;
2+
3+
import io.github.resilience4j.retry.RetryRegistry;
4+
import io.github.resilience4j.retry.event.RetryOnRetryEvent;
5+
import jakarta.annotation.PostConstruct;
6+
import lombok.RequiredArgsConstructor;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.context.annotation.Configuration;
9+
10+
@Slf4j
11+
@Configuration
12+
@RequiredArgsConstructor
13+
public class RetryEventConfig {
14+
15+
private final RetryRegistry retryRegistry;
16+
17+
@PostConstruct
18+
public void registerRetryEventListeners() {
19+
retryRegistry.getAllRetries().forEach(retry -> {
20+
retry.getEventPublisher()
21+
.onRetry(this::logRetry);
22+
});
23+
}
24+
25+
private void logRetry(RetryOnRetryEvent event) {
26+
log.info("Retry for '{}' attempt number: {}",
27+
event.getName(),
28+
event.getNumberOfRetryAttempts());
29+
}
30+
}

gateway/src/main/java/com/ticketPing/gateway/infrastructure/filter/JwtFilter.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.ticketPing.gateway.infrastructure.filter;
22

3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
35
import com.ticketPing.gateway.application.client.AuthClient;
46
import exception.ApplicationException;
57
import lombok.RequiredArgsConstructor;
@@ -56,15 +58,33 @@ public Mono<SecurityContext> load(ServerWebExchange exchange) {
5658

5759
return Mono.just((SecurityContext) new SecurityContextImpl(authentication));
5860
})
59-
.onErrorResume(ApplicationException.class, e -> {
60-
exchange.getResponse().setStatusCode(HttpStatus.SERVICE_UNAVAILABLE);
61-
DataBuffer buffer = exchange.getResponse()
62-
.bufferFactory()
63-
.wrap(e.getMessage().getBytes(StandardCharsets.UTF_8));
64-
return exchange.getResponse().writeWith(Mono.just(buffer))
65-
.then(Mono.empty());
66-
})
67-
.onErrorResume(WebClientResponseException.Unauthorized.class, e -> Mono.empty());
61+
.onErrorResume(ApplicationException.class, e -> handleErrorResponse(exchange, e.getMessage(), HttpStatus.SERVICE_UNAVAILABLE))
62+
.onErrorResume(WebClientResponseException.Unauthorized.class, e -> {
63+
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
64+
return extractMessageFromResponse(e)
65+
.flatMap(message -> handleErrorResponse(exchange, message, HttpStatus.UNAUTHORIZED));
66+
});
67+
}
68+
69+
private Mono<SecurityContext> handleErrorResponse(ServerWebExchange exchange, String message, HttpStatus status) {
70+
exchange.getResponse().setStatusCode(status);
71+
DataBuffer buffer = exchange.getResponse()
72+
.bufferFactory()
73+
.wrap(message.getBytes(StandardCharsets.UTF_8));
74+
return exchange.getResponse().writeWith(Mono.just(buffer)).then(Mono.empty());
75+
}
76+
77+
private Mono<String> extractMessageFromResponse(WebClientResponseException e) {
78+
String responseBody = e.getResponseBodyAsString();
79+
80+
try {
81+
ObjectMapper objectMapper = new ObjectMapper();
82+
JsonNode jsonNode = objectMapper.readTree(responseBody);
83+
String message = jsonNode.path("message").asText("");
84+
return Mono.just(message);
85+
} catch (Exception ex) {
86+
return Mono.just("");
87+
}
6888
}
6989

7090
}

gateway/src/main/resources/application.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,24 @@ token-value:
3333
secret-key: ${USER_TOKEN_SECRET_KEY}
3434

3535
resilience4j:
36+
retry:
37+
retry-aspect-order: 2
38+
configs:
39+
default:
40+
max-attempts: 3
41+
wait-duration: 500ms
42+
authServiceRetry:
43+
retry-exceptions:
44+
- org.springframework.web.reactive.function.client.WebClientRequestException
45+
- org.springframework.web.reactive.function.client.WebClientResponseException.ServiceUnavailable
46+
3647
timelimiter:
3748
configs:
3849
default:
3950
timeout-duration: 15s
51+
4052
circuitbreaker:
53+
circuit-breaker-aspect-order: 1
4154
configs:
4255
default:
4356
registerHealthIndicator: true

services/auth/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ dependencies {
3939
implementation project(':common:dtos')
4040
implementation project(':common:caching')
4141
implementation project(':common:monitoring')
42-
implementation project(':common:circuit-breaker')
42+
implementation project(':common:resilience4j')
4343

44-
// MVC
44+
// MVC
4545
implementation 'org.springframework.boot:spring-boot-starter-web'
4646
implementation 'org.springframework.boot:spring-boot-starter-validation'
4747

services/auth/src/main/java/com/ticketPing/auth/AuthApplication.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
@SpringBootApplication
99
@EnableFeignClients
10-
@ComponentScan(basePackages = {"com.ticketPing.auth", "aop", "exception", "caching", "circuit"})
10+
@ComponentScan(basePackages = {"com.ticketPing.auth", "aop", "exception", "caching", "resilience4j"})
1111
public class AuthApplication {
1212
public static void main(String[] args) {
1313
SpringApplication.run(AuthApplication.class, args);

0 commit comments

Comments
 (0)