クソ記事投稿週間な今日この頃です。
業務でScalaを使っているのでコードはScalaです。ご了承ください。
検討対象のコード
ドメイン層で以下のようなクラスが定義されているとしたときに、
セッターはもちろんのこと、ゲッターを禁止にするために、属性にprivate
がついている前提であったとします。
/**
* 顧客クラス
*
* @param name 名前
* @param age 年齢
* @param address 住所
*/
case class Customer(private val name: Name, private val age: Age, private val address: Address) {
// TODO 様々なふるまいが定義されいている
}
case class Name(private val first: String, private val last: String)
case class Age(private val value: Int)
case class PostalCode(private val value: String)
case class City(private val value: String)
case class Address(private val postalCode: PostalCode, private val city: City)
アプリケーション層のアプリケーションサービスでプレゼンテーション層に値を返すために、以下のようなクラスが定義されています。
/**
* レスポンス用のクラス
* DTO(Data Transfer Object)
* ドメインの知識を流用させたくないので、ただの値の入れ物になっている
*/
case class Response(firstName: String,
lastName: String,
age: Int,
address: String)
このような場合に、下記のドメインモデルのオブジェクトCustomer
をResponse
に変換するためのdomainModelToResponseModel
メソッドをどう実装するのがシンプルかを少し考えました。
// アプリケーションサービス
object SomeAppService {
def execute(): Response = {
// TODO 何かしらの処理がここに入る
val alice = Customer(
Name("チョコミント", "クソ野郎"),
Age(99),
Address(PostalCode("888-8888"), City("HappyTown")))
// ドメインモデルをResponse用のDTOに詰めかえる
// このメソッドの実装について考える
domainModelToResponseModel(alice)
}
考えた結果
ドメインオブジェクトの属性のprivate
修飾子を外してしまうと、ゲッター使い放題を許可してしまうことになるので、
アプリケーション層→プレゼンテーションへの値の詰替え限定でケースクラスに実装されているunapply
メソッドを利用するのがいいのかなと思いました。
値の詰替えをするdomainModelToResponseModel
メソッドは以下のようになりました。
/**
* ドメインオブジェクトからレスポンス用のオブジェクトに変換する
* アプリケーションサービスに定義
*
* @param customer ドメインオブジェクトのCustomer
* @return レスポンス用のオブジェクト(DTO)
*/
private def domainModelToResponseModel(customer: Customer): Response = {
// ケースクラスのunapplyメソッドを使って値を取り出す
val Customer(
Name(firstName, lastName),
Age(age),
Address(PostalCode(postalCode), City(city))) = customer
val address = s"$postalCode $city"
Response(firstName, lastName, age, address)
}
ついでにファーストクラスコレクションもこんな感じに取り出せます。
/**
* Customerのファーストクラスコレクション
*/
case class Customers(private val customers: Set[Customer]) {
def nonEmpty: Boolean = customers.nonEmpty
def empty: Boolean = !nonEmpty
// TODO 様々なふるまいが定義されている
}
val Customers(values) = customers
values.map { customer =>
val Customer(
Name(firstName, lastName),
Age(age),
Address(PostalCode(postalCode), City(city))) = customer
val address = s"$postalCode $city"
Response(firstName, lastName, age, address)
}
他の言語だと値の詰替えってどのように実施するのだろうかということが疑問に残りました。単純に考えると、ゲッターぐらいしか思い浮かばなかったです。。