docker runでエラー 'No such file or directory'

さくらのDocker入門

Dockerの理解を深めるために、さくらのDocker入門を見ながら実際に手を動かしていきました。すると、早速理解できていないことに気づきました。

1
2
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:5.7
docker run --name some-wordpress -e WORDPRESS_DB_PASSWORD=my-secret-pw --link some-mysql:mysql -d -p 8080:80 wordpress

この部分です。docker run--linkオプションの意味を理解していなかったので、手元にあるDocker実践ガイド--linkオプションの説明を探しました。

linkオプションについて

mw0001というコンテナの起動で--link mariadb0001:db0001オプションを利用しており、その説明は以下のようになっていました。

  • これから起動するコンテナmw0001は、稼働中のコンテナmariadb0001とリンクを張る
  • コンテナmw0001において、mariadb0001コンテナの環境変数を取得することができる
  • db0001はエイリアス名である
  • mw0001が取得できるmariadb0001コンテナの環境変数名には、エイリアス名を大文字にした文字列が付与される

ということなので、エイリアス名を大文字にしたDB0001をprefixとした環境変数として、mariadb0001コンテナの環境変数を取得できるのだろうことはわかりました。

上記の説明の次のページになんのためにlinkオプションを利用するのかが記載されていました。

Column linkとは

Dockerにおけるlinkは、単一のDocker環境内で稼働する複数のコンテナ間において、コンテナの環境変数を他のコンテナで利用する機能です。
コンテナは、起動するたびにIPアドレスなどが変わるため、コンテナ間で通信を行う際に、環境変数に定義されたIPアドレスやポート番号を環境変数に定義し、その環境変数を別のコンテナで利用できるようにします。

なるほど、そういう目的なのですね。実際に試してみます。

コンテナmariadb0001の作成

まず、MariaDBのコンテナを作成します。本にしたがって行いますので、ベースイメージはcentos:7.5.1804となります。

yumリポジトリの追加

MariaDBの最新版を導入するため、リポジトリファイルを用意し、yum.mariadb.orgで提供されるリポジトリを使えるようにします。

1
2
3
4
5
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.4/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

書籍のbaseurlはアクセスできなかったので修正しました。

Dockerfileの作成

Dockerfileは書籍のものを記載(したつもりでした)。

1
2
3
4
5
6
7
8
9
FROM centos:7.5.1804
COPY MariaDB.repo /etc/yum.repos.d/MariaDB.repo
RUN yum update -y && yum install -y MariaDB-server MariaDB-client
COPY server.cnf /etc/my.cnf.d/
COPY mariadb.sh /mariadb.sh
RUN chmod +x /mariadb.sh
VOLUME ['/var/lib/mysql']
EXPOSE 3306
ENTRYPOINT ['/mariadb.sh']

Dockerfileに記載のENTRYPOINTのシェルを準備したり、MariaDBの設定ファイルの準備も行いました。

イメージのビルド

では早速imageをbuildします。Dockerfileがあるディレクトリに移動し、以下のコマンドを実行します。

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
$ docker image build -f Dockerfile -t centos:mariadb0001 .
Sending build context to Docker daemon 6.144kB
Step 1/9 : FROM centos:7.5.1804
---> cf49811e3cdb
Step 2/9 : COPY MariaDB.repo /etc/yum.repos.d/MariaDB.repo
---> a5c6607df1f1
Step 3/9 : RUN yum update -y && yum install -y MariaDB-server MariaDB-client
---> Running in 8b498184156d

(中略)

Complete!
Removing intermediate container 8b498184156d
---> 95b6c03d5190
Step 4/9 : COPY server.cnf /etc/my.cnf.d/
---> d6c1d2ece48a
Step 5/9 : COPY mariadb.sh /mariadb.sh
---> 16c33f1ca0ad
Step 6/9 : RUN chmod +x /mariadb.sh
---> Running in 292c931ba940
Removing intermediate container 292c931ba940
---> 42e1af28711d
Step 7/9 : VOLUME ["/var/lib/mysql"]
---> Running in 67b800c69838
Removing intermediate container 67b800c69838
---> 2a0b5d4f6f3d
Step 8/9 : EXPOSE 3306
---> Running in ec0143ebd8db
Removing intermediate container ec0143ebd8db
---> ee16c6003675
Step 9/9 : ENTRYPOINT ["/mariadb.sh"]
---> Running in 21382026dc44
Removing intermediate container 21382026dc44
---> 8b9c2c536dc1
Successfully built 8b9c2c536dc1
Successfully tagged centos:mariadb000

