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
6 changes: 6 additions & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,10 @@ object Dependencies {

// Spring Cloud Config
const val SPRING_CLOUD_CONFIG = "org.springframework.cloud:spring-cloud-starter-config"

//Resilience4j
const val RESILIENCE4J_CIRCUITBREAKER = "io.github.resilience4j:resilience4j-circuitbreaker:${DependencyVersion.RESILIENCE4J}"
const val RESILIENCE4J_RETRY = "io.github.resilience4j:resilience4j-retry:${DependencyVersion.RESILIENCE4J}"
const val RESILIENCE4J_SPRING_BOOT = "io.github.resilience4j:resilience4j-spring-boot3:${DependencyVersion.RESILIENCE4J}"
const val RESILIENCE4J_KOTLIN = "io.github.resilience4j:resilience4j-kotlin:${DependencyVersion.RESILIENCE4J}"
}
3 changes: 3 additions & 0 deletions buildSrc/src/main/kotlin/DependencyVersion.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ object DependencyVersion {
const val OPEN_FEIGN_VERSION = "3.1.4"

const val SPRING_CLOUD_CONFIG_VERSION = "2023.0.3"

//Resilience4j
const val RESILIENCE4J = "2.0.2"
}
6 changes: 6 additions & 0 deletions casper-feed/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ dependencies {

// Cloud Config
implementation(Dependencies.SPRING_CLOUD_CONFIG)

// Resilience4j
implementation(Dependencies.RESILIENCE4J_CIRCUITBREAKER)
implementation(Dependencies.RESILIENCE4J_RETRY)
implementation(Dependencies.RESILIENCE4J_SPRING_BOOT)
implementation(Dependencies.RESILIENCE4J_KOTLIN)
}

