のんラボ

社内でドメイン駆動設計入門の読書会 #7

2020/04/10 2020/04/10

社内でドメイン駆動設計入門の読書会 #7 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #6 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 柔軟性をもたらす依存関係のコントロール 技術要素への依存がもたらすもの 依存とは 依存関係逆転の原則とは 抽象に依存せよ 主導権を抽象に 依存関係をコントロールする Service Locator パターン Ioc Containerパターン まとめ 今回は技術的な設計手法のお話ですね。文字がいっぱいで理解するのに時間がかかりました。 この章の内容はDDDに限らず、プログラミングをする上にあたって大事なことが書かれている気がします。 ディスカッション と言っても今回、体調不良で読書会に参加することができませんでした…… なので、ディスカッションの内容ではなく、私個人としての見解と意見を記載させていただます。 リポジトリって言ってもデータストアは色々あるよね? これまでのコードは私が書いたコードですが、データストアにMySQLを想定したコードでした。 しかしこれが、他のデータストアになったときにそうすればいいでしょうか? もちろん、フレームワークによってはフレームワークの設定で接続するデータストアの設定を変更することができます。 しかし、生のPHPの場合、リポジトリはこのように書くことになるかもしれません。 (コードの表示領域の関係で、雑なコードで申し訳ない。) copied.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に依存していると言ってもいいでしょう。 そこで、抽象クラスを作成し、ドメインサービスなどはその抽象クラスに依存するようにします。 抽象クラス 抽象クラスで、ドメインサービスなどで使用する関数を定義しておきます。 copied.interface FoodsRepositoryInterface { public function findAll(); public function findById(); } 具象クラス Mysql用 copied.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用 copied.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(); } } ドメインサービス copied.class FoodService { private $foodRepository; public function __construct(FoodsRepositoryInterface $foodRepository) { $this->foodRepository = $foodRepository; } public function get(int $id) { return $this->foodRepository->findById($id); } } アプリケーションサービス copied.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年半くらい前まで、設計のことなぞ全く考えずに実装してきたので、最近ようやく触れることができてとても嬉しいです。 抽象クラスなどの恩恵はこれだけでは無く、プログラムの設計を理解する上でも重要なことみたいですので、他のデザインパターンと併せて勉強して行きたいと思います。 また記事にしますので、その時はよしなに。 .

社内でドメイン駆動設計入門の読書会 #6

2020/04/01 2020/04/03

社内でドメイン駆動設計入門の読書会 #6 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #5 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 ユースケースを実現する「アプリケーションサービス」 アプリケーションサービスとは ユースケースを組み立てる ドメインオブジェクトから準備する ユーザ登録処理を作成する ユーザ情報取得処理を作成する ユーザ情報更新処理を作成する 退会処理を作成する ドメインのルールの流出 アプリケーションサービスと凝集度 凝集度が低いアプリケーションサービス アプリケーションサービスのインターフェース サービスとはなにか サービスは状態を持たない まとめ 待ちに待ったアプリケーションサービスの章ですね。 ドメインサービスとアプリケーションサービスを混同して考えていた私にとって、とても勉強になる内容でした。 ディスカッション ドメインサービスとの違いは? 結論から言うとドメインサービスはVO・Entityに書ききれないサービスを記述するサービス。アプリケーションサービスはユースケースを記述するサービスです。 ドメインサービスはドメインのためのサービスですが、アプリケーションサービスはユーザー(使い手)のためのサービスとも言えるかもしれません。 ふるまいを持ったVOやEntityを用意し、必要に応じてRepositoryで永続化処理を行う。 これら一連の流れそのものがユースケースなので、この内容が書かれたクラスはアプリケーションサービスという事になります。 MVCモデルにおけるコントローラーアクションと混同しがち!? 私はこの考えで進んでしまっていました。 copied.class UserController extends Controller { public function store() { // ここに書かれることが、アプリケーションサービス } } アプリケーションサービスはあくまでもサービス アプリケーションサービスはあくまでサービスですので、リクエストの処理やレスポンスの処理をアプリケーションサービスに書くことはできません。 リクエスト形式が違う場合や、レスポンスを同出力するかだけで、やりたいことは同じということは結構あります。(ターミナルからやるか画面でやるかの違いだけとか) その時、inputとoutputだけが違う内容が同じアプリケーションサービスを2つ作成することはとても管理が面倒臭くなります。 なので、こうすべき。 copied.class UserController extends Controller { private $useCase; public function __construct(UserApplicationService $useCase) { $this->useCase = $useCase; } public function store(Request $request) { // アプリケーションサービス用のinputクラスを作成 $input = new UserCreateInput($request); // アプリケーションサービス用のoutputクラスを作成 $response = new UserCreateOutput(); // inputとoutputをアプリケーションサービスへ渡して実行する $this->useCase->process($input, $response); // outputクラスからプロパティにアクセスし、コントローラーがレスポンス挙動の責務をもつ return response()->json($output->toArray()); } } このときに、Dtoを採用するのも良いかもしれません。 copied.class UserApplicationService { private $service; public function __construct(UserService $service) { $this->service = $service; } public function process(UserCreateInputPort $input, UserCreateOutputPort $output) { // ユーザー名を取得しVOへ $name = new UserName($input->name()); // ユーザーメールアドレスを取得しVOへ $email = new UserEmail($input->email()); // ドメインサービスを介し、永続化 $user = $this->service->create($name, $email); // ユーザーIDを使用して、ユーザー用の拡張情報を取得 $userExtendInfo = $this->service->getExtendInfo($user->id()); // ユーザーに関わるエンティティをDtoへ渡し、Dtoを作成 $dto = new UserDto($user, $userExtendInfo); // ユーザーDtoをアウトプットのふるまいをもつクラスへ渡す。 $output->output($dto); } } こうすることで今までたくさん見てきた、責務の大きいコントローラーを少なくし、サービス(モデル)へ責務を押し付けることができます。 コントローラーはリクエストに応じて、必要なユースケース(アプリケーションサービス)を実行し、適切はレスポンスを作成することに集中することができます。 更に凝集度を下げることができる 前述したソースコードを見てもらえるとわかりますが、それぞれのふるまいが色々なクラスに分散されています。 結果的にですが、凝集度の観点からも好ましい状態にあります。 最後に 実はこれまで作成していたMain処理がそのままアプリケーションサービスだったとは驚きでした。 今回触れたのはそのアプリケーションサービスを実行するときの入力と出力の部分をできるだけ、抽象化するように変更しただけでしたね。 なにかのリクエストが来たときにユースケースを実行し、正しくレスポンスを返すところまでかけるようになりました。 次回以降はもっとプログラムの構成を整理する章となっていきそうです。 また記事にしますので、その時はよしなに。 .

