第3回 The Software Architect Elevator 読書会@リモート

learning.oreilly.com

javaee-study.connpass.com

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

今回は 9. Architecture Is Selling Options(第9章 アーキテクチャはオプション取引) から。

javaee-study.connpass.com

次回は4/17(土)、14. If Software Eats the World, Better Use Version Control! から。


参加者トピック

  • 在宅勤務に最適化するための引っ越しを敢行
    • 家族(ペット:命名シロ)が増えました
  • 言語力の不足を痛感する事件があった
  • エヴァンゲリオンロス
  • 世界の車窓から 特別編集版」にはまってる
  • 時差の違いでとても疲労した
  • 流動性の高まりを実感した
  • Microsoft Igniteで紹介されていた Power Automate が気になる

ディスカッション

9. Architecture Is Selling Options(第9章 アーキテクチャオプション取引

10. Every System Is Perfect…

コンポーネントの内部構造や組み合わせ方に執着するのではなく、全体としての系の振る舞いに着目しましょう、的な。

11. Code Fear Not!

ソースコードを恐れる人たちと、そういう人たちが設定だけで済む製品を好むことに対する意見。

  • セールスフォース・・・・・・

  • 「著者はビジュアルプログラミングツールを提案するベンダーのデモについて2つのテストをする」の解釈

    • 「抽象化の範囲から故障が漏れていることに気づいているんだろう」とはどういう意味なのか
    • 運用上よく起きる間違いを実演して見せてツールとしてはそれをどのように扱うのか聞いてみようとしたけど、ツールの扱う概念として考慮できていないのではないか、という著者の意見
    • Failure doesn’t respect abstraction. - The Architect Elevator

12. If You Never Kill Anything, You Will Live Among Zombies

  • レガシーの置き換えというと Kubernetes が俎上に上がることがあるけど組織が対応できない

13. Never Send a Human to Do a Machine’s Job

自動化、セルフサービス化、セルフサービス化の先、みたいな話

参考情報

Spring Native(ベータ版)が公開されたので実験してみた

目標

環境

  • Debian 10.8 (WSL2)
  • Docker Comunity Edition (Client=19.03.11, Server=20.10.5)
  • GraalVM CE 21.0.0.2 (build 11.0.10+8-jvmci-21.0-b06)
  • Apache Maven 3.6.3

[OK] Getting Startedで動作を確認

gs-rest-serviceを使う。 spring-boot-starter-webだけのサンプルプロジェクトで、native-imageのためのヒントとかリソースとかは何も含まない状態。

pom.xmlにSpring Nativeの記述を追加してビルドしてみた。 途中で警告メッセージ(Could not register reflection metadata for ...)は出るけど成功してる。

$ git clone https://github.com/spring-guides/gs-rest-service
$ cd gs-rest-service/complete
$ mvn spring-boot:build-image
<snip>
<snip>
<snip>
[INFO] --- spring-boot-maven-plugin:2.4.3:build-image (default-cli) @ rest-service ---
[INFO] Building image 'com.example/rest-service:0.0.1-SNAPSHOT'
[INFO]
[INFO]  > Pulling builder image 'docker.io/paketobuildpacks/builder:tiny' 100%
[INFO]  > Pulled builder image 'paketobuildpacks/builder@sha256:870c2769a65f29c62a0ffe397ae14cc1d61c2e90ebe85f068e8e2fff775c0344'
[INFO]  > Pulling run image 'docker.io/paketobuildpacks/run:tiny-cnb' 100%
[INFO]  > Pulled run image 'paketobuildpacks/run@sha256:30e7b4b0e1de31e763c7b631161a508c0ef710ea44924f65177afad2644312b9'
[INFO]  > Executing lifecycle version v0.10.2
[INFO]  > Using build cache volume 'pack-cache-64d3936a0bf9.build'
[INFO]
[INFO]  > Running creator
[INFO]     [creator]     ===> DETECTING
[INFO]     [creator]     4 of 11 buildpacks participating
[INFO]     [creator]     paketo-buildpacks/graalvm        6.0.0
[INFO]     [creator]     paketo-buildpacks/executable-jar 5.0.0
[INFO]     [creator]     paketo-buildpacks/spring-boot    4.1.0
[INFO]     [creator]     paketo-buildpacks/native-image   4.0.0
[INFO]     [creator]     ===> ANALYZING
[INFO]     [creator]     Previous image with name "com.example/rest-service:0.0.1-SNAPSHOT" not found
[INFO]     [creator]     ===> RESTORING
[INFO]     [creator]     ===> BUILDING
[INFO]     [creator]
[INFO]     [creator]     Paketo GraalVM Buildpack 6.0.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/graalvm
[INFO]     [creator]       Build Configuration:
[INFO]     [creator]         $BP_JVM_VERSION              8.*             the Java version
[INFO]     [creator]       Launch Configuration:
[INFO]     [creator]         $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
[INFO]     [creator]         $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
[INFO]     [creator]         $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
[INFO]     [creator]         $JAVA_TOOL_OPTIONS                           the JVM launch flags
[INFO]     [creator]       GraalVM JDK 8.0.282: Contributing to layer
[INFO]     [creator]         Downloading from https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.0.0.2/graalvm-ce-java8-linux-amd64-21.0.0.2.tar.gz
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Expanding to /layers/paketo-buildpacks_graalvm/jdk
[INFO]     [creator]         Adding 129 container CA certificates to JVM truststore
[INFO]     [creator]       GraalVM Native Image Substrate VM 8.0.282
[INFO]     [creator]         Downloading from https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.0.0.2/native-image-installable-svm-java8-linux-amd64-21.0.0.2.jar
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Installing substrate VM
[INFO]     [creator]     Processing Component archive: /tmp/e2a182c1f79d8a97b537b843eb65e15ef5ebdd088ad01acd606bf26b1846612e/native-image-installable-svm-java8-linux-amd64-21.0.0.2.jar
[INFO]     [creator]     Installing new component: Native Image (org.graalvm.native-image, version 21.0.0.2)
[INFO]     [creator]         Writing env.build/JAVA_HOME.override
[INFO]     [creator]         Writing env.build/JDK_HOME.override
[INFO]     [creator]
[INFO]     [creator]     Paketo Executable JAR Buildpack 5.0.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/executable-jar
[INFO]     [creator]       Class Path: Contributing to layer
[INFO]     [creator]         Writing env.build/CLASSPATH.delim
[INFO]     [creator]         Writing env.build/CLASSPATH.prepend
[INFO]     [creator]
[INFO]     [creator]     Paketo Spring Boot Buildpack 4.1.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/spring-boot
[INFO]     [creator]       Class Path: Contributing to layer
[INFO]     [creator]         Writing env.build/CLASSPATH.append
[INFO]     [creator]         Writing env.build/CLASSPATH.delim
[INFO]     [creator]       Image labels:
[INFO]     [creator]         org.opencontainers.image.title
[INFO]     [creator]         org.opencontainers.image.version
[INFO]     [creator]         org.springframework.boot.spring-configuration-metadata.json
[INFO]     [creator]         org.springframework.boot.version
[INFO]     [creator]
[INFO]     [creator]     Paketo Native Image Buildpack 4.0.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/native-image
[INFO]     [creator]       Build Configuration:
[INFO]     [creator]         $BP_NATIVE_IMAGE                  true  enable native image build
[INFO]     [creator]         $BP_NATIVE_IMAGE_BUILD_ARGUMENTS        arguments to pass to the native-image command
[INFO]     [creator]       Native Image: Contributing to layer
[INFO]     [creator]         GraalVM Version 21.0.0.2 (Java Version 1.8.0_282-b07)
[INFO]     [creator]         Executing native-image -H:+StaticExecutableWithDynamicLibC -H:Name=/layers/paketo-buildpacks_native-image/native-image/com.example.restservice.RestServiceApplication
<snip>
<snip>
<snip>
[INFO]     [creator]     ===> EXPORTING
[INFO]     [creator]     Adding 1/1 app layer(s)
[INFO]     [creator]     Adding layer 'launcher'
[INFO]     [creator]     Adding layer 'config'
[INFO]     [creator]     Adding layer 'process-types'
[INFO]     [creator]     Adding label 'io.buildpacks.lifecycle.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.build.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.project.metadata'
[INFO]     [creator]     Adding label 'org.opencontainers.image.title'
[INFO]     [creator]     Adding label 'org.opencontainers.image.version'
[INFO]     [creator]     Adding label 'org.springframework.boot.spring-configuration-metadata.json'
[INFO]     [creator]     Adding label 'org.springframework.boot.version'
[INFO]     [creator]     Setting default process type 'web'
[INFO]     [creator]     *** Images (dfa282b4df5d):
[INFO]     [creator]           com.example/rest-service:0.0.1-SNAPSHOT
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/graalvm:jdk'
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/native-image:native-image'
[INFO]
[INFO] Successfully built image 'com.example/rest-service:0.0.1-SNAPSHOT'

作成したイメージはわりと小さい(75.3MB)。

$ docker images | grep com.example/rest-service
com.example/rest-service              0.0.1-SNAPSHOT      dfa282b4df5d        41 years ago        75.3MB

$ dive --ci com.example/rest-service:0.0.1-SNAPSHOT
  Using default CI config
Image Source: docker://com.example/rest-service:0.0.1-SNAPSHOT
Fetching image... (this can take a while for large images)
Analyzing image...
  efficiency: 99.9996 %
  wastedBytes: 481 bytes (481 B)
  userWastedPercent: 0.0008 %
Inefficient Files:
Count  Wasted Space  File Path
    2         342 B  /etc/passwd
    2         139 B  /etc/group
Results:
  PASS: highestUserWastedPercent
  SKIP: highestWastedBytes: rule disabled
  PASS: lowestEfficiency
Result:PASS [Total:3] [Passed:2] [Failed:0] [Warn:0] [Skipped:1]

当然ながら起動時間は超短い。 Started RestServiceApplication in 0.065 seconds (JVM running for 0.067)ってすごいなぁ。 普通にJVMで実行すると10秒かかるので100倍以上高速化することになる。

リクエストを受け付けるたびにdispatcherServletを初期化してるのはなんでだろう :thinking_face:

$ curl -s localhost:8080/greeting
{"id":1,"content":"Hello, World!"}
$ curl -s localhost:8080/greeting
{"id":2,"content":"Hello, World!"}

---
$ docker run --rm -i -p 8080:8080 com.example/rest-service:0.0.1-SNAPSHOT
2021-03-12 01:29:33.773  INFO 1 --- [           main] o.s.nativex.NativeListener               : This application is bootstrapped with code generated with Spring AOT

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.3)

