社内でドメイン駆動設計入門の読書会 #13
2020/05/25 2020/05/25社内でドメイン駆動設計入門の読書会 #13 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #12 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 複雑な条件を表現する「仕様」 仕様とは 複雑な評価処理を確認する 「仕様」による解決 リポジトリの仕様を避ける 仕様とリポジトリを組み合わせる お勧めサークルに見る複雑な検索処理 仕様による解決法 仕様とリポジトリが織りなすパフォーマンス問題 複雑なクエリは「リードモデル」で まとめ ディスカッション 今回は評価と参照 今までのドメインは「ユーザーを作成する」とか、「サークルにメンバーを追加する」など、追加、更新、削除。つまりライフサイクル周りに注目するユースケースばかりでしたが、今回はサークル一覧をどう見せるか、どのようにサークル検索するか、に注目していた章でした。 正直な話、プログラミングにおいて「参照」が一番書かれる頻度が高いと(個人的に)思っているので、気になる章ではありました。 ディスカッションにおける最終的な結論 参照はドメイン駆動で設計しなくてよくね? 参照はデータ参照といっても過言では無いくらいです。 多少の加工はありますが、それは果たしてドメインと言えるのかも甚だ疑問です。 例えば登録されている商品の値段を税抜価格から、税込価格へ変換する処理は知識とは呼べそうですが、Entityに書くほどでしょうか? また、1000円以上の商品を赤文字で表示するという画面側の要望があるときにその振る舞いをEntityに書くべきなのでしょうか? これに対する結論が「リードモデル」だそうです。 登録 / 更新 / 削除用のドメインオブジェクトとは別に参照用のドメインオブジェクトを作成する言えばいいでしょうか? 少し誤解(語弊)があるかもしれませんが、そのように私は受け取りました。 参照は性能にも密接に関わる 登録件数1億件のデータにアクセスするとき、単純なクエリで一発で取れるなら、プログラムで処理して、時間をかけるより、SQL一発で簡単にとってそのままレスポンスとして返したほうがユーザーのためになるという意見も今回のディスカッションの中で出たくらいです。 CQRSという考えもあるらしいので、全てにおいてドメイン駆動設計で実装する必要はなさそう。というのが、今回の章で言いたい部分の一つだったのかもしれません。 参照を別の設計で駆動させるときのディレクトリ構造 src┓ ┣User┓ ┃ ┣CreateUser┓ ┃ ┣User ┃ ┣UserName ┃ ┣GetUser┓ ┃ ┣全く別の設計手法で書かれたコード ... となりそうです。 少なくとも、私はそのように書いています。 Entityでgetterはできるだけ書きたくない これが、一貫してこの本に書かれているのですが、この理論で行くと参照系とは相性の悪い(?)手法なのかもしれません。 CQRSについては別でまた記事にしたい CQRSについては勉強不足なので、また別の機会に。 最後に 実はドメイン駆動設計で組むぞ!って始めたプロジェクトで一番はじめに組んだコードが参照系でした。 かなり苦労したので、別の手法でコーディングするのは納得できています。 CQRSとまでは行きませんが、単純なSELECT処理をして、フロントが欲しい物を多少加工して返しています。これのせいでコードが汚くなるということも無いですし(参照系ですので)、問題ないと思います。 次回はアーキテクチャです。 また記事にしますので、その時はよしなに。 .
社内でドメイン駆動設計入門の読書会 #12
2020/05/18 2020/05/18社内でドメイン駆動設計入門の読書会 #12 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #11 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 ドメインのルールを守る「集約」 集約とは 集約の基本的構造 オブジェクトの操作に関する基本的な原則 内部データを隠蔽するために 集約をどう区切るか IDによるコンポジション 集約の大きさと操作の単位 言葉との齟齬を消す まとめ ディスカッション 今回はより現実的な話 今までに挙がった例はどれも単体での動作のみに注目してきましたが、現実のドメインは様々なEntityが複雑に関連しています。 そうなるとEntityは肥大化していきますし、振る舞いも相当なものになるでしょう。 そういうときに、どのようにまとめるべきなのか。ということを話題にしていました。 正直まだよくわからない。 最初から申し訳ないですが、集約難しいです。 現実の関心事を論理的に切り分けるのは一苦労です。 なので、この本にかかれている内容を一部抜粋して、私の考えを記していきたいと思います。 ユーザーとサークルの関係 ユーザーはサービスを利用するにあたって、必須な情報でしょう。 ログインするため アプリを利用するため ログを残すため 色々なことにユーザー情報が利用されることでしょう。 対して、サークルも同様です。 アプリをより楽しく利用するため サークル内でやり取りすることがアプリの真髄 など、サークル作ることは必須ではありませんが、サークル作らないことはこのアプリにとって利用していないのとほとんど変わらない場合を考えます。 するとユーザー⇔サークルは密接関係を持つことがわかります。 サークルとユーザーのドメインは? ユーザー作成にサークルが関係無いことはわかります。 サークル作成にはユーザーが必須に感じますね。 class UserService { private $circleFactory; public function __construct(CircleFactoryInterface $circleFactory) { $this->circleFactory = $circleFactory; } public function createCircle(CircleName $circleName): Circle { return $this->circleFactory->create($circleName); } } class Circle { private $id; private $circleName; private $members; public function __construct(int $id, CircleName $circleName, MemberCollection $members) { $this->id = $id; $this->circleName = $circleName; $this->members = $members; } // ...(略) } class CircleName { private $circleName; public function __construct(string $circleName) { if (mb_strlen($circleName) > 50) { throw new OverflowException('サークル名は50文字以内です。'); } $this->circleName = $circleName; } public function __toString(): string { return (string)$this->circleName; } } class MemberCollection { private $members; public function __construct(array $members = []) { foreach ($members as $member) { if ($member instanceof User) { throw new RuntimeException('Member is must be User type.'); } } $this->members = $members; } public function toArray(): array { $result = []; foreach ($this->members as $member) { $result[] = [ 'id' => $member->id(), 'name' => $member->name(), ]; } return $result; } } のような感じでしょうか。ユーザーサービスはサークルEntityを作成するふるまいを持ちます。 このとき、注目すべきEntityがユーザーEntityとサークルEntityで2つ出てきます。 ユーザーEntityはユーザーの名前を変更する振る舞いを持ちますが、今回の注目ごとはサークルの作成です。果たしてこの振る舞いがこのユースケースと関係あるでしょうか? このふるまいがあるせいで、関心事が散漫とならないでしょうか? また、このコードを是とすると、changeName以外のふるまいがユーザーEntiryに集中することになるので、サークル作成ドメインに違う関心事が書かれたコードがどんどんと追加されることになっていまいます。 ドメインごとにUserEntityを作成するべき? もしかしたら境界づけられたコンテキストの方の分野かもしれませんが、ドメインごとにEntityを分ける戦略が考えられます。 src┓ ┣User┓ ┃ ┣CreateUser ┃ ┣User ┃ ┣UserName ... ┣Circle┓ ┃ ┣CreateCircle ┃ ┣Circle ┃ ┣CircleName ┃ ┣User ┃ ┣UserService ... ┣Domain ... うーん。でもCircleに関心事があるドメインにUserServiceがあるのが少し違和感。本では容赦なく使用していましたが、Circleを作成するにはUserにも関心が向くという知識がドメインに与えられるのでいいことなのかもしれない? EntityはEntityを持つことがある? class MemberCollection { private $members; public function __construct(array $members = []) { foreach ($members as $member) { if ($member instanceof User) { throw new RuntimeException('Member is must be User type.'); } } $this->members = $members; } public function toArray(): array { $result = []; foreach ($this->members as $member) { $result[] = [ 'id' => $member->id(), 'name' => $member->name(), ]; } return $result; } } に書いたように、CircleはMemberとしてUserを持ちます。 if ($member instanceof User) { throw new RuntimeException('Member is must be User type.'); } の部分。 なので、CreateCircleというドメインにEntityが2つあるのは是とするのが、普通の考えなのかもしれません。 Circleに所属するのはUserなので、Userの知識を持つUserをCircleが持つというのは自然な考えだと思います。 この辺はもっとドメイン駆動設計に慣れたらまとめ直したい。 正直、腑に落ちていない点が少しあります。この状態でかくあるべきというのは危険ですので、これ以上の展開は避けておきますね。 最後に ちょっと難しい話でした。それぞれのEntity同士の関係になると一気に難しくなった気がします。ここを理解(納得)できないと、実装も揺らぎそうで怖いです。 次回は複雑な嬢他県を表現する「仕様」です。 また記事にしますので、その時はよしなに。 .
社内でドメイン駆動設計入門の読書会 #11
2020/05/11 2020/05/11社内でドメイン駆動設計入門の読書会 #11 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #10 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 アプリケーションを1から組み立てる アプリケーションを組み立てるフロー 題材とする機能 サークル機能の分析 サークルの知識やルールをオブジェクトとして準備する ユースケースを組み立てる 言葉との齟齬が引き起こす事態 漏れ出したルールがもたらすもの まとめ ディスカッション 今回は実装ベースでの話 今回は実装する時にどのようにコーディングしていくかという話でまとめに入っていました。 実装方法は人それそれですし、この本の手法もあくまで一例で書かれているだろうということで、ここには私の所感を書かせていただきます。 ディレクトリの切り方はドメイン毎 ディレクトリはフレームワークに依存しない方がいいと思います。 もちろん、ドメインオブジェクトをフレームワーク依存させる方針であれば問題ありません。 このようにしておかないと、フレームワークの変更時に再利用できないからです。 app━laravel src┓ ┣Domain ┣Domain ┣Domain ... ドメインオブジェクトから作る 1. Entityが最重要 ドメインオブジェクトの中で最重要なのはEntityではないかという結論になりました。 ライフサイクル(ふるまい)を持つ注目したいドメインの実態というのがその理由となります。 class User { private $id; private $name; public function __construct(int $id, UserName $name) { $this->id = $id; $this->name = $name; } public function changeName(UserName $userName): void { $this->useName = $userName; } public function id(): int { return (string)$this->name; } public function name(): UserName { return $this->name; } } このEntityから読み取れるのはユーザーは自分のユーザー名を変更することができるということですね。 2. Entityを構成するValueObject そして、そのEntityに知識をもたせるという意味でValueObjectを作成していきます。 class UserName { private $userName; public function __construct(string $userName) { if (mb_strlen($userName) > 20) { throw new OverflowException('ユーザー名は20文字以内です。'); } $this->userName = $userName; } public function __toString(): string { return (string)$this->userName; } } ここから読み取れるのはユーザー名は20文字以内という知識がここに溜まることになります。 3. ライフサイクルを永続化するためのRepositoryInterfaceなど ユーザー情報をデータストアに永続化するためのRepositoryの抽象クラスを作成します。 interface UserRepositoryInterface { public function save(User $user): User; } interface CircleFactoryInterface { public function create(CircleName $circleName): Circle; } 4. 関連するDomainService 最後に関連するドメインサービスです。ここではユーザーはCircleを追加できることがわかるドメインオブジェクトとしてのふるまいが存在します。 class UserService { private $circleFactory; public function __construct(CircleFactoryInterface $circleFactory) { $this->circleFactory = $circleFactory; } public function createCircle(CircleName $circleName): Circle { return $this->circleFactory->create($circleName); } } class Circle { private $id; private $circleName; private $members; public function __construct(int $id, CircleName $circleName, MemberCollection $members) { $this->id = $id; $this->circleName = $circleName; $this->members = $members; } // ...(略) } class CircleName { private $circleName; public function __construct(string $circleName) { if (mb_strlen($circleName) > 50) { throw new OverflowException('サークル名は50文字以内です。'); } $this->circleName = $circleName; } public function toString(): string { return (string)$this->circleName; } } class MemberCollection { private $members; public function __construct(array $members = []) { foreach ($members as $member) { if ($member instanceof User) { throw new RuntimeException('Member is must be User type.'); } } $this->members = $members; } public function toArray(): array { $result = []; foreach ($this->members as $member) { $result[] = [ 'id' => $member->id(), 'name' => $member->name(), ]; } return $result; } } のような感じでしょうか。 著書にはもっと具体的な内容が載っていますので、気になる方は是非。 5. ドメインオブジェクトを駆使してユースケースを作成 class CreateCircle { private $userService; public function __construct(UserService $userService) { $this->userService = $userService; } public function process(CreateCircleInputPort $inputPort, CreateCircleOutputPort $outputPort) { $circleName = new CircleName($inputPort->circleName()); $circle = $this->userService->createCircle($circleName); $outputPort->output($circle); } } interface CreateCircleInputPort { public function circleName(): string; } interface CreateCircleOutputPort { public function output(Circle $circle): void; } という順序で作成していけば、プログラミングに沿った考えではなく現実世界の本来のユースケース、つまりドメインを中心にプログラミングすることができそうです。 最後に 一度ここで、中盤のまとめ的なポジションの章でした。 次回はドメインのルールを守る「集約」です。 また記事にしますので、その時はよしなに。 .
社内でドメイン駆動設計入門の読書会 #10
2020/05/05 2020/05/05社内でドメイン駆動設計入門の読書会 #10 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #9 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 データの整合性を保つ 整合性とは ユニーキーに制約による防衛 ユニークキー制約を重複確認の主体としたときの問題点 ユニークキー制約との付き合い方 トランザクションによる防衛 トランザクションを取り扱うパターン トランザクションスコープを利用したパターン AOPを利用したパターン ユニットおぶワークを利用したパターン トランザクションが引き起こすロックについて まとめ ディスカッション といっても今回はDDDというよりもトランザクションについての話題でしたので、メインとなる内容ではなさそうでした。 実装するときトランザクション処理どこではる? というのはDDDについてよく知っている先輩の方からの質問でした。疑問を投げ掛けられるのは珍しかったのでびっくりしました。 私はアプリケーションサービスに貼るべきだと答えました。 実際、この本にもアプリケーションサービスに貼ってあるところがありましたし、ユースケース全体に貼るのが一番いいのかなと思っています。 class ApplicationService { public function process() { DB::beginTransaction(); try { // 処理 DB::commit(); } catch (Throwable $e) { DB::rollback(); // 処理 } } } 何故そのようなことを聞いたのか尋ねると。 コントローラーに貼るべき? 先輩はコントローラーに貼るべきなんじゃないのか?と疑問に思っていたようです。 DB::beginTransaction();この箇所はLaravelのファザードで書かれていますが、この時点でLaravelに依存しています。 それをとても嫌ったようです。私も言われてみれば確かにそうだなと思いました。言語は同じなもののクリーンアーキテクチャで依存をできるだけ剥がしているのに、わざわざlaravelのコードを使ってトランザクションを使うせいで依存が発生しています。 コントローラーはLaravel側で受け止めるので、そこで貼るべきなのでは?というのがその方の意見でした。 class Controller { private $useCase; public function __construct(ApplicationServiceInterface $useCase) { $this->useCase = $useCase; } public function __invoke() { DB::beginTransaction(); try { $this->useCase->process(); DB::commit(); } catch (Throwable $e) { DB::rollback(); // 処理 } // 処理 } } class ApplicationService { public function process() { // ユースケース } } こうすれば依存しているのはLaravelの機能であるコントローラーです。 ユースケースに依存は発生していません。 でもユースケース内でトランザクション貼れてない これもまた問題です。 トランザクションをフレームワーク依存にならないようになにか実装を行う この本にかかれているAOPやユニットオブワークを利用する この辺が解決策になりそうです。 あとは最初からフレームワーク依存は諦める選択ですかね。フレームワークのアップデートについていけないコードになってしまう可能性はありますのでおすすめできませんが。 境界づけられたコンテキストとトランザクション 正直「境界づけられたコンテキスト」というのを完全に理解しているわけではないので、なんとも言えませんが、ある時ある方が トランザクションを貼る目安として境界づけられたコンテキストを見るのは手法としてありじゃない? といっていました。 集約(まだこの章では出てきていない)の単位でトランザクションは貼るべきというのはデータの整合性的に間違いないので、なにか関係が深そうではあります。 最後に 基本的になトランザクションをDDDでどのように使うべきかというのがこの章のお話だったと思います。 でも、他の本では結構深堀りして書かれているらしいので、重要なところでもあるというのがその雰囲気からわかります。今回は設計手法についての勉強なのでこれ以上深堀りはしませんが、気になる人は調べてみてもいいかもしれません。 次回はアプリケーションを1から組み立てるです。 また記事にしますので、その時はよしなに。 .
社内でドメイン駆動設計入門の読書会 #9
2020/04/21 2020/04/21社内でドメイン駆動設計入門の読書会 #9 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #8 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 複雑な生成処理を行う「ファクトリ」 ファクトリの目的 採番処理をファクトリに実装した例の確認 自動採番機能の活用 リポジトリに採番用のメソッドを用意する ファクトリとして機能するメソッド 複雑な生成処理をカプセル化してみよう まとめ ファクトリパターンをあまり勉強せずにこれまでやってきた私にとって、とても勉強になる章でした。 ディスカッション 自動採番をプログラムで行う ちなみに、私は採番処理をプログラム側で制御するという発想そのものが新しい価値観でした。 これまで携わった案件のほとんどがRDBを採用したプロジェクトでしたので。RDBを採用しないアプリはそもそもデータストアが必要ないというプロジェクトでした。 ファクトリってどういうときに使えばいいの? バックエンドで多いのはEntity作成時じゃない? Entityを作成する = データストアにレコードが存在する。 なぜならEntity作成時にレコードが存在しないと、そのまま永続化しない場合、IDに揺らぎが起きてしまうから。これは第3章でも上がっていた内容だったはずです。 ということはEntity作成時はレコードを登録する必要があるかもしれません。 class ProductEntity { private $id; private $name; private $price; public function __construct(string $name, int $price) { $product = (new Product())->newQuery()->create([ 'name' => $name, 'price' => $price, ]); $this->id = (int)$product->getAttribute('id'); $this->name = (string)$product->getAttribute('name'); $this->price = (int)$product->getAttribute('price'); } } 嘘やろ?といった感じのコードですね。 ちなみに自動採番機能もMySQLに依存しています。これを許容するにしても、なんとなく気持ち悪いです。 この__constructで行っている生成過程をFactoryに移すとこうなります。 class ProductEntityFactory { public function newProductEntity(string $name, int $price): ProductEntity { $product = (new Product())->newQuery()->create([ 'name' => $name, 'price' => $price, ]); return new ProductEntity( (int)$product->getAttribute('id'), (string)$product->getAttribute('name'), (int)$product->getAttribute('price') ); } } class ProductEntity { private $id; private $name; private $price; public function __construct(int $id, string $name, int $price) { $this->id = $id->getAttribute('id'); $this->name = $name; $this->price = $price; } } こうすることでEntityの作成処理はFactoryが行うことになるので、Entityのコードもきれいになりました。 このような効果がありそうです。 大量INSERTなどは? 高速化を目的としたときは素直にバルクインサートしたほうが良いかも。 大量データの登録はCSVの情報をそのまま登録するようなドメインの薄いユースケースでしょうし、問題ありません。ただ、サービス化はしておいて変更に強いように設計する必要はありそうですが…… もし複雑な変換処理がある場合は、EntityではなくCollectionにして、Collectionのふるまいで値の加工をします。 その後でtoArray()などで配列にしバルクインサート、などの手法も考えることができますね。 もちろんファクトリにもInterfaceクラスを実装する 前章で抽象クラスに依存せよ。とありましたが、ファクトリもこれに従ったほうが良いです。 class ProductApplicationService { private $factory; public function __construct(ProductEntityFactoryInterface $factory) { $this->factory = $factory; } public function process():void { $product = $this->factory->newProductEntity('おにぎり', 100); } } interface ProductEntityFactoryInterface { public function newProductEntity(string $name, int $price): ProductEntity; } class ProductEntity { private $id; private $name; private $price; public function __construct(int $id, string $name, int $price) { $this->id = $id->getAttribute('id'); $this->name = $name; $this->price = $price; } } こうするとnewProductEntity()の実装は自由自在ですね。 最後に ファクトリパターン難しそうなので敬遠していたのですが、一度違う本でも呼んでみるとスッと入ってきました。 これまでconstructにダラダラ書いていたのは何だったのか…… 次回はデータの整合性を保つです。 また記事にしますので、その時はよしなに。 .
社内でドメイン駆動設計入門の読書会 #8
2020/04/16 2020/04/16社内でドメイン駆動設計入門の読書会 #8 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #7 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 ソフトウェアシステムを組み立てる ソフトウェアに求められるユーザーインターフェース コマンドラインインターフェースに組み込んでみよう メインの処理を実装する MVCフレームワークに組み込んでみよう 依存関係を設定する コントローラーを実装する ユニットテストを書こう ユーザ登録処理のユニットテスト まとめ 今回はDDDをするにあたっての準備をするための手法を例を挙げて話していました。 ディスカッション 今回は特に議論なし。 今回はどのようにドメイン駆動設計を実現してみようか。というお話に注意して書かれていました。 私達はLaravelを前提に話が進んでいたので、特にこれといった疑問もなくディスカッションを終えました。 (時間が空いたので、7章までの振り返りをしました。) 普段C#や、Laravel以外のフレームワークを使用している方には参考になる章かもしれないので、気になった方は一度見てみると良いかもしれません。 最後に 次回はFactoryの話になるそうです。 また記事にしますので、その時はよしなに。 .
社内でドメイン駆動設計入門の読書会 #7
2020/04/10 2020/04/10社内でドメイン駆動設計入門の読書会 #7 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #6 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務で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年半くらい前まで、設計のことなぞ全く考えずに実装してきたので、最近ようやく触れることができてとても嬉しいです。 抽象クラスなどの恩恵はこれだけでは無く、プログラムの設計を理解する上でも重要なことみたいですので、他のデザインパターンと併せて勉強して行きたいと思います。 また記事にしますので、その時はよしなに。 .
社内でドメイン駆動設計入門の読書会 #6
2020/04/01 2020/04/03社内でドメイン駆動設計入門の読書会 #6 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #5 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 ユースケースを実現する「アプリケーションサービス」 アプリケーションサービスとは ユースケースを組み立てる ドメインオブジェクトから準備する ユーザ登録処理を作成する ユーザ情報取得処理を作成する ユーザ情報更新処理を作成する 退会処理を作成する ドメインのルールの流出 アプリケーションサービスと凝集度 凝集度が低いアプリケーションサービス アプリケーションサービスのインターフェース サービスとはなにか サービスは状態を持たない まとめ 待ちに待ったアプリケーションサービスの章ですね。 ドメインサービスとアプリケーションサービスを混同して考えていた私にとって、とても勉強になる内容でした。 ディスカッション ドメインサービスとの違いは? 結論から言うとドメインサービスはVO・Entityに書ききれないサービスを記述するサービス。アプリケーションサービスはユースケースを記述するサービスです。 ドメインサービスはドメインのためのサービスですが、アプリケーションサービスはユーザー(使い手)のためのサービスとも言えるかもしれません。 ふるまいを持ったVOやEntityを用意し、必要に応じてRepositoryで永続化処理を行う。 これら一連の流れそのものがユースケースなので、この内容が書かれたクラスはアプリケーションサービスという事になります。 MVCモデルにおけるコントローラーアクションと混同しがち!? 私はこの考えで進んでしまっていました。 class UserController extends Controller { public function store() { // ここに書かれることが、アプリケーションサービス } } アプリケーションサービスはあくまでもサービス アプリケーションサービスはあくまでサービスですので、リクエストの処理やレスポンスの処理をアプリケーションサービスに書くことはできません。 リクエスト形式が違う場合や、レスポンスを同出力するかだけで、やりたいことは同じということは結構あります。(ターミナルからやるか画面でやるかの違いだけとか) その時、inputとoutputだけが違う内容が同じアプリケーションサービスを2つ作成することはとても管理が面倒臭くなります。 なので、こうすべき。 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を採用するのも良いかもしれません。 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/23Z250で伊勢志摩ツーリングに行ってきました!! こんにちは。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: リポジトリはドメインオブジェクトを際立たせるでも書かれていることですね。 ユーザーみたいに簡単なエンティティの永続化の例はわかるけど、複雑なリレーションが組まれたものに対してはどのように実装すべき? これは経験の浅い方からの質問でしたが、私も同じことを思いました。 その日の読書会メンバーの中には答えを持っている人はいませんでしたので、私達なりに一つの結論を出しました。 中間テーブルにはリポジトリを作らず、臨機応変に対応すること。 例:ブログ記事とタグの関係 例えば、記事のみを扱うリポジトリを考えてみましょう。 $articleEntity = new ArticleEntity(); $articleRepository = new ArticleRepository(); $articleRepository->save($articleEntity); class ArticleRepository { public function save(ArticleEntity $article) { // 記事の永続化処理 } } 他にもメソッドを持っているかと思いますが、とりあえず永続化のsaveを持ったArticleRepositoryを作成しました。 記事を作成したときに、このリポジトリのsaveを呼んで永続化します。 しかし、記事登録のユースケースはそれだけでしょうか?例えば、記事には1つ以上のタグが紐づくものとしましょう。 $articleEntity = new ArticleEntity(); $articleRepository = new ArticleRepository(); $articleRepository->save($articleEntity); class ArticleRepository { public function save(ArticleEntity $article) { // 記事の永続化処理 if ($article->hasTags()) { // タグの永続化処理 } } } これではなにか不自然に感じてしまいます。ArticleRepositoryがタグの永続化処理を持っているのです。 ということは、$articleRepository->save($articleEntity);を呼んだ後、この処理をすべきでは無いでしょうか? $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) { // タグの永続化処理 } } 記事とタグの紐付けは? これについても出てきましたが、前章エンティティでのディスカッションの通り、記事がタグを持っているかどうかをエンティティで表現すれば可能になるかもしれません。 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にロジックを書くことになりそうです。その時リポジトリはこのようになってしまうかもしれません。 class TransactionRepository { public function sum(): int { // 集計関数などを用いたSQL } } この著書ではExistsが例に挙げられていましたが、このsumも同様な気がします。 よほど困った理由がなければ上記のようなコードをRepository書かずにEntityに書くべきでしょう。 class TransactionEntity { public function sum(): int { // 集計処理 } } 最後に リポジトリについては、データを扱うものからドメインオブジェクトを守るという考えが最初に理解できたので、簡単に理解できたと思います。 データストアに保存する処理や、データストアから取得する処理をできるだけ他のロジックから切り離して考えるというのは、今の知識でも理解できていますので。 リポジトリに何を書くかではなく、リポジトリをどのように扱う(呼び出す)かというのが大事になってくるのかもしれません。 次回は待ちに待ったアプリケーションサービスですね。 その時はよしなに。 .