SpringOne 2020 Day 2 視聴ログ

SpringOne 2020 のセッション動画が公開されたのを視聴して面白かったポイントをメモ。こちらは Day 2。Day 1 はこちら

タイトルの 👍 の数は面白かった度合いです。



Connect Your Functions with RSocket 👍👍

Conect Your Function with RSocket

www.slideshare.net

RSocket と Spring CLoud Function を連携する話。

RSocket の基本的な説明

Spring Cloud Function と RSocket の関係

  • 別人かってくらいやる気がなくなってるけどライブコーディング始めたら少し元気になってきた
    • spring-cloud-stream-binder-rabbit を依存ライブラリに追加するだけで RabbitMQ につながる!
    • 起動引数だけで有効化する Function Bean を制御できる --spring.cloud.function.definition=nnn
  • RSocket を使うには spring-cloud-function-rsocket を依存ライブラリに追加する
    • Function Bean は4種類のどのコミュニケーションモデルでも利用できる
    • Function Routing という考え方が登場する
      • functionRouter という組み込みの部品を使う
      • メッセージに応じて Function Bean を分配するための仕組み
        • 例: ヘッダーで判断する spring.cloud.function.routing-expression=headers.func (func というキーの値で制御する)
        • コンテンツタイプとかいろいろ制御できる

Spring Cloud Function の Function Routing の制限をどうにかする仕組み

  • Spring Cloud Gateway から分離して rsocket の incubator プロジェクトとしてやっている RSocket Routing Broker という部品の機能 (rsocket-routing-client-spring という依存ライブラリ)
  • n 対 n 同期型のブローカークラスタを形成できるみたい
    • RabbitMQ で見たやつだ

Introduction to WebMvc.fn 👍👍

Introduction to WebMvc.fn

www.slideshare.net

WebMvc.fn の基本要素を紹介。

Spring Web MVCアノテーション中心の設計をプログラミングで実現するような感じで面白い。

メソッド引数への自動的なバインディングは無くなるけど、ルーティングの自由度は高まる。

Handler Functions

  • ServerRequest から ServerResponse へ変換する役割
  • RequestEntity を ServetRequest に、ResponseEntity を ServerResponse に書き換えるだけのような感じ
    • 便利アノテーションが使えなくなるけどそれは Router Function や Filter Function で対応する
    • Validated が使えなくなるのは不便だなぁ

Router Functions

  • ServerRequest に HandlerFunction を対応付ける役割
  • アノテーション @RequestMapping(vakye=”/path”, method={”GET”}) をビルダー route().GET(“/path”, handlerFunction) に置き換えるような感じ
  • ビルダーを構成する RequestPredicate にはもっと柔軟な制御をする機能がある

Request Predicates

  • 機能
    • リクエストメソッドの判断 RequestPredicates.method(HttpMethod.GET)
    • パスの判断 RequestPredicates.path(“/path”)
    • パスの拡張子の判断 RequestPredicates.pathExtension(“json”)
    • ヘッダーの判断 RequestPredicates.accept(APPLICATION_JSON)
    • リクエストパラメーターの判断 RequestPredicates.param(“type”, “json”)
  • 特徴
    • Predicate は合成できる predicateA.and(predicateB) predicateA.or(predicateB)

Filter Functions

  • Router Function の機能の一部
  • Spring Web MVC の ExceptionHandler をビルダーの onError で置き換える感じ

Spring Security Patterns 👍👍

Spring Security Patterns

www.slideshare.net

Spring Security の始め方から簡単な使い方までの紹介

Spring Security の始め方

  • 依存ライブラリに spring-boot-starter-securityを追加するだけで basic 認証が有効になる
    • 自前のエンドポイントだけでなく組み込みのエンドポイントにも
  • Secure by Default という考え方を覚えておくこと
  • 初期ユーザーは user、初期パスワードはランダム文字列
  • デフォルトプロファイルを本番プロファイルにしておくとよい
    • プロファイルの指定を忘れたときのため
    • ローカルファイルや h2 を使うプロファイルは dev とかにしておく
  • 未知のエンドポイントへのアクセスには 404 ではなく 401 を返すほうがいい
    • エンドポイントが存在するかどうかも秘匿すべき情報
  • パスワード再発行ページでは ユーザーが存在しない エラーを伝えるべきではない
    • ユーザーが存在してたらメールを送信しておくね くらいがちょうどいい

認証情報の使い方

  • Security Context Holder の static メソッドにアクセスする
    • リクエストを処理するのは1つのスレッドに限定されている
    • 認証情報はリクエストごとに生成したスレッドローカルオブジェクトに保存している
    • スレッドローカルの情報が他のスレッドに漏れることはない
  • DI する
    • Authentication オブジェクト
    • @CurrentSecurityContext(expression=”authentication.name”) String name

例: OAuth2 のリソースサーバーを構成する

  • Basic 認証は弱いのでトークンベースの認証に切り替えたほうがいい
    • たとえば OAuth2
    • 依存ライブラリにspring-boot-starter-security-oauth2-authorization-server を追加する
    • UserDetailService の代わりに RegisteredClientRepository Bean を登録する
  • クライアントが認証・認可サーバーから取得したクレームに含まれるスコープを Authentication#authorities (認可情報)として利用できる
  • JwtGrantedAuthoritiesConverter という組み込み実装を利用すると便利
    • トークンを GrantedAuthority に変換する Converter を構成できる
  • 継承ではなくコンポジションで利用するのが Spring Security の作法なので注意
    • 段階的に機能を拡張できるのがいいところ

例: 認証・認可情報の判断をビジネスロジックから追い出す

  • 宣言的セキュリティモデル、という考え方
  • メソッドベースのセキュリティ
    • @PreAuthorize(“hasAuthority('captain')”) みたいなメソッドアノテーションを利用する
  • リクエストベースのセキュリティ
    • WebSecurityConfigureAdapter を継承した設定クラスで DSL により構成する

