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

javaee-study.connpass.com


参加者トピック

ディスカッション

Don't get locked up into avoiding lock-in

martinfowler.com

14. If Software Eats the World, Better Use Version Control!

15. A4 Paper Doesn’t Stifle Creativity

  • 標準化と差別化と
  • Wardley Maps
  • A4用紙という規格の話がアーキテクチャとどう関係するのかよく分からん
    • アーキテクチャのどこかの部分を標準化するとしたらの話
      • 「A4用紙」という標準を採用しても「絵を書きたい」「メモしたい」という創造性は阻害しない
      • IT標準を採用するとして「ビジネス価値」に注力できるようになっている?そうならないとダメじゃない?
  • こういう解釈もできそう
    • 部署によって同じ観点の表現に互換性がなく、本当はつながるはずのシステムがどのようにつながるのか分からない問題がある
    • それぞれを結びつける標準について言及しているんじゃないかと読み取った
  • 共通するのは「本質的でないことに煩わされないためにどうすればいいか」かなぁ

参考情報

JSONプロセッサーjq

使用頻度は高いのに使い方とかドキュメントをちゃんと読んでなかったので調査してきた。


趣旨

jqのマニュアルを読んだりしてるうちに日本語版ができたので個人サイトで見れるようにした。

jq マニュアル・チュートリアル(非公式訳)


経緯

ドキュメントは jinja2 でビルドしてるのが分かった。

ただ読むより翻訳したほうがいいかと思ったのでforkして対応してみた

結果は個人のgithub.ioで見えるようにした。

もちろん本体にも貢献したいんだけど、リポジトリがだいぶ前から停滞してるようなのでPRするのは諦めた。

github.com:stedolan/jqはissueとPRの数もやばい
issueとPRの数もやばい

テキストファイル処理の逆引き辞典的な何か

ファイル形式別にCLIツールと基本的な操作を整理したかった。

対象の形式とCLIツール

対象の操作

  • select/filter
  • update
  • delete

形式別のまとめ


plain text

Linuxコマンド逆引き大全 Index:テキスト操作にまとまっててよい。

// https://httpbin.org/json
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "title": "Wake up to WonderWidgets!",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Overview",
        "type": "all"
      }
    ],
    "title": "Sample Slide Show"
  }
}

select/filter

title"Wake up"含む 行を選択する。

$ cat httpbin.json | grep -o -P '"title": "Wake up.*"'
"title": "Wake up to WonderWidgets!"

title"Wake up"含まない 行を選択する。 (slideshow.slides[].title だけを抽出するのは難しい)

$ at httpbin.json | grep -o -P '"title": "(?!Wake up).*"'
"title": "Overview"
"title": "Sample Slide Show"

update

s コマンドで slideshow.title を上書きする。

\(\s\{4\}"title"\):\s\{0,1\}"[^"]*" の説明。

パターン 意味
\( 置換した後も残す部分のグループを開始
\s\{4\} 4文字の空白文字(U+0020)にマッチするパターン
"title" 見たままの文字列にマッチするパターン
\) 置換した後も残す部分のグループを終了
: 見たままの文字列にマッチするパターン
\s\{0,1\} 0文字以上1文字以下の空白文字(U+0020)にマッチするパターン
"[^"]*" ダブルクォート(U+0022)で囲まれた、ダブルクォート(U+0022)以外の文字列にマッチするパターン
 cat httpbin.json | sed 's/\(\s\{4\}"title"\):\s\{0,1\}"[^"]*"/\1: "Updated Slide Show"/1'
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "title": "Updated Slide Show",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Updated Slide Show",
        "type": "all"
      }
    ],
    "title": "Updated Slide Show"
  }
}

delete

d コマンドで slideshow.title を消す。

  • 前の行の末尾のカンマ(U+002c)も削除しないと構造が壊れてしまう
  • 複数行マッチ機能を使えばどうにかなるのかもしれない
cat httpbin.json | sed '/\s\{2\}"title":.*/d'
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "type": "all"
      }
    ],
  }
}

merge/override/append


JSON

jq Manualを読み解けばなんでも出来るようになる。

jq playで実験できる。

インストール

Linux/WSL

# Ubuntu/Debian
$ sudo apt-get install jq
# CentOS/RHEL
$ sudo yum install jq
# Binary
$ mkdir -p "${HOME}/bin"
$ curl -fsSL --output "${HOME}/bin/jq" "https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64"
$ chmod +x "${HOME}/bin/jq"

Windows

