ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

Java

[Spring] Feign Client๋ž€?

๊ฐœ๋ฐœ๊ฐœ๊ตด๐Ÿธ 2023. 11. 3. 14:09

ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋‹ค Spring ์„œ๋ฒ„์—์„œ ์™ธ๋ถ€ API์™€ ํ†ต์‹ ํ•˜๋Š” ์„œ๋ฒ„ to ์„œ๋ฒ„ ํ†ต์‹ ์„ ํ•˜๊ฒŒ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” Feign Client์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

Feign Client ๋ž€?

Feign Client๋ฅผ ํ•œ๋งˆ๋””๋กœ ์ •๋ฆฌํ•˜์ž๋ฉด Netflix์—์„œ ๊ฐœ๋ฐœ๋œ Http Client Binder์ž…๋‹ˆ๋‹ค. Spring Boot ํ™˜๊ฒฝ์—์„œ ๋‹ค๋ฅธ ์„œ๋ฒ„์˜ Api๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

์•„๋ž˜์™€ ๊ฐ™์ด ๋‹จ์ˆœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋” ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค๋‹ˆ๋‹ค.

์žฅ์ 

  • HTTP API๋ฅผ ๊ท ์ผํ•˜๊ฒŒ ๋ฐ”์ธ๋”ฉํ•˜์—ฌ ๋ณต์žก์„ฑ์„ ์ค„์ž„
  • RestTemplate๋ฐฉ์‹๊ณผ WebClient๋ฐฉ์‹๋ณด๋‹ค ๋‹จ์ˆœ
  • ์›น ์„œ๋น„์Šค ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Œ
    • Interface๋ฅผ ์ž‘์„ฑํ•˜๊ณ  Annotaion์„ ์„ ์–ธํ•˜๋ฉด ๋
    • Jpa๊ฐ€ ์‹ค์ œ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ  Interface๋งŒ ์ง€์ •ํ•˜์—ฌ ์ฟผ๋ฆฌ ์‹คํ–‰ ๊ตฌํ˜„์ฒด๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“œ๋Š”๊ฒƒ๊ณผ ์œ ์‚ฌํ•จ

์ด๋Ÿฌํ•œ ์žฅ์ ๋“ค๋กœ ์ธํ•ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋” ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค๋‹ˆ๋‹ค.

 

OkHttp

  • Feign์—์„œ ์ œ๊ณตํ•˜๋Š” Http Client ์ค‘ 1๊ฐœ
    • ๋น„๋™๊ธฐ / Non-Blocking ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์„ค์ •
    • ๊ธฐ๋ณธ ์„ค์ •์€ HttpUrlConnection
  • Rest Api, Http ํ†ต์‹ ์„ ๊ฐ„ํŽธํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • HTTP ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ๋ณธ ๊ธฐ๋Šฅ์„ ์ œ๊ณต
    • ํ—ค๋”, ๋ฐ”๋”” ๋“ฑ์˜ ์„ธ๋ถ€ ์‚ฌํ•ญ์„ ์ˆ˜๋™์œผ๋กœ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Œ
    • ์š”์ฒญ ๋ฐ ์‘๋‹ต์— ๋Œ€ํ•œ ์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ๊ฐ€๋Šฅ
  • Java์™€ ๊ฐ™์€ JVM ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ์—์„œ ๋™์ž‘

Feign Client ์˜ˆ์‹œ ์ฝ”๋“œ

์‹ค์ œ Feign Client๋ฅผ ํ™œ์šฉํ•ด ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋กœ์ง์„ ์ž‘์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด์„  Json ๋ฌด๋ฃŒ ๊ฐ€์ƒ Rest API ์„œ๋ฒ„์ธ JSONPlaceholder๋ฅผ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. 

 

 

https://jsonplaceholder.typicode.com/

 

JSONPlaceholder - Free Fake REST API

{JSON} Placeholder Free fake API for testing and prototyping. Powered by JSON Server + LowDB. Tested with XV. Serving ~2 billion requests each month.

jsonplaceholder.typicode.com

 

์šฐ์„  build.gradleํŒŒ์ผ์„ ํ†ตํ•ด Dependency๋ฅผ ์„ค์ •ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

์˜์กด์„ฑ

ext {
	set('springCloudVersion', "2021.0.5")
}

dependencies {
	implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    	implementation 'io.github.openfeign:feign-okhttp'
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
	}
}

 

์˜ˆ์‹œ ์ฝ”๋“œ

Client Interface์™€ FallbackFacory Class๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

 

FallbackFacory๋ž€?

  • Feign์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๋กœ, ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹  ์ค‘์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ๋Œ€์ฒด ๋™์ž‘์„ ์ •์˜ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
  • ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ•ด๋‹น ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ฐฑ์—”๋“œ ๊ตฌํ˜„ ๋Œ€์‹  FallbackFactory์— ์ •์˜๋œ ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ
package com.example.demofeign.api.test.feign;

import org.springframework.http.MediaType;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.Map;