2021-03-12 01:29:33.774  INFO 1 --- [           main] c.e.restservice.RestServiceApplication   : Starting RestServiceApplication using Java 1.8.0_282 on b573482d1411 with PID 1 (/workspace/com.example.restservice.RestServiceApplication started by cnb in /workspace)
2021-03-12 01:29:33.774  INFO 1 --- [           main] c.e.restservice.RestServiceApplication   : No active profile set, falling back to default profiles: default
2021-03-12 01:29:33.805  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
Mar 12, 2021 1:29:33 AM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-8080"]
Mar 12, 2021 1:29:33 AM org.apache.catalina.core.StandardService startInternal
INFO: Starting service [Tomcat]
Mar 12, 2021 1:29:33 AM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet engine: [Apache Tomcat/9.0.43]
Mar 12, 2021 1:29:33 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring embedded WebApplicationContext
2021-03-12 01:29:33.807  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 32 ms
2021-03-12 01:29:33.816  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
Mar 12, 2021 1:29:33 AM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]
2021-03-12 01:29:33.824  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-03-12 01:29:33.825  INFO 1 --- [           main] c.e.restservice.RestServiceApplication   : Started RestServiceApplication in 0.065 seconds (JVM running for 0.067)
Mar 12, 2021 1:31:09 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-03-12 01:31:09.977  INFO 1 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2021-03-12 01:31:09.977  INFO 1 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
Mar 12, 2021 1:31:09 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-03-12 01:31:09.977  INFO 1 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2021-03-12 01:31:09.977  INFO 1 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms

