この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
こんにちは、リングフィットアドベンチャーで一番好きなエクササイズはモモアゲアゲの佐々木です。
今回はScaladexで見つけて気になっていたChimneyを試してみました。
Chimneyとは
Chimney は似てるけどちょっと違うフィールドをもつオブジェクト間のフィールドコピーにおけるボイラープレートを削減できるライブラリです。
例えば以下のようにあるオブジェクトから同じ名前、同じ型のフィールドを持つ別のオブジェクトが生成できます。
import io.scalaland.chimney.dsl._
final case class ReadOrderResponse(orderId:String, vat:BigDecimal, totalExVat:BigDecimal)
final case class Order(orderId:String, vat:BigDecimal, totalExVat:BigDecimal)
val readOrderResponse = ReadOrderResponse("order_001", 30, 300)
val order = readOrderResponse.transformInto[Order]
println(readOrderResponse) // ReadOrderResponse(order_001,30,300)
println(order) // Order(order_001,30,300)
他にもネストしたコレクションの変換やValue Class の変換、フィールド名の変更などかなり柔軟に変換を行えます。公式ドキュメントにはコード例も交えて紹介されているのでわかりやすいです。
Refined型への変換
Chimneyを試していてStringやLongなどの組み込み型からRefined 型への変換をしたくなったので試してみました。
Refined型→Refined型への変換(同じ型どうし)
これは特に工夫することなく行えました。
組み込み型→Refined型への変換
結論からいうとこのパターンではTransformerを定義する必要がありました。具体的には以下の例(String → UserId
)のようになります。
これはRefinedTypeOps#unsafeFrom
を使った一番単純な実装ですが#from
を使うことで例外翻訳することもできると思います。
いずれにしても実行時例外をスローするしかないので設計によってはChimney以外の場所でバリデーションを行なったり、例外をハンドリングする必要があると思います。
import io.scalaland.chimney.Transformer
import io.scalaland.chimney.dsl._
import eu.timepit.refined._
import eu.timepit.refined.api._
import eu.timepit.refined.string._
// case class with Refined type field.
final case class User(name: UserName, id:UserId)
object User {
type UserId = String Refined MatchesRegex[W.`"[0-9]+"`.T]
object UserId extends RefinedTypeOps[UserId, String]
}
final case class UserName(firstName:String, lastName:String)
// Response from GET User API
final case class ReadUserResponse(id:String, firstName:String, lastName:String)
val response:ReadUserResponse = ReadUserResponse("123", "firstName", "lastName")
//Transformerを定義
implicit val stringToUserId:Transformer[String, UserId] = (src: String) => UserId.unsafeFrom(src)
val user:User = response.into[User]
.withFieldComputed(_.name, f => UserName(f.firstName, f.lastName))
.transform
println(response) // ReadUserResponse(123,firstName,lastName)
println(user) // User(UserName(firstName,lastName),123)
最後に
Chimneyを上手く使えばコード量をかなり削減できると思います。