Hexoで画像を表示する

_config.ymlをpost_asset_folder: trueにする。

hexo new {記事title}の時に source/_postsディレクトリ以下に{記事tile}のディレクトリができるので、
そのディレクトリ内に画像を配置。

1
{% asset_img 画像名 記事名 %}

のタグで表示できます。

画像名や記事名にスペースがふくまれる場合、ダブルクォーテーションで囲ってください。

1
{% asset_img "画像名" "記事名" %}

参考url

https://hexo.io/docs/asset-folders#Tag-Plugins-For-Relative-Path-Referencing

KotlinでSpring Bootでhello worldを出してみる

KotlinのSpring Bootでhello worldを出してみました。

前提条件

  • JDKがインストール済み
  • Intellij IDEAもインストール済み

Spring Bootのフレームワークをダウンロード

Spring Initializrのから設定してダウンロード

設定の参考url

https://qiita.com/kawasaki_dev/items/1a188878eb6928880256#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E9%9B%9B%E5%BD%A2%E3%82%92%E4%BD%9C%E6%88%90

GradleをインストールしてSpring Bootを起動してみる

Intellij IDEAからダウンロードしたSpring BootのフレームワークをOpenする。

そうすると右下にGradleをインストールしますかのようなポップアップが現れるので、そこからインストールをしてもらう。

GradleがインストールされるとProjectパネルに.gradelとbuildのディレクトリが作成され、main関数の横にrunボタンが表示されるのでrunボタンから起動してWEBアプリケーションを実行します。

Spring BootはWebサーバーが内包されている

PHPですとnginxやapacheなどのWebサーバを用意する必要がありますが、Spring Bootのフレームワーク自体にWebサーバが内包されているのでmain関数の横にrunボタンを押すだけでwebアプリケーションを起動できます、便利ですね。
ちなみに、AWSのLambdaのようなものをサーバレスといいますが、このような「WEBサーバ」の用意が不要なこともサーバレスというようです。

hello worldを表示するコード

https://github.com/unamu1229/spring-boot-demo/pull/2/files
のファイルを追加します。
再度main関数をrunすると http://localhost:8080/ にhello worldと表示されました。

参考url

Spring Initializr

https://qiita.com/kawasaki_dev/items/1a188878eb6928880256#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E9%9B%9B%E5%BD%A2%E3%82%92%E4%BD%9C%E6%88%90

サーバレス

https://qiita.com/kannkyo/items/c3d25553fc505150bfdf#31-%E3%82%B5%E3%83%BC%E3%83%90%E3%83%AC%E3%82%B9%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3

Controller

https://spring.pleiades.io/guides/tutorials/spring-boot-kotlin/

https://blog.qbist.co.jp/?p=2654

インターフェイス名はクライアント側に由来する名前にする

インターフェイスの名前を命名する時に、サーバー側の名前ではなくクライアント側の名前をつける必要があるそうです。
このソースは、アジャイルソフトウェア開発の奥義(ロバート・C・マーチン)9.3 鍵は「抽象」にありの項目(P130)に書かれていました。
理由は、抽象クラスはそれを実際に実装するクラスとの関係よりも、それを利用するクラスとの関係の方がずっと密接だからだそうです。

Laravelのapp()->make()はアンチパターンなの?

Laravelのapp()->make()はアンチパターンなのか調べてみました。

結論

フレームワークに依存すると問題のあるドメイン層以外ではapp()->make()を利用してもいい。

調査内容

アプリケーションサービスのメソッド内でapp()->make()を利用していたら、
疎結合を意識してコンストラクタインジェクションを利用するようにコードレビューをもらったことがあったのですが、app()->make()は疎結合だし、コンストラクタからタイプヒンティングでプロパティに持たせるほうがインスタンスの作成時に必ず依存することで循環参照になりやすく結合度は高そうと思ったのでいろいろ調べて見ました。

パターンの種類でいうと
app()->make()はServiceLocaorで、
コンストラクタインジェクションはDIになります。

双方のバターンとも疎結合を基本としている。
という記事や、
そもそもDIは依存性の解決であって、依存が無くなるわけではない。
という記事があったりして正反対な意見があるようですが、ServiceLocaorをDIにすることで、サービスロケータ自体には依存しないようになるようです。

