node プロジェクトの docker image をつくってみる。
社内でAWS ECS を使うことになったが、そもそも dokcer image を作ったことがなかったので
調べてみた。
目次
- node プロジェクトの用意
- Dockerfile の作成と build
- image からコンテナ起動
- docker-compose 起動
1. node プロジェクトの用意
typescript & Express を利用した簡単な web サーバを用意する。 port 番号で 3000 で起動して、localhost:3000/api に HTTP GET リクエストするとslack に通知がくるものだ。
フォルダ構成は以下の通り
. ├── __test__ ├── jest.config.js ├── node_modules ├── nodemon.json ├── package-lock.json ├── package.json ├── src └── tsconfig.json
2. Dockerfile とコンテナイメージの作成
alpine という軽量 Linux ディストリビューションを利用する。
この上に nodejs と npm をインストールし、ローカルで開発した node プロジェクトを含めるようにする。そして、npm install
とnpm start
を起動させている。
touch Dockerfile
でカレントディレクトリにファイルを作成して次の通り記述する。
Dockerfile
FROM alpine:latest # instal llatest nodejs and npm RUN apk add --update nodejs nodejs-npm # make entry directory RUN mkdir myapp WORKDIR /myapp # copy localfile to images ADD src/ src/ ADD nodemon.json . ADD package.json . ADD tsconfig.json . RUN npm set progress=false && \ npm install CMD npm start
npm set progress=false
は本質的ではないので、なくてもよい。
これはnpm install
の進捗報告を出力しない設定で起動を速くしているために記述している。
Dockerfile があるディレクトリから以下を実行する
docker build -t tsweb .
-t オプションはイメージに名前をつける。tag の略称だ。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE tsweb latest 1a656139af79 6 seconds ago 32.9MB alpine latest 4d90542f0623 3 days ago 5.58MB
3. コンテナ起動
以下のコマンドを叩く。
docker run -d --name web -p 8000:3000 tsweb
curl localhost:8000/api
と叩けば、次の通りslack通知がくる。
4. docker-compose 起動
蛇足になるが、docker-compose を利用して同じようにコンテナを起動してみる。
docker-compse.yml
web: image: tsweb ports: - '8000:3000'
docker-compose up -d
でバックグラウンドでコンテナ起動。
最後に
ローカル環境で docker image を作成してコンテナ起動までの手順を紹介した。
今度は ECR に image を登録して、ECS で利用してみるところまで紹介したい。
参考にしたサイト
docker run -vとDockerfileのVOLUMEは違うのか?
はじめに
最近dockerについてプログラマのためのDocker教科書(以下教科書と呼ぶ)で勉強し始めた。
プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化
- 作者: 阿佐志保,山田祥寛
- 出版社/メーカー: 翔泳社
- 発売日: 2015/11/20
- メディア: 大型本
- この商品を含むブログ (3件) を見る
今回は、dockerを使ってデータの永続化という話でつまづいたときの整理をしたいと思う。
データの永続化とは、たとえばmysqlコンテナとデータコンテナを分けるということである。
たとえ前者を削除しても、データコンテナは別なのでデータを失うことはない。
別のバージョンのmysqlをデータコンテナにリンクさせることでポータビリティを保てるというメリットがあるわけだ。
教科書でデータ用のコンテナを作成するときに、次のDockerfileからイメージをビルドして作成している。
Dockerfile
# Dockerイメージの取得 FROM centos:latest ...(中略)... # ボリュームの作成 VOLUME /var/log/httpd
しかしここでいくつか気になる疑問がでてきたのだ。
- ファイル内のVOLUMEが指すものは、どこに存在するものなのか?(ホストのどこか?)
- Dokcerfileでイメージをビルドしないとデータ永続用コンテナは作成できないのか?
- docker runのvオプションとはVOLUMEは別物なのか?
この疑問に関して調べた結果を残そうと思う。 先に結論を述べると、vオプションでもVOLUMEも同じである。
docker run v
オプションについて
教科書には、vオプションについて次のように書かれてある。
docker run -v [ホストのディレクトリ]:[コンテナのディレクトリ]
ホストとコンテナでディレクトリを共有化する方法。
なるほど、この場合データの実態はホストに存在している。
では、次のように書くと何を意味するのか?
docker run -v /myvol -e MYSQL_ROOT_PASSWORD=mysql --name dbserver mysql
下図の通り確かにmyvolが作成されたコンテナに存在している。
が、それはどこに繋がれてる? ホストなのか?
実は、/myvolは、docker volumeに接続されている。
docker volume ls
と入力すると、dockerで作成されたボリュームができる。
ただし-v /myvol
とした場合、volume名がハッシュ化されてどの用途のボリュームなのかがわからなくなる。上図でも一つはmysqlコンテナ自体が使用しているボリュームで、もう一つは/myvolにマウントしているボリュームになるが、ハッシュ化されたVOLUME NAMEではわからない。
名前解決できない状況を回避するためには、ボリューム名を明示的に指定して作成し、それをマウントさせる。
docker volume create v1
docker run -v v1:/myvol -name dbserver mysql
v1という名前のボリュームの作成
作成したボリュームをマウントする
docker run -d --name dbserver -v v1:/myvol -e MYSQL_ROOT_PASSWORD=root mysql
docker volume ls
で確認
以上よりdocker run
のvオプションについてまとめると、
docker run -v [ホストのディレクトリ]:[コンテナのディレクトリ] mysql
ホストと共有化
docker run -v [dokcer volume]:[コンテナのディレクトリ] mysql
名前付きボリュームを指定ディレクトリにマウント
docker run -v [コンテナのディレクトリ] mysql
ハッシュ値のボリュームを指定したディレクトリにマウント
となり、最後の2つについては、起動コンテナと別のボリュームがマウントされ、名前付きかどうかが異なる
補足: rmオプションについて
docker rm
でコンテナを削除しても、vオプションで作られたボリュームは同時に削除されない。
コンテナが不要になったと同時に削除するには、rmオプションをつけて、docker run --rm -v v1:/myvol mysql
と入力する。
※それでもマウントしたv1ボリュームは削除されないことは注意
.DockerFile
のVOLUMEについて
教科書には、VOLUMEについてこう書かれている。
VOLUME ["/マウントポイント"]
指定したマウントポイントをコンテナ内に作成し、ホストやその他コンテナからボリュームの外部マウントを行います
よくわかりませんね。。。
なので、実際にDockerfileをつくってやってみましょう。
.DockerFile
FROM mysql VOLUME /myvol ENV MYSQL_ROOT_PASSWORD mysql
docker build -t dbesrver .
でイメージ作成して、
docker run -d -name db dbserver
でコンテナ起動
マウント状況を確認すると、
上図の通り、Dockerfileを利用した場合も新たにボリュームを作成して、VOLUMEで指定したディレクトリにマウントするという意味である。ボリュームに名前をつけていないので、起動したコンテナのボリュームの名前をハッシュ値となる。
(したがって、ボリュームが不要ならば--rm
オプションを付けた起動したほうがよい。)
結果
以上まとめて、はじめの疑問に回答すると
ファイル内のVOLUMEが指すものは、どこに存在するものなのか?(ホストのどこか?)
ホストに存在するが特定のディレクトリと共有してるわけではなく、
docker volumeとして作成された新たにボリュームを指しているDokcerfileでイメージをビルドしないとデータ永続用コンテナは作成できないのか?
docker run -v
からも作成可能docker runのvオプションとはVOLUMEは別物なのか?
同じである。 名前付きかどうかという違いがあるぐらいだ。
というわけでした。
参考サイト
詳しく書かれてあったのでわかりやすかったです。
https://qiita.com/namutaka/items/f6a574f75f0997a1bb1d https://qiita.com/mtgto/items/aabc212a1d3f99878828 https://qiita.com/gounx2/items/23b0dc8b8b95cc629f32
jsでオブジェクト配列をつかった集合演算
はじめに
jsで集合演算についてしらべてみた。
調べた限り集合演算の例は[1,2,3]と[2,3,4]の積集合をもとめるといったプリミティブ値の配列が多くて、オブジェクト配列をつかった
集合演算が見つからなかったので、残して見ようと思う。
問題(特定の本の返却期日をしらべる)
たとえば、ある人が本屋から複数の本をレンタルしてたときを考えてみる。
レンタルしている本は、id、名前とサイズともつ(A)
本屋はレンタルした本のidと返却期日を保管している(B)
レンタルしてる本からサイズが小さいものの返却期日を調べるにはどうすればよいか?
(A)の部分集合の返却期日を(B)から得たいというわけです。
これらをコードで記述すれば、
// (A) レンタルしてる本一覧 const books = [ { id: 1, name: 'book1', size: 'large' }, { id: 2, name: 'book2', size: 'small' }, { id: 3, name: 'book3', size: 'medium' }, { id: 4, name: 'book4', size: 'medium' }, { id: 5, name: 'book5', size: 'small' }, ]; // (B) レンタルしている本の返却期日一覧 const lends = [ { id: 1, end_on: '2019-01-01' }, { id: 2, end_on: '2019-02-01' }, { id: 3, end_on: '2019-01-11' }, { id: 4, end_on: '2019-01-22' }, { id: 5, end_on: '2019-01-11' }, ];
サイズがsmallのものは、idが2,5である。
これの返却期日をlendsから得ると次のとおりになる。
// もとめたいもの(サイズがsmall) [ { id: 2, end_on: '2019-02-01' }, { id: 5, end_on: '2019-01-11' }, ]
解決方法
books .filter(book => book.size === 'small') // (1) .map(target => lends.find(lend => lend.id === target.id)) // (2)
(1): booksから特定サイズの部分集合をえる
(2): (1)で得た部分集合のidと同じidをもつものをfindで取得してmapで配列を返す
さいごに
jsでオブジェクト配列から積集合?をもとめる書き方を紹介してみました。
使えるときがあるのかな笑
SQLで書くと、
select L.id, L.end_on from books B where size = 'small' left join lends L on B.id = L.id
を求めたいわけでした。
jsで集合演算というとSetという演算子がつかえるみたいですね。
Setをつかって今回の問題はとけるのかな...
flow-typedで型情報を管理しやすくしてみた
flowで型を導入後、毎回importするのが面倒だなと感じるようになってきた。
flow-typedをつかえば型情報をグローバルに定義できるようなので、これでimport文書く手間をカットしたり型情報をうまく管理できるのでは...
ということで今回は、flow-typedをつかって型情報を楽に管理できる方法について書き残そうと思う。
目次
- flow, eslintを使えるように準備
- flow-typedのインストール
- flow-typedディレクトリ作成と.flowconfigの記述
- 定義ファイルをつくって動作確認
- サードパーティライブラリの型定義を利用してみる
1. flow, eslintの準備
以前作成した記事通りに準備します。 本記事では、割愛させてもらいます。
2. flow-typedのインストール
npm i -D flow-typed
を実行する
3. flow-typedディレクトリと設定ファイル
下図のようにrootディレクトリにflow-typedというディレクトリを作成する。
このディレクトリに型定義ファイルを保存していく。
自分で作成したものやreduxなどのサードパーティのライブラリものが保存される。
. ├── .eslintrc.json ├── .flowconfig ├── flow-typed <- つくりました ├── node_modules ├── package-lock.json ├── package.json └── src
.flowconfig
[ignore] [include] [libs] flow-typed [lints] [options] [strict]
型定義ファイルを保存したディレクトリがどこか指定する。
上記のとおり[libs]に対して、先程作成したflow-typedディレクトリを記述する
4. 型定義ファイルをつくって動作確認
1. 直下にファイルを作成する
flow-typed/sampleType.js
type SampleType = { id: number, name: string };
src/sample.js
const obj: SampleType = { id: '21', <- (1) name: "SampleName" };
sample.jsファイル内にimport type {SampleType} from './path'
の記述はない
そして、(1) は数値ではないので、エラーとなる。
2. ディレクトリをきってもglobalにロードしてくれる
flow-typed/myModule/myType.js
declare type MyType = { uid: number, address: string, tel: number, };
これでimport文なしに型チェックしてくれる。
4. サードパーティライブラリの型定義を利用してみる
./node_module/.bin/flow-typed install redux@4
で公開されたreduxの型情報をダウンロードできる。
ファイルは、flow-typedディレクトリに作成される。
あとは、3.の同様に使用できる。
しかし、この場合global空間を汚染させないように作られるため、import文は必要となる。
さいごに
でもflowよりもtypescriptのほうが、人気なんだよね...
参考サイト
Prettierについて調査
最近エディタをsublimeからvscodeに変更しました。
その中でPrettierも適用することになったので、こいつのことを理解しようとおもうようになりました。
はじめはimport内のタブサイズが4になってたりと思うどおりに動かなくて断念かと思ったが、再インストールしてようやく想定したとおりに動いてくれたので、安心。
目次
- Prettierとはなにか?
- eslintと併用する方法
- なぜeslint --fixではダメなのか? わざわざprettierを利用する意味はなんだ?
- vscodeでprettierを利用する
まずは、npm scripts経由でprettierの挙動を確かめ、その後vscodeで設定する方法を紹介していきたい。
Prettierとはなにか?
Prettierは、ソースをいい感じに整形してくれるツールである。
linterと非常によく似たポジションだと思える。
これを使えばプロジェクト内のコーディングスタイルを統一的にできるわけだ。
自分であっても先週と今週の書き方が異なったりするのでその差をなくしてくれる。
eslintと併用する
早速Prettierをつかってみよう
1. インストール
何はともあれ、必要なものをインストール。
eslintのインストールと設定は、以前の記事をみてほしい。
npm i -D prettier npm install eslint-config-prettier eslint-plugin-prettier -D
2. eslintの設定にprettierの設定を追加する
.eslintrc.json
{ "extends": [ "airbnb", "plugin:prettier/recommended" // <- prettierを使うことを宣言 ], "rules": { "prettier/prettier": [ "always", // prettierのオプションをここに入れる { "singleQuote": true, "trailingComma": "es5", "semi": true, "bracketSpacing": true } ] } }
3. 動作確認
npm scriptsにprettierを登録して、npm run fmt
で実行してみる。
修正前
// flow const obj = { key1: 'singleQuote', // 問題なくシングルウォートを利用してる key2: "doubleQuote", // 誤ってダブルクォートを利用してる key3: "tabSize4", // タブのサイズが4と異なる key4: "notComma" // 最後に,がない }; const {key1, key2} = obj; // 中括弧内にスペースがない // 無駄な改行がある // 1行が長すぎる関数 function doSomething(argument1: Array<Object>, argument2: boolean, argument3: boolean): string { return 'result' // 最後にセミコロンなし }
修正後
// flow const obj = { key1: 'singleQuote', key2: 'doubleQuote', key3: 'tabSize4', key4: 'notComma', }; const { key1, key2 } = obj; function doSomething( argument1: Array<Object>, argument2: boolean, argument3: boolean ): string { return 'result'; }
以上のようにコード上に問題はないが、みやすさ的な保証を担保してくれるわけだ。
なぜeslint --fixではダメなのか? 何が違うのか?
How does it compare to ESLint/TSLint/stylelint, etc.?にあるように、
linterが適用するルールには2つのタイプがあって、
prettierはその一方を楽にするもの。
2タイプというのは、
1. Formatting rules(eg: max-len, keyword-spacing, ...)
2. Code-quality rules(eg: no-unused-vars, no-extra-bind, ...)
のことであり、prettierが扱うのは前者のほうで、
コードの品質的に問題はないが、チーム内でのスタイルルールが異なる問題を自動的に解決してくれるものといわけだ。
たとえば公式の説明をつかえば、次のコードはコード上問題ない。
foo(reallyLongArg(), omgSoManyParameters(), IShouldRefactorThis(), isThereSeriouslyAnotherOne());
だからlinterで修正はされないが、読みにくさというものはのこる。 これをprettierならば、次のように整形してくれるわけだ。
foo( reallyLongArg(), omgSoManyParameters(), IShouldRefactorThis(), isThereSeriouslyAnotherOne() );
vscodeのprettierの設定
1. 拡張機能のインストール
ext install esbenp.prettier-vscode
2. prettier設定反映
設定方法は、以下の通り複数存在するが、今回は3の設定を紹介する.
エディタに依存することなく、npm scriptsで確実に動作をするのを確認してから進められ、さらにeslintrcの設定をそのまま活かしたいからである。
3. 保存時にprettier適用
最後に、ファイル保存時にprettierが自動的に実行されるように設定。
settings.json
... "prettier.eslintIntegration": true, "[javascript]": { "editor.formatOnSave": true }, ...
以上で完了。あとはいつもどおりに書けばよい。 prettierのoptionについてはまた調べよう。
参考サイト
- Prettier 入門 ~ESLintとの違いを理解して併用する~
- 公式サイト
WebpackのdevServerを使ってもサーバと通信できるフロント開発がしたい
はじめに
自分はReactやReduxを使ってWebアプリを開発するときにWebpackのdevServerにお世話になる。devServerを使えば、ソース変更時に自動でブラウザをリロードしてくれて便利だからである。
しかし、devServerを使った場合、localhostで動かしてるwebサーバと通信できない。たとえば、devServerは、http://localhost:8080で起動する。
そこから、http://localhost:8000で動作するwebサーバとHTTP通信することは異なるオリジンにアクセスすることになってしまい通信は失敗する。
localhost:8080とlocalhost:8000は、異なるオリジンなので前者から後者に対して通信することは普通できないのである。
ローカルでwebサーバを動かす一方でフロント開発でdevServerを使ってるとき、フロントがサーバからデータ取得できるようにするにはどうすればよいのだろうかと壁にぶつかった。異なるオリジンであっても通信できるようにするにはどうすればよいのだろうかということである。
なので今回は、異なるオリジンであってもサーバと通信できる設定について備忘録を残そうと思う。
目次
- expressで簡単なwebサーバの準備
- 同一オリジンから通信できることの確認
- 別オリジンから通信の失敗確認
- expressのCORS設定
- SessionありCORS
1. expressで簡単なwebサーバの準備
nodejsのexpressを使用する。
expressでのwebサーバ構築方法は、割愛させていただきます。
npm i -S express
でexpressをインストール
次のapp.js
を用意する。
app.js
const express = require('express'); const app = express(); // "/"にアクセスしたときにres.jsonの引数値を返す app.get('/', (req, res) => { res.json({ result: 'success', message: 'hello express', }); }); // 8000ポートで起動 const server = app.listen(8000, () => { console.log(`Node.js is listening to PORT: ${server.address().port}`); });
あとは、node app.js
(あるいはnpm scriptsに記述してnpm start
)でサーバを起動させる。すると、http://localhost:8000にアクセスしたときにres.json()の引数に指定したjsonを受け取ることができる。
これでまずサーバサイドの実装は終了。
2. 同一オリジンから通信できることの確認
ブラウザを起動させ、urlにhttp://localhost:8000と入力すると、 先程のjsonが画面に表示される。
さらに、fetchメソッドも使って取得できることも確認しよう。
console画面に次を入力して取得できることを確認する。
fetch('/', { method: 'GET', headers: { 'content-type': 'appliaction/json' } }).then(r => r.json()).then(r => { console.log(r); }) // コンソール出力値 // {result: "success", message: "hello express"}
3. 別オリジンから通信の失敗確認
グーグルトップページ(https://wwww.google.co.jp)を開いて、同じくコンソール画面に次を入力する
// ドメインが違うので、URLを省略せずに書く fetch('http://localhost:8000', { method: 'GET', headers: { 'content-type': 'appliaction/json'} }).then(r => r.json()).then(r => { console.log(r); })
fetchの第一引数は、http://localhost:8000になる。
"/"は、https://www.google.co.jpを意味するからだ。
その結果は次のとおりである。
やはり、ドメインが異なるため通信失敗する。 エラー内容にある通り、HTTPリクエストのヘッダーに'Access-Control-Allow-Origin'が存在しないからダメだよと言われるている。(他にもヘッダーに足りない情報がある)
4. expressのCORS設定
異なるオリジンからの通信も許可するために、サーバサイドに変更を加える。
app.js
const express = require('express'); const app = express(); // ------------- ここから -------------- app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', "*"); // (1) res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); // (2) res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS'); // (3) next(); }); // ------------- ここまで -------------- app.get('/', function(req, res) { res.json({ result: 'success', message: 'hello express', }); }); const server = app.listen(8000, () => { console.log(`Node.js is listening to PORT: ${server.address().port}`); });
追加した内容については、
(1): どんなオリジンからの通信を許可("*"はすべてを意味する)
(2): 指定するheader情報の通信を許可
今回fetchメソッドでは、headersに'content-type': 'appliaction/json'を指定してるため
(3): GETからOPTIONSまでのHTTPメソッドを使って通信すること許可
ということになる。
これでもういちど、fetchメソッドを実行すると取得できるようになる(やったね)。
5. SessionありCORS(おまけ)
session情報をもたせるときは、もう少し改良が必要になる。 変更箇所のみ記述する
app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', req.headers.origin); // (1) res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS'); res.header('Access-Control-Allow-Credentials', true); // (2) next(); });
(1): "*"ではなく、リクエストからオリジン情報を取得し、そのからの通信を許可
(2): sessionを使うときはこれを追加してあげる
これでsessionありの通信も可能となるが、最後にフロントでのfetchメソッドも確認しておく。
fetch('http://localhost:8000', { method: 'GET', headers: { 'content-type': 'application/json' }, credentials: 'include', // (3) }).then(r => r.json()).then(r => { console.log(r); })
(3)のとおり、credentailをfetchメソッドのオプションに追加する。
最後に
これでサーバと通信できるフロント開発ができるように助けになってくれれば幸いである。非常に駆け足で説明してきたので、かなり読みにくい文章になってしまった。申し訳ない(あとでもう少し読みやすくしようと思います。)
参考サイト
http://var.blog.jp/archives/60055220.html https://qiita.com/MuuKojima/items/2b2e7bc0db8d5e97ada9 https://blog.kazu69.net/2017/03/23/http-request-using-cors/
Higher Order Componentについて調査
はじめに
Higher Order Componentについて学習。
atomic Designと呼ばれるものを実現するよい手法なのだろうと感じ、
使い方を少し学ぼうと思ったので、その記録を。。。
ゴール
- MyButtonを拡張して、MyCustomButtonコンポーネントをつくる。
ポイントは、styleをどうやって拡張させることができるのかということである。
MyButtonについて
MyButtonは、赤色の単純なボタンコンポーネントである。
MyButton.jsx
// @flow import * as React from 'react'; type PropsType = { children: React.Element<any>, style: Object, }; // デフォルトstyle情報 const defaultStyle = { backgroundColor: 'red', }; export default function MyButton(props: PropsType): React.Node { const { children, style } = props; // デフォルトstyleとpropsで指定されたstyle情報を結合 const Style = { ...defaultStyle, ...style, }; return ( <button type="button" style={Style} {...props} > {children} </button> ); }
defaultスタイルとpropsから渡ってきたsytleを結合するのがポイント。
MyCustomButton
MyCustomButtonは、MyButtonの特徴を受け継ぎながら独自のスタイルをもったボタン。 背景色が青色になり、フォントサイズや文字色のstyle情報が新たに追加される。
MyCustomButton.jsx
// @flow import * as React from 'react'; import MyButton from './MyButton'; // Enhance関数は、Componentを引数にとり、コンポーネントを返す関数を返す function Enhance(Component): (Object) => React.Node { return props => ( <Component {...props} style={{ backgroundColor: 'blue', fontSize: '50px', color: 'white', }} /> ); } // HOC const MyCustomButton = Enhance(MyButton); export default MyCustomButton;
Enhance関数のように、関数を返す関数は、高階関数(Higher Order function)と呼ばれる。
このHigher OrderであるEnhance関数によりできた新たなコンポーネントこそ
Higer Order Componentということになる。
childrenがpropsに存在するが、HTML要素にchildren属性はつかなく、またMyCustomButton内では、propsをそのまま伝達するだけでよいので感心する。
App.js
ReactDOM.render( <div> <MyButton>PUSH!!</MyButton> <MyCustomButton onClick={() => {console.log('CUSTOM')}} > CUSTOM </MyCustomButton> </div>, document.getElementById('root'), );
各ボタンの表示結果は以下の通り。 MyCustomButtonで追加されているonClickも問題なく動作する。
気になること
class属性が付与されると、どちらのスタイルが優先されるのだろうか? これについても調べていこう。