通らなかった意見にも意味はあったりするよね

何か意見を言ったけど結局通らなくて
「言っても無駄やったなー」って思ったりするけど
案外そんなことなかったりする

「こんなこと言っても結果は変わらないからやめとこ」って
何も伝えないままにしてしまったりするけど
それってもったいなかったりする

たとえ、その場で目に見える結果につながらなくても
実は、相手の心のどっかに、ぶらーんってぶらさがってたりする

後になって「そういえばこういう意見もあったなぁ」って思ってもらえたり
そもそも「あの人は、こういう風に思ってるんだな」って気づいてもらえたり
「今回は難しかったけど、次に機会があったらそうしようかな」って考えてくれたり

通らなかった意見にも意味はあったりするよね

できるようになってから

まずは、できるようになりたいな。効率が悪くても、時間がかかっても、まずはできるようになりたい。改善は、その次に考えたい。

 

まだできるようになる前から「どうやったらもっと上手にできる?」とか「もっと効率よくやろう!」とかって、気が早いというかなんというか。相手を見ずに、自分の期待だけで動いてる感じがする。

 

その人にできること以上のものを期待しておいて「これくらいできないと困る」って。本当に困るなら、その人の力を見極めて、ちょうどいいものを求めるか、自分の期待を達成してくれるようなスキルを持った人にお願いしたら良さそう。

 

それに、期待で引っ張ると、それができるようになる頃には、また期待はあがってて。その結果、成長はしてるんだけど、いつも「足りない」になってしまうので好きじゃない。

 

そんなのよりも、まずは「できたー!すごいー!」ってやりたい。それから「じゃあ、次はもっと上手にやろう!どこがもっと良くできる?」ってできたら「どんどん良くなっていってる!」ってなっていいなーって。思うのだった。

 

そんな風に、子どもたちを育てたい。

 

 

 

 

ドラマやマンガの主人公みたいに

(ぼーっと書いてみる)

ギリギリまで追い詰められて、もうダメかー!って思ったときに、今まで使えなかった新しい技が使えるようになって感動的な逆転勝利をおさめる。そういうの、ワクワクする。好き。

締め切りまであと1時間しかない!もう無理だー絶体絶命。ってときに、起死回生のアイデアでなんとか乗り切って、やったー!ってなるの。ドキドキする。好き。

でも、そういうのって、現実には持ってきたくない。練習のときにできてないことが本番で突然できるなんてないし。もうだめだーってときはもうだめ。

というか、そもそも、そんな状況にならないように過ごしたい。

練習を繰り返して、ほんのちょっとずつ上手になっていて、本番では練習でできたことの中で冒険をする。のがいい。

ドラマやマンガのようなシーンは、自分のことだけになら使って遊んでもいいけど、他の人たちに無理をやらせるために使うようなもんじゃないよねぇ。

今日はくら寿司のテイクアウトでもしようかなー。

GradleでJavaのバージョンを指定するときはsourceCompatibilityじゃなくてToolchainsを使うと便利

追記 2021-02-14

Toolchains はまだ IDEA ではサポートされてないみたい。IDEA を使うときはこれまでの書き方をしておく方が良さそう。

Support detecting SDKs from Gradles toolchain support https://youtrack.jetbrains.com/issue/IDEA-252328

追記ここまで ===

昨日、Gradleのことを書いたのだけど。そういえば、触ってる中でもうひとつ学んだことがあったので、今日はそれについて。今日もタイトルの通り。

bufferings.hatenablog.com

昨日も書いたけど、Gradleって変化が速い印象あるので、しばらくするとこのやり方よりも良いやり方が出てくるかもしれない。今日は、2021年2月時点のGradle 6.8.2のお話。

これまでの書き方

これまでは、Gradleでビルドに使うJavaのバージョンを指定するのには sourceCompatibilitytargetCompatibility を使ってた。実際に、昨日の最後に出てきたやつは、Gradleの公式サイトから落としてきたやつなんだけど、こうなってる:

❯ cat build-logic/commons/src/main/groovy/com.example.commons.gradle 

...

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

...