Automated Virtualized Testing (AVT) with Docker, Kubernetes, WireMock and Gatling 👍

Automated Virtualized Testing (AVT) with Docker, Kubernetes, WireMock and Gatling

www.slideshare.net

AVT (自動化仮想テスト?) という考え方の紹介

伝統的なテストの課題

  • 不安定な環境
  • システム間の依存性
  • テストできる期間が限られてる
  • 信頼できない結果

AVT

  • 外部環境との結合を分離してコンテナ環境でモックを対象にテストする考え方
  • Liberty Mutual では Bitbucket Server + Bamboo を利用してる
  • コンテナ環境は Docker Enterprise
  • 性能テストのクライアントも同じコンテナ環境で実行する
    • 十分に負荷をかけるのが大変そうだけどどうなんだろうな
  • kompose で docker-compose.yml をマニフェストに変換してる
    • テスト結果は S3 経由で InfluxDB に記録して Grafana ダッシュボードで確認できるようにしてる、など

Enabling Cloud Native Buildpackas for Windows Containers 👍

Enabling Cloud Native Buildpackas for Windows Containers

www.slideshare.net

Build a Windows app · Cloud Native Buildpacks

Windows Container をビルドできる CNB を作った話

  • pack-cli によるビルダーイメージの構築部分を改修するためいろいろ頑張った
  • ライフサイクル管理の実装は bash を前提にしてたので改修するのがとてもとても大変だった
  • Windows イメージのレイヤリングは Linux イメージと全然違う
    • 特別な形式の TAR が必要になった
    • C:\Windows\System32\tar.exe を利用してるのかな
  • いろいろ大変だったけど完成した
    • Tanzu Build Service でベータ版を利用できる
    • pack-cli から利用できる
  • nanoserver のコンテナイメージは 4GB くらいある

Spring Boot Omakase: A First-Paced “Chef’s Choice” Dive into Fun and Useful Topics! 👍👍

Spring Boot Omakase: A First-Paced “Chef’s Choice” Dive into Fun and Useful Topics!

www.slideshare.net

SpringOne 2020 最後のセッション。Omakase = シェフのおまかせ。コンフィギュレーションやデプロイメントのおすすめレシピを紹介する話。

全編ライブコーディングなので動画を見ないとわからないやつ。🎥

Configuration

プロパティファイル、環境変数、アプリケーション引数をいい感じに利用する話。

  • Dtoクラスに ConigurationProperties で対応するプロパティキーを束縛できる
    • ConfigurationPropertiesScan も忘れずに
    • すると IDE がプロパティキーの型をクラスのフィールド型から推測できるようになる
      • コンストラクタにConstructorBinding を指定、引数にName でフィールド名とは異なるキー名を指定できる
    • 便利
  • Bean メソッドにも ConfigurationProperties を指定できる
    • 便利

Deployment

  • Shaded jar と Spring Boot の Executable Jar の違い
    • Shaded Jar や Uber Jar は依存ライブラリを展開して単一の jar ファイルに格納する
    • Spring Boot の Executable Jar は依存ライブラリをネストするのでクラスの読み込みとかが少し速い
  • Value で DI した値の取得元は Actuator で参照できる
    • 便利

SpringOne 2020 Day 1 視聴ログ

SpringOne 2020 のセッション動画が公開されたのを視聴して面白かったポイントをメモ。こちらは Day 1。Day 2 はこちら

タイトルの 👍 の数は面白かった度合いです。



Introducing Spring Framework 5.3 👍👍👍

Introducing Spring Framework 5.3 | SpringOne | September 2–3, 2020

www.slideshare.net

Spring Framework 5.x の歴史と 5.3 の新フィーチャー、そして未来の話。

  • Spring Framework 5.3 は LTS リリース
    • 2024 年くらいまでを想定してる
  • Spring Boot 2.4 および 2.5 は 5.3 ベースになる
  • JDK 8,11,15,17 をサポートする
  • 5.x 系列は打ち止め
    • 5.4 は出ない
  • 次のメジャーバージョン 6.x でやりたいこと
    • JavaEE から JakartaEE への移行
    • GraalVM の継続的なサポート
  • Java 14 で導入された record 型が活用されそう
    • フィールドやセッターではなく、コンストラクタインジェクションを推奨してるのはたぶんこのため。(おそらく Lombok サポートは廃れていく)

Game of Streams 🐉: How to Tame and Get the Most from Your Messaging Platforms 👍

Game of Streams 🐉: How to Tame and Get the Most from Your Messaging Platforms | SpringOne | September 2–3, 2020

www.slideshare.net

GitHub - mkheck/game-of-streams-aircraft-edition: Game of Streams meta repo for imperative AND reactive source/processor/sink microservices

メッセージングによるシステム連携を Spring Cloud Stream で簡単に実現してみようという話

  • Spring Boot: Up and Running という本を O’Reilly から出版するみたい (予定では 2021-02 になってる)
    • 「XXX: Up and Runnig」ていう本は日本の「YYY 実践入門」に相当する感じ
  • Auto Configuration とプロパティの定義だけで RabbitMQ や Kafka を利用したメッセージングが簡単に実現できる様子を実演してた
    • プロパティの命名規則に従ってキューやチャンネルを作るのはお勧めできないような気もする
    • デモンストレーションにはいいんだけど

If Hemingway Wrote JavaDocs 👍

If Hemingway Wrote JavaDocs | SpringOne | September 2–3, 2020

www.slideshare.net

