社内でドメイン駆動設計入門の読書会 #7
こんにちは。Nonです。
今回も会社で読書会をしている話をしようと思います。
内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。
より具体的なコードや内容がみたい!という方は購入しましょう!
読んでいる本
読んでいる本はこちらのドメイン駆動設計です。
ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本
以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。
そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。
進行方法
読書会の進行方法は
- 今回読書の対象にする章を決める。
- 10〜20分間その章を読む
- 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう
- その後40分間で、その章に対する疑問や考え方をディスカッションする
- 1〜3を毎週定期的に行う
という進行方向となっています。
社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。
今回読んだ内容
- 柔軟性をもたらす依存関係のコントロール
- 技術要素への依存がもたらすもの
- 依存とは
- 依存関係逆転の原則とは
- 抽象に依存せよ
- 主導権を抽象に
- 依存関係をコントロールする
- Service Locator パターン
- Ioc Containerパターン
- まとめ
今回は技術的な設計手法のお話ですね。文字がいっぱいで理解するのに時間がかかりました。
この章の内容はDDDに限らず、プログラミングをする上にあたって大事なことが書かれている気がします。
ディスカッション
と言っても今回、体調不良で読書会に参加することができませんでした……
なので、ディスカッションの内容ではなく、私個人としての見解と意見を記載させていただます。
リポジトリって言ってもデータストアは色々あるよね?
これまでのコードは私が書いたコードですが、データストアにMySQLを想定したコードでした。
しかしこれが、他のデータストアになったときにそうすればいいでしょうか?
もちろん、フレームワークによってはフレームワークの設定で接続するデータストアの設定を変更することができます。
しかし、生のPHPの場合、リポジトリはこのように書くことになるかもしれません。
(コードの表示領域の関係で、雑なコードで申し訳ない。)
class FoodsRepository
{
protected $dsn = 'mysql:dbname=mysql;host=mysql';
protected $user = 'root';
protected $password = 'secret';
protected $pdo;
public function __construct()
{
$this->pdo = new PDO($this->dsn, $this->user, $this->password);
}
public function findAll($howFar, $feeling, $cost)
{
$sql = "SELECT * FROM `foods`";
$stmt = $this->pdo->prepare($sql);
$stmt->execute();
return $stmt->fetchAll();
}
public function findById(int $id)
{
$sql = "SELECT * FROM `foods` WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
}
このように書く場合も正常に動作しますし、Repositoryとしての責務を果たしています。
しかし、これはMySQLに依存していると言って良いのではないでしょうか?
仮にデータストアが変わったときにコードの大量変更が行われることになります。
抽象クラスを作成せよ
前述のソースコードから、リポジトリはMySQLに依存していて、その具象リポジトリを使用するドメインサービスなどもMySQLに依存していると言ってもいいでしょう。
そこで、抽象クラスを作成し、ドメインサービスなどはその抽象クラスに依存するようにします。
抽象クラス
抽象クラスで、ドメインサービスなどで使用する関数を定義しておきます。
interface FoodsRepositoryInterface
{
public function findAll();
public function findById();
}
具象クラス
Mysql用
class FoodsMySqlRepository implements FoodsRepositoryInterface
{
protected $dsn = 'mysql:dbname=mysql;host=mysql';
protected $user = 'root';
protected $password = 'secret';
protected $pdo;
public function __construct()
{
$this->pdo = new PDO($this->dsn, $this->user, $this->password);
}
public function findAll($howFar, $feeling, $cost)
{
$sql = "SELECT * FROM `foods`";
$stmt = $this->pdo->prepare($sql);
$stmt->execute();
return $stmt->fetchAll();
}
public function findById(int $id)
{
$sql = "SELECT * FROM `foods` WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
}
PostgreSQL用
class FoodsPostgreSqlRepository implements FoodsRepositoryInterface
{
protected $dsn = 'pgsql:dbname=pgsql;host=pgsql';
protected $user = 'root';
protected $password = 'secret';
protected $pdo;
public function __construct()
{
$this->pdo = new PDO($this->dsn, $this->user, $this->password);
}
public function findAll($howFar, $feeling, $cost)
{
$sql = "SELECT * FROM `foods`";
$stmt = $this->pdo->prepare($sql);
$stmt->execute();
return $stmt->fetchAll();
}
public function findById(int $id)
{
$sql = "SELECT * FROM `foods` WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
}
ドメインサービス
class FoodService
{
private $foodRepository;
public function __construct(FoodsRepositoryInterface $foodRepository)
{
$this->foodRepository = $foodRepository;
}
public function get(int $id)
{
return $this->foodRepository->findById($id);
}
}
アプリケーションサービス
class FoodApplicationService
{
public function show(int $id)
{
// mysqlの場合
$service = new FoodService(new FoodsMySqlRepository());
// pgsqlの場合
$service = new FoodService(new FoodsPostgreSqlRepository());
$service->get($id);
}
}
こうすることで、データストアが変わってしまったときや、一部のデータが違うデータストアにあるときに対応することができます。
何がすごいの?
ドメインサービスにどのリポジトリクラスを注入するかで、サービスのコードを変更することなく実装することができます。
ドメインサービスはFoodsRepositoryInterface
が必要なだけで、その抽象クラスに沿って実装されているクラスならキチンと動作するからです。
上記の例は Ioc Containerパターンです。
Service Locator パターンと比較して、一長一短ですが、私はこちらの方がうまいこと行きそうな気がしました。
普段触っているフレームワークがDIの仕組みを標準でサポートしているLaravelなので、とても使いやすいです。
- Service Locator パターン
- Ioc Containerパターン
この2つに関してはまだまだ勉強不足なので、こういうのが書いてあったよとだけお伝えします。
最後に
今回は依存関係についてでした。1年半くらい前まで、設計のことなぞ全く考えずに実装してきたので、最近ようやく触れることができてとても嬉しいです。
抽象クラスなどの恩恵はこれだけでは無く、プログラムの設計を理解する上でも重要なことみたいですので、他のデザインパターンと併せて勉強して行きたいと思います。
また記事にしますので、その時はよしなに。
.