[NG] samplesの何かで動作を確認

samples/data-jpaで実験。

$ git clone https://github.com/spring-projects-experimental/spring-native
$ cd spring-native/samples/data-jpa

build-imageは途中まで順調だけど失敗する。

$ mvn spring-boot:build-image
<snip>
<snip>
<snip>
[INFO]     [creator]     WARNING: Could not register reflection metadata for org.springframework.data.querydsl.binding.QuerydslBindingsFactory. Reason: java.lang.NoClassDefFoundError: com/querydsl/core/types/EntityPath.
[INFO]     [creator]     WARNING: Could not register reflection metadata for org.springframework.transaction.ReactiveTransactionManager. Reason: java.lang.NoClassDefFoundError: reactor/core/publisher/Mono.
[INFO]     [creator]     WARNING: Could not register reflection metadata for org.springframework.boot.autoconfigure.transaction.jta.BitronixJtaConfiguration. Reason: java.lang.NoClassDefFoundError: bitronix/tm/Configuration.
[INFO]     [creator]     01:00:52.940 [ForkJoinPool-2-worker-1] DEBUG org.jboss.logging - Logging Provider: org.jboss.logging.Log4j2LoggerProvider
[INFO]     [creator]     Error: Image build request failed with exit status 137
[INFO]     [creator]     unable to invoke layer creator
[INFO]     [creator]     unable to contribute native-image layer
[INFO]     [creator]     error running build
[INFO]     [creator]     exit status 137
[INFO]     [creator]     ERROR: failed to build: exit status 1
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------

exit statusの説明はnative-imageのリファレンスに書いてない。

QuarkusのOpen Issue#1140のコメントによるとOOMkillerによる異常終了だそうな。 native-imageから実行するjavaのヒープサイズを大きくとるようにすれば解決するかもしれないらしい。

Building a native image with graalvm ce rc11 & rc12, I occasionally see the following error;

Error: Image building with exit status 137

This occurs if the system runs out of ram (including free swap pages) and the OOM Killer terminates the native image build process. The error message seen by users is not very informative. Could we capture the exit code of the native-image process, and if equal to 137, display a more helpful message to users?

