Clean Architecture を読んだ感想

Clean Architecture を読んだ感想というかまとめというか、備忘も兼ねて書きたいことをテキトーにメモします。

Clean Architecture 達人に学ぶソフトウェアの構造と設計

Clean Architecture 達人に学ぶソフトウェアの構造と設計

全体的に、Clean Architecture という設計手法の具体的な How to が書かれているという感じではなく、ソフトウェアを設計する際に大事にするべき考え方の部分を軸に書かれているなあという印象でした。ソフトウェア設計の原則や考え方がざーっと序盤に書かれていて、それらを大事にするとこういうアーキテクチャがいいよね、という流れで Clean Architecture という設計が登場する流れです。ソフトウェア開発って、手法の優劣というか、主張の妥当性を示す決定的な根拠を出すのは難しいと思うのですが、その中でも、アーキテクチャが大事なんだという筆者の主張はそれなりに納得感のある形で伝わってきた気がします。

そんな感じで、優れた設計方法を知るというよりは、優れた設計の背景にある考え方を知るという感覚で読めました。あまりプログラミングや設計に慣れていない自分のような人間にもわかりやすく書かれていて、汎用的な知識として身についたものが結構あったと思います。オブジェクト指向や設計に深い理解がある人にとっては情報量が少ないかもしれないなーという気がしなくもないですが。

設計を題材にした本なので、優れた設計とはこういうものだという話を色々しているのですが、まとめると「変更に強い設計」を優れた設計と考えていいのではないかと思いました。逆に変更に対しての弱さはどこから生まれてくるのかと考えると、おそらく多くの場合で不適切な依存関係からではないかと思います。すなわち、システムやプログラムが疎結合な状態や依存関係がキレイな状態は、変更に強い状態と言えるのではないでしょうか。

で、プログラムをそのような状態にするために、オブジェクト指向プログラミングではポリモーフィズムが活躍するという話が沢山書いてあります。ポリモーフィズムを活用することで詳細な実装を隠し、アダプティブというかプラガブルな設計を作り出すことができるからです。依存関係逆転の原則によれば、ポリモーフィズムを使えば依存関係を自由自在にコントロールできるとも言えます。筆者はこれをオブジェクト指向の最たる強みであるとしています。変更への弱さが不適切な依存関係から生まれるのだとすれば、依存関係をコントロールできれば変更への強さを意図的に作れるからです。

オブジェクト指向についてまだまだ理解が浅い自分にとっては、その辺の考え方も整理された感があります。有名なソフトウェア設計の原則に、この依存関係逆転の原則や、オープンクローズドの原則といったものがありますが、改めてこれらにオブジェクト指向のエッセンスが垣間見えた気がします。「オブジェクト指向プログラミングとは何か」と聞かれたら「抽象でプログラミングする手法」とテキトーに答えていましたが、その点では筆者と考え方自体は近い気がしたので少し安心した感もありました。ちなみにオブジェクト指向以前のパラダイムの言語でも、一応ポリモーフィズム的なことは実現できたらしいのですが、関数ポインタを使うため危険性が高く、実用的ではなかったとのことです。

ただ、抽象化にはコストが伴うということは多くの人が経験的に知っていると思います。過激に抽象化して変更に強い状態を作るのは手間のかかる作業だし、逆に構造を把握しづらくなったりもします(YAGNI なんて言葉が生まれるくらいですから)。設計なんて考えずに、動けばいいやで書いたほうがサクッと作れたりするものです。しかしこの方法では機能変更、追加時に高く付きます。すなわち、将来のために投資するか否かという選択がここにあるわけです。投資すべきかどうかを絶対的に評価/判断する方法があるわけでもなさそうなので、この辺りはアーキテクトの腕の見せどころといったところなのでしょう。適切な意思決定には経験値はもちろん、業務知識なんかも必要になりそうです。とまあそんな感じで、YAGNI や DRY なんかと相反するように見える「優れた設計」についても筆者の意見が書かれていて、なるほどなーという感じでした。

あと個人的に面白いなと思ったのは、デキるアーキテクトは詳細(使うDBとかの具体的な話)の決定をできるだけ遅らせるようにデザインする的な話で、あまりできていなかったことだなーと。決定を遅らせるのが大事というよりは、決定を遅らせても大丈夫なような状態にしておくのが大事ということでしょう。Web サービスを作るときなんかは、開発初期段階でサーバや DB のことをかなり意識してしまっていたので、詳細よりもサービスの本当の価値となるビジネスロジック部分の方にもっとフォーカスしていくべきだと思いました。中核にあるビジネスロジックを捉えることは DDD に限らず大事な気がしますし。Web サービスを作る際に大前提として考えてしまいがちな 「Web で配信する」ということすら意識する必要がないという主張にはハッとした感がありました。