Z250で伊勢志摩ツーリング!!

2020/03/22 2020/03/23

Z250で伊勢志摩ツーリングに行ってきました!! こんにちは。Nonです。 今回は久々にツーリングの記事を更新します🏍 行ってきたのは伊勢志摩!天気にも恵まれました。 めっちゃきれい コロナに負けない青空!!ウッキウキでバイクに跨りましたね。 那智の滝 相方がどうしても行きたいと言ってた場所 「那智の滝」 https://www.nachikan.jp/kumano/nachi-no-taki/ https://www.nachikan.jp/kumano/nachi-no-taki/を見る 去年くらいから「行きたいね〜」という話をしていたので、行くことになりました。 この辺は世界遺産多くて、滝の上にも熊野那智⼤社とかあります。 ここまで登るのが辛かった・・・😇😇😇 伊勢志摩スカイライン 素晴らしかった。 相方と有料道路制覇に向けての第一歩ですね🦾🦾 相方写り良すぎやろwww いい道路なので、スピードガチ勢もライトな観光勢も両方楽しめる道です。 頂上からの景色もいいですし、恋愛の聖地らしいです。(恋愛の聖地はいろんなとところにありますが、風景がいい分夕方になればいい雰囲気になれそう) そう言えば、100日後に死ぬワニの100日目だった ツーリング中にも話題になりましたw それ繋がりでどうぞ。 シュポ よくね? 最後に 天気に恵まれた良いツーリングでした! バイクはいいぞ!!! .

社内でドメイン駆動設計入門の読書会 #5

2020/03/18 2020/03/18

