ボーイスカウト・ルールを現実的に実現するためには?
2020/07/06 2020/07/06プログラマが知るべき97のこと こんにちは。のんです。 本日はプログラマが知るべき97のことを読んでいてこれ「いいな」と思った箇所について書いてみようと思います。 こちらの書籍はとても有名なので、既読の方もいらっしゃるかもしれません。 そういう方は復習というか、内容を思い出したり、私の意見と違うところや共感できるところを探していただければ幸いです。 ボーイスカウト・ルールってなに? プログラマが知るべき97のことにはこう書いています。 ボーイスカウトには大切なルールがあります。それは、「来た時よりも美しく」です。たとえ自分が来た時にキャンプ場が汚くなっていたとしても、そしてたとえ汚したのが自分でなかったとしても、綺麗にしてからその場を去る、というルールです。 最近、私の町ではボーイスカウトの姿を見なくなってしまいました。私が子供の頃は友達がボーイスカウトしていたこともあって結構身近な存在だったのですが、現在はどうなんでしょうか? しかし、これまでの人生このような光景は見たことあるかもしれません。ボーイスカウトに限らず、小学校で美化活動として昼過ぎくらいに町内清掃している姿くらいは見かけているはずです。 「この考えをプログラミングにも取り入れようよ」という考えがボーイスカウト・ルールです。 結構簡単そうじゃない?いえいえ、かなり難しいはずです。 コードに散々しているゴミコードをただ修正変更するのは結構簡単と思えるかもしれませんが、私は難しいと感じます。その理由を1つずつ挙げると、 バグ修正は気軽にできない すでに正常動作しているコードを修正することへの心理的ハードルが高い 納期に追われているなど、時間的余裕が無い コミットをきれいに保ちたい。(ブランチ作成や切り替え作業が面倒くさい) すぐに思いつくのは以上のような理由でしょうか?他にも色々あるかもしれませんが、多分共感していただけると思います。 バグ修正は気軽にできない バグ修正は気軽にできません。影響範囲の調査や、バグを修正したことによるデグレーションを考慮しなければならなくて、とても工数がかかります。ゴミ拾いと同じ感覚で修正していたらいつの間にかscrap & buildしていたとかなりかねません。(時間的余裕と、影響範囲潰しができればとてもGJ👍ですが!!) すでに動作しているコードを修正することへの心理的ハードルが高い 四則演算の効率化や簡単化など、ちょっとした処理の修正に対して変更を行うことを想定しましょう。すでに正常に動作しているコードを修正すると考えるだけで不安になります。心の中で、(うーん……次のマイナーバージョンでするか)みたいになるのは火を見るより明らかです。 納期に追われているなど、時間的余裕が無い。 これは簡単ですね。そもそもその程度の修正をする時間すら存在しない時です。 コミットをきれいに保ちたい 一般的にGitを用いて開発をする場合、ブランチは機能単位に切られ、マージできるレベルでコミットされるはずです。あるAという機能を作成しているときに、ボーイスカウト・ルールに則って修正を行うと、A機能のためのブランチにBの修正が混じることになります。これを嫌がる人や、是としないチームはたくさんあるはず(というか、そういうルールが推奨されているはず)です。 そういうルールがある中でボーイスカウト・ルールを適用しようと思うと、(Aという機能を作るという別の仕事をしているにも関わらず、)ブランチを新しく作成したり、マネージャーへ連絡したりしなければなりません。 それが面倒臭いという人は必ずいるはずです。(私もそうでした。) こういった「課題をクリアする」というより、ボーイスカウト・ルールを適用しやすい環境を作る方がとても簡単です。上記の課題を例にボーイスカウト・ルールを適用しやすい環境を考えてみました。 (adsbygoogle = window.adsbygoogle || []).push({}); ボーイスカウト・ルールのハードルを下げる 見かけたバグはすぐ直す。このルールをそう受け取る方もいるかも知れませんし、そう思っていない方も、義務感から修正への執念を持っているかもしれません。私の知り合いには、あまりにひどいコードに遭遇し、もはやこの考えが強迫観念となっている方がいました。 もっと気軽に考えましょう。以下のポイントを押さえ、継続すればうまくいくはずです。 変数名や関数名本当に軽微なポイントにのみ注目する ノルマなどは当然設けない 修正というより整頓と考える(後述) 関連して、一気に修正 / 整頓しない 修正というより整頓と考える 例えば、引き継いだプロジェクトで下記のようなコードに出会ったとしましょう。自分はある程度経験を積んで、よくわからない処理を調査していました。数十分ほどかけて、そのロジックを解き明かしたとしましょう。 copied.<?php class DustSample { /** * @var string */ private $dust; /** * @param string $dust */ public function __construct(string $dust) { $this->dust = $dust; } public function unknown() { // 難解なコードで自分が頑張って読み解いた重要な処理 } } この時、unknowメソッドをボーイスカウト・ルールに則り修正するのではなく、コメントを書くなどして、わかりやすくしたり、一部privateメソッドなどで切り分けられそうなものを切っておきましょう。だから修正より整頓と考えるわけです。 copied.<?php class DustSample { /** * @var string */ private $dust; /** * @param string $dust */ public function __construct(string $dust) { $this->dust = $dust; } /** * 〇〇を✕✕して△する処理 */ public function unknown() { // 難解なコードで自分が頑張って読み解いた重要な処理 } /** * 〇〇を✕✕する処理 */ private function unknown_parts() { // 難解なコードの一部、でも、処理を切り分けることでどんな処理をするのかが少しわかりやすくなっている } } 少しはハードルが下がるはずです。少なくともコメントの追加で動作が変わるというのは早々ないでしょう。 これまでの慣習をできるだけ無視し、オブジェクト単位で、特に依存を無くす実装にする PHPだとよくあるのは、全部Controllerに書かれていて、しかもそれが他の処理に密結合を起こしている場合です。 そうなってくると、ボーイスカウト・ルールどころではありません。 ちょっとした改修タスクを作成し、その部分だけでもオブジェクト化しておきましょう。 クリーンアーキテクチャに基づいたDDDがオススメではありますが、そうでなくても、振る舞いを閉じ込めたEntityのみを採用するだけにとどめたり、ValueObjectだけでも採用したりすることで、ボーイスカウト・ルールをしやすいコードへと変身させるのです! わかりやすい、あるいは依存していないコードに対してはとても修正をかけやすいです。心理的ハードルが下がるはずです。Interfaceで抽象クラスを作成したり、ControllerではなくActionでリクエストを捌いてみたり、とにかく小さく処理を作成するように心がけると、みんなは自然とゴミを拾ってくれます。 ここでも大事なのはストレスのかからないレベルで導入することです。隅っこの方のディレクトリにModels/Entityとディレクトリを切り、その中に必要な振る舞いと値を入れるだけとかでもいいです👍 テストコードを整備する ここまでで気づいている方は多いでしょう。 テストコード、これがあればストレスとはおさらばですね👍 なんちゃってリファクタリングではなく、本格的なリファクタリングができるようになります。 https://www.amazon.co.jp/Java言語で学ぶリファクタリング入門-結城-浩/dp/4797337990 その時はこちらの書籍を読むといいかもしれません。 TDD(テスト駆動開発)と勘違いする方がいますがここでは関係ありません。 リファクタリングについては、また違う記事でしましょう。 やはり一番の処方箋は開発者の心理的ハードル下げることでしょうか。 修正してくれたことを褒めたり、感謝する文化を作ったり その修正を勤務評価にフィードバックしたり ポジティブな理由ですすめることができたら間違い探しをしているみたいに楽しいかもしれません ボーイスカウト・ルールを適用するにあたっての注意点 一応デメリットとしてあげられるのは、計画的 / 継続的にやりましょうとういことです。 計画的に進めなければ、プロジェクトの方針と食い違う可能性が出てきます。継続的に進めなければ、中途半端になってしまいます。 見かけたら100%の修正をする必要はありませんが、その分続けなければならないことは感覚的にわかりますね。 最後に ボーイスカウト・ルール。この考えとても好きです。年末に大掃除するとき、慌てない家庭はこのルールをキチンと守っているのでしょう。あるいは夏休み宿題で苦労することはないでしょう。 継続的インテグレーションが完全に普及した今、コードを作成したらそれで終わりという文化もなくなりました。 コードのメンテナンスをしっかりすることで、バグの早期発見や仕様の理解もできるでしょう。 私も完全に出来ているわけではないので、今後も頑張りたいです。 プログラマが知るべき97のことについての記事はまた書くかもしれません。 そのときはよしなに。 .
Z250で三方五湖レインボーラインへツーリング!
2020/06/25 2020/06/25Z250で三方五湖レインボーラインへツーリング! こんにちは。Nonです。 今回は技術記事ではなくバイク記事です。 三方五湖レインボーラインへ行って来たよ 前日が雨でツーリング行けるかどうか不安でしたが、無事いい天気で当日を迎えることができました☀️ 行きは大津周辺で集合。琵琶湖の西側、367号線を北上するルートで向かいました。 この道路もめちゃめちゃいいですね👍 10時から大津を出発して、12時に到着。2時間くらいで行けるものなんですね。 実はレインボーラインへ行くの2回目なんですが、そのときは舞鶴若狭道で向かったものですから、少し遠回りだったんですよ。 レインボーラインの入り口でパシャリ レインボーラインの道中でパシャリ レインボーラインで昼飯食って13時くらいから帰路に着きました。 レインボーライン降りて少し北の216号線走る 北の方には良いワインディングがあるものの、道路の状態と、視界が悪くて少し怖かったかも。。 北の最頂には特に何もありませんでした。でも港はキレイでしたね。 162号線で帰る 162号線いいですよ👍 あの辺で走ってる黒いZ250は僕かもしれません🏍 最後に コロナ禍でなかなかツーリング行けなかったので、久々のツーリングでした。 一番いい時期を奪われてほんまに。。。 やっぱりツーリングはいいものですね。 ちょいちょいですが、こういったバイク記事を投稿しています。よかったらこちらも見てみてください。 Z250で伊勢志摩ツーリング!! | のんラボ Z250で伊勢志摩ツーリングに行ってきました!!こんにちは。Nonです。今回は久々にツーリングの記事を更新します🏍正確には上図のマップどおりではありませんが、スケールはコレより大きい範囲回ってきました... Z250で初!キャンプツーリング! | のんラボ 洞川キャンプ場へツーリングしてきましたこんにちは。Nonです。今回は久々に技術とは全く関係ない記事を投稿します。週1ペースで技術系の記事を投稿していますが、月1でこういうのもいいと思っています。毎回技... Z250での冒険日記 | のんラボ 定期的にこんな感じの記事も追加して行くと思う。4月27日〜29日 西日本一周ツーリングこれはお友達と弾丸ツーリングに行きましたZXR1200のパワーを見せつけられましたね。鳥取砂丘写真は撮影してないけ... また記事書きます!動画もアップロードできるようにこのブログもバージョンアップしようかな。 最後に!バイクはいいぞ!! .
フロントに処理を書くとき、フレームワーク内で処理は書かない方がいい気がする
2020/06/17 2020/06/17フロントに処理を書くとき、フレームワーク内で処理は書かない方がいい気がする こんにちはNonです。 今回はこれまでと変わってフロントに着目したいと思います。 前提 (laravel-mixでの)Vue フレームワーク内で処理を書くとこうなる copied.export default { name: 'ApplicationComponent', data() { return { food: { name: '', price: 0, num: 0, }, drink: { name: '', price: 0, num: 0, }, sum: { price: 0, num: 0, }, tax: 0.08, }; }, created() { this.food.name = 'おにぎり'; this.food.price = 100; this.food.num = 3; this.drink.name = 'お茶'; this.drink.price = 150; this.drink.num = 1; }, computed: { sum() { return { price: this.calcTax(this.food.price, this.food.num) + this.calcTax(this.drink.price * this.drink.num), num: this.food.num + this.drink.num, }; } }, methods: { calcTax(price, num) { return price * (1 + this.tax) * num; }, } }; コンポーネント単位でこのような処理を書くと、違うコンポーネントのロジックを書くときに、計算方法を間違えそうになるかもしれません。 そこで、計算処理、つまりビジネスロジックを持つモデルを外部のjsで持つことでロジックの重複を防ぎます。 フレームワーク内はレンダリングに注力し、処理は他のパッケージで持つ フレームワーク内はレンダリング、つまり値の監視や、コンポーネントの管理などはフレームワーク(ここではVue)で行い、フロントで行うべき処理であるビジネスロジックはモデルとして外部に持つようにします。すると下記のようになります。 copied.export default { name: 'ApplicationComponent', data() { return { food: { name: '', price: 0, num: 0, }, drink: { name: '', price: 0, num: 0, }, sum: { price: 0, num: 0, }, tax: 0.08, }; }, created() { this.food.name = 'おにぎり'; this.food.price = 100; this.food.num = 3; this.drink.name = 'お茶'; this.drink.price = 150; this.drink.num = 1; }, computed: { sum() { const productCollection = new ProductCollection([ new Product(this.food.name, this.food.price, this.food.num), new Product(this.drink.name, this.drink.price, this.drink.num), ]); return { price: productCollection.sumPrice(), num: productCollection.sumNum(), }; } } }; copied.export class ProductCollection { constructor(items = []) { this.items = items; } sumPrice() { return this.items.reduce((previous, current) => { return previous.sumPrice() + current.sumPrice(); }); } sumNum() { return this.items.reduce((previous, current) => { return previous.num + current.num; }); } } copied.export class Product { static get TAX() { return 0.08; } constructor(name, price, num) { this.name = name; this.price = price; this.num = num; } calcTax() { return this.price * (1 + this.TAX); } sumPrice() { return this.calcTax() * this.num; } } このように、商品のビジネスロジックをクラスの中に封じ込め、更にフレームワーク依存しない箇所に集めることで、使い回すことを可能にしています。 さらに、フレームワーク依存しないので、他のフレームワークに移行するときにも重宝します。 Vueの場合、mixinに書けばいいじゃん? もちろん、Vueに依存することを前提にしている場合はそれでもいいですが、HelperやUtilのような役回りが多いmixinにビジネスロジックを書くのはいかがなものでしょうか? 〇〇mixinが多くなってしまうのが、目に見えます。 クラス(オブジェクト)のパワーを借りることができる。 例えば、バリデーターを自作したとして、このようなクラス作成します。 copied.export class StringValidator { constructor(value) { this.value = value; this.result = []; this.messages = []; } max(length) { this.result.push(this.value.length > length); this.messages.push(length + '文字以下で入力してください。'); return this; } min(length) { this.result = this.value.length < length; this.messages.push(length + '文字以上で入力してください。'); return this; } number() { this.result = Number.isInteger(this.value); this.messages.push('半角数字のみで入力してください。'); return this; } invalid() { return this.result.filter((item) => { return item === true; }).length > 0; } } このようにすれば、jQueryだろうがReactだろうが使用できるようになるでしょう。 今回の場合はVueで書いてみます。 copied.export default { name: 'ApplicationComponent', data() { return { name: '', errors: [], }; }, methods: { submit() { const validator = new StringValidator(this.name); const invalid = validator .max(10) .min(1) .invalid(); if (invalid) { this.errors = validator.messages; return false; } // 処理 } } }; これならバリデーションの内容を外部に任せることができます。 もちろん、入力中のバリデーションやVueで行いたい基本的なバリデーションをmixinなどに書いてリアルタイムにエラーを出力することなどもできますので、そちらはVue側に記述して、ビジネスロジックに関するバリデーションは外部に持つというのが、いい気がします。 最後に 今回はフロントにロジックを記載するときについて書いてみました。 PHPでWebアプリを作成するときは少し前まではフロントを書くときjQueryを採用することが多かったです。 こうなるとどうしてもロジックをスクリプトにチョチョイと書いてしまう実装は多いのではないでしょうか? フロント言語がしっかりとしてきた今、フロントのコードもしっかりと設計していきたいものです。 今は社内でスクラムの勉強会をしているので、その記事も書くかもしれません。 そのときはよしなに。 .
社内でドメイン駆動設計入門の読書会 #15
2020/06/08 2020/06/08社内でドメイン駆動設計入門の読書会 #15 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #14 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 ドメイン駆動設計のとびらを開こう 軽量DDDに陥らないために ドメインエキスパートとモデリングする 本当に解決すべきものを見つけよう ドメインとコードを結びつけるモデル ユビキタス言語 深い洞察を得るために ユビキタス言語がコードの表現として使われる 境界づけられたコンテキスト コンテキストマップ テストがチームの架け橋に ボトムアップドメイン駆動設計 まとめ ディスカッション ディスカッションではコードの話のほうが多かった でも、ドメイン駆動はドメインに注目して開発していく設計なので、コードは実はあまり関係ないのかもしれません。もちろん、コードが二の次というわけではなく、コードはドメインを反映させるためのふさわしい構造になっていないといけません。 ユビキタス言語 この記事を読んでいる皆さんにはすでに周知のとおりですが、変数名は重要です。 変数名はその処理を追うための手かがりになるからです。 ドメイン駆動設計でもこの点に触れていて、create, make, registerや、update, saveなど、普段何気なく使っている変数名や関数名について触れています。 ドメインで頻出する単語はそのドメインにとって重要なので、それもコードに反映しなければなりません。 例えば、あるドメインで、ユーザー登録ではなくエージェント登録という内容であれば、userではなくagentという名前になりますし、ユーザー更新ではなくユーザー保存と慣ればsaveUserとなります。 この微妙な命名でコードとドメインが乖離してしまう原因になってしまうのです。 境界づけられたコンテキスト これはまだ完全に理解できていないので、今後の課題とします。 最後に 実は結構前に、ドメイン駆動設計の全ての章を読み終えて、業務のコードに実装まで始めていました。実際にコードにするとなると非常に難しく、軽量DDDになってしまっている感が否めません。今後も精進していきたいです。 常時、自社プロダクトにコミットしているので、自分の作成したプロダクトくらい自分の支配下においていきたいものです。ということはドメインに一番詳しいのは、自分たちということになります。 そういう意味でも、ドメイン駆動設計を用いて、自分たちが実現したいことをコードで表すことは義務であるのかなと思います。 読書会は引き続き進めているので、機会があればまた連載記事として、掲載していこうと思います。 その時はよしなに。 .
社内でドメイン駆動設計入門の読書会 #14
2020/06/01 2020/06/01社内でドメイン駆動設計入門の読書会 #14 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #13 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 アーキテクチャ アーキテクチャの役目 アンチパターン:利口なUI ドメイン駆動設計がアーキテクチャに求めること アーキテクチャの解説 レイヤードアーキテクチャとは ヘキサゴナルアーキテクチャとは クリーンアーキテクチャとは まとめ ディスカッション 今回はアーキテクチャについて これまでのドメインオブジェクトをどうやって実装していくか、それにあったアーキテクチャを紹介してくれている章でした。 抽象に依存せよ これまでの章で何度も登場してきたのが、この文言です。 抽象に依存しておけば、実装クラスの内容をある程度縛ることができますし、抽象クラスを見るだけどんな内容が実装されるべきなのかわかります。 クリーンアーキテクチャ The Clean Code Blogより抜粋 https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html 徹底的に依存を嫌うので、値を返さない用な処理が多いです。 前回試しにかいたコードを見てみましょう。 copied.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; } このprocess関数は何も返却されていません。 Actionからこのアプリケーションサービスを呼び出すイメージなので、アクションには何も帰らないことになります。 こうすることで、Actionはこのアプリケーションサービスに依存しないようにできています。 ではActionは何をレスポンスとして返せばいいのかというと、上図にあるようにUse Case Output Portに返したいものを注入しておきます。 copied.class CreateCircleAction extends Controller { private $useCase; public function __construct(CreateCircle $useCase) { $this->useCase = $useCase; } public function __invoke(Request $request) { $response = new CreateCircleResponse(); $this->useCase->process($request, $response); return response()->json($response); } } returnするのは同一のドメイン内だけで行うことのようです。 アーキテクチャの勉強にもなった 正直この読書会をするまでは、MVCすら危うい感じでしたが、このように具体的に書いてくれる本があったので、設計にも少しは明るく慣れたと思います。 抽象に依存せよ。(interfaceを利用せよ) ドメイン内以外でreturnするな(オブジェクトを渡して、結果はそこに注入せよ) 強いてあげるなら、フレームワークに依存するな。 これくらいを守れればコードの可読性と保守性は飛躍的に向上すると思います。 最後に どうやって設計すれば。。。という壁にぶち当たっていたので、今回の章(今回の読書会)でこの壁は壊せたと思います。 このアーキテクチャはドメイン駆動だけでなく他のモデルにも通じますし、PHPだけじゃなくてjavascriptにも通用しますので、フロントのコードも多少はきれいになると思います。 次回はドメイン駆動設計のとびらを開こうです。 また記事にしますので、その時はよしなに。 .
社内でドメイン駆動設計入門の読書会 #13
2020/05/25 2020/05/25社内でドメイン駆動設計入門の読書会 #13 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #12 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 複雑な条件を表現する「仕様」 仕様とは 複雑な評価処理を確認する 「仕様」による解決 リポジトリの仕様を避ける 仕様とリポジトリを組み合わせる お勧めサークルに見る複雑な検索処理 仕様による解決法 仕様とリポジトリが織りなすパフォーマンス問題 複雑なクエリは「リードモデル」で まとめ ディスカッション 今回は評価と参照 今までのドメインは「ユーザーを作成する」とか、「サークルにメンバーを追加する」など、追加、更新、削除。つまりライフサイクル周りに注目するユースケースばかりでしたが、今回はサークル一覧をどう見せるか、どのようにサークル検索するか、に注目していた章でした。 正直な話、プログラミングにおいて「参照」が一番書かれる頻度が高いと(個人的に)思っているので、気になる章ではありました。 ディスカッションにおける最終的な結論 参照はドメイン駆動で設計しなくてよくね? 参照はデータ参照といっても過言では無いくらいです。 多少の加工はありますが、それは果たしてドメインと言えるのかも甚だ疑問です。 例えば登録されている商品の値段を税抜価格から、税込価格へ変換する処理は知識とは呼べそうですが、Entityに書くほどでしょうか? また、1000円以上の商品を赤文字で表示するという画面側の要望があるときにその振る舞いをEntityに書くべきなのでしょうか? これに対する結論が「リードモデル」だそうです。 登録 / 更新 / 削除用のドメインオブジェクトとは別に参照用のドメインオブジェクトを作成する言えばいいでしょうか? 少し誤解(語弊)があるかもしれませんが、そのように私は受け取りました。 参照は性能にも密接に関わる 登録件数1億件のデータにアクセスするとき、単純なクエリで一発で取れるなら、プログラムで処理して、時間をかけるより、SQL一発で簡単にとってそのままレスポンスとして返したほうがユーザーのためになるという意見も今回のディスカッションの中で出たくらいです。 CQRSという考えもあるらしいので、全てにおいてドメイン駆動設計で実装する必要はなさそう。というのが、今回の章で言いたい部分の一つだったのかもしれません。 参照を別の設計で駆動させるときのディレクトリ構造 copied.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は肥大化していきますし、振る舞いも相当なものになるでしょう。 そういうときに、どのようにまとめるべきなのか。ということを話題にしていました。 正直まだよくわからない。 最初から申し訳ないですが、集約難しいです。 現実の関心事を論理的に切り分けるのは一苦労です。 なので、この本にかかれている内容を一部抜粋して、私の考えを記していきたいと思います。 ユーザーとサークルの関係 ユーザーはサービスを利用するにあたって、必須な情報でしょう。 ログインするため アプリを利用するため ログを残すため 色々なことにユーザー情報が利用されることでしょう。 対して、サークルも同様です。 アプリをより楽しく利用するため サークル内でやり取りすることがアプリの真髄 など、サークル作ることは必須ではありませんが、サークル作らないことはこのアプリにとって利用していないのとほとんど変わらない場合を考えます。 するとユーザー⇔サークルは密接関係を持つことがわかります。 サークルとユーザーのドメインは? ユーザー作成にサークルが関係無いことはわかります。 サークル作成にはユーザーが必須に感じますね。 copied.class UserService { private $circleFactory; public function __construct(CircleFactoryInterface $circleFactory) { $this->circleFactory = $circleFactory; } public function createCircle(CircleName $circleName): Circle { return $this->circleFactory->create($circleName); } } copied.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を分ける戦略が考えられます。 copied.src┓ ┣User┓ ┃ ┣CreateUser ┃ ┣User ┃ ┣UserName ... ┣Circle┓ ┃ ┣CreateCircle ┃ ┣Circle ┃ ┣CircleName ┃ ┣User ┃ ┣UserService ... ┣Domain ... うーん。でもCircleに関心事があるドメインにUserServiceがあるのが少し違和感。本では容赦なく使用していましたが、Circleを作成するにはUserにも関心が向くという知識がドメインに与えられるのでいいことなのかもしれない? EntityはEntityを持つことがある? copied.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を持ちます。 copied.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から組み立てる アプリケーションを組み立てるフロー 題材とする機能 サークル機能の分析 サークルの知識やルールをオブジェクトとして準備する ユースケースを組み立てる 言葉との齟齬が引き起こす事態 漏れ出したルールがもたらすもの まとめ ディスカッション 今回は実装ベースでの話 今回は実装する時にどのようにコーディングしていくかという話でまとめに入っていました。 実装方法は人それそれですし、この本の手法もあくまで一例で書かれているだろうということで、ここには私の所感を書かせていただきます。 ディレクトリの切り方はドメイン毎 ディレクトリはフレームワークに依存しない方がいいと思います。 もちろん、ドメインオブジェクトをフレームワーク依存させる方針であれば問題ありません。 このようにしておかないと、フレームワークの変更時に再利用できないからです。 copied.app━laravel src┓ ┣Domain ┣Domain ┣Domain ... ドメインオブジェクトから作る 1. Entityが最重要 ドメインオブジェクトの中で最重要なのはEntityではないかという結論になりました。 ライフサイクル(ふるまい)を持つ注目したいドメインの実態というのがその理由となります。 copied.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を作成していきます。 copied.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の抽象クラスを作成します。 copied.interface UserRepositoryInterface { public function save(User $user): User; } copied.interface CircleFactoryInterface { public function create(CircleName $circleName): Circle; } 4. 関連するDomainService 最後に関連するドメインサービスです。ここではユーザーはCircleを追加できることがわかるドメインオブジェクトとしてのふるまいが存在します。 copied.class UserService { private $circleFactory; public function __construct(CircleFactoryInterface $circleFactory) { $this->circleFactory = $circleFactory; } public function createCircle(CircleName $circleName): Circle { return $this->circleFactory->create($circleName); } } copied.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. ドメインオブジェクトを駆使してユースケースを作成 copied.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についてよく知っている先輩の方からの質問でした。疑問を投げ掛けられるのは珍しかったのでびっくりしました。 私はアプリケーションサービスに貼るべきだと答えました。 実際、この本にもアプリケーションサービスに貼ってあるところがありましたし、ユースケース全体に貼るのが一番いいのかなと思っています。 copied.class ApplicationService { public function process() { DB::beginTransaction(); try { // 処理 DB::commit(); } catch (Throwable $e) { DB::rollback(); // 処理 } } } 何故そのようなことを聞いたのか尋ねると。 コントローラーに貼るべき? 先輩はコントローラーに貼るべきなんじゃないのか?と疑問に思っていたようです。 DB::beginTransaction();この箇所はLaravelのファザードで書かれていますが、この時点でLaravelに依存しています。 それをとても嫌ったようです。私も言われてみれば確かにそうだなと思いました。言語は同じなもののクリーンアーキテクチャで依存をできるだけ剥がしているのに、わざわざlaravelのコードを使ってトランザクションを使うせいで依存が発生しています。 コントローラーはLaravel側で受け止めるので、そこで貼るべきなのでは?というのがその方の意見でした。 copied.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作成時はレコードを登録する必要があるかもしれません。 copied.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に移すとこうなります。 copied.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クラスを実装する 前章で抽象クラスに依存せよ。とありましたが、ファクトリもこれに従ったほうが良いです。 copied.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にダラダラ書いていたのは何だったのか…… 次回はデータの整合性を保つです。 また記事にしますので、その時はよしなに。 .