✨
domainをsharedに入れてはいけない理由
背景
-
feature/subscription/domain
で作った Subscription オブジェクト(振る舞いあり)を、
他の場所で使おうとした。
※振る舞い=そのオブジェクトが行う処理やロジック。例: cancel(), upgrade(), withTax() - ユーザーのサブスク状態を返すときにHono の context に domain を突っ込んで返せば一瞬やんと思ってAIに修正命令
- めちゃくちゃlintエラーが出ておかしいと思って設計を見直した。
結論
- domain を shared に昇格させるのは境界違反。
- shared は DTO(形)だけを置き、振る舞いは feature 内に閉じる。
振る舞いの共通化で起こる問題
1. 境界漏れ
- Domain は「プラン仕様」や「不変条件」など内側のルールを持つ。
- それをそのまま返すと、外部API契約が内部実装と直結してしまう。
- 例:
Subscription
の構造を返していると、プラン体系の変更(FREE/PRO → STARTER/PRO/ENTERPRISE)がそのまま API 破壊になる。
2. シリアライズ不安定
- class を返すと、getter / private フィールド / メソッドが JSON 化に混じる。
- Date や BigInt を持っているとシリアライズに失敗したり、意図しない文字列に変換される。
- 実際に lint エラー(「JSONにできないフィールドを返している」系)が発生するなど、型と実際の出力がズレやすい。
3. 意味論の波及
-
Domain に
canAccess(featureKey)
のようなメソッドを置いたとする。 -
これを shared に昇格させると、player / editor / billing など複数の feature から呼ばれるようになる。
-
一見「どれもアクセス可否を知りたい」ので同じ意味に見えるが、実際には異なる:
- player → 「UIのボタンを出すか」
- editor → 「高画質エクスポートを許すか」
- billing → 「課金対象に含めるか」
-
共通メソッドにまとめてしまうと、ある境界に合わせた仕様変更が他の境界を壊す。
-
問題は「呼ばれていること」ではなく、境界ごとに意味が違うものを1つにしてしまったこと。
4. テスト・進化の足かせ
- Domain を返すと、テストが クラス内部の構造やメソッドに依存してしまう。
- 仕様を差し替えたり内部表現を変えたくても、返却形式が固定されているため後方互換の縛りになる。
- DTO に落として返せば、テストは安定した形(プレーンオブジェクト)の比較で済み、domain は自由に進化できる。
「メソッド増やせば解決?」の落とし穴
「用途ごとにメソッドを増やせば済むのでは?」と一瞬思った。
例
class Subscription {
toDtoForApi(): SubscriptionDTO { … }
toDtoForBilling(): BillingDTO { … }
toDtoForReporting(): ReportDTO { … }
}
問題
-
神クラス化
-
Subscription
が API, Billing, Reporting すべての出力仕様を抱えることになる。 - 本来 feature ごとに独立すべき仕様が 1 クラスに集中してしまう。
-
-
誤用リスク
-
toDtoForBilling()
を API 側で呼んでしまうなど、間違ったメソッドを選んでも型では防げない。 - 境界ごとの正しい意味を守れなくなる。
-
-
更新負荷
- 新しい境界(例: 分析や通知)が増えるたびに、このクラスにメソッドを追加する必要がある。
- 変更のたびに全機能へ波及する「破壊的共有ポイント」になってしまう。
正しいアプローチ
-
shared: DTO だけ(安定した形)
type SubscriptionDTO = { id: string userId: string plan: string status: string // …他はシンプルにデータのみ }
-
feature/subscription/domain:
不変条件や内部のルール(キャンセル、アップグレードなどの処理)を保持する。
外に出さない。 -
mapper / presenter:
domain → DTO への変換を担い、context やレスポンスに載せる。
境界ごとに独自の Mapper を持てば、API と Billing が別々の契約を保てる。
学び
- AIに修正してもらってlintが大量発生したらまず設計を疑おう!!!!
Discussion