Spring Project のテクニカルライターの人がどんなことに気をつけてるか紹介してた。JavaDoc の話はない。

  • 読者には初心者、中堅、エキスパートがいる
    • 釣鐘上の分布をしている
    • 壁を乗り越えて中堅からエキスパートに至る初心者はごくわずか
  • 探し物をしに来ているのであって文章を読みに来ているのではない
  • 冗長な表現をやめて、できるだけ簡潔に表現すること(ヘミングウェイとかの文豪からこの辺の格言を引用してた)
    • 不必要な言葉を消す
      • Before: “The fact that Java is used to implement the web service is an implementation detail -- an important detail, but a detail nonetheless.”
      • After: “Using Java to implement the web service is an implementation detail.”
    • 現在を表す時制にする
      • Before: “In this tutorial, we will define a Web service that is created by a Human Resources department.”
      • After: “In this tutorial, we define a Web service that is created by a Human Resources department.”
    • 短い表現にする
      • “utilize” => “use”
      • “allows you to” => “lets you”
    • 主語を置いて直接的な表現にする
      • Before: “Note that, in Spring-WS, writing the WSDL by hand is not required”
      • After: “Note that, in Spring-WS, you need not write the WSDL by hand.”
  • RFC とか重要な情報への外部リンクを設けるのは大事だけど “ここ” みたいに代名詞をリンク文字列にするのはよくない
  • 文字通りすべての読者を対象に考慮すること
    • ローカルな表現を入れない
    • i.e とか e.g みたいな古いラテン語由来の省略記法は理解できない人が多いので避けること
      • 自分が翻訳するときは軒並み文章に展開してるなぁ
    • 短縮形やジョークを入れない、一貫した用語を使う
    • 文学作品を目指すのではなく、ユーザーヘルプを目指す

Functions: Implement Once, Execut Anyware ! 👍👍

Functions: Implement Once, Execute Anywhere! | SpringOne | September 2–3, 2020

www.slideshare.net

Spring Cloud Function の紹介。

  • Spring Cloud Function は Java 8 Functional Interface の上位版として使える雰囲気がある。特に関数合成とかカリー化とか
    • Functional Interface を Spring Bean として登録、利用できる
  • Spring Cloud Function は FunctionCatalog という SpringBean を提供してる
    • 文字列リテラルで Fuction Bean にアクセスできる
    • たとえば “uppercase” という Bean には catalog.lookup(“uppercase”) でアクセスできる
    • 関数合成もできる
      • funA.andThen(funB)catalog.lookup(“funA | funB”) で実現できる
  • (真偽不明) Function Bean の引数は Spring の Converter で解決してる
    • Function<String, String> という Function Bean があるとする
      • fun.apply(1374) という呼び出しは Integer Converter が作用して Integer => Stringへ変換する
      • fun.apply(“{\”name\”:\”foo\”}”) という呼び出しは JSON Converter が作用して String => Map<String, Object> へ変換する
        • メディアタイプなどで判別している
        • 独自の Converter を作れば任意の Java Object へ変換できる
  • spring-boot-starter-web を追加するだけで Function Bean のための http エンドポイントを公開できる *たとえばuppercaseという Function Bean を登録していたら、http://localhost/uppercase/xxx` みたいにアクセスできる
    • なんかすげー

Making Spring Home (customization & extensibility) 👍👍

Make Spring Home (Spring Customization and Extensibility) | SpringOne | September 2–3, 2020

www.slideshare.net

GitHub - sasiperi/alexa-spring-boot

Spring Boot で Alexa Skill を 作る話。

  • 毎回準備するのは大変なので spring-boot-starter にしたほうがい
    • AutoConfiguration を実装する方法を説明してた
    • Maven Repository に公開するときの注意点も
      • javadoc はついてるかーとか、source はついてるか―とか、署名は大丈夫かーとか
  • 開発者体験を向上するには spring-initilizr から利用できるようにしないといけない
    • spring-initilizr を fork してカスタマイズしてた
  • その場で作ってデモンストレーション
    • Spring Security を利用して OAuth2 の認可コードフローを実装して、Alexa Skill 利用者がアカウントを紐づけする
    • ローカルPC で実行したアプリを ngrok で公開し、Alexa からアクセスできるようにしてた

A Deep Dive into Spring Application Events 👍👍👍

A Deep Dive into Spring Application Events

www.slideshare.net

Spring Data の中の人が Appliation Event について紹介してる。DDD や Event Sourcing の知識があるとわかりやすいな。

基礎

  • ApplicationEventListener のコールバック呼び出しは同期的に行われる
    • @EventListener をメソッドの付けるだけでリスナーになれる
    • イベントの種類はアノテーションで指定してもいいし、メソッド引数の型で指定してもいい
    • @Async を付ければ非同期にもできる
  • Spring の抽象 ApplicationEventPublisher をビジネスロジックに DI するのは嫌だよね
  • Spring Data の抽象クラス Aggregate にはドメインイベントの考え方が実装されてる
    • registerEvent すればアプリケーションイベントを発行できる
    • Eventuate-Tram でも利用してた気がする
    • Baeldung でも詳しく説明されてる

トランザクション制御

  • EventListener はトランザクション範囲に含まれる
    • 自分ではトランザクションを開始しない
    • 途中で失敗したら後続のリスナーは呼び出さない
  • TransactionalEentListener はそれ自体がトランザクション範囲になるイベントリスナー
    • それぞれのリスナーは独立してイベントを受信する
  • 実用例
    • バッチ的なリトライロジックの実現
      • spring-domain-events-starter という個人プロジェクトを利用してるみたい (もう削除されてた)
      • 別々のログを記録する @TransactionalEventListener を用意して、それぞれ成功したら捨てる。すると次回から空振りするようになる

