Micronaut で CLI アプリケーション + Picocli

シンプルな CLI アプリを作る機会があり、ちょうど Micronaut を試してみたかったので Micronaut で作ってみました。備忘録。

Standalone Command Line Applications - Micronaut

準備

Micronaut は Scoop でインストール。

> scoop search micronaut
'main' bucket:
    micronaut (1.2.6)

> scoop install micronaut
Installing 'micronaut' (1.2.6) [64bit]
micronaut-1.2.6.zip (12.3 MB) [=================================================================================================] 100%
Checking hash of micronaut-1.2.6.zip ... ok.
Extracting micronaut-1.2.6.zip ... done.
Linking ~\scoop\apps\micronaut\current => ~\scoop\apps\micronaut\1.2.6
Creating shim for 'mn'.
'micronaut' (1.2.6) was installed successfully!

> mn --version
| Micronaut Version: 1.2.6
| JVM Version: 1.8.0_212

Scoop についてはこちら。

Scoop on Windows - kntmr-blog

今回試したサンプルのプロジェクトはこちら。

kntmr/playground/micronaut-cli-examples - GitHub

プロジェクト作成

--featuresコンポーネントを指定すると依存関係に追加してくれる。

> mn create-cli-app com.example.app --features http-client

Picocli

Micronaut では Picocli を使って CLI アプリを作る。この Picocli がとても便利。

Micronaut Picocli Configuration - Micronaut

コマンドライン引数は @Option アノテーションを付与したフィールドにバインドされる。さらに、カンマ区切りの値を配列にしてくれたり、ファイルパスを File オブジェクトにしてくれたり。

@Option(names = {"-a"}, split = ",")
String[] array = {}; // 初期値
@Option(names = {"-f"})
File file;

@Mixinコマンドライン引数を1つのオブジェクトにまとめてくれるものかと思ってたけどそうではない。ドキュメントには共通のオプションとパラメータを再利用するためにあると書いてある。

@Mixin
private Options options;
static class Options {
    @Option(names = {"-v", "--verbose"}, description = "...")
    boolean verbose;
    @Option(names = {"-a"}, split = ",")
    String[] array = {};
    @Option(names = {"-f"})
    File file;
}

IDE 上からコマンドライン引数を渡す場合は Gradle Tasks で --args="-v" で実行する。ただ、2つ目以降の引数を正しく認識してくれない...。要調査

run --args="-v -a=foo,bar" #=> Unknown command-line option '-a'.

CLI アプリ終了時に任意のステータスコードを返したい場合は Callable<Integer> を実装して call() の中でステータスコードを返す。もう少しいい書き方があるかもしれない。

@Command(name = "app")
public class AppCommand implements Callable<Integer> {
    public static void main(String[] args) throws Exception {
        int exitStatus = PicocliRunner.execute(AppCommand.class, args);
        System.exit(exitStatus);
    }
    @Override
    public Integer call() throws Exception {
        return 0;
    }
}

その他

Maven を使う場合、mvnw compile exec:execmvnw package でコケることがある。以下の通り、pom.xml を修正する。

CLI generates a non working pom.xml for cli apps (1.2.0) #2031 - GitHub

あと、先日の JJUG CCC 2019 Fall で Picocli のひとが来てたらしい。(家庭の事情で CCC には参加できず...)

remkop.github.io

JSUG勉強会 2019 その10 SpringOne Platform 2019 報告会 に行ってきた #jsug

JSUG勉強会 2019 その10 SpringOne Platform 2019 報告会 に行ってきました。簡単にまとめます。

jsug.doorkeeper.jp

SpringOne Platform 2019概要 + Resilience4j + LTした話

  • SpringOne Platform
    • Spring, Microservices, CF, k9s などのセッション
  • Resilience4j
    • OSSのサーキットブレーカー
    • マイクロサービスで障害の伝播を防ぐ
    • メンテモードの Hystrix の代わりに Resilience4j
  • 処理は関数かアノテーションで記述する
  • Prometheus でモニタリングできる
  • 状態遷移
    • CLOSE ⇒ OPEN ⇔ HALF OPEN ⇒ CLOSE
  • Spring Cloud Circuit Breaker には Actuator のエンドポイントがない