$ mvn spring-boot:build-image
<snip>
<snip>
<snip>
[INFO]     [creator]     WARNING: Could not register reflection metadata for org.springframework.data.querydsl.binding.QuerydslBindingsFactory. Reason: java.lang.NoClassDefFoundError: com/querydsl/core/types/EntityPath.
[INFO]     [creator]     WARNING: Could not register reflection metadata for org.springframework.transaction.ReactiveTransactionManager. Reason: java.lang.NoClassDefFoundError: reactor/core/publisher/Mono.
[INFO]     [creator]     WARNING: Could not register reflection metadata for org.springframework.boot.autoconfigure.transaction.jta.BitronixJtaConfiguration. Reason: java.lang.NoClassDefFoundError: bitronix/tm/Configuration.
[INFO]     [creator]     01:00:52.940 [ForkJoinPool-2-worker-1] DEBUG org.jboss.logging - Logging Provider: org.jboss.logging.Log4j2LoggerProvider
[INFO]     [creator]     Error: Image build request failed with exit status 137
[INFO]     [creator]     unable to invoke layer creator
[INFO]     [creator]     unable to contribute native-image layer
[INFO]     [creator]     error running build
[INFO]     [creator]     exit status 137
[INFO]     [creator]     ERROR: failed to build: exit status 1
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------

AOTコンパイルは分散、並列化できないのでCPUとメモリの両方がボトルネックになる。 そもそも時間がかかりすぎるという問題もある。 解決するにはハイスペックマシンを用意するしかないので、頻繁にビルドを繰り返す開発プロジェクトに導入できるかは疑問。

native-imageに渡す引数はspring-boot-maven-pluginの設定にBP_NATIVE_IMAGE_BUILD_ARGUMENTSで指定する。 結局ヒープサイズ指定を32GBにしても成功しなかった。 そもそもpaketo-buildpacksコンテナを実行する環境には32GBもメモリを割り当ててないからかもしれない。

diff --git a/samples/data-jpa/pom.xml b/samples/data-jpa/pom.xml
index 4187cf63..b6535097 100644
--- a/samples/data-jpa/pom.xml
+++ b/samples/data-jpa/pom.xml
@@ -114,6 +114,14 @@
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
+                               <configuration>
+                                       <image>
+                                               <builder>paketobuildpacks/builder:tiny</builder>
+                                               <env>
+                                                       <BP_NATIVE_IMAGE_BUILD_ARGUMENTS>-J-Xmx32G</BP_NATIVE_IMAGE_BUILD_ARGUMENTS>
+                                               </env>
+                                       </image>
+                               </configuration>
                        </plugin>
                        <plugin>
                                <groupId>org.hibernate.orm.tooling</groupId>

[OK] samplesの何かで動作を確認

samples/data-jpaで実験。

ただしAWS EC2(r5.2xlarge, vCPU=8, RAM=64GiB)で実行する。 時間もリソースも金で解決できるんですよ。

準備

$ uname -a
Linux ip-10-0-0-80.ap-northeast-1.compute.internal 4.14.219-164.354.amzn2.x86_64 #1 SMP Mon Feb 22 21:18:39 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ free -h
              total        used        free      shared  buff/cache   available
Mem:            62G        504M         57G        500K        4.4G         61G
Swap:            0B          0B          0B

$ sudo yum update -y
$ sudo yum install -y git
$ sudo amazon-linux-extras install docker
$ sudo usermod -a -G docker ec2-user
$ sudo systemctl enable docker
$ sudo systemctl start docker
$ curl -s "https://get.sdkman.io" | bash
$ source ~/.sdkman/bin/sdkman-init.sh
$ sdk install java 21.0.0.2.r11-grl
$ sdk install maven install 3.6.3

実験

成功した。 メモリは9.3GB以上必要だったようだ。

$ git clone https://github.com/spring-projects-experimental/spring-native
$ cd spring-native/samples/data-jpa
$ mvn spring-boot:build-image
<snip>
<snip>
<snip>
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]     (clinit):   2,413.81 ms,  7.15 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]   (typeflow):  62,320.78 ms,  7.15 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]    (objects):  58,314.63 ms,  7.15 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]   (features):  11,451.10 ms,  7.15 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]     analysis: 140,293.14 ms,  7.15 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]     universe:   4,119.72 ms,  7.26 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]      (parse):   9,142.60 ms,  7.28 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]     (inline):  12,394.42 ms,  8.97 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]    (compile):  44,175.39 ms,  9.39 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]      compile:  71,024.26 ms,  9.39 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]        image:   8,853.52 ms,  9.30 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]        write:   1,264.68 ms,  9.30 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/app.main.SampleApplication:176]      [total]: 233,022.78 ms,  9.30 GB
[INFO]     [creator]       Removing bytecode
[INFO]     [creator]       Process types:
[INFO]     [creator]         native-image: /workspace/app.main.SampleApplication (direct)
[INFO]     [creator]         task:         /workspace/app.main.SampleApplication (direct)
[INFO]     [creator]         web:          /workspace/app.main.SampleApplication (direct)
[INFO]     [creator]     ===> EXPORTING
[INFO]     [creator]     Adding 1/1 app layer(s)
[INFO]     [creator]     Adding layer 'launcher'
[INFO]     [creator]     Adding layer 'config'
[INFO]     [creator]     Adding layer 'process-types'
[INFO]     [creator]     Adding label 'io.buildpacks.lifecycle.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.build.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.project.metadata'
[INFO]     [creator]     Adding label 'org.opencontainers.image.title'
[INFO]     [creator]     Adding label 'org.opencontainers.image.version'
[INFO]     [creator]     Adding label 'org.springframework.boot.spring-configuration-metadata.json'
[INFO]     [creator]     Adding label 'org.springframework.boot.version'
[INFO]     [creator]     Setting default process type 'web'
[INFO]     [creator]     *** Images (cc33ab8d5de7):
[INFO]     [creator]           docker.io/library/data-jpa:0.0.1-SNAPSHOT
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/graalvm:jdk'
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/native-image:native-image'
[INFO]
[INFO] Successfully built image 'docker.io/library/data-jpa:0.0.1-SNAPSHOT'

