社内でドメイン駆動設計入門の読書会 #4
2020/03/10 2020/03/14社内でドメイン駆動設計入門の読書会 #4 こんにちは。Nonです。 今回も会社で読書会をしている話をしようと思います。 内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。 より具体的なコードや内容がみたい!という方は購入しましょう! 前回:会社でドメイン駆動設計入門の読書会 #3 読んでいる本 読んでいる本はこちらのドメイン駆動設計です。 ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。 そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。 進行方法 読書会の進行方法は 今回読書の対象にする章を決める。 10〜20分間その章を読む 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう その後40分間で、その章に対する疑問や考え方をディスカッションする 1〜3を毎週定期的に行う という進行方向となっています。 社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。 今回読んだ内容 第4章の不自然さを解決する「ドメインサービス」です。 サービスが指し示すもの ドメインサービスとは 不自然なふるまいを確認する 不自然さを解決するオブジェクト ドメインサービスの乱用が行き着く先 可能な限りドメインサービスを避ける エンティティや値オブジェクトと共にユースケースを組み立てる ユーザーエンティティの確認 ユーザー作成処理の実装 物流システムに見るドメインサービスの例 物流拠点のふるまいとして定義する 輸送ドメインサービスを定義する まとめ これは後で気づいたのですが、ドメインサービスとアプリケーションサービスを混同してしまう人は多いようです。 私はこの章を読んで自分も同じようになっていたことに気づきました。 ディスカッション ドメインサービスいらなくね?いつ使うの? 最初は私の質問でした。 私がそのように考えた理由は、以前に勉強した、「値オブジェクト」「エンティティ」の2つがあれば、実装ベースで困ることが無いと思っていたからです。 その2つさえあれば、大抵のことができますし、ドメインサービスが具体的にどのような実装をするのかわからなかったので、不要論を発言してしまいました。 当然この著書には4.2.1「不自然なふるまいを確認する」で、具体的なコードが書かれています。 エンティティは自分に関連する情報を操作する事はできますが、自分以外のエンティティの操作はすることはできませんし、自分自身がデータストアなどに存在するかどうかを確認することも不自然とあります。 class User { public function __construct() { // 省略 } public function exists(User $user): bool { // 存在確認コード } } 自分自身に存在するexists関数に自分自身を放り込むというこの実装はとても不自然です。 ここでようやく私も気づけました。 (正直私は自分自身の存在チェックも自分自身が行うものという日々の価値観があったので、この考えにたどり着けなかったのです。) 上記のようなときに、ドメインサービスを利用します。 class UserService { public function exists(User $user): bool { // 存在確認コード } } ドメインサービスにエンティティを渡すことで、エンティティ自身が解決できないことを解決しようということですね。 ドメインサービスって存在チェックくらいしか実装できなくね? 続いても私の疑問でした。 エンティティが自分自身で解決できないふるまいってなんだろうか?と考えたことがきっかけでした。 エンティティが自分自身だけでは解決できないようなふるまいを想像したとき、なにも思い浮かばなかったのです。 Uさん『それは4.5「物流システムに見るドメインサービスの例」にあるように、エンティティ同士の値のやり取りがその例に当てはまります。』 私『正直この例よくわからなかったんですよね。』 Uさん『この例以外によく見る例は、銀行口座のお金のやり取りです。エンティティは銀行口座ですが、口座自身がその相手先に対して出金や入金を行うわけではないですよね?』 私『そりゃそうですね。出金や入金を行うのは銀行です。』 Uさん『これをプログラムに落とし込むときに、口座エンティティは入金と出金という振る舞いを持つことになりますが、自分自身のエンティティしか操作できないという制約で、入金と出金をセットにした振る舞いは実装できないということになります。そこで、口座サービスを作成して、2つの口座エンティティの操作を行うのです。』 私『なるほど、物流システムの例もそれと同じなんですね。』 $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) { // 出金処理 } } このように処理の最中にダラダラと直接の書くのではなく、ドメインサービスにまとめてしまうのです。 $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の構造を示すので、ただの値の集合体ですが、この著書では振る舞いを持ちます。 値オブジェクトが、プログラムの知識をしめすなら、エンティティはその知識を集め、振る舞いを持たせることで実体にするという考えなのかもしれません。 値オブジェクトと違い、可変である これは本にも記載されていますが、値オブジェクトと違い可変です。 class ProductId { private $id; public function __construct(int $productId) { $this->id = $productId; } public function toInt(): int { return (int)$this->id; } } 上記のように値オブジェクトは、一度生成したら、値を変更することはできませんが、 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をどんなコードを書けばいいのかわかりませんでしたが、この章を見た後に考え直したらこうなりました。 // 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に当てはめたときのコードですが、不自然じゃなさそうです。 この章を読むまでは // 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するような動作の方がいいのかもしれません。 $id = $request->get('id'); $name = $request->get('name'); // ProductはModelを継承 $model = new Product(); $product = $model->find((int)$id); // IDを使った強制的な更新のように見える $product ->update([ 'name' => (string)$name, ]); と書くより、 $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はデータベースのデータから設計するのではなく、やりたいこと、叶えたいことすなわちドメインから設計をしていくという手法です。 考えを改めてみると、ライフサイクルですから何かが変更したときに、同期するように他の変更も変わるということはありそうです。 例えば、 $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; } } とするよりも、 $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)ではなく、所謂クラス型として採用してきました。 例えば、 class ProductId { private $id; public function setId(int $productId): void { $this->id = $productId; } public function getId(): int { return (int)$this->id; } } こんな感じで変数に型を付けるために採用したことはありました。 しかしこの本ではsetterが無く、値を再代入することができないように作ることで、不変性をもたせるということでした。 class ProductId { private $id; public function __construct(int $productId) { $this->id = $productId; } public function toInt(): int { return (int)$this->id; } } このように書くことで、ProductIdのインスタンスを生成するときにしか、値をセットすることができないので、不変となります。一度作ったProductIdはこれ以降信頼できる変数として使用することができますね。 振る舞いをもつってどういうこと? VOなので、振る舞いをもつ値、変数ということに疑問を持った方の質問でした。 上で書いた class ProductId { private $id; public function __construct(int $productId) { $this->id = $productId; } public function toInt(): int { return (int)$this->productId; } } にも値を返すtoIntしか存在しませんね。 回答ととして出た例として、弊社スマレジではPOSを開発しております。 POSレジなので、税抜、税込のデータは必須となっています。このようなとき、VOを使えば簡単だよねという話でした。 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は不変で税込、税抜を考慮した値段を取得できるふるまいを持つクラスとなります。 更に、 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(); } } とすれば、他の商品単価を税込か税抜を意識せずに価格計算をすることができます。 // 軽減税率で税抜価格 $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に採用することで、更にセキュアな処理が実行できそうですね。 <?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を使い回すことになるので忘れなどの防止になりそうですね。 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にすればいいのではとディスカッションで結論づきました。本でも出てきていましたが、例えば、 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など裏の機能に向いてるとか言う話では無く、モチベーションがどう上がるかという話 どちらの生徒も、やはり定期的にプログラミングをやっていると飽きが来てしまうのですが、そのモチベを回復すべく結構工夫しました。 その試行錯誤の結果、わかったことです。 フロント、いわゆる見た目をきれいに整える(機能はまだ無い)手伝いをしてあげて、きれいな画面を見せるとモチベーションが上がる人 バック、いわゆる実際に動くよう(デザインはまだ無い)手伝いをしてあげて、機能を見せるとモチベーションが上がる人 がいました。もしかしたらプログラミングに対する考えというか、求めるものが結構違うのではと思うきっかけになりました。 アプリを作りたいと言うのは便利なアプリを作りたいのか、美しいアプリを作りたいのかとかいう話になりそうです。(自分でもまだ噛み砕いていない) コツ やっぱり先生から熱意を見せることは大事だと思いました。 僕は何かを伝えるとき、結構声に抑揚がついたり、声が大きくなったりします。僕的には(起怒っている風に見えないかな?)とか思っているのですが、以外とそうでもなく、「おとなしい人でも」しっかり聞いて頑張ろうとしていることがわかりました。 できるだけ自由に 最初は冒頭に言ったような課題を用意していたのですが、モチベーションが下がっていくのは目に見えてわかりましたので、自由な課題を用意して、アドバイスを出していくようにしたらうまくいくようになりました。 最後に 今回は未経験の方にプログラミングを教えるときのコツのようなものに関して書いてみましたが、僕も初めて半年くらいなものですので、当てにならないかもしれません。 皆さんの力になれれば嬉しいです。 また更新するかもしれません。 その時はよしなに .
昨年の振り返りと本年の目標
2020/01/01 2020/01/01昨年の振り返りと本年の目標 こんにちは。Nonです。 少し遅れてしまいましたが、去年の振り返りと、本年の目標を書いていこうと思います。 2019年はどんな年だったか? 職場 実は2018年に転職をしておりまして、前職がそれはもうレガシーな環境での開発でした。 Gitサーバー 存在しているが、「全く」使用されていない状態 つまりフォルダバージョン管理 サクラエディタでの開発 VSCodeの存在を知らない人が多数 フレームワークの使い方を知らない MVCすら怪しい 途中から僕一人での保守運用 開発業務がほとんどできずドメインについて知っているのが僕一人 ちょっと愚痴っぽくなってしまいましたが、ほとんどあっていると思います。 2019年になる間際に転職をしまして、現職となります。 現職では首都圏のエンジニア事情より少し遅れてる感は否めませんが、前職よりかなり進んだ環境になったと思います。 それに伴いかなり広い範囲で勉強になりました。 GitLab Git管理手法 CI/CD 自分以外が書いたMVC設計 チケットを用いた業務管理 設計などは個人で勉強していたので、特にという感じでしたがコード管理と業務管理は参考になるところが多いと思います。 個人活動 大きく変わったのはこれでしょうね。 新卒の頃から色々自分で作成していましたが、サーバーを借りて公開し始めたのはちょうど去年の今頃です。 クソアプリですが、 旧:non's Labo トドTask ツーマッチ 今年の抱負メーカー などなど色々作成しました。 特に今年の抱負メーカーはちょうど去年の1月1日にリリースしたものでした。 こちらその時のQiitaの記事です。 この子達を開発するにあたってLaravelに手を出して、色々勉強しながら開発していました。 最初はどちらかというとLaravelやDDD、クリーンアーキテクチャを勉強するためにアプリを作ってた感がありましたが、2019年後半になってくるとアプリのために開発するようになっていました。 そう言えば家庭教師はじめました 友人からプログラミングを教えてほしいとのことなので、かなりの格安で毎週家庭教師をしています。 後輩以外にプログラミングを教える機会がなかったので、うまく行っているか不安でしょうがないです。 これはまた詳しく別の記事にしたいですね。 2020年の目標 目標というか方針ですね。 なにか1つのアプリをじっくりと開発する 今までは何か一つ作ってポーンって出したりして、リリース後は放置みたいなずさんなアプリ開発だったので、きっちりバージョンごとに区切り、ピリオドをつけるようにしていきたいと思います。 とはいえ個人開発であることに変わりはないので、ツーマッチのように規模が大きすぎて無謀な挑戦にならないようにはしたいです。 自作アプリを紹介するHP作成 構想としてあるのが、一昔(平成時代)に流行った、自作アプリを一覧にしてそこからDLやリンクで使用してもらうという、あのページを作成してnons Laboに埋め込みたいです。 うまく作成すればそのままポートフォリオになりますし、共同開発者を募るページも作成したいですね。 ということは 現在作成中のあるアプリ これに一番注力したい nons Laboの管理 アプリまとめページ 技術記事更新(Qiita含め) 会社で使用されるであろうLuncher 会社で使用される予定で、一人で結構大きめのアプリをつくったので、なにかにまとめて記事にしたいと思います。 これを2020年に落ち着かせるのが目標でしょうか。 その他私生活に関すること 貯金 ホントは去年頑張るよていだったのですが、全く達成されず😂 バイクやキャンプ用具を買っていたらお金が全く貯まりませんでしたw この辺我慢するようにはしていませんが、ちょっとしたカフェ台や食費が結構かかっているので、その辺の管理をちゃんとできるようにしないといけませんね。 おしゃれに気を使う 流石にいい年齢になりましたので、そろそろ身につけるものに気を使おうと思いますw 寝間着同然の格好で外歩き回ったりする人間なので僕😱 ここに書いたこと以外は我慢しない これが意外に重要かもしれません。気楽に行きましょう! 最後に こんなものでしょうか。 怠けグセがあるので、どれか忘れそうですが、気を引き締めて行きたいと思います。 ブログのネタにしたいものをたくさんあるので頑張って記事にしていきます! その時はよしなに。 .
Laravelの404ページやその他の例外ページの作成方法
2019/07/15 2019/12/27Laravelの404ページやその他の例外ページの作成方法 こんにちは。Nonです。 今回は備忘録としてLaravelでの404ページなどのカスタマイズ方法について書いていきたいと思います。 ページ用のbladeを作成する 普通にbladeとして作成します。 下記の例ではh1タグのみにしていますが、CSSなどを利用してページを装飾することをおすすめします。 <h1>404 Not Found. このページは存在しません。</h1> <a href="/" class="btn btn-primary">TOPへ戻る</a> 404というか例外時の処理 Laravelはroutesに無い時NotFoundHttpExceptionをスローします。 このスローの結果、最終的に/app/Exceptions/Handler.phpのrenderメソッドへ処理が移ります。 また、自分のアプリケーションで独自の例外を作成している場合もアプリケーション層でキャッチしていない場合、ここへ処理が来ます。 /** * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @param \Exception $exception * @return \Illuminate\Http\Response */ public function render($request, Exception $exception) { return parent::render($request, $exception); } ここのrenderメソッドを編集します。 ページがないときの例外 /** * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @param \Exception $exception * @return \Illuminate\Http\Response */ public function render($request, Exception $exception) { if ($exception instanceof NotFoundHttpException) { return view('pages.errors.404'); } return parent::render($request, $exception); } サーバーエラーが発生したときの例外 他の例外時、例えばサーバーエラーを示す500エラーなどの場合は、独自の例外や、Laravelの例外すべてを拾いたいので、このようにするのがいいかもしれません。 /** * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @param \Exception $exception * @return \Illuminate\Http\Response */ public function render($request, Exception $exception) { if ($exception instanceof NotFoundHttpException) { return view('pages.errors.404'); } // 一番最後に書くことを推奨 if ($exception instanceof Throwable) { return view('pages.errors.500'); } return parent::render($request, $exception); } ログインに失敗した時や、権限のエラーのときの例外 ログイン時は401 Unauthorizedで、権限エラーは403 Forbiddenです。 しかし、このエラーをここで拾うのはちょっとおかしいかもしれません。 このメソッドはアプリケーションが想定していない入力をされた時に発生する例外をまとめるべきです。 ログインエラーや、アプリケーション権限の例外は想定できる例外ですので、ControllerやMiddlewareレベルでtry-catchを使用するべきです。 例えばログインが必要な機能ページの例外はLoggedInMiddlewareなどを作成して自身のログインチェック時にスローされる例外をキャッチしてエラーページをレンダリングするほうが、/app/Exceptions/Handler.phpのコードを汚さなくてすみます。 /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param string|null $guard * @return mixed */ public function handle($request, Closure $next, $guard = null) { try { // ログインチェック } catch (UnauthorizedHttpException $e) { // 認証のエラーページやリダイレクト処理 } return $next($request); } ちなみにLaravelの標準で付属している認証システムを利用している場合は、すでにRedirectIfAuthenticatedが実装されているので、不要です。 アプリケーションの権限などの場合はログインと同様にRoleMiddlewareなどを作成し、権限が関係するルーティングにこのミドルウェアを適用し、try-catchを使います。 /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param string|null $guard * @return mixed */ public function handle($request, Closure $next, $guard = null) { try { // 権限チェック } catch (RoleException $e) { // 権限のエラーページやリダイレクト処理 } return $next($request); } (adsbygoogle = window.adsbygoogle || []).push({}); APIのときとかは? LaravelのRequestクラスにはexpectsJsonメソッドが実装されているので、これを利用してAPIでコールされているかどうかを確認しましょう。(APIがXMLとかの場合は他の関数を用意して利用しましょう) /** * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @param \Exception $exception * @return \Illuminate\Http\Response */ public function render($request, Exception $exception) { if ($exception instanceof NotFoundHttpException) { if ($request->expectsJson()) { response()->json([ 'message' => 'Not Found.', ]); } return view('pages.errors.404'); } // 一番最後に書くことを推奨 if ($exception instanceof Throwable) { if ($request->expectsJson()) { response()->json([ 'message' => 'Server Error.', ]); } return view('pages.errors.500'); } return parent::render($request, $exception); } 基本的に/app/Exceptions/Handler.phpはあまり利用しないようにしたほうがいいかも ここに例外がスローされる時はアプリケーション上本当に拾えない例外か、よっぽどの想定外(PHP構文エラーや実装時のバグとか)を拾うためのメソッドです。 なので、アプリケーション上で開発時に想定できる例外はキチンと他の層でキャッチしたほうが可読性が上がります。 最後に 今回は備忘録とエラーハンドリングの整理がてらまとめてみました。 最初は404エラーについてにまとめようと思っていましたが、結局それはエラーハンドリングをどのような構造で作成するかということでしたので、他の例外についてもまとめてみました。 正常時の設計も大事で、こちらばかりに注目が行きがちですが、エラーハンドリングをしっかりすることもとても重要です。例外を自分の想定どおりにハンドリングできていないと、運用のときどこでエラーが起きたとかを拾いづらくなってとても困ります。 一度適当な記事を書いてしまったので、リライトしました。 これからもLaravelを利用したアプリの設計について思うところがあれば記事を更新したいと思います。 その時はよしなに。 .
CakePHP3でShellでメール送信できなかった話
2019/12/17 2019/12/18CakePHP3でShellでメール送信するときに気をつけること こんにちは。Nonです。 今回は僕がCakePHP3でメール送信する際にハマったときのことを書こうと思います。 ちょっと重ための処理をシェルで実行した後、メール送信する処理を作った話。 ハマる前に少しだけ前提知識を書くと、ちょっと重ための処理が合ったのですが、これをユーザーに待たせるのは避けたく、バックグラウンド(シェル)で実行して、その完了通知をメールで通知しようという処理があったのです。 そのときに、メールが送信できない現象が発生しました。 Gsuiteのrelayを使用していますので、他の人はこの現象発生しないかも? 結論 config/app.phpのEmail設定にclientは指定しておいたほうが吉。 'EmailTransport' => [ 'default' => [ 'className' => 'Smtp', // The following keys are used in SMTP transports 'host' => 'smtp-relay.gmail.com', 'port' => 587, 'timeout' => 30, // ここNULLは避ける↓ // 'client' => null, // ここドメインを指定↓ 'client' => 'example.com' 'tls' => true, ], ], 何故か? /vendor/cakephp/cakephp/src/Mailer/Transport/SmtpTransport.php protected function _connect() { $this->_generateSocket(); if (!$this->_socket->connect()) { throw new SocketException('Unable to connect to SMTP server.'); } $this->_smtpSend(null, '220'); $config = $this->_config; if (isset($config['client'])) { $host = $config['client']; } elseif ($httpHost = env('HTTP_HOST')) { // ここ$config['client']がNULLだったら設定されたドメイン list($host) = explode(':', $httpHost); } else { $host = 'localhost'; } ... } clientが存在しなければ環境変数HTTP_HOSTがclient、それ以外ならlocalhostとして設定され、接続されます。 このときの環境変数HTTP_HOSTですが、諸事情により自分で設定した違うドメイン(購入していないローカルPCだけのドメイン)でした。 さらに本番環境でShellを実行すると、環境変数が強制的にLocalになってしまう始末。。。(皆さんはちゃんとプログラムで制御してください。) まぁlocalhost.test.comで接続なんて許されませんよねw それに仮にlocalhostで接続したとして、Gmailがlocalhostでの接続を許してくれるのでしょうか? これは検証する必要があるかもしれません。 最後に 今回は短めでしたが、CakePHP3のソースを追わないとわからない内容だったと思います。 皆さんの力になれれば嬉しいです。 ところで 'client' => 'example.com' で設定してもうまくいくのでしょうか?私の修正後のコードはセキュリティの観点からお見せすることはできませんが、購入したサーバーに接続できるhostでした。 仮にここにlocalhost.test.comを入れた送信できないのでしょうかね?(まぁできないと思う。) 最近ちょっとした処理でフレームワークの処理を追うことになったので、こういったニッチな解決法を発見したら、今後も記事にしていきたいと思います。 その時はよしなに。 .
さくらのレンタルサーバーのPHPをモジュールモードにしたらアホみたいに早くなった話
2019/12/13 2019/12/13さくらのレンタルサーバーのPHPをモジュールモードにしたらアホみたいに早くなった話 こんにちは。Nonです。 今回は僕の大事なクソアプリ'sが稼働している本番環境のレンタルサーバーについてお話したいと思います。 本番環境がレンタルサーバーってどうなの? 僕はかなりアリだと思っています。 というのも僕の考えるアプリって複雑な機能を必要としないアプリですし、基本的にPHPが動作すれば問題ありません。DBも使用できますし。(じゃないとWordPress動かない) また、セキュリティも問題ないと思っています。レンタルサーバーだろうがVPSだろうがIDとPASSをしっかり守ること、知っていても簡単にログイン出来ないようすること、やることは対して変わりません。IP制限とかもできるサービスもありますね。 そもそも大半のHPはレンタルサーバーで稼働しているサイトが多いと思いますしね。 メリット 安い。月額500円くらいで100GB使えます。ファイルサーバーやメールサーバーとしても使用できます。 メールの設定などすでに実装されているものを作成する必要がない。 デメリット 環境の選択肢が有限。使用できる言語が限られたりする。 マシンパワーに依存してしまう。 僕の作ったクソアプリはユーザー数少ないのでまだパワー不足に陥ったことはありませんので、なんとも言えませんが、有名サイトや有名HPがレンタルサーバーで動いているのを考えると、ある程度のパワーも持っているのではないでしょうか? herokuなどのサービスやAWSばかりに注目が行っていますがレンタルサーバーという選択肢は結構アリだと思っています。 PHPのCGIモードとモジュールモード モード説明 モジュールモードApacheの拡張機能でPHPを直接実行するモード CGIモードCGIプログラムを別途実行するモード さくらのレンタルサーバーの公式HPでは ※自社調べ。スタンダードでCGIモード(PHP5.6)とPHPモジュールモード(PHP7.1)を比較。WordPress4.9.5を使用し、同一の負荷をかけた状態でWordPressページのソース表示リクエストの処理時間を計測しました。結果、CGIモード8.2秒、PHPモジュールモード0.5秒の処理時間となりました。 >なお計測値は参考となり、すべての環境で同様に表示速度が改善されるわけではなく、お客さまが運営するウェブサイトの構成などにより変化が見られない場合もございます。 とあります。 CGIモード(PHP5.6)とPHPモジュールモード(PHP7.1)で、PHPのバージョンに差異があるのは気になりますが、それを踏まえてもかなり早くなっていることがわかります。 最大16倍に高速化するそうです。 僕のクソアプリもかなり早くなった感あってうれしい このnon'Laboも表示までかなり早くなったと思います。 まだ胸張って「良いアプリだ!」といえるアプリは作っていないので、他の例は挙げていませんが、現在新しいアイデアをアプリ化しているのでそのアプリでも体験してみてください! その時はよしなに。 .