アーキテクチャ

  • Inventory と Orders の関係性を考えてる
    • completeOrder というメソッドにはいろいろな機能追加がありそうだし Inventory を操作するのは違うんじゃないかという疑問
      • トランザクション範囲がどんどん広がってしまう
      • テストを書くと違和感がはっきりする
  • @ModuleTest で疎結合性を検証する
  • イベントで連携する場合はユニットテストじゃなくてインテグレーションテストで検証する
  • ビジネスロジックはきれいに分離できたのでイベントストアを外部化するのは簡単だと思うよ (どうかなぁ🤔)

雑感

  • プレゼンテーション資料に書いてない詳細をひたすら喋りまくる人ばかりで、再生速度を遅くして英語字幕(自動生成)も付けてようやく追いつける感じだった。
  • 巻き舌なインド英語を正確に文字化する YouTube の自動字幕機能はすごい (たとえば purpose をパーパスじゃなくてパルパスと発音するんだよね…)

Public Cloud Provider ごとの Java ランタイム

ちょっと気になったのでリストアップした。


OCI (Oracle Cloud Infrastructure) は Oracle JDK を無償で(!)利用できる。

blogs.oracle.com


AWS (Amazon Web Service) は Amazon Corret (OpenJDK) を利用できる。

aws.amazon.com


Microsoft Azure は Azul Zule (OpenJDK) を利用できる。

azure.microsoft.com


GCP (Google Cloud Platform) は独自のランタイムを持ってないんだよなぁ…

cloud.google.com


Alibaba Cloud は Alibaba Dragonwell (OpenJDK) を利用できる。

102.alibaba.com

Java で1つのソースコードファイルに複数のクラスを記述する

実験で書いてみましたが業務で利用するといろんな人がびっくりするのでやめましょう

テストコードならありなのかなぁ

前書き的なやつ

よくライブコーディングで見かけるやつが気になったので整理しました。 Java でも 1 つのソースコードファイルに複数のクラスを定義することができます。

  • example/app/Application.java
    • 定義してるクラス
    • Application クラス
    • public static void main メソッドを定義してる
  • example/test/Utility.java
    • 定義してるクラス
    • Utility クラス
    • Item クラス ?
    • Option クラス ??
package example.app;

import example.test.Utility;

public class Application {

    public static void main(String[] args) {

        var utility = new Utility();
        System.out.println("app: " + utility.doSomething(args[0], args[1]));
    }
}
package example.test;

public class Utility {

    public static void main(String[] args) {

        var utility = new Utility();
        System.out.println("test: " + utility.doSomething(args[0], args[1]));
    }

    public Option doSomething(String name, String value) {

        return new Option(new Item(name, value));
    }
}

class Item {
    private final String name;
    private final String value;

    Item(String name, String value) {
        this.name = name;
        this.value = value;
    }

    @Override
    public String toString() {
        return "Item[name=" + this.name + ",value=" + this.value + "]";
    }
}

class Option {
    private final Item item;

    Option(Item item) {
        this.item = item;
    }

    @Override
    public String toString() {
        return "Option[item=" + this.item.toString() + "]";
    }
}

後者の Utility.java に3つもクラスが定義されてるのは、普段見かけないからすごい不思議な感じがすると思います。

個人的には Application クラスから example.test.Option を返り値とする Utility#doSomething を利用できてるのが不思議です。

理屈

パッケージに関する Java 言語仕様が関係してます。

少なくとも1つはファイル名と同じクラスやインターフェイスが存在すること、それらのクラスやインターフェイスの可視性が public であることがコンパイル可能であることの制約になっています。

結局、 Utility.java で定義している ItemOption はただのパッケージスコープになっているということです。

パッケージがファイルシステム(7.2.1)に格納されるとき,ホストシステムは,次のいずれかが真の場合は,型名及び(.java 又は .jav のような)拡張子から構成される名前でファイル内にその型を見つけられなければ,コンパイル時エラーとする制約を課すことができる。

  • 型を宣言しているパッケージの他のコンパイル単位のコードによって,その型が参照されている。
  • 型を public と宣言している(それ故,潜在的には他のパッケージ内のコードからアクセスできる)。

実践

コンパイルしてみましょう。成功するはずです。

