CoingeckoService.kt
package com.distasilucas.cryptobalancetracker.service
import com.distasilucas.cryptobalancetracker.constants.COINGECKO_CRYPTOS_CACHE
import com.distasilucas.cryptobalancetracker.constants.CRYPTO_INFO_CACHE
import com.distasilucas.cryptobalancetracker.exception.ApiException
import com.distasilucas.cryptobalancetracker.model.response.coingecko.CoingeckoCrypto
import com.distasilucas.cryptobalancetracker.model.response.coingecko.CoingeckoCryptoInfo
import io.github.oshai.kotlinlogging.KotlinLogging
import org.apache.commons.lang3.StringUtils
import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.annotation.Cacheable
import org.springframework.core.ParameterizedTypeReference
import org.springframework.http.HttpHeaders
import org.springframework.retry.annotation.Backoff
import org.springframework.retry.annotation.Retryable
import org.springframework.stereotype.Service
import org.springframework.util.MultiValueMap
import org.springframework.web.client.RestClient
import org.springframework.web.client.RestClientException
import org.springframework.web.util.UriBuilder
import org.springframework.web.util.UriComponentsBuilder
import java.net.URI
import java.util.function.Function
const val COIN_URI = "/coins/"
const val COINS_URI = "$COIN_URI/list"
@Service
class CoingeckoService(
@Value("\${coingecko.api-key.pro}")
private val proCoingeckoApiKey: String,
@Value("\${coingecko.api-key.demo}")
private val demoCoingeckoApiKey: String,
private val coingeckoRestClient: RestClient
) {
private val logger = KotlinLogging.logger { }
private val DEMO_API_KEY_QUERY_PARAM = "x_cg_demo_api_key"
private val PRO_API_KEY_QUERY_PARAM = "x_cg_pro_api_key"
@Cacheable(cacheNames = [COINGECKO_CRYPTOS_CACHE])
@Retryable(retryFor = [RestClientException::class], backoff = Backoff(delay = 1500))
fun retrieveAllCryptos(): List<CoingeckoCrypto> {
val coingeckoCryptosURI = getCryptosURI()
val uriAsString = coingeckoCryptosURI.apply(UriComponentsBuilder.newInstance())
logger.info { "Hitting Coingecko API for URI [$uriAsString]. Retrieving all cryptos." }
return coingeckoRestClient.get()
.uri(coingeckoCryptosURI)
.retrieve()
.body(object : ParameterizedTypeReference<List<CoingeckoCrypto>>() {}) ?: emptyList()
}
@Cacheable(cacheNames = [CRYPTO_INFO_CACHE], key = "#coingeckoCryptoId")
@Retryable(retryFor = [RestClientException::class], backoff = Backoff(delay = 1500))
fun retrieveCryptoInfo(coingeckoCryptoId: String): CoingeckoCryptoInfo {
val coinURI = "$COIN_URI/$coingeckoCryptoId"
val coingeckoCryptoInfoURI = getCoingeckoCryptoInfoURI(coinURI)
val uriAsString = coingeckoCryptoInfoURI.apply(UriComponentsBuilder.newInstance())
logger.info { "Hitting Coingecko API for URI [$uriAsString]. Retrieving information for [$coingeckoCryptoId]." }
return coingeckoRestClient.get()
.uri(coingeckoCryptoInfoURI)
.retrieve()
.body(object : ParameterizedTypeReference<CoingeckoCryptoInfo>() {})
?: throw ApiException("Error retrieving crypto information for $coingeckoCryptoId")
}
private fun getCryptosURI(): Function<UriBuilder, URI> {
val proCoingeckoURI = Function { uriBuilder: UriBuilder ->
uriBuilder.path(COINS_URI)
.queryParam(PRO_API_KEY_QUERY_PARAM, proCoingeckoApiKey)
.build()
}
val freeCoingeckoURI = Function { uriBuilder: UriBuilder ->
uriBuilder.path(COINS_URI)
.queryParam(DEMO_API_KEY_QUERY_PARAM, demoCoingeckoApiKey)
.build()
}
return if (StringUtils.isNotBlank(proCoingeckoApiKey)) proCoingeckoURI else freeCoingeckoURI
}
private fun getCoingeckoCryptoInfoURI(url: String): Function<UriBuilder, URI> {
val params: MultiValueMap<String, String> = HttpHeaders()
params.add("tickers", "false")
params.add("community_data", "false")
params.add("developer_data", "false")
params.add("localization", "false")
val proCoingeckoUri = Function { uriBuilder: UriBuilder ->
uriBuilder.path(url)
.queryParam(PRO_API_KEY_QUERY_PARAM, proCoingeckoApiKey)
.queryParams(params)
.build()
}
val freeCoingeckoUri = Function { uriBuilder: UriBuilder ->
uriBuilder.path(url)
.queryParam(DEMO_API_KEY_QUERY_PARAM, demoCoingeckoApiKey)
.queryParams(params)
.build()
}
return if (StringUtils.isNotBlank(proCoingeckoApiKey)) proCoingeckoUri else freeCoingeckoUri
}
}