Skip to content

Commit ff5f427

Browse files
Merge pull request #213 from mju-likelion/feature/caching-circuit-breaker-#211
Feature/#211 캐싱 서킷 브레이커 적용
2 parents 36aa881 + 6596915 commit ff5f427

File tree

9 files changed

+227
-2
lines changed

9 files changed

+227
-2
lines changed

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ dependencies {
4141

4242
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3'// Jackson
4343

44+
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.0.2'// Resilience4j
45+
4446
testImplementation 'org.springframework.boot:spring-boot-starter-test'
4547
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
4648
}

src/main/java/org/mju_likelion/festival/booth/service/BoothQueryService.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package org.mju_likelion.festival.booth.service;
22

3+
import static org.mju_likelion.festival.common.config.Resilience4jConfig.REDIS_CIRCUIT_BREAKER;
34
import static org.mju_likelion.festival.common.exception.type.ErrorType.BOOTH_DEPARTMENT_NOT_FOUND_ERROR;
45
import static org.mju_likelion.festival.common.exception.type.ErrorType.BOOTH_NOT_FOUND_ERROR;
56
import static org.mju_likelion.festival.common.exception.type.ErrorType.NOT_BOOTH_OWNER_ERROR;
67
import static org.mju_likelion.festival.common.exception.type.ErrorType.NOT_EVENT_BOOTH_ERROR;
78

9+
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
810
import java.util.List;
911
import java.util.UUID;
1012
import lombok.RequiredArgsConstructor;
13+
import lombok.extern.slf4j.Slf4j;
1114
import org.mju_likelion.festival.admin.domain.Admin;
1215
import org.mju_likelion.festival.admin.service.AdminQueryService;
1316
import org.mju_likelion.festival.booth.domain.Booth;
@@ -24,13 +27,15 @@
2427
import org.mju_likelion.festival.booth.dto.response.SimpleBoothResponse;
2528
import org.mju_likelion.festival.booth.dto.response.SimpleBoothResponses;
2629
import org.mju_likelion.festival.booth.util.qr.manager.BoothQrManager;
30+
import org.mju_likelion.festival.common.circuit_breaker.FallBackUtil;
2731
import org.mju_likelion.festival.common.exception.BadRequestException;
2832
import org.mju_likelion.festival.common.exception.ForbiddenException;
2933
import org.mju_likelion.festival.common.exception.NotFoundException;
3034
import org.springframework.cache.annotation.Cacheable;
3135
import org.springframework.stereotype.Service;
3236
import org.springframework.transaction.annotation.Transactional;
3337

38+
@Slf4j
3439
@Service
3540
@RequiredArgsConstructor
3641
@Transactional(readOnly = true)
@@ -49,7 +54,17 @@ public List<BoothAffiliationResponse> getBoothAffiliations() {
4954
}
5055