@FeignClient(name = "testApi", url = "https://jsonplaceholder.typicode.com", fallbackFactory = ApiClientFallbackFactory.class)
public interface ApiClient {

    @GetMapping(value = "/users/{userNo}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    Map getTestData(@PathVariable String userNo);
}
package com.example.demofeign.api.test.feign;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;

import java.util.Map;

@Slf4j
@Component
public class ApiClientFallbackFactory implements FallbackFactory<ApiClient> {
    @Override
    public ApiClient create(Throwable cause) {
        return new ApiClient() {
            @Override
            public Map getTestData(String userNo) {
                log.error("testData Fallback reason was: " + cause.getMessage(), cause);
                return null;
            }
        };
    }
}

 

๋‹ค์Œ์œผ๋กœ ApiClient๋ฅผ ํ˜ธ์ถœํ•˜๋Š” TestService๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

package com.example.demofeign.api.test.service;

import com.example.demofeign.api.test.feign.ApiClient;
import com.example.demofeign.api.test.model.response.ApiResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class TestService {

    private final ApiClient apiClient;
    private final ObjectMapper objectMapper;

    public ApiResponse getTestData(String userNo) {
        Map testData = apiClient.getTestData(userNo);
        ApiResponse apiResponse = objectMapper.convertValue(testData, ApiResponse.class);
        log.info("TestData: {}", apiResponse);
        return apiResponse;
    }
}

 

๋งˆ์ง€๋ง‰์œผ๋กœ TestService๋ฅผ ํ˜ธ์ถœ ํ…Œ์ŠคํŠธํ•ด๋ณผ TestController๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

package com.example.demofeign.api.test.controller;

import com.example.demofeign.api.test.model.response.ApiResponse;
import com.example.demofeign.api.test.service.TestService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class TestController {
    private final TestService testService;

    @GetMapping("/test/{userNo}")
    public ResponseEntity<ApiResponse> test(@PathVariable String userNo) {
        return ResponseEntity.ok(testService.getTestData(userNo));
    }
}

 

 

Postman์œผ๋กœ ํ˜ธ์ถœ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜๋ฉด, ์œ„์— ๋งํฌ์™€ ๊ฐ™์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

ํ•ด๋‹น ์˜ˆ์ œ ์†Œ์Šค ๋งํฌ์ž…๋‹ˆ๋‹ค.

https://github.com/Jisue/Spring-Boot---Feign-Client

 

GitHub - Jisue/Spring-Boot---Feign-Client: Feign Client๋ฅผ ์ •๋ฆฌํ•ด๋ณด์ž

Feign Client๋ฅผ ์ •๋ฆฌํ•ด๋ณด์ž. Contribute to Jisue/Spring-Boot---Feign-Client development by creating an account on GitHub.

github.com


[์ฐธ๊ณ ]

https://techblog.woowahan.com/2630/

 

์šฐ์•„ํ•œ feign ์ ์šฉ๊ธฐ | ์šฐ์•„ํ•œํ˜•์ œ๋“ค ๊ธฐ์ˆ ๋ธ”๋กœ๊ทธ

{{item.name}} ์•ˆ๋…•ํ•˜์„ธ์š”. ์ €๋Š” ๋น„์ฆˆ์ธํ”„๋ผ๊ฐœ๋ฐœํŒ€์—์„œ ๊ฐœ๋ฐœํ•˜๊ณ  ์žˆ๋Š” ๊ณ ์ •์„ญ์ž…๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” ๋ฐฐ๋‹ฌ์˜๋ฏผ์กฑ ๊ด‘๊ณ ์‹œ์Šคํ…œ ๋ฐฑ์—”๋“œ์—์„œ feign ์„ ์ ์šฉํ•˜๋ฉด์„œ ๊ฒช์—ˆ๋˜ ๊ฒƒ๋“ค์— ๋Œ€ํ•ด์„œ ๊ณต์œ  ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค

techblog.woowahan.com

https://kdhyo98.tistory.com/33#google_vignette

 

[SPRING] feign์€ ๋ญ˜๊นŒ?

๐Ÿค” ์„œ๋ก  ๋‹ค๋ฅธ ์„œ๋ฒ„์™€ ํ†ต์‹ ์„ ํ•˜๊ธฐ ์œ„ํ•œ API ๋ฅผ ์„ค๊ณ„ ๋ฐ ๊ฐœ๋ฐœ์„ ํ•˜๋ฉด์„œ ์•Œ๊ฒŒ ๋œ feign์— ๋Œ€ํ•ด์„œ ์ •๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์ž‘์„ฑํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ๊ทธ ์ „๊นŒ์ง€๋Š” spring์—์„œ ๋‹ค๋ฅธ ์„œ๋ฒ„๋ฅผ ํ˜ธ์ถœํ•ด๋ณธ ์ ์ด ์—†์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์–ด

kdhyo98.tistory.com

 

'Java' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Spring] Mockito๋ž€?  (0) 2023.05.21