Slide 1

Slide 1 text

SpringBoot+Kotlinで使う Exposed(仮) タイトルから(仮)を取り忘れた・・・!

Slide 2

Slide 2 text

自己紹介 名前:なかやまひろ(Twitterは@setys0でスナネコアイコン) お仕事:東京都内の企業でWebサービス開発     宮仕えに飽きるとフリーランス Java暦:お仕事だと10年ちょっと(たぶん)     一番最初に触ったのは学生時代にアプレット・・・ 最近の趣味:AIを使ってコードや絵を生成して遊んでます

Slide 3

Slide 3 text

今日のスライドはこちら 本スライドは右のQRコードから取得できます。 https://speakerdeck.com/wenas/jjug-ccc- 2023-exposed また、ちょっと試してみたい方向けに、DDLとクエリ のサンプルのプロジェクトを用意してみました。

Slide 4

Slide 4 text

最初におことわり 本日お話しするExposedはKotlinのSQLライブラリです。 解説やサンプルコードはSpringを利用していますが、ExposedをJavaで利用 することはできません。(たぶん・・・) 本セッションはKotlinに触れたことがなくても大丈夫なように、Exposedの紹 介だけでなく採用した経緯やその効果についてもお話ししたいと思います。

Slide 5

Slide 5 text

今日お話しすること Exposedの紹介 Exposedを採用した経緯と効果 Springに導入して動かすまで まとめ

Slide 6

Slide 6 text

今日お話ししないこと Exposedの細かい機能の紹介(20分じゃ足りないので・・・!) Spring+Kotlin+Exposedで構築したアプリケーションのうち、Exposedじゃ ない部分の話(APIとかデプロイとか・・・) Java要素・・・

Slide 7

Slide 7 text

Exposedとは マスコットのイカ!

Slide 8

Slide 8 text

Exposedとは ExposedはKotlinで実装されたSQLライブラリです。ライセンスはApache License 2.0で、4/17時点でGitHubの⭐は7033。 開発はKotlin、Intellij IDEAを開発しているJetBrains。OSSではありますが 大きな開発元が背後についているので安心感があります。

Slide 9

Slide 9 text

Exposedが提供する機能 Exposedには2つのAPIがあります。 ・DSL(Domain Specific Language) SQLに近い書き方ができるタイプセーフなAPI。今日はこちらについて紹介しま す。 ・DAO (Data Access Object) 一般的なORMフレームワークと同等の機能を持つ軽量API。

Slide 10

Slide 10 text

ExposedのDSL APIとは テーブルに対応するオブジェクトと、そのオブジェクトからクエリを実行するタイ プセーフなAPIです。 クエリではSQLのうち以下の機能をAPIで記述できます。 次のページでそれぞれのサンプルを簡単に紹介します。 Basic CRUD ,Where expression, Conditional where, Count, Order-by ,Group-by, Limit, Join, Union, Alias, Schema, Sequence, Batch Insert, Insert From Select

Slide 11

Slide 11 text

テーブルオブジェクト // 製造者(maker)テーブルの外部参照をもつ製品(product)テーブル object Products : IntIdTable("product", "product_id") { val name = varchar("name", 128) // 商品名(予約語はEscape) val makerId = reference("maker_id", Makers) // 外部参照 val price = decimal("price", 20, 5) // 価格 val summary = varchar("summary", 1024).nullable() // Null許容列 val registerDate = date("register_date") // 登録日 val createdAt = timestamp("created_at") } // ほとんどDDL!

Slide 12

Slide 12 text