$ ${JAVA_HOME}/bin/javac -version
javac 11.0.8
$ ${JAVA_HOME}/bin/javac -verbose -d out example/app/Application.java example/test/Utility.java
[/modules/java.transaction.xa/module-info.classを読込み中]
[/modules/jdk.internal.jvmstat/module-info.classを読込み中]
[/modules/jdk.jartool/module-info.classを読込み中]
[/modules/jdk.crypto.ec/module-info.classを読込み中]
[/modules/jdk.jshell/module-info.classを読込み中]
[/modules/java.datatransfer/module-info.classを読込み中]
[/modules/java.desktop/module-info.classを読込み中]
[/modules/jdk.naming.rmi/module-info.classを読込み中]
[/modules/jdk.jdeps/module-info.classを読込み中]
[/modules/jdk.jsobject/module-info.classを読込み中]
[/modules/jdk.jfr/module-info.classを読込み中]
[/modules/jdk.security.jgss/module-info.classを読込み中]
[/modules/java.logging/module-info.classを読込み中]
[/modules/java.smartcardio/module-info.classを読込み中]
[/modules/jdk.rmic/module-info.classを読込み中]
[/modules/java.instrument/module-info.classを読込み中]
[/modules/jdk.dynalink/module-info.classを読込み中]
[/modules/jdk.pack/module-info.classを読込み中]
[/modules/jdk.naming.dns/module-info.classを読込み中]
[/modules/java.se/module-info.classを読込み中]
[/modules/java.security.sasl/module-info.classを読込み中]
[/modules/jdk.charsets/module-info.classを読込み中]
[/modules/jdk.internal.vm.compiler.management/module-info.classを読込み中]
[/modules/jdk.internal.ed/module-info.classを読込み中]
[/modules/java.rmi/module-info.classを読込み中]
[/modules/jdk.sctp/module-info.classを読込み中]
[/modules/jdk.security.auth/module-info.classを読込み中]
[/modules/jdk.internal.vm.ci/module-info.classを読込み中]
[/modules/jdk.jlink/module-info.classを読込み中]
[/modules/java.base/module-info.classを読込み中]
[/modules/java.sql/module-info.classを読込み中]
[/modules/jdk.unsupported.desktop/module-info.classを読込み中]
[/modules/jdk.compiler/module-info.classを読込み中]
[/modules/java.net.http/module-info.classを読込み中]
[/modules/jdk.xml.dom/module-info.classを読込み中]
[/modules/jdk.scripting.nashorn.shell/module-info.classを読込み中]
[/modules/jdk.management.agent/module-info.classを読込み中]
[/modules/java.management/module-info.classを読込み中]
[/modules/jdk.management/module-info.classを読込み中]
[/modules/jdk.internal.le/module-info.classを読込み中]
[/modules/jdk.zipfs/module-info.classを読込み中]
[/modules/jdk.jstatd/module-info.classを読込み中]
[/modules/jdk.internal.vm.compiler/module-info.classを読込み中]
[/modules/java.compiler/module-info.classを読込み中]
[/modules/java.xml.crypto/module-info.classを読込み中]
[/modules/jdk.httpserver/module-info.classを読込み中]
[/modules/jdk.aot/module-info.classを読込み中]
[/modules/jdk.jcmd/module-info.classを読込み中]
[/modules/java.scripting/module-info.classを読込み中]
[/modules/jdk.localedata/module-info.classを読込み中]
[/modules/jdk.editpad/module-info.classを読込み中]
[/modules/java.prefs/module-info.classを読込み中]
[/modules/java.xml/module-info.classを読込み中]
[/modules/jdk.unsupported/module-info.classを読込み中]
[/modules/java.sql.rowset/module-info.classを読込み中]
[/modules/jdk.net/module-info.classを読込み中]
[/modules/jdk.jdi/module-info.classを読込み中]
[/modules/jdk.internal.opt/module-info.classを読込み中]
[/modules/java.naming/module-info.classを読込み中]
[/modules/jdk.crypto.cryptoki/module-info.classを読込み中]
[/modules/jdk.management.jfr/module-info.classを読込み中]
[/modules/jdk.hotspot.agent/module-info.classを読込み中]
[/modules/jdk.jdwp.agent/module-info.classを読込み中]
[/modules/java.security.jgss/module-info.classを読込み中]
[/modules/jdk.attach/module-info.classを読込み中]
[/modules/jdk.accessibility/module-info.classを読込み中]
[/modules/jdk.javadoc/module-info.classを読込み中]
[/modules/jdk.scripting.nashorn/module-info.classを読込み中]
[/modules/jdk.crypto.mscapi/module-info.classを読込み中]
[/modules/jdk.jconsole/module-info.classを読込み中]
[/modules/java.management.rmi/module-info.classを読込み中]
[ソース・ファイルの検索パス: .]
[/modules/java.base/java/lang/Object.classを読込み中]
[/modules/java.base/java/lang/String.classを読込み中]
[/modules/java.base/java/lang/Deprecated.classを読込み中]
[/modules/java.base/java/lang/Override.classを読込み中]
[/modules/java.base/java/lang/annotation/Annotation.classを読込み中]
[/modules/java.base/java/lang/annotation/Retention.classを読込み中]
[/modules/java.base/java/lang/annotation/RetentionPolicy.classを読込み中]
[/modules/java.base/java/lang/annotation/Target.classを読込み中]
[/modules/java.base/java/lang/annotation/ElementType.classを読込み中]
[example.app.Applicationを確認中]
[/modules/java.base/java/io/Serializable.classを読込み中]
[/modules/java.base/java/lang/AutoCloseable.classを読込み中]
[/modules/java.base/java/lang/System.classを読込み中]
[/modules/java.base/java/io/PrintStream.classを読込み中]
[/modules/java.base/java/lang/Appendable.classを読込み中]
[/modules/java.base/java/io/Closeable.classを読込み中]
[/modules/java.base/java/io/FilterOutputStream.classを読込み中]
[/modules/java.base/java/io/OutputStream.classを読込み中]
[/modules/java.base/java/io/Flushable.classを読込み中]
[out\example\app\Application.classを書込み完了]
[example.test.Utilityを確認中]
[out\example\test\Utility.classを書込み完了]
[example.test.Optionを確認中]
[/modules/java.base/java/lang/Byte.classを読込み中]
[/modules/java.base/java/lang/Character.classを読込み中]
[/modules/java.base/java/lang/Short.classを読込み中]
[/modules/java.base/java/lang/Long.classを読込み中]
[/modules/java.base/java/lang/Float.classを読込み中]
[/modules/java.base/java/lang/Integer.classを読込み中]
[/modules/java.base/java/lang/Double.classを読込み中]
[/modules/java.base/java/lang/Boolean.classを読込み中]
[/modules/java.base/java/lang/Void.classを読込み中]
[/modules/java.base/java/lang/invoke/StringConcatFactory.classを読込み中]
[/modules/java.base/java/lang/invoke/MethodHandles.classを読込み中]
[/modules/java.base/java/lang/invoke/MethodHandles$Lookup.classを読込み中]
[/modules/java.base/java/lang/invoke/MethodType.classを読込み中]
[/modules/java.base/java/lang/invoke/CallSite.classを読込み中]
[out\example\test\Option.classを書込み完了]
[example.test.Itemを確認中]
[out\example\test\Item.classを書込み完了]
[合計317ミリ秒]

ちゃんと実行できます。