# Chocolatey
PS> chocolatey install jq
# Scoop
PS> scoop install jq
# Binary
PS> New-Item -Path $env:USERPROFILE -Name bin -ItemType Directory -Force
PS> curl -fsSL --output "$env:USERPROFILE/bin/jq.exe" "https://github.com/stedolan/jq/releases/download/jq-1.6/jq-win64.exe"
// https://httpbin.org/json
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "title": "Wake up to WonderWidgets!",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Overview",
        "type": "all"
      }
    ],
    "title": "Sample Slide Show"
  }
}

select/filter

select(boolean_expression)

title"Wake up"含む オブジェクトを選択する。

# slideshow.slides.title contains "Wake up"
$ cat httpbin.json | jq -r '.slideshow.slides[] | select(.title | contains("Wake up")) | .'
{
  "title": "Wake up to WonderWidgets!",
  "type": "all"
}

title"Wake up"含まない オブジェクトを選択する。

# slideshow.slides.title not contains "Wake up"
$ cat httpbin.json | jq -r '.slideshow.slides[] | select(.title | contains("Wake up") | not) | .'
{
  "items": [
    "Why <em>WonderWidgets</em> are great",
    "Who <em>buys</em> WonderWidgets"
  ],
  "title": "Overview",
  "type": "all"
}

update

Addition:+

+ は上書きする(もとの slideshow が上書きされた)。

$ cat httpbin.json | jq -r '. + {slideshow: {title: "Updated Slide Show"}}'
{
  "slideshow": {
    "title": "Updated Slide Show"
  }
}

Multiplication, division, modulo: *, /, and %

* はマージする(もとの slideshow に新しい slideshow がマージされた)。

$ cat httpbin.json | jq -r '. * {slideshow: {title: "Updated Slide Show"}}'
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "title": "Wake up to WonderWidgets!",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Overview",
        "type": "all"
      }
    ],
    "title": "Updated Slide Show"
  }
}

delete

演算子はないから代わりに新しいオブジェクトを作る。(元のオブジェクトから author を消した)

$ cat httpbin.json | \
jq -r '.slideshow | {slideshow: { date: .date, title: .title, slides: .slides }}'
{
  "slideshow": {
    "date": "date of publication",
    "title": "Sample Slide Show",
    "slides": [
      {
        "title": "Wake up to WonderWidgets!",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Overview",
        "type": "all"
      }
    ]
  }
}

YAML

インストール

Linux/WSL

# Binary
$ mkdir -p "${HOME}/bin"
$ durl=$(curl -fsSL "https://api.github.com/repos/mikefarah/yq/releases/latest" | \
jq -r '.assets[] | select( .name | contains("linux_amd64")) | .browser_download_url')
$ curl -fsSL --output "${HOME}/bin/yq" "${durl}"
$ chmod +x "${HOME}/bin/yq"

Windows

# Chocolatey
PS> chocolatey install yq
# Scoop
PS> scoop install yq
# Binary
PS> New-Item -Path $env:USERPROFILE -Name bin -ItemType Directory -Force
PS> $durl = curl -fsSL "https://api.github.com/repos/mikefarah/yq/releases/latest" | `
ConvertFrom-Json | `
Select -Expand assets | `
Where -Match -Property name -Value windows_amd64.exe | `
Select -ExpandProperty browser_download_url
PS> curl -fsSL --output "$env:USERPROFILE/bin/yq.exe" $durl
# httpbin.yaml
slideshow:
  author: Yours Truly
  date: date of publication
  slides:
    - title: Wake up to WonderWidgets!
      type: all
    - items:
        - Why <em>WonderWidgets</em> are great
        - Who <em>buys</em> WonderWidgets
      title: Overview
      type: all
  title: Sample Slide Show

select/filter

Select - yq

title"Wake up to WonderWidgets!"一致する オブジェクトを選択する。

$ yq e '.slideshow.slides[] | select(.title == "Wake up to WonderWidgets!") | .' httpbin.yaml
title: Wake up to WonderWidgets!
type: all

title"Wake up to WonderWidgets!"一致しない オブジェクトを選択する。

$ yq e '.slideshow.slides[] | select(.title != "Wake up to WonderWidgets!") | .' httpbin.yaml
items:
  - Why <em>WonderWidgets</em> are great
  - Who <em>buys</em> WonderWidgets
title: Overview
type: all

update

Reduce - yq

# other.yaml
slideshow:
  title: Updated

同じ構造のドキュメントをマージする。

