CoingeckoService.java

package com.distasilucas.cryptobalancetracker.service;

import com.distasilucas.cryptobalancetracker.model.response.coingecko.CoingeckoCrypto;
import com.distasilucas.cryptobalancetracker.model.response.coingecko.CoingeckoCryptoInfo;
import lombok.extern.slf4j.Slf4j;
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.util.StringUtils;
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.List;
import java.util.function.Function;

import static com.distasilucas.cryptobalancetracker.constants.Constants.COINGECKO_CRYPTOS_CACHE;
import static com.distasilucas.cryptobalancetracker.constants.Constants.CRYPTO_INFO_CACHE;

@Slf4j
@Service
public class CoingeckoService {

    private static final String COIN_URI = "/coins/";
    private static final String COINS_URI = COIN_URI + "/list";
    private static final String DEMO_API_KEY_QUERY_PARAM = "x_cg_demo_api_key";
    private static final String PRO_API_KEY_QUERY_PARAM = "x_cg_pro_api_key";

    private final String proCoingeckoApiKey;
    private final String demoCoingeckoApiKey;
    private final RestClient coingeckoRestClient;

    public CoingeckoService(@Value("${coingecko.api-key.pro}") String proCoingeckoApiKey,
                            @Value("${coingecko.api-key.demo}") String demoCoingeckoApiKey,
                            RestClient coingeckoRestClient) {
        this.proCoingeckoApiKey = proCoingeckoApiKey;
        this.demoCoingeckoApiKey = demoCoingeckoApiKey;
        this.coingeckoRestClient = coingeckoRestClient;
    }

    @Cacheable(cacheNames = COINGECKO_CRYPTOS_CACHE)
    @Retryable(retryFor = RestClientException.class, backoff = @Backoff(delay = 1500))
    public List<CoingeckoCrypto> retrieveAllCryptos() {
        var coingeckoCryptosURI = getCryptosURI();
        var uriAsString = coingeckoCryptosURI.apply(UriComponentsBuilder.newInstance());
        log.info("Hitting Coingecko API for URI [{}] Retrieving all cryptos.", uriAsString);

        return coingeckoRestClient.get()
            .uri(coingeckoCryptosURI)
            .retrieve()
            .body(new ParameterizedTypeReference<>() {});
    }

    @Cacheable(cacheNames = CRYPTO_INFO_CACHE, key = "#coingeckoCryptoId")
    @Retryable(retryFor = RestClientException.class, backoff = @Backoff(delay = 1500))
    public CoingeckoCryptoInfo retrieveCryptoInfo(String coingeckoCryptoId) {
        var coinURI = COIN_URI.concat(coingeckoCryptoId);
        var coingeckoCryptoInfoURI = getCoingeckoCryptoInfoURI(coinURI);
        var uriAsString = coingeckoCryptoInfoURI.apply(UriComponentsBuilder.newInstance());
        log.info("Hitting Coingecko API for URI [{}] Retrieving information for {}.", uriAsString, coingeckoCryptoId);

        return coingeckoRestClient.get()
            .uri(coingeckoCryptoInfoURI)
            .retrieve()
            .body(CoingeckoCryptoInfo.class);
    }

    private Function<UriBuilder, URI> getCryptosURI() {
        Function<UriBuilder, URI> proCoingeckoURI = uriBuilder -> uriBuilder.path(COINS_URI)
            .queryParam(PRO_API_KEY_QUERY_PARAM, proCoingeckoApiKey)
            .build();

        Function<UriBuilder, URI> freeCoingeckoURI = uriBuilder -> uriBuilder.path(COINS_URI)
            .queryParam(DEMO_API_KEY_QUERY_PARAM, demoCoingeckoApiKey)
            .build();

        return StringUtils.hasText(proCoingeckoApiKey) ? proCoingeckoURI : freeCoingeckoURI;
    }

    private Function<UriBuilder, URI> getCoingeckoCryptoInfoURI(String url) {
        MultiValueMap<String, String> commonParams = new HttpHeaders();
        commonParams.add("tickers", "false");
        commonParams.add("community_data", "false");
        commonParams.add("developer_data", "false");
        commonParams.add("localization", "false");

        Function<UriBuilder, URI> proCoingeckoURI = uriBuilder -> uriBuilder.path(url)
            .queryParam(PRO_API_KEY_QUERY_PARAM, proCoingeckoApiKey)
            .queryParams(commonParams)
            .build();

        Function<UriBuilder, URI> freeCoingeckoURI = uriBuilder -> uriBuilder.path(url)
            .queryParams(commonParams)
            .queryParam(DEMO_API_KEY_QUERY_PARAM, demoCoingeckoApiKey)
            .build();

        return StringUtils.hasText(proCoingeckoApiKey) ? proCoingeckoURI : freeCoingeckoURI;
    }
}