Slide 1

Slide 1 text

「2024年版 Kotlin サーバーサイドプログラミング 実践開発」の補講 〜O/Rマッパー編〜 2024年7月18日 Server-side Kotlin Night 2024/07 竹端 尚人

Slide 2

Slide 2 text

自己紹介

Slide 3

Slide 3 text

竹端 尚人 主にバックエンドエンジニア Twitter: @n_takehata ● 2006.04〜 公務員 ● 2007.12〜 SES ● 2011.04〜 モバイルゲーム開発(サーバーサイド Kotlinを始める) ● 2020.12〜 フリーランス(バックエンド開発、 テックリード、技術顧問など) 概要 現在は主に、クラウド型電子カルテを開発している株式 会社ヘンリーでエンジニアとして従事 Kotlin愛好会というコミュニティの運営もやっています

Slide 4

Slide 4 text

Kotlin愛好会も明日やります!

Slide 5

Slide 5 text

● CEDEC 2018、2019登壇 ● Software Design 2019年2月号〜4月号で短期連載 「サーバーサイド開発の品質を向上させる Java→Kotlin移行のススメ」執筆 ● 2021年4月 書籍「Kotlin サーバーサイドプログラ ミング実践開発」出版 ● 2023年4月 Techpitにて「Kotlin入門ガイドー言語 思想から特徴・歴史・使いどころまで、まるっと予 備知識がわかる教科書」執筆 ● Kotlin Fest 2024登壇 登壇、執筆

Slide 6

Slide 6 text

2024年版 Kotlin サーバーサイドプログラミング実践開発

Slide 7

Slide 7 text

Kotlin Fest 2024で話した内容から、 盛り上がったO/Rマッパーについて深堀って話します

Slide 8

Slide 8 text

Kotlin Fest 2024の発表資料はこちら https://speakerdeck.com/n_takehata/kotlin-s erver-side-programming-practice-2024

Slide 9

Slide 9 text

アジェンダ 1. Kotlin Fest 2024の振り返り 2. 各種O/Rマッパーのおさらい 3. 色々なSQLを実装してみる 4. まとめ

Slide 10

Slide 10 text

アジェンダ 1. Kotlin Fest 2024の振り返り 2. 各種O/Rマッパーのおさらい 3. 色々なSQLを実装してみる 4. まとめ

Slide 11

Slide 11 text

1. Kotlin Fest 2024の振り返り

Slide 12

Slide 12 text

「2024年版 Kotlin サーバーサイドプログラミング 実践開発」 の内容

Slide 13

Slide 13 text

● 「Kotlin サーバーサイドプログラミング実践開発」の 内容紹介 ● 現在のサーバーサイドKotlinで使えるフレームワークの 紹介(Web、DI、O/Rマッパー、テスト) ● 今Kotlinでアプリケーションを作る場合のフレームワー ク選定の紹介 「2024年版 Kotlin サーバーサイドプログラミング 実践開発」 の内容

Slide 14

Slide 14 text

● Ktor(Webアプリケーションフレームワーク) ● Koin(DIフレームワーク) ● JOOQ(O/Rマッパー) ● Kotest(テストフレームワーク)

Slide 15

Slide 15 text

RepositoryImpl O/Rマッパー (JOOQ) ドメイン オブジェクト Repository Test (Kotest) Service Controller (Ktor) DI(Koin)

Slide 16

Slide 16 text

Ask the Speaker、懇親会で O/Rマッパーの話で盛り上がった

Slide 17

Slide 17 text

● セッションでも「O/Rマッパーが一番迷った」と話して いた ● 「O/Rマッパーだけは何使おうか迷うんですよね」とい う悩みが多かった ● SQLに近い形で書ける方がいいという意見に「やっぱ りそうですよね」というリアクション O/Rマッパーの話で盛り上がった

Slide 18

Slide 18 text

結局エンジニアはSQLを書きたい(人が多い)

Slide 19

Slide 19 text