$ yq eval-all '. as $item ireduce ({}; . * $item)' httpbin.yaml other.yaml
slideshow:
  author: Yours Truly
  date: date of publication
  slides:
    - title: Wake up to WonderWidgets!
      type: all
    - items:
        - Why <em>WonderWidgets</em> are great
        - Who <em>buys</em> WonderWidgets
      title: Overview
      type: all
  title: Updated

delete

Delete - yq

slideshow.author を削除する。

$ yq e 'del(.slideshow.author)' httpbin.yaml
slideshow:
  date: date of publication
  slides:
    - title: Wake up to WonderWidgets!
      type: all
    - items:
        - Why <em>WonderWidgets</em> are great
        - Who <em>buys</em> WonderWidgets
      title: Overview
      type: all
  title: Sample Slide Show

XML

インストール

Linux/WSL

# Ubuntu/Debian
$ apt-get install xmlstarlet
# CentOS/RHEL
$ yum install xmlstarlet

Windows

# Chocolatey
PS> chocolatey install xmlstarlet
# Scoop
PS> scoop install xmlstarlet
<?xml version='1.0' encoding='us-ascii'?>

<!--  A SAMPLE set of slides  -->

<slideshow
    title="Sample Slide Show"
    date="Date of publication"
    author="Yours Truly"
    >

    <!-- TITLE SLIDE -->
    <slide type="all">
      <title>Wake up to WonderWidgets!</title>
    </slide>

    <!-- OVERVIEW -->
    <slide type="all">
        <title>Overview</title>
        <item>Why &gt;em&lt;WonderWidgets&gt;/em&lt; are great</item>
        <item/>
        <item>Who &gt;em&lt;buys&gt;/em&lt; WonderWidgets</item>
    </slide>

</slideshow>

select/filter

1. Querying XML documents

title"Wake up"含む オブジェクトを選択する。

$ cat httpbin.xml | MSYS_NO_PATHCONV=1 xml sel -t -m '/slideshow/slide/title[contains(., "Wake up")]' -c ..
<slide type="all">
      <title>Wake up to WonderWidgets!</title>
    </slide>

title"Wake up"含まない オブジェクトを選択する。

$ cat httpbin.xml | MSYS_NO_PATHCONV=1 xml sel -t -m '/slideshow/slide/title[not(contains(., "Wake up"))]' -c ..
<slide type="all">
        <title>Overview</title>
        <item>Why &gt;em&lt;WonderWidgets&gt;/em&lt; are great</item>
        <item/>
        <item>Who &gt;em&lt;buys&gt;/em&lt; WonderWidgets</item>
    </slide>

update

3. Editing XML documents

slideshow[@title] を更新する。

$ cat httpbin.xml | MSYS_NO_PATHCONV=1 xml ed -u '/slideshow/@title' -v "Updated"
<?xml version="1.0" encoding="us-ascii"?>
<!--  A SAMPLE set of slides  -->
<slideshow title="Updated" date="Date of publication" author="Yours Truly">
  <!-- TITLE SLIDE -->
  <slide type="all">
    <title>Wake up to WonderWidgets!</title>
  </slide>
  <!-- OVERVIEW -->
  <slide type="all">
    <title>Overview</title>
    <item>Why &gt;em&lt;WonderWidgets&gt;/em&lt; are great</item>
    <item/>
    <item>Who &gt;em&lt;buys&gt;/em&lt; WonderWidgets</item>
  </slide>
</slideshow>

delete

3. Editing XML documents

slideshow[@author] を削除する。

$ cat httpbin.xml | MSYS_NO_PATHCONV=1 xml ed -d '/slideshow/@author'
<?xml version="1.0" encoding="us-ascii"?>
<!--  A SAMPLE set of slides  -->
<slideshow title="Sample Slide Show" date="Date of publication">
  <!-- TITLE SLIDE -->
  <slide type="all">
    <title>Wake up to WonderWidgets!</title>
  </slide>
  <!-- OVERVIEW -->
  <slide type="all">
    <title>Overview</title>
    <item>Why &gt;em&lt;WonderWidgets&gt;/em&lt; are great</item>
    <item/>
    <item>Who &gt;em&lt;buys&gt;/em&lt; WonderWidgets</item>
  </slide>
</slideshow>

TOML

インストール

Linux(WSL)