Clean Architecture を導入したプロジェクトはどのようなディレクトリ構成をとるべきなのかとか、ユースケースが実際にはどのように表現されるのかとか、Clean Architecture の具体的な部分はまだ理解していないことだらけなので、テスト的に実践してみようかなーと思います。ただ、こういうシッカリしたアーキテクチャってそれなりの規模のプロジェクトじゃないと有り難さがわからなそうな感はありますが...。

総括として、個人的な本書の位置づけは、ある程度オブジェクト指向を理解できてきたら次のステップとして読みたい本、という感じでした。同列のステップとしてデザインパターンの本などもありだと思いますが、あっちはなんというか具体例の裏にあるエッセンスを自分で掴んでいかないと、汎用的な知識としては得られるものが少ない気がしたので。本書は設計の「考え方」の部分が大きなウェイトを占めていたので、Clean Architecture というアーキテクチャ自体に興味がなくてもなにかしら気づきがありそうな印象を受けました。おわり。

【クラスローダ】JVMが読み込むクラスを見つける仕組み【パッケージ】

NoClassDefFoundError や ClassNotFoundException に遭遇した時など,Java で書くならある程度クラスローディングについて知っておいたほうがいいかなあと思い調べたときのメモ.なんとなくの概要.

クラスローダ

基本的な仕組み

Java アプリケーションは基本的に,複数のファイルに分かれた沢山のクラスによって成り立っています.アプリケーションを動かすためには,これらのクラスを JVM が読み込まなければなりません.Java では JVM のクラスローダと呼ばれる機構がこの処理を担います.クラスローダは,読み込むべきクラスを判断し,ストレージから当該クラスファイルを探し出し,ファイルを開いてクラスをロードします.

クラスロードの戦略は,オンデマンドな感じになっています.アプリケーションの開始時に必要な全てのクラスを読み込むのではなく,実行中に必要になったタイミングで必要になったクラスのロード(検索)をします.

クラスローダは担当検索範囲ごとに複数存在していて,それらが階層構造(親子関係)をとっています.それぞれのクラスローダがやることは同じなのですが,クラスを検索する場所がそれぞれ異なります.あるクラスローダはどこどこのパスからクラスをロードするのを担当して,あるクラスローダは別のパスからロードするのを担当するという感じです.

プログラム実行中に実際にロードを担当する(起点となる)クラスローダは,実行中のクラスをロードしたクラスローダです.ですが,基本的には,親の担当範囲で見つけられるクラスなら親にロードしてもらう,親がロードできなければ子がロードするという風に,上位のクラスローダから順に特定のクラスのロードを試みていきます.この仕組みは委譲(delegate)と呼ばれます.

また,クラスローダは基本的に子にはロードを委譲しません.これは結構重要なポイントです.例えば,クラスローダAの検索範囲にクラスaがあり,その子クラスローダであるクラスローダBの検索範囲にクラスbがあるとします。この場合,クラスaを実行中にクラスのロードの起点になるのはクラスローダAです.すなわち,クラスa実行中には,クラスローダBがロードを担当する範囲にあるクラスbをロード出来ないということを意味します.起点となったクラスローダより階層的に下位に存在するクラスローダは使われないのです。(が,このやり方が問題になるケースも存在するということで,委譲の順番をあえて変更しているサーバ等も一応存在する模様).

このように,ロードに優先順位というか,方向性をもたせることで,複数のクラスローダが同じクラスを重複してロードしたりして競合が発生することを防げます.このような仕組みになっているので,とあるクラスが,起点となるクラスローダの下位クラスローダ配下にしか存在しないクラスに依存する,という設計はよろしくないでしょう.

また,クラスローダは階層構造の横方向にも委譲しません.すなわち,兄弟関係にあるクラスローダ同士はお互いに影響し合わない仕組みになっています.こうすることで,単一のサーバにデプロイされる複数のアプリケーションごとに兄弟関係になるクラスローダを作ることで,それぞれのアプリケーションでバージョンが違うクラスを読み込んだりできます.

基本となるクラスローダ

基本的なクラスローダは,ブートストラップクラスローダ,拡張クラスローダ,システムクラスローダの3つです.これらは挙げた順に親子関係になっています.