5156
@Cacheable(value = "simpleBooths", key = "#affiliationId")
57+
@CircuitBreaker(name = REDIS_CIRCUIT_BREAKER, fallbackMethod = "getBoothsFallback")
5258
public SimpleBoothResponses getBooths(final UUID affiliationId) {
59+
return getBoothsLogic(affiliationId);
60+
}
61+
62+
public SimpleBoothResponses getBoothsFallback(final UUID affiliationId, final Exception e) {
63+
FallBackUtil.handleFallBack(e);
64+
return getBoothsLogic(affiliationId);
65+
}
66+
67+
private SimpleBoothResponses getBoothsLogic(final UUID affiliationId) {
5368
validateBoothDepartment(affiliationId);
5469

5570
List<SimpleBooth> simpleBooths = boothQueryRepository.findAllSimpleBoothByAffiliationId(
@@ -63,12 +78,39 @@ public SimpleBoothResponses getBooths(final UUID affiliationId) {
6378
}
6479

6580
@Cacheable(value = "boothDetail", key = "#id")
81+
@CircuitBreaker(name = REDIS_CIRCUIT_BREAKER, fallbackMethod = "getBoothFallback")
6682
public BoothDetailResponse getBooth(final UUID id) {
83+
return getBoothLogic(id);
84+
}
85+
86+
public BoothDetailResponse getBoothFallback(final UUID id, final Exception e) {
87+
FallBackUtil.handleFallBack(e);
88+
return getBoothLogic(id);
89+
}
90+
91+
private BoothDetailResponse getBoothLogic(final UUID id) {
6792
return BoothDetailResponse.from(getExistingBoothDetail(id));
6893
}
6994

7095
@Cacheable(value = "boothManagingDetail", key = "#boothId + ' : ' + #boothAdminId")
71-
public BoothManagingDetailResponse getBoothManagingDetail(final UUID boothId,
96+
@CircuitBreaker(name = REDIS_CIRCUIT_BREAKER, fallbackMethod = "getBoothManagingDetailFallback")
97+
public BoothManagingDetailResponse getBoothManagingDetail(
98+
final UUID boothId,
99+
final UUID boothAdminId) {
100+
101+
return getBoothManagingDetailLogic(boothId, boothAdminId);
102+
}
103+
104+
public BoothManagingDetailResponse getBoothManagingDetailFallback(
105+
final UUID boothId,
106+
final UUID boothAdminId,
107+
final Exception e) {
108+
109+
FallBackUtil.handleFallBack(e);
110+
return getBoothManagingDetailLogic(boothId, boothAdminId);
111+
}
112+
113+
private BoothManagingDetailResponse getBoothManagingDetailLogic(final UUID boothId,
72114
final UUID boothAdminId) {
73115
validateBoothExistence(boothId);
74116
adminQueryService.validateAdminExistence(boothAdminId);

src/main/java/org/mju_likelion/festival/booth/service/BoothService.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.mju_likelion.festival.booth.service;
22

3+
import static org.mju_likelion.festival.common.config.Resilience4jConfig.REDIS_CIRCUIT_BREAKER;
4+
5+
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
36
import java.util.UUID;
47
import lombok.RequiredArgsConstructor;
58
import org.mju_likelion.festival.admin.domain.Admin;
@@ -9,6 +12,7 @@
912
import org.mju_likelion.festival.booth.dto.request.UpdateBoothRequest;
1013
import org.mju_likelion.festival.booth.util.qr.BoothQrStrategy;
1114
import org.mju_likelion.festival.booth.util.qr.manager.BoothQrManager;
15+
import org.mju_likelion.festival.common.circuit_breaker.FallBackUtil;
1216
import org.mju_likelion.festival.user.domain.User;
1317
import org.mju_likelion.festival.user.service.UserQueryService;
1418
import org.springframework.cache.annotation.CacheEvict;
@@ -44,11 +48,30 @@ public void visitBooth(
4448
}
4549

4650
@CacheEvict(value = "boothDetail", key = "#boothId")
51+
@CircuitBreaker(name = REDIS_CIRCUIT_BREAKER, fallbackMethod = "updateBoothFallback")
4752
public void updateBooth(
4853
final UUID boothId,
4954
final UpdateBoothRequest updateBoothRequest,
5055
final UUID boothAdminId) {
5156

57+
updateBoothLogic(boothId, updateBoothRequest, boothAdminId);
58+
}
59+
60+
public void updateBoothFallback(
61+
final UUID boothId,
62+
final UpdateBoothRequest updateBoothRequest,
63+
final UUID boothAdminId,
64+
final Exception e) {
65+
66+
FallBackUtil.handleFallBack(e);
67+
updateBoothLogic(boothId, updateBoothRequest, boothAdminId);
68+
}
69+
70+
public void updateBoothLogic(
71+
final UUID boothId,
72+
final UpdateBoothRequest updateBoothRequest,
73+
final UUID boothAdminId) {
74+
5275
Admin admin = adminQueryService.getExistingAdmin(boothAdminId);
5376
Booth booth = boothQueryService.getExistingBooth(boothId);
5477

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.mju_likelion.festival.common.circuit_breaker;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.mju_likelion.festival.common.exception.CustomException;
5+
6+
@Slf4j
7+
public class FallBackUtil {
8+
9+
public static void handleFallBack(final Exception e) {
10+
if (e instanceof CustomException) {
11+
throw (CustomException) e;
12+
}
13+
14+
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
15+
StackTraceElement caller = stackTrace[1];
16+
writeLog(e, caller);
17+
}
18+
19+
private static void writeLog(final Exception e, final StackTraceElement caller) {
20+
log.info("Fallback to method {}(): {}, {}",
21+
caller.getMethodName(),
22+
e.getClass().getSimpleName(),
23+
e.getMessage()
24+
);
25+
}
26+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.mju_likelion.festival.common.config;
2+
3+
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
4+
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
5+
import io.lettuce.core.RedisException;
6+
import java.time.Duration;
7+
import org.mju_likelion.festival.common.exception.CustomException;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
import org.springframework.data.redis.RedisConnectionFailureException;
11+
import org.springframework.data.redis.RedisSystemException;
12+
13+
@Configuration
14+
public class Resilience4jConfig {
15+
16+
public static final String REDIS_CIRCUIT_BREAKER = "redis";
17+
18+
@Bean
19+
public CircuitBreakerRegistry redisCircuitBreakerRegistry() {
20+
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
21+
.failureRateThreshold(80)
22+
.slidingWindowSize(10)
23+
.permittedNumberOfCallsInHalfOpenState(5)
24+
.waitDurationInOpenState(Duration.ofHours(1))
25+
.recordExceptions(RedisException.class, RedisSystemException.class,
26+
RedisConnectionFailureException.class)
27+
.ignoreExceptions(CustomException.class)
28+
.build();
29+
30+
CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);
31+
circuitBreakerRegistry.circuitBreaker(REDIS_CIRCUIT_BREAKER, circuitBreakerConfig);
32+
return circuitBreakerRegistry;
33+
}
34+
}

src/main/java/org/mju_likelion/festival/lost_item/service/LostItemQueryService.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package org.mju_likelion.festival.lost_item.service;
22

3+
import static org.mju_likelion.festival.common.config.Resilience4jConfig.REDIS_CIRCUIT_BREAKER;
34
import static org.mju_likelion.festival.common.exception.type.ErrorType.LOST_ITEM_NOT_FOUND_ERROR;
45
import static org.mju_likelion.festival.common.exception.type.ErrorType.PAGE_OUT_OF_BOUND_ERROR;
56

7+
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
68
import java.util.List;
79
import java.util.UUID;
810
import lombok.RequiredArgsConstructor;
11+
import org.mju_likelion.festival.common.circuit_breaker.FallBackUtil;
912
import org.mju_likelion.festival.common.enums.SortOrder;
1013
import org.mju_likelion.festival.common.exception.NotFoundException;
1114
import org.mju_likelion.festival.lost_item.domain.LostItem;
@@ -59,7 +62,17 @@ public SimpleLostItemsResponse searchLostItems(
5962
}
6063

6164
@Cacheable(value = "lostItem", key = "#lostItemId")
65+
@CircuitBreaker(name = REDIS_CIRCUIT_BREAKER, fallbackMethod = "getLostItemFallback")
6266
public LostItemDetailResponse getLostItem(final UUID lostItemId) {
67+
return getLostItemLogic(lostItemId);
68+
}
69+
70+
public LostItemDetailResponse getLostItemFallback(final UUID lostItemId, final Exception e) {
71+
FallBackUtil.handleFallBack(e);
72+
return getLostItemLogic(lostItemId);
73+
}
74+
75+
private LostItemDetailResponse getLostItemLogic(final UUID lostItemId) {
6376
validateLostItemExistence(lostItemId);
6477
LostItemDetail lostItemDetail = lostItemQueryRepository.findLostItemDetailById(lostItemId);
6578

src/main/java/org/mju_likelion/festival/lost_item/service/LostItemService.java

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package org.mju_likelion.festival.lost_item.service;
22

3+
import static org.mju_likelion.festival.common.config.Resilience4jConfig.REDIS_CIRCUIT_BREAKER;
34
import static org.mju_likelion.festival.common.util.null_handler.NullHandler.doIfNotNull;
45

6+
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
57
import java.util.UUID;
68
import lombok.RequiredArgsConstructor;
79
import org.mju_likelion.festival.admin.domain.Admin;
810
import org.mju_likelion.festival.admin.service.AdminQueryService;
11+
import org.mju_likelion.festival.common.circuit_breaker.FallBackUtil;
912
import org.mju_likelion.festival.image.domain.Image;
1013
import org.mju_likelion.festival.lost_item.domain.LostItem;
1114
import org.mju_likelion.festival.lost_item.domain.repository.LostItemJpaRepository;
@@ -42,11 +45,30 @@ public void createLostItem(
4245
}
4346

4447
@CacheEvict(value = "lostItem", key = "#lostItemId")
48+
@CircuitBreaker(name = REDIS_CIRCUIT_BREAKER, fallbackMethod = "updateLostItemFallback")
4549
public void updateLostItem(
4650
final UUID lostItemId,
4751
final UpdateLostItemRequest updateLostItemRequest,
4852
final UUID studentCouncilId) {
4953

54+
updateLostItemLogic(lostItemId, updateLostItemRequest, studentCouncilId);
55+
}
56+
57+
public void updateLostItemFallback(
58+
final UUID lostItemId,
59+
final UpdateLostItemRequest updateLostItemRequest,
60+
final UUID studentCouncilId,
61+
final Exception e) {
62+
63+
FallBackUtil.handleFallBack(e);
64+
updateLostItemLogic(lostItemId, updateLostItemRequest, studentCouncilId);
65+
}
66+
67+
private void updateLostItemLogic(
68+
final UUID lostItemId,
69+
final UpdateLostItemRequest updateLostItemRequest,
70+
final UUID studentCouncilId) {
71+
5072
LostItem lostItem = lostItemQueryService.getExistLostItem(lostItemId);
5173
adminQueryService.validateAdminExistence(studentCouncilId);
5274

@@ -55,12 +77,31 @@ public void updateLostItem(
5577
lostItemJpaRepository.save(lostItem);
5678
}
5779

58-
@CacheEvict(value = "lostItem", key = "#lostItemId")
80+
@CacheEvict(value = "lostItem", key = "#lostItemId", beforeInvocation = true)
81+
@CircuitBreaker(name = REDIS_CIRCUIT_BREAKER, fallbackMethod = "foundLostItemFallback")
5982
public void foundLostItem(
6083
final UUID lostItemId,
6184
final LostItemFoundRequest lostItemFoundRequest,
6285
final UUID studentCouncilId) {
6386

87+
foundLostItemLogic(lostItemId, lostItemFoundRequest, studentCouncilId);
88+
}
89+
90+
public void foundLostItemFallback(
91+
final UUID lostItemId,
92+
final LostItemFoundRequest lostItemFoundRequest,
93+
final UUID studentCouncilId,
94+
final Exception e) {
95+
96+
FallBackUtil.handleFallBack(e);
97+
foundLostItemLogic(lostItemId, lostItemFoundRequest, studentCouncilId);
98+
}
99+
100+
public void foundLostItemLogic(
101+
final UUID lostItemId,
102+
final LostItemFoundRequest lostItemFoundRequest,
103+
final UUID studentCouncilId) {
104+
64105
LostItem lostItem = lostItemQueryService.getExistLostItem(lostItemId);
65106
adminQueryService.validateAdminExistence(studentCouncilId);
66107

@@ -70,7 +111,21 @@ public void foundLostItem(
70111
}
71112

72113
@CacheEvict(value = "lostItem", key = "#lostItemId")
114+
@CircuitBreaker(name = REDIS_CIRCUIT_BREAKER, fallbackMethod = "deleteLostItemFallback")
73115
public void deleteLostItem(final UUID lostItemId, final UUID studentCouncilId) {
116+
deleteLostItemLogic(lostItemId, studentCouncilId);
117+
}
118+
119+
public void deleteLostItemFallback(
120+
final UUID lostItemId,
121+
final UUID studentCouncilId,
122+
final Exception e) {
123+
124+
FallBackUtil.handleFallBack(e);
125+
deleteLostItemLogic(lostItemId, studentCouncilId);
126+
}
127+
128+
private void deleteLostItemLogic(final UUID lostItemId, final UUID studentCouncilId) {
74129
LostItem lostItem = lostItemQueryService.getExistLostItem(lostItemId);
75130
adminQueryService.validateAdminExistence(studentCouncilId);
76131

src/main/java/org/mju_likelion/festival/term/service/TermQueryService.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package org.mju_likelion.festival.term.service;
22

3+
import static org.mju_likelion.festival.common.config.Resilience4jConfig.REDIS_CIRCUIT_BREAKER;
4+
5+
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
36
import java.util.List;
47
import java.util.Map;
58
import java.util.Set;
69
import java.util.UUID;
710
import java.util.stream.Collectors;
811
import lombok.RequiredArgsConstructor;
12+
import org.mju_likelion.festival.common.circuit_breaker.FallBackUtil;
913
import org.mju_likelion.festival.common.exception.BadRequestException;
1014
import org.mju_likelion.festival.common.exception.type.ErrorType;
1115
import org.mju_likelion.festival.term.domain.Term;
@@ -25,7 +29,17 @@ public class TermQueryService {
2529
private final TermJpaRepository termJpaRepository;
2630

2731
@Cacheable(value = "terms")
32+
@CircuitBreaker(name = REDIS_CIRCUIT_BREAKER, fallbackMethod = "getTermsFallback")
2833
public List<TermResponse> getTerms() {
34+
return getTermsLogic();
35+
}
36+
37+
public List<TermResponse> getTermsFallback(final Exception e) {
38+
FallBackUtil.handleFallBack(e);
39+
return getTermsLogic();
40+
}
41+
42+
private List<TermResponse> getTermsLogic() {
2943
return termQueryRepository.findTermsByOrderBySequenceAsc().stream()
3044
.map(TermResponse::of)
3145
.collect(Collectors.toList());

0 commit comments

Comments
 (0)