# Python
$ pip install wildq
# Ubuntu/Debian
$ durl=$(curl -fsSL "https://api.github.com/repos/ahmet2mir/wildq/releases/latest" | \
jq -r '.assets[] | select( .name | contains(".deb")) | .browser_download_url')
$ curl -fsSL --output "${HOME}/wildq.deb" "${durl}"
$ sudo dpkg -i "${HOME}/wildq.deb"
# CentOS/RHEL
$ durl=$(curl -fsSL "https://api.github.com/repos/ahmet2mir/wildq/releases/latest" | \
jq -r '.assets[] | select( .name | contains(".rpm")) | .browser_download_url')
$ curl -fsSL --output "${HOME}/wildq.rpm" "${durl}"
$ sudo yum install -y "${HOME}/wildq.rpm"
# Binary
$ mkdir -p "${HOME}/bin"
$ durl=$(curl -fsSL "https://api.github.com/repos/ahmet2mir/wildq/releases/latest" | \
jq -r '.assets[] | select( .name | contains("linux-x86_64.tar.gz")) | .browser_download_url')
$ curl -fsSL "${durl}" | tar -xzvf - -C "${HOME}/bin" wq wildq

Windows

# Python
PS> pip install wildq
# httpbin.toml
[slideshow]
author = "Yours Truly"
date = "date of publication"
title = "Sample Slide Show"

[[slideshow.slides]]
title = "Wake up to WonderWidgets!"
type = "all"

[[slideshow.slides]]
items = ["Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets"]
title = "Overview"
type = "all"

select/filter

title"Wake up"含む オブジェクトを選択する。

$ cat httpbin.toml | wildq -i toml -r '.slideshow.slides[] | select(.title | contains("Wake up")) | .'
title = "Wake up to WonderWidgets!"
type = "all"

title"Wake up"含まない オブジェクトを選択する。

$ cat httpbin.toml | wildq -i toml -r '.slideshow.slides[] | select(.title | contains("Wake up") | not) | .'
items = [ "Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets",]
title = "Overview"
type = "all"

update

+ は上書きする(もとの slideshow が上書きされた)。

$ $ cat httpbin.toml | wildq -i toml -r '. + {slideshow: {title: "Updated Slide Show"}}'
[slideshow]
title = "Updated Slide Show"

* はマージする(もとの slideshow に新しい slideshow がマージされた)。

$ cat httpbin.toml | wildq -i toml -r '. * {slideshow: {title: "Updated Slide Show"}}'
[slideshow]
author = "Yours Truly"
date = "date of publication"
title = "Updated Slide Show"
[[slideshow.slides]]
title = "Wake up to WonderWidgets!"
type = "all"

[[slideshow.slides]]
items = [ "Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets",]
title = "Overview"
type = "all"

delete

演算子はないから代わりに新しいオブジェクトを作る。(元のオブジェクトから author を消した)

$ cat httpbin.toml | \
wildq -i toml -r '.slideshow | {slideshow: { date: .date, title: .title, slides: .slides }}'
[slideshow]
date = "date of publication"
title = "Sample Slide Show"
[[slideshow.slides]]
title = "Wake up to WonderWidgets!"
type = "all"

[[slideshow.slides]]
items = [ "Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets",]
title = "Overview"
type = "all"

CSV

インストール

Linux/WSL

# Ubuntu/Debian
$ durl=$(curl -fsSL "https://api.github.com/repos/harelba/q/releases/latest" | \
jq -r '.assets[] | select( .name | contains(".deb")) | .browser_download_url')
$ curl -fsSL --output "${HOME}/q.deb" "${durl}"
$ sudo dpkg -i "${HOME}/q.deb"
# CentOS/RHEL
$ durl=$(curl -fsSL "https://api.github.com/repos/harelba/q/releases/latest" | \
jq -r '.assets[] | select( .name | contains(".rpm")) | .browser_download_url')
$ curl -fsSL --output "${HOME}/q.rpm" "${durl}"
$ sudo yum install -y "${HOME}/q.rpm"
# Binary
$ mkdir -p "${HOME}/bin"
$ durl=$(curl -fsSL "https://api.github.com/repos/harelba/q/releases/latest" | \
jq -r '.assets[] | select( .name | contains("-Linux")) | .browser_download_url')
$ curl -fsSL --output "${HOME}/bin/q" "${durl}"
$ chmod +x "${HOME}/bin/q"

Windows

# Binary
PS> New-Item -Path $env:USERPROFILE -Name bin -ItemType Directory -Force
PS> $durl = curl -fsSL "https://api.github.com/repos/harelba/q/releases/latest" | `
ConvertFrom-Json | `
Select -Expand assets | `
Where -Match -Property name -Value Windows.exe | `
Select -ExpandProperty browser_download_url
PS> curl -fsSL --output "$env:USERPROFILE/bin/q.exe" $durl