こういう設定でコンパイルしたclassファイルのバージョンをみるとJava 8のになってる:

❯ javap -v app/build/classes/java/main/toolchains/sample/App.class | grep major
  major version: 52

Toolchainsを使う

これを、Toolchainsを使うように変更する:

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
    }
}

Toolchainsを使うとどうなるの?

Toolchainsは

  1. 指定されたバージョンがないかをローカルマシーンの中で探す
  2. 見つからなかったらダウンロードする
  3. それを使ってくれる

この辺りを読んだ。

https://docs.gradle.org/6.8.2/userguide/toolchains.html

https://blog.gradle.org/java-toolchains

遊んでみよう

こういう build.gradle を用意してみた:

plugins {
    id 'application'
}

repositories {
    mavenCentral()
}

application {
    mainClass = 'toolchains.sample.App'
}

実行するJavaのバージョンを出してみようかな。App.java をこうしてみる。

package toolchains.sample;

public class App {
    public static void main(String[] args) {
        System.out.println(System.getProperty("java.version"));
        System.out.println(System.getProperty("java.home"));
    }
}

実行

❯ ./gradlew run

> Task :app:run
15.0.2
/home/bufferings/.sdkman/candidates/java/15.0.2.hs-adpt

BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed

いまAkkaで遊んでて 15.0.2 をデフォルトにしてるから、それが使われてる。

バージョンを指定してみる

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
    }
}

実行

❯ ./gradlew run

// こんな感じでダウンロードされた
<=============> 100% CONFIGURING [4s] 
> Provisioning toolchain adoptopenjdk-11-x64-linux.tar.gz > adoptopenjdk-11-x64-linux.tar.gz

> Task :app:run
11.0.10
/home/bufferings/.gradle/jdks/jdk-11.0.10+9

BUILD SUCCESSFUL in 2s
2 actionable tasks: 2 executed

ほほー。homeの.gradleの下に入るのか。便利。バージョンも上がってる↓

❯ javap -v app/build/classes/java/main/toolchains/sample/App.class | grep major
  major version: 55

既存のJDKを使う

指定したバージョンのJDKが、既にSDKMAN!でインストール済みの場合は、そっちが使われるんかな?14は入ってるから試してみる。

❯ ./gradlew run

> Task :app:run
14.0.1
/home/bufferings/.sdkman/candidates/java/14.0.1.hs-adpt

BUILD SUCCESSFUL in 2s
2 actionable tasks: 2 executed

ほほー。賢い。

ということで

Toolchainsを使うと、使いたいJDKがインストールされていない環境でも、ダウンロードしてきてビルドを実行してくれる。最初にダウンロードの時間はかかるけど。便利。

Gradleでマルチプロジェクトをやるならsubprojects{}じゃなくてプラグインシステムを使おう

Gradleでマルチプロジェクトってどうやるんだろう?って公式ドキュメントを眺めて遊んだのでメモ。タイトルの通りの話。

Gradleって変化が速い印象ある。ので、しばらくするとこのやり方も非推奨になるのかもしれない。2021年2月時点のGradle 6.8.2のお話。

❯ gradle -v

------------------------------------------------------------
Gradle 6.8.2
------------------------------------------------------------

Build time:   2021-02-05 12:53:00 UTC
Revision:     b9bd4a5c6026ac52f690eaf2829ee26563cad426

Kotlin:       1.4.20
Groovy:       2.5.12
Ant:          Apache Ant(TM) version 1.10.9 compiled on September 27 2020
JVM:          15.0.2 (AdoptOpenJDK 15.0.2+7)
OS:           Linux 5.4.0-59-generic amd64

マルチプロジェクトをやってみたい

マルチプロジェクトをやってみたいなぁと思って、じゃあGradleでやってみようーって気持ちになった。そもそもGradle自体をあんまりちゃんと理解してないんだけど、ちょうど良い機会だし面白いかなと。

ということで、Gradleでマルチプロジェクトのやり方について、適当に検索してブログとか日本語ドキュメントとかを読んでみたけどいまいちよくわかんなくて、あと情報が古そうだったので、結局公式ドキュメントを読むことにした。

