Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JJUG_CCC_2023_Exposed.pdf

H.Naka
June 04, 2023

 JJUG_CCC_2023_Exposed.pdf

JJUG CCC 2023 Spring
コンファレンスC 10:25 - 10:45

で使用したスライドです。
(仮)は永遠に(仮)です
白いページは時間の都合で入りきれず、喋らなかった補足の部分です

H.Naka

June 04, 2023
Tweet

More Decks by H.Naka

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. 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

    View Slide

  11. テーブルオブジェクト
    // 製造者(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!

    View Slide

  12. テーブルオブジェクトから生成するクエリ
    (メーカーごとの価格の合計を抽出する)
    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でクエリを組み立てている部分です

    View Slide

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

    View Slide

  14. Exposedを採用した経緯

    View Slide

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

    View Slide

  16. ざっくりシステム構成

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. Springに導入して動かすまで

    View Slide

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

    View Slide

  33. 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")
    }

    View Slide

  34. 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

    View Slide

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

    View Slide

  36. 補足:logbackの設定




    View Slide

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

    View Slide

  38. まとめ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  44. おまけ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide