Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package circuit.config;
package resilience4j.config;

import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnErrorEvent;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package resilience4j.config;

import io.github.resilience4j.retry.RetryRegistry;
import io.github.resilience4j.retry.event.RetryOnRetryEvent;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
@RequiredArgsConstructor
public class RetryEventConfig {

private final RetryRegistry retryRegistry;

@PostConstruct
public void registerRetryEventListeners() {
retryRegistry.getAllRetries().forEach(retry -> {
retry.getEventPublisher()
.onRetry(this::logRetry);
});
}

private void logRetry(RetryOnRetryEvent event) {
log.info("Retry for '{}' attempt number: {}",
event.getName(),
event.getNumberOfRetryAttempts());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@ spring:
circuitbreaker: enabled

resilience4j:
retry:
retry-aspect-order: 2
configs:
default:
max-attempts: 3
wait-duration: 500ms
retry-exceptions:
- feign.RetryableException
- feign.FeignException.ServiceUnavailable

circuitbreaker:
circuit-breaker-aspect-order: 1
configs:
default:
registerHealthIndicator: true
Expand All @@ -18,10 +29,6 @@ resilience4j:
permittedNumberOfCallsInHalfOpenState: 3
recordSlowCalls: true
record-exceptions:
- java.util.concurrent.TimeoutException
- java.net.SocketTimeoutException
- java.net.UnknownHostException
- java.net.ConnectException
- feign.RetryableException
- feign.FeignException.GatewayTimeout
- feign.FeignException.BadGateway
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
import auth.UserCacheDto;
import com.ticketPing.gateway.application.client.AuthClient;
import com.ticketPing.gateway.common.exception.CircuitBreakerErrorCase;
import com.ticketPing.gateway.common.exception.SecurityErrorCase;
import exception.ApplicationException;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import io.netty.channel.ChannelOption;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Component;
Expand All @@ -20,7 +19,6 @@

import java.time.Duration;

@Slf4j
@Component
public class AuthWebClient implements AuthClient {

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

@Retry(name = "authServiceRetry")
@CircuitBreaker(name = "authServiceCircuitBreaker", fallbackMethod = "validateTokenFallback")
public Mono<UserCacheDto> validateToken(String token) {
return webClient.post()
.uri("http://auth/api/v1/auth/validate")
.header("Authorization", token)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<CommonResponse<UserCacheDto>>() {})
.flatMap(response -> {
if (response.getData() != null) {
return Mono.just(response.getData());
} else {
return Mono.error(new ApplicationException(SecurityErrorCase.USER_CACHE_IS_NULL));
}
});
.flatMap(response -> Mono.just(response.getData()));
}

private Mono<UserCacheDto> validateTokenFallback(String token, Throwable ex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.ticketPing.gateway.infrastructure.config;

import io.github.resilience4j.retry.RetryRegistry;
import io.github.resilience4j.retry.event.RetryOnRetryEvent;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
@RequiredArgsConstructor
public class RetryEventConfig {

private final RetryRegistry retryRegistry;

@PostConstruct
public void registerRetryEventListeners() {
retryRegistry.getAllRetries().forEach(retry -> {
retry.getEventPublisher()
.onRetry(this::logRetry);
});
}

private void logRetry(RetryOnRetryEvent event) {
log.info("Retry for '{}' attempt number: {}",
event.getName(),
event.getNumberOfRetryAttempts());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.ticketPing.gateway.infrastructure.filter;

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

return Mono.just((SecurityContext) new SecurityContextImpl(authentication));
})
.onErrorResume(ApplicationException.class, e -> {
exchange.getResponse().setStatusCode(HttpStatus.SERVICE_UNAVAILABLE);
DataBuffer buffer = exchange.getResponse()
.bufferFactory()
.wrap(e.getMessage().getBytes(StandardCharsets.UTF_8));
return exchange.getResponse().writeWith(Mono.just(buffer))
.then(Mono.empty());
})
.onErrorResume(WebClientResponseException.Unauthorized.class, e -> Mono.empty());
.onErrorResume(ApplicationException.class, e -> handleErrorResponse(exchange, e.getMessage(), HttpStatus.SERVICE_UNAVAILABLE))
.onErrorResume(WebClientResponseException.Unauthorized.class, e -> {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return extractMessageFromResponse(e)
.flatMap(message -> handleErrorResponse(exchange, message, HttpStatus.UNAUTHORIZED));
});
}

private Mono<SecurityContext> handleErrorResponse(ServerWebExchange exchange, String message, HttpStatus status) {
exchange.getResponse().setStatusCode(status);
DataBuffer buffer = exchange.getResponse()
.bufferFactory()
.wrap(message.getBytes(StandardCharsets.UTF_8));
return exchange.getResponse().writeWith(Mono.just(buffer)).then(Mono.empty());
}

private Mono<String> extractMessageFromResponse(WebClientResponseException e) {
String responseBody = e.getResponseBodyAsString();

try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
String message = jsonNode.path("message").asText("");
return Mono.just(message);
} catch (Exception ex) {
return Mono.just("");
}
}

}
13 changes: 13 additions & 0 deletions gateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,24 @@ token-value:
secret-key: ${USER_TOKEN_SECRET_KEY}

resilience4j:
retry:
retry-aspect-order: 2
configs:
default:
max-attempts: 3
wait-duration: 500ms
authServiceRetry:
retry-exceptions:
- org.springframework.web.reactive.function.client.WebClientRequestException
- org.springframework.web.reactive.function.client.WebClientResponseException.ServiceUnavailable

