読者です 読者をやめる 読者になる 読者になる

第4回 コードリーディング

コードリーディング

FuelPHPのDB周りと書籍「ドメイン駆動設計」のサンプルを読んでみた。

ドメイン駆動設計は全体像を把握するだけで終わったので、
次はドメイン設計部分を読もうと思う。


以下はFuelPHP

【DB】
以下はselectのコード。
これを追っていく。

                      • -

$db = \DB::query($sql);
$db->parameters(array('service_type'=>$serviceType, 'service_user_id'=>$serviceUserId));
$result = $db->execute($db1Name)->as_array();

                          • -

DBクラスは「Database object creation helper methods.」とあるように各種DB操作のfacadeになっているだけ。
DB::query() も return new \Database_Query($sql, $type); としているだけ。
つまり、本体はQueryクラスになる。

Queryクラスはクエリを表現しており、
SQL文、クエリのタイプ(select,insert,delete,update)、結果のキャッシュ、キャッシュ設定を保持している。
FuelPHPでは珍しく(?) static な実装ではない。
クラス自体が1つのクエリを表現しているから、staticにするわけもないけど・・・。

execute() はDB接続、SQLの組み立て(compile)、実行をしている。

まずはDB接続から。
接続はDatabase_Connectionクラスが管理している。
抽象クラス + シングルトンで、confファイルで設定したDBのドライバを返す。
Connection という命名だが、接続以外にもSQL実行、エスケープ、テーブル一覧など
接続以外の機能も実装されている(abstractも含む)。
今回はPDOを利用しているので、Database_PDO_Connectionクラスをドライバとして利用している。
接続処理自体はPDOを利用して Database_Connection で定義されているabstractなメソッドを実装しているだけ。
あと、接続を保持している。

次にSQLの組み立てをする。
Database_Connection の quote() でパタメータの値を文字列に変換し(null を 'null' にするイメージ)、エスケープ処理をする。
それらのパラメータを Str::tr() でSQLバインドする。

次に実行。
Database_PDO_Connection の query() で実行している。
組み立てたSQLを PDO::query() で実行するだけ。
SQLのタイプ(select, update, insert)によって返す情報を変えている。
select ... 結果
insert ... lastInsertIdの値, 挿入行数。
のような感じ。

プリペアドステートメントとか使ってなかった・・・。
入力値をエスケープ(サニタイズ)することでSQLインジェクションを防いでいるっぽい。
Database_PDO_Connection の list_columns() では使ってるけど・・・。
プリペアドステートメントについては同じ疑問を抱いている人もいた。
http://stackoverflow.com/questions/12715499/fuelphp-two-questions-db-and-custom-functions

解決法というか、fuelでプリペアードステートメントを利用する方法は以下らしい。
http://fuelphp.com/docs/classes/database/usage.html#/binding

でも、文字列置換で独自実装しているという批判記事もある。
http://hackersome.com/p/chrismeller/db



【クエリビルダー】
クエリビルダーを追っていく。

                          • -

$result = DB::select('id','name')->from('users')->execute();

                          • -

DB::select() は Database_Query_Builder_Select をnewするだけなので、
実体は Database_Query_Builder_Select になる。

クエリビルダーの継承関係は以下。
Database_Query_Builder_Select -> Database_Query_Builder_Where -> Database_Query_Builder -> Database_Query

Database_Query ... ルートクラス。SQLの実行、サニタイズを実装している。SQL直書き実行の場合に利用するクラス。
Database_Query_Builder ... join、where & having で利用される共通部分, updateのset, order by のクエリを構築する。
Database_Query_Builder_Where ... where の構築
Database_Query_Builder_Select ... select系操作(havingとかdistinct)のSQLを構築する。

並べてみると Database_Query から Database_Query_Builder_Select までキレイに特化された継承ツリーに思える。
当然ながら Database_Query_Builder_Select の insert, update, delete バージョンも存在する。

クエリビルダーはSQLを構築するだけで、実行は Database_Query の execute() にSQLを渡しているだけ。
SQLの構築は execute() 内で $this->compile() と指定されているので、
Database_Query ではなく、 Database_Query_Builder_Select の compile() が利用される。

and_where_open(), and_where_close() のようにSQLを組み立てるメソッドはシンプルだけど、
これを利用するのであればSQLを直書きした方が早いような・・・。



以下はドメイン駆動設計のサンプル。

レイヤーアーキテクチャになっている。
application
domain
infrastructure
interfaces

各例やでインターフェースと実装を分けている。
application でいうと、CargoInspectionService というインターフェースとそれに紐づく CargoInspectionServiceImpl がある。
ドメインリポジトリとサービスはインターフェースになっていて実装はインフラになっている。
つまり、DIPで書いてある。

エンティティには Entityインターフェースを実装させているが、
実装機能は sameIdentityAs() という同一性を確認するものだけ。

viewへの表示はドメインモデルをAssemblerでDTOにしている。

domainのモジュール構造は以下になっている。

domain
	model
		cargo
			Cargo.java
			CargoRepository.java
		handling
			xxx.java
		location
			xxx.java
	service
		RoutingService.java
	shared
		Entity.java
		DomainEvent.java

サービスはドメインモデルに実装できない振る舞いだから、modelとは別モジュールになっている。
sharedは共有カーネルで他のモデルと共用する必要があるから、別モジュールになっている。

実際のコードを読むと参考になる。
アプリケーションを動かしてみたかったので、ググると以下の記事を発見。
http://qiita.com/opengl-8080/items/4f8938c65d8a2b7e50d0

まとめられていたとは・・・。
先に目を通しておけばよかった・・・。