MicronautãŸãã¯Darlingãè©ŠããŠããã¬ãŒã ã¯ãŒã¯ãæžãããŸãã
ãã€ã¯ãããŒããã¬ãŒã ã¯ãŒã¯ã«ã€ããŠã¯ããã¥ãŒã¹ã¬ã¿ãŒãã€ãžã§ã¹ããå£éèŠãããšãã§ããŸããã 圌ã¯èªåãã©ããªç£ãªã®ããšæããŸããã ãã¬ãŒã ã¯ãŒã¯ã¯ãSpringã«å¿
èŠãªãã¹ãŠã®ããŒã«ãè©°ã蟌ãŸããŠããã®ãšã¯å¯Ÿç
§çã§ãã

éçºè
ããã€ã¯ããµãŒãã¹ã§ãã€ã¯ãããŒãã䜿çšããæ¹æ³ã話ãã説æããã«ã³ãã¡ã¬ã³ã¹ãäºæ³ããŠãå°ãªããšãäžåºŠã¯æºåãããŠãç¹å®ã®åé¡ãšè³ªåã®ã»ããã§å°ãªããšãããã€ãã®ã³ã³ããã¹ããé ã«å
¥ããããšã«ããŸããã ããã°å®¿é¡ãããŸãã ç§ã¯ã2ã3ã®å€æ¹ã®ããã«å°ããªããããããžã§ã¯ããç«ã¡äžããããšã«ããŸããïŒé²è¡äžïŒã èšäºã®æåŸã«ããã¹ãŠã®ãããžã§ã¯ããœãŒã¹ã®ãªããžããªãžã®ãªã³ã¯ããããŸãã
Micronautã¯ãJavaãKotlinãGroovyã®3ã€ã®éçºèšèªããµããŒãããJVMãã¬ãŒã ã¯ãŒã¯ã§ãã GrailsãæäŸããã®ãšåãäŒç€Ÿã§ããOCIã«ãã£ãŠéçºãããŸããã CLIã¢ããªã±ãŒã·ã§ã³ãšäžé£ã®æšå¥šã©ã€ãã©ãªïŒããŸããŸãªãªã¢ã¯ãã£ãHTTPããã³ããŒã¿ããŒã¹ã¯ã©ã€ã¢ã³ãïŒã®åœ¢ã§ãã¥ãŒãã³ã°ãããŠããŸã
Springã®ã¢ã€ãã¢ãå®è£
ããã³ç¹°ãè¿ãDIãããããã®ãããã®å€ããè¿œå ããŸã-éåææ§ãAWS Lambdaã®ãµããŒããã¯ã©ã€ã¢ã³ãåŽã®è² è·åæ£ã
ãµãŒãã¹ã®ã¢ã€ãã¢ïŒäžåºŠã«6人ã®ããŸããŸãªæå·é貚ã§è³Œå
¥ããæãè
ã®å人ã§ãå«æµžãããäŒæãšå¬ã®ãžã£ã±ããã®é ãå Žæã«æè³ããŸãã ãã®ãã¹ãŠã®æå·é貚ç©è³ªã®ãã©ãã£ãªãã£ã¯éçã§ããããããã¯èªäœã¯äžè¬ã«äºæž¬ã§ããªãããšãç¥ã£ãŠããŸããå人ã¯æçµçã«åœŒã®ç¥çµââã®äžè©±ããã圌ã®ãè³ç£ãã§äœãèµ·ãã£ãŠããããåã«æ²ããããšã«ããŸããã ããããæã
ããªãã¯ãŸã èŠãããšæããããããŸããããããããã¹ãŠã«ããã«ãããã®ã¯ãçªç¶è±ãã«ãªããŸãã ããã§ãã·ã³ãã«ãªããã«ïŒGrafanaãªã©ã®ããã·ã¥ããŒãïŒã也ç¥ããæ
å ±ãå«ãäžçš®ã®WebããŒãžããã¹ãŠã®è²»çšãæ³å®é貚ïŒUSDãRURïŒã§ãããã«ãªãããšããã¢ã€ãã¢ãæµ®äžããŸããã
å
責äºé
- ç¬èªã®ãœãªã¥ãŒã·ã§ã³ãäœæãããšããå®çŸå¯èœæ§ã¯ãã®ãŸãŸã«ããŠãããŸããHelloWorldãããé£ãããã®ã§æ°ãããã¬ãŒã ã¯ãŒã¯ãè©Šãã ãã§ãã
- èšç®ã¢ã«ãŽãªãºã ãäºæ³ããããšã©ãŒããšã©ãŒãªã© ïŒå°ãªããšã補åã®æåã®ãã§ãŒãºã§ã¯ïŒãæ
å ±ãæœåºããããã®æå·äº€æã®éžæã®æå¹æ§ãå人ã®ãæè³ãæå·ããŒããã©ãªãªãåé¡å€ã§ãããè°è«ãäœããã®æ·±ãåæã®å¯Ÿè±¡ã§ã¯ãããŸããã
ãã®ãããèŠä»¶ã®å°ããªã»ããïŒ
- WebãµãŒãã¹ïŒhttpãä»ããå€éšããã®ã¢ã¯ã»ã¹ïŒ
- æå·é貚ããŒããã©ãªãªã®åèšå€ã®æŠèŠãå«ããã©ãŠã¶ãŒã§ããŒãžã衚瀺ãã
- ããŒããã©ãªãªãæ§æããæ©èœïŒããŒããã©ãªãªæ§é ãããŒãããã³ã¢ã³ããŒãããããã®JSON圢åŒãéžæããŸãïŒã ããŒããã©ãªãªãæŽæ°ããŠããŒãããããã®ç¹å®ã®REST APIãã€ãŸã 2 APIïŒä¿å/æŽæ°çš-POSTãã¢ã³ããŒãçš-GETã ããŒããã©ãªãªæ§é ã¯åºæ¬çã«ã·ã³ãã«ãªã¿ã€ãã®ãã¬ãŒãã§ã
BTC â 0.00005 . XEM â 4.5 . ...
- æå·é貚亀æãšé貚亀æãœãŒã¹ããããŒã¿ãååŸããŸãïŒäžæé貚çšïŒ
- ããŒããã©ãªãªã®åèšå€ãèšç®ããããã®ã«ãŒã«ïŒ

