{"id":7395,"date":"2023-01-08T12:40:19","date_gmt":"2023-01-08T03:40:19","guid":{"rendered":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=7395"},"modified":"2023-01-08T13:01:10","modified_gmt":"2023-01-08T04:01:10","slug":"feign-client-%ec%98%88%ec%a0%9c","status":"publish","type":"post","link":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=7395","title":{"rendered":"Feign Client \uc608\uc81c"},"content":{"rendered":"<h1>Feign Client \uc608\uc81c<\/h1>\n<p>Feign Client \uc608\uc81c\ub97c \uc791\uc131\ud569\ub2c8\ub2e4.<\/p>\n<p>. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .<\/p>\n<h2>\ub370\uc774\ud0c0 \uc804\uc1a1\uc6a9 \ud074\ub798\uc2a4<\/h2>\n<pre><code class=\"language-java\">@Getter\n@RequiredArgsConstructor\npublic enum ResponseCode implements Serializable {\n    OK(&quot;000&quot;, &quot;OK&quot;),\n    DATA_NOT_FOUND(&quot;404&quot;, &quot;\ub370\uc774\ud0c0\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.&quot;),\n\n    private final String key;\n    private final String title;\n}<\/code><\/pre>\n<pre><code class=\"language-java\">@Getter\n@Setter\n@NoArgsConstructor\npublic class ApiResponseMessage implements Serializable {\n    private String code;\n    private String message;\n\n    public ApiResponseMessage(ResponseCode responseCode) {\n        this.code = responseCode.getKey();\n        this.message = responseCode.getTitle();\n    }\n\n    public ApiResponseMessage(ResponseCode responseCode, String message) {\n        this.code = responseCode.getKey();\n        this.message = message;\n    }\n}<\/code><\/pre>\n<pre><code class=\"language-java\">@Getter\n@Setter\n@NoArgsConstructor\npublic class ApiResponseWithData extends ApiResponseMessage implements Serializable {\n    private Object data;\n\n    public ApiResponseWithData(ResponseCode responseCode, Object data) {\n        super(responseCode);\n        this.data = data;\n    }\n}<\/code><\/pre>\n<h2>API Side<\/h2>\n<p>\uc624\ub958 \uc0c1\ud669\ub3c4 <code>ResponseEntity.ok<\/code> \ub85c \ub9ac\ud134\ud574\uc57c \ud569\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-java\">@Service\n@RequiredArgsConstructor\npublic class CompanyService {\n\n    public ResponseEntity&lt;?&gt; get(Long companyId) {\n        Optional&lt;Company&gt; company = repository.findById(companyId);\n        if (company.isPresent()) {\n            return ResponseEntity.ok(new ApiResponseWithData(ResponseCode.OK, converter.toDto(company.get())));\n        } else {\n            return ResponseEntity.ok(new ApiResponseMessage(ResponseCode.DATA_NOT_FOUND));\n        }\n    }\n\n}<\/code><\/pre>\n<pre><code class=\"language-java\">@RestController\n@RequiredArgsConstructor\n@RequestMapping(&quot;\/v1\/account&quot;)\npublic class CompanyController {\n\n    @GetMapping(&quot;\/{companyId}&quot;)\n    public ResponseEntity&lt;?&gt; get(@PathVariable Long companyId) {\n        return service.get(companyId);\n    }\n\n}<\/code><\/pre>\n<h2>Client \uc124\uc815<\/h2>\n<pre><code class=\"language-yaml\">feign:\n  retry:\n    period: 3\n    max-period: 5\n    max-attempt: 5\n  account-api:\n    url: http:\/\/localhost:8081\/v1\/account<\/code><\/pre>\n<pre><code class=\"language-gradle\">dependencies {\n    implementation &#039;org.springframework.cloud:spring-cloud-openfeign-core:3.1.5&#039;\n    implementation &#039;org.springframework.cloud:spring-cloud-starter-openfeign:3.1.5&#039;\n    implementation &#039;com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.0&#039;\n}<\/code><\/pre>\n<p><code>@EnableFeignClients<\/code> \ub97c \uc124\uc815\ud569\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-java\">@EnableFeignClients\n@SpringBootApplication\npublic class WarehouseApplication {\n\n  public static void main(String[] args) {\n    SpringApplication.run(WarehouseApplication.class, args);\n  }\n\n}<\/code><\/pre>\n<pre><code class=\"language-java\">public class FeignClientConfig implements Jackson2ObjectMapperBuilderCustomizer {\n\n    public FeignClientConfig() {\n    }\n\n    @Bean\n    public FeignFormatterRegistrar localDateFeignFormatterRegister() {\n        return (registry) -&gt; {\n            DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();\n            registrar.setUseIsoFormat(true);\n            registrar.registerFormatters(registry);\n        };\n    }\n\n    @Bean\n    public FeignErrorDecoder decoder() {\n        return new FeignErrorDecoder();\n    }\n\n    public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {\n        jacksonObjectMapperBuilder\n                .featuresToEnable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)\n                .featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)\n                .timeZone(TimeZone.getDefault())\n\/\/                .modulesToInstall(new Module[]{new JavaTimeModule()})\n                .locale(Locale.getDefault()).simpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;);\n    }\n}<\/code><\/pre>\n<pre><code class=\"language-java\">public class FeignErrorDecoder implements ErrorDecoder {\n    @Override\n    public Exception decode(String methodKey, Response response) {\n        switch (response.status()){\n            case 400:\n                break;\n            case 404:\n                if(methodKey.contains(&quot;getOrders&quot;)){\n                    return new ResponseStatusException(HttpStatus.valueOf(response.status()),\n                            &quot;User&#039;s orders is empty.&quot;);\n                }\n                break;\n            default:\n                return new Exception(response.reason());\n        }\n        return null;\n    }\n}<\/code><\/pre>\n<pre><code class=\"language-java\">public class FeignRetryConfig {\n    @Value(&quot;${feign.retry.period}&quot;)\n    private long period;\n    @Value(&quot;${feign.retry.max-period}&quot;)\n    private long maxPeriod;\n    @Value(&quot;${feign.retry.max-attempt}&quot;)\n    private int maxAttempt;\n\n    public FeignRetryConfig() {\n    }\n\n    @Bean\n    public Retryer retryer() {\n        return new Retryer.Default(this.period, this.maxPeriod, this.maxAttempt);\n    }\n}<\/code><\/pre>\n<h2>Client<\/h2>\n<pre><code class=\"language-java\">@FeignClient(name = &quot;account-api&quot;, url = &quot;${feign.account-api.url}&quot;, configuration = {FeignClientConfig.class, FeignRetryConfig.class})\npublic interface AccountService {\n\n    @GetMapping(&quot;\/{companyId}&quot;)\n    ApiResponseWithData getByCompanyId(@PathVariable(&quot;companyId&quot;) Long companyId);\n}<\/code><\/pre>\n<pre><code class=\"language-java\">@Service\n@RequiredArgsConstructor\npublic class ItemService {\n\n    private final ItemRepository repository;\n    private final AccountService accountService;\n    private final ItemConverter converter = Mappers.getMapper(ItemConverter.class);\n\n    @Transactional\n    public ResponseEntity&lt;?&gt; create(ItemDto dto) {\n\n        ApiResponseWithData response = accountService.getByCompanyId(dto.getCompanyId());\n        if (!ResponseCode.OK.getKey().equals(response.getCode())) {\n            return ResponseEntity.badRequest().body(new ApiResponseMessage(ResponseCode.COMPANYID_NOT_FOUND));\n        }\n\n        ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());\n        CompanyDto companyDto = mapper.convertValue(response.getData(), CompanyDto.class);\n        System.out.println(companyDto.getCompanyName());\n\n        return ResponseEntity.ok(new ApiResponseWithData(ResponseCode.OK, converter.toDto(repository.save(converter.toEntity(dto)))));\n    }\n}<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Feign Client \uc608\uc81c Feign Client \uc608\uc81c\ub97c \uc791\uc131\ud569\ub2c8\ub2e4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\u2026 <span class=\"read-more\"><a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=7395\">Read More &raquo;<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[29],"tags":[],"class_list":["post-7395","post","type-post","status-publish","format-standard","hentry","category-spring-boot-2-5"],"_links":{"self":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/7395","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=7395"}],"version-history":[{"count":5,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/7395\/revisions"}],"predecessor-version":[{"id":7400,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/7395\/revisions\/7400"}],"wp:attachment":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7395"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7395"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7395"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}