https://docs.gradle.org/6.8.2/userguide/multi_project_builds.html

最初から公式ドキュメント読めよって自分にいいつつ。だって、英語だと気合入れないと、すぅーって単語が頭を通り過ぎていくんだもん日本語で先にハードルさげたいじゃんって自分にいいつつ。読んだ。

プロジェクトの構造

なるほど?とか言いながら、ぼーっと読みながら手を動かしてみた。実際にはその次のページに書いてあるこっちの構造を手元で作った。

https://docs.gradle.org/6.8.2/userguide/declaring_dependencies_between_subprojects.html

f:id:bufferings:20210206005514p:plain:w300

先に言っておくと、↑のページに書いてある情報だけだとビルドが通らないので、↓を参考にして buildSrc/build.gradle を書いたらビルドできた。

https://docs.gradle.org/6.8.2/samples/sample_convention_plugins.html

共通の設定をどこに書くか?

マルチプロジェクトだと共通の処理をどこかに定義すると思うんだけど、その定義には「プロジェクト横断設定を書くんじゃなくて、プラグインシステムを使おう」って感じのことが書いてあって「へー」って思った。

https://docs.gradle.org/6.8.2/userguide/sharing_build_logic_between_subprojects.html

プロジェクト横断設定?

結構これを紹介してるサイト多かったんだけど subprojects {}allprojects {} を使うやり方。これはこれでシンプルで分かりやすいと思う。「Gradle マルチプロジェクト」とかで検索したらたくさん出てくるからそちらを参考にしてください。

プラグインシステム?

プラグインシステムと言っても、プラグインを別でビルドしておいて使うんじゃなくて、サブプロジェクトの横に置いてしまう。んで、サブプロジェクトでそれを使う。

buildSrc というフォルダの中に共通設定を書いとくと自動的にプラグインとしてビルドされて使えるようになるみたいね。↓こんな風に書いておくと myproject.java-conventions というIDでプラグインとして利用できる。

❯ cat buildSrc/build.gradle 
plugins {
    id 'groovy-gradle-plugin'
}

❯ cat buildSrc/src/main/groovy/myproject.java-conventions.gradle 
plugins {
    id 'java'
}

group = 'me.bufferings'
version = '1.0'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation "junit:junit:4.13"
}

利用する側はこう。

❯ cat api/build.gradle 
plugins {
    id 'myproject.java-conventions'
}

dependencies {
    implementation project(':shared')
}

なるほどー。まぁ、こっちの方がキレイかな。

もっと複雑な場合

ここをざっと読んだ。

https://docs.gradle.org/6.8.2/userguide/structuring_software_products.html

ソースはここのをダウンロードして遊んだ。

https://docs.gradle.org/6.8.2/samples/sample_structuring_software_projects.html

大きな違い

大きな違いは、プラグインを複数定義できるようにしていること、と、プラットフォームを定義していること。かな。

プラグインを複数定義

プラグインを複数定義しているので、buildSrc は使わずに build-logic ってフォルダ名にしてある。

f:id:bufferings:20210206011704p:plain:w300

なので使う側も明示的にそのフォルダを includeBuild してから使ってる。

❯ cat server-application/settings.gradle 
// == Define locations for build logic ==
pluginManagement {
    repositories {
        gradlePluginPortal()
    }
}
includeBuild('../platforms')
includeBuild('../build-logic')

// == Define locations for components ==
dependencyResolutionManagement {
    repositories {
        mavenCentral()
    }
}
includeBuild('../user-feature')
includeBuild('../admin-feature')

// == Define the inner structure of this component ==
rootProject.name = 'server-application' // the component name
include('app')

プラットフォームを定義

Gradleの持つplatformという仕組みを使ってバージョンを先に決めておくことで、利用する側ではバージョンを書かなくてすむ。

プラットフォームの定義はこんな感じ

❯ cat platforms/product-platform/build.gradle 
plugins {
    id('java-platform')
}

group = 'com.example.platform'

// allow the definition of dependencies to other platforms like the Spring Boot BOM
javaPlatform.allowDependencies()