社内でドメイン駆動設計入門の読書会 #5 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #4 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 第4章の不自然さを解決する「ドメインサービス」です。 データにまつわる処理を分離する「リポジトリ」 リポジトリとは リポジトリの責務 リポジトリのインターフェース SQLを利用したリポジトリを作成する テストによる確認 テストに必要な作業を確認する 祈り信者のテスト理論 祈りを捨てよう テスト用のリポジトリを作成する オブジェクトリレーショナルマッパーを用いたリポジトリを作成する リポジトリに定義されるふるまい 永続化に関するふるまい 再構築に関するふるまい まとめ 私はWEBでの管理プログラムや、WindowsFormを用いてアプリ作成などしかプログラムを作成した経験がないので、MySQLなどしかデータストアとして使ってきませんでした。 この章を読んで色々なものがデータストアとして見ることができることに気づきました。 ディスカッション リポジトリとは? 経験の長い方が、読書会に入ってくださっているので、簡単に説明してくれました。 DDDの読書会をしているときに申し訳がないが、そもそもドメイン駆動設計の論点とは、設計全体ではなく、例えばMVCのM(モデル)の設計手法について語っているように感じます。 なので、ここでいうリポジトリはこれまでと違って、 「 データ」 をメインに扱うことになるので、コレまでやってきた内容とは一線を画すように感じます。 前回までは、できるだけ「データ」に注目することなく進んできましたが、データストアを扱う章なので、今回ばかりはドメインではなくデータに注目することになりそうです。 第1章でもドメインオブジェクトは知識をプログラムで表現するとありました。 リポジトリとはドメインオブジェクトではなく、ドメインオブジェクトに侵食しそうな プログラムを受け止め防衛するものかもしれません。 COLUMN: リポジトリはドメインオブジェクトを際立たせるでも書かれていることですね。 ユーザーみたいに簡単なエンティティの永続化の例はわかるけど、複雑なリレーションが組まれたものに対してはどのように実装すべき? これは経験の浅い方からの質問でしたが、私も同じことを思いました。 その日の読書会メンバーの中には答えを持っている人はいませんでしたので、私達なりに一つの結論を出しました。 中間テーブルにはリポジトリを作らず、臨機応変に対応すること。 例:ブログ記事とタグの関係 例えば、記事のみを扱うリポジトリを考えてみましょう。 copied.$articleEntity = new ArticleEntity(); $articleRepository = new ArticleRepository(); $articleRepository->save($articleEntity); class ArticleRepository { public function save(ArticleEntity $article) { // 記事の永続化処理 } } 他にもメソッドを持っているかと思いますが、とりあえず永続化のsaveを持ったArticleRepositoryを作成しました。 記事を作成したときに、このリポジトリのsaveを呼んで永続化します。 しかし、記事登録のユースケースはそれだけでしょうか?例えば、記事には1つ以上のタグが紐づくものとしましょう。 copied.$articleEntity = new ArticleEntity(); $articleRepository = new ArticleRepository(); $articleRepository->save($articleEntity); class ArticleRepository { public function save(ArticleEntity $article) { // 記事の永続化処理 if ($article->hasTags()) { // タグの永続化処理 } } } これではなにか不自然に感じてしまいます。ArticleRepositoryがタグの永続化処理を持っているのです。 ということは、$articleRepository->save($articleEntity);を呼んだ後、この処理をすべきでは無いでしょうか? copied.$articleEntity = new ArticleEntity(); $tagEntity = new TagEntity(); $articleRepository = new ArticleRepository(); $tagRepository = new TagRepository(); $articleRepository->save($articleEntity); if ($article->hasTags()) { $tagRepository->save($tagEntity); } class ArticleRepository { public function save(ArticleEntity $article) { // 記事の永続化処理 } } class TagRepository { public function save(TagEntity $article) { // タグの永続化処理 } } 記事とタグの紐付けは? これについても出てきましたが、前章エンティティでのディスカッションの通り、記事がタグを持っているかどうかをエンティティで表現すれば可能になるかもしれません。 copied.class ArticleEntity { public $tagIds; public function __construct() { // 省略 } public function hasTags(): bool { return count($this->tagIds) > 0; } public function getTags(): array { return $this->tagIds; } } やっぱりふるまいは持つべきではない。 弊社はPOSレジを扱う会社ですので、色々な集計関数をSQLを用いて取得することでより高速にレジを打てるように努力しています。 しかし、集計関数を利用すると、SQLにロジックを書くことになりそうです。その時リポジトリはこのようになってしまうかもしれません。 copied.class TransactionRepository { public function sum(): int { // 集計関数などを用いたSQL } } この著書ではExistsが例に挙げられていましたが、このsumも同様な気がします。 よほど困った理由がなければ上記のようなコードをRepository書かずにEntityに書くべきでしょう。 copied.class TransactionEntity { public function sum(): int { // 集計処理 } } 最後に リポジトリについては、データを扱うものからドメインオブジェクトを守るという考えが最初に理解できたので、簡単に理解できたと思います。 データストアに保存する処理や、データストアから取得する処理をできるだけ他のロジックから切り離して考えるというのは、今の知識でも理解できていますので。 リポジトリに何を書くかではなく、リポジトリをどのように扱う(呼び出す)かというのが大事になってくるのかもしれません。 次回は待ちに待ったアプリケーションサービスですね。 その時はよしなに。 .

社内でドメイン駆動設計入門の読書会 #4

2020/03/10 2020/03/14

