๐ก OpenFeign์ด๋?
Netflix์ ์ํด ์ฒ์ ๋ง๋ค์ด์ง Declarative(์ ์ธ์ ์ธ) HTTP Client ๋๊ตฌ๋ก์จ, ์ธ๋ถ API ํธ์ถ์ ์ฝ๊ฒํ ์ ์๋๋ก ๋์์ค๋ค. Open Feign์ ์ธํฐํ์ด์ค์ ์ด๋ ธํ ์ด์ ๋ค๋ง ๋ถ์ฌ์ฃผ๋ฉด ๊ตฌํ์ด ๋๋ค. ์ด๋ฌํ ๋ฐฉ์์ Spring Data JPA์ ์ ์ฌํ๋ฉฐ, ์๋นํ ํธ๋ฆฌํ๊ฒ ๊ฐ๋ฐ์ ํ ์ ์๋๋ก ๋์์ค๋ค.
๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ
ext {
set('springCloudVersion', "2023.0.1")
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
dependencies {
...
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
...
}
** ์ฐธ๊ณ https://spring.io/projects/spring-cloud#overview
ํ์ฌ ํ๋ก์ ํธ ๋ฒ์ ์ด 3.2.5์ฌ์ 2023.0.x ๋ฒ์ ์ ์ฌ์ฉํ๊ณ , ๊ทธ ์ค์์๋ ์ ์ผ ์ต์ ๋ฒ์ ์ผ๋ก ์์ฑํด์คฌ๋ค!
OpenFeign ์ค์
๋ฉ์ธ ํด๋์ค์ @EnableFeignClients
๋ฅผ ๋ถ์ฌ์ฃผ๋ฉด ๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด @FeignClient
๊ฐ ๋ถ์ ์ธํฐํ์ด์ค๋ฅผ ์๋์ผ๋ก ์ฐพ๋๋ค.
ํ์ง๋ง ๋๋ ๋ณ๋์ Config ํ์ผ์ ๋ง๋ค์ด์ ์ค์ ํด์คฌ๋ค.
package com.todayeat.backend._common.config;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableFeignClients("com.---.---")
public class OpenFeignConfig {
๋ณ๋์ ํ์ผ์ ๋ง๋ค์ด ์ค์ ํด์ค ๋๋ ์ธํฐํ์ด์ค๋ค์ ์์น๋ฅผ ์ง์ ํด์ค์ผ ํ๋ค.
๋๋ @EnableFeignClients
๋ฅผ ๋ถ์ฌ์ ํจํค์ง๋ก ์ง์ ํด์คฌ๋ค.
ํด๋ผ์ด์ธํธ ๊ตฌํ
// ์์1
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
@FeignClient(name = "KakaoRequestClient", url = "https://kapi.kakao.com")
public interface KakaoRequestClient {
@PostMapping(value = "/v1/user/unlink", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
void unlink(@RequestHeader(HttpHeaders.AUTHORIZATION) String accessToken);
}
// ์์2
import com.todayeat.backend.order.api.dto.request.CancelPaymentRequest;
import com.todayeat.backend.order.api.dto.response.CancelPaymentResponse;
import com.todayeat.backend.order.api.dto.response.GetPaymentResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@FeignClient(name = "IamportRequestClient", url = "https://api.portone.io")
public interface IamportRequestClient {
@GetMapping(value = "/payments/{paymentId}", consumes = MediaType.APPLICATION_JSON_VALUE)
GetPaymentResponse getPayment(@RequestHeader(HttpHeaders.AUTHORIZATION) String apiSecret,
@PathVariable String paymentId,
@RequestParam String storeId);
@PostMapping(value = "/payments/{paymentId}/cancel", consumes = MediaType.APPLICATION_JSON_VALUE)
CancelPaymentResponse cancelPayment(@RequestHeader(HttpHeaders.AUTHORIZATION) String apiSecret,
@PathVariable String paymentId,
@RequestBody CancelPaymentRequest request);
}
API ํธ์ถ์ ์ํํ ํด๋ผ์ด์ธํธ๋ ์ธํฐํ์ด์ค๋ก ์์ฑํ ํ, @FeignClient
๋ฅผ ๋ถ์ฌ์ฃผ๋ฉด ๋๋ค.
๊ธฐ์กด์ ์ปจํธ๋กค๋ฌ์ ๊ต์ฅํ ์ ์ฌํ๊ฒ ์์ฑ์ด ๊ฐ๋ฅํ๋ค!
์๋น์ค์์ ํด๋น ๋ฉ์๋๋ฅผ ํธ์ถํ๊ธฐ๋ง ํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ํธ๋ฆฌํ๊ฒ API ํธ์ถ์ ํ ์ ์๋ค.
ํธ์ถ ์์
import com.todayeat.backend._common.oauth2.api.KakaoRequestClient;
import com.todayeat.backend._common.oauth2.dto.response.OAuth2Provider;
import com.todayeat.backend._common.oauth2.dto.response.OAuth2UserResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class OAuth2UnlinkService {
private final KakaoRequestClient kakaoRequestClient;
public void kakaoUnlink(String accessToken) {
log.info("[OAuth2UnlinkService.kakaoUnlink]");
kakaoRequestClient.unlink("Bearer " + accessToken);
}
}
์์ ์์1์์ ์์ฑํ ํด๋ผ์ด์ธํธ์ ๋ํ ํธ์ถ ์์์ด๋ค.
์นด์นด์ค ํ์ ํํด๋ฅผ ์งํํ์๊ณ , ๋ฐํ๊ฐ์ ํด๋น ๋ก์ง์ ๋ถํ์ํ ์ ๋ณด๋ผ๊ณ ํ๋จํ์ฌ void๋ก ๋ฐ์๋ค.
๋๋ฌด๋๋ฌด๋๋ฌด ๊ฐ๋จํ๋ค. ๋!
์์ธ ์ฒ๋ฆฌ
private void cancelPayment(String paymentId) {
try {
iamportRequestClient.cancelPayment(
PORTONE_PREFIX + IAMPORT_API_SECRET_V2,
paymentId,
CancelPaymentRequest.of("invalid value"));
} catch (Exception e) {
log.error("[OrderService.cancelPayment] paymentId {} error : {}", paymentId, e.getMessage());
throw new BusinessException(ORDER_CANCEL_FAIL);
}
}
HTTP ์์ฒญ์ด ์ฑ๊ณตํ ๋๋ ๊ฐ์ฒด๊ฐ ์ฑ๊ณต์ ์ผ๋ก ๋งคํ๋์ง๋ง,
๊ทธ ์ธ์๋ ๋ชจ๋ ์์ฒญ์ด FeignException์ผ๋ก ์ฒ๋ฆฌ๋๋ค.
// feign.FeignException#clientErrorStatus
switch (status) {
case 400:
return new BadRequest(message, request, body);
case 401:
return new Unauthorized(message, request, body);
case 403:
return new Forbidden(message, request, body);
case 404:
return new NotFound(message, request, body);
case 405:
return new MethodNotAllowed(message, request, body);
case 406:
return new NotAcceptable(message, request, body);
case 409:
return new Conflict(message, request, body);
case 410:
return new Gone(message, request, body);
case 415:
return new UnsupportedMediaType(message, request, body);
case 429:
return new TooManyRequests(message, request, body);
case 422:
return new UnprocessableEntity(message, request, body);
default:
return new FeignClientException(status, message, request, body);
}
// feign.FeignException#serverErrorStatus
switch (status) {
case 500:
return new InternalServerError(message, request, body);
case 501:
return new NotImplemented(message, request, body);
case 502:
return new BadGateway(message, request, body);
case 503:
return new ServiceUnavailable(message, request, body);
case 504:
return new GatewayTimeout(message, request, body);
default:
return new FeignServerException(status, message, request, body);
}
์ํ ์ฝ๋๊ฐ 400๋ฒ๋์ผ ๋๋ FeignClientException์ด๊ณ , 500๋ฒ๋์ผ ๋๋ FeignServerException์ด๋ค.
๋ฐ๋ผ์ ํด๋น ์์ฒญ์ ์์ธ๋ ์ผ๋ฐ์ ์ธ ์์ธ์ ๋๊ฐ์ด ์ฒ๋ฆฌํด์ฃผ๋ฉด ๋๋ค. ๋๋ try~catch ๋ฌธ์ผ๋ก ์ฒ๋ฆฌํ์๋ค.
๋ณดํต์ ErrorDecoder๋ฅผ ๋ง์ด ์ฌ์ฉํ๋ ๊ฒ ๊ฐ์๋ค. ์ด ๋ถ๋ถ์ ์กฐ๊ธ ๋ ์์๋ด์ผ ํ ๊ฒ ๊ฐ๋ค!
'* > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring Boot] ๋์์ฑ ์ ์ด (1) - ๋์์ฑ ๋ฌธ์ , ์ฌ๊ณ ๊ฐ์ ์์คํ ๊ตฌํ ๋ฐ ํ ์คํธ (1) | 2024.11.09 |
---|---|
[JPA] ์ํฐํฐ equals ๋ฉ์๋ ๊ตฌํ ์ ์ฃผ์ํ ์ (0) | 2024.11.08 |
[JPA] Join Fetch ์ MultipleBagFetchException (0) | 2024.11.07 |
[JPA] ์ํฐํฐ ์์ฑ ์ ์๊ฒผ๋ ๋ฌธ์ ๋ค (์๋ณ๊ด๊ณ ๋ฌธ์ ) (0) | 2024.03.17 |
Java ํต์ ์ธํฐํ์ด์ค ์ค์ต (0) | 2023.12.30 |