なぜエンジニアはSQLを書きたいのか?

Slide 20

Slide 20 text

● 結局「こういうSQLを書きたいからコードは・・・」 という順序で考えている ● テーブル設計を意識してデータを扱うのに、SQLだけ 抽象化しても混乱する ● テーブルもSQLも意識せず「こういうデータが欲し い」を考えるだけにできないと抽象化の意味がない なぜエンジニアはSQLを書きたいのか?

Slide 21

Slide 21 text

結局エンジニアはSQLを書きたい(人が多い) 書きたいわけではないけど、いい具合に抽象化された ソリューションがない?

Slide 22

Slide 22 text

今回は各種O/Rマッパーで色々なSQLの 実装方法を紹介します

Slide 23

Slide 23 text

参考: Kotlin Fest 2024登壇の振り返り(ブログ) https://blog.takehata-engineer.com/entry/loo k-back-at-kotlin-fest-2024

Slide 24

Slide 24 text

アジェンダ 1. Kotlin Fest 2024の振り返り 2. 各種O/Rマッパーのおさらい 3. 色々なSQLを実装してみる 4. まとめ

Slide 25

Slide 25 text

2. 各種O/Rマッパーのおさらい

Slide 26

Slide 26 text

● Exposed ● JOOQ ● Ktorm Kotlin Fest 2024で紹介したO/Rマッパー

Slide 27

Slide 27 text

Exposed

Slide 28

Slide 28 text

● JetBrains社が開発しているKotlin製のO/Rマッパー ● SQLライクに実装できるDSL、軽量なDAOという2つの アクセス方法が用意されている ● 長い間0.x系が続いていたが、2024年中に1.0になるこ とが予定されている Exposedとは?

Slide 29

Slide 29 text

JOOQ

Slide 30

Slide 30 text

● SQLに近いDSLでのクエリ作成が可能 ● テーブルスキーマからのコード生成が可能で、もとも とJavaのO/RマッパーだがKotlinのコード生成にも対応 している ● R2DBCによるノンブロッキングI/Oにも対応 JOOQとは?

Slide 31

Slide 31 text

Ktorm

Slide 32

Slide 32 text

● 純粋なJDBCに基づいたKotlin用の軽量なO/Rマッパー ● Kotlin製のサードパーティフレームワーク ● 生のSQLに近い形で書ける柔軟なDSLが用意されている Ktormとは?

Slide 33

Slide 33 text

Kotlin Fest 2024では紹介しなかった その他のO/Rマッパー

Slide 34

Slide 34 text

● MyBatis ● Doma2 ● Komapper その他のO/Rマッパー

Slide 35

Slide 35 text

アジェンダ 1. Kotlin Fest 2024の振り返り 2. 各種O/Rマッパーのおさらい 3. 色々なSQLを実装してみる 4. まとめ

Slide 36

Slide 36 text

3. 色々なSQLを実装してみる

Slide 37

Slide 37 text

● Exposed ● JOOQ ● Ktorm ※ExposedはSQL DSLを使用します この3つのO/Rマッパーで色々なクエリを書いて 比較していきます

Slide 38

Slide 38 text

使用するテーブル(RDBはMySQLを使用) CREATE TABLE users ( id varchar(10) NOT NULL, name varchar(50) NOT NULL, age int NOT NULL, PRIMARY KEY (id) ); CREATE TABLE user_purchase_histories ( id varchar(10) NOT NULL, user_id varchar(10) NOT NULL, purchase_date date NOT NULL, price int NOT NULL, PRIMARY KEY (id) );

Slide 39

Slide 39 text

基本的なCRUDのクエリ (Kotlin Festからの再掲)

Slide 40

Slide 40 text