前の例より少し大きくなっている(137MB)。 そしてやはり起動は高速だ。 Started SampleApplication in 0.114 seconds (JVM running for 0.115)

$ docker images | grep data-jpa
data-jpa                   0.0.1-SNAPSHOT      cc33ab8d5de7        41 years ago        137MB

$ docker run -d --name data-jpa -p 8080:8080 data-jpa:0.0.1-SNAPSHOT
e72516c73a31d90078c0b88effd2c9d9ab9c9ca5911bc18bd513f1d65c52baeb

$ docker logs --tail=10 data-jpa
2021-03-12 04:27:53.878  INFO 1 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-03-12 04:27:53.903  WARN 1 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2021-03-12 04:27:53.911  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-03-12 04:27:53.915  INFO 1 --- [           main] o.s.w.s.f.support.RouterFunctionMapping  : Mapped (GET && /) -> app.main.SampleApplication$$Lambda$1e60cb8412269e314b131e1c351c5acea34567f4@75858ac2
Mar 12, 2021 4:27:53 AM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]
2021-03-12 04:27:53.923  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-03-12 04:27:53.924  INFO 1 --- [           main] app.main.SampleApplication               : Started SampleApplication in 0.114 seconds (JVM running for 0.115)

動作確認してみる。 最初のリクエストを受け付けたときだけdispatcherServletを初期化してる。 spring-boot-starter-webfluxspring-boot-starter-webとは初期構成が異なるんだと思う。

リクエストを受け付けるたびにdispatcherServletを初期化してるのはなんでだろう :thinking_face:

$ curl -s localhost:8080/; echo
{"value":"Hello"}
$ curl -s localhost:8080/; echo
{"value":"Hello"}

$ docker logs --tail=10 data-jpa
2021-03-12 04:34:31.249  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-03-12 04:34:31.253  INFO 1 --- [           main] o.s.w.s.f.support.RouterFunctionMapping  : Mapped (GET && /) -> app.main.SampleApplication$$Lambda$1e60cb8412269e314b131e1c351c5acea34567f4@d127876
Mar 12, 2021 4:34:31 AM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]
2021-03-12 04:34:31.262  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-03-12 04:34:31.262  INFO 1 --- [           main] app.main.SampleApplication               : Started SampleApplication in 0.115 seconds (JVM running for 0.116)
Mar 12, 2021 4:35:04 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-03-12 04:35:04.896  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2021-03-12 04:35:04.897  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms

[OK] Spring Initializrでプロジェクトを作って動作を確認

デモプロジェクトのzipアーカイブ

  • Spring Initializrで以下のコンポーネントを選択してプロジェクトを作成
    • spring-native
    • spring-boot-starter-web
    • spring-boot-starter-security
    • spring-boot-starter-data-jpa
    • h2
  • ちょっとだけ設定と実装を追加
    • ビルドするイメージ名を変更
    • Spring Securityの設定
      • Basic認証
      • ユーザー名=test,パスワード=test
    • 本(Book)というリソースのコンポーネント

引き続きEC2で試すことにする。 ちゃんと成功した。

$ curl -fsSL --output demo.zip 'https://www.dropbox.com/s/xn0nfhqjb86lxp6/demo.zip?dl=0'
$ unzip -q demo.zip
$ cd demo
$ mvn spring-boot:build-image
<snip>
<snip>
<snip>
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]     (clinit):   2,669.42 ms,  5.99 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]   (typeflow):  54,900.98 ms,  5.99 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]    (objects):  84,588.13 ms,  5.99 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]   (features):  13,431.18 ms,  5.99 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]     analysis: 162,606.05 ms,  5.99 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]     universe:   4,543.12 ms,  5.88 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]      (parse):  15,612.61 ms,  6.90 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]     (inline):  18,884.95 ms,  8.12 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]    (compile):  53,247.44 ms,  8.66 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]      compile:  95,278.63 ms,  8.71 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]        image:  13,747.40 ms,  8.72 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]        write:   1,646.05 ms,  8.72 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.demo.DemoApplication:162]      [total]: 284,598.43 ms,  8.72 GB
[INFO]     [creator]       Removing bytecode
[INFO]     [creator]       Process types:
[INFO]     [creator]         native-image: /workspace/com.example.demo.DemoApplication (direct)
[INFO]     [creator]         task:         /workspace/com.example.demo.DemoApplication (direct)
[INFO]     [creator]         web:          /workspace/com.example.demo.DemoApplication (direct)
[INFO]     [creator]     ===> EXPORTING
[INFO]     [creator]     Adding 1/1 app layer(s)
[INFO]     [creator]     Reusing layer 'launcher'
[INFO]     [creator]     Adding layer 'config'
[INFO]     [creator]     Reusing layer 'process-types'
[INFO]     [creator]     Adding label 'io.buildpacks.lifecycle.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.build.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.project.metadata'
[INFO]     [creator]     Adding label 'org.opencontainers.image.title'
[INFO]     [creator]     Adding label 'org.opencontainers.image.version'
[INFO]     [creator]     Adding label 'org.springframework.boot.spring-configuration-metadata.json'
[INFO]     [creator]     Adding label 'org.springframework.boot.version'
[INFO]     [creator]     Setting default process type 'web'
[INFO]     [creator]     *** Images (745609869101):
[INFO]     [creator]           com.example/demo:0.0.1-SNAPSHOT
[INFO]     [creator]     Reusing cache layer 'paketo-buildpacks/graalvm:jdk'
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/native-image:native-image'
[INFO]
[INFO] Successfully built image 'com.example/demo:0.0.1-SNAPSHOT'

