diff --git a/graal/pom.xml b/graal/pom.xml
index ca8b919b..7b006cde 100644
--- a/graal/pom.xml
+++ b/graal/pom.xml
@@ -28,6 +28,7 @@
sharing-comment
21
+ 2.1.0
@@ -112,6 +113,16 @@
0.12.3
runtime
+
+ io.github.resilience4j
+ resilience4j-circuitbreaker
+ ${resilience4jVersion}
+
+
+ io.github.resilience4j
+ resilience4j-all
+ ${resilience4jVersion}
+
diff --git a/graal/src/main/java/io/sixwaaaay/sharingcomment/client/UserClientWrapper.java b/graal/src/main/java/io/sixwaaaay/sharingcomment/client/UserClientWrapper.java
new file mode 100644
index 00000000..0e6bb414
--- /dev/null
+++ b/graal/src/main/java/io/sixwaaaay/sharingcomment/client/UserClientWrapper.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2023-2024 sixwaaaay.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.sixwaaaay.sharingcomment.client;
+
+import io.github.resilience4j.circuitbreaker.CircuitBreaker;
+import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
+import io.github.resilience4j.decorators.Decorators;
+import io.github.resilience4j.retry.Retry;
+import io.github.resilience4j.retry.RetryConfig;
+import io.sixwaaaay.sharingcomment.transmission.GetMultipleUserReply;
+import io.sixwaaaay.sharingcomment.transmission.GetUserReply;
+
+import java.time.Duration;
+import java.util.Collection;
+
+public class UserClientWrapper implements UserClient {
+ private final UserClient userClient;
+
+ private final CircuitBreaker circuitBreaker;
+
+ private final Retry retry;
+
+
+ public UserClientWrapper(UserClient userClient) {
+ this.userClient = userClient;
+ var name = "userClient";
+ var retryConfig = RetryConfig.custom()
+ .maxAttempts(3)
+ .waitDuration(Duration.ofMillis(1000))
+ .build();
+ retry = Retry.of(name, retryConfig);
+ var circuitBreakerConfig = CircuitBreakerConfig.custom()
+ .failureRateThreshold(50)
+ .waitDurationInOpenState(Duration.ofMillis(1000))
+ .permittedNumberOfCallsInHalfOpenState(2)
+ .slidingWindowSize(2)
+ .build();
+ circuitBreaker = CircuitBreaker.of(name, circuitBreakerConfig);
+ }
+
+ @Override
+ public GetUserReply getUser(long id, String token) {
+ var getUserReplySupplier = Decorators.ofSupplier(() -> userClient.getUser(id, token))
+ .withCircuitBreaker(circuitBreaker)
+ .withRetry(retry)
+ .decorate();
+ return getUserReplySupplier.get();
+ }
+
+
+ @Override
+ public GetMultipleUserReply getManyUser(Collection ids, String token) {
+ var getManyUserReplySupplier = Decorators.ofSupplier(() -> userClient.getManyUser(ids, token))
+ .withCircuitBreaker(circuitBreaker)
+ .withRetry(retry)
+ .decorate();
+ return getManyUserReplySupplier.get();
+ }
+}
diff --git a/graal/src/main/java/io/sixwaaaay/sharingcomment/client/VoteClientWrapper.java b/graal/src/main/java/io/sixwaaaay/sharingcomment/client/VoteClientWrapper.java
new file mode 100644
index 00000000..07b4bcc9
--- /dev/null
+++ b/graal/src/main/java/io/sixwaaaay/sharingcomment/client/VoteClientWrapper.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2023-2024 sixwaaaay.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.sixwaaaay.sharingcomment.client;
+
+import io.github.resilience4j.circuitbreaker.CircuitBreaker;
+import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
+import io.github.resilience4j.decorators.Decorators;
+import io.github.resilience4j.retry.Retry;
+import io.github.resilience4j.retry.RetryConfig;
+import io.sixwaaaay.sharingcomment.transmission.*;
+
+import java.time.Duration;
+import java.util.Set;
+
+public class VoteClientWrapper implements VoteClient {
+
+
+ private final VoteClient voteClient;
+
+ public VoteClientWrapper(VoteClient voteClient) {
+ this.voteClient = voteClient;
+ }
+
+ private final String name = "voteClient";
+
+ private final CircuitBreaker circuitBreaker = CircuitBreaker.of(name, CircuitBreakerConfig.custom()
+ .failureRateThreshold(50)
+ .waitDurationInOpenState(Duration.ofMillis(1000))
+ .permittedNumberOfCallsInHalfOpenState(2)
+ .slidingWindowSize(2)
+ .build()
+ );
+
+ private final Retry retry = Retry.of(name, RetryConfig.custom()
+ .maxAttempts(3)
+ .waitDuration(Duration.ofMillis(1000))
+ .build()
+ );
+
+ @Override
+ public VoteReply itemAdd(VoteReq req) {
+ return voteClient.itemAdd(req);
+ }
+
+ @Override
+ public VoteReply itemDelete(VoteReq req) {
+ return voteClient.itemDelete(req);
+ }
+
+ @Override
+ public VoteExistsReply exists(VoteExistsReq req) {
+ var existsReplySupplier = Decorators.ofSupplier(() -> voteClient.exists(req))
+ .withCircuitBreaker(circuitBreaker)
+ .withRetry(retry)
+ .withFallback(this::fallback)
+ .decorate();
+ return existsReplySupplier.get();
+ }
+
+ @Override
+ public ScanVotedReply scan(ScanVotedReq req) {
+ return voteClient.scan(req);
+ }
+
+ private VoteExistsReply fallback(Throwable e) {
+ var reply = new VoteExistsReply();
+ reply.setExists(Set.of());
+ return reply;
+ }
+}
diff --git a/graal/src/main/java/io/sixwaaaay/sharingcomment/config/Config.java b/graal/src/main/java/io/sixwaaaay/sharingcomment/config/Config.java
index dbf44690..562fea0c 100644
--- a/graal/src/main/java/io/sixwaaaay/sharingcomment/config/Config.java
+++ b/graal/src/main/java/io/sixwaaaay/sharingcomment/config/Config.java
@@ -14,7 +14,9 @@
package io.sixwaaaay.sharingcomment.config;
import io.sixwaaaay.sharingcomment.client.UserClient;
+import io.sixwaaaay.sharingcomment.client.UserClientWrapper;
import io.sixwaaaay.sharingcomment.client.VoteClient;
+import io.sixwaaaay.sharingcomment.client.VoteClientWrapper;
import io.sixwaaaay.sharingcomment.request.Principal;
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
import org.springframework.beans.factory.annotation.Value;
@@ -34,9 +36,8 @@ VoteClient repositoryService(
@Value("${service.vote.base-url}") String baseUrl,
RestClient.Builder restClientBuilder
) {
- return HttpServiceProxyFactory.builderFor(
- RestClientAdapter.create(restClientBuilder.baseUrl(baseUrl).build())
- ).build().createClient(VoteClient.class);
+ var client = createService(VoteClient.class, baseUrl, restClientBuilder);
+ return new VoteClientWrapper(client);
}
@Bean
@@ -44,9 +45,14 @@ UserClient userService(
@Value("${service.user.base-url}") String baseUrl,
RestClient.Builder restClientBuilder
) {
- return HttpServiceProxyFactory.builderFor(
- RestClientAdapter.create(restClientBuilder.baseUrl(baseUrl).build())
- ).build().createClient(UserClient.class);
+ var client = createService(UserClient.class, baseUrl, restClientBuilder);
+ return new UserClientWrapper(client);
}
+ private T createService(Class clazz, String baseUrl, RestClient.Builder restClientBuilder) {
+ var restClient = restClientBuilder.baseUrl(baseUrl).build();
+ var adapter = RestClientAdapter.create(restClient);
+ var proxyFactory = HttpServiceProxyFactory.builderFor(adapter);
+ return proxyFactory.build().createClient(clazz);
+ }
}
diff --git a/graal/src/main/java/io/sixwaaaay/sharingcomment/util/RetryWrapper.java b/graal/src/main/java/io/sixwaaaay/sharingcomment/util/RetryWrapper.java
deleted file mode 100644
index d36ed145..00000000
--- a/graal/src/main/java/io/sixwaaaay/sharingcomment/util/RetryWrapper.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (c) 2023-2024 sixwaaaay.
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.sixwaaaay.sharingcomment.util;
-
-import java.util.concurrent.Callable;
-import java.util.function.Predicate;
-
-
-
-/*
-example usage:
-RetryWrapper retryWrapper = new RetryWrapper<>(
- () -> userClient.getUser(id, token),
- 3,
- 1000,
- 2,
- e -> e instanceof IOException
-);
-GetUserReply reply = retryWrapper.retry();
-*/
-
-
-public class RetryWrapper {
- private final Callable callable;
- private final int maxAttempts;
- private final long initialDelay;
- private final double delayMultiplier;
- private final Predicate retryOn;
-
- public RetryWrapper(Callable callable, int maxAttempts, long initialDelay, double delayMultiplier, Predicate retryOn) {
- this.callable = callable;
- this.maxAttempts = maxAttempts;
- this.initialDelay = initialDelay;
- this.delayMultiplier = delayMultiplier;
- this.retryOn = retryOn;
- }
-
- public RetryWrapper(Callable callable, int maxAttempts, long initialDelay, double delayMultiplier, Class extends Exception> exceptionClass) {
- this.callable = callable;
- this.maxAttempts = maxAttempts;
- this.initialDelay = initialDelay;
- this.delayMultiplier = delayMultiplier;
- this.retryOn = exceptionClass::isInstance;
- }
-
- public T retry() throws Exception {
- long delay = initialDelay;
- for (int i = 0; i < maxAttempts; i++) {
- try {
- return callable.call();
- } catch (Exception e) {
- if (i == maxAttempts - 1 || !retryOn.test(e)) {
- throw e;
- }
- Thread.sleep(delay);
- delay *= (long) delayMultiplier;
- }
- }
- throw new Exception("Retry attempts exceeded");
- }
-
-}
diff --git a/graal/src/test/java/io/sixwaaaay/sharingcomment/SharingCommentApplicationTests.java b/graal/src/test/java/io/sixwaaaay/sharingcomment/SharingCommentApplicationTests.java
index 4e613879..af5b2f12 100644
--- a/graal/src/test/java/io/sixwaaaay/sharingcomment/SharingCommentApplicationTests.java
+++ b/graal/src/test/java/io/sixwaaaay/sharingcomment/SharingCommentApplicationTests.java
@@ -29,7 +29,6 @@
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
-import static org.junit.jupiter.api.Assertions.assertFalse;
@SpringBootTest
@AutoConfigureMockMvc
@@ -101,7 +100,6 @@ public void createCommentTest() throws Exception {
@Test
public void deleteComments() throws Exception {
- // todo: add payload
var token = jwtUtil.generateToken("n", "1");
var json = "{ \"content\": \"This is a test comment\", \"reply_to\": null, \"belong_to\": 1 }";
mockMvc.perform(MockMvcRequestBuilders.delete("/comments/21")