> For the complete documentation index, see [llms.txt](https://tech.x2bee.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tech.x2bee.com/dev-guide/dev-start/markdown/restapi.md).

# RestAPI 통신

본 문서는 Spring의 REST API HTTP 통신에 대한 구현 및 X2BEE 환경에서의 적용 방식을 설명합니다.

***

## Spring REST API 지원의 변화

Spring에서 REST API 통신을 지원하는 방식은 버전에 따라 다음과 같이 진화했습니다:

* Spring 3.0
  * RestTemplate: REST API 호출을 위한 표준으로 사용되었으나, Spring 6.0부터는 사용 중단 예정입니다.
  * 주의: RestTemplate은 신규 프로젝트에서는 사용하지 않는 것이 권장됩니다.
* Spring 5.0
  * WebClient: 비동기 및 블로킹 통신 모두를 지원하며, WebFlux 모듈에 포함되어 있습니다.
  * X2BEE에서는 WebClient를 내부 통신 모듈로 활용 중입니다.
* Spring 6.0
  * HTTP Interface:
    * WebClient를 내부적으로 사용하여 REST API와의 통신을 인터페이스로 정의합니다.
    * FeignClient와 유사하지만, 명시적으로 WebClient와 Bean 등록이 필요합니다.
    * X2BEE에서는 이와 유사한 RestApiInterface 어노테이션을 구현하여 외부 통신에 활용하고 있습니다.

{% hint style="info" %}
참고: RestTemplate은 과거 표준이었으나, 현재는 WebClient / HTTP Interface 기반 사용이 권장됩니다.
{% endhint %}

## HTTP Interface 설정

HTTP Interface는 WebClient를 사용해 REST API의 엔드포인트를 설정하고 이를 Spring Bean으로 등록합니다.

설정 예제:

{% code title="HttpConfig.java" %}

```java
@Configuration
public class HttpConfig {
    @Bean
    RepositoryService repositoryService() {
        WebClient client = WebClient.create("https://jsonplaceholder.typicode.com");
        HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
        return factory.createClient(RepositoryService.class);
    }
}
```

{% endcode %}

* WebClient.create: Base URL 설정.
* HttpServiceProxyFactory: HTTP 인터페이스 Bean 생성을 위한 팩토리 클래스

## X2BEE `RestApiInterface` (공통 커스텀 어노테이션)

X2BEE는 REST API 엔드포인트 관리의 편리성을 위해 `@RestApiInterface`라는 커스텀 어노테이션을 제공합니다.

어노테이션 정의 예제:

{% code title="RestApiInterface.java" %}

```java
/**
 * Rest Api Interface 용 커스텀 어노테이션
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RestApiInterface {
    String baseUrl() default "";
    String defaultPath() default "";
    int connectionTimeoutSeconds() default 10;
    int readTimeoutSeconds() default 60;
    int writeTimeoutSeconds() default 60;
    boolean enableTokenAuth() default true;
    boolean useMemberToken() default false;
}
```

{% endcode %}

### 주요 속성

| 속성명                        | 설명                                                     | 기본값   |
| -------------------------- | ------------------------------------------------------ | ----- |
| `baseUrl`                  | API 엔드포인트 URL (필수). 환경변수와 연동 가능: `${app.apiUrl.event}` | -     |
| `defaultPath`              | 기본 경로. `baseUrl`와 결합하여 최종 URL 형성.                      | -     |
| `connectionTimeoutSeconds` | 연결 대기 시간 설정 (초).                                       | 10    |
| `readTimeoutSeconds`       | 응답 대기 시간 설정 (초).                                       | 60    |
| `writeTimeoutSeconds`      | 요청 데이터 전송 시간 설정 (초).                                   | 60    |
| `enableTokenAuth`          | 인증 토큰을 헤더에 포함 여부 설정.                                   | true  |
| `useMemberToken`           | 사용자 인증 토큰 포함 여부 설정 (`enableTokenAuth=true`일 때 동작).     | false |

## HTTP Interface 구현

REST API Interface 예제:

{% code title="TestInterface.java" %}

```java
@RestApiInterface(baseUrl = "${app.apiUrl.common}", defaultPath = "/api/common", enableTokenAuth = false, useMemberToken = false)
public interface TestInterface {
    @Headers({"Content-Type: application/json; charset=utf-8"})
    @GET("/interface/test1")
    Call<Response> commonTest1();

    @Headers({"Content-Type: application/json; charset=utf-8"})
    @GET("/interface/test2")
    Call<Response> commonTest2();
}
```

{% endcode %}

* `@RestApiInterface`: Bean 등록 시 인터페이스를 자동 검색 및 설정.
* `@Headers`: API 호출 시 필요한 헤더 값 설정.

HTTP 요청 호출 예 (HTTPSample.java):

{% code title="HTTPSample.java" %}

```java
private final TestInterface testInterfaceService;

public ResponseEntity<Response> http1SearchSamples1() {
    ResponseEntity<Response> result1 = Http.requestEntitySync(testInterfaceService.commonTest1(), new TypeReference<Response>() {});
    // -> 동기식 ResponseEntity 반환

    Response<String> result2 = Http.requestSync(testInterfaceService.commonTest1(), new TypeReference<Response>() {});
    // -> 동기식 payload 반환

    Mono<ResponseEntity<Response>> result3 = Http.requestEntityAsync(testInterfaceService.commonTest1(), new TypeReference<Response>() {});
    // -> 비동기식 ResponseEntity 반환
    ResponseEntity<Response> result33 = result3.block();
    // -> 비동기적인 작업의 결과를 동기적으로 변환

    Mono<Response> result4 = Http.requestAsync(testInterfaceService.commonTest1(), new TypeReference<Response>() {});
    // -> 비동기식 payload 반환
    Response result44 = result4.block();
    // -> 비동기적인 작업의 결과를 동기적으로 변환

    return result1;
}
```

{% endcode %}

## RestApiUtil (X2BEE 공통 Http 통신)

X2BEE 내부 통신 모듈인 `RestApiUtil`은 REST API 호출을 간소화합니다. 해당 유틸의 경우 내부통신에서만 사용되기 때문에 반환값이 Response으로 고정되어 있습니다.

JAVA 사용 예:

{% code title="RestApiUtil 사용 예.java" %}

```java
@Value("${app.apiUrl.common}")
private String commonApiUrl;

private final RestApiUtil restApiUtil;

public Response<String> http1SearchSamples1() {
    return restApiUtil.get(
        commonApiUrl + "/interface/test1",
        null,
        new ParameterizedTypeReference<Response<String>>() {}
    );
}
```

{% endcode %}

## React 연동 예제

React에서 HTTP 요청 헬퍼를 구성하여 간편하게 REST API를 호출할 수 있습니다.

React 사용 예:

{% code title="RestApi.ts" %}

```typescript
const RestApi = (() => {
    class RestApi {
        private static instance: RestApi // Singleton 패턴 구현
        static getInstance() {
            if (RestApi.instance) {
                return this.instance
            }
            this.instance = new RestApi()
            return this.instance
        }

        // HTTP 요청 메서드
        async http(
            url: string,
            method: string,
            options?: RequestOptions
        ): Promise<ResponseEntity | null> {
            // Method 분기 처리 ...
            return this.fetch(url, method, options)
        }

        // 내부 fetch 메서드
        private async fetch(
            url: string,
            method: string,
            options?: RequestOptions,
            accessToken: string = '',
            isAuth: boolean = true
        ): Promise<ResponseEntity | null> {
            ...
            switch (method) {
                case 'GET':
                    response = await restApiUtil
                        .get(url, options)
                        .then((response) => response)
                        .catch((errorResponse) => errorResponse)
                    break
                ...
            }
            return response
        }
    }
})()

// GET 요청을 위한 헬퍼 함수
function httpGet<T = object>(
    url: string,
    options?: RequestOptions
): Promise<T> {
    return RestApi.getInstance().http(url, 'GET', options) as Promise<T>
}

// restApi 객체 내보내기
export const restApi = {
    get: httpGet
}
```

{% endcode %}

API 호출 예:

{% code title="sampleApi.ts" %}

```typescript
import { restApi } from './RestApi'

export const fetchSampleList = async (
    params: SampleSchemaType
): Promise<SampleApiResponse> => {
    const response = (await restApi.get(
        '/api/common/interface/test1',
        { params }
    )) as GridResponse<SampleResponseDetail[]>
    return response.payload
}
```

{% endcode %}

***

##