前の例より少し大きくなっている(176MB)。 そしてやはり起動は高速だ。 Started DemoApplication in 0.248 seconds (JVM running for 0.249)

$ docker images com.example/demo
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
com.example/demo    0.0.1-SNAPSHOT      745609869101        41 years ago        176MB

$ docker run -d --name demo -p 8080:8080 com.example/demo:0.0.1-SNAPSHOT
e72516c73a31d90078c0b88effd2c9d9ab9c9ca5911bc18bd513f1d65c52baeb

$ docker logs --tail=10 demo
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
2021-03-12 07:26:02.561  INFO 1 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-03-12 07:26:02.561  INFO 1 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-03-12 07:26:02.586  WARN 1 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2021-03-12 07:26:02.689  INFO 1 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@60450f0a, org.springframework.security.web.context.SecurityContextPersistenceFilter@5494e33d, org.springframework.security.web.header.HeaderWriterFilter@47241e24, org.springframework.security.web.authentication.logout.LogoutFilter@66bb42a0, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@722091c3, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@61ff4cbb, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@1efca9f3, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@233b0d7a, org.springframework.security.web.session.SessionManagementFilter@531dc996, org.springframework.security.web.access.ExceptionTranslationFilter@4542c3e3, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@439ab877]
2021-03-12 07:26:02.697  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
Mar 12, 2021 7:26:02 AM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]
2021-03-12 07:26:02.711  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-03-12 07:26:02.712  INFO 1 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.248 seconds (JVM running for 0.249)

動作確認してみる。だいたいよさそうだ。

$ curl --user test:test -s localhost:8080/book; echo
[]

$ curl --user test:test --request POST --header 'Content-Type: application/json' -d '{"title":"test 001"}' -s localhost:8080/book; echo
{"id":1,"title":"test 001"}

$ curl --user test:test --request POST --header 'Content-Type: application/json' -d '{"title":"test 002"}' -s localhost:8080/book; echo
{"id":2,"title":"test 002"}

$ curl --user test:test -s localhost:8080/book; echo
[{"id":1,"title":"test 001"},{"id":2,"title":"test 002"}]

$ curl --user test:test --request PUT --header 'Content-Type: application/json' -d '{"title":"test 001u1"}' -s localhost:8080/book?id=1; echo
{"id":1,"title":"test 001u1"}

$ curl --user test:test --request DELETE -s localhost:8080/book?id=2; echo
2

$ curl --user test:test -s localhost:8080/book; echo
[{"id":1,"title":"test 001u1"}]

$ curl -s localhost:8080/book; echo
{"timestamp":"2021-03-12T07:28:35.328+00:00","status":401,"error":"Unauthorized","message":"","path":"/book"}

$ docker logs --tail=10 demo
Hibernate: call next value for hibernate_sequence
Hibernate: insert into book (title, id) values (?, ?)
Hibernate: call next value for hibernate_sequence
Hibernate: insert into book (title, id) values (?, ?)
Hibernate: select book0_.id as id1_0_, book0_.title as title2_0_ from book book0_
Hibernate: select book0_.id as id1_0_0_, book0_.title as title2_0_0_ from book book0_ where book0_.id=?
Hibernate: update book set title=? where id=?
Hibernate: select book0_.id as id1_0_0_, book0_.title as title2_0_0_ from book book0_ where book0_.id=?
Hibernate: delete from book where id=?
Hibernate: select book0_.id as id1_0_, book0_.title as title2_0_ from book book0_

参考リンク

テストの実践知識を学べる「Fifty Quick Ideas To Improve Your Tests」

今月から非公開グループで読書会することになった Fifty Quick Ideas To Improve Your Tests が好印象だったのでメモ。

Fifty Quick Ideas To Improve Your User Tests -- Fifty Quick Ideas Books

