自分の担当したWebアプリケーションを引き継ぐ際に、予備知識として説明したことのまとめ
注意事項
- もともと明確に定義されていない概念や、簡単に説明するため正確さを犠牲にした部分が多い
- 間違っていることを前提に、疑いながら読むのがベター
アプリケーションの層構造
- アプリケーションを構成するオブジェクトには非常の多くの種類がある
- アプリケーションの(より良い)構成をオブジェクト単位で考えるのは難しいので、もっと粒度の大きい単位で考えたい
- アプリケーションをいくつかの層(オブジェクトの所属するグループ)に分割し、層単位でアプリケーションの構成を考える
View層(ビュー層)
- レスポンスをクライアントにとって都合のいい形(i.e. 画面)に変換する層
- View層のオブジェクトは
- Controller層のオブジェクトから利用される
- DomainModel層のオブジェクトを利用して、ユーザーに表示したいデータを用意する
- RESTfulなアプリケーションの場合、View層が存在しないこともある
Controller層(コントローラー層)
- ユーザーからのリクエストに対応し、レスポンスを生成する層
- Controller層のオブジェクトは
- View層のオブジェクトを利用して、データをユーザーにとって都合のいい形に変換する
- DomainModel層のオブジェクトを利用して、アプリケーション特有の問題を解決する
- DataAccess層のオブジェクトを利用して、DomainModel層のオブジェクトを取得/保存する
Service層(サービス層)
- Controller層とDomainModel層、DataAccess層を仲介する層
- Service層のオブジェクトは
- Controller層のオブジェクトから利用される
- DomainModel層、DataAccess層のオブジェクトを利用する
DomainModel層(ドメインモデル層)
- アプリケーション特有の問題(i.e. 料金計算)を解決する層
- DomainModel層のオブジェクトは
- DomainModel層、およびInfrastructure層以外のオブジェクトを利用しない
- アプリケーション特有の問題を解決することだけに集中する
- DomainModel層、およびInfrastructure層以外のオブジェクトを利用しない
DataAccess層(データアクセス層)
- ファイルや関係データベースなどに保存されたデータにアクセスする層
- DataAccess層のオブジェクトは
- DomainModel層のオブジェクトをデータに変換、保存する
- データを取得し、DomainModel層のオブジェクトに変換する
Infrastructure層(インフラストラクチャ層)
- アプリケーションの構成を決定、技術的な問題を解決し、その他の層の実装をサポートする層
- たとえば
- フレームワーク(i.e. Zend Framework)
- メーラー(i.e. Swiftmailer)
- DIコンテナ(i.e. Pimple)
- データアクセスオブジェクト(i.e. PDO)
- Infrastructure層のオブジェクトは
- ほか、すべての層のオブジェクトから利用される
- アプリケーションの変更に直接影響されない
- たとえば
- アプリケーションに画面を追加する場合、変更されるのはフレームワークのコードではなく、それを利用するアプリケーション側のコード
- PDOをDoctrineに入れ替える場合、変更されるのはPDOではなくPDOを利用しているアプリケーション側のコード
- たとえば
アプリケーションを構成する概念およびオブジェクト
DI(Dependency Injection:依存性の注入)
- 広義の「DI」と狭義の「DI」で指しているものが異なるため、注意が必要
- Inversion of Control コンテナと Dependency Injection パターンが参考になる
広義の「DI」
- IoC(Inversion of Control:制御の逆転)に同じ
- アプリケーションを構成するオブジェクトを簡単に交換できるようにすること
狭義の「DI」
- 広義のDIを実現するための手法、もしくはオブジェクト
- ServiceLocatorと対比される
- パラメータの型やType Hintingなどを参考に、あるオブジェクトが必要としている別のオブジェクトを自動的に与える(依存性を注入する)
- リフレクションを駆使するため、ServiceLocatorに比べて処理の流れがつかみにくい
- Zend Framework 2ではDIよりもServiceLocatorを利用することが推奨されている?
ServiceLocator(サービスロケーター)
- 広義のDIを実現するための手法、もしくはオブジェクト
- DIと対比される
- ServiceLocatorは(利用者側から見た場合)単にServiceのコレクションとして振る舞う
- Serviceを利用したいオブジェクトは、ServiceLocatorを通じてServiceを取得する
- ServiceLocatorに追加(定義)するServiceを変更するだけで、利用者が取得するServiceを簡単に変更できる
- ServiceLocatorの実装と利用方法についてはZend\ServiceManagerやPimpleが参考になる
Service(サービス)
- 文脈によってServiceという用語が指しているものは異なるため、注意が必要
DIに関する文脈の場合
- なんらかの機能を提供するオブジェクトを指す
- ほとんどすべてのオブジェクトをServiceと呼ぶことができる
- ただし、メーラー、DB接続(i.e. PDO)など、ほとんど状態が変更されない(アプリケーション中で使い回したい)オブジェクトを指すことが多い
アプリケーションの層構造に関する文脈の場合
- Service層に所属するオブジェクトを指す
- (この場合の)Serviceの役割は、Controller層とModel層を仲介すること
- Model層のオブジェクトを利用する処理は複雑になることが多い
- EntityやDataMapperなど、Model層に所属するオブジェクトの操作を肩代わりし、Controllerが肥大化しないようにする
- Entityがうまく解決できない問題に対処するのもServiceの役割
- アプリケーション特有の問題と技術的な問題がうまく分離できない場合、処理をControllerに記述すると実装が複雑になりやすいため、とりあえずServiceを用意しておくと問題になりにくい
- たとえば
- 通知メールの送信
- CSVのインポート
- あとからServiceの機能をEntityに移動することは容易
View(ビュー)
- ユーザーへのレスポンスを、ユーザにとってわかりやすい形(i.e. 画面)に変換するオブジェクト
- テンプレートを使って画面を生成するのがViewであり、テンプレートはViewそのものではない
- テンプレートエンジン(i.e. Smarty)は
- Infrastructure層に所属するオブジェクト
- Viewの実装を支援する
- Viewにテンプレートをレンダリングする以外の機能がない場合、Viewそのものとして扱われていることが多い
Entity(エンティティ)
- ドメインモデル層に所属するオブジェクト
- 単にModel(モデル)と呼ばれることも多い
- 混同されている
- Entityの役割は、アプリケーション特有の問題(i.e. 料金計算)を解決すること
- 自身の永続化(DBやファイルなどに保存すること)は、Entityの主な役割ではない
- ActiveRecord、DataMapperを参照
- 自身の永続化(DBやファイルなどに保存すること)は、Entityの主な役割ではない
- Entityは必ず一意な識別子(ID)を持つ
- 2つのEntityが同じものかどうかは、属性(その他のプロパティ)に関わらず、IDか同じかどうかだけで判断する
- 自然数(DBテーブルの人工キー)を利用することが多い
Factory(ファクトリー)
- オブジェクトを生成するオブジェクト
- EntityやServiceの初期化は実装が複雑になることが多いため、Factoryとして別のオブジェクトに分離しておくと問題になりにくい
ActiveRecord(アクティブレコード)
- Entityを永続化するための機能をEntity自身が持つパターン、もしくはそのパターンにもとづくオブジェクト
- Ruby on RailsやSymfonyで採用されていた
- 現在はDataMapperが主流
- ActiveRecordを採用した場合、Entityは複数の役割を持つことになる
- アプリケーション特有の問題を解決する役割
- 自身(計算結果など)を永続化する役割
- DataMapperに比べて、
- 使いやすい
- 計算(アプリケーション特有の問題を解決)、永続化(計算結果などを保存)の流れが1オブジェクトで完結するため、利用者側のコードは簡潔になる
- 複数の役割(機能)を持つため、Entityの実装が肥大化しやすい
- オブジェクトのプロパティとDBテーブルのカラムが1:1で対応するなど、オブジェクトとDBテーブルの対応関係が簡単な場合はあまり問題にならない
- 使いやすい
dummy.php
$entity = Entity::load($id);
$entity->setName($name);
$entity->save();
DataMapper(データマッパー)
- Entityを永続化するための機能をEntity以外のオブジェクトが持つパターン、もしくはそのパターンにもとづくオブジェクト
- Zend FrameworkやSymfony、Ruby on Railsで採用されている
- ActiveRecordに比べて、
- 使いにくい
- 計算(アプリケーション特有の問題を解決) => 永続化(計算結果などを保存)の流れに複数のオブジェクトが絡んでくるため、利用者側のコードは複雑になる
- 単一の役割しかもたないため、Entityの実装が肥大化しにくい
- オブジェクトとDBテーブルの対応関係が複雑になっても問題になりにくい
- 使いにくい
- 特に思想がない場合はActiveRecordよりもDataMapperを採用しておくほうが無難
dummy.php
$dataMapper = new DataMapper();
$entity = $dataMapper->load($id);
$entity->setName($name);
$dataMapper->save($entity);
Repository(リポジトリ)
- Entityを効率的に永続化するためのオブジェクト
- DataMapperは単一のEntityを永続化するためのオブジェクト
- Repositoryは複数のEntityを永続化するためのオブジェクト
- Doctrine ORMのEntityManagerはRepositoryとしての役割も持つ
dummy.php
$repository = new Repository();
$first = new Entity();
$second = new Entity();
$repository->add($first);
$repository->remove($first);
$repository->add($second);
$repository->flush();
TableDataGateway(テーブルデータゲートウェイ)
- DBテーブルの操作を簡単にするためのオブジェクト
- DataAccess層に所属する
- DBテーブルと1:1で対応する
- JOINなど複数のDBテーブルにまたがる操作は複雑になる
- DBテーブルに対するクエリをカプセル化する
- TableDataGatewayを利用する側は、メソッドを通してDBテーブルからデータを取得/DBテーブルにデータを保存できるようになる
- Zend_Db_Tableのドキュメントが参考になる
dummy.php
$table = new TableDataGateway('table_name');
$table->find('primary_key');
$table->findAll();
$table->insert(array('column_name' => 'column_value'));
$table->update(array('column_name' => 'column_value'), 'primary_key');
$table->delete('primary_key');
RowDataGateway(行データゲートウェイ)
- DBテーブル行の操作を簡単にするためのオブジェクト
- DataAccess層に所属する
- DBテーブル行と1:1で対応する
- DBテーブル行に対するクエリをカプセル化する
- RowDataGatewayを利用する側は、メソッドを通してDBテーブル行からデータを取得/DBテーブル行にデータを保存できるようになる
- RowDateGatewayの実装および利用方法についてはZend_Db_Table_Rowが参考になる
dummy.php
$table = new TableDataGateway('table_name');
$row = $table->find('primary_key');
$row
->set('column_name', 'column_value')
->save();
ValueObject(値オブジェクト)
-
名前の通り、値を表すオブジェクト
-
Entityと同じく、アプリケーション特有の問題を解決する役割を持つ こともある
-
Infrastructure層もしくはDomainModel層に所属する(?)
- ValueObjectがEntityと同じくアプリケーション特有の問題を解決する役割を持っている場合、そのValueObjectはEntityと同じくDomainModel層に所属する
- ValueObjectが汎用的な(アプリケーションに特有でない)機能しか持っていない場合(i.e. DateTime)、そのValueObjectはInfrastructure層に所属する
-
Entityとは異なり、ValueObjectはIDを持たない
- 2つのValueObjectが同じものかどうかは、すべての属性(プロパティ)が同じかどうかなどで判断する
-
色や日付など、ある値を表す方法が複数存在する場合、ValueObjectを用意しておくと実装が簡潔になる
値 表記例 Color #000000, rgb(0, 0, 0), hsl(0, 0, 0) Date 2014/1/1, 2014-1-Jan, 2014/01/01 00:00:00 DataSize 1バイト, 1Byte, 0.001KB Price 1ドル, 90円, 1.4ユーロ
ValueObjectの例
<?php
class DataSize
{
protected static $cofficients = array(
'byte' => 1,
'kilobyte' => 1e3,
'megabyte' => 1e6,
);
protected $bytes;
public function __construct($value, $unit = 'byte')
{
$this->value = $value * static::$cofficients[$unit];
}
public function value($unit = 'byte', $precision = 1)
{
return round($this->value / static::$cofficients[$unit], $precision);
}
}
$size = new DataSize(1000);
$size->value('byte'); // 1000
$size->value('kilobyte'); // 1