LTの資料はこちら。

Spring Initializrをハックする

  • Spring Initializr
    • Web, IDE, CLI で Spring Boot のプロジェクトを生成するツール
  • 2013年誕生
  • UI, フォルダ構成や生成するファイルなどをカスタマイズできる
  • 昔に比べるとアーキテクチャは複雑になっている
  • @ProjectGenerationConfiguration

Let's イベント駆動 on Spring Cloud Stream

www.slideshare.net

  • Spring Cloud Stream
    • イベント駆動な Microservices アーキテクチャが簡単に構築できる
    • Kafka の接続が簡単
  • Source, Sink, Processor

Spring 18年の歴史

www.slideshare.net

  • 2001年に最初のコミット
    • GitHub はなく CVS を使っていたらしい
  • 当時のエンタープライズアプリケーション開発
    • 複雑な機能を実現するために1つのモノリスに詰め込んでいた
  • Design and Development 出版
  • 議論が巻き起こる尖ったアイデア
  • Jürgenize
  • 少数精鋭チームでスタート
    • 徐々にスケールアップ
  • Spring コミュニティは常にオープン

Spring HATEOAS

  • PayPal, AWS (API Gateway) などで使われている
  • 1.0 リリース
  • レスポンスにハイパーリンク情報を持たせる
    • 自分との関連を示すことができる (Affordance)
  • Hypermedia + REST
  • HATEOAS
    • アプリケーションの状態を持つもの
  • Separate Resource / Inline Resource
    • メタデータが多い場合は Separate Resource
    • ドキュメントのリンクを返す
  • Traverson
    • リンクをネストして1発で取る?(GraphQLみたいな?)

あとで読む。

blog.tagbangers.co.jp

Pack to the future

Google スライド - オンラインでプレゼンテーションを作成/編集できる無料サービスです

  • Dockerfile を書きたくない
  • Dockerfile は煩雑になりがち
  • Docker != Dockerfile
  • CF, Heroku
    • ソースコードを push してコンテナイメージを作る
    • Buildpack: プラットフォーム非依存
  • Cloud Native Buildpack
  • ソースコード ⇒ Buildpack ⇒ Dockerfile
  • Pack CLI 組み込み Builder

Google Nest Hub で自作アプリを動かしてみる

先日、Google Nest Hub を購入しました。Google Nest Hub のようなスマートディスプレイでは Actions on Google と組み合わせて自作したアプリを動かせるようです。

aogdevs.jp

ちょうど妻から Google Nest Hub に『ねずみタイマー』が欲しいというリクエストが。

ねずみタイマー

ねずみタイマー

  • LITALICO Inc.
  • 教育
  • 無料
apps.apple.com

一応、Google Nest Hub にもタイマーはありますが、子供向けにもう少しかわいい感じのタイマーが欲しいということで、上記のチュートリアルを参考にねずみタイマーを作ってみました。基本的にチュートリアルの流れに沿って作れます。

シミュレーターで動かすとこんな感じ。3分はカップラーメン用。

f:id:knt_mr:20191112122953p:plain

タイマーが進むにつれてねずみがチーズに迫っていきます。(以下はテストのためタイマーを15秒固定にしてあります)

f:id:knt_mr:20191112123125p:plain

時間になったら終了。子供が宿題するときに使うことを考えてお疲れ様でしたのメッセージ付き。ここでタイマーをリセットできる。

f:id:knt_mr:20191112123621p:plain

一応、実機でも動かせますが、起動するときに「テスト用アプリにつないで」と言う必要がある。要調査

アプリは Firebase にホストしています。ソースコードはこちら。

GitHub - kntmr/rat-timer


以下、備忘録。

> node -v
v8.16.0
> npm -v
6.4.1
> firebase --version
7.6.1

タイマー終了

Dialogflow にタイマー終了のインテントを定義する。

タイマーは普通に JavaScriptsetInterval で実装。時間になったら interactiveCanvas#sendTextQuery で Dialogflow のインテントにリクエストを投げる。画面の更新は interactiveCanvasイベントハンドラ (onUpdate) の中に実装。

タイマーリセット

アプリの再開や終了には Follow-up インテントを使う。タイマーをリセットするためにタイマー終了のインテントに再開用の Follow-up インテントを追加。

