SlideShare a Scribd company logo
(DDD)
Kotlin 1.5 で stable になった
value class を深掘りする
baseballyama / 山下 裕一朗
目次
1. (DDD文脈での) value object とは何か
2. (Kotlin文脈での) value object とは何か
3. 実際に使ってみてわかったこと / 注意点
第一章
(DDD文脈での) value object とは何か
(DDD文脈での) value object とは何か
「実践ドメイン駆動設計」によると、value object とは以下の特徴を満たしたオブジェクト
のことだとされています。
1 計測/定量化/説明 ドメイン内の何かを計測したり定量化したり説明したりする
2 不変性 状態を不変に保つことができる
3 概念的な統一体 関連する属性を不可欠な単位として組み合わせることで、概念的な統一体を形成する
4 交換可能性 計測値や説明が変わったときには、全体を完全に置き換えられる
5 等価性 値が等しいかどうかを、他と比較できる
6 副作用のない振る舞い 協力関係にあるその他の概念に「副作用のない振る舞い」を提供する
いや難しいな!
細かいことは各自で調べて頂くとして...
実例で理解する
弊社では、value object を以下のように活用しています。(一番簡単な例)
data class MailAddress(private val text: String) {
companion object {
private val PATTERN = "(メールアドレスの正規表現 )".toRegex()
}
init {
if (!text.matches(PATTERN)) throw Exception("「${text}」は不正な値です。")
}
}
// Exceptionがスローされる
MailAddress("foo")
// trueが出力される
println(MailAddress("foo@bar.com") == MailAddress("foo@bar.com"))
value object を使用するメリット (個人的に)
● インスタンス生成の時点で正当な値であることを保証できる
○ 例えば、弊社ではHTTPリクエストで受信したJSONをPOJOにマッピングする時点で、
不正な値が入っている場合に 400 を返却するようにしています。
● メソッドの引数の設定間違いをコンパイル時点で防げる
fun doSomething(userId: UserID, productId: ProductID) { /** do something */ }
doSomething(ProductID("def456"), UserID("abc123")) // コンパイルエラーで間違いに気づける
value object を使用するメリット (個人的に)
● 業務知識をバリューオブジェクトに凝縮できる
○ 良い例 (バリューオブジェクトを使用する例 )
data class RelativeURL(private val url: String) { // 初期化処理 (値の正当性の確認 ) は記載を省略
fun toAbsoluteURL(basePath: String): AbsoluteURL = AbsoluteURL(removeTrailingSlash(basePath) + "/" + url)
}
// ポイント1: 型レベルで値の妥当性が保証されている
// ポイント2: 相対パスに対して可能な操作が 1個のクラスに凝縮されており理解しやすい
val path = relativePath.toAbsolutePath("https://example.com")
// do something
○ 悪い例 (バリューオブジェクトを使用しない例 )
// ポイント1: 型レベルで値の妥当性が保証されないので冗長な検査が必要
if (!isRelativePath(path)) throw Exception("This is not relative path")
// ポイント2: 相対パスに対する操作がユーティリティクラスに切り出されており凝縮度が低い
// (凝縮度が低いことがなぜよくないことであると言われているかは各自ぐぐって頂ければと思います )
val path = URLUtils.toAbsolutePath("https://example.com", relativePath)
// do something
弊社では...
弊社では、APIのエンドポイントで受け取るプロパティを全てバリューオブジェクトにして
おり、もし、異常な値を含むリクエストだった場合は、その時点で400をレスポンスするよ
うにしています。
また、アプリケーション全体でバリューオブジェクトを使用しているので、冗長な値チェッ
ク (例えばメールアドレスが正統か) が存在しません。
value object は便利
第二章
(Kotlin文脈での) value object とは何か
コード例 (冒頭に出てきた data class を value class に移植)
@JvmInline // JVM バックエンドをターゲットにする場合のみ必要
value class MailAddress(private val text: String) {
companion object {
private val PATTERN = "(メールアドレスの正規表現 )".toRegex()
}
init {
if (!text.matches(PATTERN)) throw Exception("「${text}」は不正な値です。")
}
}
基本的には `data` を `value` に書き換えばOK (簡単!)
value class は Kotlin1.5 で stable になった新機能
機能概要
● 一言でいえば、data class のサブセット。以下の制約がある
○ 単一のプロパティしか持てない
○ mutableなプロパティを持てない ( var を使ったプロパティが宣言できない )
○ 参照の比較はできない ( === を使った比較ができない )
● value class に依存したメソッド名はメソッド名がマングリングされる
@JvmInline
value class UInt(val x: Int)
// コンパイル後、関数は compute-<hashcode>(int x) となる (<hashcode> は7桁の英数字になる)
fun compute(x: UInt) { }
参考: https://kotlinlang.org/docs/inline-classes.html / https://star-zero.medium.com/kotlin%E3%81%AEvalue-class-27e865696f35
value class は Kotlin1.5 で stable になった新機能
機能概要 (続き)
● コンパイル時にできる限りプリミティブ型としてバイトコードを生成するので data classより高速
○ 但し以下のような場合はプリミティブ型への最適化はされない
@JvmInline
value class Foo(val i: Int) : I
fun <T> asGeneric(x: T) {} // ジェネリクスとして引数を設定する場合
fun asInterface(i: I) {} // インターフェイスとして引数を設定する場合
fun asNullable(i: Foo?) {} // Nullableなプロパティとして引数を設定する場合
val f = Foo(42)
asGeneric(f)
asInterface(f)
asNullable(f)
参考: https://kotlinlang.org/docs/inline-classes.html / https://star-zero.medium.com/kotlin%E3%81%AEvalue-class-27e865696f35
value object を完全に理解した
第三章
実際に使ってみてわかったこと / 注意点
(注意点1) JSONシリアライズ
例えば、jackson を使用して以下の User クラスをシリアライズすると
プロパティ名がマングルされてしまいます。
// kotlin
value class UserID(val userId: String)
value class UserName(val userName: String)
data class User(val userId: UserID, val name: UserName)
// JSON
// 注意: プロパティ名がマングルされている
{
"userId-abc1234": "abc123",
"name-def5678": "foo"
}
(注意点1) JSONシリアライズ
解決策としては以下があります。
● 🟢 kotlinx.serialization を使用する
● 🟡 gson を使用する
○ 但し、gsonは現在メンテナンスモードなので、今後の積極的な開発はなさそう
?
● ❌ jackson-module-kotlin
○ デシリアライズがサポートされていないよう
? (GitHub issue)
● ❌ Moshi
○ 全くサポートされていないよう? (Github issue)
調査結果: https://github.com/baseballyama/kotlin-json-research
(注意点2) mockito が対応していない
value class を使用したオブジェクトをmockitoで使用できません。
value class UserID(val userId: String)
// モック
val someClass = Mockito.mock(SomeClass::class.java)
Mockito.doNothing().`when`(someClass.doSomething(isA(UserID::class.java)))
fun <T> isA(type: Class<T>): T {
Mockito.isA(type)
return null as T
}
// エラーメッセージ
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "UserID.unbox-impl()"
because the return value of "isA(java.lang.Class)" is null
(注意点2) mockito が対応していない
解決策としては mockk を使用すれば良い (GitHub issue)
value class UserID(val userId: String)
// モック
val someClass = mockk<SomeClass>()
every {
someClass.doSomething(UserID(any()))
}
(注意点3) CGLIB を使用するライブラリが想定通りに動作しない1
例えばHibernateを使用してメソッドの自動生成機能を使用する場合、
userId-abcd123 というフィールドがUserEntity に存在しないためエラーになる。
// コンパイル前
fun findByUserId(userId: FlyleUserID): UserEntity?
// コンパイル後 / cglib による自動生成前 (イメージ)
// value class に依存しているメソッド名はマングルされることによって発生する
fun findByUserId-abcd123(userId: String): UserEntity?
補足: CGLIB とは
CGLIBとは、Javaコード生成ライブラリです。
クラスファイルを実行時に読み込んで編集することが可能です
(Javassistと同じジャンル)。 CGLIB
は、Hibernate、iBatisなどのDBアクセスツールや、
Spring、SeasorなどのAOPコンテナなどで利用されています。
(注意点3) CGLIB を使用するライブラリが想定通りに動作しない1
例えばHibernateを使用してメソッドの自動生成機能を使用する場合、
userId-abcd123 というフィールドがUserEntity に存在しないためエラーになる。
// コンパイル前
fun findByUserId(userId: FlyleUserID): UserEntity?
// コンパイル後 / cglib による自動生成前 (イメージ)
// value class に依存しているメソッド名はマングルされることによって発生する
fun findByUserId-abcd123(userId: String): UserEntity?
(注意点3) CGLIB を使用するライブラリが想定通りに動作しない1
解決策は、@JvmName アノテーションを使用する。
@JvmName を使用することでコンパイル後のメソッド名を指定できる。
// コンパイル前
@Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("findByUserId")
fun findByUserId(userId: UserID): UserEntity?
// コンパイル後 / cglib による自動生成前 (イメージ)
fun findByUserId(userId: String): UserEntity?
(注意点3) CGLIB を使用するライブラリが想定通りに動作しない2
ジェネリクスなどプリミティブ型への最適化がされない使い方をしている場合、
想定通りに動作しない場合がある。
例えば、Hibernateの場合 UserID 型の処理方法を知らないのでエラーになる。
// コンパイル前
fun findByUserIds(userIds: List<UserID>): List<UserEntity>
// コンパイル後 / CGLIB による自動生成前 (イメージ)
// value class をジェネリクスで使用している場合、プリミティブ型への最適化がされない
fun findByUserId(userIds: List<UserID>): List<UserEntity>
これは僕の知る限りで、value class の使用を辞める以外の回避策がない。
まとめ
まとめ
● 🟢 cglib に依存したライブラリを使用していないのであれば簡単に性能向上を得られる
● 🟡 但し、ライブラリ選定に制約が発生する場合がある
● 🟡 また、アプリケーション開発において、 data class を わざわざvalue class に
置き換える必要があるほど性能チューニングが必要なケースがどれだけあるかは疑問
● ユーザー定義のプリミティブ型を使用する機能は、 JVMでも導入が検討されている。
(Project Valhalla)
Kotlin の value class はこれに先駆けた実装なので、どうしてもプリミティブ型への最適化ができない場合
があるのが現状。プリミティブ型への最適化がされない場合がある仕様を許容できない場合は、 Project
Valhalla が実装されるまで待つのが吉 ?

