Domaの開発で大切にしている10のこと

## 自己紹介
- 中村
- Domaの開発者
    - 開発歴 約16年
- X: [nakamura_to](https://x.com/nakamura_to)
- GitHub: [nakamura-to](https://github.com/nakamura-to)

---
## Domaとは？
- [JavaのDBアクセスフレームワーク](https://github.com/domaframework/doma)
- 注釈処理（JSR 269）でコード生成 & コンパイル時検証
- 実行可能なSQLテンプレート

---
## Domaの歴史
- 2009/05: 開発開始
- 2009/02: v1.0.0リリース (Java 6対応)
- 2014/07: v2.0.0リリース (Java 8対応)
- 2024/09: v3.0.0リリース（Java 17対応）
- 2025/03: v3.6.0リリース（最新版）

---
## 1. 動かさないとわからないを減らす
- <font color="red">コンパイル時</font>にできるだけチェック

---
- Javaコードに対して
    - 例：アノテーションの存在チェック

<img width="747" alt="apt_error_1.png" src="https://qiita-image-store.s3.amazonaws.com/0/41471/4d7b0542-f955-c541-cb2b-4ed2480084d6.png">

---
- SQLファイルに対して
    - 例：パラメータの存在チェック

<img width="753" alt="apt_error_2.png" src="https://qiita-image-store.s3.amazonaws.com/0/41471/60ea989c-25ce-1baf-6d36-1de6665f5b24.png">

```sql
select * from EMP where ID = /*identity*/100
```

---
- SQL内の式コメントに対して
    - 例: 型チェック

```sql
select * from EMP where ID = /*id.hoge*/100
```

- コンパイル時に `id` が `Integer` 型だとわかる
    - `hoge` への参照はコンパイルエラー

---
## 2. コードの信頼性を高める
- SQLテンプレートでSQLインジェクションを防ぐ

```sql
select * from emp whrere name = /* name */'taro'
```

- PreparedStatementへ変換して実行するので安全

```sql
select * from emp whrere name = ?
```

---
- リソース（Connectionとか）を自動で解放する

```java
// トランザクション内で検索と更新をするコード
tm.required(() -> {
    Employee employee = dao.selectById(1);
    employee.setName("hoge");
    employee.setJobType(JobType.PRESIDENT);
    dao.update(employee);
});
```

---
## 3. シンプルさを重視する
- 例えばCriteria APIは提供しない
- Criteria APIはシンプルさを損なう
    - [JPAのCriteria API](http://docs.oracle.com/javaee/6/tutorial/doc/gjrij.html)

```
EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
cq.select(pet);
TypedQuery<Pet> q = em.createQuery(cq);
List<Pet> allPets = q.getResultList();
```

---
- 利点
    - 型安全で組み立てられる
        - 文字列のタイポが発生しない
- 欠点
    - 学習コストが高い
    - いつCriteria APIを使うべきかで迷う
        - 迷う ≒ シンプルさの欠如

---
## 4. SQLの発行場所とタイミングを明確にする
- SQLの発行はDAOインタフェースの呼び出しで
    - 遅延ロードはサポートしない

```java
// ここでSQLを発行するならOK
Dept dept = dao.selectById(1);

// ここでSQLを発行するのはNG！
List<Employee> employees = dept.getEmployees(); 
```

---
- キャッシュはしない
    - いつも必ずSQLを発行
    - 必要ならアプリレイヤで

```java
// SQL発行
Dept dept = dao.selectById(1);
// SQL発行
Dept dept = dao.selectById(1);
```

---
## 5. SQLの完全性を重視する
- SQLの断片化を促進させる機能は持たない

```sql
-- こんな機能はありません！
select /*%include common_select_list.sql*/ 
from employee
where /*%include common_search_condition.sql*/
```

- 過剰な共通化は見通しを悪くする

---
## 6. JDBCでできることを制限しない
- 例えばストアドプロシージャーの呼び出し

```java
@Procedure
void execute(@In Integer id, @InOut Reference<BigDecimal> salary);
```

---
- 結果セットを複数個返すようなストアドプロシージャー

```java
@Procedure
void execute(
  @ResultSet List<Employee> employees,
  @ResultSet List<Department> departments,
  @In Integer employee_id,
  @In Integer department_id);
```

---
## 7. 逃げ道を用意する
- いざという時にJDBCを自由に使えるのが重要
    - かつ呼び出し元に影響を与えないものポイント

```java
@Dao
public interface EmpDao {
  default Emp select() {
    Config config = Config.get(this);
    DataSource ds = config.getDataSource();
    ... // 自由にJDBCの機能が使える
  }
}
```

---
- Domaを捨てる場合にも使える
    - 他のDBアクセスライブラリへの移行する場合
- Javaのインタフェースを使っていることの利点

---
## 8. 様々な環境で長期的に使えるようにする
- JDK/JRE以外には依存しない
    - 依存ライブラリがJavaのバージョンに追随しないリスクを避ける
    - 依存ライブラリが他のライブラリと競合するリスクを避ける

---
- 実行環境に依存しがちなところはインタフェースで実装を差し替えられるように
    - 例えば、クラスローディング周り
        - Play Framework v1
        - Spring Boot DevTools

---
- バイトコードをハックしない
    - 同じ注釈処理をするライブラリでもLombokとは違うところ
    - Domaは将来のJavaでもほぼそのまま動作するはず

---
## 9. 変化（トレンド）に追随する
- Java 8対応
    - Stream
    - Optional
    - JSR310 Date & Time API

---
- Kotlin対応（実験的）

```kotlin
@Entity(immutable = true)
data class Person(
  @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
  val id: Int? = null,
  val name: String,
  val age: Int,
  @Version
  val version: Int = -1)
```

---
## 10. 利用者の声を聞く
- 自分が考えた機能は2010年時点でほぼ実装完了
    - それ以降の機能追加は利用者の意見が中心

<img width="1132" alt="http://d.hatena.ne.jp/taedium/20101002/p2" src="https://qiita-image-store.s3.amazonaws.com/0/41471/1466d385-5ac0-e007-f100-bbe9175f2af5.png">

---
- 利用者の声は宝の山
    - そんな使い方があったとは！という良い意味での発見が多い
    - 7年も続けられている理由の1つ

---
### Twitterのつぶやき
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">DomaのEntity annotation出指定する引数、親クラスで指定しても効かず、エンティティの子クラスで改めて指定する必要あるんだね・・・。親クラスだけNamingType必要で、子クラスのすべてのカラム名は名前を明示的に指定するからいらないと思ったけどダメだったお</p>&mdash; Ktz (@ktz_alias) <a href="https://twitter.com/ktz_alias/status/374756352448425984">2013年9月3日</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

---
### Twitterでメンション
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/nakamura_to">@nakamura_to</a> すいません質問させてください。DomaのDomainオブジェクト定義で、全部のDomainにequalsやcompareToなどの委譲メソッドを定義するのは辛いので、<a href="https://t.co/poBhLF89">https://t.co/poBhLF89</a> こんな風に抽象クラスを作ったのですが(続く)</p>&mdash; がくぞ (@gakuzzzz) <a href="https://twitter.com/gakuzzzz/status/278432513330909185">2012年12月11日</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

---
### Gitterへの書き込み
<img width="734" alt="https://gitter.im/domaframework/doma?at=56dd0db91263673835717662" src="https://qiita-image-store.s3.amazonaws.com/0/41471/00ae143b-e083-c38b-885e-69ef0a6aec2b.png">

---
### もちろんPull Requestも
<img alt="https://github.com/domaframework/doma/pull/71" src="https://qiita-image-store.s3.amazonaws.com/0/41471/9fb4a017-d5b1-a93d-805d-48b2dbea1c39.png">

---
### 疑問や要望はいつでもWelcome

- できるだけ応えたい
- 面白いアイデアに発展するかも？

---

### ぜひ使ってみてください

---
## Domaの開発で大切にしている10のこと
1. 動かさないとわからないを減らす
2. コードの信頼性を高める
3. シンプルさを重視する
4. SQLの発行場所とタイミングを明確にする
5. SQLの完全性を重視する
6. JDBCでできることを制限しない
7. 逃げ道を用意する
8. 様々な環境で長期的に使えるようにする
9. 変化（トレンド）に追随する
10. 利用者の声を聞く

---
## 追記（2020年5月30日）

このスライドの中で「例えばCriteria APIは提供しない」と書きましたが、2020年5月、Doma 2に[Criteria API](https://doma.readthedocs.io/en/latest/criteria-api/)を導入することにしました（2.35.0で正式リリース）。

ひと言で言えば当時と状況が変わったからということになりますが、細かい理由は次のようなものです。

---
### Criteria API導入の理由 1
- SQLファイル内の大部分が条件分岐だらけになっているようなプロジェクトを度々見かけるが、非効率かつ保守性に難がある。そのようなケースではJavaでSQLを組み立てた方が適切である。

---
### Criteria API導入の理由 2

- Javaの複数のORMを併用する事例を見かけるが、個人的にはオーバーヘッドだと思うのでできるだけ1つのORMで済むようにしたい（Domaが機能不足であれば補いたい）

---
### Criteria API導入の理由 3

- GraalVMを使ってNative Imageにするためにはリフレクションに制約がかかるケースがある。SQLファイル内の式コメントはリフレクションに頼っているが、リフレクションを使わないで動的SQLを組み立てる手段を提供したい。

---
### Criteria API導入の理由 4

- コンパイル時（Annotation Processingの最中）にファイルを読むのは環境（ビルドツールやIDE）依存の部分がありハマりポイントになっている。できればこのハマりポイントを回避したい。Criteria APIが主流になればファイルを読むのは実行時だけにしてコンパイル時に読むのは止めてもいい（オプションにするなど）と思っている。

---
### Criteria API導入の理由 5

- Doma 1を作り始めた当時はJava 6がリリースされたばかりであり言語の制約上使いやすいCriteria APIを作ることができなかった。しかしJava 8で導入されたラムダ式を使えば使いやすいAPIを構築可能に思われた。

---
### Criteria API導入の理由 6

- Domaが関連をサポートしていないので使えないとか古いなどと言われるのがちょっと癪なので :smile: 

---
## 追記（2025年4月12日）

Doma 3.1.0 で[新しい Criteria API](https://doma.readthedocs.io/ja/latest/query-dsl/) を導入。

---
## 参考情報

- [Doma入門](https://qiita.com/nakamura-to/items/deaff05ba530317f3cf8)