LDBC

LDBCは1.0以前のソフトウェアであり、現在も活発に開発中であることに注意してください。新しいバージョンは以前のバージョンとバイナリ互換性がなくなってしまう可能性があります。

はじめに

私たちのアプリケーション開発では大抵の場合データベースを使用します。
Scalaでデータベースアクセスを行う場合JDBCを使用する方法がありますが、ScalaにはこのJDBCをラップしたライブラリがいくつか存在しています。

  • 関数型DSL (Slick, quill, zio-sql)
  • SQL文字列インターポレーター (Anorm, doobie)

LDBCも同じくJDBCをラップしたライブラリであり、LDBCはそれぞれの側面を組み合わせたScala 3ライブラリで、型安全でリファクタブルなSQLインターフェイスを提供し、MySQLのデータベース上でのSQL式を表現できます。

また、LDBCのコンセプトは、LDBCを使用することで単一リソースを管理することでScalaのモデルやsqlのスキーマ、ドキュメントを一元化できる開発を行えることです。

このコンセプトは宣言的でタイプセーフなWebエンドポイントライブラリであるtapirから影響を受けました。
tapirを使用することで、型安全なエンドポイントを構築することができ、構築したエンドポイントからOpenAPIドキュメントを生成することもできます。

LDBCはデータベース層でScalaを使用して、同じように型安全な構築を可能にし、構築されたものを使用してドキュメントの生成を行えるようにします。

なぜLDBCなのか?

データベースを利用したアプリケーション開発では、様々な変更を継続的に行う必要があります。

例えば、データベースに構築されたテーブルのどの情報をアプリケーションで扱うべきか、データ検索にはどのようなクエリが最適か、などである。

テーブル定義にカラムを1つ追加するだけでも、SQLファイルの修正、対応するモデルへのプロパティの追加、データベースへの反映、ドキュメントの更新などが必要になります。

他にも考慮すべきこと、修正すべきことなどたくさんあります。

日々の開発の中で全てをメンテナンスし続けるのはとても大変なことであり、メンテナンス漏れだって起こるかもしれません。

テーブル情報をアプリケーション・モデルにマッピングすることなく、プレーンなSQLでデータを取得し、データを取得する際には指定された型で取得するというアプローチは非常に良い方法だと思います。

この方法であれば、データベース固有のモデルを構築する必要がなく、開発者はデータを取得したいときに、取得したい種類のデータを使って自由にデータを扱うことができるからです。
また、プレーンなクエリを扱うことで、どのようなクエリが実行されるかを瞬時に把握できる点も非常に優れていると思います。

しかし、この方法ではテーブル情報のアプケーションでの管理がなくなっただけでドキュメントの更新などを解消することはできません。

LDBCは、これらの問題のいくつかを解決するために開発されています。

  • 型安全性:コンパイル時の保証、開発時の補完、読み取り時の情報
  • 宣言型:テーブル定義の形(“What”)とデータベース接続(“How”)を分離する。
  • SchemaSPYの統合:テーブル記述からドキュメントを生成する
  • フレームワークではなくライブラリ: あなたのスタックに統合できる

LDBCを使用するとデータベースの情報をアプリケーションで管理しなければいけませんが、型安全性とクエリの構築、ドキュメントの管理を一元化することができます。

LDBCでのモデルをテーブル定義にマッピングするのはとても簡単です。

モデルが持つプロパティと、そのカラムのために定義されるデータ型の間のマッピングも非常にシンプルです。開発者は、モデルが持つプロパティと同じ順序で、対応するカラムを定義するだけです。

import ldbc.core.*

case class User(
  id: Long,
  name: String,
  age: Option[Int],
)

val table = Table[User]("user")(
  column("id", BIGINT, AUTO_INCREMENT, PRIMARY_KEY),
  column("name", VARCHAR(255)),
  column("age", INT.UNSIGNED.DEFAULT(None)),
)

また、間違った型を組み合わせようとするとコンパイルエラーになります。

例えば、Userが持つString型のnameプロパティに関連するカラムにINT型のカラムを渡すとエラーになります。

[error] -- [E007] Type Mismatch Error:
[error] 169 |    column("name", INT),
[error]     |                   ^^^
[error]     |Found:    ldbc.core.DataType.Integer[T]
[error]     |Required: ldbc.core.DataType[String]
[error]     |
[error]     |where:    T is a type variable with constraint <: Int | Long | Option[Int | Long]

これらのアドオンの詳細については、テーブル定義 を参照してください。

クイックスタート

現在のバージョンは Scala 3.3.3 に対応した 0.3.0-alpha9 です。

libraryDependencies ++= Seq(

  // まずはこの1つから
  "io.github.takapi327" %% "ldbc-core" % "0.3.0-alpha9",

  // そして、必要に応じてこれらを加える
  "io.github.takapi327" %% "ldbc-dsl"           % "0.3.0-alpha9", // プレーンクエリー データベース接続
  "io.github.takapi327" %% "ldbc-query-builder" % "0.3.0-alpha9", // 型安全なクエリ構築
  "io.github.takapi327" %% "ldbc-schemaspy"     % "0.3.0-alpha9", // SchemaSPYドキュメント生成
)

sbtプラグインの使い方については、こちらのdocumentationを参照してください。

TODO

  • JSONデータタイプのサポート
  • SETデータタイプのサポート
  • Geometryデータタイプのサポート
  • CHECK制約のサポート
  • MySQL以外のデータベースサポート
  • ストリーミングのサポート
  • ZIOモジュールのサポート
  • 他データベースライブラリとの統合
  • テストキット
  • etc…
The source code for this page can be found here.