dependencies {
    api(platform('org.springframework.boot:spring-boot-dependencies:2.4.0'))

    constraints {
        api('org.apache.juneau:juneau-marshall:8.2.0')
    }
}

この例ではSpring Bootのplatformに加えて、juneau-marshall のバージョンを指定してる:

使う側はこう

❯ cat build-logic/commons/src/main/groovy/com.example.commons.gradle 
plugins {
    id('java')
    id('com.example.jacoco')
}

group = 'com.example.myproduct'

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

dependencies {
    implementation(platform('com.example.platform:product-platform'))

    testImplementation(platform('com.example.platform:test-platform'))
    testImplementation('org.junit.jupiter:junit-jupiter-api')
    testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine')
}

tasks.named("test") {
    useJUnitPlatform()
}

この中の↓の部分。

implementation(platform('com.example.platform:product-platform'))

面白かった

大きなマルチプロジェクトの場合に、プラグインをどんな風に構成するかは頭を悩ませるだろうけど、公式ドキュメントのおすすめが分かって良かったや。

今日は Akka HTTP Quickstart for Java を楽しんだ

昨日は、Hello Akka 的なやつをやった。

bufferings.hatenablog.com

ので、今日は HTTP のやつを触った。内容が分かってるわけじゃなくて、とりあえず動いたなぁ、っていう感じ。雰囲気は理解した。

Akka HTTP Quickstart for Java · Lightbend Tech Hub

3日で忘れるので、自分用メモ。

まずは動かす

また今日も Records を使おうと思ってるから、Java 15 で動くように build.gradle を少し書き換えてから実行。

ユーザーを追加して:

❯ curl -H "Content-type: application/json" -X POST -d '{"name": "MrX", "age": 31, "countryOfResidence": "Canada"}' http://localhost:8080/users       
{"description":"User MrX created."}

❯ curl -H "Content-type: application/json" -X POST -d '{"name": "Anonymous", "age": 55, "countryOfResidence": "Iceland"}' http://localhost:8080/users
{"description":"User Anonymous created."}

❯ curl -H "Content-type: application/json" -X POST -d '{"name": "Bill", "age": 67, "countryOfResidence": "USA"}' http://localhost:8080/users
{"description":"User Bill created."}

追加されたことを確認:

❯ curl -s http://localhost:8080/users                                                                                                  
{"users":[{"age":31,"name":"MrX","countryOfResidence":"Canada"},{"age":55,"name":"Anonymous","countryOfResidence":"Iceland"},{"age":67,"name":"Bill","countryOfResidence":"USA"}]}

OK。3人登録されてるなー。面白いなー。

じゃ、個別で取得してみよう:

❯ curl -s http://localhost:8080/users/Bill       
{"empty":false,"present":true}

お。思ってたんと違う。ソースを見てみたら、Optional のフィールドだからだな:

  public final static class GetUserResponse {
    public final Optional<User> maybeUser;
    public GetUserResponse(Optional<User> maybeUser) {
      this.maybeUser = maybeUser;
    }
  }

ふーん。それはとりあえずおいといて、DELETEもチェックしといた。これはOK。

❯ curl -X DELETE http://localhost:8080/users/Bill
{"description":"User Bill deleted."}

❯ curl -s http://localhost:8080/users            
{"users":[{"age":55,"name":"Anonymous","countryOfResidence":"Iceland"},{"age":31,"name":"MrX","countryOfResidence":"Canada"}]}

Optional を扱えるように

さて。Optional を扱えるようにしようかな。そもそも Optional をフィールドに持ってそれを返すのってありなのかなぁ?とは思うけど、Quickstart だからそのまま進めよう。

Jackson の ObjectMapper に設定を追加すれば良いんだよなぁ。と思いつつ Akka のそれっぽいドキュメントを読んでみる。

Serialization with Jackson • Akka Documentation

ん? Jdk8Module はデフォルトで有効っぽい?

The following Jackson modules are enabled by default:

akka.serialization.jackson {

  # The Jackson JSON serializer will register these modules.
  jackson-modules += "akka.serialization.jackson.AkkaJacksonModule"
  # AkkaTypedJacksonModule optionally included if akka-actor-typed is in classpath
  jackson-modules += "akka.serialization.jackson.AkkaTypedJacksonModule"
  # AkkaStreamsModule optionally included if akka-streams is in classpath
  jackson-modules += "akka.serialization.jackson.AkkaStreamJacksonModule"
  jackson-modules += "com.fasterxml.jackson.module.paramnames.ParameterNamesModule"
  jackson-modules += "com.fasterxml.jackson.datatype.jdk8.Jdk8Module"
  jackson-modules += "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule"
  jackson-modules += "com.fasterxml.jackson.module.scala.DefaultScalaModule"
}

でも、実際は効いてないわけだから・・・。ふーむ。コード見てみるか。

この Quickstart で使ってる ObjectMapper はこれだな?

akka-http/Jackson.java at master · akka/akka-http · GitHub

  private static final ObjectMapper defaultObjectMapper =
    new ObjectMapper().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);

設定が入ってるようには見えないな。

ということは、Akka HTTP と akka-serialization-jackson は関係ないってことか?と思ってうろうろしたら見つけた:

akka-http-jackson and akka-serialization-jackson shall share the same jackson-json ObjectMapper. · Issue #3168 · akka/akka-http · GitHub

なるほど。akka-serialization-jackson には設定が色々入ってるけど、Akka HTTP はそれとは関係ないってことか。

akka-serialization-jackson の ObjectMapper を使う

なら、Akka HTTP デフォルトの ObjectMapper を使うのをやめて、Jdk8Module を設定したものから Marshaller を作れば良さそう。

  // Quickstart ではこっちを使ってる
  public static <T> Marshaller<T, RequestEntity> marshaller() {
    return marshaller(defaultObjectMapper);
  }

  // こっちを使って設定済みの ObjectMapper を渡せば良さそう
  public static <T> Marshaller<T, RequestEntity> marshaller(ObjectMapper mapper) {
    return Marshaller.wrapEntity(
      u -> toJSON(mapper, u),
      Marshaller.stringToEntity(),
      MediaTypes.APPLICATION_JSON
    );
  }

自前で用意するの面倒くさいから、さっきの akka-serialization-jackson から ObjectMapper を取ってこれないかなぁ?と思ったら取れた。でも、これが正しいやり方なのかどうかはよく分かってない。

    if (!(system.classicSystem() instanceof ExtendedActorSystem extendedActorSystem)) {
      throw new IllegalArgumentException("Failed to get object mapper.");
    }
    objectMapper = new JacksonObjectMapperProvider(extendedActorSystem).getOrCreate("akka-http", Optional.empty());

使ってる system が akka-actor-typed のもので、キャストできないって怒られたからしばらく悩んで、ぼーっと ActorSystem のメソッドを眺めてたら classicSystem() ってメソッドがあって、これでキャストできた。

ところで、折角なので instanceof のパターンマッチング使ってみた。Records 同様 Java 15 で Second Preview みたいで、Java 16 で正式に導入されるのかなぁ。

JEP 394: Pattern Matching for instanceof

こういうときは if の後ろのブロックでこの変数を使えるっての面白いなー。良い。

https://docs.oracle.com/javase/jp/15/docs/specs/patterns-instanceof-jls.html#jls-6.3.2

はい動いたー。

❯ curl -s http://localhost:8080/users/Bill                                                                                                           
{"name":"Bill","age":67,"countryOfRecidence":"USA"}

Records に変えてみる

昨日に引き続き、メッセージ用のオブジェクトを Records に変更してみる。けど、HTTP のレスポンスに使ってるオブジェクトもあるから「はて?Jackson は Records に対応しているのだろうか?」と思いつつ実行。

ちなみに IntelliJ IDEA さん、よくできる子やで。

static final がつくけど要らないかなぁって思うのでそれだけ消しておいた。

からの実行!

❯ curl -H "Content-type: application/json" -X POST -d '{"name": "Bill", "age": 67, "countryOfResidence": "USA"}' http://localhost:8080/users  
The request content was malformed:
Cannot unmarshal JSON as User

お。JSON の unmarshal ができないって言われた。ふむふむ。Jackson の Records 対応を見てみる。