github.com/inouet/ken-all/testdata/test_001.csv

注意: 文字コードSJISからUTF-8へ、改行コードをLFへ変換すること

PS> (curl -fsSL https://raw.githubusercontent.com/inouet/ken-all/master/testdata/test_001.csv) -join "`n" | Set-Content -Path test_001.csv -Encoding utf8

PS> q -b -O -d , 'SELECT * FROM test_001.csv'
c1  ,c2,c3    ,c4     ,c5          ,c6                ,c7 ,c8    ,c9         ,c10,c11,c12,c13,c14,c15
1101,60,600000,ホッカイドウ,サッポロシチュウオウク,イカニケイサイガナイバアイ   ,北海道,札幌市中央区,以下に掲載がない場合 ,0  ,0  ,0  ,0  ,0  ,0
1101,64,640941,ホッカイドウ,サッポロシチュウオウク,アサヒガオカ           ,北海道,札幌市中央区,旭ケ丘        ,0  ,0  ,1  ,0  ,0  ,0
1101,60,600041,ホッカイドウ,サッポロシチュウオウク,オオドオリヒガシ        ,北海道,札幌市中央区,大通東        ,0  ,0  ,1  ,0  ,0  ,0
1101,60,600042,ホッカイドウ,サッポロシチュウオウク,オオドオリニシ(1-19チョウメ),北海道,札幌市中央区,大通西(1〜19丁目),1  ,0  ,1  ,0  ,0  ,0

select/filter

旭ケ丘含む 行を選択する。

$ q -b -O -d , 'SELECT * FROM test_001.csv WHERE c9="旭ケ丘"'
c1  ,c2,c3    ,c4     ,c5          ,c6     ,c7 ,c8    ,c9 ,c10,c11,c12,c13,c14,c15
1101,64,640941,ホッカイドウ,サッポロシチュウオウク,アサヒガオカ,北海道,札幌市中央区,旭ケ丘,0  ,0  ,1  ,0  ,0  ,0

旭ケ丘含まない 行を選択する。

$ q -b -O -d , 'SELECT * FROM test_001.csv WHERE c9<>"旭ケ丘"'
c1  ,c2,c3    ,c4     ,c5          ,c6                ,c7 ,c8    ,c9         ,c10,c11,c12,c13,c14,c15
1101,60,600000,ホッカイドウ,サッポロシチュウオウク,イカニケイサイガナイバアイ   ,北海道,札幌市中央区,以下に掲載がない場合 ,0  ,0  ,0  ,0  ,0  ,0
1101,60,600041,ホッカイドウ,サッポロシチュウオウク,オオドオリヒガシ        ,北海道,札幌市中央区,大通東        ,0  ,0  ,1  ,0  ,0  ,0
1101,60,600042,ホッカイドウ,サッポロシチュウオウク,オオドオリニシ(1-19チョウメ),北海道,札幌市中央区,大通西(1〜19丁目),1  ,0  ,1  ,0  ,0  ,0

update

plain text の項を参照。

delete

plain text の項を参照。


TSV

インストール

CSV の項を参照。

github.com/harelba/q/examples/exampledatafile

PS> curl -fsSL --remote-name https://raw.githubusercontent.com/harelba/q/master/examples/exampledatafile

PS> q "SELECT * FROM exampledatafile LIMIT 10"
-rw-r--r-- 1 root root 2064 2006-11-23 21:33 netscsid.conf
-rw-r--r-- 1 root root 1343 2007-01-09 20:39 wodim.conf
-rw-r--r-- 1 root root 112 2007-06-22 18:08 apg.conf
-rw-r--r-- 1 root root 15752 2009-07-25 18:13 ltrace.conf
-rw-r--r-- 1 root root 624 2010-05-16 14:18 mtools.conf
-rw-r--r-- 1 root root 395 2010-06-20 11:11 anacrontab
-rw-r--r-- 1 root root 18673 2010-10-18 06:49 globash.rc
-rw-r--r-- 1 root root 23958 2010-11-15 10:07 mime.types
-rw-r--r-- 1 root root 449 2010-11-15 10:07 mailcap.order
-rw-r--r-- 1 root root 8453 2010-12-03 22:32 nanorc

select/filter

ssh含む 行を選択する。

$ q 'SELECT * FROM exampledatafile WHERE c8="ssh"'
drwxr-xr-x 2 root root 4096 2011-12-22 18:57 ssh

ssh含まない 行を選択する。

$ q 'SELECT * FROM exampledatafile WHERE c8<>"ssh" ORDER BY c8 LIMIT 10'
drwxr-xr-x 5 root root 4096 2011-10-12 16:28 ConsoleKit
drwxr-xr-x 5 root root 4096 2011-10-12 16:30 NetworkManager
drwxr-xr-x 2 root root 4096 2011-03-15 23:05 ODBCDataSources
drwxr-xr-x 3 root root 4096 2011-12-18 12:09 OpenCL
drwxr-xr-x 2 root root 4096 2012-01-13 12:48 R
drwxr-xr-x 2 root root 4096 2011-10-12 16:30 UPower
drwxr-xr-x 10 root root 4096 2012-01-16 16:08 X11
drwxr-xr-x 3 root root 4096 2011-10-12 16:30 acpi
-rw-r--r-- 1 root root 2981 2011-10-12 16:27 adduser.conf
-rw-r--r-- 1 root root 10 2011-12-18 11:45 adjtime

update

plain text の項を参照。

delete

plain text の項を参照。


LTSV

インストール

Linux/WSL

# Binary
$ mkdir -p "${HOME}/bin"
$ durl=$(curl -fsSL "https://api.github.com/repos/sters/ltsvq/releases/latest" | \
jq -r '.assets[] | select( .name | contains("linux_amd64.tar.gz")) | .browser_download_url')
$ curl -fsSL "${durl}" | tar -xzvf - -C "${HOME}/bin" ltsvq

Windows

# Binary
# PS> New-Item -Path $env:USERPROFILE -Name bin -ItemType Directory -Force
# PS> $durl = curl -fsSL "https://api.github.com/repos/sters/ltsvq/releases/latest" | `
# ConvertFrom-Json | `
# Select -Expand assets | `
# Where -Match -Property name -Value windows_amd64.tar.gz | `
# Select -ExpandProperty browser_download_url
# PS> curl -fsSL --output "$env:USERPROFILE/ltsvq.tar.gz" $durl
# PS> tar -xzvf "$env:USERPROFILE/ltsvq.tar.gz" -C "$env:USERPROFILE/bin" ltsvq.exe

github.com/sters/ltsvq/example.ltsv

host:127.0.0.1    ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:192.168.1.1    ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:127.0.0.1  ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:192.168.1.1    ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:127.0.0.1  ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:192.168.1.1    ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:127.0.0.1  ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:192.168.1.1    ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)