中断

タイマーを途中で終了するための Dialogflow インテントを定義。

Default Fallback Intent

Default Fallback Intent は Dialogflow にデフォルトで定義されているインテント。どのインテントにもマッチしない場合に Default Fallback Intent が呼び出される。

で、タイマーが作動中に想定外のリクエストを受け付けると Default Fallback Intent が呼び出されてしまう。パッといいアイデアが思い付かなったのでインテントのハンドラの中で握りつぶす...。

app.intent('Default Fallback Intent', conv => {
  conv.ask(new HtmlResponse({
    url: `https://${firebaseConfig.projectId}.firebaseapp.com/`
  }));
});

一応、これでタイマーを動かしたままにできるが、次のリクエストを受け付けなくなってしまう。要調査

SonarQube on Docker with Windows 2

前回の続き

SonarQube on Docker with Windows - kntmr-blog

プロジェクト作成

Create new project で、Project key と Display name を入力して作成する。次に Token を作成する。Token を作成するときはプロジェクト名を使えばよいと思われる。

SonarScanner

ダウンロードして展開して PATH を通す。

https://docs.sonarqube.org/latest/analysis/scan/sonarscanner/

実行

解析したいプロジェクトフォルダでコマンドプロンプトを開く

> sonar-scanner -Dsonar.projectKey=<PROJECTKEY> -Dsonar.sources=src -Dsonar.java.binaries=classes -Dsonar.host.url=http://localhost:9000 -Dsonar.login=<TOKEN>

... ()

INFO: Analysis total time: 3:55.805 s
INFO: ------------------------------------------------------------------------
INFO: EXECUTION SUCCESS
INFO: ------------------------------------------------------------------------
INFO: Total time: 3:58.185s
INFO: Final Memory: 16M/60M
INFO: ------------------------------------------------------------------------

プロジェクトページの Overview を開くと解析結果が表示される。

今回はここまで。

SonarQube on Docker with Windows

SonarQube を試してみようかと。Docker イメージがあるようなので、まずは、Windows に Docker インストールするところから...。備忘録。

  • Windows 10 Enterprise 64bit
  • タスクマネージャー > パフォーマンス > CPU 仮想化: 有効
  • コントロールパネル > プログラムと機能 > Windows の機能の有効化または無効化 > Hyper-V にチェック (要再起動)

Hyper-V の設定は Docker Desktop をインストールすると勝手にやってくれるかも。ただし、再起動は必要。

Docker Desktop on Windows

Docker-docs-ja の Windows 導入ガイド には Docker Toolbox のインストール手順が書かれているが、Docker Toolbox のページに行くと「Docker Toolbox は Lagacy 向けです」と書かれている。今回は Docker Desktop をインストールする。

Docker Hub からインストーラをダウンロードしてインストール。(要アカウント作成)

Install Docker Desktop on Windows - Docker docs

2019/10/10 時点では、Docker Desktop 2.1.0.1 がインストールされる。

> docker -v
Docker version 19.03.1, build 74b1e89

SonarQube

Docker Hub の公式イメージを使う。無念にもプロキシ環境の場合は事前に Docker Desktop で設定する。

sonarqube - Docker Hub

> docker run -d --name sonarqube -p 9000:9000 sonarqube

localhost:9000 にアクセスして admin/admin でログイン。

f:id:knt_mr:20191010125023p:plain

今回はここまで。

参考)

古いバージョンのアプリケーションを Scoop で管理したい

前回の続き。

Scoop on Windows - kntmr-blog

Scoop に存在しないアプリケーションをインストールする場合は自分で Manifest を書いてインストールする。

普通にインストーラーを使ってインストールしてもいいけど、Scoop でインストールすると、管理が一元化できるし、バージョン切り替えが簡単にできるようになる。

例えば、プライベートでは最新の JDK を使っているけど、業務では 無念にも 古いバージョンの JDK を使っているとか。ということで、以降は jdk-7u45-windows-x64.exe でインストールした Java を Scoop に移行する手順。なぜこのバージョンを使っているのかはお察しいただきたい。

手順

Git リポジトリ作成する。このリポジトリbucket になる。

> mkdir sample-bucket
> cd sample-bucket
> git init

