Slide 1

Slide 1 text

たけうち ひでゆき @chimerast Delegatesと 拡張関数・拡張プロパティ 利用ライブラリの、かゆいところに手を届かせる 2021/09/22 Server-Side Kotlin Study #2

Slide 2

Slide 2 text

株式会社イエソド 代表取締役 エンジニア • 静的型付け言語以外でのプロダクト開発を認めない系ITエンジニア • JVM以外信じない系ITエンジニア • パソコンの大先生 • 物理レイヤーからフロントエンドまで • 全業種・全職種の人とだいたい話が合わせられる • 元 株式会社ユーザベース チーフテクノロジスト 「SPEEDA」「NewsPicks」「FORCAS」を作った 東証の鐘を鳴らしたことがある たけうち ひでゆき @chimerast きれいなたけうちさん マサカリを投げるたけうちさん

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

企業の人・組織・情報に まつわる非効率をなくす

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

要するに次世代の 時系列の人組織データベース と、それを起点とした 
 各種サービスの権限管理サービス 作ってます!

Slide 10

Slide 10 text

言語 Kotlin / TypeScript フロントエンド HTML / CSS / Sass(SCSS) / TypeScript / Vue.js / Vuex バックエンド Ktor / Exposed / Express データベース PostgreSQL (Cloud SQL) インフラ GCP / Docker / Kubernetes / Istio バージョン管理等 GitHub / JIRA CI/CD CloudBuild / GitHub Actions / Kustomize / Argo CD コミュニケーション Slack / Notion / Zoom / Google Meet / Miro / Figma 技術スタック

Slide 11

Slide 11 text

本題

Slide 12

Slide 12 text

お品書き "Open Classes"について 拡張関数・拡張プロパティ おさらい Delegates おさらい 合わせ技と実際のユースケース 
  「Exposedがちょっとつらかった件」

Slide 13

Slide 13 text

"Open Classes" について

Slide 14

Slide 14 text

"Open Classes" について • いにしえから存在する、 
 「俺、このクラス or インターフェースに横から機能を追加・変更したいんだよね」 
 というプログラマのエゴを満たすための機能 • "Extension Classes" とも言う • 情報科学の世界では古くからの研究分野 (論文いっぱいある)

Slide 15

Slide 15 text

"Open Classes" 全般について • あんま使うな • ライブラリ開発者の設計思想を破壊する可能性がある • モジュラリティを破壊する可能性がある • でもかゆいところに手が届いて便利だよね • ライブラリ開発者が想定しきれなかった所をモンキーパッチ • 変換関数は、拡張関数使った方が収まりが良いよね

Slide 16

Slide 16 text

Kotlinでの"Open Classes" • 拡張関数・拡張プロパティ • 元のクラスの挙動を変更することはなく機能の追加のみ • スコープの概念がある ← 結構重要 • ほどよくできることが制限されているので、ちょうどよい案配 • 完全な無法地帯にはならない ※ 個人の感想です

Slide 17

Slide 17 text

拡張関数・拡張プロパティ 
 おさらい

Slide 18

Slide 18 text