transaction { // Insert Users.insert { it[id] = "kotlin" it[name] = "Kotlin Fest" it[age] = 3 } // Update Users.update({ Users.id eq "kotlin" }) { it[age] = 4 } // Select val users = Users.select(Users.name, Users.age).where { Users.age greaterEq 3 } // Delete Users.deleteWhere{ id eq "kotlin" } } Exposed

Slide 41

Slide 41 text

// Insert dslContext.insertInto(USERS) .columns(USERS.ID, USERS.NAME, USERS.AGE) .values("kotlin", "Kotlin Fest", 3) .execute() // Update dslContext.update(USERS) .set(USERS.AGE, 4) .where(USERS.ID.eq("kotlin")) .execute() // Select val users = dslContext.selectFrom(USERS).where(USERS.AGE.ge(3)).fetch() // Delete dslContext.deleteFrom(USERS) .where(USERS.ID.eq("kotlin")) .execute() JOOQ

Slide 42

Slide 42 text

// Insert database.insert(Users) { set(it.id, "kotlin") set(it.name, "Kotlin Fest" ) set(it.age, 3) } // Update database.update(Users) { set(it.age, 4) where { it.id eq "kotlin" } } // Select val users = database.from(Users).select().where { Users.age greaterEq 3 } // Delete database.delete(Users) { it.id eq "kotlin" } Ktorm

Slide 43

Slide 43 text

● Exposedはfromやupdate文のsetなど、キーワードを 削って抽象化している ● JOOQはほぼ生のSQLと同じ構文で書ける ● Ktormも生のSQLに近いが、fromの位置やinsertで使 うsetキーワードなど、少し差分がある

Slide 44

Slide 44 text

GROUP BY、ORDER BY、LIMIT、OFFSET

Slide 45

Slide 45 text

select age, count(*) from USERS group by age order by count(*) desc limit 3 offset 1;

Slide 46

Slide 46 text

Users.select(Users.age, Users.id.count()) .groupBy(Users.age) .orderBy(Users.id.count(), SortOrder.DESC) .limit(3, offset = 1) Exposed

Slide 47

Slide 47 text

dslContext.select(USERS.AGE, count()) .from(USERS) .groupBy(USERS.AGE) .orderBy(count().desc()) .limit(1, 3) .fetch() JOOQ

Slide 48

Slide 48 text

dslContext.select(USERS.AGE, count()) .from(USERS) .groupBy(USERS.AGE) .orderBy(count().desc()) .limit(3) .offset(1) .fetch() offsetを別で渡すことも可能

Slide 49

Slide 49 text

database.from(Users) .select(Users.age, count()) .groupBy(Users.age) .orderBy(count().desc()) .limit(1, 3) Ktorm

Slide 50

Slide 50 text

database.from(Users) .select(Users.age, count()) .groupBy(Users.age) .orderBy(count().desc()) .limit(3) .offset(1) off offsetを別で渡すことも可能

Slide 51

Slide 51 text

● 3つとも書き方に大きな差分はない ● ExposedがFromがない分短いくらい

Slide 52

Slide 52 text

JOIN

Slide 53

Slide 53 text

select * from USERS left join USER_PURCHASE_HISTORIES on USERS.id = USER_PURCHASE_HISTORIES.user_id;

Slide 54

Slide 54 text

Users.leftJoin( UserPurchaseHistories , { Users.id }, { UserPurchaseHistories.userId } ).selectAll() Exposed

Slide 55

Slide 55 text

dslContext.select() .from(USERS) .leftJoin(USER_PURCHASE_HISTORIES) .on(USERS.ID.eq(USER_PURCHASE_HISTORIES.USER_ID)) .fetch() JOOQ

Slide 56

Slide 56 text

database .from(Users) .leftJoin( UserPurchaseHistories , on = Users.id eq UserPurchaseHistories.userId) .select() Ktorm

Slide 57

Slide 57 text

● Exposedはonのキーワードを使わず、少し抽象化して いる ● JOOQとKtormは近いが、JOOQの方がonをチェーンし て書いてる分、より生のSQLに近い印象

Slide 58

Slide 58 text

サブクエリ

Slide 59

Slide 59 text

select * from USERS where exists( select * from USER_PURCHASE_HISTORIES where USERS.id = USER_PURCHASE_HISTORIES.user_id );

Slide 60

Slide 60 text

Users.select(Users.columns) .where { exists( UserPurchaseHistories .select(UserPurchaseHistories.id).where{ Users.id eq UserPurchaseHistories.userId } ) } Exposed

Slide 61

Slide 61 text

dslContext.selectFrom(USERS) .whereExists( dslContext.selectOne() .from(USER_PURCHASE_HISTORIES) .where(USER_PURCHASE_HISTORIES.USER_ID.eq(USERS.ID)) ) .fetch() JOOQ

Slide 62

Slide 62 text

database .from(Users) .select() .where { exists( database.from(UserPurchaseHistories) .select(UserPurchaseHistories.id) .where { UserPurchaseHistories.userId eq Users.id } ) } Ktorm

Slide 63

Slide 63 text

● JOOQだけwhereExists、selectOneなどの関数があり 少し抽象化されている ● ExposedとKtormはwhere→exists→selectの階層に なっており、生のSQLに近い

Slide 64

Slide 64 text

JOINして絞り込む

Slide 65

Slide 65 text

select USERS.id, USERS.name, sum(USER_PURCHASE_HISTORIES.price) from USERS left join USER_PURCHASE_HISTORIES on USERS.id = USER_PURCHASE_HISTORIES.user_id where USERS.age >= 20 group by USERS.id having sum(USER_PURCHASE_HISTORIES.price) >= 3000;

Slide 66

Slide 66 text

Users.leftJoin(UserPurchaseHistories , { Users.id }, { UserPurchaseHistories .userId }) .select(Users.id, Users.name, UserPurchaseHistories .price.sum()) .where { Users.age greaterEq 20 } .groupBy(Users.id) .having { UserPurchaseHistories .price.sum() greaterEq 3000 } Exposed

Slide 67

Slide 67 text

dslContext.select(USERS.ID, USERS.NAME, DSL.sum(USER_PURCHASE_HISTORIES.PRICE)) .from(USERS) .leftJoin(USER_PURCHASE_HISTORIES) .on(USERS.ID.eq(USER_PURCHASE_HISTORIES.USER_ID)) .where(USERS.AGE.ge(20)) .groupBy(USERS.ID) .having(DSL.sum(USER_PURCHASE_HISTORIES.PRICE). ge(BigDecimal(3000))) .fetch() JOOQ

Slide 68

Slide 68 text

database .from(Users) .leftJoin(UserPurchaseHistories, on = Users.id eq UserPurchaseHistories .userId) .select(Users.id, Users.name, sum(UserPurchaseHistories.price)) .where { Users.age greaterEq 20 } .groupBy(Users.id, Users.name) .having { sum(UserPurchaseHistories.price) greaterEq 3000 } Ktorm

Slide 69

Slide 69 text

● 少し複雑で長いSQLになった分、構文で抽象化してい るExposedの短さがより際立っている ● JOOQは生のSQLに近い構文な上、”DSL.”のように書か なければいけないキーワードが多いので長い

Slide 70

Slide 70 text

アジェンダ 1. Kotlin Fest 2024の振り返り 2. 各種O/Rマッパーのおさらい 3. 色々なSQLを実装してみる 4. まとめ

Slide 71

Slide 71 text

4. まとめ

Slide 72

Slide 72 text

● 書きたいわけではないが、書く以外でいいソリュー ションがない ● SQLだけ抽象化された状態では微妙 ● 結果SQLっぽく書けるO/Rマッパーが好まれやすい エンジニアはSQLを書きたい?

Slide 73

Slide 73 text

● ExposedはFROMを省略したりJOINの書き方が簡略化 されたりと、少し短く書けるようになっている ● JOOQは一番SQLに近い構文で書ける印象 ● KtormもSQLに近いが、句の順序やJOINの書き方など 一部差分がある 各種O/Rマッパーの特性

Slide 74

Slide 74 text

Have a nice Server-side Kotlin!