ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Client 모듈 구성하기
    프로젝트/선착순 쿠폰 발급 시스템 2023. 2. 27. 00:01
    728x90

    Client 모듈이란?

    우리가 서버를 구성하다 보면 또 다른 서버로부터 데이터를 요청받고 처리해야 할 수 있습니다.

    이때 Rest 방식으로 API를 호출하기 위해서 RestTemplate, WebClient, FeignClient 등이 있습니다.

     

    이렇게 외부 서비스를 호출하기 위해서 Client 모듈을 두고 해당 모듈에 다른 서버로부터 데이터를 요청받고 처리하는 코드를 작성하고자 합니다.

     

    이렇게 되면 외부 호출건에 대한 수정은 Client 모듈에서 이루어지게 됩니다.

     

    Client 모듈 구성하기

    1. settings.gradle.kts 수정

    //추가
    include("client")

     

    2. 프로젝트 root에 client 디렉터리 생성 및 gradle refresh

     

    3. buidl.gradle.kts에 의존성 추가

    import org.springframework.boot.gradle.tasks.bundling.BootJar
    
    val jar: Jar by tasks
    val bootJar: BootJar by tasks
    
    bootJar.enabled = false
    jar.enabled = true
    
    
    
    dependencies {
    	implementation("org.springframework.boot:spring-boot-starter-web")
    	implementation("org.springframework.cloud:spring-cloud-starter-openfeign")
    	implementation("io.github.openfeign:feign-okhttp")
    }
    
    dependencyManagement {
    	imports{
    		mavenBom("org.springframework.cloud:spring-cloud-dependencies:2021.0.1")
    	}
    }

    client 모듈에서는 실행을 하지 않기 때문에  boorJar.enabled = false로 설정하고 jar.enabled = true로 설정합니다.

     

    4. application-client.yml 설정

    coupon:
      clients:
        test:
          endpoint: http://localhost:9090
    
    feign:
      okhttp:
        enable: true
      client:
        config:
          default:
            connectTimeout: 5000
            readTimeout: 10000
            loggerLevel: full
      httpclient:
        max-connections: 1000

    endpoint에는 http://localhost:9090 (테스트로 띄울 서버)의 엔드포인트가 들어갑니다.

     

    5. 환경 구성하기

    com.demo.client.common 패키지를 구성합니다.

    여기에 하위로 annotation, config 패키지를 구성합니다.

     

    최종적으로는 common 패키지는 다음과 같이 연출됩니다.

     

    6. config 패키지 하위에서 yml 설정 불러오기

    @ConstructorBinding
    @ConfigurationProperties(prefix = "coupon.clients")
    class FeignClientProperties(
        val test: Test,
    ) {
        companion object{
            data class Test(
                val endpoint: String,
            )
        }
    }

    위의 yml설정에서 endpoint를 설정했었습니다.

    @ConsturctorBinding은 객체가 생성되었을 때 설정되어야 하는 값을 setting 시킬 수 있습니다.

    @ConfigurationProperties은 *.properties, *.yml 파일에 있는 property를 클래스에 값을 가져와서 바인딩할 수 있도록 해주는 어노테이션입니다.

     

    coupon.clients가 prefix로 붙어있는 test.endpoint의 값을 가져오도록 설정되었습니다.

     

    이후 @SpringBootApplication 코드쪽에 @ConfigurationPropertiesScan을 추가해줍니다.

    @ConfigurationPropertiesScan
    @SpringBootApplication
    class CouponApplication
    
    fun main(args: Array<String>) {
    	runApplication<CouponApplication>(*args)
    }

     

    7. feignClient 설정하기

    @Configuration
    @EnableConfigurationProperties(FeignClientProperties::class)
    @EnableFeignClients(basePackages = ["com.demo.client"])
    class FeignClientConfig: ApplicationListener<ApplicationReadyEvent> {
        override fun onApplicationEvent(event: ApplicationReadyEvent) {
            Security.setProperty("networkaddress.cache.ttl","1")
            Security.setProperty("networkaddress.cache.negative.ttl","3")
        }
    
    }

     

    @Configuration으로 스프링빈으로 등록해줍니다.

    @EnableConfigurationProperties로 FeignClientProperties에서 클래스에서 등록된 Properties값을 활용할 수 있도록 합니다.

    @EnableFeingClients는 하위 클래스를 탐색하면서 @FeignClient를 찾아 구현체를 생성하는 역할을 담당합니다.

     

    ApplicationLisnter는 스프링부트가 시작되었을 때 이벤트를 받아 다음과 같은 설정을 수행하도록 합니다.

    - "networkaddress.cache.ttl" ,"1" (성공 시 1초가 지났을 때 DNS Server를 통해 다시 lookup)

    - "networkaddress.cache.negative.ttl","3"(실패시 3초가 지났을 때 DNS Server를 통해 다시 lookup)

     

    TTL 전략을 쓰지 않으면 한번 lookup한 도메인 이름을 VM이 내려갈 때까지 계속 캐싱하고 있습니다.

    외부에 있는 서버의 IP가 변경되는 경우 예전 IP로 접속하는 문제를 막을 수 있습니다.

     

    8. 해당 설정을 활용하기위한 Annotation 만들기

    @Target(AnnotationTarget.CLASS)
    @Retention(AnnotationRetention.RUNTIME)
    @Import(FeignClientConfig::class)
    annotation class EnableClient()

    @Target은 사용자가 만든 어노테이션이 CLASS에 부착될 수 있도록 타입을 지정합니다.

    @Retention은 해당 어노테이션이 RUNTIME시점까지 살아 남아 있을지를 지정합니다.

    @Import는 @Configuration 어노테이션이 선언된 스프링 설정 클래스를 가져옵니다. 

     

    이로 인해 SpringBootApplication부분에 FeignClientConfig의 설정 부분들이 전부 들어가게 됩니다.

    @EnableClient
    @ConfigurationPropertiesScan
    @SpringBootApplication
    class CouponApplication
    
    fun main(args: Array<String>) {
    	runApplication<CouponApplication>(*args)
    }

     

    9. FeignClient 개발

    test 패키지에 client, config 패키지를 하위로 만들어냅니다.

    최종적으로 다음과 같이 구성됩니다.

     

    @FeignClient(
        name = "test",
        url = "\${coupon.clients.test.endpoint:sample.com}",
        configuration = [TestConfiguration::class]
    )
    interface TestClient {
        @GetMapping("/test/{params}")
        fun requestTestServer(@PathVariable params: String): String
    }

    @FeignClient로 이름은 test, url은 설정에서 지정한 endpoint, 설정은 TestConfiguration에서 주입받도록 합니다.

     

    interface로 TestClient를 만들어내고 Controller에서 url을 호출하는 것처럼 호출할 서버에 요청할 http를 만들어냅니다.

     

    params에 따라 1이면 정상호출 이외의 값들은 예외를 만들어내기 위해 위처럼 구성하였습니다.

     

    위에 설정시 localhost:9090으로 endpoint가 설정되어있기 때문에 추후에 호출할 url은 다음과 같습니다.

    - localhost:9090/test/1 (정상 응답)

    - localhost:9090/test/2 (예외 응답)

     

    10. TestConfiguration 설정

    class TestConfiguration(
        private val feignClientProperties: FeignClientProperties,
    ) {
    
        @Bean
        fun connectionManagerRequestInterceptor(): RequestInterceptor{
            return MessageRequestInterceptor(feignClientProperties)
        }
    
        class MessageRequestInterceptor(
            private val feignClientProperties: FeignClientProperties
        ) : RequestInterceptor{
            override fun apply(template: RequestTemplate) {
                template.header("Content-Type","application/json")
            }
    
        }
    }

    RequestInterceptor는 공통으로 사용되는 header를 추가하기 위해 사용할 수 있습니다.

    apply 메서드를 override하였으며 Content-Type은 application/json 타입으로 받게 합니다.

     

    11. 컨트롤러 구성

    @RestController
    class TestController(
        private val testClient: TestClient
    ) {
        @GetMapping("/test/params}")
        fun test(@PathVariable params: String): String{
            return testClient.requestTestServer(params)
        }
    }

    이제 testClient를 통해 params를 넣고 호출하면 localhost:9090/test/{params}를 호출할 것입니다.

     

    12. api-external-coupon build.gradle.kts 설정

    dependencies {
    	implementation(project(":client"))
    	implementation("org.springframework.boot:spring-boot-starter-web")
    }
    
    dependencyManagement {
    	imports{
    		mavenBom("org.springframework.cloud:spring-cloud-dependencies:2021.0.1")
    	}
    }

    api 모듈에서 client 모듈을 사용하도록 해야 합니다.

    그리고 spring-cloud-dependenceis를 추가해줍니다.

    이제 모든 환경 구성이 끝났고 테스트를 위한 서버를 구성하겠습니다.

     

    테스트를 위한 외부 서버 구성

    1. settings.gradle.kts 수정

    //수정 "api-test-server" 부분 추가됨
    include("api-external-coupon", "api-test-server")

     

    2. api-external-coupon의 /src , build.gradle.kts를 그대로 가져옵니다.

     

    3. package명 com.demo.test로 수정, Application이름 TestApplication으로 수정

     

    4. application.properties 또는 application.yml에 server port 9090으로 설정

    기본적으로 설정되는 port는 8080으로 다른 서버와 충돌되지 않도록 9090으로 설정하였습니다.

    server.port = 9090

     

    5. Application을 한번 실행해보고 localhost:9090으로 접속이 잘 되는 것을 확인할 수 있습니다.

     

    6. Controller 구성

    @RestController
    class TestController {
    
        @GetMapping("/test/{params}")
        fun test(@PathVariable params: String): String{
            return when(params){
                "1" -> "This is Test Server"
                else -> throw IllegalArgumentException("error")
            }
        }
    }

     

    localhost:8080/test/1을 호출하면 This is Test Server가 반환됩니다.

    localhost:8080/test/2를 호출하면 Exception이 발생합니다.

     

     

     

     

     

    참고자료

    https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/

     

    Spring Cloud OpenFeign

    Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable

    docs.spring.io

    https://www.lesstif.com/java/java-vm-dns-caching-ttl-17105897.html

     

    Java VM 의 DNS caching TTL

     

    www.lesstif.com

    https://wildeveloperetrain.tistory.com/172

     

    FeignClient 기본적인 사용법 (Spring Cloud OpenFeign)

    기존 프로젝트에서는 서비스 간 통신에서 RestTemplate을 사용했는데 FeignClient를 통해 조금 더 편리하게 사용할 수 있다고 하여 적용해보며 정리한 내용입니다. 잘못된 부분은 댓글로 남겨주시면

    wildeveloperetrain.tistory.com

    https://javacan.tistory.com/entry/springboot-configuration-properties-class

     

    스프링 부트 커스텀 설정 프로퍼티 클래스 사용하기

    스프링 부트는 application.properties 파일을 이용해서 설정을 제공한다. 이 파일에는 부트가 제공하는 프로퍼티뿐만 아니라 커스텀 프로퍼티를 추가할 수 있다. 커스텀 프로퍼티를 사용하는 몇 가지

    javacan.tistory.com

    https://techblog.woowahan.com/2657/

     

    feign 좀더 나아가기 | 우아한형제들 기술블로그

    {{item.name}} 안녕하세요. 저는 상품시스템팀에서 개발하고 있는 고정섭입니다. 이 글에서는 배달의민족 광고시스템 백엔드에서 feign 을 적용하면서 겪었던 것들에 대해서 공유 하고자 합니다. 이

    techblog.woowahan.com

     

    댓글

Designed by Tistory.