$ ${JAVA_HOME}/bin/java -cp out example.app.Application a 1
app: Option[item=Item[name=a,value=1]]

$ ${JAVA_HOME}/bin/java -cp out example.test.Utility b 2
test: Option[item=Item[name=b,value=2]]

banner コマンド 2020

 

banner コマンドは 1990 年より前に開発されたコマンドで、今は Cedar Solutions という会社がソースコード保有しているようです。

こんな風に指定した文字列を大きな文字にしてくれるコマンドです。サーバー作業手順書で見かけたことのある人がいるかもしれません。

最近のディストリビューションでも利用できるのか調べてみました。

 
# banner tech ##### ###### #### # # # # # # # # # ##### # ###### # # # # # # # # # # # # ###### #### # #

 CentOS / RHEL

RHEL 8 の yum リポジトリには残ってませんでした。

$ docker container run --rm -it registry.access.redhat.com/ubi8/ubi:latest [root@690b60b58764 /]# cat /etc/os-release NAME="Red Hat Enterprise Linux" VERSION="8.2 (Ootpa)" ID="rhel" ID_LIKE="fedora" VERSION_ID="8.2" PLATFORM_ID="platform:el8" PRETTY_NAME="Red Hat Enterprise Linux 8.2 (Ootpa)" ANSI_COLOR="0;31" CPE_NAME="cpe:/o:redhat:enterprise_linux:8.2:GA" HOME_URL="https://www.redhat.com/" BUG_REPORT_URL="https://bugzilla.redhat.com/" REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 8" REDHAT_BUGZILLA_PRODUCT_VERSION=8.2 REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux" REDHAT_SUPPORT_PRODUCT_VERSION="8.2" [root@690b60b58764 /]# yum install -q -y epel-release Error: Unable to find a match: epel-release [root@690b60b58764 /]# yum install -q -y banner Error: Unable to find a match: banner

CentOS 8 の yum リポジトリ にも残ってません。

$ docker container run --rm -it centos:8 [root@7267ea99d5f6 /]# cat /etc/os-release NAME="CentOS Linux" VERSION="8 (Core)" ID="centos" ID_LIKE="rhel fedora" VERSION_ID="8" PLATFORM_ID="platform:el8" PRETTY_NAME="CentOS Linux 8 (Core)" ANSI_COLOR="0;31" CPE_NAME="cpe:/o:centos:centos:8" HOME_URL="https://www.centos.org/" BUG_REPORT_URL="https://bugs.centos.org/" CENTOS_MANTISBT_PROJECT="CentOS-8" CENTOS_MANTISBT_PROJECT_VERSION="8" REDHAT_SUPPORT_PRODUCT="centos" REDHAT_SUPPORT_PRODUCT_VERSION="8" [root@7267ea99d5f6 /]# yum install -q -y epel-release Failed to set locale, defaulting to C.UTF-8 warning: /var/cache/dnf/extras-cbfb2f07b0021b7e/packages/epel-release-8-8.el8.noarch.rpm: Header V3 RSA/SHA256 Signature, key ID 8483c65d: NOKEY Importing GPG key 0x8483C65D: Userid : "CentOS (CentOS Official Signing Key) <security@centos.org>" Fingerprint: 99DB 70FA E1D7 CE22 7FB6 4882 05B5 55B3 8483 C65D From : /etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial [root@7267ea99d5f6 /]# yum install -q -y banner Failed to set locale, defaulting to C.UTF-8 Error: Unable to find a match: banner
 
 かろうじて CentOS 7 用のリポジトリには残っていました。

$ docker container run --rm -it centos:7 [root@a9ea0e617cbd /]# cat /etc/os-release NAME="CentOS Linux" VERSION="7 (Core)" ID="centos" ID_LIKE="rhel fedora" VERSION_ID="7" PRETTY_NAME="CentOS Linux 7 (Core)" ANSI_COLOR="0;31" CPE_NAME="cpe:/o:centos:centos:7" HOME_URL="https://www.centos.org/" BUG_REPORT_URL="https://bugs.centos.org/" CENTOS_MANTISBT_PROJECT="CentOS-7" CENTOS_MANTISBT_PROJECT_VERSION="7" REDHAT_SUPPORT_PRODUCT="centos" REDHAT_SUPPORT_PRODUCT_VERSION="7" [root@a9ea0e617cbd /]# yum install -q -y epel-release warning: /var/cache/yum/x86_64/7/extras/packages/epel-release-7-11.noarch.rpm: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY Public key for epel-release-7-11.noarch.rpm is not installed Importing GPG key 0xF4A80EB5: Userid : "CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>" Fingerprint: 6341 ab27 53d7 8a78 a7c2 7bb1 24c6 a8a7 f4a8 0eb5 Package : centos-release-7-8.2003.0.el7.centos.x86_64 (@CentOS) From : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 [root@a9ea0e617cbd /]# yum install -q -y banner warning: /var/cache/yum/x86_64/7/epel/packages/banner-1.3.4-3.el7.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID 352c64e5: NOKEY Public key for banner-1.3.4-3.el7.x86_64.rpm is not installed Importing GPG key 0x352C64E5: Userid : "Fedora EPEL (7) <epel@fedoraproject.org>" Fingerprint: 91e9 7d7c 4a5e 96f1 7f3e 888f 6a2f aea2 352c 64e5 Package : epel-release-7-11.noarch (@extras) From : /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7 [root@a9ea0e617cbd /]# banner CentOS 7 ##### ####### # # ####### ####### ##### # # # ## # # # # # # # # # # # # # # # # ##### # # # # # # ##### # # # # # # # # # # # # # ## # # # # # ##### ####### # # # ####### ##### ##### # # # # # #

 Debian / Ubuntu

Debian 10 では main ラインに残っていますSystem-V banner clone という名称になっていますけど。

 

