悩み
Domain駆動設計を採用しており、Domain層をPOJO(ScalaだとPOSO?)に作りたいってなったら、First Class Collectionを作るにしても、なんやかんやで、mapとか使いたくなることが多い。
とはいえ、そこで、mapを使えるようにしたら、なんか負けた気がするので、ちょっとしたテクニックで回避してみた。
課題
SomethingEntity
の集合を保持するFirst Class Collectionを想定する。
case class SomethingEntity(id: SomethingId)
case class SomethingEntities(value: Seq[SomethingEntity])
この SomethingEntities
の保持しているデータを全部JSONにしたいとき、
somethingEntites.value.map { somethingEntity =>
convertToJson(somethingEntity)
}
// convertToJsonで好きなJSONライブラリを使って変換するのを想定している
みたいな感じにしたら意味ないよねって前提。
First Class Collectionだから、そもそも、classは、
case class SomethingEntities(private val value: Seq[SomethingEntity])
って感じで、 private
にする。
すると、
somethingEntites.value.map { somethingEntity =>
convertToJson(somethingEntity)
}
のvalueが取れなくてコンパイルエラーになる。
convertToJson
部分はライブラリに依存するから、
case class SomethingEntities(private val value: Seq[SomethingEntity]) {
def toJson: Seq[String] =
value.map(v => convertToJson(v))
}
みたいな実装はできない。(あくまでPOSOにこだわるなら。ただし、JSONはDomainに依存させても良いじゃんって場合はぜんぜん良いと思う。)
解決策
うすいJsonWriter的なtraitを定義だけしておいて、実装はお任せ的なことにするといける。
trait SomethingEntitiesWriter[JSON] {
def toJson(entity: SomethingEntity): JSON
}
これを宣言しておいて、
case class SomethingEntities(private val value: Seq[SomethingEntity]) {
def toJson[JSON](implicit writer: SomethingEntitiesWriter[JSON]): Seq[JSON] =
value.map(writer.toJson)
}
みたいな感じ。
こうしておいて、 SomethingEntitiesWriter
を以下みたいに実装する。
implicit object SomethingEntitiesWriterImpl extends SomethingEntitiesWriter[String] {
override def toJson(entity: SomethingEntity): String =
convertToJson(entity)
}
// convertToJsonで好きなJSONライブラリを使って変換するのを想定している
この implicit object
をスコープに入れておいて、
somethingEntites.toJson[String]
と呼び出せば、良い感じに中のCollectionに直接アクセスしないで全部のEntityを Seq[String]
にしてくれる感じが出来る。
このテクニック使えば、大体のことはいける気がする。
以上です。