ブートストラップクラスローダは JVM そのものの実行に必要になるクラスをロードする基礎的なクラスローダです.JDK のコア API に関するクラスなどがこのクラスローダにロードされます(java.lang.* など.<JAVA_HOME>/lib にあるような jar とかですね).このクラスローダは基本的には JVM 内にネイティブ実装されています.そのおかげで,そもそも最上位のクラスローダは誰がロードするのか問題(パラドックス)を解決できています.

拡張クラスローダは,拡張用ディレクトリ(一般的には <JAVA_HOME>/lib/ext)にあるクラスをロードします.なので,クラスパスを変更することなく基礎的な機能を Java に追加したい場合には,このディレクトリに追加してあげれば簡単に実現できます.

システムクラスローダは,クラスパス(環境変数)で指定している場所のクラスのロードを担当します.普段プログラムを動かすためにクラスパスを追加することがあると思いますが,そのパスのクラスはこいつが読み込んでくれています.また,このシステムクラスローダを親として独自のクラスローダを作成することも可能です(ユーザ定義クラスローダ).ユーザ定義クラスローダを作ることで,アプリケーション単位でクラスローダを割り当て,ライブラリのロード方法を変えたりできます.

ブートストラップクラスローダはネイティブ実装されているのですが,その他のクラスローダは Java オブジェクトとして存在するので,プログラム中でそのインスタンスを取得したりなんてことも簡単にできます.

クラスローディングの流れの大枠

まず,JVM がプログラムを実行中にクラスのロードが必要だと判断すると,クラスローダに処理を依頼します.クラスローダはロードすべきクラスの完全修飾名を基に処理を始めます.完全修飾名とは,com.example.Hoge のようにクラス名とパッケージ名を繋げたものです.クラスローダは自分がどの場所からクラスを探せばよいかは知っていますが,その場所のどのクラスをロードすればよいかは知らないので,この完全修飾名を与えてロードすべきクラスを決定します.

例えば,/usr/local/lib を探すクラスローダがあったとして,JVM がそれに com.example.Hoge クラスをロードしてもらう指示を出した場合,完全修飾名がパスに変換され /usr/local/lib/com/example/Hoge.class をロードしようとします.この際,クラスが見つからない場合には ClassNotFound の例外が投げられることになります.

言い換えると,読み込まれるクラスは,パッケージ名とクラス名,そしてクラスローダの3つによって決定されるということです.なので,完全修飾名が全く同じクラスが別の場所に複数存在していた場合,処理をするクラスローダによって読み込まれるクラスが変わります.また,同名の完全修飾名をもったクラスが複数のクラスローダから検索され得る場合には,一番最初に発見されたクラスがロードされる事になっています.

クラスが無事発見されると,そのクラスが正しい構造になっているかどうかを検証したり,static フィールドを初期化したり,static イニシャライザを実行したりして,クラスを使える状態にします.

おわり

このように Java ではクラスローダという機構がクラスロードを担当しています.他の言語でのクラスロードの仕組みがどうなっているのか少し気になったので,また暇な時に調べて記事にしようかなーと思います.おわり.

参考

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.3
https://www.ibm.com/developerworks/jp/websphere/library/java/j2ee_classloader/1.html

Apache2.4 を event MPM + FastCGI に変更して省メモリする

ApacheのMPMとして、プロセスベースの並列実行をする prefork を使用していたのですが、省メモリのためにスレッドベースの並列実行をする event へ変更しました。構成としては、Apache2.4 + event + mod_proxy_fcgi + php-fpm です。

それぞれの MPM の特徴に関しては、以前書いたこちらの記事で簡単に紹介しています。 norikone.hatenablog.com

動きを確認するためにそれぞれデフォルトの設定で、PHPプログラムが動いているサーバに簡単な負荷テストを行ってみたところ、プロセスを多数起動しない分やはり event の方が使用メモリ量が少ないという結果が出ました。スループットも若干 event が prefork を上回る結果になりました。

ただ、速度に関しては配信するコンテンツの内容やリクエスト数によって prefork が上回るケースも十分考えられるので、環境ごとに検討する必要があるかと思います。

event で動かすために必要なものと背景

prefork を選択している場合には、ApachePHP の連携の定番である mod_php を使用することができました。 しかし、event を選択した場合には PHP の実行を mod_php に任せることができません。というのも、mod_php がスレッドセーフではないためです。

prefork では、Apache がリクエストごとにプロセスを生成するため、メモリ空間上で競合が生じません。event では、リクエストごとにスレッドを生成して処理するので、メモリ空間を共有していまい競合が生じてしまいます。そのため、スレッドセーフな環境で実行してあげる必要があります。

