티스토리 뷰

기록

Kotlin: resilience4j circuit breaker fallback

Korean Eagle 2024. 3. 15. 14:43
728x90

기능을 구현하려는데 라이브러리를 사용하려는 뭔가 복잡하고, 이해하기 힘든다는 생각이 들면, 그 라이브러리 탓이지 사용하는 개발자 문제가 아니다. 기본적으로 프로그램은 은닉성이 중요한데, 내부 동작을 알고 작업해야 한다는 것은 그 프로그램의 심각한 결함이 있다는 말이다. circuit breaker는 그냥 호출하는 서비스가 죽었을 때 대처하는 방법을 우아하게 한 것 뿐이다. 예전에 try catch 에서 다 하던 거라 별 것 없다.

 

1. io.github.resilience4j:resilience4j-spring-boot3 의존성 사용하여 구현한다면 웬만하면 이 설정 부분 만큼은 자바 쓰는 게 낫다. 설정 많이 추가해서 코클린 컴파일 하는 것 보다 default interface를 자바로 구현하는 것이 안전하다.

 

2. 이런 cloud api 들은 설정 문제인지 코드 문제인지 혼동되는 경우가 많다. resilience4j 같은 범용 코드는 kotlin을 공식지원하는 spring 프레임워크와 다른 정책을 가지고 있다. 

 

// 동작하지 않는 코드
package net.philipheur.hshop.catalogservice.exchange

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker
import net.philipheur.hshop.catalogservice.domain.service.dto.SearchResponse
import net.philipheur.hshop.common.domain.dtos.settings.SettingDto
import org.springframework.cloud.openfeign.FeignClient
import org.springframework.web.bind.annotation.GetMapping
import java.util.Locale

import java.util.UUID

@FeignClient(name = "settings-service")
interface SettingsServiceClient {

    @GetMapping(value = ["/api/settings"])
    @CircuitBreaker(name = "settings-service", fallbackMethod = "findAllSettingFallBack")
    fun findAllSettings(): SearchResponse<SettingDto>


    private fun findAllSettingFallBack(exception: Throwable): SearchResponse<SettingDto> {

        print("circuit break default function executed")

        val locale = Locale.getDefault()
        locale.displayCountry
        locale.language

        println("${locale.displayCountry}, ${locale.language}")

        return SearchResponse(
            totalElements = 1,
            totalPages = 1,
            pageNo = 0,
            pageSize = 1,
            last = true,
            content = listOf(SettingDto(
                id = UUID.randomUUID().toString(),
                key = "CURRENCY",
                value = "ko,KR",
                category = "GENERAL"
            ))
        )
    }
}

// 그냥 자바로 구현하는 게 낫다.
@FeignClient(name = "settings-service")
public interface SettingsServiceClient {

    @GetMapping(value = "/api/settings")
    @CircuitBreaker(name = "settings-service", fallbackMethod = "findAllSettingFallBack")
    SearchResponse<SettingDto> findAllSettings();


    default SearchResponse<SettingDto> findAllSettingFallBack(Throwable exception) {

        System.out.println("circuit break default function executed");

        var locale = Locale.getDefault();

        System.out.println(locale.getDisplayCountry() + " " + locale.getLanguage());

        return new SearchResponse<>(
            1,
            1,
            0,
            1,
             true,
                List.of(new SettingDto(
                        UUID.randomUUID().toString(),
                        "CURRENCY",
                        "ko,KR",
                        "GENERAL"
                ))
        );
    }
}

 

3. 위의 방식으로 구현하는 것보다 spring-cloud-starter-circuitbreaker-resilience4j 를 바로 추가해서 fallback 클래스를 구현하는 방법도 있다. 아래 방법이 좀 더 깔끔해 보이고 resilience4j를 바로 import하지 않아도 되기 때문에 다른 기능이 필요없으면 이런 식으로도 가능하다.

  3-1 io.github.resilience4j:resilience4j-spring-boot3를 제거하고 spring-cloud-starter-circuitbreaker-resilience4j 를 추가한다.

 

  3-2 아래를 사용하려면 resilience4j 설정 이외에도 아래 설정의 추가가 필요하다. application.yml에 추가 한다.

cloud:
  openfeign:
    circuitbreaker:
      enabled: true
// @Feign Client
package net.philipheur.hshop.catalogservice.exchange;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker
import net.philipheur.hshop.catalogservice.domain.service.dto.SearchResponse
import net.philipheur.hshop.common.domain.dtos.settings.SettingDto
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping
import java.util.*

@FeignClient(name = "settings-service", fallback = SettingServiceClientFallback::class)
interface SettingsServiceClient {

    @GetMapping(value = ["/api/settings"])
    fun findAllSettings(): SearchResponse<SettingDto>
}


// fallback 클래스
package net.philipheur.hshop.catalogservice.exchange

import net.philipheur.hshop.catalogservice.domain.service.dto.SearchResponse
import net.philipheur.hshop.common.domain.dtos.settings.SettingDto
import org.springframework.stereotype.Component
import java.util.*

@Component
class SettingServiceClientFallback : SettingsServiceClient {
    override fun findAllSettings(): SearchResponse<SettingDto> {

        println("circuit break default function executed")

        val locale = Locale.getDefault();

        return SearchResponse(
            totalElements = 1,
            totalPages = 1,
            pageNo = 0,
            pageSize = 1,
            last = true,
            content = listOf(
                SettingDto(
                    id = UUID.randomUUID().toString(),
                    key = "CURRENCY",
                    value = "${locale.language},${locale.country}",
                    category = "GENERAL"
                )
            )
        )
    }
}
728x90
댓글