パッケージの依存を単方向にするための拡張メソッド
DDDの話をするとしよう*1。
IDDD 第9章 モジュールにおいて、モジュール(Scalaだとパッケージ)の依存は極力減らせ。現実的には難しいが、単方向にしたり、親子関係にあるならば双方向を許可する、ということが述べられている。
しかしこれめっちゃ難しい。集約が別集約のファクトリになる場合、単方向という制限はだいたい破綻する。
// userモジュールの集約 class User(id: UserId) { def createTask(): Task = // Taskを返すため、taskモジュールに依存 new Task(TaskId.generate(), id) } // taskモジュールの集約 class Task(id: TaskId, ownerId: UserId) // ownerIdとしてUserIdを持つため、userモジュールに依存
そもそもこういった関係のものにファクトリを持たせるな、という話な気もしてくるが、だからといって避けては通れないときもあると思う。ならば親子関係にすれば良いのか、というとそうでもない。そうしちゃうとほとんどがuserモジュールの子になってしまう。たぶん。
そういうときに拡張メソッドを使うことも出来るな、という話をしたいわけです。
さきほどの例だと user.User
をこうする。
class User(id: UserId)
要はファクトリを削る。これでtaskモジュールへの依存はなくなる。
で、taskモジュールに以下のようなのを書く。
import user._ package object task { implicit class TaskCreator(user: User) { def createTask(): Task = new Task(TaskId.generate(), user.id) } }
user.User
から task.TaskCreator
への変換を定義し、そこに拡張メソッドとしてファクトリを持たせてあげる。こうするとファクトリのある場所がtaskモジュールに移動するので、依存が単方向に収まる。
ちなみに使うときは:
import user._ import task._ val user = createUser() val task = user.createTask() // import task._ をしていないとcreateTaskメソッドが使えない
のように、両方のモジュールをimportしてあげる必要がある。が、むしろこれはtaskモジュール中のオブジェクトを使いますよ、という意味になるので、むしろ好都合だと思ってる。
実践で用いてはいないのだけど、「あれ、こうすればいいんじゃ?」と思った話でした。デメリットは実践してないから知らない。