More Related Content

PPTX
C#/.NETがやっていること 第二版
PDF
Kotlinアンチパターン
PPTX
[DL輪読会]Wav2CLIP: Learning Robust Audio Representations From CLIP
PPTX
[DL輪読会]Batch Renormalization: Towards Reducing Minibatch Dependence in Batch-...
PDF
ドメイン駆動設計のプラクティスでカバーできること、できないこと[DDD]
PDF
【メタサーベイ】Transformerから基盤モデルまでの流れ / From Transformer to Foundation Models
PPTX
[DL輪読会]GLIDE: Guided Language to Image Diffusion for Generation and Editing
PDF
研究室ゼミ ガイダンス資料
C#/.NETがやっていること 第二版
Kotlinアンチパターン
[DL輪読会]Wav2CLIP: Learning Robust Audio Representations From CLIP
[DL輪読会]Batch Renormalization: Towards Reducing Minibatch Dependence in Batch-...
ドメイン駆動設計のプラクティスでカバーできること、できないこと[DDD]
【メタサーベイ】Transformerから基盤モデルまでの流れ / From Transformer to Foundation Models
[DL輪読会]GLIDE: Guided Language to Image Diffusion for Generation and Editing
研究室ゼミ ガイダンス資料

What's hot (20)

PPTX
[DL輪読会]Live-Streaming Fraud Detection: A Heterogeneous Graph Neural Network A...
PDF
【DL輪読会】ConvNeXt V2: Co-designing and Scaling ConvNets with Masked Autoencoders
PDF
[Tutorial] Sentence Representation
PDF
DeNA流cocos2d xとの付き合い方
PPTX
どや!?おやつ神社 実践しているおやつ神社を通して見るカイゼンパターン
PDF
Transformer 動向調査 in 画像認識(修正版)
PDF
【メタサーベイ】基盤モデル / Foundation Models
PDF
【メタサーベイ】Vision and Language のトップ研究室/研究者
PDF
Transformerを多層にする際の勾配消失問題と解決法について
PPTX
DockerコンテナでGitを使う
PDF
深層学習と確率プログラミングを融合したEdwardについて
PDF
【メタサーベイ】数式ドリブン教師あり学習
PDF
第6回WBAシンポジウム:脳参照アーキテクチャ 駆動開発からの AGI構築ロードマップ
PDF
【DL輪読会】HRDA: Context-Aware High-Resolution Domain-Adaptive Semantic Segmentat...
PDF
機械学習モデルのサービングとは?
ODP
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
PPTX
【DL輪読会】Which Shortcut Cues Will DNNs Choose? A Study from the Parameter-Space...
PPTX
SSII2020SS: 微分可能レンダリングの最新動向 〜「見比べる」ことによる3次元理解 〜​
PPTX
動的輪郭モデル
PPTX
[DL輪読会]Graph R-CNN for Scene Graph Generation
[DL輪読会]Live-Streaming Fraud Detection: A Heterogeneous Graph Neural Network A...
【DL輪読会】ConvNeXt V2: Co-designing and Scaling ConvNets with Masked Autoencoders
[Tutorial] Sentence Representation
DeNA流cocos2d xとの付き合い方
どや!?おやつ神社 実践しているおやつ神社を通して見るカイゼンパターン
Transformer 動向調査 in 画像認識(修正版)
【メタサーベイ】基盤モデル / Foundation Models
【メタサーベイ】Vision and Language のトップ研究室/研究者
Transformerを多層にする際の勾配消失問題と解決法について
DockerコンテナでGitを使う
深層学習と確率プログラミングを融合したEdwardについて
【メタサーベイ】数式ドリブン教師あり学習
第6回WBAシンポジウム:脳参照アーキテクチャ 駆動開発からの AGI構築ロードマップ
【DL輪読会】HRDA: Context-Aware High-Resolution Domain-Adaptive Semantic Segmentat...
機械学習モデルのサービングとは?
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
【DL輪読会】Which Shortcut Cues Will DNNs Choose? A Study from the Parameter-Space...
SSII2020SS: 微分可能レンダリングの最新動向 〜「見比べる」ことによる3次元理解 〜​
動的輪郭モデル
[DL輪読会]Graph R-CNN for Scene Graph Generation
Ad

Similar to 2022/4/15_(DDD) Kotlin 1.5 で stable になった value class を深掘りする (10)

PDF
Swift らしい表現を目指そう #eventdots
PDF
F#によるFunctional Programming入門
PDF
Essential Scala 第5章 シーケンス処理
PDF
今日からできる!簡単 .NET 高速化 Tips
PDF
Boost Fusion Library
PDF
関数型プログラミング入門 for Matlab ユーザー
PDF
ちょっと詳しくJavaScript 第2回【関数と引数】
PDF
Boost.Flyweight
PPTX
C# 9.0 / .NET 5.0
PDF
C++ lecture-0
Swift らしい表現を目指そう #eventdots
F#によるFunctional Programming入門
Essential Scala 第5章 シーケンス処理
今日からできる!簡単 .NET 高速化 Tips
Boost Fusion Library
関数型プログラミング入門 for Matlab ユーザー
ちょっと詳しくJavaScript 第2回【関数と引数】
Boost.Flyweight
C# 9.0 / .NET 5.0
C++ lecture-0
Ad

2022/4/15_(DDD) Kotlin 1.5 で stable になった value class を深掘りする