そのため、ServiceLocaorがアンチパターンのような記事をいくつか見かけたのですが、マーティンファウラー先生によるとそれは違うようです。
先生によるとDIよりService Locatorが利用できるのであれば、それを利用するのはもっともだとおっしゃっています。

1
Service Locator と Dependency Injection とのどちらを採用するかの判断は、 ロケータへの依存性が問題になるかどうかにかかっている。

とありましたので、Laravelのフレームワークを利用する上で、フレームワークに依存すると問題のあるドメイン層以外では Service Locatorを採用したほうがよさそうです。

また、同様の意見が「上田勲. プリンシプル オブ プログラミング 3年目までに身につけたい 一生役立つ101の原理原則」にも書かれていました。

1
2
結合先モジュールの質にも着目しましょう。密な結合自体は問題ですが、本質的には、不安定な要素と密に結合するのが問題になります。安定的なライブラリに依存するのは特に問題ではありません。盲目的にデータ結合を目指すというより、相手によって付き合いの深さを判断しましょう。
`

「データ結合」とは、この本の中でもっとも疎結合であるという形です。
フレームワークとして提供されるサービスロケータの質は安定的しているといえるので、盲目的に疎結合を目指してDIを利用するということは良くないようです。

参考url

マーティンファウラー

https://kakutani.com/trans/fowler/injection.html#ServiceLocatorVsDependencyInjection

マーティンファウラーとは反対の意見

http://blog.a-way-out.net/blog/2015/08/31/your-dependency-injection-is-wrong-as-I-expected/

日毎のlogrotateをテストで動かしたい

日毎のlogrotateをテストで動かしたい

AmazonLinuxで日毎の設定のlogrotateの動作確認をしたいと思い。
対象のファイルの日付をtouchで昨日にして、
/usr/sbin/logrotate /etc/logrotate.conf を実行してもログファイルが作成されませんでした。

調べてみると
/var/lib/logrotate/logrotate.status
というファイルに前回のログローテートを行った時刻を記載しているとのこと。

/var/lib/logrotate/logrotate.status に先程logrotateコマンドをした日時が記録されていたので、
その日時を昨日に変更して、再度 /usr/sbin/logrotate /etc/logrotate.conf を実行すると

無事ログファイルがローテートされました。

tips

調べている際に、logrotateは/etc/cron.dailyで実行されていて。
cron.dailyの実行は/etc/anacrontabで設定されているということを知りました。

参考サイト

https://www.khstasaba.com/?p=958
https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/6/html/deployment_guide/ch-automating_system_tasks#s2-configuring-anacron-jobs

systemd Failed to execute command: Permission denied

CentOS8にてsystemdのtest.serviceを作成してみてsystemd start testを行ってみると実行に失敗、
/var/log/messagesに下記のエラーが表示されていました。

1
2
3
Mar 20 13:21:45 unamu systemd[1]: Started testscript.
Mar 20 13:21:45 unamu systemd[83619]: test.service: Failed to execute command: Permission denied
Mar 20 13:21:45 unamu systemd[83619]: test.service: Failed at step EXEC spawning /tmp/test: Permission denied

Permission deniedの表示からファイルのパーミッションを確認しましたが、問題無し。
SELinuxが邪魔をしていたようです。
有効になっているか確認

1
2
$ getenforce
Enforcing

Permissiveモードにします。

1
$ setenforce 0

その後、systemd start testで無事実行されました。

参考url

https://fujiyasu.hatenablog.com/entry/2016/08/05/094804

ファイアウォールでpingの疎通確認を拒否する

CentOS8のfirewall-cmdでpingの疎通確認を拒否を試してみました。
pingはicmpのプロトコルです。

firewallの設定内容を確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost unamu]# firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: enp0s3
sources:
services: cockpit dhcpv6-client http ssh
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:

icmp-block-inversionがyesの時にicmp-blocksに記載されたICMP Typeを受け入れ、
noの時はicmp-blocksに記載されたICMP Typeを拒否します。
icmp-blocksに何も設定されていないので、icmp-block-inversionをyesにすることですべてのICMP Typeを拒否することで
pingを拒否できそうです。

icmp-block-inversionをyesにする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@localhost unamu]# firewall-cmd --add-icmp-block-inversion
success
[root@localhost unamu]# firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: yes
interfaces: enp0s3
sources:
services: cockpit dhcpv6-client http ssh
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:

icmp-block-inversionが yes になったので、pingを実行して拒否されるか確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
~/D/g/D/laradock ❯❯❯ ping 192.168.11.17                                            (git)-[master]
PING 192.168.11.17 (192.168.11.17): 56 data bytes
92 bytes from 192.168.11.17: Communication prohibited by filter
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
4 5 00 5400 59d4 0 0000 40 01 896f 192.168.11.4 192.168.11.17

Request timeout for icmp_seq 0
92 bytes from 192.168.11.17: Communication prohibited by filter
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
4 5 00 5400 9edf 0 0000 40 01 4464 192.168.11.4 192.168.11.17

Request timeout for icmp_seq 1
92 bytes from 192.168.11.17: Communication prohibited by filter
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
4 5 00 5400 40eb 0 0000 40 01 a258 192.168.11.4 192.168.11.17

^C
--- 192.168.11.17 ping statistics ---
3 packets transmitted, 0 packets received, 100.0% packet loss

100.0% packet loss となっているので拒否できているようです。

icmp-block-inversionをnoに戻しておきます。

1
2
[root@localhost unamu]# firewall-cmd --remove-icmp-block-inversion
success

応答自体を返さない場合はtargetをDROPにすることで可能です、
targetの設定を反映するにはreloadが必要なようです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@localhost unamu]# firewall-cmd --set-target=DROP --permanent
success
[root@localhost unamu]# firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: enp0s3
sources:
services: cockpit dhcpv6-client http ssh
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
[root@localhost unamu]# firewall-cmd --reload
success
[root@localhost unamu]# firewall-cmd --list-all
public (active)
target: DROP
icmp-block-inversion: no
interfaces: enp0s3
sources:
services: cockpit dhcpv6-client http ssh
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:

pingで応答が無いか確認

1
2
3
4
5
6
7
8
9
~/D/g/D/laradock ❯❯❯ ping 192.168.11.17                                            (git)-[master]
PING 192.168.11.17 (192.168.11.17): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
Request timeout for icmp_seq 3
^C
--- 192.168.11.17 ping statistics ---
5 packets transmitted, 0 packets received, 100.0% packet loss

AWSのサービスにpingを実行した時と同じ反応です、
これが応答が無い場合のリアクションなんですね。

元に戻しておきます。

1
2
3
4
[root@localhost unamu]# firewall-cmd --set-target=default --permanent
success
[root@localhost unamu]# firewall-cmd --reload
success

参考サイト

https://milestone-of-se.nesuke.com/sv-basic/linux-basic/firewall-cmd/
https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/security_guide/sec-managing_icmp_requests
https://milestone-of-se.nesuke.com/sv-basic/linux-basic/drop-ping-on-linux/

Goの使い始めにハマったこと

GOPATH 関係

コードポイント 関係

ElasticSearchでqueryの結果が下記の\u6771\u4eac0ように日本語がUTF16のコードポイントとして表示されていた。
本当はprefの項目は東京0と表示されて欲しかった。

1
2
3
4
5
body, _ := ioutil.ReadAll(resp.Body)
mt.Println(string(body))

# コンソール出力
{"took":27,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":1.0,"hits":[{"_index":"search_job","_type":"_doc","_id":"0","_score":1.0,"_source":{"pref":"\u6771\u4eac0","employment":"full-time","line":{"name":"\u5c71\u624b\u7dda","station":["\u6771\u4eac"]}}}]}}

日本語がUTF16のコードポイントとして表示されており、
tranceformパッケージでurf16のバイトのDecodeを試したが文字化けして失敗。
(失敗例)

1
2
3
4
5
6
7
8
9
body, _ := ioutil.ReadAll(resp.Body)
hoge, _, _ := transform.Bytes(
unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewDecoder(),
body,
)
fmt.Println(string(hoge))

# コンソール出力
≻潴歯㨢ⰵ琢浩摥潟瑵㨢慦獬ⱥ弢桳牡獤㨢≻潴慴≬ㄺ∬畳捣獥晳汵㨢ⰱ猢敲慬楴湯㨢攢≱ⱽ洢硡獟潣敲㨢⸱ⰰ栢瑩≳嬺≻楟摮硥㨢猢慥捲彨潪≢∬瑟≦∺畜㜶ㄷ畜攴捡∰∬浥汰祯敭瑮㨢昢汵⵬楴敭Ⱒ氢湩≥笺渢浡≥∺畜挵ㄷ畜㈶戴畜搷慤Ⱒ猢慴楴湯㨢≛畜㜶ㄷ畜攴捡崢絽嵽絽

structにqueryの結果を入れて表示すると、期待通り東京0と表示されました。
Unmarshalの中でdecodeがされているようです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
type GetResponse struct {
Took int `json:"took"`
TimedOut bool `json:"timed_out"`
Shards struct {
Total int `json:"total"`
Successful int `json:"successful"`
Skipped int `json:"skipped"`
Failed int `json:"failed"`
} `json:"_shards"`
Hits struct {
Total struct {
Value int `json:"value"`
Relation string `json:"relation"`
} `json:"total"`
MaxScore float64 `json:"max_score"`
Hits []struct {
Index string `json:"_index"`
Type string `json:"_type"`
ID string `json:"_id"`
Score float64 `json:"_score"`
Source struct {
Pref string `json:"pref"`
Employment string `json:"employment"`
Line struct {
Name string `json:"name"`
Station []string `json:"station"`
} `json:"line"`
} `json:"_source"`
} `json:"hits"`
} `json:"hits"`
}

func main() {
body, _ := ioutil.ReadAll(resp.Body)
var getResponse GetResponse
unmarshalErr := json.Unmarshal(body, &getResponse)
if unmarshalErr != nil {
fmt.Println(unmarshalErr)
}

for _, hit := range getResponse.Hits.Hits {
fmt.Println("pref", hit.Source.Pref)
}
}

# コンソール出力
pref 東京0

json全体ではなく、コードポイントの箇所のみであれば、Unquoteで文字に変換できました。

1
2
3
4
5
t := "\\u6771\\u4eac0"
converted, _ := strconv.Unquote(`"` + t + `"`)
fmt.Println(converted)
# コンソール出力
東京0

Elasticsearch 使い方

実行環境

  • Elasticsearch 7.10.1
  • Kibana 7.10.1

    検索

完全一致検索

データ項目のタイプがキーワードとして登録されている必要があります。
検索対象のtypekeywordで、querytermを指定して行う必要があります。

request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#対象index作成 都道府県(pref)のtypeを完全一致の対象にする為にkeywordで登録する
PUT search_job
{
"mappings" : {
"properties": {
"pref": {
"type": "keyword"
}
}
}
}

#type確認
GET search_job/_mapping

response

1
2
3
4
5
6
7
8
9
10
11
{
"search_job" : {
"mappings" : {
"properties" : {
"pref" : {
"type" : "keyword"
}
}
}
}
}

request

1
2
3
4
5
6
7
データ登録
POST /search_job/_doc
{
"pref" : "東京"
}
# データ確認
GET /search_job/_search

response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "search_job",
"_type" : "_doc",
"_id" : "jS17e3YB0KvFjllCp3-y",
"_score" : 1.0,
"_source" : {
"pref" : "東京"
}
}
]
}
}

request

1
2
3
4
5
6
7
8
9
# queryの種類をtermにすることで完全一致検索
GET /search_job/_search
{
"query" : {
"term": {
"pref":"東京"
}
}
}

response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "search_job",
"_type" : "_doc",
"_id" : "jS17e3YB0KvFjllCp3-y",
"_score" : 0.2876821,
"_source" : {
"pref" : "東京"
}
}
]
}
}

request

1
2
3
4
5
6
7
8
9
# queryの種類をtermにすることで完全一致検索
GET /search_job/_search
{
"query" : {
"term": {
"pref":"東"
}
}
}

response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 該当するものは無い
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
}
}

indexを定義せずにデータ登録した場合

request

1
2
3
4
POST /search_job2/_doc
{
"pref" : "東京"
}

response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# typeでtextで登録されて、fieldsにtype:keywordでも登録されます
{
"search_job2" : {
"mappings" : {
"properties" : {
"pref" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}

request

1
2
3
4
5
6
7
8
9
# termの対象をpref.keywordとすることで、fieldsのtype:keywordを対象とすることができます
GET /search_job2/_search
{
"query" : {
"term" : {
"pref.keyword": "東京"
}
}
}

response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "search_job2",
"_type" : "_doc",
"_id" : "ki2Ne3YB0KvFjllCJX_C",
"_score" : 0.2876821,
"_source" : {
"pref" : "東京"
}
}
]
}
}

複数検索条件の完全一致

queryのboolmustで複数termを指定する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#request
PUT search_job3
{
"mappings" : {
"properties": {
"pref": {
"type": "keyword"
},
"employment": {
"type": "keyword"
}
}
}
}
#response
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "search_job3"
}
#request
GET search_job3/_mapping
#response
{
"search_job3" : {
"mappings" : {
"properties" : {
"employment" : {
"type" : "keyword"
},
"pref" : {
"type" : "keyword"
}
}
}
}
}
#request
POST /search_job3/_doc
{
"pref" : "東京",
"employment" : "full-time"
}
#response
{
"_index" : "search_job3",
"_type" : "_doc",
"_id" : "lS1OfXYB0KvFjllCZ3-D",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
#request
GET /search_job3/_search
#response
{
"took" : 484,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "search_job3",
"_type" : "_doc",
"_id" : "lS1OfXYB0KvFjllCZ3-D",
"_score" : 1.0,
"_source" : {
"pref" : "東京",
"employment" : "full-time"
}
}
]
}
}

#request
#複数検索条件の完全一致
GET /search_job3/_search
{
"query" : {
"bool": {
"must" : [
{
"term": {
"pref": "東京"
}
},
{
"term": {
"employment": "full-time"
}
}
]
}
}
}
#response
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.5753642,
"hits" : [
{
"_index" : "search_job3",
"_type" : "_doc",
"_id" : "lS1OfXYB0KvFjllCZ3-D",
"_score" : 0.5753642,
"_source" : {
"pref" : "東京",
"employment" : "full-time"
}
}
]
}
}

ネストした検索対象への完全一致検索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#  路線が駅を持つようなネストしたデータを作成します
# request
PUT search_job4
{
"mappings" : {
"properties": {
"pref": {
"type": "keyword"
},
"employment": {
"type": "keyword"
},
"line": {
"properties" : {
"name": {
"type": "keyword"
},
"stations" : {
"properties" : {
"name" : {
"type" : "keyword"
}
}
}
}
}
}
}
}

POST /search_job4/_doc
{
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}

GET /search_job4/_search

# response
# 路線が駅を持つようなネストしたデータが登録できました
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "search_job4",
"_type" : "_doc",
"_id" : "li1_fXYB0KvFjllCmn_A",
"_score" : 1.0,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}
}
]
}
}

# resquest
# ネストされている駅名に対して完全一致検索をします
GET /search_job4/_search
{
"query" : {
"term": {
"line.stations.name": "渋谷"
}
}
}

ネストした検索対象にネスト構造の経路の関係(Nested datatype)をもたせる

ネストした経路の関係(Nested datatype)を持たせたい場合にtypeにnested指定してmappingをします。

先程のmappingだと、何線の何駅かというのは検索できません。
下記のようなデータを追加した場合。
山手線の渋谷駅というデータを絞りこめません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# request
POST /search_job4/_doc
{
"pref" : "東京",
"employment" : "full-time",
"line" : [
{
"name" : "東横線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "中目黒"
},
{
"name" : "横浜"
}
]
},
{
"name" : "山手線",
"stations" : [
{
"name" : "東京"
},
{
"name" : "神田"
},
{
"name" : "浅草"
}
]
}
]
}

GET /search_job4/_search

# response
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "search_job4",
"_type" : "_doc",
"_id" : "li1_fXYB0KvFjllCmn_A",
"_score" : 1.0,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}
},
{
"_index" : "search_job4",
"_type" : "_doc",
"_id" : "mC2sfXYB0KvFjllCHn8N",
"_score" : 1.0,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : [
{
"name" : "東横線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "中目黒"
},
{
"name" : "横浜"
}
]
},
{
"name" : "山手線",
"stations" : [
{
"name" : "東京"
},
{
"name" : "神田"
},
{
"name" : "浅草"
}
]
}
]
}
}
]
}
}

# request
# 山手線の渋谷駅で検索
GET /search_job4/_search
{
"query" : {
"bool": {
"must" : [
{
"term": {
"line.name": "山手線"
}
},
{
"term": {
"line.stations.name": "渋谷"
}
}
]
}
}
}

# response
# 山手線の渋谷駅でないものも含まれてしまう。
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.47851416,
"hits" : [
{
"_index" : "search_job4",
"_type" : "_doc",
"_id" : "li1_fXYB0KvFjllCmn_A",
"_score" : 0.47851416,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}
},
{
"_index" : "search_job4",
"_type" : "_doc",
"_id" : "mC2sfXYB0KvFjllCHn8N",
"_score" : 0.47851416,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : [
{
"name" : "東横線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "中目黒"
},
{
"name" : "横浜"
}
]
},
{
"name" : "山手線",
"stations" : [
{
"name" : "東京"
},
{
"name" : "神田"
},
{
"name" : "浅草"
}
]
}
]
}
}
]
}
}

これは、追加したデータがElasticsearchの内部ではネスト構造を保たず、項目ごとにグルーピングされたような構造(Object datatype)で解釈される為になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"line" : {
"name" : [
"東横線",
"山手線"
],
"stations" : {
"name" : [
"渋谷",
"中目黒",
"横浜",
"東京",
"神田",
"浅草"
]
}
}
}

路線のtypenestedにしてネスト構造を保ったままmappingし、ネスト構造の経路を指定して検索してみる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# request
PUT search_job5
{
"mappings" : {
"properties": {
"pref": {
"type": "keyword"
},
"employment": {
"type": "keyword"
},
"line": {
"type" : "nested",
"properties" : {
"name": {
"type": "keyword"
},
"stations" : {
"properties" : {
"name" : {
"type" : "keyword"
}
}
}
}
}
}
}
}

GET search_job5/_mapping
# response
# line(路線)のtypeがnestedになっていることがわかる
{
"search_job5" : {
"mappings" : {
"properties" : {
"employment" : {
"type" : "keyword"
},
"line" : {
"type" : "nested",
"properties" : {
"name" : {
"type" : "keyword"
},
"stations" : {
"properties" : {
"name" : {
"type" : "keyword"
}
}
}
}
},
"pref" : {
"type" : "keyword"
}
}
}
}
}

# request
POST /search_job5/_doc
{
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}

POST /search_job5/_doc
{
"pref" : "東京",
"employment" : "full-time",
"line" : [
{
"name" : "東横線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "中目黒"
},
{
"name" : "横浜"
}
]
},
{
"name" : "山手線",
"stations" : [
{
"name" : "東京"
},
{
"name" : "神田"
},
{
"name" : "浅草"
}
]
}
]
}

GET /search_job5/_search
# response
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "search_job5",
"_type" : "_doc",
"_id" : "mS3wfnYB0KvFjllCcn9x",
"_score" : 1.0,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}
},
{
"_index" : "search_job5",
"_type" : "_doc",
"_id" : "mi3wfnYB0KvFjllCk3_f",
"_score" : 1.0,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : [
{
"name" : "東横線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "中目黒"
},
{
"name" : "横浜"
}
]
},
{
"name" : "山手線",
"stations" : [
{
"name" : "東京"
},
{
"name" : "神田"
},
{
"name" : "浅草"
}
]
}
]
}
}
]
}
}

# request
# nestedのpathにlineを指定し、line(路線)のネスト構造の経路内で山手線の渋谷を検索する
GET /search_job5/_search
{
"query": {
"nested": {
"path": "line",
"query" : {
"bool": {
"must" : [
{
"term": {
"line.name": "山手線"
}
},
{
"term": {
"line.stations.name": "渋谷"
}
}
]
}
}
}
}
}

# response
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.1162586,
"hits" : [
{
"_index" : "search_job5",
"_type" : "_doc",
"_id" : "mS3wfnYB0KvFjllCcn9x",
"_score" : 1.1162586,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}
}
]
}
}

参考url