kawasimaさん主催のアーキ部に参加しました!
テーマの発端になったツイート
部門に社員を配属するとか、カートに商品追加するとか、コレクションを集約としてアイテムを追加する訳だが、件数多くいちいちコレクション全体をメモリにロードしてられないこともある(というかそういうケースの方が多いのでは?) 。そういう時にどういう設計パターンが考えうるか、まで論じて欲しい。
— kawasima (@kawasima) 2023年1月13日
これまでドメイン駆動設計やクリーンアーキテクチャとかを勉強してきましたが、このツイートを見て「実際に『性能』を意識してコードを書いていくってどうしたら良いんだろう?」と謎になりました。
この勉強会では、『性能』は重視しつつ、どうやってドメインをコードに表現していくのか、というお話をkawasimaさんからしていただきました!
🗒 ざっくりまとめ
🤔 課題
次の2つを重視しすぎると、ドメインモデルのクラスは美しくなるけれど、性能に問題が出てくるケースがある。
- ドメインロジックをドメインモデルの中に閉じ込める:完全性
- ドメイン層は他のレイヤーに依存させないようにする:純粋性
例えば、Cartクラスに新たにItemを追加するというユースケースの場合
- Cartに入れられる数量の上限に達していないか?
- Itemは存在している商品か?
- Cartの中に同じIDのItemは存在しないか?
などをチェックするためには、CartクラスにItemのリストを持たせて、リスト内のアイテムに対して上記のチェックをしなければならない。(DBに対してSQLを叩けばサクッと解決できるはずなのに。)
そうなるとメモリ上にデータを多くもつ、性能に影響を与えてしまう。
(ただし、扱うデータ量が少なければこの限りではない)
💡 基本になる考え方
DDDのトリレンマ
次の3つ全てを得ることは出来ない。「どの選択をしているのか」を認識することが重要。
- ドメインロジックをドメインモデルの中に閉じ込める:完全性
- コードの変更容易性に影響する
- ドメイン層は他のレイヤーに依存させないようにする:純粋性
- テスト容易性と移植性が向上する
- 性能
- 外すことが出来ない
性能は外せない選択肢となる。完全性・純粋性はビジネス影響としては与えないので、ここを悩みすぎるのは微妙。
完全性・純粋性を犠牲にしつつも、それぞれのメリットを少しずつでも享受する方法を考える。
参考
scrapbox.io
✨ 解決方法
高凝集を目指すために、複雑さを紐解いていく
解決に向けての2つのステップ
1. どこに定義するかより、同じでないものを区別する
「カートのアイテム」とまとめて考えるのではなく、「カートに追加したいアイテム」「カートに入れられるアイテム(チェックが済んだアイテム)」「既にカートに保存されたアイテム」は特性が別々なのでは、という考え方。
例えば
「カートに追加したいアイテム」は「カートID」「商品ID」「数量」を情報に持ち、
「カートに入れられるアイテム(チェックが済んだアイテム)」は「カートID」「発売中の商品ID」「数量」「既にカートに追加されている商品IDかどうか」の情報を持つ
という感じ。
本来表現したい特性が別々なので、持つ情報も違ってくる。
また、型として上記を分けることで、チェックしてないアイテムがカートに入るというミスも避けられる
参考(③Design by Typesドメインモデル )
scrapbox.io
このあたりで感じたこと
「Design by Typesドメインモデル」、確かに感はあるけど、めちゃくちゃ型増えそう…!https://t.co/cgwSunz8wr#アーキ部
— しょぼちむ (@syobochim) 2023年1月23日
2. Domain Modeling Made Functional
ステップ1で実施した型を使いつつ、ユースケースのステップをそれぞれ関数にしてドメイン層に実装していく
例えば、
Input「カートに追加したいアイテム」 → 関数:アイテムのチェック → Output「カートに入れられるアイテム(チェックが済んだアイテム)」
という感じ
type ParseAddableCartItem = (cartItem: CartItem) => AddableCartItem
引用:revisiting-domain-model/made_functional.ts at main · kawasima/revisiting-domain-model · GitHub
また、実装の関数としてRepositoryを差し込めるようにすることで、テストもしやすい状態を作れる
function parseAddableCartItem( cartRepository: CartRepository, productRepository: ProductRepository, ): ParseAddableCartItem { return (cartItem: CartItem) => { ...
引用:revisiting-domain-model/made_functional.ts at main · kawasima/revisiting-domain-model · GitHub
このあたりで感じたこと
なるほど感あるけど、一生カートの実装終わらなさそう…!
— しょぼちむ (@syobochim) 2023年1月23日
ドメインモデルは深掘りすればするほど深くなっていくということか…!#アーキ部
💬 全体の感想
変更容易性やテストのしやすさも大事だけど、「性能」を犠牲にしちゃいけないし、そういうコードになってないかについて考えなきゃ!と気付けるすごく良い機会でした!
ドメイン駆動設計やクリーンアーキテクチャを勉強してると、どうしても完全性や純粋性を意識したくなるし、「そうすべきだ!」って気持ちに陥りがちなので、気をつけねば。
今、勉強でゆっくり書いてるコードも、この話を聞くと色々修正してみたくなるし、コードの改善って一生終わらない感じする。(でもそれが楽しい)
kawasimaさん、お話ありがとうございました!楽しかったです!🙌
オライリーのサブスク折角あるので、この本も読書チャレンジしたい