From 16adca5d6cc29daa333534c234246472673a981f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Pit=C3=A1k?= <xpitak@fi.muni.cz> Date: Sun, 21 Apr 2024 03:33:05 +0200 Subject: [PATCH] Add implementation for Exchange rates from public API --- .../ExchangeRateInitializer.java | 39 ++++++++++++++++ .../messaging/ProcessProducer.java | 9 ---- .../banking/application/proxy/Dummy.java | 20 -------- .../application/proxy/ExchangeRatesApi.java | 10 ---- .../rate/ExchangeRateResponseProcessor.java | 46 +++++++++++++++++++ .../proxy/rate/ExchangeRatesApi.java | 16 +++++++ .../service/ExchangeRateServiceImpl.java | 25 ++++++---- .../src/main/resources/application.yaml | 5 ++ 8 files changed, 123 insertions(+), 47 deletions(-) create mode 100644 transaction-processor/src/main/java/cz/muni/pa165/banking/application/configuration/ExchangeRateInitializer.java delete mode 100644 transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/Dummy.java delete mode 100644 transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/ExchangeRatesApi.java create mode 100644 transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRateResponseProcessor.java create mode 100644 transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRatesApi.java diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/configuration/ExchangeRateInitializer.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/configuration/ExchangeRateInitializer.java new file mode 100644 index 0000000..8cff4df --- /dev/null +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/configuration/ExchangeRateInitializer.java @@ -0,0 +1,39 @@ +package cz.muni.pa165.banking.application.configuration; + +import cz.muni.pa165.banking.domain.money.exchange.ExchangeRateService; +import jakarta.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Currency; +import java.util.List; + +@Component +public class ExchangeRateInitializer { + + private final Logger logger = LoggerFactory.getLogger(ExchangeRateInitializer.class); + + private final ExchangeRateService exchangeRateService; + + @Value("#{'${banking.apps.rates.initial.currencies}'.split(',')}") + private List<String> currencies; + + public ExchangeRateInitializer(ExchangeRateService exchangeRateService) { + this.exchangeRateService = exchangeRateService; + } + + @PostConstruct + void initialize() { + for (String currency : currencies) { + try { + // ignore result, just call service to trigger mechanism to cache data + exchangeRateService.getRate(Currency.getInstance(currency), Currency.getInstance(currency)); + } catch (Exception e) { + logger.warn(String.format("Initializing exchange rates for %s failed. API might be unavailable!", currency)); + } + } + } + +} diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/messaging/ProcessProducer.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/messaging/ProcessProducer.java index e625165..ff6cb78 100644 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/messaging/ProcessProducer.java +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/messaging/ProcessProducer.java @@ -3,13 +3,10 @@ package cz.muni.pa165.banking.application.messaging; import com.fasterxml.jackson.databind.ObjectMapper; import cz.muni.pa165.banking.domain.messaging.MessageProducer; import cz.muni.pa165.banking.domain.messaging.ProcessRequest; -import cz.muni.pa165.banking.domain.transaction.TransactionType; import cz.muni.pa165.banking.exception.ServerError; -import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Service; import java.util.Map; -import java.util.UUID; @Service public class ProcessProducer implements MessageProducer { @@ -22,12 +19,6 @@ public class ProcessProducer implements MessageProducer { this.messagingService = messagingService; this.mapper = mapper; } - - @PostConstruct - void testing() { - ProcessRequest data = new ProcessRequest(UUID.randomUUID(), TransactionType.DEPOSIT); - send(data); - } @Override public void send(ProcessRequest data) { diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/Dummy.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/Dummy.java deleted file mode 100644 index f832677..0000000 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/Dummy.java +++ /dev/null @@ -1,20 +0,0 @@ -package cz.muni.pa165.banking.application.proxy; - -import cz.muni.pa165.banking.account.query.CustomerServiceApi; -import jakarta.annotation.PostConstruct; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class Dummy { - - @Autowired - private CustomerServiceApi customerServiceApi; - - @PostConstruct - public void test() { - var jj = customerServiceApi.getBalance("id1"); - System.out.println("jozo"); - } - -} diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/ExchangeRatesApi.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/ExchangeRatesApi.java deleted file mode 100644 index 62e21b8..0000000 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/ExchangeRatesApi.java +++ /dev/null @@ -1,10 +0,0 @@ -package cz.muni.pa165.banking.application.proxy; - -// some proxy for a REST API, which contains real-time data about the currency rates -// e.g. https://exchangeratesapi.io/ -// https://nordicapis.com/10-apis-for-currency-exchange-rates/ -public interface ExchangeRatesApi { - - // TODO nejake methods po vybere a nastudovani API - -} diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRateResponseProcessor.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRateResponseProcessor.java new file mode 100644 index 0000000..e5f4ba4 --- /dev/null +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRateResponseProcessor.java @@ -0,0 +1,46 @@ +package cz.muni.pa165.banking.application.proxy.rate; + +import cz.muni.pa165.banking.exception.UnexpectedValueException; + +import java.math.BigDecimal; +import java.util.Currency; +import java.util.HashMap; +import java.util.Map; + +public class ExchangeRateResponseProcessor { + + public static Map<Currency, BigDecimal> process(Map<String, Object> response) { + if (!response.get("result").equals("success")) { + throw new UnexpectedValueException("Exchange rate API unavailable"); + } + + Map<String, Object> rates = (Map<String, Object>) response.get("conversion_rates"); + + Map<Currency, BigDecimal> result = new HashMap<>(); + for (String currencyCode : rates.keySet()) { + Currency currency; + try { + currency = Currency.getInstance(currencyCode); + } catch (Exception e) { + continue; + } + Object value = rates.get(currencyCode); + + BigDecimal rate; + if (value instanceof Integer val) { + rate = BigDecimal.valueOf(val); + } else if (value instanceof Double val) { + rate = BigDecimal.valueOf(val); + } else { + String strVal = value.toString(); + double val = Double.parseDouble(strVal); + rate = BigDecimal.valueOf(val); + } + + result.put(currency, rate); + } + + return result; + } + +} diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRatesApi.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRatesApi.java new file mode 100644 index 0000000..f95e758 --- /dev/null +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRatesApi.java @@ -0,0 +1,16 @@ +package cz.muni.pa165.banking.application.proxy.rate; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Map; + +// https://app.exchangerate-api.com +@FeignClient(name = "ExchangeRateApi", url = "${banking.apps.rates.url}") +public interface ExchangeRatesApi { + + @GetMapping("/latest/{currency}") + Map<String, Object> getRatesOfCurrency(@RequestParam String currency); + +} diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/ExchangeRateServiceImpl.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/ExchangeRateServiceImpl.java index fb0786f..4a0c3ca 100644 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/ExchangeRateServiceImpl.java +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/ExchangeRateServiceImpl.java @@ -1,25 +1,34 @@ package cz.muni.pa165.banking.application.service; +import cz.muni.pa165.banking.application.proxy.rate.ExchangeRateResponseProcessor; +import cz.muni.pa165.banking.application.proxy.rate.ExchangeRatesApi; import cz.muni.pa165.banking.domain.money.exchange.ExchangeRateService; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.Currency; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; @Service public class ExchangeRateServiceImpl implements ExchangeRateService { - // TODO call external API containing current information about exchange rates -> Milestone2 - // either actual API containing real data, or custom 'mock' API containing static data - -// private final ExchangeRatesApi proxy; + private final ExchangeRatesApi exchangeRatesApi; + + Map<Currency, Map<Currency, BigDecimal>> exchangeRates = new ConcurrentHashMap<>(); -// public ExchangeRateServiceImpl(ExchangeRatesApi proxy) { -// this.proxy = proxy; -// } + public ExchangeRateServiceImpl(ExchangeRatesApi exchangeRatesApi) { + this.exchangeRatesApi = exchangeRatesApi; + } @Override public BigDecimal getRate(Currency base, Currency target) { - return BigDecimal.ONE; + if (!exchangeRates.containsKey(base)) { + Map<String, Object> response = exchangeRatesApi.getRatesOfCurrency(base.getCurrencyCode()); + exchangeRates.put(base, ExchangeRateResponseProcessor.process(response)); + } + + return exchangeRates.get(base).get(target); } + } diff --git a/transaction-processor/src/main/resources/application.yaml b/transaction-processor/src/main/resources/application.yaml index e901a7e..5ab2509 100644 --- a/transaction-processor/src/main/resources/application.yaml +++ b/transaction-processor/src/main/resources/application.yaml @@ -11,6 +11,11 @@ banking: host: localhost port: 8081 url: ${banking.apps.query.host}:${banking.apps.query.port} + rates: + api-key: 5f40db3a892e886bda8a6967 + url: https://v6.exchangerate-api.com/v6/${banking.apps.rates.api-key} + initial: + currencies: CZK, EUR, USD spring: application: -- GitLab