Manifest を作成する。Manifest のファイル名が scoop の中で使われるアプリケーションの名前になる。今回は legacy-jdk.json という名前で作成する。

{
  "version": "1.7.0_45",
  "architecture": {
    "64bit": {
      "url": "http://{web-server}/path/to/jdk1.7.0_45.zip", // zip ファイル
      "hash": "5c4c76711d19be11089a342181fb457f8799a19a7918745c53de87401a521748" // SHA256
    }
  },
  "env_add_path": "bin", // bin をユーザー環境変数に設定する
  "env_set": {
    "JAVA_HOME": "$dir" // JAVA_HOME をユーザー環境変数に設定する
  }
}

環境変数はデフォルトでユーザー環境変数に設定される。システム環境変数に設定する場合は --global を付ける。(未検証)

ちなみに、今回は C:\Program Files\Java\jdk1.7.0_45 下のファイル群 (展開したときにファイルが配置されるように) を zip 圧縮して Scoop でダウンロードできるところに配置しているが、もっといい方法はないだろうか...。

> git add .
> git commit -m "add legacy-jdk"

Scoop で bucket を追加してインストールする。無念にもプロキシ環境の場合は事前に設定すること。

> scoop bucket add sample-bucket .
Checking repo... ok
The sample-bucket bucket was added successfully.

> scoop update
Updating Scoop...
Updating 'main' bucket...
Updating 'sample-bucket' bucket...
 * 73e7c0e add legacy-jdk                                                4 minutes ago
Scoop was updated successfully!

> scoop install legacy-jdk
Installing 'legacy-jdk' (1.7.0_45) [64bit]
jdk1.7.0_45.zip (131.7 MB) [======================================================================================] 100%
Checking hash of jdk1.7.0_45.zip ... ok.
Extracting jdk1.7.0_45.zip ... done.
Linking ~\scoop\apps\legacy-jdk\current => ~\scoop\apps\legacy-jdk\1.7.0_45
'legacy-jdk' (1.7.0_45) was installed successfully!

参考

Scoop on Windows

備忘録。

Cygwin 環境はないので Scoop を試してみる。Scoop は Mac で言うところの Homebrew みたいなもの。

scoop.sh

PowerShell 5 以上が必要。

> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.18362.145
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.18362.145
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

実行ポリシーを変更してインストールする。

> Set-ExecutionPolicy RemoteSigned -scope CurrentUser

> Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
Initializing...
Downloading scoop...
Extracting...
Creating shim...
Downloading main bucket...
Extracting...
Adding ~\scoop\shims to your path.
'lastupdate' has been set to '2019-09-27T12:47:42.7720596+09:00'
Scoop was installed successfully!
Type 'scoop help' for instructions.

試してみる

無念にもプロキシ環境の場合は事前に設定すること。

今回は JDK をインストールしてみる。Scoop の JDKJava bucket というものに含まれる。

> scoop search jdk
Results from other known buckets...
(add them using 'scoop bucket add <name>')

'java' bucket:
    bucket/adoptopenjdk-hotspot-jre
    bucket/adoptopenjdk-hotspot
    bucket/adoptopenjdk-lts-hotspot-jre
    bucket/adoptopenjdk-lts-hotspot
    bucket/adoptopenjdk-lts-openj9-jre
    bucket/adoptopenjdk-lts-openj9
    bucket/adoptopenjdk-openj9-jre
    bucket/adoptopenjdk-openj9
    bucket/adoptopenjdk-upstream
    bucket/ojdkbuild-full
    bucket/ojdkbuild
    bucket/ojdkbuild10-full
    bucket/ojdkbuild10
    bucket/ojdkbuild11-full
    bucket/ojdkbuild11
    bucket/ojdkbuild12-full
    bucket/ojdkbuild12
    bucket/ojdkbuild8-full
    bucket/ojdkbuild8
    bucket/ojdkbuild9-full
    bucket/ojdkbuild9
    bucket/openjdk
    bucket/openjdk10
    bucket/openjdk11
    bucket/openjdk12
    bucket/openjdk13
    bucket/openjdk14
    bucket/openjdk7-unofficial
    bucket/openjdk9
    bucket/oraclejdk
    bucket/oraclejdk12

Java bucket を追加する。