拡張関数 fun ByteArray.uuidv5(namespace: UUID): UUID { return UUIDv5(namespace, this) } fun <拡張したいクラス>.<新しく追加したい関数名>(<引数>): <返り値の型> { // 拡張したクラスのインスタンスが this としてアクセスできるので // 何かしらの処理をして返す } 書き方 実際の例

Slide 19

Slide 19 text

拡張プロパティ val編 val <拡張したいクラス>.<新しく追加したいプロパティ名> get(): <プロパティの型> { // 拡張したクラスのインスタンスが this としてアクセスできるので // インスタンスの状態を返す } 書き方 実際の例 val String.uuid get() = when (this.length) { 22 -> UUIDConverter.decodeBase64(this) 36 -> requireNotNull(UUID.fromString(this)) else -> throw IllegalUUIDFormatException(this) }

Slide 20

Slide 20 text

拡張プロパティ var編 var <拡張したいクラス>.<新しく追加したいプロパティ名> get(): <プロパティの型> { // 拡張したクラスのインスタンスが this としてアクセスできるので // インスタンスの状態を返す } set(value: <プロパティの型>){ // 拡張したクラスのインスタンスが this としてアクセスできるので // インスタンスの状態を変更する } 書き方

Slide 21

Slide 21 text

拡張関数・プロパティのスコープ制御 • クラス内で、 private や protected で拡張関数・プロパティを定義すると、 
 そのクラス内もしくはサブクラス内でしか利用できない拡張関数・プロパティが 
 宣言できる class Foo { fun say(): String = "foo".bar() // Fooの中でしか使えない private fun String.bar(): String = this + "bar" } ゴミみたいな例

Slide 22

Slide 22 text

拡張関数・プロパティの悩みどころ • 引数をとらないような拡張をする場合に、 
 拡張関数にするのか、拡張プロパティ(val)にするのか悩む • 重たい処理は関数、簡単な変換処理はプロパティにするのが無難? • 拡張フィールド的なものは存在しない (→ 後ほどのネタ) • バッキングフィールド( fi eld 識別子)も使えない

Slide 23

Slide 23 text

Delegates おさらい

Slide 24

Slide 24 text

Delegates Delegates って? val lazyValue: String by lazy { "Hello" } 上のようなフィールド定義の lazy関数 が Delegateインスタンスを返す プロパティのsetterやgetterで行う処理を共通化できる よくある用途としては、 lazy (遅延初期化)、 Delegates.observable() (監視) val <プロパティ名>: <プロパティの型> by Delegatesを使ったプロパティ定義の構造

Slide 25

Slide 25 text

Delegates の作り方 Delegates の定義のしかた class { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { // プロパティのgetterの代わりに実行される処理 } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { // プロパティのsetterの代わりに実行される処理 } }

Slide 26

Slide 26 text

Delegates の作り方 その二 とりあえず、下記のインターフェースを実装すればよい interface ReadWriteProperty interface ReadOnlyProperty val 用 var 用

Slide 27

Slide 27 text

拡張関数・拡張プロパティとDelegates 合わせ技と実際のユースケース 
 Exposedがちょっとつらかった件

Slide 28

Slide 28 text

Exposed がちょっとつらかった部分 課題編 1. Exposed のデータソースとして HikariCP を利用 2. 開発時にコードの更新をして Ktor の Auto-reload が走ると、 Exposed と HikariCP は再作成されるが、 更新前の HikariCP のコネクションプールは破棄 されず残ったまま 3. 何度かコードを更新すると、ローカルのDB側の最大受け入れコネクション数を 超えてしまい、JVMごと再起動する必要が出てくる

Slide 29

Slide 29 text

Exposed がちょっとつらかった部分 解決できなかった編 1. Ktor 側には、 Auto-reload が走ったときに、環境の破棄をフックする為の environment.onShutdown() というメソッドが用意されていた 2. Exposed 側は、コネクションを管理する Database インスタンスのクローズ処 理をする方法はあったが、紐付く HikariCP を破棄するためのフック等が全く用 意されていなかった。 3. そうだ、拡張関数でDatabase クラスに HikariCP を閉じるためのメソッドを追 加すればいいじゃないか。 → 失敗

Slide 30

Slide 30 text

拡張関数・プロパティの悩みどころから抜粋 拡張フィールド的なものは存在しない 拡張関数および拡張プロパティは あくまで元のクラスファイルは変更せず 関数を生成しうまくコードに差し込んでいるだけ そのため、 Database インスタンスに HikariCP のインスタンスを 保持するためのフィールドの追加ができなかった。

Slide 31

Slide 31 text

拡張プロパティとDelegatesでなんとかする fun connect(): Database { val hikariDataSource = HikariDataSource() return Database.connect(hikariDataSource).apply { dataSource = hikariDataSource } } private var Database.dataSource: HikariDataSource by ExtensionInstanceVariable() fun Database.shutdown() { this.dataSource.close() TransactionManager.closeAndUnregister(this) } Database.dataSource プロパティへの操作を 
 ExtensionInstanceVariable() に移譲

Slide 32

Slide 32 text

拡張プロパティの値を保持するDelegate 無理やり作られた解決策 private class ExtensionInstanceVariable : ReadWriteProperty { private val valueMap: MutableMap = Collections.synchronizedMap(WeakHashMap()) override fun getValue(thisRef: Any, property: KProperty<*>): T { return valueMap[thisRef] ?: throw IllegalStateException("Property ${property.name} should } override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { valueMap[thisRef] = value } } WeakHashMap で thisRef に紐付けて値を保持 速度的な性能は保証できない

Slide 33

Slide 33 text

ダメな拡張プロパティのためのDelegates private class BadExtensionInstanceVariable : ReadWriteProperty { private var value: T? = null override fun getValue(thisRef: Any, property: KProperty<*>): T { return value ?: throw IllegalStateException("Property ${property.name} should be initial } override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { this.value = value } } 拡張プロパティへのDelegateは、インスタンス毎ではなく、 一つの object に保持されるため、全てのインスタンスで 
 同じ値が返るようになるし上書きされる

Slide 34

Slide 34 text

拡張関数・拡張プロパティでうまくいった Delegatesを添えて environment.onShutdown { db.shutdown() } スッキリ!快適! DBを利用したシナリオテストも 挙動不審な動作が無くなった

Slide 35

Slide 35 text

本日のまとめ

Slide 36

Slide 36 text

まとめ • 拡張関数・プロパティとDelegatesを合わせると 
 なんか色々マジカルなことができるよ • Delegatesも自分で一回作ってみると面白い使いどころがわかるよ • 拡張関数・プロパティは、節度を持って利用しましょう

Slide 37

Slide 37 text

ご静聴ありがとうございました 株式会社イエソドでは、 Kotlinを使ってサーバサイドを開発したいエンジニアを募集しています。 TypeScriptとVue.jsを使って開発したいエンジニアも募集してます。