Scala
DDD

ScalaでDomain層にFirst Class Collectionを作るときのテクニック(JSON)

悩み

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] にしてくれる感じが出来る。

このテクニック使えば、大体のことはいける気がする。

以上です。