社内でドメイン駆動設計入門の読書会 #4 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #3 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 第4章の不自然さを解決する「ドメインサービス」です。 サービスが指し示すもの ドメインサービスとは 不自然なふるまいを確認する 不自然さを解決するオブジェクト ドメインサービスの乱用が行き着く先 可能な限りドメインサービスを避ける エンティティや値オブジェクトと共にユースケースを組み立てる ユーザーエンティティの確認 ユーザー作成処理の実装 物流システムに見るドメインサービスの例 物流拠点のふるまいとして定義する 輸送ドメインサービスを定義する まとめ これは後で気づいたのですが、ドメインサービスとアプリケーションサービスを混同してしまう人は多いようです。 私はこの章を読んで自分も同じようになっていたことに気づきました。 ディスカッション ドメインサービスいらなくね?いつ使うの? 最初は私の質問でした。 私がそのように考えた理由は、以前に勉強した、「値オブジェクト」「エンティティ」の2つがあれば、実装ベースで困ることが無いと思っていたからです。 その2つさえあれば、大抵のことができますし、ドメインサービスが具体的にどのような実装をするのかわからなかったので、不要論を発言してしまいました。 当然この著書には4.2.1「不自然なふるまいを確認する」で、具体的なコードが書かれています。 エンティティは自分に関連する情報を操作する事はできますが、自分以外のエンティティの操作はすることはできませんし、自分自身がデータストアなどに存在するかどうかを確認することも不自然とあります。 copied.class User { public function __construct() { // 省略 } public function exists(User $user): bool { // 存在確認コード } } 自分自身に存在するexists関数に自分自身を放り込むというこの実装はとても不自然です。 ここでようやく私も気づけました。 (正直私は自分自身の存在チェックも自分自身が行うものという日々の価値観があったので、この考えにたどり着けなかったのです。) 上記のようなときに、ドメインサービスを利用します。 copied.class UserService { public function exists(User $user): bool { // 存在確認コード } } ドメインサービスにエンティティを渡すことで、エンティティ自身が解決できないことを解決しようということですね。 ドメインサービスって存在チェックくらいしか実装できなくね? 続いても私の疑問でした。 エンティティが自分自身で解決できないふるまいってなんだろうか?と考えたことがきっかけでした。 エンティティが自分自身だけでは解決できないようなふるまいを想像したとき、なにも思い浮かばなかったのです。 Uさん『それは4.5「物流システムに見るドメインサービスの例」にあるように、エンティティ同士の値のやり取りがその例に当てはまります。』 私『正直この例よくわからなかったんですよね。』 Uさん『この例以外によく見る例は、銀行口座のお金のやり取りです。エンティティは銀行口座ですが、口座自身がその相手先に対して出金や入金を行うわけではないですよね?』 私『そりゃそうですね。出金や入金を行うのは銀行です。』 Uさん『これをプログラムに落とし込むときに、口座エンティティは入金と出金という振る舞いを持つことになりますが、自分自身のエンティティしか操作できないという制約で、入金と出金をセットにした振る舞いは実装できないということになります。そこで、口座サービスを作成して、2つの口座エンティティの操作を行うのです。』 私『なるほど、物流システムの例もそれと同じなんですね。』 copied.$money = new Money(1000); $fromAccount = new AccountEntity(); $toAccount = new AccountEntity(); // 出金完了 $fromAccount->withdraw($money); // 入金完了 $toAccount->deposit($money); class AccountEntity { // 省略 public function deposit(Money $money) { // 入金処理 } public function withdraw(Money $money) { // 出金処理 } } このように処理の最中にダラダラと直接の書くのではなく、ドメインサービスにまとめてしまうのです。 copied.$money = new Money(1000); $fromAccount = new AccountEntity(); $toAccount = new AccountEntity(); $accountService = new AccountService(); $accountService->moneyTransafer($fromAccount, $toAccount, $money); class AccountEntity { // 省略 public function deposit(Money $money) { // 入金処理 } public function withdraw(Money $money) { // 出金処理 } } class AccountService { public function moneyTransafer( AccountEntity $fromAccount, AccountEntity $toAccount, Money $money ) { // 出金完了 $fromAccount->withdraw($money); // 入金完了 $toAccount->deposit($money); } } 要はエンティティの操作をするサービスなんでしょうか? これは最近入社したばかりの方の質問でした。 私もこれまでディスカッションした内容をまとめる限りそのようなサービスを連想するので、概ね納得できています。(より正確に言うと、値オブジェクトとエンティティに持てないような振る舞いを実装するサービス) あれ、じゃあアプリケーションサービスってなんなん? その方の質問というよりまとめで納得はしたものの、私はそのような操作はすべてアプリケーションサービスに書くべきなのでは?と思っていました。 それが完全に否定される形になったので、読書会が終わった今でも悶々とした日々を送っていますw これは第6章の「ユースケースを実現するアプリケーションサービス」がとても楽しみですね。 最後に ディスカッションで出たのはこんなものでしょうか? サービスというと、値オブジェクトやエンティティと違い、具体的なものを連想できないので 「理解するのが難しい」というより、「勘違いしている」 方が多いのでは?と思っています。 この著書は簡単なプログラムが載っていますので、わかりやすいですし、GitHubに完成された実務向きのコードもあるので、より実装ベースでサービスの考え方を勉強することができますので、そちらもオススメです。 次回はリポジトリですね。 その時はよしなに。 .

社内でドメイン駆動設計入門の読書会 #3

2020/03/04 2020/03/07