> scoop bucket add java
Checking repo... ok
The java bucket was added successfully.

> scoop search jdk
'java' bucket:
    adoptopenjdk-hotspot-jre (12.0.2-10)
    adoptopenjdk-hotspot (12.0.2-10)
    adoptopenjdk-lts-hotspot-jre (11.0.4-11)
    adoptopenjdk-lts-hotspot (11.0.4-11)
    adoptopenjdk-lts-openj9-jre (11.0.4-11-0.15.1)
    adoptopenjdk-lts-openj9 (11.0.4-11-0.15.1)
    adoptopenjdk-openj9-jre (12.0.2-10-0.15.1)
    adoptopenjdk-openj9 (12.0.2-10-0.15.1)
    adoptopenjdk-upstream (11.0.3-7)
    ojdkbuild-full (12.0.1.12-1)
    ojdkbuild (12.0.1.12-1)
    ojdkbuild10-full (10.0.2-1.b13)
    ojdkbuild10 (10.0.2-1.b13)
    ojdkbuild11-full (11.0.3.7-1)
    ojdkbuild11 (11.0.3.7-1)
    ojdkbuild12-full (12.0.1.12-1)
    ojdkbuild12 (12.0.1.12-1)
    ojdkbuild8-full (1.8.0.212-1.b04)
    ojdkbuild8 (1.8.0.212-1.b04)
    ojdkbuild9-full (9.0.4-1.b11)
    ojdkbuild9 (9.0.4-1.b11)
    openjdk (12.0.2-10)
    openjdk10 (10.0.2-13)
    openjdk11 (11.0.2-9)
    openjdk12 (12.0.2-10)
    openjdk13 (13-33)
    openjdk14 (14-16-ea)
    openjdk7-unofficial (7u80-b32)
    openjdk9 (9.0.4-12)
    oraclejdk (12.0.2-10)
    oraclejdk12 (12.0.2-10)

OpenJDK をインストールする。現時点では OpenJDK 12 がインストールされて、ユーザー環境変数JAVA_HOME=~\scoop\apps\openjdk\current が設定される。

> scoop install openjdk
Installing 'openjdk' (12.0.2-10) [64bit]
openjdk-12.0.2_windows-x64_bin.zip (187.4 MB) [===============================================================] 100%
Checking hash of openjdk-12.0.2_windows-x64_bin.zip ... ok.
Extracting openjdk-12.0.2_windows-x64_bin.zip ... done.
Linking ~\scoop\apps\openjdk\current => ~\scoop\apps\openjdk\12.0.2-10
'openjdk' (12.0.2-10) was installed successfully!

> java -version
openjdk version "12.0.2" 2019-07-16
OpenJDK Runtime Environment (build 12.0.2+10)
OpenJDK 64-Bit Server VM (build 12.0.2+10, mixed mode, sharing)

OpenJDK 13 をインストールしてみる。

> scoop install openjdk13
Installing 'openjdk13' (13-33) [64bit]
openjdk-13_windows-x64_bin.zip (186.8 MB) [===================================================================] 100%
Checking hash of openjdk-13_windows-x64_bin.zip ... ok.
Extracting openjdk-13_windows-x64_bin.zip ... done.
Linking ~\scoop\apps\openjdk13\current => ~\scoop\apps\openjdk13\13-33
'openjdk13' (13-33) was installed successfully!

> java -version
openjdk version "13" 2019-09-17
OpenJDK Runtime Environment (build 13+33)
OpenJDK 64-Bit Server VM (build 13+33, mixed mode, sharing)

バージョンを切り替える。

> scoop reset openjdk@12.0.2-10 # @ でバージョンを指定する場合
Resetting openjdk (12.0.2-10).
Linking ~\scoop\apps\openjdk\current => ~\scoop\apps\openjdk\12.0.2-10

> scoop reset openjdk13 # 名前で指定する場合
Resetting openjdk13 (13-33).
Linking ~\scoop\apps\openjdk13\current => ~\scoop\apps\openjdk13\13-33

その他

仕組み的に PATH が汚れないしバージョンの切り替えが簡単でいいかもしれない。ローカルの開発環境を構築するのに便利そう。というかその目的で作られたツールっぽい。

参考)