From 631f8868d233892a3cda82947c6e874596911638 Mon Sep 17 00:00:00 2001 From: huhdy32 Date: Fri, 9 Jan 2026 18:13:05 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(school):=20=ED=95=99=EA=B5=90=20?= =?UTF-8?q?=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8=EC=97=90=20rate?= =?UTF-8?q?=20limiter=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/external/school/SchoolClient.java | 22 +++++++++++++++++++ .../external/school/SchoolClientTest.java | 3 +++ 2 files changed, 25 insertions(+) diff --git a/client/external/mathrank-school/src/main/java/kr/co/mathrank/client/external/school/SchoolClient.java b/client/external/mathrank-school/src/main/java/kr/co/mathrank/client/external/school/SchoolClient.java index f8599c37..573771eb 100644 --- a/client/external/mathrank-school/src/main/java/kr/co/mathrank/client/external/school/SchoolClient.java +++ b/client/external/mathrank-school/src/main/java/kr/co/mathrank/client/external/school/SchoolClient.java @@ -11,6 +11,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.client.RestClient; +import io.github.resilience4j.ratelimiter.annotation.RateLimiter; import jakarta.validation.constraints.NotNull; import kr.co.mathrank.client.config.RestClientResponseDecorator; import kr.co.mathrank.client.config.TimeoutConfiguredClient; @@ -19,7 +20,9 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Component public class SchoolClient extends TimeoutConfiguredClient { @Value("${neice.school.key:}") @@ -39,6 +42,7 @@ public SchoolClient(final SchoolClientProperties schoolClientProperties) { } @Deprecated + @RateLimiter(name = "neiceApi", fallbackMethod = "fallBackSchoolInfo") public Optional getSchool(final String type, final String schoolCode) { if (schoolCode == null || schoolCode.isBlank()) { return Optional.empty(); @@ -58,11 +62,18 @@ public Optional getSchool(final String type, final String schoolCode return response.getSchoolInfo().isEmpty() ? Optional.empty() : Optional.of(response.getSchoolInfo().getFirst()); } + // fall back - #getSchool + private Optional fallBackSchoolInfo(final String type, final String schoolCode, final Throwable t) { + log.warn("[SchoolClient.fallBackSchoolInfo] fallback called - type: {}, schoolCode: {}", type, schoolCode, t); + return Optional.empty(); + } + public ClientResponse> getSchoolResponse(final String type, final String schoolCode) { return responseDecorator.wrap(() -> getSchool(type, schoolCode)); } @Deprecated + @RateLimiter(name = "neiceApi", fallbackMethod = "fallBackGetSchools") public SchoolResponse getSchools(String type, Integer pageIndex, Integer pageSize, String schoolName) { return restClient.get() .uri(uriBuilder -> uriBuilder.path("/hub/schoolInfo") @@ -75,6 +86,11 @@ public SchoolResponse getSchools(String type, Integer pageIndex, Integer pageSiz .body(SchoolResponse.class); } + private SchoolResponse fallBackGetSchools(String type, Integer pageIndex, Integer pageSize, String schoolName, Throwable t) { + log.warn("[SchoolClient.fallBackGetSchools] fallback called - type: {}, pageIndex: {}, pageSize: {}, schoolName: {}", type, pageIndex, pageSize, schoolName, t); + return new SchoolResponse(null); + } + public ClientResponse getSchoolsResponse(String type, Integer pageIndex, Integer pageSize, String schoolName) { return responseDecorator.wrap(() -> this.getSchools(type, pageIndex, pageSize, schoolName)); } @@ -88,6 +104,7 @@ public ClientResponse getSchoolsResponse(String type, Integer pa * @return 해당 도시의 학교 정보가 담긴 응답 객체 */ @Deprecated + @RateLimiter(name = "neiceApi", fallbackMethod = "fallBackGetSchoolsByCityName") public SchoolResponse getSchoolsByCityName(String type, String cityName) { return restClient.get() .uri(uriBuilder -> uriBuilder.path("/hub/schoolInfo") @@ -101,6 +118,11 @@ public SchoolResponse getSchoolsByCityName(String type, String cityName) { .body(SchoolResponse.class); } + private SchoolResponse fallBackGetSchoolsByCityName(String type, String cityName, Throwable t) { + log.warn("[SchoolClient.fallBackGetSchoolsByCityName] fallback called - type: {}, cityName: {}", type, cityName, t); + return new SchoolResponse(null); + } + public ClientResponse getSchoolsByCityNameResponse(String type, String cityName) { return responseDecorator.wrap(() -> this.getSchoolsByCityName(type, cityName)); } diff --git a/client/external/mathrank-school/src/test/java/kr/co/mathrank/client/external/school/SchoolClientTest.java b/client/external/mathrank-school/src/test/java/kr/co/mathrank/client/external/school/SchoolClientTest.java index bc05771f..ec4690a4 100644 --- a/client/external/mathrank-school/src/test/java/kr/co/mathrank/client/external/school/SchoolClientTest.java +++ b/client/external/mathrank-school/src/test/java/kr/co/mathrank/client/external/school/SchoolClientTest.java @@ -13,6 +13,9 @@ @SpringBootTest(properties = """ client.school.read-timeout-seconds=10 client.school.connection-timeout-seconds=10 +resilience4j.ratelimiter.instances.neiceApi.limit-refresh-period=1s +resilience4j.ratelimiter.instances.neiceApi.limit-for-period=100 +resilience4j.ratelimiter.instances.neiceApi.timeout-duration=1s """) class SchoolClientTest { @Autowired From ae02b317de7f5f74e17db173992d5af832d90dc8 Mon Sep 17 00:00:00 2001 From: huhdy32 Date: Fri, 9 Jan 2026 18:21:18 +0900 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20=EB=B9=A0=EC=A7=84=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/external/mathrank-school/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/external/mathrank-school/build.gradle b/client/external/mathrank-school/build.gradle index bd5fbb70..f33d29e9 100644 --- a/client/external/mathrank-school/build.gradle +++ b/client/external/mathrank-school/build.gradle @@ -1,4 +1,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0' + implementation 'org.springframework.boot:spring-boot-starter-aop' implementation 'org.springframework.boot:spring-boot-starter-validation' }