protobuf {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package hs.kr.entrydsm.feed.global.config

import io.github.resilience4j.circuitbreaker.CircuitBreaker
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry
import io.github.resilience4j.retry.Retry
import io.github.resilience4j.retry.RetryRegistry
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

/**
* Resilience4j 관련 설정을 담당하는 클래스입니다.
*
* @property circuitBreakerRegistry CircuitBreaker 인스턴스를 생성하고 관리합니다.
* @property retryRegistry Retry 인스턴스를 생성하고 관리합니다.
*/
@Configuration
class ResilienceConfig(
private val circuitBreakerRegistry: CircuitBreakerRegistry,
private val retryRegistry: RetryRegistry
) {

/**
* user-grpc 서킷 브레이커를 생성합니다.
*
* @return user-grpc 이름의 CircuitBreaker 인스턴스
*/
@Bean
fun userGrpcCircuitBreaker(): CircuitBreaker {
return circuitBreakerRegistry.circuitBreaker("user-grpc")
}

/**
* user-grpc 리트라이를 생성합니다.
*
* @return user-grpc 이름의 Retry 인스턴스
*/
@Bean
fun userGrpcRetry(): Retry {
return retryRegistry.retry("user-grpc")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package hs.kr.entrydsm.feed.global.extension

import io.github.resilience4j.circuitbreaker.CircuitBreaker
import io.github.resilience4j.kotlin.circuitbreaker.executeSuspendFunction
import io.github.resilience4j.kotlin.retry.executeSuspendFunction
import io.github.resilience4j.retry.Retry
import kotlinx.coroutines.CancellationException
import org.slf4j.LoggerFactory

private val log = LoggerFactory.getLogger("ResilienceGrpcExtensions")

/**
* gRPC 호출에 대해 Retry와 CircuitBreaker를 적용합니다.
*
* @param T gRPC 함수의 반환 타입
* @param retry 적용할 Retry 인스턴스
* @param circuitBreaker 적용할 CircuitBreaker 인스턴스
* @param fallback gRPC 호출 실패 시 실행할 함수
* @param block 실행할 gRPC 함수
* @return gRPC 함수의 실행 결과를 반환하거나, 실패 시 fallback 함수의 결과를 반환
*/
suspend fun <T> executeGrpcCallWithResilience(
retry: Retry,
circuitBreaker: CircuitBreaker,
fallback: suspend () -> T,
block: suspend () -> T
): T =
try {
retry.executeSuspendFunction {
circuitBreaker.executeSuspendFunction(block)
}
} catch (ce: CancellationException) {
throw ce
} catch (e: Exception) {
log.warn("gRPC 호출 실패, fallback 실행: {}", e.toString())
fallback()
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package hs.kr.entrydsm.feed.global.utils.admin

import hs.kr.entrydsm.feed.global.exception.InvalidTokenException
import hs.kr.entrydsm.feed.infrastructure.grpc.client.AdminGrpcClient
import hs.kr.entrydsm.feed.infrastructure.grpc.client.dto.response.InternalAdminResponse
import hs.kr.entrydsm.feed.infrastructure.grpc.user.client.UserGrpcClient
import hs.kr.entrydsm.feed.infrastructure.grpc.user.client.dto.response.InternalAdminResponse
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Component
import java.util.*
Expand All @@ -12,11 +12,11 @@ import java.util.*
* Spring Security의 SecurityContext를 사용하여 현재 인증된 관리자 정보를 조회하고,
* gRPC를 통해 관리자 서비스에서 추가 정보를 조회합니다.
*
* @property adminGrpcClient 관리자 정보 조회를 위한 gRPC 클라이언트
* @property userGrpcClient 관리자 정보 조회를 위한 gRPC 클라이언트
*/
@Component
class AdminUtils(
private val adminGrpcClient: AdminGrpcClient
private val userGrpcClient: UserGrpcClient
) {

/**
Expand All @@ -28,7 +28,7 @@ class AdminUtils(
* @throws hs.kr.entrydsm.feed.global.exception.InvalidTokenException 인증 토큰이 유효하지 않은 경우
* @throws io.grpc.StatusRuntimeException gRPC 통신 중 오류가 발생한 경우
*/
suspend fun getCurrentAdmin(): InternalAdminResponse = adminGrpcClient.getAdminInfoByAdminId(getCurrentAdminId())
suspend fun getCurrentAdmin(): InternalAdminResponse = userGrpcClient.getAdminInfoByAdminId(getCurrentAdminId())

/**
* 현재 인증된 관리자의 고유 식별자(UUID)를 조회합니다.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package hs.kr.entrydsm.feed.infrastructure.grpc.user.client

import hs.kr.entrydsm.casper.admin.proto.AdminServiceGrpc
import hs.kr.entrydsm.casper.admin.proto.AdminServiceProto
import hs.kr.entrydsm.feed.global.extension.executeGrpcCallWithResilience
import hs.kr.entrydsm.feed.infrastructure.grpc.user.client.dto.response.InternalAdminResponse
import io.github.resilience4j.circuitbreaker.CircuitBreaker
import io.github.resilience4j.retry.Retry
import io.grpc.Channel
import io.grpc.stub.StreamObserver
import kotlinx.coroutines.suspendCancellableCoroutine
import net.devh.boot.grpc.client.inject.GrpcClient
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import java.util.UUID
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
* 관리자 서비스와의 gRPC 통신을 담당하는 클라이언트 클래스입니다.
*
* @property channel gRPC 통신을 위한 채널 (user-service로 자동 주입됨)
*/
@Component
class UserGrpcClient(
@Qualifier("userGrpcRetry") private val retry: Retry,
@Qualifier("userGrpcCircuitBreaker") private val circuitBreaker: CircuitBreaker
) {

@GrpcClient("user-service")
lateinit var channel: Channel

/**
* 관리자 ID를 기반으로 관리자 정보를 비동기적으로 조회합니다.
* gRPC 비동기 스트리밍을 사용하여 관리자 서비스로부터 정보를 가져옵니다.
*
* @param adminId 조회할 관리자의 고유 식별자(UUID)
* @return 조회된 관리자 정보를 담은 [InternalAdminResponse] 객체
* @throws io.grpc.StatusRuntimeException gRPC 서버에서 오류가 발생한 경우
* @throws java.util.concurrent.CancellationException 코루틴이 취소된 경우
*/
suspend fun getAdminInfoByAdminId(adminId: UUID): InternalAdminResponse {

return executeGrpcCallWithResilience(
retry = retry,
circuitBreaker = circuitBreaker,
fallback = { InternalAdminResponse(adminId) }
) {
val adminStub = AdminServiceGrpc.newStub(channel)

val request = AdminServiceProto.GetAdminIdRequest.newBuilder()
.setAdminId(adminId.toString())
.build()

val response = suspendCancellableCoroutine { continuation ->
adminStub.getAdminByUUID(request, object : StreamObserver<AdminServiceProto.GetAdminIdResponse> {
override fun onNext(value: AdminServiceProto.GetAdminIdResponse) {
continuation.resume(value)
}
override fun onError(t: Throwable) {
continuation.resumeWithException(t)
}
override fun onCompleted() {}
})
}

InternalAdminResponse(id = UUID.fromString(response.adminId))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package hs.kr.entrydsm.feed.infrastructure.grpc.client.dto.response
package hs.kr.entrydsm.feed.infrastructure.grpc.user.client.dto.response

import java.util.UUID

Expand Down