select/filter

host の値が 192.始まる 行を選択する。

$ cat example.ltsv | ltsvq -q "select * from ltsv where host like '192%'"
_dummy: host:192.168.1.1        ident:- referer:http://www.example.com/start.html       req:GET /apache_pb.gif HTTP/1.0 size:2326       status:200      time:[10/Oct/2000:13:55:36 -0700]       ua:Mozilla/4.08 [en] (Win98; I ;Nav)     user:frank
_dummy: host:192.168.1.1        ident:- referer:http://www.example.com/start.html       req:GET /apache_pb.gif HTTP/1.0 size:2326       status:200      time:[10/Oct/2000:13:55:36 -0700]       ua:Mozilla/4.08 [en] (Win98; I ;Nav)     user:frank
_dummy: host:192.168.1.1        ident:- referer:http://www.example.com/start.html       req:GET /apache_pb.gif HTTP/1.0 size:2326       status:200      time:[10/Oct/2000:13:55:36 -0700]       ua:Mozilla/4.08 [en] (Win98; I ;Nav)     user:frank
_dummy: host:192.168.1.1        ident:- referer:http://www.example.com/start.html       req:GET /apache_pb.gif HTTP/1.0 size:2326       status:200      time:[10/Oct/2000:13:55:36 -0700]       ua:Mozilla/4.08 [en] (Win98; I ;Nav)     user:frank

host の値が 192.始まらない 行を選択する。

