尝试Micronaut或Darling,我简化了框架

尝试Micronaut或Darling,我简化了框架


关于micronaut框架,我瞥见了新闻摘要。 他想知道他是哪种野兽。 该框架与装有所有必需工具的弹簧形成对比。


微航海


期待即将举行的开发人员大会,他们将告诉您并演示如何在您的微服务中使用micronaut,因此我决定至少准备一次,并在脑海中至少考虑一些问题和疑问。 可以这么说来做作业。 我决定做几个晚上的小型宠物项目(视情况而定)。 在文章的结尾,将有一个链接到所有项目源的存储库。


Micronaut是一个JVM框架,支持三种开发语言:Java,Kotlin,Groovy。 它是由OCI(Grails给我们的公司)开发的。 它以cli应用程序的形式进行了调整,并提供了一组推荐的库(各种react-http和数据库客户端)

有一个DI实现并重复了Spring的思想,并添加了它的许多芯片-异步性,对AWS Lambda的支持,客户端负载平衡。

服务的想法:我的一个朋友与一个傻瓜一起一次购买了六种各种不同的加密货币,并向他们投资了一个短暂的假期和一个冬装的藏匿处。 我们都知道,所有这些加密货币物质的波动性都是很大的,而且话题本身通常是无法预测的,一个朋友最终决定照顾自己的神经,只不过沉浸在他的“资产”中正在发生的事情。 但是有时候您仍然想看一下,但是所有这些又是什么,突然间它已经很丰富了。 因此,现在想到了一个简单的面板(一个仪表盘,例如Grafana或更简单的东西),一个包含干燥信息的网页,用法定货币(美元,RUR)花费的全部成本的想法。


免责声明


  1. 我们将保留编写自己的解决方案的权宜之计,我们只需要在比HelloWorld更有趣的东西上试用新框架。
  2. 计算算法,预期误差,误差等 (至少对于产品的第一阶段),选择加密交易所以获取信息的有效性,朋友的“投资”加密投资组合也将成为不可能,并且无需讨论或进行深入分析。

因此,有一小部分要求:


  1. Web服务(从外部通过http访问)
  2. 在浏览器中显示一个页面,其中包含加密货币投资组合的总值摘要
  3. 能够配置投资组合(选择用于加载和卸载投资组合结构的JSON格式)。 用于更新和加载作品集的特定REST API,即 2 API:用于保存/更新-POST,用于卸载-GET。 投资组合结构本质上是一个简单的铭牌
    BTC –  0.00005 . XEM –  4.5 . ... 
  4. 我们从加密货币交易所和货币兑换来源获取数据(法定货币)
  5. 计算投资组合总价值的规则:
    计算投资组合总价值的公式


当然,第5段中所写的所有内容都是单独的争议和疑问的主题,但可以说企业希望如此。


项目开始


因此,我们访问了该框架的官方网站,看看如何开始开发。 官方站点提供了安装sdkman工具的信息。 有助于在微型导航框架(和其他包括Grails)上进行项目开发和管理的工具。


各种SDK的一致管理员

小提示:如果您仅在没有任何键的情况下启动项目初始化,则默认情况下将选择gradle收集器。 删除文件夹,然后再次输入密钥:
 mn create-app com.room606.cryptonaut -b=maven 

有趣的一点是,与Spring Tool Suite一样,sdkman在创建项目的阶段为您提供了设置,以设置您一开始要使用的“块”。 我没有对此做太多实验,我还使用默认预设创建了它。


最后,我们在Intellij Idea中打开该项目,并欣赏向导提供的用于创建micronaut项目的一组资源,资源和光盘。


裸项目的结构

对Dockerfile的关注
 FROM openjdk:8u171-alpine3.7 RUN apk --no-cache add curl COPY target/cryptonaut*.jar cryptonaut.jar CMD java ${JAVA_OPTS} -jar cryptonaut.jar 

好吧,这很有趣而且值得称赞。 立即为我们提供了一个工具,用于将应用程序快速输出到Prod / INT / QA /任何环境。 为此,对项目要有一个心理加号。


只需由Maven收集项目,然后收集Docker映像并将其发布到Docker注册表中,或者只是将映像二进制文件作为选项导出到CI系统即可,这就是您想要的。


在resources文件夹中,我们还准备了一个包含应用程序配置参数(Spring中application.properties的模拟)的空白,以及用于logback库的配置文件。 好酷!


我们转到应用程序的入口并学习课程。 我们看到了Spring Boot给我们带来的痛苦的画面。 在这里,框架的开发人员也没有开始发明任何东西。


 public static void main(String[] args) throws IOException { Micronaut.run(Application.class); } 