$ docker container run --rm -it debian:10 root@3f80cbca76a0:/# cat /etc/os-release PRETTY_NAME="Debian GNU/Linux 10 (buster)" NAME="Debian GNU/Linux" VERSION_ID="10" VERSION="10 (buster)" VERSION_CODENAME=buster ID=debian HOME_URL="https://www.debian.org/" SUPPORT_URL="https://www.debian.org/support" BUG_REPORT_URL="https://bugs.debian.org/" root@3f80cbca76a0:/# apt update -qq -y All packages are up to date. root@3f80cbca76a0:/# apt install -qq -y sysvbanner The following NEW packages will be installed: sysvbanner 0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. Need to get 6982 B of archives. After this operation, 32.8 kB of additional disk space will be used. debconf: delaying package configuration, since apt-utils is not installed Selecting previously unselected package sysvbanner. (Reading database ... 6677 files and directories currently installed.) Preparing to unpack .../sysvbanner_1%3a1.0-16_amd64.deb ... Unpacking sysvbanner (1:1.0-16) ... Setting up sysvbanner (1:1.0-16) ... root@3f80cbca76a0:/# banner Debian 10 ###### # # ###### ##### # ## # # # # # # # # # # ## # # # ##### ##### # # # # # # # # # # # # ###### # # # # # # # # # # # # ## ###### ###### ##### # # # # # # ### ## # # # # # # # # # # # # # # # # # # ##### ###
 
Ubuntu 20 でも同じ感じでした。
$ docker container run --rm -it ubuntu:20.10 root@84880acdaf84:/# cat /etc/os-release NAME="Ubuntu" VERSION="20.10 (Groovy Gorilla)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu Groovy Gorilla (development branch)" VERSION_ID="20.10" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=groovy UBUNTU_CODENAME=groovy root@84880acdaf84:/# apt update -qq -y 27 packages can be upgraded. Run 'apt list --upgradable' to see them. root@84880acdaf84:/# apt install -qq -y sysvbanner The following NEW packages will be installed: sysvbanner 0 upgraded, 1 newly installed, 0 to remove and 27 not upgraded. Need to get 7112 B of archives. After this operation, 36.9 kB of additional disk space will be used. debconf: delaying package configuration, since apt-utils is not installed Selecting previously unselected package sysvbanner. (Reading database ... 4218 files and directories currently installed.) Preparing to unpack .../sysvbanner_1%3a1.0-17fakesync1_amd64.deb ... Unpacking sysvbanner (1:1.0-17fakesync1) ... Setting up sysvbanner (1:1.0-17fakesync1) ... root@84880acdaf84:/# banner Ubuntu 20.10 # # # # ##### # # # # ##### # # # # # # # # ## # # # # # # ##### # # # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # ##### ##### #### # # # #### ##### ### # ### # # # # ## # # # # # # # # # # # ##### # # # # # # # # # # # ### # # # # # # # ### # # # ####### ### ### ##### ###

Alpine

Alpine 3.9 の apk リポジトリには存在しませんでした。残念。

$ docker container run --rm -it alpine:3.9 / # cat /etc/os-release NAME="Alpine Linux" ID=alpine VERSION_ID=3.9.6 PRETTY_NAME="Alpine Linux v3.9" HOME_URL="https://alpinelinux.org/" BUG_REPORT_URL="https://bugs.alpinelinux.org/" / # apk add -u -q banner ERROR: unsatisfiable constraints: banner (missing): required by: world[banner]

第6回 Chaos Engieering 読書会@リモートのログ

learning.oreilly.com

javaee-study.connpass.com

引き続き Discord によるオンライン開催。

次回は 10/17(土曜日)で、Part III Human Factors の 12 Experiment Selection Problem (and a Solution) から。

javaee-study.connpass.com

対象範囲

  • Part III Human Factors(第三部 ヒューマンファクター)
  • 10 Humanistic Chaos
    • 10.3 Putting the Principles into Practice
    • 10.3 Case Study 1 ~ Gaming Your Game Day
    • 10.3 Case Study 2 ~ Connecting the Dots
    • 10.3 Case Study 3 -  Changing a Basic Assumption
  • 11 People in the Loop

トピック

  • BIT VALLEY 2020 で話してきた
  • 転職したら自己紹介のための動画撮影が大変だった
  • 東京離脱を検討中
  • private で YouTube 活動し始めた
  • 新卒採用の会社紹介動画をスタジオ収録してきた(400人以上の視聴者!) *

ディスカッション

10. Humanistic Chaos

  • 弱いシグナルとか定常状態とかいうけど、info レベルのアラートは数が多すぎて重要な情報が埋もれがち

  • 他の人とまったく意見が合わなかった

    • メトリクスの解釈の仕方は難しい、という話じゃないと思うんだけどな
    • メトリクスの大小を判断する話じゃないと思うんだけど

抄訳作るのに deepL 使ってるそうだけど、文章が日本語として成立してないのでまともに読めてる人がどれだけいるんだろうか。

10.3. Putting the Principles into Practice

人間系を部分取り替え可能な系だと考えるところが常に疑問

10.3 Case Study 1 ~ Gaming Your Game Day

  • ゲームデイはやったほうがいいんだね
  • 「唯一の専門家を休暇扱いにする、あるいは、運営側にする」のは面白いやり方

10.3 Case Study 2 ~ Connecting the Dots

  • 組織マネジメントの話だった
  • 仮説の記述が甘いよね
  • 各社のローテーション話で盛り上がってた