テストの概念や技法ではなく、利用者の役に立つソフトウェアを開発するための手段としてテストを活用するための4種類のカテゴリに編成されたノウハウを、具体的な事例と共に紹介している。

  • テストの発想を生み出す(Generating testing ideas)
    • 利用者のためになるテストとは何か、それを導き出すためにはどうすればいいか、どうすれば上手く実施できるか、みたいなことがまとまってる
  • 適切なチェックの設計(Designing good checks)
    • 未読
  • テスト容易性の改善(Improving testability)
    • 未読
  • 大規模なテストスイーツの管理(Managing large test suites)
    • 未読

冒頭の4章くらいしか読んでないけど、専門家の人からしても的を射た内容になっている。

それぞれの章は10分あれば読めるくらいの分量だし、特につながりもないので気になる章をつまみ読みできるのもよい。

出版されたのは2015年と少し古いけど、普遍的な内容を扱ってるので2021年でも十分に読む価値がある。


Fifty Quick Ideasではユーザーストーリーやふりかえりの本も紹介されており、たぶんそれらもいい感じの内容なんじゃないかと思う。


LeanPubではePUB/PDF/Kindle/オンラインビューアそれぞれの形態で販売されてる。 個人的にはPDFやオンラインビューアがあるのがとても好印象。

Javaプロジェクトのビルドツール、Gradleにするか、Mavenにするか

最近になって突然理解した。 選択するときは、柔軟性とAPIの安定性のトレードオフを考えないといけない。

総合評価

「成果物のビルド」を改善する意思と時間の有無に応じて選択するとよさそう。 消極的な姿勢なら Maven を、前向きな姿勢なら Gradle を採用すればいいだろう。

  • ビルドスクリプトに高度な柔軟性が求められるなら、まずは構成を工夫して簡素化できないか試みた方がよい
    • どうしても簡素化できないなら柔軟性に優れた Gradle を採用するしかない
  • プロジェクトの積極的な開発は1年未満で終了する(消極的な維持、保守は継続する場合)
    • APIの安定性を重視して、Maven を採用する(維持や保守のためにビルドツールをメンテナンスするのはムダである)
  • プロジェクトの積極的な開発は1年以上継続する
    • 「成果物のビルド」を開発プロセスの一部として重視しており、改善し続ける意思があるなら Gradle を採用する
    • 「成果物のビルド」よりアプリケーション本体の拡充を中心とするなら Maven を採用する

柔軟性

Gradle は Groovy/Kotlin でスクリプトを記述できるし、プラグインプロジェクトをbuildSrcで管理できるため、柔軟性がとても高い。 それに比べて Mavenプラグインモデルはややこしいので、解決したい問題の重みとプラグインを自作する大変さが割に合わないことが多いと思う。

問題の規模 Gradle Maven 結論
公開プラグインで解決できる 同等
ビルド定義ファイルを工夫すれば解決できる Gradle
プラグインを自作すれば解決できる Gradle

APIの安定性

基本的にメジャーバージョンアップでは一部のAPIの互換性が損なわれる、あるいは、排除される場合がある。 そうするとビルドが壊れてしまうので、プロジェクトとしては追加の仕事が増えてしまうことになる。

リリースノートからメジャーバージョンアップの期日を取り出して並べてみるとこうなる。 Gradle は更新間隔が短く Maven は長い。 更新間隔が長いということは、APIの安定する期間が長いということだ。

Maven

バージョン 期日 前回のリリースからの期間
1.0 2004-07-13
2.0 2005-10-20 1年3ヶ月
3.0 2010-10-08 5年
3.1.0 2013-07-15 2年9ヶ月
3.6.3 2019-11-25 6年4ヶ月

Gradle

バージョン 期日 前回のリリースからの期間
v1.0 2012-06-12
v2.0 2014-07-01 2年1ヶ月
v3.0 2016-08-15 2年1ヶ月
v4.0 2017-06-14 10ヶ月
v5.0 2018-11-26 1年5ヶ月
v6.0 2019-11-08 1年
v6.8.2 2021-02-05 1年3ヶ月

第2回 The Software Architect Elevator 読書会@リモートのログ

learning.oreilly.com

javaee-study.connpass.com

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

今回は 04. Enterprise Architect or Architect in the Enterprise? から。

javaee-study.connpass.com

次回は3/13(土)、9. Architecture Is Selling Options から。


トピック

  • トレンドに逆張りして東京へ引っ越した
    • 食洗機の稼働が遅れてる(自損)
    • popIn Aladdinをお試し中
  • ミキサーが届いたので調整中
  • Clubhouse の利用体験を改善できそうなデバイスを模索中
  • 腰痛で日常生活が崩壊した(気温の変動が激しすぎ)
  • 仕事の余暇にGoとかTypeScriptとかを試したりしてる
  • ワンダビジョンはいいぞ

ディスカッション

4. Enterprise Architect or Architect in the Enterprise?

5. An Architect Stands on Three Legs

6. Making Decisions

7. Question Everything

Part II. Architecture