ãã¡ããããã©ã°ã©ã5ã«æžãããŠããããšã¯ãã¹ãŠãå¥åã®çŽäºãšç念ã®å¯Ÿè±¡ã§ãããããžãã¹ããããæãã§ããããšãèªããŸãããã
ãããžã§ã¯ãéå§
ãããã£ãŠããã¬ãŒã ã¯ãŒã¯ã®å
¬åŒWebãµã€ãã«ã¢ã¯ã»ã¹ããéçºãéå§ããæ¹æ³ã確èªããŸãã å
¬åŒãµã€ãã§ã¯ãsdkmanããŒã«ã®ã€ã³ã¹ããŒã«ãææ¡ããŠããŸãã micronautãã¬ãŒã ã¯ãŒã¯ïŒããã³Grailsãªã©ã®ä»ã®ãããžã§ã¯ãïŒã®ãããžã§ã¯ãã®éçºãšç®¡çã容æã«ããäœåã

ã¡ãã£ãšãã泚æïŒããŒãªãã§ãããžã§ã¯ãã®åæåãéå§ããã ãã®å Žåã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ã³ã³ãããŒãäœæããŸãã å
¬åŒããã¥ã¡ã³ãã«åŸã£ãŠå°ãå®è¡ããã®ã§ããã£ãããšéçºãéå§ããŸãã
以äžãå¿
èŠã§ãã
- ãã¡ã€ã³ã¢ãã«
- REST APIãå®è£
ããããã®ã³ã³ãããŒã©ãŒã
- ããŒã¿ã¹ãã¬ãŒãžã¬ã€ã€ãŒïŒããŒã¿ããŒã¹ã¯ã©ã€ã¢ã³ããŸãã¯ORMãªã©ïŒ
- æå·äº€æããã®ããŒã¿ã®æ¶è²»è
ã®ã³ãŒããããã³äžæé貚ã®äº€æããã®ããŒã¿ã ã€ãŸã ãµãŒãããŒãã£ãµãŒãã¹çšã®æãåçŽãªã¯ã©ã€ã¢ã³ããäœæããå¿
èŠããããŸãã Springã§ã¯ãæåãªRestTemplateããã®åœ¹å²ã«é©ããŠããŸããã
- æè»ãªç®¡çãšã¢ããªã±ãŒã·ã§ã³ã®éå§ã®ããã®æå°æ§æïŒæ§æã®å
容ãšæ¹æ³ã«ã€ããŠèããŠã¿ãŸãããïŒ
- ãã¹ãïŒ ã¯ããèªä¿¡ãæã£ãŠå®å
šã«ã³ãŒããåçµåããæ°ããæ©èœãå®è£
ããããã«ãå€ããã®ã®å®å®æ§ã確èªããå¿
èŠããããŸã
- ãã£ãã·ã³ã°ã ããã¯åºæ¬çãªèŠä»¶ã§ã¯ãããŸããããè¯å¥œãªããã©ãŒãã³ã¹ãåŸãã«ã¯è¯ãããšã§ãããã®ã·ããªãªã§ã¯ããã£ãã·ã¥ãééããªãåªããããŒã«ã§ããå ŽæããããŸãã
ãã¿ãã¬ïŒããã§ã¯ãã¹ãŠãéåžžã«æªããªããŸãã
ãã¡ã€ã³ã¢ãã«
ç§ãã¡ã®ç®çã®ããã«ã次ã®ã¢ãã«ã§ååã§ãïŒæå·é貚ããŒããã©ãªãªã®ã¢ãã«ãäžå¯Ÿã®äžæé貚ã®çºæ¿ã¬ãŒããäžæé貚ã§ã®æå·éè²šäŸ¡æ ŒãããŒããã©ãªãªã®åèšå€ã
以äžã¯ãã»ãã®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 -> {
ç°å¢ãç«ã¡äžããäºåã«æ
éã«å®è£
ããã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() {
, 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ã€ã®APIãããŒãªã³ã°ããŸããããã¯ãå¿çã§1è¡ãè¿ãã ãã§ã
- APIããã®å¿çãåä¿¡ãããšããã«ããã¿ã€ããŒããåæ¢ããŸãã
- çµæã¯ããªç§åäœã§ã¿ãã¬ããã«æ
éã«å
¥åãããŸã
â Rest Controller
â IoC-, .
â â :
| Micronaut | Spring Boot |
---|
Avg.(ms) | 2708.4 | 2735.2 |
cryptonaut (ms) | 1082 | - |
, â 27 Micronaut
. , .
?
. , , , â . . Groovy-, , . SO
Spring. , , . â . Spring.
:
- Micronaut â service-discovery, AWS
- Java. Kotlin Groovy.
.