与熟悉的Spring代码进行比较。


 public static void main(String[] args) { SpringApplication.run(Application.class, args); } 

即 我们还会根据需要将IoC容器与工作中包含的所有bean一起提高。 根据官方文档进行了一些操作之后,我们逐渐开始开发。


我们将需要:


  1. 领域模型
  2. 用于实现REST API的控制器。
  3. 数据存储层(数据库客户端或ORM或其他)
  4. 来自加密货币交换的数据以及法定货币交换的数据的消费者代码。 即 我们需要为第三者服务编写最简单的客户。 在春季,众所周知的RestTemplate非常适合此角色。
  5. 用于灵活管理和启动应用程序的最低配置(让我们考虑一下如何以及如何进行配置)
  6. 测试! 是的,为了放心安全地提取代码并实现新功能,我们需要确保旧版本的稳定性
  7. 正在缓存。 这不是基本要求,但是为了获得良好的性能而应该很好,在我们的场景中,缓存绝对是一个很好的工具。
    剧透:这里一切都会变得很糟。

领域模型


就我们的目的而言,以下模型就足够了:加密货币投资组合的模型,一对法定货币的汇率,法定货币的加密货币价格以及投资组合的总价值。


以下是仅几个模型的代码,其余模型可以在存储库中查看。 是的,我懒得把Lombok在这个项目中。


 Portfolio.java package com.room606.cryptonaut.domain; import java.math.BigDecimal; import java.util.Collections; import java.util.Map; import java.util.TreeMap; public class Portfolio { private Map<String, BigDecimal> coins = Collections.emptyMap(); public Map<String, BigDecimal> getCoins() { return new TreeMap<>(coins); } public void setCoins(Map<String, BigDecimal> coins) { this.coins = coins; } 

 FiatRate.java package com.room606.cryptonaut.domain; import java.math.BigDecimal; public class FiatRate { private String base; private String counter; private BigDecimal value; public FiatRate(String base, String counter, BigDecimal value) { this.base = base; this.counter = counter; this.value = value; } public String getBase() { return base; } public void setBase(String base) { this.base = base; } public String getCounter() { return counter; } public void setCounter(String counter) { this.counter = counter; } public BigDecimal getValue() { return value; } public void setValue(BigDecimal value) { this.value = value; } } 

 Price.java ... Prices.java () ... Total.java ... 

控制器


我们正在尝试编写一个实现最简单API的控制器,根据给定的硬币字母代码发行加密货币的值。


 GET /cryptonaut/restapi/prices.json?coins=BTC&coins=ETH&fiatCurrency=RUR 

应该产生如下内容:


 {"prices":[{"coin":"BTC","value":407924.043300000000},{"coin":"ETH","value":13040.638266000000}],"fiatCurrency":"RUR"} 

根据文档 ,没有什么复杂的,并提醒了相同的Spring方法和约定:


 package com.room606.cryptonaut.rest; import com.room606.cryptonaut.domain.Price; import com.room606.cryptonaut.domain.Prices; import com.room606.cryptonaut.markets.FiatExchangeRatesService; import com.room606.cryptonaut.markets.CryptoMarketDataService; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.*; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @Controller("/cryptonaut/restapi/") public class MarketDataController { private final CryptoMarketDataService cryptoMarketDataService; private final FiatExchangeRatesService fiatExchangeRatesService; public MarketDataController(CryptoMarketDataService cryptoMarketDataService, FiatExchangeRatesService fiatExchangeRatesService) { this.cryptoMarketDataService = cryptoMarketDataService; this.fiatExchangeRatesService = fiatExchangeRatesService; } @Get("/prices.json") @Produces(MediaType.APPLICATION_JSON) public Prices pricesAsJson(@QueryValue("coins") String[] coins, @QueryValue("fiatCurrency") String fiatCurrency) { return getPrices(coins, fiatCurrency); } private Prices getPrices(String[] coins, String fiatCurrency) { List<Price> prices = Stream.of(coins) .map(coin -> new Price(coin, cryptoMarketDataService.getPrice(coin, fiatCurrency))) .collect(Collectors.toList()); return new Prices(prices, fiatCurrency); } } 

即 我们冷静地将POJO指定为返回的类型,并且无需配置任何序列化器/反序列化器,即使不挂任何附加注释,Micronaut也会使用框中的数据构建正确的http正文。 让我们与Spring方式进行比较:


 @RequestMapping(value = "/prices.json", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity<Prices> pricesAsJson(@RequestParam("userId") final String[] coins, @RequestParam("fiatCurrency") String fiatCurrency) { 

一般来说,根据文档,我对控制器没有任何问题,它们只是按预期工作。 他们的拼写既直观又简单。 我们继续前进。


数据存储层


对于该应用程序的第一个版本,我们将仅存储用户的作品集。 通常,我们只会保留一个用户的一个投资组合。 简而言之,我们将不会得到许多用户的支持,只有一个主要用户拥有其加密货币产品组合。 太棒了!


为了实现数据持久性,文档提供了使用JPA连接的选项,以及使用不同客户端从数据库读取数据的片段示例(“ 12.1.5配置Postgres”一节)。 JPA果断地丢弃,并且优先考虑自己编写和处理查询。 根据文档,数据库配置已添加到application.yml(选择Postgres作为RDBMS):


 postgres: reactive: client: port: 5432 host: localhost database: cryptonaut user: crypto password: r1ch13r1ch maxSize: 5 

根据postgres-reactive库添加。 这是用于以异步方式和同步方式使用数据库的客户端。


 <dependency> <groupId>io.micronaut.configuration</groupId> <artifactId>postgres-reactive</artifactId> <version>1.0.0.M4</version> <scope>compile</scope> </dependency> 

最后,将docker-compose.yml文件添加到/ docker-compose.yml文件docker-compose.yml以部署应用程序的未来环境,并在其中添加了数据库组件:


 db: image: postgres:9.6 restart: always environment: POSTGRES_USER: crypto POSTGRES_PASSWORD: r1ch13r1ch POSTGRES_DB: cryptonaut ports: - 5432:5432 volumes: - ${PWD}/../db/init_tables.sql:/docker-entrypoint-initdb.d/1.0.0_init_tables.sql 

下面是具有非常简单的表结构的数据库初始化脚本:


 CREATE TABLE portfolio ( id serial CONSTRAINT coin_amt_primary_key PRIMARY KEY, coin varchar(16) NOT NULL UNIQUE, amount NUMERIC NOT NULL ); 

现在,让我们尝试抛出一个更新用户档案的代码。 我们的投资组合部分将如下所示:


 package com.room606.cryptonaut; import com.room606.cryptonaut.domain.Portfolio; import java.math.BigDecimal; import java.util.Optional; public interface PortfolioService { Portfolio savePortfolio(Portfolio portfolio); Portfolio loadPortfolio(); Optional<BigDecimal> calculateTotalValue(Portfolio portfolio, String fiatCurrency); } 

查看Postgres reactive client方法的集合,我们将抛出此类:


 package com.room606.cryptonaut; import com.room606.cryptonaut.domain.Portfolio; import com.room606.cryptonaut.markets.CryptoMarketDataService; import io.micronaut.context.annotation.Requires; import io.reactiverse.pgclient.Numeric; import io.reactiverse.reactivex.pgclient.*; import javax.inject.Inject; import java.math.BigDecimal; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; public class PortfolioServiceImpl implements PortfolioService { private final PgPool pgPool; ... private static final String UPDATE_COIN_AMT = "INSERT INTO portfolio (coin, amount) VALUES (?, ?) ON CONFLICT (coin) " + "DO UPDATE SET amount = ?"; ... public Portfolio savePortfolio(Portfolio portfolio) { List<Tuple> records = portfolio.getCoins() .entrySet() .stream() .map(entry -> Tuple.of(entry.getKey(), Numeric.create(entry.getValue()), Numeric.create(entry.getValue()))) .collect(Collectors.toList()); pgPool.preparedBatch(UPDATE_COIN_AMT, records, pgRowSetAsyncResult -> { //   pgRowSetAsyncResult.cause().printStackTrace(); }); return portfolio; } ... } 

启动一个环境,我们尝试预先通过审慎实施的API更新我们的产品组合:


 package com.room606.cryptonaut.rest; import com.room606.cryptonaut.PortfolioService; import com.room606.cryptonaut.domain.Portfolio; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.*; import javax.inject.Inject; @Controller("/cryptonaut/restapi/") public class ConfigController { @Inject private PortfolioService portfolioService; @Post("/portfolio") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Portfolio savePortfolio(@Body Portfolio portfolio) { return portfolioService.savePortfolio(portfolio); } 

执行curl请求:


 curl http://localhost:8080/cryptonaut/restapi/portfolio -X POST -H "Content-Type: application/json" --data '{"coins": {"XRP": "35.5", "LSK": "5.03", "XEM": "16.23"}}' -v 

然后在日志中捕获错误:


 io.reactiverse.pgclient.PgException: syntax error at or near "," at io.reactiverse.pgclient.impl.PrepareStatementCommand.handleErrorResponse(PrepareStatementCommand.java:74) at io.reactiverse.pgclient.impl.codec.decoder.MessageDecoder.decodeError(MessageDecoder.java:250) at io.reactiverse.pgclient.impl.codec.decoder.MessageDecoder.decodeMessage(MessageDecoder.java:139) ... 

抓了萝卜之后,我们在正式的扩展坞中找不到任何解决方案,我们尝试在postgres-reactive库本身上用Google搜索扩展坞,事实证明这是正确的解决方案,其中详细举例说明了正确的查询语法。 原来,这是一个占位符参数的问题,您需要使用形式$x ($1, $2, etc.)编号标签。 因此,解决方法是重写目标请求:


 private static final String UPDATE_COIN_AMT = "INSERT INTO portfolio (coin, amount) VALUES ($1, $2) ON CONFLICT (coin) " + "DO UPDATE SET amount = $3"; 

我们重新启动应用程序,尝试相同的REST请求。 数据相加。 让我们继续阅读。


我们面临着最简单的任务,即从数据库中读取用户的加密货币组合并将它们映射到POJO对象。 为此,我们使用pgPool.query方法(SELECT_COINS_AMTS,pgRowSetAsyncResult):


 public Portfolio loadPortfolio() { Map<String, BigDecimal> coins = new HashMap<>(); pgPool.query(SELECT_COINS_AMTS, pgRowSetAsyncResult -> { if (pgRowSetAsyncResult.succeeded()) { PgRowSet rows = pgRowSetAsyncResult.result(); PgIterator pgIterator = rows.iterator(); while (pgIterator.hasNext()) { Row row = pgIterator.next(); coins.put(row.getString("coin"), new BigDecimal(row.getFloat("amount"))); } } else { System.out.println("Failure: " + pgRowSetAsyncResult.cause().getMessage()); } }); Portfolio portfolio = new Portfolio(); portfolio.setCoins(coins); return portfolio; } 

我们将所有这些与负责加密货币投资组合的控制器连接在一起:


 @Controller("/cryptonaut/restapi/") public class ConfigController { ... @Get("/portfolio") @Produces(MediaType.APPLICATION_JSON) public Portfolio loadPortfolio() { return portfolioService.loadPortfolio(); } ... 

重新启动服务。 为了进行测试,我们首先使用至少一些数据填充此组合:


 curl http://localhost:8080/cryptonaut/restapi/portfolio -X POST -H "Content-Type: application/json" --data '{"coins": {"XRP": "35.5", "LSK": "5.03", "XEM": "16.23"}}' -v 

现在,最后测试我们从数据库读取的代码:


 curl http://localhost:8080/cryptonaut/restapi/portfolio -v 

而且……我们得到……一些奇怪的东西:


 {"coins":{}} 

是不是很奇怪? 我们会重新检查该请求十次,尝试再次执行curl请求,甚至重新启动我们的服务。 结果仍然是相同的。在重新读取该方法的签名之后,还记得我们有一个Reactive Pg client ,因此得出结论,我们正在处理异步。 周到的提包证实了这一点! 值得一点点解密,就像瞧,我们得到了非空数据!


再次转到库停靠站,our起袖子,我们用真正的阻塞代码重写了代码,但是完全可以预测:


 Map<String, BigDecimal> coins = new HashMap<>(); PgIterator pgIterator = pgPool.rxPreparedQuery(SELECT_COINS_AMTS).blockingGet().iterator(); while (pgIterator.hasNext()) { Row row = pgIterator.next(); coins.put(row.getString("coin"), new BigDecimal(row.getValue("amount").toString())); } 

现在我们得到了我们所期望的。 我们决定了这个问题,继续。


我们写一个客户来获取市场数据


当然,在这里,我想用最少的自行车来解决这个问题。 结果是两个解决方案:


  • 现成的客户端库,用于访问特定的加密货币交易所
  • 小客户自己的用于申请汇率的代码。 开箱即用的是Micronaut。

使用现成的库,一切都不那么有趣。 我只注意到在快速搜索期间,选择了项目https://github.com/knowm/XChange


原则上,库的架构很简单,只有三个便士-有一组用于接收数据的接口,主要接口和模型类,如Ticker (您可以找到askask ,各种开盘价,收盘价等), CurrencyPairCurrency 。 此外,您已在代码中初始化实现本身,之前已将依赖项与引用特定加密交易所的实现相关联。 我们操作的主要类是MarketDataService.java


例如,对于我们的实验,对于初学者,我们对这样的“配置”感到满意:


 <dependency> <groupId>org.knowm.xchange</groupId> <artifactId>xchange-core</artifactId> <version>4.3.10</version> </dependency> <dependency> <groupId>org.knowm.xchange</groupId> <artifactId>xchange-bittrex</artifactId> <version>4.3.10</version> </dependency> 

以下是执行关键功能的代码-以法定形式计算特定加密货币的成本(请参阅需求块中本文开头所述的公式):


 package com.room606.cryptonaut.markets; import com.room606.cryptonaut.exceptions.CryptonautException; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.Ticker; import org.knowm.xchange.exceptions.CurrencyPairNotValidException; import org.knowm.xchange.service.marketdata.MarketDataService; import javax.inject.Inject; import javax.inject.Singleton; import java.io.IOException; import java.math.BigDecimal; @Singleton public class CryptoMarketDataService { private final FiatExchangeRatesService fiatExchangeRatesService; private final MarketDataService marketDataService; @Inject public CryptoMarketDataService(FiatExchangeRatesService fiatExchangeRatesService, MarketDataServiceFactory marketDataServiceFactory) { this.fiatExchangeRatesService = fiatExchangeRatesService; this.marketDataService = marketDataServiceFactory.getMarketDataService(); } public BigDecimal getPrice(String coinCode, String fiatCurrencyCode) throws CryptonautException { BigDecimal price = getPriceForBasicCurrency(coinCode, Currency.USD.getCurrencyCode()); if (Currency.USD.equals(new Currency(fiatCurrencyCode))) { return price; } else { return price.multiply(fiatExchangeRatesService.getFiatPrice(Currency.USD.getCurrencyCode(), fiatCurrencyCode)); } } private BigDecimal getPriceForBasicCurrency(String coinCode, String fiatCurrencyCode) throws CryptonautException { Ticker ticker = null; try { ticker = marketDataService.getTicker(new CurrencyPair(new Currency(coinCode), new Currency(fiatCurrencyCode))); return ticker.getBid(); } catch (CurrencyPairNotValidException e) { ticker = getTicker(new Currency(coinCode), Currency.BTC); Ticker ticker2 = getTicker(Currency.BTC, new Currency(fiatCurrencyCode)); return ticker.getBid().multiply(ticker2.getBid()); } catch (IOException e) { throw new CryptonautException("Failed to get price for Pair " + coinCode + "/" + fiatCurrencyCode + ": " + e.getMessage(), e); } } private Ticker getTicker(Currency base, Currency counter) throws CryptonautException { try { return marketDataService.getTicker(new CurrencyPair(base, counter)); } catch (CurrencyPairNotValidException | IOException e) { throw new CryptonautException("Failed to get price for Pair " + base.getCurrencyCode() + "/" + counter.getCurrencyCode() + ": " + e.getMessage(), e); } } } 

使用我们自己的接口已尽一切可能,以便略微忽略项目https://github.com/knowm/XChange提供的特定实现。


考虑到以下事实:在很多(如果不是全部)加密货币交易所中,流通的法定货币只有少数几种(美元,欧元,也许仅此而已..),对于用户问题的最终答案,有必要添加另一个数据源-法定货币汇率,以及也是可选的转换器。 即 要回答这个问题,现在WTF加密货币的RUR(目标货币,目标货币)的价格是多少,您将必须回答两个子问题:WTF / BaseCurrency(我们认为是美元),BaseCurrency / RUR,然后将这两个值相乘并给出结果。


对于我们的第一版服务,我们仅支持将美元和卢布作为目标货币。
因此,为了支持RUR,建议采用与服务地理位置相关的资源(我们将仅在俄罗斯托管和使用该资源)。 简而言之,央行利率将适合我们。 在Internet上找到了此类数据的开放源,可以将其作为JSON使用。 太好了


以下是服务对当前汇率请求的响应:


 { "Date": "2018-10-16T11:30:00+03:00", "PreviousDate": "2018-10-13T11:30:00+03:00", "PreviousURL": "\/\/www.cbr-xml-daily.ru\/archive\/2018\/10\/13\/daily_json.js", "Timestamp": "2018-10-15T23:00:00+03:00", "Valute": { "AUD": { "ID": "R01010", "NumCode": "036", "CharCode": "AUD", "Nominal": 1, "Name": "ђІЃ‚Ђ°»№Ѓє№ ґѕ»»°Ђ", "Value": 46.8672, "Previous": 46.9677 }, "AZN": { "ID": "R01020A", "NumCode": "944", "CharCode": "AZN", "Nominal": 1, "Name": "ђ·µЂ±°№ґ¶°ЅЃє№ ј°Ѕ°‚", "Value": 38.7567, "Previous": 38.8889 }, "GBP": { "ID": "R01035", "NumCode": "826", "CharCode": "GBP", "Nominal": 1, "Name": "¤ѓЅ‚ Ѓ‚µЂ»ЅіѕІ ЎѕµґЅµЅЅѕіѕ єѕЂѕ»µІЃ‚І°", "Value": 86.2716, "Previous": 87.2059 }, ... 

实际上,下面是CbrExchangeRatesClient客户端CbrExchangeRatesClient


 package com.room606.cryptonaut.markets.clients; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.room606.cryptonaut.exceptions.CryptonautException; import io.micronaut.http.HttpRequest; import io.micronaut.http.client.Client; import io.micronaut.http.client.RxHttpClient; import javax.inject.Inject; import javax.inject.Singleton; import java.io.IOException; import java.math.BigDecimal; import java.util.*; @Singleton public class CbrExchangeRatesClient { private static final String CBR_DATA_URI = "https://www.cbr-xml-daily.ru/daily_json.js"; @Client(CBR_DATA_URI) @Inject private RxHttpClient httpClient; private final ObjectReader objectReader = new ObjectMapper().reader(); public Map<String, BigDecimal> getRates() { try { //return ratesCache.get("fiatRates"); HttpRequest<?> req = HttpRequest.GET(""); String response = httpClient.retrieve(req, String.class).blockingSingle(); JsonNode json = objectReader.readTree(response); String usdPrice = json.get("Valute").get("USD").get("Value").asText(); String eurPrice = json.get("Valute").get("EUR").get("Value").asText(); String gbpPrice = json.get("Valute").get("GBP").get("Value").asText(); Map<String, BigDecimal> prices = new HashMap<>(); prices.put("USD", new BigDecimal(usdPrice)); prices.put("GBP", new BigDecimal(gbpPrice)); prices.put("EUR", new BigDecimal(eurPrice)); return prices; } catch (IOException e) { throw new CryptonautException("Failed to obtain exchange rates: " + e.getMessage(), e); } } } 

在这里,我们注入RxHttpClient ,它是Micronaut一个组件。 它还使我们可以选择执行异步请求处理或阻塞。 我们选择经典的阻止:


 httpClient.retrieve(req, String.class).blockingSingle(); 

构型


在项目中,您可以突出显示更改和影响业务逻辑或某些特定方面的内容。 让我们将受支持的法定货币列表作为属性,并在应用程序开始时将其注入。


以下代码将舍弃我们尚无法为其计算投资组合价值的货币代码:


 public BigDecimal getFiatPrice(String baseCurrency, String counterCurrency) throws NotSupportedFiatException { if (!supportedCounterCurrencies.contains(counterCurrency)) { throw new NotSupportedFiatException("Counter currency not supported: " + counterCurrency); } Map<String, BigDecimal> rates = cbrExchangeRatesClient.getRates(); return rates.get(baseCurrency); } 

因此,我们的目的是以某种方式将application.yml的值注入supportedCounterCurrencies变量中。


在第一个版本中,此类代码是在FiatExchangeRatesService.java类的字段下方编写的:


 @Value("${cryptonaut.currencies:RUR}") private String supportedCurrencies; private final List<String> supportedCounterCurrencies = Arrays.asList(supportedCurrencies.split("[,]", -1)); 

在这里, placeholder对应于application.yml文档的以下结构:


 micronaut: application: name: cryptonaut #Uncomment to set server port server: port: 8080 postgres: reactive: client: port: 5432 host: localhost database: cryptonaut user: crypto password: r1ch13r1ch maxSize: 5 # app / business logic specific properties cryptonaut: currencies: "RUR" 

应用程序启动,快速冒烟测试...错误!


 Caused by: io.micronaut.context.exceptions.BeanInstantiationException: Error instantiating bean of type [com.room606.cryptonaut.markets.CryptoMarketDataService] Path Taken: new MarketDataController([CryptoMarketDataService cryptoMarketDataService],FiatExchangeRatesService fiatExchangeRatesService) --> new CryptoMarketDataService([FiatExchangeRatesService fiatExchangeRatesService],MarketDataServiceFactory marketDataServiceFactory) at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1266) at io.micronaut.context.DefaultBeanContext.createAndRegisterSingleton(DefaultBeanContext.java:1677) at io.micronaut.context.DefaultBeanContext.getBeanForDefinition(DefaultBeanContext.java:1447) at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:1427) at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:852) at io.micronaut.context.AbstractBeanDefinition.getBeanForConstructorArgument(AbstractBeanDefinition.java:943) ... 36 common frames omitted Caused by: java.lang.NullPointerException: null at com.room606.cryptonaut.markets.FiatExchangeRatesService.<init>(FiatExchangeRatesService.java:20) at com.room606.cryptonaut.markets.$FiatExchangeRatesServiceDefinition.build(Unknown Source) at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1252) ... 41 common frames omitted 

Micronaut Spring , compile time . , :


 @Value("${cryptonaut.currencies:RUR}") private String supportedCurrencies; private List<String> supportedCounterCurrencies; @PostConstruct void init() { supportedCounterCurrencies = Arrays.asList(supportedCurrencies.split("[,]", -1)); } 

, – javax.annotation.PostConstruct , , , , . .


, , Spring. micronaut @Property Map<String, String> , @Configuration , Random Properties (, ID , , - ) PropertySourceLoader , .. . SpringApplicationContext ( xml , web , groovy , ClassPath etc.) , .



, micronaut. Embedded Server feature, Groovy Spock . Java , groovy- . , EmbeddedServer + HttpClient Micronaut API —


 GET /cryptonaut/restapi/portfolio/total.json?fiatCurrency={x} 

API, .


:


 public class PortfolioReportsControllerTest { private static EmbeddedServer server; private static HttpClient client; @Inject private PortfolioService portfolioService; @BeforeClass public static void setupServer() { server = ApplicationContext.run(EmbeddedServer.class); client = server .getApplicationContext() .createBean(HttpClient.class, server.getURL()); } @AfterClass public static void stopServer() { if(server != null) { server.stop(); } if(client != null) { client.stop(); } } @Test public void total() { //TODO: Seems like code smell. I don't like it.. portfolioService = server.getApplicationContext().getBean(PortfolioService.class); Portfolio portfolio = new Portfolio(); Map<String, BigDecimal> coins = new HashMap<>(); BigDecimal amt1 = new BigDecimal("570.05"); BigDecimal amt2 = new BigDecimal("2.5"); coins.put("XRP", amt1); coins.put("QTUM", amt2); portfolio.setCoins(coins); portfolioService.savePortfolio(portfolio); HttpRequest request = HttpRequest.GET("/cryptonaut/restapi/portfolio/total.json?fiatCurrency=USD"); HttpResponse<Total> rsp = client.toBlocking().exchange(request, Total.class); assertEquals(200, rsp.status().getCode()); assertEquals(MediaType.APPLICATION_JSON_TYPE, rsp.getContentType().get()); Total val = rsp.body(); assertEquals("USD", val.getFiatCurrency()); assertEquals(TEST_VALUE.toString(), val.getValue().toString()); assertEquals(amt1.toString(), val.getPortfolio().getCoins().get("XRP").toString()); assertEquals(amt2.toString(), val.getPortfolio().getCoins().get("QTUM").toString()); } } 

, mock PortfolioService.java :


 package com.room606.cryptonaut; import com.room606.cryptonaut.domain.Portfolio; import io.micronaut.context.annotation.Requires; import javax.inject.Singleton; import java.math.BigDecimal; import java.util.Optional; @Singleton @Requires(env="test") public class MockPortfolioService implements PortfolioService { private Portfolio portfolio; public static final BigDecimal TEST_VALUE = new BigDecimal("56.65"); @Override public Portfolio savePortfolio(Portfolio portfolio) { this.portfolio = portfolio; return portfolio; } @Override public Portfolio loadPortfolio() { return portfolio; } @Override public Optional<BigDecimal> calculateTotalValue(Portfolio portfolio, String fiatCurrency) { return Optional.of(TEST_VALUE); } } 

@Requires(env="test") , Application Context . -, micronaut test, , . , , PortfolioServiceImpl @Requires(notEnv="test") . – . Micronaut .


, – , , – mockito . :


 @Test public void priceForUsdDirectRate() throws IOException { when(marketDataServiceFactory.getMarketDataService()).thenReturn(marketDataService); String coinCode = "ETH"; String fiatCurrencyCode = "USD"; BigDecimal priceA = new BigDecimal("218.58"); Ticker targetTicker = new Ticker.Builder().bid(priceA).build(); when(marketDataService.getTicker(new CurrencyPair(new Currency(coinCode), new Currency(fiatCurrencyCode)))).thenReturn(targetTicker); CryptoMarketDataService cryptoMarketDataService = new CryptoMarketDataService(fiatExchangeRatesService, marketDataServiceFactory); assertEquals(priceA, cryptoMarketDataService.getPrice(coinCode, fiatCurrencyCode)); } 


, . . , . , , - IP. , @Cacheable .


快取

但是,这里的一切都不成功。这方面的文档令人困惑,在滚动几个屏幕后,您会发现相互矛盾的配置(appliction.yml)。作为缓存,选择了redis,并将其放在旁边的Docker容器中。这是它的配置:
 redis: image: 'bitnami/redis:latest' environment: - ALLOW_EMPTY_PASSWORD=yes ports: - '6379:6379' 

这是@Cacheable注释的一段代码:


 @Cacheable("fiatRates") public Map<String, BigDecimal> getRates() { HttpRequest<?> req = HttpRequest.GET(""); String response = httpClient.retrieve(req, String.class).blockingSingle(); try { JsonNode json = objectReader.readTree(response); String usdPrice = json.get("Valute").get("USD").get("Value").asText(); String eurPrice = json.get("Valute").get("EUR").get("Value").asText(); String gbpPrice = json.get("Valute").get("GBP").get("Value").asText(); Map<String, BigDecimal> prices = new HashMap<>(); prices.put("USD", new BigDecimal(usdPrice)); prices.put("GBP", new BigDecimal(gbpPrice)); prices.put("EUR", new BigDecimal(eurPrice)); return prices; } catch (IOException e) { throw new RuntimeException(e); } } 

但是,application.yml最重要的奥秘是。我尝试了各种配置。这是一个:


 caches: fiatrates: expireAfterWrite: "1h" redis: caches: fiatRates: expireAfterWrite: "1h" port: 6379 server: localhost 

这是一个:


 #cache redis: uri: localhost:6379 caches: fiatRates: expireAfterWrite: "1h" 

甚至尝试删除缓存名称中的大写字母。但是结果是,启动应用程序时得到了相同的结果-“发生意外错误:未为名称配置缓存:fiatRates”:


 ERROR imhsnetty.RoutingInBoundHandler - Unexpected error occurred: No cache configured for name: fiatRates io.micronaut.context.exceptions.ConfigurationException: No cache configured for name: fiatRates at io.micronaut.cache.DefaultCacheManager.getCache(DefaultCacheManager.java:67) at io.micronaut.cache.interceptor.CacheInterceptor.interceptSync(CacheInterceptor.java:176) at io.micronaut.cache.interceptor.CacheInterceptor.intercept(CacheInterceptor.java:128) at io.micronaut.aop.MethodInterceptor.intercept(MethodInterceptor.java:41) at io.micronaut.aop.chain.InterceptorChain.proceed(InterceptorChain.java:147) at com.room606.cryptonaut.markets.clients.$CbrExchangeRatesClientDefinition$Intercepted.getRates(Unknown Source) at com.room606.cryptonaut.markets.FiatExchangeRatesService.getFiatPrice(FiatExchangeRatesService.java:30) at com.room606.cryptonaut.rest.MarketDataController.index(MarketDataController.java:34) at com.room606.cryptonaut.rest.$MarketDataControllerDefinition$$exec2.invokeInternal(Unknown ... 

GitHub - SO . . , . , . boilerplate-, - Redis - , , Spring Boot , .



, Micronaut – , Spring-.


标杆管理

当然,在这里,您应该用十二个免责声明来表明:我不是基准专家,有关启动和测量启动时间的方法,有关实验条件(机器负载,硬件配置,操作系统等)的信息。

但是,最后一点:


操作系统: 16.04.1-Ubuntu x86_64 x86_64 x86_64 GNU / Linux
CPU: Intel®Core(TM)i7-7700HQ CPU @ 2.80GHz
Mem: 2 x 8 Gb DDR4,速度:2400 MHz
SSD磁盘: PCIe NVMe M SSD 2,256 GB


我的 防御 技术:


  1. 兑换独轮车
  2. 开车
  3. 申请开始
  4. 与此并行,循环中的客户端代码轮询一个API,该API仅在响应中返回一行
  5. 收到API的响应后,“计时器”就会停止。
  6. 以毫秒为单位的结果已在平板电脑上仔细输入

Rest Controller – IoC-, .


“ ” :


MicronautSpring Boot
Avg.(ms)2708.42735.2
cryptonaut (ms)1082--

, – 27 Micronaut . , .


?


. , , , – . . Groovy-, , . SO Spring. , , . — . Spring.


:


  • Micronaut – service-discovery, AWS
  • Java. Kotlin Groovy.

.

Source: https://habr.com/ru/post/zh-CN427595/


All Articles