Jackson 2.12 Most Wanted (5/5):. Support ‘java.lang.Record’ | by @cowtowncoder | Dec, 2020 | Medium

2.12 で対応したみたいだな。Akka HTTP は 2.10 を使ってるみたいだから、2.12 を使うようにしてみるか。

    implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.1'

からの再実行:

❯ curl -H "Content-type: application/json" -X POST -d '{"name": "MrX", "age": 31, "countryOfResidence": "Canada"}' http://localhost:8080/users
{"description":"User MrX created."}

❯ curl -H "Content-type: application/json" -X POST -d '{"name": "Bill", "age": 67, "countryOfResidence": "USA"}' http://localhost:8080/users
{"description":"User Bill created."}

❯ curl -H "Content-type: application/json" -X POST -d '{"name": "Anonymous", "age": 55, "countryOfResidence": "Iceland"}' http://localhost:8080/users
{"description":"User Anonymous created."}

❯ curl -s http://localhost:8080/users                                                                                                  
{"users":[{"name":"MrX","age":31,"countryOfResidence":"Canada"},{"name":"Bill","age":67,"countryOfResidence":"USA"},{"name":"Anonymous","age":55,"countryOfResidence":"Iceland"}]}

❯ curl -s http://localhost:8080/users/Bill       
{"name":"Bill","age":67,"countryOfResidence":"USA"}

❯ curl -X DELETE http://localhost:8080/users/Bill
{"description":"User Bill deleted."}

❯ curl -s http://localhost:8080/users            
{"users":[{"name":"MrX","age":31,"countryOfResidence":"Canada"},{"name":"Anonymous","age":55,"countryOfResidence":"Iceland"}]}

動いたー。面白かったヽ(=´▽`=)ノ

Akka Java の勉強を始めた。Records 使ってみた。

Committee

うちの部署には他のグループの人たちと交流できるように「コミッティ」って制度がある。そこでは、10%くらいの時間を使って適当に同じ興味を持った人が集まって好きに勉強とかしてる。これまで僕は、Micronaut とか Kubernetes とか触ったかな。

そこで「Akka に興味があるー」って言ったら「面白そう」って言ってくれて何人かで勉強を始めてみた。これまでずっと興味はあったんだけど手を出せてなかったので嬉しい。

勉強をするってなると、家でも触りたくなったので触ることにする。

Akka Java

Akka やるなら Scala を使う人が多いのかなぁって思うけど、僕は今生では Scala は諦めてるので(←来世の自分よろしく)、Java でやる。

Akka Actors Quickstart with Java · Lightbend Tech Hub

Quick Start プロジェクトをダウンロードしてきて動かして楽しんだ。

Records

そういえば、Quick Start の中にこういう部分がある。やりとりするメッセージオブジェクト。

  public static final class Greet {
    public final String whom;
    public final ActorRef<Greeted> replyTo;

    public Greet(String whom, ActorRef<Greeted> replyTo) {
      this.whom = whom;
      this.replyTo = replyTo;
    }
  }

  public static final class Greeted {
    public final String whom;
    public final ActorRef<Greet> from;

    public Greeted(String whom, ActorRef<Greet> from) {
      this.whom = whom;
      this.from = from;
    }

// #greeter
    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      Greeted greeted = (Greeted) o;
      return Objects.equals(whom, greeted.whom) &&
              Objects.equals(from, greeted.from);
    }

    @Override
    public int hashCode() {
      return Objects.hash(whom, from);
    }

    @Override
    public String toString() {
      return "Greeted{" +
              "whom='" + whom + '\'' +
              ", from=" + from +
              '}';
    }
// #greeter
  }

これ、Records で書けそうだなーと思って書いてみたら動いた。Records は Java 15 で Second Preview になってて、Java 16 で正式に入る予定の機能。

  public record Greet (String whom, ActorRef<Greeted> replyTo) {}

  public record Greeted (String whom, ActorRef<Greet> from) {}

楽だなー。フィールドに直接アクセスしていた部分をメソッド呼び出しに書き換えたくらい。

それだけ。