8. Is This Architecture?

参考情報

第1回 The Software Architect Elevator 読書会@リモートのログ

learning.oreilly.com

learning.oreilly.com

javaee-study.connpass.com

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

今回は Chaos Engineering の補習をしてから The Software Architect Elevator に進む。

次回は 04. Enterprise Architect or Architect in the Enterprise? から。

javaee-study.connpass.com


トピック

ディスカッション

20. The Case for Security Chaos Engineering

  • GitHub - Optum/ChaoSlingr: ChaoSlingr: Introducing Security into Chaos Testing
    • 企業が主体となって公開する本業と異なる目的のツールはアーカイブされがち(主観)
  • パープルチームエクササイズってなんだろう
    • 攻撃(レッド)と防御(ブルー)だけではゲームになってしまうため、それぞれの内容を混合したチーム(パープル)を使うみたいな
  • 普通のカオスエンジニアリングの話のような
    • セキュリティの取り組みは基本的に未知への対応なので同じような話になりがち

21. Conculusion

  • 人間が重要だった
  • "Risk Management in a Dynamic Society: A Modelling Problem"
    • 安全性の向上に加え、境界を可視化すること
    • インシデントレビューやレジリエンシーを文脈として普遍の知識とすることは、「根本原因」を探したり、ルールを適用したりするより実用的かつ実践的である
  • 直感に反している事例3つ、もっと掘り下げてよかったと思う
    • 直感的には、システムに冗長性を追加すると安全性が高まることは理にかなっている。残念ながら、経験からこの直感は正しくないことが分かっている
    • 直感的には、システムから複雑さを取り除くことでシステムがより安全になることは理にかなっている。残念ながら、経験からこの直感は正しくないことが分かっている。
    • 直感的には、システムを効率的に運用することで安全性が高まることは理にかなっている。残念ながら、経験からこの直感は正しくないことが分かっている

完走した感想


第一部 アーキテクト

  • 頻出フレーズ corporate IT
  • 「ゾンビの中で生きたいと思わないように、システムを(ブレードランナー的な意味で)「解任」させることも含まれている」
    • どういう意味だろう
    • ja.wikipedia.org/wiki/ブレードランナー
      • 脱走レプリカント達を判別し見つけ出した上で「解任(抹殺)」する任務を負うのが、警察の専任捜査官「ブレードランナー」であった。
  • 頻出フレーズ First Derivative

    • どういう意味だろう
    • 一次導関数 ではないと思われる
    • 第一級の派生物(証券の派生商品的な)じゃないかなぁ
  • アーキテクトっていう職種はある?あるとしたら仕事の切り分けってどうしてる?

    • まさに「浮世離れ」した人でレビューとか相談相手とかしてる
    • プラットフォームに近い領域から全体を俯瞰する仕事をしてる
    • SIer 的な組織で自分の名刺にアーキテクトって書いてた
      • PM と具体的な作業をする人たちの成果をつなぐための仕事をしてた
        • 設計書のフォーマットを用意する
        • 設計書の記述を実装に変換するためのガイド
    • いわゆる「ソリューションアーキテクト」は「技術営業」相当
    • 公開されてる job description
  • 人と人とをつなぐ仕事と、システムとシステムをつなぐ仕事があるような気がしてきた

  • わたしたちにとって典型的なアーキテクト象は「マーティン・ファウラー

    • アナリシスパターンの分かりにくさが印象的
    • 最近は技術要素の細分化が激しいのでアーキテクトの役割も細分化していっている

01. アーキテクトエレベーター

  • なるほど。ビルのメタファーだったのか

  • どういう人がアーキテクトとして残るんだろう

    • 今の会社(組織)が好きな人は残りそう
      • ビジネスではなくビジネスを実現する仕組みが好きな人、という意味
    • 今の会社(組織)が好きな人にはドラスティックな変更が大変そう

2. Movie-Star Architects

03. Architects Live in the First Derivative

  • first derivative の訳語はどうしよう・・・

  • なんでいきなりアーキテクチャの重要な要素とか言い出してるのか

    • 突然すぎてびっくりする
    • first derivative がビルドツールチェインだという説明をしたいから、その必要性を変更頻度に求めている感じ?
    • 大筋は理解できるけど、文章構成に疑問が残る
  • 相変わらず CTO の仕事とアーキテクトの仕事の区別が分からない

    • CTOは立場、アーキテクトは役割
    • 立場には責任がついてくるけど、役割はそうではない
    • CTO=親分、アーキテクト=鉄砲玉、みたいなイメージ
  • 変更が不要なシステムは現実的に存在しないので、変更は必要だよね、その上で時間とか規模とか考えていくと変更頻度が重要なことは分かるよね、という展開なら分かる

参考情報

2020-12 にキーワード「Software Testing」でヒットした最近の本

某読書会で読む機会があると助かるやつ!

www.amazon.com

www.amazon.com

www.amazon.com

www.amazon.com

www.amazon.com

www.amazon.com

www.amazon.com

www.amazon.com

www.amazon.com