社内でドメイン駆動設計入門の読書会 #3 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。内容が気になる方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #3 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 第3章のライフサイクルのあるオブジェクト「エンティティ」です。 エンティティとは エンティティの性質について 可変である 同じ属性であっても区別される 同一性を持つ エンティティの判断基準としてのライフサイクルと連続性 値オブジェクトとエンティティのどちらにもなりうるモデル ドメインオブジェクトを定義するメリット コードのドキュメント性が高まる ドメインにおける変更をコードに伝えやすくする まとめ 僕はこのエンティティというのが、DDDを勉強していて一番わからないことでしたので、読むのが楽しみでした。 ディスカッション エンティティってなに? 最初は私の質問でした。 そもそもエンティティという言葉に馴染みがなく、エンティティとは言葉としてどういう意味を持つのかすら怪しいまでありました。 そこで意味を調べてみることにしました。 https://ejje.weblio.jp/content/entity 主な意味 実在、存在、実在物、実体、本体、自主独立体 Google翻訳では実体という意味を持つらしいです。 更に、私でも知っていたER図はEntity Relationship Diagramというとその時に教えていただきました。 ただ、ER図のエンティティと違うのは「振る舞いを持つ」ということですね。 ER図はDBの構造を示すので、ただの値の集合体ですが、この著書では振る舞いを持ちます。 値オブジェクトが、プログラムの知識をしめすなら、エンティティはその知識を集め、振る舞いを持たせることで実体にするという考えなのかもしれません。 値オブジェクトと違い、可変である これは本にも記載されていますが、値オブジェクトと違い可変です。 copied.class ProductId { private $id; public function __construct(int $productId) { $this->id = $productId; } public function toInt(): int { return (int)$this->id; } } 上記のように値オブジェクトは、一度生成したら、値を変更することはできませんが、 copied.class Product { /** @var ProductId */ private $id; /** @var ProductName */ private $name; /** @var ProductPrice */ private $price; public function __construct(ProductId $id, ProducrtName $name, ProductPrice $price) { $this->id = $id; $this->name = $name; $this->price = $price; } public function changePrice(ProductPrice $price) { $this->price = $price; } } エンティティは上記の用にクラスの持つ情報を変更することが許容されています。 更に変更する際に代入で変更するのではなく、振る舞いで属性を変更することになります。 ライフサイクルをもつって何? 私達は、「プログラム、アプリを使用する上で変更が生じること」と表現しました。 ここでは生と死と変更と表現されていますので、それと同じですね。 そう考えると、LaravelやCakePHPのModel機能にも納得ができるような気がしてきました。 これまで、私はMVCモデルのMをどんなコードを書けばいいのかわかりませんでしたが、この章を見た後に考え直したらこうなりました。 copied.// ProductはModelを継承 $model = new Product(); $product = $model->find((int)$id); // ライフサイクル $product->changePrice(200); // 先取りになってしまうが、永続化 $product->save(); class Product extends Model { public function changePrice(int $price): self { $this->price = $price; return $this; } } 上記はModelクラスをEntityに当てはめたときのコードですが、不自然じゃなさそうです。 この章を読むまでは copied.// ProductはModelを継承 $model = new Product(); $product = $model->findByName((string)$name); class Product extends Model { public function findByName(string $name): Product { return self::where('name', $name)->get(); } } のような、ただのデータベースに対するInterfaceのような使い方をしていました。 ということはライフサイクルのあるオブジェクトを操作するときはupdateで、入力されている値を操作するのではなく、一度Entityとして取得したあとにsaveするような動作の方がいいのかもしれません。 copied.$id = $request->get('id'); $name = $request->get('name'); // ProductはModelを継承 $model = new Product(); $product = $model->find((int)$id); // IDを使った強制的な更新のように見える $product ->update([ 'name' => (string)$name, ]); と書くより、 copied.$id = $request->get('id'); $name = $request->get('name'); // ProductはModelを継承 $model = new Product(); $product = $model->find((int)$id); // IDで参照したエンティティに対して、振る舞いで名前変更し、それを永続化 $product ->changeName((string)$name) ->save(); と書いたほうがライフサイクル的。 この例ではModelクラスを使用しているので、この著書のいうところのエンティティでは無いところに注意して下さい。 あくまでも、Modelクラスを利用してエンティティ的な動作をさせてみたというだけです。 何かを変更するとき、その変更されるものが他に影響するときのライフサイクル 私は今まで、何か要望が来たら「データ」から考えて設計する、DBから設計する手法をしていました。 しかしDDDはデータベースのデータから設計するのではなく、やりたいこと、叶えたいことすなわちドメインから設計をしていくという手法です。 考えを改めてみると、ライフサイクルですから何かが変更したときに、同期するように他の変更も変わるということはありそうです。 例えば、 copied.$id = $request->get('id'); $birthday = $request->get('birthday'); // 現在の日付と誕生日から年齢を計算する処理 $old = DateUtil::calcOld((string)$birthday); $user = new User(); $user = $user->find((int)$id); $user ->changeBirthday((string)$birthday) ->changeOld((int)$old) ->save(); class User extends Model { public function changeBirthday(string $birthday): self { $this->birthday = $birthday; return $this; } public function changeOld(int $old): self { $this->old = $old; return $this; } } とするよりも、 copied.$id = $request->get('id'); $birthday = $request->get('birthday'); $user = new User(); $user = $user->find((int)$id); $user ->changeBirthday((string)$birthday) ->save(); class User extends Model { public function changeBirthday(string $birthday): self { $this->birthday = $birthday; // 現在の日付と誕生日から年齢を計算する処理 $this->old = DateUtil::calcOld((string)$this->birthday); return $this; } } としたほうが、感覚的にも自然に変更できますので、ライフサイクル的ですね。 最後に ディスカッションで出たのはこんなものでしょうか? 値オブジェクトにエンティティがでてきたので、ディスカッションの中でも永続化の単語や、サービスの単語が飛び出してくるようになりました。 一応、その章以外の話にならないように意識しながらディスカッションを進めているのですが、材料が揃ってくるとその先について考えたくなってしまいますね。 今回はModelクラスに困惑した私が一つのブレイクスルーを手にした回でもありました。 次回はドメインサービスですね。 その時はよしなに。 .

