🐫

[Kotlin] ExposedとKomapperの違い

2022/09/04に公開

はじめに

先日Server-Side Kotlin Meetup vol.5にてExposedに関する2つのセッションがありました。

私が作っているKomapperとの違いを改めて考える良い機会となったので感じたことを少しまとめてみたいと思います。特にセッションで触れらた話題について言及してみます。

なお、この記事はExposedに対してネガティブなコメントをする意図は全くありません。あくまで違いを述べることを目的としています。

アーキテクチャの方向性

Exposedのアーキテクチャ上の特性を方向づける機能として次のものが挙げられます。

  • caching
  • lazy loading
  • write behind

これらはJPAなど他のORMでも提供されているものでExposed特有の機能というわけではありませんが、Exposedがこういった機能を持っていることはORMを選定する上で1つのポイントなると思います。
というのも、これらの機能はパフォーマンス上の利点やコードの見通しの良さを提供する一方で複雑性も持ち込んでしまうからです。

例えば、Spring Data JDBCはこれらの機能を提供しない選択をしています

Spring Data JDBCに倣ったというわけではないですがKomapperもこれらの機能は提供しません。過去を振り返ると私も開発や保守に関わったS2Dao、S2JDBC、Domaなどでもこれらの機能は提供されていませんでした。

これらの機能は開発者の直感に反する部分があり、少し学習コストが高いのではと感じています。特にキャッシュの存在を意識したコードを書かなければいけないなどは嵌りポイントの1つだと思います。

ドキュメント

Wikiに簡単な説明はあるもののExposedがドキュメントを提供していないのは私も気になっていました。自分はExposedの利用者ではないですが興味のあるプロダクトなのでぜひ読んでみたいところです。今後に期待します。

Komapperは英語と日本語でドキュメントを提供しています。全ての機能を網羅しているわけではないですがざっくり8割くらいはカバーしているのではと思っています。情報が足りなければ拡充も考えようと思っていますが、今のところはまだそういった要望をいただいたことはありません。

型安全

セッションではExposedのエンティティのプロパティの型をValue Objectで表現するための方法が紹介されていました。

Komapperでもエンティティのプロパティの型をValue ObjectやDomain Primitiveで表現するための方法をいくつか提供しています。お手軽な順に書くと次の方法があります。

拡張性

拡張性はExposedの方が高そうです。セッションではRDBMS固有の演算子や関数に対応するSQLを発行する方法が紹介されていました。同様のことはKomapperではサポートしていません。ニーズがあれば検討してもいいのですが、大体の演算子や関数はRDBMS(SQL)でやるよりもアプリケーション(Kotlin)でやった方が生産性が高いと考えているため今のところこの部分の拡張性に力を入れていません。

また、これはどのライブラリについても言えると思いますが、ライブラリ利用者からすると拡張性は高い方が望ましい一方でライブラリ開発者からすると過度に高い拡張性は将来的な保守性(機能追加容易性)を妨げる側面があるのでどこまで拡張性を広げるかは悩ましいところです。Komapperの場合、Exposedに比べれば拡張性は限定的と言えると思いますが逆に言えば将来的に安定的にメンテナンスできる余地が多く残っています。

セッションの中では、これまで数年Exposedを利用しているがバージョンアップによってある機能が動かなくなったことがない、数年来JetBrain内で利用されている、といった説明も合ったのでExposedは拡張性と保守性をうまく両立できていると言えるかもしれません。

トランザクション

Exposedはトランザクションに関する機能が豊富なようでした。セッションではトランザクションに紐づけた処理を行えることが紹介されました。Exposedはcachingやwrite behindを実現するためにトランザクションに連動した処理を行えるようにする必要性があり、副次的にAPIとしてアプリケーションに公開されているのではと想像します。

Komapperはトランザクション管理機能を持ちますが拡張関数などを使ってトランザクションの開始と終了を明示的にフックすることで大体のことは実現できるだろうと考えて特に便利なAPIを提供していません。また、SpringFrameworkと合わせて使うことを主要なユースケースとして考えているため、複雑なことをしたい場合はSpringFrameworkのトランザクション管理機能を使うのが良いのではと思っています。

セッションの中で触れられていたJUnitとの連携ですがKomapperでもJUnitのテスト開始前にトランザクションをbeginしテスト終了後にロールバックすることは比較的簡単に実現できます(SpringFrameworkを使わなくても)。

https://github.com/komapper/komapper/blob/v1.3.0/integration-test-jdbc/src/main/kotlin/integration/jdbc/JdbcEnv.kt#L47-L61

上記のようにJUnitのライフサイクルをフックすれば通常のテストケースをトランザクション内で実行できます。

https://github.com/komapper/komapper/blob/v1.3.0/integration-test-jdbc/src/test/kotlin/integration/jdbc/JdbcInsertSingleTest.kt#L52-L63

リンクはKomapperの結合テストのコードからの抜粋です。結合テストではTestcontainersを使って複数データベースのテストを実行していますがテストごとにコンテナを起動するのは時間がかかるので1度コンテナを起動した後はテストごとにトランザクションをロールバックすることでテスト間の影響が生じないようにしています。

開発リソース

Exposedは開発リソースが潤沢でない(今増やしている最中?)といった話がありました。GitHubの活動状況が比較的ゆっくりであることやロードマップがあまり進まないことを見てなんとなく感じていましたが裏付けを得られました。企業がバックにいてその企業内で使われているようなプロダクトであってもそういう状況なのは少し意外でありつつ、よくあることかもしれないとも思いました。

開発リソースが潤沢でないのは個人プロジェクトであるKomapperも同様です。ただ、ロードマップ的に何か大きなマイルストーンやタスクが残ったままになっているわけではないのは違う点かもしれません。ちなみに一緒に開発してくれる方や議論してくれる方はいつでも歓迎です。もし興味があれば声をかけてください(もちろん利用してくれるだけでも嬉しいです)。

おわりに

ExposedとKomapperの違いについて幾つかの観点で意見を述べてみました。エンティティの定義の仕方、関連の扱い、楽観的ロック、R2DBC対応などまだ面白い比較ポイントはありそうな気がしますが、個人的には今回述べたものだけでも色々と違いがあって興味深く感じています。Exposedのコードを読んでみてもKomapperとは全く違う発想で書かれている気がしています。

Komapperを作っている身としては当然ながらKomapperを使って欲しいですが、いろんな思想を持ったORMがあった方が刺激を得られるのでExposedももっと利用が広がるといいなと思っています(Kotlinで使えるJVM系のORMという括りだとKomapperより遥かに知名度のあるExposedでさえまだ少数派だと思います)。

今回はExposedとKomapperの違いについて簡単に述べてみましたが、さらに深掘りしたり、いずれはKtormと比較したりなどもやってみたいですね。

Discussion