EC機能のドメインモデル設計（簡易版）


# ドメイン駆動設計（Domain Driven Design）

## ドメインとは
- 商品を検索する
- 商品をカートに入れる
- 商品を購入する

など、アプリケーションを通じてユーザーが実際に取る行動、もしくは行動したいという要求（関心事）

## ドメインモデル
- エンティティ
- 値オブジェクト
- サービス

## ドメインモデルのライフサイクル管理
- アグリゲート
- ファクトリー
- リポジトリー

### エンティティ
一意となる要素を保持するオブジェクト
- ユーザー
- カート
- 商品
など

### 値オブジェクト
保持した値が決して変更されない不変のオブジェクト
- 都道府県情報
- 郵便番号情報
- 決済代行サービス
など

### サービス
エンティティでも値オブジェクトでもない処理を持つオブジェクト
- 検索処理
など

### ファクトリー
- オブジェクトの生成をカプセル化

### リポジトリー
- 永続化されたオブジェクトへのアクセス手段を提供

### アグリゲート
- エンティティ間の依存関係を保持
- 関連する複数のエンティティに対して処理が必要な場合に命令を受け取る

# ドメインモデルをCakePHP3で表現

## CakePHP3のModel

CakePHPはバージョン3になって2種類のモデルに分割
- Tablesクラス
- Entityクラス

[Entities - CakePHP Cookbook](http://book.cakephp.org/3.0/en/orm/entities.html#namespace-Cake\ORM)

>While Table Objects represent and provide access to a collection of objects, entities represent individual rows or **domain objects** in your application. 
テーブルオブジェクトはオブジェクトの集合へのアクセスを可能としており、エンティティは個々の行や**ドメインオブジェクト**を表している。

ドキュメントにもあるようにドメインオブジェクトはEntityクラスを継承する。

### ポイント
- 各ドメインモデルはEntityクラスを継承
- リポジトリーはTableクラスを継承
  - リポジトリーはドメインモデルではないため
  - CakePHP3のQuery BuilderはTableクラスが持つため

## 商品を検索する処理

```php:UserEntity

namespace App\Model\Entity;

use Cake\ORM\Entity;
use Cake\ORM\Entity\ProductSearchService;

class UserEntity extends Entity
{
  public function productSearch($keyword)
  {
    $productSearchService = new ProductSearchService();
    $productSearchService->keyword = $keyword;
    return $productSearchService->searchByUser();
  }
}

```

```php:ProductSearchService
namespace App\Model\Entity;

use Cake\ORM\Entity;
use Cake\ORM\TableRegistry;

class ProductSearchService extends Entity
{
  private $keyword;

  public function setKeyword($keyword)
  {
    $this->keyword = $keyword;
  }

  public function getKeyword()
  {
    return $this->keyword;
  }

  public function searchByUser()
  {
    $productRepository = TableRegister::get('ProductRepository');
    return $productRepository->searchByUser($this);
  }
}

```

```php:ProductRepository
namespace App\Model\Table;

use Cake\ORM\Table;

class ProductRepository extends Table
{
  public function searchByUser($productSearchService)
  {
    $keyword = $productSearchService->keyword;
    return $this->find()
      ->where(['id' => $keyword])
      ->orWhere(['product_name' => $keyword])
      ->all();
  }

}

```

### 判断に悩んだ点
1. `ProductSearchService`は必要か
  - `ProductRepository`の生成や`searchByUser`の呼び出しは`UserEntity`から行えばよいという考えもあったが、検索項目を保持する何らかのサービスドメインが必要であると判断した。厳密には、キーワードは対象となる全ての`Product`プロパティにセットしたり、価格帯は相当する値オブジェクトを作成したりするなど、各検索項目に最適なドメインクラスを作成すれば`ProductSearchService`は必要ないと思われる。仕様が複雑さを増すと共に前記の変更は必要であり、このような状況に応じた設計の判断を求められることが、***ドメイン駆動は変化し続けることが重要***と言われる所以のひとつであろう。
2. `ProductSearchService`や`ProductRepository`を生成するファクトリーは必要か
  - ファクトリーの役割はドメインを生成することよりも、生成する際の前処理（複数のドメインの生成や初期値の設定など）が複雑な場合、それらの処理をまとめておく（カプセル化）のに利用するものであるため、今回の簡単な例題では必要ないと判断した。

## 商品をカートに入れる

```php:UserEntity

namespace App\Model\Entity;

use Cake\ORM\Entity;
use Cake\ORM\TableRegistry;

class UserEntity extends Entity
{
  public function putProductInCart($selectedProductId)
  {
    $product = new ProductEntity();
    $product->id = $selectedProductId;
    $productRepository = TableRegister::get('ProductRepository');
    $selectedProduct = $productRepository->getSelectedProductByUser($product);

    $cart = new Cart();
    $cart-> setProduct($selectedProduct);
  }
}

```

```php:CartEntity

namespace App\Model\Entity;

use Cake\ORM\Entity;

class CartEntity extends Entity
{
  private $products;

  public function setProduct($product)
  {
    $this->products[] = $product;
  }
}

```

```php:ProductEntity

namespace App\Model\Table;

use Cake\ORM\Table;

class ProductEntity extends Entity
{
  private $id;

  public function setId($id)
  {
    $this->id = $id;
  }
}


```

```php:ProductRepository

namespace App\Model\Table;

use Cake\ORM\Table;

class ProductRepository extends Table
{
  public function getSelectedProductByUser($product)
  {
    return $this->find()
      ->where{['id' => $product->id])
      ->first();
  }
}


```

### 判断に悩んだ点
1. `PutProductInCartService`は必要か
  - 検索処理とは異なり、カートに入れる商品情報（ここでは商品ID）は`Product`プロパティとしてセットでき、`ProductRepository`から対象の商品情報を取得できるため、必要ないと判断した。


# まとめ
- 設計に非常に時間がかかる
- 判断に基準が必要
- クラス図必須
- コードレビュー必須
- テストクラス必須


# 参照
- [.NETでドメイン駆動開発～ValueObject後編～](http://www.infoq.com/jp/articles/DDD_ValueObject_0325)
- [Domain-Driven Designのエッセンス](https://www.ogis-ri.co.jp/otc/hiroba/technical/DDDEssence/chap2.html#LayeredArchitecture)
- [「これって、ドメイン駆動設計？」という資料を公開しました。](http://gothedistance.hatenadiary.jp/entry/2015/06/11/155843)
- [【まとめ】『実践ドメイン駆動設計』: 7. サービス](http://qiita.com/suin/items/2202bf27b5f865805226)
- [コードで学ぶドメイン駆動設計入門 ～エンティティとバリューオブジェクト編～](http://d.hatena.ne.jp/j5ik2o/20101228/1293551861)