社内でドメイン駆動設計入門の読書会 #2

2020/02/26 2020/03/07

社内でドメイン駆動設計入門の読書会 #2 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。内容が気になる方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #1 次回:会社でドメイン駆動設計入門の読書会 #3 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 第2章の値オブジェクトです。 値オブジェクトとは 値の性質と値オブジェクトの実装 不変である 不変のメリット 交換が可能である 等価性によって比較される 値オブジェクトにする基準 振る舞いをもった値オブジェクト 定義できないからこそわかること 値オブジェクトを採用するモチベーション 表現力を増す 不正な値を存在させない 誤った代入を防ぐ ロジックの散在を防ぐ まとめ この本で一番ページのある章だったはず、、、 それくらい大事な内容ということですね。 ディスカッション 値オブジェクト書いたことある? 僕はありませんでした。 しかし、リファクタリング入門 でクラス型についてすでに知っており、ValueObject(以下VO)ではなく、所謂クラス型として採用してきました。 例えば、 copied.class ProductId { private $id; public function setId(int $productId): void { $this->id = $productId; } public function getId(): int { return (int)$this->id; } } こんな感じで変数に型を付けるために採用したことはありました。 しかしこの本ではsetterが無く、値を再代入することができないように作ることで、不変性をもたせるということでした。 copied.class ProductId { private $id; public function __construct(int $productId) { $this->id = $productId; } public function toInt(): int { return (int)$this->id; } } このように書くことで、ProductIdのインスタンスを生成するときにしか、値をセットすることができないので、不変となります。一度作ったProductIdはこれ以降信頼できる変数として使用することができますね。 振る舞いをもつってどういうこと? VOなので、振る舞いをもつ値、変数ということに疑問を持った方の質問でした。 上で書いた copied.class ProductId { private $id; public function __construct(int $productId) { $this->id = $productId; } public function toInt(): int { return (int)$this->productId; } } にも値を返すtoIntしか存在しませんね。 回答ととして出た例として、弊社スマレジではPOSを開発しております。 POSレジなので、税抜、税込のデータは必須となっています。このようなとき、VOを使えば簡単だよねという話でした。 copied.class ProductPrice { /** @var int $price */ private $price; /** @var Tax $tax */ private $tax; /** @var TaxDivision $taxDivision */ private $taxDivision; public function __construct( int $productPrice, Tax $tax, TaxDivision $taxDivision ) { $this->price = $productPrice; $this->tax = $tax; $this->taxDivision = $taxDivision; } // ~~省略 public function toIncludedTax(): int { $price = $this->price; if (!$this->taxDivision->isIncludeTax()) { $price = $price * $this->tax->toDecimal(); } return floor($price); } } 上記のコードはかなり省いていますが、商品の各項目のVOを用意して商品型VO に対して、注入を行います。 こうすればProductは不変で税込、税抜を考慮した値段を取得できるふるまいを持つクラスとなります。 更に、 copied.class ProductPrice { /** @var int $price */ private $price; /** @var Tax $tax */ private $tax; /** @var TaxDivision $taxDivision */ private $taxDivision; public function __construct( int $productPrice, Tax $tax, TaxDivision $taxDivision ) { $this->price = $productPrice; $this->tax = $tax; $this->taxDivision = $taxDivision; } // ~~省略 public function toIncludedTax(): int { $price = $this->price; if (!$this->taxDivision->isIncludeTax()) { $price = $price * $this->tax->toDecimal(); } return floor($price); } public function addPriceIncludedTax(ProductPrice $price): int { return $this->toIncludedTax() + $price->toIncludedTax(); } } とすれば、他の商品単価を税込か税抜を意識せずに価格計算をすることができます。 copied.// 軽減税率で税抜価格 $lunchPrice = new ProductPrice(750, new Tax(8), new TaxDivision(0)); // 標準税率で税込価格 $teaPrice = new ProductPrice(150, new Tax(10), new TaxDivision(1)); // 税込価格の合計金額を取得 $sum = $lunchPrice->addPriceIncludedTax($teaPrice); 税計算まわりはバグを生みやすい箇所なので、VOの採用でそれが軽減できるならかなりいいものですね。 バリデーションについて あなたは「何回」入力チェックをしているだろうか?で記事を書きましたが、バリデーションは多階層に渡って実装すべき処理です。 VOに採用することで、更にセキュアな処理が実行できそうですね。 copied.<?php class ProductPrice { /** @var int $price */ private $price; /** @var Tax $tax */ private $tax; /** @var TaxDivision $taxDivision */ private $taxDivision; public function __construct( int $productPrice, Tax $tax, TaxDivision $taxDivision ) { $this->validate($productPrice); $this->price = $productPrice; $this->tax = $tax; $this->taxDivision = $taxDivision; } // ~~省略 public function validate(int $price): void { if (is_null($price)) { throw new InvalidArgumentException('Price is required.'); } if (!is_int($price)) { throw new InvalidArgumentException('Price must be integer.'); } } } Requestでバリデーション ValueObjectでバリデーション ビジネスロジックでバリデーション この3つを必ず行うことで、リリース前にかなりのバグを滅ぼすことができそうです。 一度実装したらVOを使い回すことになるので忘れなどの防止になりそうですね。 copied.class CreateProductAction extends Controller { private $productRepository; public function __construct(ProductRepository $productRepository) { $this->productRepository = $productRepository; } // CreateProductRequestでバリデーション public function __invoke(CreateProductRequest $request) { // ProductPriceでバリデーション $price = new ProductPrice((int)$this->request->getPrice()); // 登録時にバリデーション $this->productRepository->registerPrice(); // ~~省略 } } いつVOにするか?その粒度は? もう1文字ずつVOしちゃえば良くない??? いや、よくない。 ドメインに必要な分だけをVOにすればいいのではとディスカッションで結論づきました。本でも出てきていましたが、例えば、 copied.class FirstName { private $firstName; public function __construct(string $firstName) { $this->firstName = $firstName; } } class LastName { private $lastName; public function __construct(string $lastName) { $this->lastName = $lastName; } } class FullName { /** @var FirstName $firstName */ private $firstName; /** @var LastName $lastName */ private $lastName; public function __construct(FirstName $firstName, LastName $lastName) { $this->firstName = $firstName; $this->lastName = $lastName; } } 例えばこのコードを使用するアプリが、姓と名を別々に考えるアプリで、さらにフルネームとしても使う場合は、上記3つのクラスは必要かと思います。 しかし、フルネームをユーザー名として、姓名関係無く使用する場合は、FirstName / LastNameクラスは必要無いでしょう。 最後に ディスカッションで出たのはこんなものでしょうか? 全体的にやはりVOについてはポジティブにとらえていて、同実装すべきか、どこまでVOにすべきかと言った議論となっていました。 僕の個人開発プロジェクトでも、勉強がてら導入していますので、今後も試行錯誤していきたいと思います。 次回の読書回の内容も書く予定です。 その時はよしなに。 .