ということで、無事ビルドできました。

コンテナの起動

ビルドしたイメージからコンテナを起動します。

1
2
$ docker run -d -e DBNAME=testdb -e MARIADBUSER=root -e MARIADBPASSWORD=mysqlPassword -v /var/lib/mysql:/var/lib/mysql:rw --name mariadb0001 centos:mariadb0001
1ad8c112f237ed46c442f90373ff11d126753b930703f81caa6b3b8f6b6c2620

起動したコンテナのIDが返ってきました。

無事起動できていると思いきや

1
2
3
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1ad8c112f237 centos:mariadb0001 "/bin/sh -c ['/maria…" 3 seconds ago Exited (127) 3 seconds ago mariadb0001

Exited…止まってしまっています…

起動しない原因の調査

まずはログをみてみます。

1
2
$ docker logs 1ad8c112f237
/bin/sh: [/mariadb.sh]: No such file or directory

DockerfileでCOPYしているはずのファイルがないと言われています。そんなはずはないと思って、なんどか再ビルドしたりしてみましたが全く変化がありません。

検索して出てくる結果は、docker image buildのタイミングでNo such file or directoryエラーが出るというものばかりで、docker container runのタイミングでエラーになるケースは見当たりません。

エラーの原因

もう一度Dockerfileをよくみてみました。すると、書籍はファイルパスがダブルクォートで括られていましたが、自分のDockerfileはシングルクォートになっていました。

もしやと思ってダブルクォートに変更後、再ビルド→起動してみると、無事起動しました!

1
2
3
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
65b5af36d7ac centos:mariadb0001 "/mariadb.sh" 20 seconds ago Up 19 seconds 3306/tcp mariadb0001

ダブルクォートで記載しないといけない点に関しては公式ドキュメントにも記載がありました。
こちらの下の方に注釈があり、その中で記載されています。