$ cat example.ltsv | ltsvq -q "select * from ltsv where host not like '192%'"
_dummy: host:127.0.0.1  ident:- referer:http://www.example.com/start.html   req:GET /apache_pb.gif HTTP/1.0 size:2326   status:200  time:[10/Oct/2000:13:55:36 -0700]   ua:Mozilla/4.08 [en] (Win98; I ;Nav)    user:frank  
_dummy: host:127.0.0.1  ident:- referer:http://www.example.com/start.html   req:GET /apache_pb.gif HTTP/1.0 size:2326   status:200  time:[10/Oct/2000:13:55:36 -0700]   ua:Mozilla/4.08 [en] (Win98; I ;Nav)    user:frank  
_dummy: host:127.0.0.1  ident:- referer:http://www.example.com/start.html   req:GET /apache_pb.gif HTTP/1.0 size:2326   status:200  time:[10/Oct/2000:13:55:36 -0700]   ua:Mozilla/4.08 [en] (Win98; I ;Nav)    user:frank  
_dummy: host:127.0.0.1  ident:- referer:http://www.example.com/start.html   req:GET /apache_pb.gif HTTP/1.0 size:2326   status:200  time:[10/Oct/2000:13:55:36 -0700]   ua:Mozilla/4.08 [en] (Win98; I ;Nav)    user:frank  

update

plain text の項を参照。

delete

plain text の項を参照。


properties

TBD

JaSST'21 Tokyoで視聴したセッションのメモ

JaSST'21 Tokyoで視聴したセッションのメモ。

イベント情報

ソフトウェアテストシンポジウム 2021 東京

セッション情報

基調講演 "Being Agile about Architecture"(Joseph W. Yoder (Refactory CEO))

Being Agile about Architecture

CI/CDパイプラインにおけるテストレベル(武井 眞人(テクバン))

後工程を最適化するために既存の仕組みを大きく変更するのはコストがかかるため、できるだけ負担が生じないように後工程の役割や事前条件を前工程に移していくのが実践知なんだなと思った。

スポンサーセッションだからか資料は見つからなかった

自動テスト普及活動の勘所(松⽊ 直之(富⼠通))

  • 概要:自動テストの普及にはとても地道な努力が必要
  • 紹介されてたこと
    • たくさんの「〇〇ができない」に応えるため自作したテストツールを配布している
    • 間口を広げる「入門」や活用を進める「事例紹介」の両方のセミナーしてる
    • 見積もりやPoCや技術選定のための基礎資料をメンテしてる

自動テストのコスト削減に対する効果は薄いから、その線で売り込むのは筋が悪いという話だった。 ただ、手動テストで不可能な網羅テストを実施できるということは、実質的にコスト削減できているということにもなるよね、という話もあってそれは確かにそうだなぁと思った。

スポンサーセッションだからか資料は見つからなかった

仕様整理のためのテスト設計入門(河野 哲也(DeNA))

  • テスト設計技法の紹介とワークのセッション
  • 名前は知らなくても自然とやっていたものがあるかも

普段の仕事にすぐ取り入れられる内容だった。 日本語であの場合この場合と議論するより、はるかに効率的で正確な仕様や設計を決められるので、是非とも習得してもらいたいテクニックだった。 別の表現に変換しなければテストできない仕様や設計は未完成なんですよ(主観)。

仕様整理のためのテスト設計入門

招待講演 "パターンQA2AQによるアジャイル品質のマインド、体制、プロセス、技術"(鷲崎 弘宜 氏(早稲田大学 / NII / システム情報 / エクスモーション))

  • スクラムによるソフトウェア開発に品質の観点を織り込もうとしている活動の紹介
    • パタンランゲージとアジャイルソフトウェア開発の理解が求められるので難しい……
  • 誰かがコメントしてたけど「スクラムマスターと同じように品質マスターが増えるような感じ」
    • つまり品質を保証するための型を作り、守・破・離していく

パターンQA2AQによるアジャイル品質のマインド、体制、プロセス、技術

QuarkusのSpring Integrationとネイティブ実行可能ファイル作成とコンテナイメージ作成を実験

QuarkusプロジェクトにSpring Boot アプリの実装を取り込んだデモプロジェクトでいろいろ実験してみた。

ソースコード一式のzipアーカイブ

QuarkusのSpringインテグレーション機能

Quarkus - Quarkus Extension for Spring DI API

この辺を注意するといいのかもしれません。

インテグレーション機能と考えるより、移行機能と考えるのがいいかもしれない。

  • エントリポイントは不要
    • @SpringBootApplicationのクラスは削除する
  • アプリケーションコンテキストの代替品はありません
    • おそらくCDIのコンテキストへ読み替えて実現しているため
    • したがって Spring Test で実装したテストコードは移行できない……(ちゃんと調べてないから分からないけど)
  • Spring Frameworkのライブラリに対応するQuarkusプラグインが、それぞれのアノテーションをQuarkusのコンテキストへ変換している

プロジェクトの生成

Mavenコマンドで生成します。