会社でドメイン駆動設計入門の読書会 #1

2020/02/18 2020/03/07

会社でドメイン駆動設計入門の読書会した話 こんにちは。Nonです。 今回は会社で読書会をしている話をしようと思います。 次回:会社でドメイン駆動設計入門の読書会 #2 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 今回読んだ内容は第1章ドメイン駆動設計についての触りでした。 ですので、特に疑問とかありませんでしたが、ディスカッションのときに出たメンバーの一言がすっごい納得できたので、共有しようと思います。 ドメインの考え方 Non:「図1.5の『ドメインの概念』と『ドメインモデル』と『ドメインオブジェクト』の垣根がよくわからんよなぁ」  A: 「うーん。例えば、車があるとして、『ガソリンの量』、『速度』とかをプロパティに設定しますよね?」 Non:「うんうん。わかる。」  A: 「そこに空を飛ぶ機能を追加するとしましょう。」 Non:「(車は飛ばんやろ……)」  A: 「すると『高度』ってプロパティを追加します。なぜなら、『ドメインの概念』が変わったので、『ドメインモデル』と『ドメインオブジェクト』も変化したからです。」 Non:「うおおおおおおお!!!めっちゃ納得した!これまでありえないことをプログラミングしないように気をつけていましたが、ドメインの変更を許容する考え方と言えるかもしれないということですね!」  A: 「そうです。なぜならドメインはドメインスペシャリストが付いて一緒に進めるものですから、ドメインの変更はできるだけプログラムに落とし込んでいきたいですよね。消費税だって軽減税率で消費税が2種類できたとドメインが変更したと言えるかもしれないです。」 これめっちゃ納得しました。 普段自社開発しているので、仕様を決めるのは自分でした。 しかもそのときに、「ありえないこと」「やってはいけないこと」に注視して、プログラミングしてきましたので、今回のAさんの一言でかなり価値観が変わりましたね。 最後に 今回は1章だけでしたので、気付きは少なめでしたが、Aさんの言った内容はぜひ共有したかったので、記事にしました。 次回以降は具体的な実装方法になってくるので、考え方とかではなくコードベースでの気付きはあるかもしれません。 毎週やっていくので、しばらくはこの記事になるかもしれません。 別の新個人開発プロジェクトでPWAの通知方法のコードも進んできているので、こちらも時間ができたらシェアしたいですね。 その時はよしなに。 .