そこで、 PHP 処理は php-fpm に任せることにします。mod_php の場合、Apache が生成する各 httpd プロセスに PHPインタプリタを埋め込んでそのプロセス内で PHP を処理していたので結びつきが強かったのですが、php-fpm の場合 Apache とは分離された存在になっています。その辺りはこちらの図が分かりやすいです。
  f:id:norikone:20160207044201p:plain  
f:id:norikone:20160207044159p:plain (参照 : http://z-issue.com/wp/apache-2-4-the-event-mpm-php-via-mod_proxy_fcgi-and-php-fpm-with-vhosts/)

一枚目が prefork + mod_php 構成の図で、二枚目が event + php-fpm + mod_proxy_fcgi 構成の図です。二枚目は、スレッドがリクエストを受け付けて、mod_proxy_fcgi というモジュールを介して php-fpm に PHP の実行を依頼する、という流れです。 静的ファイル等は Apache が、PHPphp-fpm が、という形になります。


ということで前置きが長くなりましたが、php-fpm と mod_proxy_fcgi があれば event で動かすことが出来るようになるので、設定していきます。環境は Amazon Linux AMI , Apache2.4 です。

php-fpm インストール

まずは PHP プログラムを処理するための php-fpm をインストールします。

yum install php56-fpm

起動します。

service php-fpm start

自動起動をONにします。

chkconfig php-fpm on

これで php-fpm の準備はできました。特に設定を変更していなければ、9000番ポートでリクエストを待ち受けるようになります。

mod_proxy_fcgi を有効にする

Apache が起動したスレッドと php-fpm の仲介をするモジュールの設定をします。mod_proxy_fcgi を有効にすることで、ApacheFastCGI プロトコルを使用して外部の PHP 処理系にリクエストを渡すことが出来るようになります。また、mod_proxy_fcgi が動作するためには、mod_proxy が必要です。

恐らくデフォルトで有効になっていますが、/etc/httpd/conf.modules.d/00-proxy.conf の以下の行がコメントアウトされていないか念のため確認しておきます。

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so

次に、仮想ホストファイルなどを編集してプロキシの設定をします。以下の行を追記するだけです。

<FilesMatch \.php$>
    SetHandler "proxy:fcgi://127.0.0.1:9000/"
</FilesMatch>

例として、こんな感じになります。

<VirtualHost *:80>
    DocumentRoot "/var/www/html/public"
    ServerName hoge.com
    <Directory "/var/www/html/public">
        <FilesMatch \.php$>
            SetHandler "proxy:fcgi://127.0.0.1:9000/"
        </FilesMatch>
    </Directory>
</VirtualHost>

これで、mod_proxy_fcgi を通して php ファイルの実行を php-fpm に委任する設定が完了しました。

MPM を event に切り替える

event の動作に必要な環境は整いました。実際に MPM を切り替えてみます。

/etc/httpd/conf.modules.d/00-mpm.conf で prefork をロードしている箇所を event をロードするよう修正します。Apache2.3 からは MPM は LoadModule ディレクティブで動的に選択することが可能になっています。

# LoadModule mpm_prefork_module modules/mod_mpm_prefork.so #コメントアウト
~
LoadModule mpm_event_module modules/mod_mpm_event.so #コメントアウト削除

これだけで event へ切り替えることが出来ます。あとは、/etc/httpd/conf.modules/01-cgi.conf 等をいじって使用しないモジュールをロードしないようにしておきましょう。

確認

httpd -M

で、 mpm_event_module (shared) が表示されていれば event が選択された状態になっています。

また、phpinfo で FastCGI で動作しているか確認できます。
f:id:norikone:20160207213201p:plain prefork + mod_php で動作している場合は、ここに「Apache handler 2.0」と表示されるので、変更できていることが分かります。

Apache MPMとはなんぞやという話

Apache のチューニングにあたって MPM について知ったときのメモ、なんとなくの概要。
Apache MPM is 何か、ざっくりと。

Webサーバの実装モデルの話

MPMの話に入る前に、Webサーバの基本的な並行処理のモデルをおさえておきます。

Webサーバに接続するクライアントが1人だけであれば、並行処理について考えることは少ないでしょう。しかし、多くの場合は同時に複数人のクライアントに対応しなければなりません。 そういった状況でリクエストを並行処理するためのWebサーバの実装モデルがいくつかありまして、代表的なものを簡単におさらいします。

マルチプロセスモデル

クライアントからのリクエストごとに fork をして子プロセスを生成し、その子プロセスに処理を委ねる方式です。

プロセスの fork では、メモリ上の親プロセスのアドレス空間を、生成した子プロセスのアドレス空間にコピーします。したがって、その分のコストが発生し、低速と言われています。また、リクエストが増えれば増えるほど、子プロセスの数とそれに伴うメモリ消費量も増えてしまいます(すべての子プロセスがPHPインタプリタおよび関連ライブラリをロードする)。

この方式の利点として、メモリ空間がプロセスごとに独立しているためスクリプト言語などを組み込みやすい、後述のマルチスレッドモデルと違い資源の競合について考慮しなくてよい、などが挙げられます。

マルチスレッドモデル

クライアントからのリクエストごとにスレッドを生成する方式と、あらかじめスレッドを生成しておくモデルがあります。

マルチプロセスモデルとは違い、プロセスではなくスレッドを使用します。このため、プロセスの fork の際に発生するコピー作業が発生しません(各スレッドはメモリ空間を共有する)。このため、プロセスの生成よりもオーバヘッドが小さいと言われています(メモリ消費量についても同様)。

また、この方式ではメモリ空間を共有するので、コンテキストスイッチの際に発生するメモリ空間の切り替えや、それに伴うキャッシュの削除を省略できるというメリットもあります。メモリ空間を共有することのデメリットとして、スレッド間での資源の競合を考慮したプログラムを書く必要があり、実装が難しくなりコードも複雑なものになりやすい、などが挙げられます。

イベント駆動モデル

1つのプロセスで複数のリクエストを処理する方式です。

上記2つのモデルでは、クライアントからの要求を受けてレスポンスを返すという一連の流れに対してを1つのプロセス or スレッドが割り当てることで、それぞれのリクエストに対応していました。 イベント駆動モデルでは、リクエスト数に関係なくイベント発生のタイミングで処理を切り替え、1つのプロセスがすべてのリクエストを処理します。

プロセスが1つしか無いということは、CPUコアを1つしか活用できないことを意味します。Nginx などでは、イベント駆動のプロセスをCPUコアそれぞれに起動しておくなどして、この欠点に対応しているみたいです。

このモデルは、リクエスト数が増えてもプロセスやスレッドの数が増えることがないので、メモリ消費量やコンテキスト切り替えのオーバヘッドなどのコストを抑える事ができるという利点があります。

ところで MPM って?

MPM は (Multi Processing Module) の略で、Webブラウザからのリクエストを Apache がどのように並行処理するか、という部分の処理をモジュール化したものです。
先にWebサーバのモデルに関して記述しましたが、Apache ではそのようなモデルの中からどの実装を使用するかをこの MPM によって選択することができます。
Apache そのものにこれらの処理が組み込まれずにモジュール化されていることによって、各々のWebサイト向けにカスタマイズすることが容易になっています。
Apache2.2までは MPM は静的にリンクしなければなりませんでしたが、Apache2.3 からは LoadModule ディレクティブで動的に選択することが可能になりました。

MPM の種類

代表的なものをまとめます。

prefork

マルチプロセスモデルです。

prefork という名の通り、クライアントからリクエストが来る前にあらかじめ一定数の子プロセスを fork して待機させておきます。これにより、fork の回数を減らしてパフォーマンス向上を図ります。リクエストごとにプロセスが分かれているため、あるプロセスの障害が他のプロセスに影響を及ぼすことがありません。したがって、安定した通信をすることが可能です。

worker

マルチスレッドモデルとマルチプロセスモデルのハイブリッドモデルです。

制御用の親プロセスがいくつかの子プロセスを作成し、その子プロセスそれぞれがマルチスレッドモデルでリクエストを捌きます。スレッド1つが1つのクライアントの処理を担当します。

prefork に比べ生成されるプロセスの数を抑えることができるので、資源の節約が可能です。しかし、スレッドを使用するモデルなので、mod_php などの非スレッドセーフなモジュールを利用する際には使用できません。

event

worker をベースとしたマルチスレッドモデルとマルチプロセスモデルとイベント駆動モデルのハイブリッドモデルです。

KeepAlive の処理を別のスレッドに割り振って通信を処理することによって、パフォーマンスの向上を図っています。また、クライアントとのネットワークI/Oのみイベント駆動モデルで実装されています。

こちらもスレッドを使用するモデルなので、非スレッドセーフなモジュールを利用する際には使用できません。

おわり

以上 Apache MPM についてでした。運用しているサーバの MPM を prefork から event に変更してみたところ、僕の環境では実際にメモリの消費量をかなり低減できました。Apche をチューニングする際はこの MPM について検討してみてもいいかもしれません。event への変更方法については以下の記事で書いています。おわり。

norikone.hatenablog.com