본문 바로가기
스프링/만들면서 배우는 실무 백엔드 개발

5. Upbit API 연결하기

by kdohyeon (김대니) 2023. 3. 22.
반응형

가장 기본적인 형태로 멀티 모듈 프로젝트를 구성했습니다. 이제 가상화폐 거래소 Upbit 에서 제공하는 OPEN API 를 연결해서 crypto-labs 서비스가 제공할 수 있는 하나의 API 를 만들어보겠습니다.


https://github.com/kdohyeon/crypto-labs/pull/9/commits

API Key 발급받기

OPEN API 를 사용하기 위해서는 API Key 를 먼저 발급받아야 한다. Upbit 의 경우에는 여기서 받을 수 있다.

API Key 를 발급받기 위해서는 로그인을 해야 한다.

Open API 사용하기를 클릭하면 로그인을 할 수 있다.

Upbit 로그인 화면

사용하고자 하는 기능만 선택을 하고 접근할 전용 IP 주소를 입력한다. 일단은 조회만 하면 되니 조회 관련 기능만 선택했다.

Open API Key 발급받기 버튼을 클릭하면 네이버나 카카오로 인증이 필요하다. 인증 이후에는 API Key 가 발급되는데 Access key 와 Secret key 모두 저장해두어야 한다.

Access key, Secret key 꼭 저장해두기

IP 주소의 경우에는 MacOS 기준으로 시스템 설정 > WI-FI > 세부사항... 에서 IP 주소를 확인할 수 있다.

Mac 에서 IP 주소 찾기

발급받은 API Key 입력하기

발급받은 API Key 는 adapter-http 모듈의 resources 디렉토리에 property 파일을 생성하여 넣어두면 된다. API Key 는 민감 정보이기 때문에 Github 에 올리면 안된다고 생각되어 별도의 프로퍼티 파일로 관리할 수 있다. 해당 파일을 .gitignore 에 입력하고 로컬에서만 관리할 수 있도록 하자.

민감 정보는 따로 관리할 수 있도록 한다

해당 프로퍼티 파일은 스프링 어플리케이션이 실행될 때 import 를 하면 된다. app-api 모듈에 위치한 application.yml 파일에서 불러올 수 있다. 

api key 는 로컬에서 따로 관리하기

프로퍼티 파일에 입력해둔 API Key 값들은 @Value 어노테이션을 활용해서 코드에 적용할 수 있다.

@Value 를 활용하여 API key 값 받아오기

RestTemplate 설정하기

Open API 를 호출하는 방법은 다양하다. 이번 시간에는 RestTemplate 을 사용해서 API 를 호출해보려고 한다.

먼저 RestTemplate 에 대한 설정부터 해줘야 한다. HTTP 통신을 해야 하기 때문에 connection timeout 과 read timeout 에 대한 설정을 해줘야 하고, 읽어온 JSON 데이터를 ObjectMapper 를 활용하여 파싱을 해야 한다. 

 

@Configuration
class HttpClientConfig {
    @Bean
    fun restTemplate(
        builder: RestTemplateBuilder,
        @Qualifier(BEAN_NAME_OBJECT_MAPPER) restTemplateObjectMapper: ObjectMapper,
    ): RestTemplate {
        return builder
            .additionalMessageConverters(MappingJackson2HttpMessageConverter(restTemplateObjectMapper))
            .setConnectTimeout(Duration.ofSeconds(CONNECT_TIMEOUT_SECONDS))
            .setReadTimeout(Duration.ofSeconds(READ_TIMEOUT_SECONDS))
            .build()
    }

    companion object {
        const val CONNECT_TIMEOUT_SECONDS = 1L
        const val READ_TIMEOUT_SECONDS = 5L
    }
}

ObjectMapper 에 대한 설정은 adapter-json 모듈에서 해주었다.

@Configuration
class ObjectMappers {

    companion object {
        const val BEAN_NAME_OBJECT_MAPPER = "restTemplateObjectMapper"
    }

    @Bean(BEAN_NAME_OBJECT_MAPPER)
    fun restTemplateObjectMapper(): ObjectMapper {
        return JsonMapper.builder()
            .findAndAddModules()
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .defaultTimeZone(DEFAULT_TIME_ZONE)
            .defaultLocale(DEFAULT_LOCALE)
            .build()
    }
}

REST API 호출하기

호출하고자 하는 API 는 마켓 코드를 조회하는 API 이다. 업비트에서 거래 가능한 마켓 목록을 조회할 수 있다.

https://api.upbit.com/v1/market/all

https://api.upbit.com/v1 /market/all

UpbitMarketHttpClient

위에서 설정한 RestTemplate 을 주입받는다. RestTemplate 호출을 하기 위해서는 uri, httpMethod, headers 가 필요한데, 이는 restApiBuilder 에 넣어두었다. 그 이유는 호출할 API 마다 uri, httpMethod, headers 를 설정하는 부분이 조금씩 달라서 별도 클래스로 관리하는 것이 관리 측면에서 효과적일 것이라고 생각했다. 

@Component
class UpbitMarketHttpClient(
    private val restTemplate: RestTemplate,
    private val builders: List<RestApiBuilder>,
) : UpbitMarketClient {
    override fun marketAll(): List<UpbitMarket> {
        val targetRestApiType = RestApiType.MARKET_ALL
        val restApiBuilder = builders.first { it.isTarget(targetRestApiType) }

        return restTemplate.exchange(
            restApiBuilder.buildUri(),
            targetRestApiType.httpMethod,
            HttpEntity<HttpHeaders>(restApiBuilder.buildHeaders()),
            parameterizedTypeReference<List<UpbitMarket>>()
        ).body.takeIf { it != null && it.isNotEmpty() }
            ?: emptyList()
    }
}

MarketSearchService

@Service
class MarketSearchService(
    private val upbitMarketClient: UpbitMarketClient,
    private val marketDtoConverter: MarketDtoConverter,
) : SearchMarketUseCase {
    override fun marketAll(): List<MarketDto> {
        return upbitMarketClient.marketAll().map { marketDtoConverter.convert(it) }
    }
}

MarketController

@RestController
class MarketController(
    private val searchMarketUseCase: SearchMarketUseCase,
) {
    @GetMapping("/api/v1/market/all")
    fun marketAll(): List<MarketDto> {
        return searchMarketUseCase.marketAll();
    }
}

호출해보기

Controller 와 Service 를 모두 구현하고 만들 Controller 를 호출해보면 다음과 같이 결과가 잘 출력된다.

IntelliJ 에서 http 파일을 만들어 호출해보기
결과

반응형

댓글