テーブルオブジェクトから生成するクエリ (メーカーごとの価格の合計を抽出する) Products .join(Makers, JoinType.INNER) .slice(Makers.id, Makers.name, Products.price.sum()) .select {Products.registerDate greaterEq LocalDate.now().minusWeeks(1L)} .groupBy { Makers.id } .map { // 結果を設定する } // 赤字の部分がDSLでクエリを組み立てている部分です

Slide 13

Slide 13 text

● insertAndGetIdでInsert時にシーケンスで採番されたPKを戻り値で取 れるのが使いやすい ● insertIgnoreにすることでキー重複が発生した場合に例外が発生しなく なるのがいい ● JOINがサクサク書ける。いわゆるフラグ類を列ではなくFKだけ持つテー ブルのレコードの有無で表現したが、クエリで表現するときに全然苦にな らない。 個人的に好きなところ

Slide 14

Slide 14 text

Exposedを採用した経緯

Slide 15

Slide 15 text

開発したシステムの説明 サーバーサイドの言語をKotlinにしたいという思いがありました。 理由はKotlinのMultiplatformやWASMがよさそうに見えてて、Kotlinにして おけばフロントとメンバーを兼任できるかもと考えたためです。 構築するシステムはフロントエンド(スマホアプリ)向けのAPI、バッチ処理、運 用管理向けアプリというよくある構成です。 それぞれは別のプロセスですが、データベースは共有しています。

Slide 16

Slide 16 text

ざっくりシステム構成

Slide 17

Slide 17 text

各サービスについて軽く説明 ● バッチはDB内のデータの集計と、外部から受領したデータの登録を行い ます。登録したデータは主にAPIが参照します。 ● APIはユーザーによるデータの参照、更新を行います。リソースのIDを指 定した処理がほとんどです。 ● 管理アプリは登録されているデータを横断的に取得します。 それぞれのサービスごとに開発チームが分かれています。 また、データベースは共有していますが、プロセス間で直接通信することはほ ぼありません。

Slide 18

Slide 18 text

フレームワークについて 言語はkotlinですが、フレームワークは統一していません。 モバイルアプリ向けのAPIはKtor、バッチと管理アプリはSpring Boot (Kotlin)を利用することにしました。 そのため、KtorとSpringで共通に使えるSQLフレームワークを検討した結果、 Exposedを採用することにしました。 Ktorは起動が爆速(1秒くらい)なところがとても良い・・・。

Slide 19

Slide 19 text

Exposedをどのように導入したか ExposedのDSL(SQLに近い記述ができるAPI)で利用するテーブルObject (アクセスするテーブルの列、型を定義)を作成し、各プロセスで共有しました。 共有するのはテーブルObjectのみとし、クエリの共有は行わないことにしまし た。(それぞれのサービスで実装) クエリを共有しない理由はプロセスによって必要なデータが異なり、共有するメ リットがほとんどないことです。

Slide 20

Slide 20 text

テーブルに対応するオブジェクトのみ共有

Slide 21

Slide 21 text

なぜExposedを選択したのか? Kotlinで使えるSQLフレームワークは他にもあります。例えばJPAやMyBatis もKotlinで利用できます。 それらと比較した時、なぜExposedを選んだのか?

Slide 22

Slide 22 text

選定時に重視した点 SQLフレームワーク選定をするにあたり、以下の点を重視しました。 ● タイプセーフに書ける ● 実装時に選択肢が少ないもの(どちらかというとおまけ)

Slide 23

Slide 23 text

タイプセーフに書ける 前提としてSQLをできるだけ活用するという思いがありました。 例えば「ひと月の商品別の売上額」という集計が必要な場合、アプリの処理で はなくSQLで実装したい。 この時に必要となるsumやgroup byの構文を文字列で実装するのはつらい ので、流れるようなメソッドチェーンでクエリを実行したい。 また、結果も列がnullableであればアプリでもNull安全になるようにしたい。 ExposedのDSLはこの要求を満たしてくれます。

Slide 24

Slide 24 text

実装する選択肢が少ないものを選ぶ フレームワークで複数の実装手段があるのは避けたいと考えていました。 ExposedにはDSLとDAOの2つしかなく、SQLに近い実装をするのであれば DSLに絞れるのが良いです。

Slide 25

Slide 25 text

以前、そこそこ大規模なプロジェクトでSpring Data JPAを使っていた時、チー ムごとに実装がバラバラな問題が発生した。 個人的にはそれは仕方ないことだと思うが、「統一されていないことを許せな い」勢力が出てきて揉めたのが辛い・・・。 そして最終的にNativeQueryに全部書き直すということに・・・。 選択肢が少ないものがよかった理由補足

Slide 26

Slide 26 text

● SQL推しなんですけど文字列では書きたくない。IDEだとコード補完とかあ りますが・・・。 ● MyBatisとかであるSQLの内のパラメーターを置換する仕組みが好きじゃ ない。 ● なんだかんだでクエリ組み立てる時のIFは発生すると思っている。それを どこで吸収するかだが、やはりアプリの処理でやりたい。(MyBatisのifは 好きじゃない・・・) MyBatisの悪口っぽくなってる・・・。 選定理由補足

Slide 27

Slide 27 text

実際、導入してどうだったのか 選定理由を満たしているのは導入前からわかっていましたが、それ以上の効 果もありました。 DB共有の場合、とあるプロセスによるテーブルの修正が別サービスの障害と なるケースがあります。今回はDLLとテーブルオブジェクトを同一リポジトリで 管理し、常にDBとExposedが同期する状態を維持したことで、Exposedがイ ンターフェースの役割を果たしました。

Slide 28

Slide 28 text

インターフェース? 例えば、あるサービスがテーブルの列をNullableに変更します。テーブルオブ ジェクトも合わせて修正することで、同じ列を見ているサービスが変更を検知し やすくなりました。 テーブル共有には不安がありましたが、このおかげで大きな問題は着ることな く開発ができました。 // この列を利用している箇所でコンパイルエラーになり、変更に気付ける val name = varchar("name", 128).nullable()

Slide 29

Slide 29 text

DB共有という不安定になりがちな仕組みがExposedで安定した インターフェースと しての役割

Slide 30

Slide 30 text

● サービス間で更新の競合がなかった(というよりないように設計した) ○ 例えば同じキーのレコードを複数のサービスから更新する場合、更新 する列が一致しないのであればテーブルを分けた ○ Updateを極力排除し、Insert+更新イベントという設計にした ● 発表のスライドにあったnullable話は、バッチ取得する外部データ周りで 多く発生した ○ 外部IFが適当なところが多くて、実際アクセスしたらnullということが 何回か・・・ うまくいった理由補足

Slide 31

Slide 31 text

Springに導入して動かすまで

Slide 32

Slide 32 text

Exposedを動かす手順 1. Dependencyの追加 2. application.propertiesの修正 3. ExposedConfigクラスの作成 4. テーブルに対応するオブジェクトを作成 5. @Transactionalなメソッドでクエリ実行 4と5はExposedの説明時に記載したので省略します

Slide 33

Slide 33 text

Dependencyの追加(build.gradle.ktの場合) repositories { mavenCentral() } dependencies { // Exposedが提供しているexposed-spring-boot-starter implementation("org.jetbrains.exposed:exposed-spring-boot-starter:0.41.1") // 日付関連をJavaのDate-Time APIで扱う implementation("org.jetbrains.exposed:exposed-java-time:0.41.1") }

Slide 34

Slide 34 text

application.propertiesの修正 # データベースの接続 spring.datasource.url=jdbc:mysql://localhost:3306/pfm spring.datasource.username=ktoruser spring.datasource.password=ktorpass spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # DSLからDDLを生成して実行する(デフォルトはfalse) spring.exposed.generate-ddl = false # 実行されたSQLを出力する(デフォルトはfalse) spring.exposed.show-sql=true

Slide 35

Slide 35 text

ExposedConfigクラスの作成 // SpringBoot 3.XではGitHubに記載されている通りに書いでもダメ・・・ // ExposedAutoConfigurationを読み込んで、 // DataSourceTransactionManagerAutoConfigurationを無効にする @Import(value = {ExposedAutoConfiguration::class}) @EnableAutoConfiguration(exclude = {DataSourceTransactionManagerAutoConfiguration::class}) @Configuration public open class ExposedConfig

Slide 36

Slide 36 text

補足:logbackの設定

Slide 37

Slide 37 text

実行時例外 実行時にエラーが発生した場合、ExposedSQLExceptionがスローされま す。実際に発生したSQLExceptionは、ExposedSQLExceptionから取得で きます。 データソース、トランザクション管理はSpring側で行っているため、そこで発生 した例外はExposedを使わない場合と変わりません(例えばDataSourceの 設定や誤っている等々)

Slide 38

Slide 38 text

まとめ

Slide 39

Slide 39 text

Exposedを使ってみてどうだったか 小さなところで不満はあるものの、全体としては非常に良かった。SQLフレーム ワークで気になるところのパフォーマンスでも問題は発生していません。 DSLではSQLに近い構文で書け、それがそのままSQLになります。よくORMで 発生する「実行されたSQLが辛い」という問題も発生せず、開発効率もよかっ たです。(少なくとも発行されたSQLでトラブルはなし) N+1問題とかも自分でそう作らない限りは起きません。

Slide 40

Slide 40 text

導入してよかった(補足) ExposedのDSLは主にバッチでいい感じだった。ただ、そもそもKotlinでバッ チ書く需要がそもそもあるのだろうかという点は少し気になった。 あとはテストでDBのデータを取得してAssertするとき、DSLを気兼ねなく使え るのがよかった。気兼ねなくというのは、ORM使ってるとAssert用のデータ取 得するときにORM使ったコードを用いるかで大体悩む・・・。 導入してよかった(補足)

Slide 41

Slide 41 text

辛かったところ ほとんどないです。 強いてあげるなら0.39.1から0.41.1にバージョンアップした時に非互換があった ところです。 あとはそもそもSQLが苦手だとDSL APIはちょっと辛いです。 困った時は公式ドキュメントとStackOverFlow(先人が同じとこでハマってる)

Slide 42

Slide 42 text

日本での採用事例もあります Exposed kotlinで検索すると結構日本語の記事がヒットします。 ちなみに私がExposedと出会ったのは「KtorとNuxt.jsで作るWebアプリケー ション入門」という本でした。KotlinでAPI作ってみたい人におすすめです。

Slide 43

Slide 43 text

最後に 自分はKotlinに入門したばかりの初心者です(1年未満)。でもSpringと組み 合わせることで、あまりハマることなくKotlinかけてます。Springだと「いざと なったらJavaで」という保険にもなります。 ExposedもサーバーサイドKotlinも楽しいのでぜひ! 今ならAIパワーで入門も楽・・・だと思います。

Slide 44

Slide 44 text

おまけ

Slide 45

Slide 45 text

将来的なロードマップ ロードマップには以下の3つが記載されています。 ● R2DBC ● RepositoryパターンのDAO ● Kotlin Nativeの対応 これ以外にも各データベース固有の型の対応は随時行われています。

Slide 46

Slide 46 text

ChatGPTでコードは生成できますか 全然いけます! GitHubのサンプルプロジェクトでは、ChatGPTにテーブルとDSLを作っても らってます。ただ、GPTの生成コードのバージョンが古く、Importでエラーにな る部分があることと、最新のAPIに対応していない点があります。 とはいえ、複雑なクエリも結構生成してくれるので、試しに使ってみるのもいい かも。

Slide 47

Slide 47 text

興味があるのでJavaで使いたい! ごめんなさい、たぶん無理です! JavaでSQLチックに書きたい場合、Jooqというライブラリもあるのでこちらを試 してみるのもいいかもしれません。商用で使うと有償になりますが・・・。 ちなみにChatGPTは「できます!」と言ってサンプルコード出力しましたが動き ませんでした orz