timelimiter:
configs:
default:
timeout-duration: 15s

circuitbreaker:
circuit-breaker-aspect-order: 1
configs:
default:
registerHealthIndicator: true
Expand Down
4 changes: 2 additions & 2 deletions services/auth/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ dependencies {
implementation project(':common:dtos')
implementation project(':common:caching')
implementation project(':common:monitoring')
implementation project(':common:circuit-breaker')
implementation project(':common:resilience4j')

// MVC
// MVC
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@SpringBootApplication
@EnableFeignClients
@ComponentScan(basePackages = {"com.ticketPing.auth", "aop", "exception", "caching", "circuit"})
@ComponentScan(basePackages = {"com.ticketPing.auth", "aop", "exception", "caching", "resilience4j"})
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import feign.RetryableException;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -18,6 +19,7 @@
@FeignClient(name = "user", configuration = CustomFeignConfig.class)
public interface UserFeignClient extends UserClient {
@GetMapping("/api/v1/users/login")
@Retry(name = "userServiceRetry")
@CircuitBreaker(name = "userServiceCircuitBreaker", fallbackMethod = "fallbackForUserService")
CommonResponse<UserResponse> getUserByEmailAndPassword(@RequestBody UserLookupRequest userLookupRequest);

Expand Down
5 changes: 5 additions & 0 deletions services/auth/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ jwt:
expiration: 604800000 # 7일

resilience4j:
retry:
instances:
userServiceRetry:
baseConfig: default

circuitbreaker:
instances:
userServiceCircuitBreaker:
Expand Down
4 changes: 2 additions & 2 deletions services/order/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ dependencies {
implementation project(':common:rdb')
implementation project(':common:messaging')
implementation project(':common:monitoring')
implementation project(':common:circuit-breaker')
implementation project(':common:resilience4j')

// MVC
// MVC
implementation 'org.springframework.boot:spring-boot-starter-web'

// Cloud
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@SpringBootApplication
@EnableFeignClients
@ComponentScan(basePackages = {"com.ticketPing.order", "aop", "exception", "audit", "messaging", "circuit"})
@ComponentScan(basePackages = {"com.ticketPing.order", "aop", "exception", "audit", "messaging", "resilience4j"})
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.ticketPing.order.application.client.PaymentClient;
import com.ticketPing.order.infrastructure.config.CustomFeignConfig;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -18,6 +19,7 @@
@FeignClient(name = "payment", configuration = CustomFeignConfig.class)
public interface PaymentFeignClient extends PaymentClient {
@GetMapping("/api/v1/payments/completed")
@Retry(name = "paymentServiceRetry")
@CircuitBreaker(name = "paymentServiceCircuitBreaker", fallbackMethod = "fallbackForPaymentService")
ResponseEntity<CommonResponse<PaymentResponse>> getCompletedPaymentByOrderId(@RequestParam("orderId") UUID orderId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.ticketPing.order.application.client.PerformanceClient;
import com.ticketPing.order.infrastructure.config.CustomFeignConfig;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -19,12 +20,14 @@
public interface PerformanceFeignClient extends PerformanceClient {

@GetMapping("/api/v1/client/seats/{seatId}/order-info")
@Retry(name = "performanceServiceRetry")
@CircuitBreaker(name = "performanceServiceCircuitBreaker", fallbackMethod = "fallbackForGetOrderInfo")
ResponseEntity<CommonResponse<OrderSeatResponse>> getOrderInfo(@RequestHeader("X_USER_ID") UUID userId,
@RequestParam("scheduleId") UUID scheduleId,
@PathVariable("seatId") UUID seatId);

@PostMapping("/api/v1/client/seats/{seatId}/extend-ttl")
@Retry(name = "performanceServiceRetry")
@CircuitBreaker(name = "performanceServiceCircuitBreaker", fallbackMethod = "fallbackForExtendTTL")
ResponseEntity<CommonResponse<Object>> extendPreReserveTTL(@RequestParam("scheduleId") UUID scheduleId,
@PathVariable("seatId") UUID seatId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.ticketPing.order.presentation.controller;

import com.ticketPing.order.application.client.PaymentClient;
import com.ticketPing.order.application.client.PerformanceClient;
import com.ticketPing.order.application.dtos.OrderResponse;
import com.ticketPing.order.application.service.OrderService;
import com.ticketPing.order.presentation.request.CreateOrderRequest;
Expand All @@ -22,6 +24,15 @@
public class OrderController {

private final OrderService orderService;
private final PaymentClient performanceClient;

@PostMapping("/test")
public ResponseEntity<CommonResponse<Object>> test() {
performanceClient.getCompletedPaymentByOrderId(UUID.randomUUID());
return ResponseEntity
.status(200)
.body(success());
}

@Operation(summary = "예매 좌석 생성")
@PostMapping
Expand Down
7 changes: 7 additions & 0 deletions services/order/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ server:
port: 10013

resilience4j:
retry:
instances:
performanceServiceRetry:
baseConfig: default
paymentServiceRetry:
baseConfig: default

circuitbreaker:
instances:
performanceServiceCircuitBreaker:
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ include ("common:rdb")
include ("common:caching")
include ("common:messaging")
include ("common:monitoring")
include ("common:circuit-breaker")
include ("common:resilience4j")

include ("services:auth")
include ("services:user")
Expand Down
Loading