mvn io.quarkus:quarkus-maven-plugin:1.12.2.Final:create \
    -DprojectGroupId=com.example \
    -DprojectArtifactId=spring-data-jpa-web-security-demo \
    -DclassName="com.example.demo.GreetingController" \
    -Dpath="/greeting" \
    -Dextensions="resteasy,spring-web,spring-data-jpa,spring-security,quarkus-elytron-security-properties-file,resteasy-jackson,quarkus-jdbc-h2"

ネイティブ実行可能ファイルの作成

Quarkus - Building a Native Executable

nativeプロファイルでpackageフェーズを実行するとネイティブの実行可能ファイルを作成します。

実行可能ファイルはtarget/spring-data-jpa-web-security-demo-1.0.0-SNAPSHOT-runnerに作成します。

AOTコンパイルに時間がかかるのは変わらないのですが、フレームワーク自体がSpring Bootに比べて小さいため、ビルドマシンに要求するリソースは比較的低くなります。

./mvnw package -Pnative

コンテナイメージの作成

Quarkus - Container Images

コンテナイメージの作成方法が2種類あります。

  • Javaアプリケーションとしてコンテナイメージを作成
  • ネイティブ実行可能ファイルからコンテナイメージを作成

Javaアプリケーションとしてコンテナイメージを作成

Jibでコンテナイメージを作成するときはプロファイルにbuild-java-jibを指定します。

mvn package -Pbuild-java-jib

src/main/docker/Dockerfile.jvmに従ってコンテナイメージを作成するときはプロファイルにbuild-java-dockerを指定します。

mvn package -Pbuild-java-docker

作成したイメージの大きさはこれぐらいになります。ベースイメージが UBI になるため、比較的大きめ。

$ docker images com.example/spring-data-jpa-web-security-demo
REPOSITORY                                      TAG                 IMAGE ID       CREATED          SIZE
com.example/spring-data-jpa-web-security-demo   java-docker         93303798e24f   14 minutes ago   406MB
com.example/spring-data-jpa-web-security-demo   java-jib            bb8fd47095f3   15 minutes ago   225MB

ネイティブ実行可能ファイルからコンテナイメージを作成

Jibでコンテナイメージを作成するときはプロファイルにnative,build-navive-jibを指定します。

mvn package -Pnative,build-navive-jib

src/main/docker/Dockerfile.nativeに従ってコンテナイメージを作成するときはプロファイルにnative,build-navive-dockerを指定します。

mvn package -Pnative,build-navive-docker

src/main/docker/Dockerfile.native-distrolessに従ってコンテナイメージを作成するときはプロファイルにnative,build-navive-distrolessを指定します。

mvn package -Pnative,build-navive-distroless

作成したイメージの大きさはこれぐらいになります。 QuarkusのJibプラグインはベースイメージとしてregistry.access.redhat.com/ubi8/ubi-minimal(103MB)を利用するため、Dockerfileでdistrolessを使う場合に比べると少し大きくなってしまうのが分かります。

$ docker images com.example/spring-data-jpa-web-security-demo
REPOSITORY                                      TAG                 IMAGE ID       CREATED          SIZE
com.example/spring-data-jpa-web-security-demo   native-distroless   618ac1277742   7 seconds ago    87.3MB
com.example/spring-data-jpa-web-security-demo   native-docker       eae04a98e2d0   4 minutes ago    171MB
com.example/spring-data-jpa-web-security-demo   native-jib          232110e84e33   9 minutes ago    171MB

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

$ docker run --rm -i -p 8080:8080 com.example/spring-data-jpa-web-security-demo:java-jib
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-03-15 08:18:09,374 INFO  [io.agr.pool] (main) Datasource '<default>': Initial size smaller than min. Connections will be created when necessary
2021-03-15 08:18:10,221 INFO  [io.quarkus] (main) spring-data-jpa-web-security-demo 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.12.2.Final) started in 2.235s. Listening on: http://0.0.0.0:8080
2021-03-15 08:18:10,222 INFO  [io.quarkus] (main) Profile prod activated.
2021-03-15 08:18:10,222 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-h2, mutiny, narayana-jta, resteasy, resteasy-jackson, security, security-properties-file, smallrye-context-propagation, spring-data-jpa, spring-di, spring-security, spring-web]

---

$ curl --user test:test -s localhost:8080/books; 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/books; 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/books; echo
[{"id":1,"title":"test 001u1"}]

$ curl -s localhost:8080/books; echo

第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_

参考リンク