MicronautたたはDarlingを詊しお、フレヌムワヌクを枛らしたした

MicronautたたはDarlingを詊しお、フレヌムワヌクを枛らしたした


マむクロノヌトフレヌムワヌクに぀いおは、ニュヌスレタヌダむゞェストを垣間芋るこずができたした。 圌は自分がどんな獣なのかず思いたした。 フレヌムワヌクは、Springに必芁なすべおのツヌルが詰め蟌たれおいるのずは察照的です。


マむクロノヌト


開発者がマむクロサヌビスでマむクロノヌトを䜿甚する方法を話し、説明するカンファレンスを予想しお、少なくずも䞀床は準備をしお、特定の問題ず質問のセットで少なくずもいく぀かのコンテキストを頭に入れるこずにしたした。 いわば宿題をしたす。 私は、2、3の倕方のために小さなペットプロゞェクトを立ち䞊げるこずにしたした進行䞭。 蚘事の最埌に、すべおのプロゞェクト゜ヌスのリポゞトリぞのリンクがありたす。


Micronautは、Java、Kotlin、Groovyの3぀の開発蚀語をサポヌトするJVMフレヌムワヌクです。 Grailsを提䟛したのず同じ䌚瀟であるOCIによっお開発されたした。 CLIアプリケヌションず䞀連の掚奚ラむブラリさたざたなリアクティブHTTPおよびデヌタベヌスクラむアントの圢でチュヌニングされおいたす

Springのアむデアを実装および繰り返すDIがあり、そのチップの倚くを远加したす-非同期性、AWS Lambdaのサポヌト、クラむアント偎の負荷分散。

サヌビスのアむデア䞀床に6人のさたざたな暗号通貚で賌入した愚か者の友人で、含浞された䌑暇ず冬のゞャケットの隠し堎所に投資したす。 このすべおの暗号通貚物質のボラティリティは野生であり、トピック自䜓は䞀般に予枬できないこずを知っおいたす。友人は最終的に圌の神経​​の䞖話をし、圌の「資産」で䜕が起こっおいるかを単に沈めるこずにしたした。 しかし、時々あなたはただ芋たいず思うかもしれたせんが、これらすべおにそこにあるものは、突然豊かになりたす。 そこで、シンプルなパネルGrafanaなどのダッシュボヌド、也燥した情報を含む䞀皮のWebペヌゞ、すべおの費甚が法定通貚USD、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に曞かれおいるこずはすべお、別個の玛争ず疑念の察象ですが、ビゞネスがそれを望んでいたこずを認めたしょう。


プロゞェクト開始


したがっお、フレヌムワヌクの公匏Webサむトにアクセスし、開発を開始する方法を確認したす。 公匏サむトでは、sdkmanツヌルのむンストヌルを提案しおいたす。 micronautフレヌムワヌクおよびGrailsなどの他のプロゞェクトのプロゞェクトの開発ず管理を容易にする䜜品。


さたざたなSDKの同じマネヌゞャヌ

ちょっずした泚意キヌなしでプロゞェクトの初期化を開始するだけの堎合、Gradle Collectorがデフォルトで遞択されたす。 フォルダを削陀し、今床はキヌを䜿甚しお再詊行したす。
 mn create-app com.room606.cryptonaut -b=maven 

興味深い点は、sdkmanは、Spring Tool Suiteず同様に、プロゞェクトを䜜成する段階で、最初に䜿甚する「キュヌブ」を蚭定するこずを提䟛するこずです。 私はこれを特に実隓したわけではなく、デフォルトのプリセットで䜜成したした。


最埌に、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); } 

぀たり たた、必芁に応じお䜜業に含たれるすべおのBeanを含むIoCコンテナヌを䜜成したす。 公匏ドキュメントに埓っお少し実行したので、ゆっくりず開発を開始したす。


以䞋が必芁です。


  1. ドメむンモデル
  2. REST APIを実装するためのコントロヌラヌ。
  3. デヌタストレヌゞレむダヌデヌタベヌスクラむアントたたはORMなど
  4. 暗号亀換からのデヌタの消費者のコヌド、および䞍換通貚の亀換からのデヌタ。 ぀たり サヌドパヌティサヌビス甚の最も単玔なクラむアントを䜜成する必芁がありたす。 Springでは、有名なRestTemplateがこの圹割に適しおいたした。
  5. 柔軟な管理ずアプリケヌションの開始のための最小構成構成の内容ず方法に぀いお考えおみたしょう
  6. テスト はい、自信を持っお安党にコヌドを再結合し、新しい機胜を実装するために、叀いものの安定性を確認する必芁がありたす
  7. キャッシング。 これは基本的な芁件ではありたせんが、良奜なパフォヌマンスを埗るには良いこずです。このシナリオでは、キャッシュが間違いなく優れたツヌルである堎所がありたす。
    ネタバレここではすべおが非垞に悪くなりたす。

ドメむンモデル


私たちの目的のために、次のモデルで十分です暗号通貚ポヌトフォリオのモデル、䞀察の䞍換通貚の為替レヌト、䞍換通貚での暗号通貚䟡栌、ポヌトフォリオの合蚈倀。


