Skip to content

Commit

Permalink
Feat: circuit breaker로 날씨 API 이중화 (#213)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jaewon-pro authored Sep 19, 2024
1 parent e5c59fc commit 08c1576
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 8 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ dependencies {

implementation(libs.caffeine)

implementation(libs.resilience4j.spring.boot)

testImplementation(libs.bundles.spring.boot.test)
testImplementation(libs.bundles.testcontainers)
testRuntimeOnly(libs.junit.platform.launcher)
Expand Down
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ spring-boot-devtools = { group = "org.springframework.boot", name = "spring-boot
spring-boot-starter-test = { group = "org.springframework.boot", name = "spring-boot-starter-test", version.ref = "spring-boot" }
spring-boot-testcontainers = { group = "org.springframework.boot", name = "spring-boot-testcontainers", version.ref = "spring-boot" }
spring-boot-cache = { group = "org.springframework.boot", name = "spring-boot-starter-cache", version.ref = "spring-boot" }
spring-boot-aop = { group = "org.springframework.boot", name = "spring-boot-starter-aop", version.ref = "spring-boot" }

bcpkix = {group= "org.bouncycastle", name = "bcpkix-jdk18on", version = "1.78.1" }

Expand All @@ -44,6 +45,8 @@ junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform
spring-cloud-starter-contract-stub-runner = { group = "org.springframework.cloud", name = "spring-cloud-starter-contract-stub-runner", version = "4.1.4" }
caffeine = { group = "com.github.ben-manes.caffeine", name = "caffeine", version = "3.1.8" }

resilience4j-spring-boot = { group = "io.github.resilience4j", name = "resilience4j-spring-boot3", version = "2.2.0" }

[bundles]
spring-boot = [
"spring-boot-starter",
Expand All @@ -53,6 +56,7 @@ spring-boot = [
"spring-boot-starter-actuator",
"spring-boot-starter-data-jpa",
"spring-boot-cache",
"spring-boot-aop",
]

spring-boot-test = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.dnd.runus.infrastructure.weather;

import com.dnd.runus.global.constant.WeatherType;
import com.dnd.runus.global.exception.BusinessException;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Slf4j
@Primary
@Component
public class WeatherClientProxy implements WeatherClient {

private final WeatherClient mainWeatherClient;
private final WeatherClient fallbackWeatherClient;

public WeatherClientProxy(
@Qualifier("openweathermapWeatherClient") WeatherClient openweathermapWeatherClient,
@Qualifier("weatherApiComWeatherClient") WeatherClient weatherApiComWeatherClient) {
this.mainWeatherClient = openweathermapWeatherClient;
this.fallbackWeatherClient = weatherApiComWeatherClient;
}

@Override
@CircuitBreaker(name = "weatherClient", fallbackMethod = "fallback")
public WeatherInfo getWeatherInfo(double longitude, double latitude) {
return mainWeatherClient.getWeatherInfo(longitude, latitude);
}

public WeatherInfo fallback(double longitude, double latitude, BusinessException e) {
log.error("Business exception occurred. type: {}, message: {}", e.getType(), e.getMessage());
return fallbackWeatherClient.getWeatherInfo(longitude, latitude);
}

public WeatherInfo fallback(double longitude, double latitude, CallNotPermittedException e) {
log.error("Circuit breaker is open. {}", e.getMessage());
return fallbackWeatherClient.getWeatherInfo(longitude, latitude);
}

public WeatherInfo fallback(Exception e) {
log.error("Fallback occurred. {}", e.getMessage());
return new WeatherInfo(WeatherType.CLOUDY, 0, 0, 0, 0, 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public class OpenweathermapWeatherHttpClientConfig {
@Bean
public OpenweathermapWeatherHttpClient openweathermapWeatherHttpClient() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
.withReadTimeout(Duration.ofSeconds(5))
.withConnectTimeout(Duration.ofSeconds(10));
.withReadTimeout(Duration.ofSeconds(1))
.withConnectTimeout(Duration.ofSeconds(3));
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings);

RestClient restClient =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
import com.dnd.runus.infrastructure.weather.weatherapicom.dto.WeatherapicomHistory;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import static com.dnd.runus.global.exception.type.ErrorType.WEATHER_API_ERROR;
import static org.apache.commons.lang3.ObjectUtils.isEmpty;

@Component
@RequiredArgsConstructor
public class WeatherApiComWeatherClient implements WeatherClient {
private final WeatherapicomWeatherHttpClient weatherapicomWeatherHttpClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public class WeatherapicomWeatherHttpClientConfig {
@Bean
public WeatherapicomWeatherHttpClient weatherapicomWeatherHttpClient() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
.withReadTimeout(Duration.ofSeconds(5))
.withConnectTimeout(Duration.ofSeconds(10));
.withReadTimeout(Duration.ofSeconds(1))
.withConnectTimeout(Duration.ofSeconds(3));
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings);

RestClient restClient =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.dnd.runus.presentation.v1.weather;

import com.dnd.runus.application.weather.WeatherService;
import com.dnd.runus.global.exception.type.ApiErrorType;
import com.dnd.runus.global.exception.type.ErrorType;
import com.dnd.runus.presentation.v1.weather.dto.WeatherResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -19,7 +17,6 @@ public class WeatherController {

@GetMapping
@ResponseStatus(HttpStatus.OK)
@ApiErrorType({ErrorType.WEATHER_API_ERROR})
@Operation(summary = "날씨 정보 조회", description = "경도와 위도를 입력받아 날씨 정보를 조회합니다.")
public WeatherResponse getWeather(@RequestParam double longitude, @RequestParam double latitude) {
return weatherService.getWeather(longitude, latitude);
Expand Down
5 changes: 4 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ management:
endpoints:
web:
exposure:
include: health, metrics
include: health, info, circuitbreakers
health:
circuitbreakers:
enabled: true

---
spring.config.activate.on-profile: local
Expand Down

0 comments on commit 08c1576

Please sign in to comment.