二つの違い
ドメイン駆動設計(DDD)
焦点
DDDは、複雑なビジネスロジックの専門知識をソフトウェア設計の中心に置き、ドメイン内での言語(ユビキタス言語)やモデルを重視
主要概念
-
ドメインモデル
ビジネスドメインの概念とその関係をモデル化したもの。 -
ユビキタス言語
ビジネスと開発チーム間のコミュニケーションギャップを埋める共通言語 -
集約
関連するオブジェクトの集まりで、一貫性のある変更単位を提供
メリットとデメリット
利点
ビジネス要件と密接に連携した設計が可能。
欠点
-
複雑性
ビジネスロジックが複雑なアプリケーションに適している分、小規模あるいは単純なアプリケーションでは過剰となる。 -
実装の難易度
ユビキタス言語の開発や集約の境界を適切に定義する事が難しい。 -
インフラストラクチャとの結合
ドメインモデルがインフラ層に影響を及ぼす事がある。
クリーンアーキテクチャ
焦点
ソフトウェアの全体構造に関する設計原則で、ソフトウェアシステムの柔軟性、保守性、テスト容易性の向上が目的。
主要概念
- 独立性
フレームワーク、UI、データベースなどからの独立性 - テスト可能性
ビジネスロジックがUI、データベース、外部エージェントから独立しているため、テストしやすい。 - 層状構造
内側の層(ドメイン)から外部の層(インフラストラクチャ、UI)への依存関係
メリットとデメリット
利点
システムの各部分を独立して更新・交換が可能。
欠点
-
過剰な抽象化と層状構造
小規模あるいは単純なアプリケーションでは過剰になる。 -
学習曲線
クリーンアーキテクチャの原則の理解に一定の学習と経験が必要。 -
ビジネスロジックとの統合の欠如
ビジネスロジックが十分に強調されない場合、技術中心の設計に陥る。
ソースによる差異(PHP+Laravel)
DDDのみの実装の場合
// ドメインモデル(例:Product.php)
namespace App\Domain\Models;
class Product
{
private $id;
private $name;
private $price;
public function __construct($id, $name, $price)
{
$this->id = $id;
$this->name = $name;
$this->price = $price;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function getPrice()
{
return $this->price;
}
// ビジネスロジック(例:価格更新)
public function updatePrice($newPrice)
{
$this->price = $newPrice;
}
}
#-------------------------------------------------------------
// リポジトリ(例:ProductRepository.php)
namespace App\Domain\Repositories;
use App\Domain\Models\Product;
use DB; // データベースへの直接的な依存
class ProductRepository
{
public function findById($id)
{
$data = DB::table('products')->where('id', $id)->first();
return new Product($data->id, $data->name, $data->price);
}
public function save(Product $product)
{
// データベースへの保存ロジック
}
}
#-------------------------------------------------------------
// コントローラー(例:ProductController.php)
namespace App\Http\Controllers;
use App\Domain\Repositories\ProductRepository;
class ProductController extends Controller
{
protected $productRepository;
public function __construct(ProductRepository $productRepository)
{
$this->productRepository = $productRepository;
}
public function show($id)
{
$product = $this->productRepository->findById($id);
return view('product.show', compact('product'));
}
}
DDDでは核となるドメインモデル(Productクラス)とレポジトリが重視され、Productクラスはビジネスロジックをカプセル化し、ProductRepositoryはProductの永続化を担当。
また、欠点にも挙げたインフラストラクチャ層(DBクラス)への依存がドメイン層であるレポジトリクラスに表れているのが分かる。
クリーンアーキテクチャのみの実装の場合
// ドメインモデル(例:Product.php)
namespace App\Domain\Models;
class Product
{
private $id;
private $name;
private $price;
public function __construct($id, $name, $price)
{
$this->id = $id;
$this->name = $name;
$this->price = $price;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function getPrice()
{
return $this->price;
}
// ここではビジネスロジックは省略
}
#-------------------------------------------------------------
// リポジトリインターフェース(例:ProductRepositoryInterface.php)
namespace App\Domain\Repositories;
use App\Domain\Models\Product;
interface ProductRepositoryInterface
{
public function findById($id);
public function save(Product $product);
}
#-------------------------------------------------------------
// アプリケーションサービス(例:ProductService.php)
namespace App\Application\Services;
use App\Domain\Repositories\ProductRepositoryInterface;
class ProductService
{
protected $productRepository;
public function __construct(ProductRepositoryInterface $productRepository)
{
$this->productRepository = $productRepository;
}
public function getProductById($id)
{
return $this->productRepository->findById($id);
}
}
#-------------------------------------------------------------
// インフラストラクチャ層(例:EloquentProductRepository.php)
namespace App\Infrastructure\Repositories;
use App\Domain\Models\Product;
use App\Domain\Repositories\ProductRepositoryInterface;
use DB;
class EloquentProductRepository implements ProductRepositoryInterface
{
public function findById($id)
{
$data = DB::table('products')->where('id', $id)->first();
return new Product($data->id, $data->name, $data->price);
}
public function save(Product $product)
{
// データベースへの保存ロジック
}
}
#-------------------------------------------------------------
// コントローラー(例:ProductController.php)
namespace App\Http\Controllers;
use App\Application\Services\ProductService;
class ProductController extends Controller
{
protected $productService;
public function __construct(ProductService $productService)
{
$this->productService = $productService;
}
public function show($id)
{
$product = $this->productService->getProductById($id);
return view('product.show', compact('product'));
}
}
それぞれが独立した責任を持つ。
- ドメイン層(
Product
、ProductRepositoryInterface
)
ビジネスロジックとデータの構造を定義 - アプリケーション層(
ProductService
)
ユースケースを実装、 - インフラストラクチャ層(
EloquentProductRepository
)
インフラストラクチャ層はデータの永続化 - プレゼン層(
ProductController
)
プレゼン層はユーザーのリクエストを処理し、適切なレスポンスを返す。
DDDとクリーンアーキテクチャを併用する事でどう変わる?
DDDから見たクリーンアーキテクチャによる補完効果
- クリーンアーキテクチャは、その層状の構造を通じ、アプリケーションの各部分を独立させ、DDDの複雑さと実装の難しさを軽減
- インフラストラクチャとドメイン層の独立性を高めることで、DDDのモデルとインフラストラクチャとの結合を緩和
クリーンアーキテクチャから見たDDDによる補完効果
- DDDはビジネスの複雑さをモデル化する強力な手段を提供し、クリーンアーキテクチャにビジネス中心の視点をもたらす
- ユビキタス言語とドメインモデルの概念は、クリーンアーキテクチャの技術的な構造をビジネスニーズと結びつける
二つを併用したサンプルソース
// ドメインモデル(例:Product.php)
namespace App\Domain\Models;
class Product
{
private $id;
private $name;
private $price;
public function __construct($id, $name, $price)
{
$this->id = $id;
$this->name = $name;
$this->price = $price;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function getPrice()
{
return $this->price;
}
// ここではビジネスロジックは省略
}
#-------------------------------------------------------------
// リポジトリインターフェース(例:ProductRepositoryInterface.php)
namespace App\Domain\Repositories;
use App\Domain\Models\Product;
interface ProductRepositoryInterface
{
public function findById($id);
public function save(Product $product);
}
#-------------------------------------------------------------
// アプリケーションサービス(例:ProductService.php)
namespace App\Application\Services;
use App\Domain\Repositories\ProductRepositoryInterface;
class ProductService
{
protected $productRepository;
public function __construct(ProductRepositoryInterface $productRepository)
{
$this->productRepository = $productRepository;
}
public function getProductById($id)
{
return $this->productRepository->findById($id);
}
}
#-------------------------------------------------------------
// インフラストラクチャ層(例:EloquentProductRepository.php)
namespace App\Infrastructure\Repositories;
use App\Domain\Models\Product;
use App\Domain\Repositories\ProductRepositoryInterface;
use DB;
class EloquentProductRepository implements ProductRepositoryInterface
{
public function findById($id)
{
$data = DB::table('products')->where('id', $id)->first();
return new Product($data->id, $data->name, $data->price);
}
public function save(Product $product)
{
// データベースへの保存ロジック
}
}
#-------------------------------------------------------------
// コントローラー(例:ProductController.php)
namespace App\Http\Controllers;
use App\Application\Services\ProductService;
class ProductController extends Controller
{
protected $productService;
public function __construct(ProductService $productService)
{
$this->productService = $productService;
}
public function show($id)
{
$product = $this->productService->getProductById($id);
return view('product.show', compact('product'));
}
}
こんな奇特な記事をここまで見てくれた方がいるか不明だが、上のサンプルだとクリーンアーキテクチャ単体の場合と、DDD+クリーンアーキテクチャのソースに違いはない。
(理由はサンプルが単純過ぎるから)なのでクリーンアーキテクチャ単体の場合とDDD+クリーンアーキテクチャの場合の違いについてメモしとく。
クリーンアーキテクチャのみの場合
- 厳格な層状構造: クリーンアーキテクチャは、ドメイン層(エンティティとビジネスルール)、アプリケーション層(ユースケース)、インフラストラクチャ層(データアクセス、外部インターフェース)などの明確な層を持つ
- 依存関係逆転: 高レベルのモジュールが低レベルのモジュールに依存しないように設計、具体的には主にインターフェースと実装クラスの分離によって実現される
- 交換可能なインフラストラクチャ: 例えば、リポジトリのインターフェースはドメイン層にあり、その実装(インフラ層)は簡単に交換可能
DDD+クリーンアーキテクチャの場合
- ドメイン中心の設計: ドメイン層はビジネスルールとロジックに焦点を当て、DDDの原則により、ドメインモデル(エンティティ、値オブジェクト)はビジネスの複雑性を表現する
- 層状アーキテクチャの維持: クリーンアーキテクチャの層状構造は維持されるが、DDDの原則により、ドメイン層はよりリッチなビジネスロジックを含むようになる
- ビジネスロジックの強化: DDDのアプローチにより、ドメインモデルはビジネスプロセスをより深く反映し、アプリケーションの他の部分との統合が改善される
要約すると、大きな違いはドメイン層のビジネスロジックの複雑さが違うよ。
クリーンアーキテクチャ単独の場合は、設計の焦点は技術的な清潔さと層状構造だからこれを崩しちゃダメだよって事。
蛇足だけどEntityとValueObjectの違い
※特にクリーンアーキテクチャではEntityとValueObjectの区別が重要。
Entity
Productクラスが製品IDのような一意の識別子を持ってる場合、Entityとして扱われる。
価格の更新など状態が時間と共に変化する。
ValueObject
ValueObjectは識別子に依存しない不変の値の集合体で、一度作成されると値は変更されない。
金額を表すValueObjectは通貨変換のロジックを含めることは可能だが、状態を変更するロジックは含めてはいけない。