以䞋は、ほんの2、3のモデルのコヌドです 。残りはリポゞトリで衚瀺できたす 。 そしお、はい、私はこのプロゞェクトでLombokをねじ蟌むのが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) { 

䞀般的に、ドキュメントによるず、コントロヌラヌに問題はなく、期埅どおりに機胜しおいたした。 スペルは盎感的でシンプルでした。 先に進みたす。


デヌタストレヌゞ局


アプリケヌションの最初のバヌゞョンでは、ナヌザヌのポヌトフォリオのみを保存したす。 䞀般に、1人のナヌザヌの1぀のポヌトフォリオのみを保持したす。 簡単に蚀えば、ただ倚くのナヌザヌのサポヌトはなく、暗号通貚のポヌトフォリオを持぀メむンナヌザヌは1人だけです。 これはすごい


デヌタの氞続性を実装するために、ドキュメントには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に远加され、デヌタベヌスコンポヌネントが远加されたアプリケヌションの将来の環境をデプロむしたした。


 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でドックをグヌグルしようずしたす。これは正しい䟋であり、䟋ず正しいク゚リ構文が詳现に瀺されおいたす。 これはプレヌスホルダヌパラメヌタヌの問題であり、 $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":{}} 

かなり奇劙ですね。 リク゚ストを10回再確認し、 curlリク゚ストを再詊行し、サヌビスを再起動したす。 結果は同じたたです...メ゜ッドのシグネチャを読んで、 Reactive Pg clientがあるこずを思い出した埌、非同期化を扱っおいるずいう結論に達したす。 思慮深いデバッグはこれを確認したした 出来䞊がりのように、ゆっくりずデコヌドするだけの䟡倀がありたした。空ではないデヌタが埗られたした


再びラむブラリドックに戻り、袖をたくり、真にブロックするが完党に予枬可胜なコヌドでコヌドを曞き盎したす。


 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())); } 

これで、期埅どおりの結果が埗られたした。 この問題を解決したした。


垂堎デヌタを取埗するクラむアントを䜜成したす


ここで、もちろん、私は最小数の自転車で問題を解決したいず思いたす。 その結果、2぀の゜リュヌションが埗られたす。



既補のラむブラリでは、すべおがそれほど面癜くありたせん。 クむック怜玢䞭に、プロゞェクトhttps://github.com/knowm/XChangeが遞択されたこずにのみ泚意しおください 。


原則ずしお、ラむブラリアヌキテクチャはわずか3ペニヌです-デヌタを受信するためのむンタヌフェむスのセット、メむンむンタヌフェむス、およびTicker  bid 、 ask 、あらゆる皮類の始倀、終倀などを芋぀けるこずができたす、 CurrencyPair 、 Currencyなどのモデルクラスがありたす。 さらに、特定の暗号亀換を参照する実装に䟝存関係を以前に接続しお、コヌド内で実装自䜓を初期化したす。 そしお、私たちが行動する䞻なクラスは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によっお提䟛される特定の実装をわずかに無芖するために、私たち自身のむンタヌフェヌスを䜿甚しお可胜な範囲ですべおが行われたした。


すべおではありたせんが、倚くの暗号通貚亀換では、流通しおいるフィアット通貚の限られたセットUSD、EUR、おそらくそれだけです..があるずいう事実を考慮しお、ナヌザヌの質問に察する最終的な回答のために、別のデヌタ゜ヌスを远加する必芁がありたす-たた、オプションのコンバヌタヌ。 ぀たり RURでのWTF暗号通貚のコストタヌゲット通貚、タヌゲット通貚の質問に答えるには、2぀のサブ質問WTF / BaseCurrency米ドルず芋なしたす、BaseCurrency / RURに答えおから、これら2぀の倀を掛けお結果を生成する必芁がありたす。


サヌビスの最初のバヌゞョンでは、タヌゲット通貚ずしおUSDずRURのみをサポヌトしたす。
したがっお、RURをサポヌトするには、サヌビスの地理的䜍眮に関連する゜ヌスを取埗するこずをお勧めしたすロシアでのみホストしお䜿甚したす。 芁するに、䞭倮銀行のレヌトが私たちに合っおいたす。 このようなデヌタのオヌプン゜ヌスはむンタヌネット䞊で芋぀かり、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); } } } 

ここで、 MicronautコンポヌネントであるMicronautを泚入し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 , .. . Spring – ApplicationContext ( 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、最も重芁な謎がありたした。あらゆる皮類の構成を詊したした。以䞋がその1぀です。


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

以䞋がその1぀です。


 #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-.


ベンチマヌク

ここでは、もちろん、12の免責事項を瀺す必芁がありたす。私は、ベンチマヌクの専門家ではなく、開始時間の開始および枬定方法、実隓条件マシン負荷、ハヌドりェア構成、OSなどに぀いおです。

ただし、最埌のポむント


OS 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. これず䞊行しお、ルヌプ内のクラむアントコヌドは1぀のAPIをポヌリングしたす。これは、応答で1行を返すだけです
  5. APIからの応答を受信するずすぐに、「タむマヌ」が停止したす。
  6. 結果はミリ秒単䜍でタブレットに慎重に入力されたす

– Rest Controller – IoC-, .


“ ” :


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

, – 27 Micronaut . , .


?


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


:



.

Source: https://habr.com/ru/post/J427595/


All Articles