1
2
exec 形式は JSON 配列として解釈されます。
したがって文字列をくくるのはダブルクォート(")であり、シングルクォート(')は用いてはなりません。

なるほど、JSONなのですね、JSONでは文字列を括るのはダブルクォートでないとエラーになってしまいます。そういうことなのですね。

まとめ

Dockerfileでのexec形式([]で引数を取るパターン)では、文字列をダブルクォートで括るということを忘れないようにします。

次回はちゃんとlinkのテストを行いたいと思います。

その他

この調査でよく使ったコマンド

  • docker container prune
    止まっているコンテナを一括削除

  • docker ps -a
    全てのコンテナの一覧表示

  • docker logs <コンテナID>
    コンテナのログを出力する。エラーが起こった時には真っ先に確認する

  • docker image rm <イメージ名>
    指定したイメージを削除する

単数形/複数形の変換がおかしいときの外部キー制約がエラーになる場合の対処法

外部キー制約の設定ができない

テーブルを作成するためにmigrationファイルを作成しdb:migrateしたら思いもよらないエラーが発生したので対処法を調査しました。

エラー内容

既存のテーブル(document_bases)があり、そのテーブルのidを外部キーに持つテーブル(document_views)を作成します。

migrationファイルを準備します

1
2
3
4
5
6
7
8
class CreateDocumentView < ActiveRecord::Migration
def change
create_table :document_views do |t|
t.references :document_base, index: true, foreign_key: { name: 'document_base_id_on_document_views' }
t.timestamps
end
end
end

db:migrateします。すると以下のようなエラーが表示されました。

1
ActiveRecord::StatementInvalid: Mysql2::Error: Key column 'document_basis_id' doesn't exist in table: ALTER TABLE `document_views` ADD CONSTRAINT `document_base_id_on_document_views` FOREIGN KEY (`document_basis_id`) REFERENCES `document_bases` (`id`)

原因調査

最初なぜエラーになるのか全くわからなかったのですが、よく見るとKey column 'document_basis_id'となっていて、存在するカラムdocument_base_idと異なっています。

調べてみるとbasesはbaseの複数形でもあり、basisの複数形でもあるようです。basesから単数形に戻すときにbaseではなくbasisを選択されてしまっているようです。

対応方法

t.referencesのオプションであるforeign_keyでなんとか指定できないかと思い、add_foreign_keyのオプションを調べてみました。

https://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/add_foreign_key

するとoptionsの説明の最初に答えが書いてありました。

1
2
:column
The foreign key column name on from_table. Defaults to to_table.singularize + "_id"

Defaults to to_table.singularize + "_id"と書かれているので、singularizeメソッドを試してみました。

1
2
[1] pry(main)> 'bases'.singularize
=> "basis"

ということでforeign_keyのオプションにcolumn: :document_base_idを指定することで無事migrateできました。

修正済migrationファイル

1
2
3
4
5
6
7
8
class CreateDocumentView < ActiveRecord::Migration
def change
create_table :document_views do |t|
t.references :document_base, index: true, foreign_key: { name: 'document_base_id_on_document_views', column: :document_base_id }
t.timestamps
end
end
end

まとめ

Railsの単数形/複数形変換で存在しないカラム名/テーブル名に変換されてしまった場合は明示的に名前を指定するようにする。

APIドキュメントをちゃんと読むようにする。

AmazonLinux2のDocker Imageにnginxをインストールする

ベースイメージの選択

最終目標として、AWS ECS(以下ECS)でRailsアプリケーションを動作させたいと思っています。ECSで利用するイメージのベースはなにがよいかわかっていないので、とりあえずAmazonLinux2を利用します。一旦テストのためにローカルでDockerfileを作成してみます。

AmazonLinux2のイメージはdockerhubにありました。
https://hub.docker.com/_/amazonlinux

tagはlatest2とすれば大丈夫のようです。

nginxのインストール

Dockerfileを記述していきます

1
2
3
FROM amazonlinux:latest
RUN yum update -y
RUN yum install -y nginx

docker image buildを実行します

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ docker image build -f Dockerfile -t al2_nginx:latest .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM amazonlinux:latest
---> 7f335821efb5
Step 2/3 : RUN yum update -y
---> Running in 1d974faaaefa
Loaded plugins: ovl, priorities
Resolving Dependencies
--> Running transaction check
---> Package glib2.x86_64 0:2.56.1-4.amzn2 will be updated
(中略)
FROM amazonlinux:latest

Complete!
Removing intermediate container 1d974faaaefa
---> b36e0bbfc4cf
Step 3/3 : RUN yum install -y nginx
---> Running in f244f466cb1b
Loaded plugins: ovl, priorities
No package nginx available.
Error: Nothing to do
The command '/bin/sh -c yum install -y nginx' returned a non-zero code: 1

nginxのパッケージがないようなので調べてみると、 amazon-linux-extras install nginx1.12でインストールが可能とのこと。Dockerfileを編集します。

1
2
3
FROM amazonlinux:latest
RUN yum update -y
RUN amazon-linux-extras install nginx1.12

そして、再度ビルドします

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ docker image build -f Dockerfile -t al2_nginx:latest .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM amazonlinux:latest
---> 7f335821efb5
Step 2/3 : RUN yum update -y
---> Using cache
---> b36e0bbfc4cf
Step 3/3 : RUN amazon-linux-extras install nginx1.12
---> Running in de67234984e9
NOTE: The livepatch extra is in public preview, not meant for production use

Topic nginx1.12 has end-of-support date of 2019-09-20
Loaded plugins: ovl, priorities
Cleaning repos: amzn2-core amzn2extra-nginx1.12
(中略)
* Extra topic has reached end of support.
Removing intermediate container de67234984e9
---> 97bc3952d1a8
Successfully built 97bc3952d1a8
Successfully tagged al2_nginx:latest

依存関係によって82ものパッケージがインストールされますが、無事nginxはインストールできたようです。

ただ気になるメッセージがありました。

1
2
3
NOTE: The livepatch extra is in public preview, not meant for production use

Topic nginx1.12 has end-of-support date of 2019-09-20

livepatch extraというのは本番用ではないということと、nginx1.12のサポートがすでにを終わっているということでした。

nginxのバージョン変更

nginxのバージョンをあげるにはどういう風に指定すればいいのかを調べてみると、以下のページが見つかりました。

https://dev.classmethod.jp/articles/al2-nginx1-new-extras-topic/

記事に書いてある内容ですと、nginx1と指定するとよさそうです。
再度Dockerfileを更新します。

1
2
3
FROM amazonlinux:latest
RUN yum update -y
RUN amazon-linux-extras install nginx1

再度buildします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ docker image build -f Dockerfile -t al2_nginx:latest .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM amazonlinux:latest
---> 7f335821efb5
Step 2/3 : RUN yum update -y
---> Using cache
---> b36e0bbfc4cf
Step 3/3 : RUN amazon-linux-extras install nginx1
---> Running in a7280e4e4c82
NOTE: The livepatch extra is in public preview, not meant for production use

Loaded plugins: ovl, priorities
Cleaning repos: amzn2-core amzn2extra-nginx1
6 metadata files removed
2 sqlite files removed
0 metadata files removed
Loaded plugins: ovl, priorities
Resolving Dependencies
--> Running transaction check
---> Package nginx.x86_64 1:1.16.1-1.amzn2.0.1 will be installed

今度は1.16.1がインストールできました(2020年6月現在)

まとめ

  • AmazonLinux2のDockerImageはDockerhubから取得できる。
  • AmazonLinux2にnginxをインストールする場合は、amazon-linux-extrasコマンドを利用する
  • トピックをnginx1とすることで1系の最新バージョンがインストールできる
  • extrasリポジトリで配布されるパッケージは、トピック変更が発生するため、定期的なOSパッチ更新などのタイミングで利用中のトピックのサポート状況を確認する。Dockerfileのメンテナンスを行う。

次にRubyのインストールとRailsアプリケーションのデプロイを行いたいと思います。

100人のプロが選んだソフトウェア開発の名著 後編

プロが選んだソフトウェア開発の名著 後編

前回の続きです。51以降も読みたい本がたくさんありました。

持っていてもう一度読みたい本

  • XPエクストリーム・プログラミング入門

    ケント・ベックの本。アジャイルの原点とも言えます。今読むと原点に帰って今の状況を俯瞰できるかもしれません。わたしが持っているのは古い方です。

  • 人月の神話

    ソフトウェア開発のプロジェクトを成功に導くために必要な原理原則が書かれています。

  • 達人プログラマー

    偉大なプログラマになるための指針が書かれています。忘れそうになったときに何度も繰り返し読みたい本です。

  • スーパーエンジニアへの道

    ちょっと手を出しづらいタイトルですが、ミックさんが紹介していて、無駄なページが1ページもないとおっしゃっているので是非読みたいです。この紹介文が書かれた時点で20年前の本ということは、30年前の本ということです。確認したら1991年初版発行でした。こちらもワインバーグさんの本です。

  • アジャイルプラクティス

    最近スプリントがうまく回せていないので、もう一度手法を見直したいです。

  • アジャイルな見積もりと計画作り

    アジャイルプラクティスと同様、スプリントがうまく回せていないので読みたいです。

  • 情熱プログラマー

    最近チケット作成など管理系の仕事が多くてうんざりしています。仕事が楽しくできてないので、初心を取り戻すべく読んでみたいと思います。

  • 達人に学ぶSQL徹底指南書

    ここで紹介されているのは第1版です。紹介されいている時代は第2版はまだ出てないです。わたしはこの本を読んでcase式についてしっかり理解してからSQLの幅がグッと広がったのを感じました。とても良い本です。いつでも再読したい本です。

  • エンタープライズアプリケーションアーキテクチャパターン

    ActiveRecordなど今あるアーキテクチャパターンはほとんどこの本で説明されています。オブジェクト指向に傾倒しているわたしとしてはマーチン・ファウラーさんの本は読まないわけにはいきません。

持っていないけど読みたい本

  • コンピュータアーキテクチャのエッセンス

    紹介している前田修吾さんは計算機科学の専門教育を受けずにプログラマになった。そのことで大変な思いをしたので、同じような人にこの本を薦めたいとのこと。自分も物理専攻で計算機科学の専門教育を受けておらずちょっとしたコンプレックスを感じることがあります。というか、情報系の授業を受けていた人たちが羨ましいです。

  • コンサルタントの秘密

    ワインバーグさんの本。紹介してくれている方もワインバーグさんの他の著書も読んでみてはどうか?と言っています。

  • ソフトウェア・テストの技法

    なかなかテストコードを書く習慣がつかない自分を戒めてくれるのではないかと期待しています。

まとめ

後編もボニュームのある本がそろっていました。比較的古い本が多いですが、紹介されてから10年経った今も色褪せてないことは間違いないでしょう。読み終わったら紹介します。

100人のプロが選んだソフトウェア開発の名著

プロが選んだソフトウェア開発の名著

本棚を整理していたら、100人のプロが選んだソフトウェア開発の名著が出てきました。本が好きなので、本を紹介する本は読みたくなってしまいます。2012年出版の本ですが、内容や紹介されている本はプロが選んでいるだけあって、長年読まれている名著ばかりで古い感じはありません。持っていてもう一度読みたい本、持ってないけど読みたい本をリストアップしたいと思います。

持っていてもう一度読みたい本

持っていないけど読みたい本

  • プログラマの数学 第2版

    本書で紹介されているのはこちらですが、第二版が出ているので第二版を買おうと思います。紹介してくれている方は数学に対する劣等感があるそうで、数学ガールを読んだけど理解できなかったとのことです。わたしと全く一緒だなと思いました。わたしは大学で物理を専攻していたのですが、数学が理解できずコンプレックスをなくすために数学研究会のサークルに入ったくらいです。数学が好きになれるよう読んでみたいと思います。

  • ジェネレーティブプログラミング

    広範囲な問題解決を行う場合、パッケージ、ライブラリを作ることが良い方法なのかなと思います。そのために読んでみたいと思います。

  • C++の設計と進化

    紹介文にあるように、いろんなプログラミング言語に影響を与えた言語の成り立ちを知ることはプログラミング言語を理解する上で有益だと思います。

まとめ

長くなってしまったので、100のうちの50までまとめました。51からの後半はまた後日記載したいと思います。

マイクロサービスにおける単一責任の原則

動機

前回の動機で書いたように、マイクロサービスアーキテクチャには凝集性はマイクロサービスを考える際に重要な概念であり、SRP(単一責任の原則)の定義によって認識が強化されたと記載されていました。その概念はマイクロサービスを設計していく上で重要になりそうなので、しっかりと理解したいと思います。

凝集性とは

凝集性の定義を確認します。意味はわかるのですが言葉にするのは難しいです。ウィキペディアによると、凝集度(ぎょうしゅうど、コヒージョン、cohesion)とは、情報工学においてモジュール内のソースコードが特定の機能を提供すべく如何に協調しているかを表す度合いである。とのことです。
ウィキペディアの高い凝集度に記載の説明、凝集度は、あるコードがどれだけそのクラスの責任分担に集中しているかを示す尺度である。オブジェクト指向プログラミングでは、クラスの凝集度を高めるようにそのクラスの責任範囲を設定することが有益とされている。凝集度の高いシステムでは、コードの読みやすさと再利用の容易さが増し、複雑さが管理可能な程度に抑えられる。がわかりやすいかなと思います。この説明で重要だと思う点はクラスの凝集度を高めるようにそのクラスの責任範囲を設定することが有益という点です。これは責務駆動設計であり、単一責任の原則に従うべきであるということです。責務駆動設計についてはオブジェクトデザインが参考になると思います。この本は10年くらい前、まだオブジェクト指向がしっかり理解できていなかった頃に読んで、とても参考になりました。みなさんもある処理を追加するときに、この処理はどのオブジェクトに行わせるのがよいのかを考えると思います。それが責務駆動設計です。

単一責任の原則について

単一責任の原則について、再度確認したいと思います。

アジャイルソフトウェア開発の奥義によると、

クラスを変更する理由は1つ以上存在してはならない

と記載されています。これだけを見るとどういうことかわかりづらいですし、しかもそんなこと可能なのか?と思ってしまいます。実際には、クラスを変更する理由はただ1つであるように設計するということです。なぜそうすべきなのでしょうか。

単一責任にすべき理由

アジャイルソフトウェア開発の奥義にはいくつか単一責任にすべき理由がかかれていますが、正直あまりわかりやすいものではないと思います。
わたしは、単一責任にすべき理由はただ1つで依存関係を減らすということだと思います。

これは静的言語で特にいえることだと思うのですが、変更したクラスに直接依存しているクラスは再コンパイルが必要です。クラスが複数の責務を持ち、いろんなクラスと依存関係を持っている場合、ある一つの変更で、依存関係を持っているクラス全てを再コンパイルしないといけません。再コンパイルしたプログラムはさらにデプロイを行わなければいけません。
これは静的言語だけではありません。わたしが業務で関わっているRailsでも複数の責務を持つクラスを変更すると、そのクラスに影響する全てのクラスのspecを実行しなければなりません。その数は少ない方が良いです。

変更する理由を1つにするということは、関係のないクラスの再コンパイル、デプロイが発生しないということだと思います。プログラマが知るべき97のことで、ロバート・C・マーティンは、良いシステムデザインとは、システムのコンポーネントがそれぞれ独立してデプロイできるようになっているデザインのことですと言っています。まさにその通りだと思います。
ただ依存関係を持たずに単独で仕事をするクラスはあまりありません。ということは、結局あるクラスを変更すると、依存関係を持っているクラスも再コンパイル、デプロイが発生するのではないか?という指摘があるかもしれません。その場合は依存関係逆転の原則を利用します。わたしがSOLID原則の中で一番好きな原則です。また別の機会で詳しくみていきたいと思います。

マイクロサービスにおける単一責任の原則の応用

では、マイクロサービスの設計において、単一責任の原則はどう考慮していくのが良いでしょうか。
先ほどデプロイの話が出てきました。あるサービスを変更したときに、依存関係を持っているサービスが複数あると、それらを同時にデプロイしなければいけません。それっは不可能ではないかもしれませんが難しいことだと思います。それを防ぐために単一責任の原則を守っていくべきだと思います。

しかしそれでもプログラムと同じような問題は発生します。サービス間のインターフェースなどを変更してしまうと、依存関係を持っているサービスも変更せざるを得ません。それを防ぐためには依存関係逆転の原則をサービス間に適用します。サービスは他のサービスに依存するのではなく、サービス間のインターフェースに依存するようにします。そうすれば、各サービスの変更を行っても他のサービスは変更を行わなずにすみます。

まとめ

単一責任の原則をマイクロサービスの設計にどう活用するかをみてきました。マイクロサービスは以下の二つのことを守れば、他のマイクロサービスの変更の影響を受けずにすむでしょう。

  • サービスの凝集度を上げ、責務をはっきりさせる。
  • サービス間はサービスに依存するのではなく、サービスのインターフェースに依存するようにする。

マイクロサービスを設計するときには気をつけていきたいと思います。

プログラマが知るべき97のこと 09. 他人よりまず自分を疑う

動機

マイクロサービスアーキテクチャを読んでいて(寄り道するからなかなか進まないですが)、凝集性はマイクロサービスを考える際に重要な概念であり、SRP(単一責任の原則)の定義によって認識が強化されたと記載されていました。
引用がプログラマが知るべき97のことだったので、懐かしいと思いパラパラめくっていたら、全く別のところに目がとまりました。09. 他人よりまず自分を疑うというエッセイです。

他人よりまず自分を疑う

エッセイの内容を要約すると次のような感じです。

  • プログラマは自分が書いたプログラムが思い通りに動作しないことがあると、なかなか自分を疑うことをせず、何か他の原因だと思う
  • しかし実際はだいたい自分が原因である
  • なので、他に原因があることを証明することよりも、自分のコードのバグを見つけることに時間とエネルギーを注いだ方がずっと良い
  • 自分の書いたコードを疑い、デバッグに関して一般に「すべき」と言われていることをやる
  • 自分のコードを徹底的に調べて、それでもバグが見つからない場合に、他の調査を開始する

なぜこのエッセイが気になったか

少し前、同僚から以下のような指摘を受けました。

「あなたの変更が原因で、バグが発生していました。修正しましたので、レビューお願いします。」

そんなはずはないのに…と思ったのですが、まずは修正してもらったコードをレビューしました。修正は私の変更とは関係ないところがされていて、しかもバグは残ったままのようでした。冷静に、「この修正では、バグはなくなっていないのではないですか?この部分を修正すべきだと思います。」とコメントで伝えました。

コメントの内容は伝わったようで、バグの原因であるコードを修正してくれたのは良いのですが、まだ納得がいかないようで、「あなたが修正する前は、正しく動作していました。」と言ってきます。修正した箇所をしっかり理解していれば、そんなことを言うはずがありません。「本当ですか?」と聞いてみたのですが、「本当です!」と強気な態度を崩しません。

結果的に、修正箇所だけではなく、その周りのコードの説明をすることで理解してもらえました。しかし、なぜこのような無礼なことを言ってくるのでしょうか。相手の精神状態などいろいろ原因はあると思いますが、プログラマ、エンジニアとして足りてないことは確かです。そしてチーム内に無駄な軋轢を生んでいます。

まとめ

他人よりまず自分を疑う

常に自分が間違っているかもしれないと思いながら、自分のコード、作業に細心の注意を払うことでバグをなくすことができます。また、バグをなくす過程で発生する不要な人間関係の悪化を防ぐことができます。そしてなにより、自分の失敗からよりたくさん学ぶことができます。そういった姿勢、マインドがプログラマ、エンジニアの成長する条件だと思います。

AWSの薄い本 IAMのマニアックな話 その3 IAMグループのデザインパターン

前回まで

前回はIAMポリシーとそのデザインパターンについて学びました。
今回はIAMグループのデザインパターンについて理解したいと思います。

IAMグループのデザインパターン

IAMグループのデザインパターンは大きく2つの方法があります。

  • 個々のIAMグループには、グループの責務にあったIAMポリシーのみを付与し、IAMユーザーは複数のIAMグループに所属する方法
  • 1つのIAMグループに必要な権限を全て付与し、IAMユーザーはそのIAMグループにのみ所属する方法

それぞれのメリット、デメリットをみていきましょう

複数のグループに所属するバターン

ユーザーが複数のグループに所属することを前提とします。

メリット

ポリシーが複数のグループから利用されることがほとんどない(ポリシー:グループ = 1: 1)ので、変更の際の影響範囲がわかりやすくなります。
ポリシーから見ると、グループは1つに特定されるように設計すべきだということです。

また、必須の権限を全ユーザーが所属するグループに付与することで、抜け漏れを防ぐことができます。

デメリット

ユーザーがどのグループに所属しているのか、どのグループに所属すべきなのかの管理が煩雑になりそうが気がしています。本来ポリシーを付与すべきでないユーザーに付与してしまう事故が起こりかねないと思います。

グループ内に複数のポリシー

ユーザーが1つのグループに所属することを前提とします。

メリット

ユーザーは基本的に1つのグループにしか所属しないため、ユーザーから見るとシンプルな構造になります。また、ポリシーも複数のグループから利用されることが前提になるため、自然とシンプルで使いやすいポリシー設計になります。

デメリット

権限を細かくわけたい場合は、そのユーザーのためのグループのような設計になってしまうかもしれません。1ユーザーあたり1グループ作成しないといけなくなる可能性もあり、グループの数が増えて管理が煩雑になるかもしれません。

まとめ

IAMグループのデザインパターンとして、複数グループに所属するパターンと、グループ内に複数ポリシーを配置するパターンの2つを見てきました。
筆者はこの2つの方法に、機能的な優劣はないので、どちらを選択するかは好みの問題であると言っています。しかし、筆者はグループ内に複数ポリシーを配置するデザインパターンを利用することが多いとのことです。特に好みがなければグループ内に複数ポリシーを配置するパターンを利用するのがよいのではないでしょうか。

わたしは、この2つのデザインパターンの違いは責務(権限)をどこに凝縮するかによるのかなと思います。ユーザーが複数のグループに所属するパターンだと、グループが責務の単位となり、グループがそれぞれ疎になると思います。設計としてはこちらの方がきれいかもしれません。グループ内に複数のポリシーをもつパターンだと、ポリシーが疎になります。グループ自体はポリシーを複数持っており重複する部分もあるため、設計はそれほどきれいではないかもしれませんが、ユーザーから見た時にとてもシンプルになると思います。

一旦はグループ内に複数のポリシーを持つパターンで設計を考えてみるという指針にしようと思います。

次回はIAMのセキュリティーについて読んでいきたいと思います。

クリーンアーキテクチャ

動機

前回、ヘキサゴナルアーキテクチャについて調べました。日本語訳で、似たようなアーキテクチャであると紹介されていたクリーンアーキテクチャを知ることでより深くヘキサゴナルアーキテクチャを知ろうと思います。

書籍 Clean Architectureとブログの内容について

原文日本語訳(はもちろん一緒)と書籍でクリーンアーキテクチャに言及している章をみてみましたが、内容はすべて一緒でした。なので、ここからは書籍の22章をもとに内容を理解していきたいと思います。

クリーンアーキテクチャ

アーキテクチャの特徴

ヘキサゴナルアーキテクチャの紹介のなかで、実践テスト駆動開発で採用したものと記載がありました。この書籍の中では、17章のMainクラスを分解するで、Mainクラスからさまざまな責務を抽出した結果、「ポートとアダプタ」アーキテクチャになったという形で利用されていました。こちらの書籍についてはまた別の機会で詳しくみていきます。

いろんな類似のアーキテクチャがありますが、共通する点は「関心ごとの分離」という目的を持っている点です。外部との依存関係がなくなるため、外部の環境(UI,DBなど)は交換可能になります。

クリーンアーキテクチャ

この図はブログでも紹介されています。正直言って、六角形が円になっただけのような気がしなくもないです。層がすこし増えているかなというのと、ビジネスロジックがEnterprise Business RulesApplication Business Rulesにわけられているところが変わったかなというくらいです。

このアーキテクチャを動作させるもっとも重要なルールは、依存性のルールであると言っています。そのルールは

ソースコードの依存性は、内側(上位レベルの方針)だけにむかっていなければいけない。

ということです。外部に依存してはいけないということです。次にこのアーキテクチャを構成する各要素を書籍を引用して紹介します。

エンティティ

エンティティは最重要ビジネスルールをカプセル化したもの。

ユースケース

ユースケースはアプリケーション固有のビジネスルールが含まれている。ユースケースは、エンティティに入出力するデータの流れを調整し、ユースケースの目標を達成できるように、エンティティに最重要ビジネスルールを使用するように指示を出す。

インターフェースアダブター

インターフェースアダプターはユースケースやエンティティに便利なフォーマットから、データベースやウェブなどの外部エージェントに便利なフォーマットにデータを変換するアダプター。

この層のインターフェースに依存するように実装することで、外部エージェントを交換可能にしているのだと思います。

フレームワークとドライバ

図のもっとも外側の縁は、フレームワークやツールで構成されています。たとえば、データベースやウェブフレームワークなどです。フレームワークとドライバの層には詳細が詰まっています。ウェブも詳細、データベースも詳細。被害が抑えられるようにこれらは外側に置いておきます。

境界を越える

図の右下に、円の境界線をどのように越えるべきかの例が記載されています。コントローラーからプレゼンターへの制御の流れがあるが、ユースケースを経由しています。この際に、依存関係逆転の法則を用いてコントローラー、プレゼンター共通のインターフェースを提供します。
制御の流れは外側から内側であるが、ソースコードの依存関係は内側から外側へ発生します。

まとめ

こうした単純なルールに従うのは、それほど難しいことではなさそうです。ルールを守っていればいずれ多くの苦痛から解放してくれると言っています。

全体のまとめ

クリーンアーキテクチャも関心ごとの分離が目的のアーキテクチャです。外部を交換可能にするためには依存関係を守ること、しっかりと関心ごとを分離することが重要です。ここでもオブジェクト指向の基本原則であるSOLIDが重要になってきます。特にD(依存関係逆転の法則)は重要だと思いました。

マイクロサービスにおけるサービスはオブジェクト指向におけるオブジェクトと同義だと思います。責務を明確にし、インターフェースを正しく定義し利用しやすいマイクロサービスを設計したいものです。