10.3 Case Study 3 -  Changing a Basic Assumption

  • 文章構成がよく分からない。こういうことかな
    • 前提 - 組織の基本的な前提となるシステムを変える手助けをしましょう
    • 仮説 - 人々が仕事をしたくなるような場所を作ることで、最終的にはより幸せで生産性の高い従業員が生まれるのです
    • 変数 - 私の個人的な経験に基づいて、二つの例を挙げると、エンジニアが行った行動が当社の文化の大きな柱となったことに由来しています
    • 成果 - 私がこの種の活動で最も成功しているのは、"何かをやってみたい "と明確に表明しているときです。これは失敗してもいい、それは大丈夫だという期待を明確に設定することで、最終的には成功する可能性が高くなります

11. People in the Loop

参考情報

Spring Framework のアプリケーションを停止したときの振る舞い

Spring Framework で実装したアプリケーションを停止するときに何が起きているのか整理しました。

具体的な停止方法については Shutdown a Spring Boot Application(Baeldung) にまとまっているので参照してください。

要点

  • Spring Framework のアプリケーションを安全に停止する方法が知りたい
  • Spring Framework のアプリケーションに停止処理を追加する方法が知りたい
    • ContextClosedEvent に対するイベントリスナーを実装するか、@PreDestroy などで修飾したメソッドに記述する
  • コンテナ実行基盤で実行している Spring Framework のアプリケーションに停止処理を追加したい
    • 停止処理を実行できる時間には制限があるので気を付けましょう

停止のきっかけと振る舞い

フレームワークと関係ない Java HotSpot VM の話です。

Java HotSpot VM が停止するきっかけには内部要因と外部要因があります。

  • 内部要因
    • daemonize していない全てのスレッドが終了した場合
      • 例 - main スレッドと daemonize した複数のスレッドが存在するプログラムで main スレッドが終了した場合
    • System.exit() を呼び出した場合
  • 外部要因
    • OS からシグナルを受信した場合

どちらの場合でも、 Runtime#addShutdownHook で登録されて待機状態になっていたスレッド(シャットダウンフック)が起動するようになっています。 (参考 - Runtime (Java SE 11 & JDK 11))

Spring Framework の登録するシャットダウンフック

ConfigurableApplicationContext を実装した抽象基底クラスの AbstractApplicationContex がシャットダウンフックを登録します。

https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java#L990

    /**
     * Register a shutdown hook {@linkplain Thread#getName() named}
     * {@code SpringContextShutdownHook} with the JVM runtime, closing this
     * context on JVM shutdown unless it has already been closed at that time.
     * <p>Delegates to {@code doClose()} for the actual closing procedure.
     * @see Runtime#addShutdownHook
     * @see ConfigurableApplicationContext#SHUTDOWN_HOOK_THREAD_NAME
     * @see #close()
     * @see #doClose()
     */
    @Override
    public void registerShutdownHook() {
        if (this.shutdownHook == null) {
            // No shutdown hook registered yet.
            this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
                @Override
                public void run() {
                    synchronized (startupShutdownMonitor) {
                        doClose();
                    }
                }
            };
            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
        }
    }

doClose メソッドはアプリケーションコンテキストイベントの ContextClosedEvent を通知します。 (つまり、アプリケーションコードに何かしら終了処理を追加したいなら ContextClosedEvent に対するイベントリスナーを実装すればよい)

ライフサイクル管理イベントも通知する (onClose) ので、@PreDestroy などで修飾したメソッドも呼び出されるはずです。

https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java#L1050

    protected void doClose() {
        // Check whether an actual close attempt is necessary...
        if (this.active.get() && this.closed.compareAndSet(false, true)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Closing " + this);
            }

            if (!IN_NATIVE_IMAGE) {
                LiveBeansView.unregisterApplicationContext(this);
            }

            try {
                // Publish shutdown event.
                publishEvent(new ContextClosedEvent(this));
            }
            catch (Throwable ex) {
                logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
            }

            // Stop all Lifecycle beans, to avoid delays during individual destruction.
            if (this.lifecycleProcessor != null) {
                try {
                    this.lifecycleProcessor.onClose();
                }
                catch (Throwable ex) {
                    logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
                }
            }

            // Destroy all cached singletons in the context's BeanFactory.
            destroyBeans();

            // Close the state of this context itself.
            closeBeanFactory();

            // Let subclasses do some final clean-up if they wish...
            onClose();

            // Reset local application listeners to pre-refresh state.
            if (this.earlyApplicationListeners != null) {
                this.applicationListeners.clear();
                this.applicationListeners.addAll(this.earlyApplicationListeners);
            }

            // Switch to inactive.
            this.active.set(false);
        }
    }

コンテナ実行基盤ごとの停止処理と猶予時間

ここでは Docker コンテナを実行できる環境のことを コンテナ実行基 と呼んでいます。

停止操作を行うと、コンテナのメインプロセス(PID が 1 のプロセス) に TERM シグナル (SIGTERM) を通知します。 その後、所定の猶予時間を経過すると KILL シグナル (SIGKILL) を通知します。

KILL シグナルはプログラムで処理できないシグナルで、対象のプロセスは OS により停止されます。 (参考 - signal(7) - Linux manual page)

つまり、アプリケーションで終了処理を実行できるのは TERM シグナルを受信してから KILL シグナルを受信するまでの間だけです。

コンテナ実行基盤ごとに猶予時間の初期値を整理してみました。

種類 猶予時間 変更できるかどうか
Docker Daemon 10 秒 コマンド引数で指定できる [1]
Kubernetes 30 秒 マニフェストで設定できる [2]
Amazon ECS 30 秒 コンテナエージェントの環境変数で設定できる [3] [4]

今のところ設定できる猶予時間に上限値は設けられていないようです。 Too big terminationGracePeriodSeconds value leads to pod hanging forever in Terminating state #84298

あまり長い時間を設定するとコンテナの特徴である軽量な停止と開始を阻害するだけなので、仕組み自体を検討したほうがいいでしょう。