ドメイン駆動設計入門

2020/02/14 2020/02/14

ドメイン駆動設計入門を買った話 こんにちは。Nonです。 今回はドメイン駆動設計入門という本を買った話をしたいと思います。 最近DDDで開発することが多くなってきた 最近のプロダクトでDDD(クリーンアーキテクチャ版)で開発することが多くなったので、この際に勉強しようということで購入しました。 Twitterでも評判高かったので、これを見た方は購入してみるのもいいかもしれません。 社内勉強会の教科書になる予定 僕が発足というわけではありませんが、社内で読書会を開こうということになりまして、みんながこれを購入した経緯もあります。 うまいこといくことを願って頑張ろうと思います。 ぶっちゃけかなり楽しみ 最後に 今回は技術本の紹介(と言っても社内勉強会の教科書になったというくらい)をさせていただきました。 最近忙しすぎて記事が薄い・・・ DDDについてそのうちまとめられたらなと言う次第です。 その時はよしなに。 .

家庭教師をやってみて

2020/02/05 2020/02/05

実は少し前から未経験の方の家庭教師をしていました こんにちは。Nonです。 今回は10月くらいから始めていたプログラミングの家庭教師についてお話しようと思います。 ハマりがちなところ? 配列と連想配列 以外と思うかもしれませんが、配列がわかっても連想配列がわからないことが多いです。 それに絡んでループなど入ってくると、さらにわからなくなるようです。 個人的には、ループと箱の関係はわかりやすくて参照やポインタなどのほうがよっぽどわかりにくいと思うのですが、なんででしょうかね? 数人持って数人ともそうだったので、僕の教え方の問題かもしれません。もう少し掘り下げる必要があるかと思います。 物事の考え方、論理的思考 これは個人で大きく違うと思いました。 できる人は言わずとも(プログラミングは完璧でなくても)完璧に近い構造で課題をこなします。 これが苦手な人は、プログラミングができていても構造がおかしい、みたいな課題を提出してきます。 気づいたのはユースケースを思い浮かべているかどうかだと感じました。 例えば、ただ商品の合計金額を算出する問題でも、現実では明細や小計、税額など色々な数字が登場し、その結果、合計が算出されると思います。 レシートなど見てもそうですよね。 これを頭で想像しながら組んだプログラムと、ただ合計金額を算出するだけのプログラムとでは、構造が天と地ほど違いました。 前者で組んだ生徒さんは一つ一つの計算と商品の小計を出す処理が独立していて、それらを全て加算した結果が合計という構造。 後者で組んでしまった生徒さんはひたすら四則演算で頑張ってしまったという構造 今では後者の生徒も構造化を理解できているので、問題ありませんが、学校を卒業した今でも、パッと思いつける人はいないように感じました。 やっぱりプログラミングに対するスタイルというかタイプは存在する デザイン重視 機能重視 今の所出会った生徒はこの2つのタイプが多かったです。 HTMLやCSSなどフロントが向いてるとか(今回で言えば)PHPなど裏の機能に向いてるとか言う話では無く、モチベーションがどう上がるかという話 どちらの生徒も、やはり定期的にプログラミングをやっていると飽きが来てしまうのですが、そのモチベを回復すべく結構工夫しました。 その試行錯誤の結果、わかったことです。 フロント、いわゆる見た目をきれいに整える(機能はまだ無い)手伝いをしてあげて、きれいな画面を見せるとモチベーションが上がる人 バック、いわゆる実際に動くよう(デザインはまだ無い)手伝いをしてあげて、機能を見せるとモチベーションが上がる人 がいました。もしかしたらプログラミングに対する考えというか、求めるものが結構違うのではと思うきっかけになりました。 アプリを作りたいと言うのは便利なアプリを作りたいのか、美しいアプリを作りたいのかとかいう話になりそうです。(自分でもまだ噛み砕いていない) コツ やっぱり先生から熱意を見せることは大事だと思いました。 僕は何かを伝えるとき、結構声に抑揚がついたり、声が大きくなったりします。僕的には(起怒っている風に見えないかな?)とか思っているのですが、以外とそうでもなく、「おとなしい人でも」しっかり聞いて頑張ろうとしていることがわかりました。 できるだけ自由に 最初は冒頭に言ったような課題を用意していたのですが、モチベーションが下がっていくのは目に見えてわかりましたので、自由な課題を用意して、アドバイスを出していくようにしたらうまくいくようになりました。 最後に 今回は未経験の方にプログラミングを教えるときのコツのようなものに関して書いてみましたが、僕も初めて半年くらいなものですので、当てにならないかもしれません。 皆さんの力になれれば嬉しいです。 また更新するかもしれません。 その時はよしなに .