考え方#
jimble には守っている決めごとがいくつかあります。 機能を足すかどうかは、たいていここで決まります。
1. 上から順に追えること#
コードを読む人が、main から目的の処理までを指でたどれることを最優先します。
そのために捨てたもの:
- 注釈でのルート定義(
@GET @Path("/x"))。 どのクラスが読まれるのかがコードに出ないため - DI コンテナ。
@Injectされた実装がどれになるかは、実行するまで分からないため - 注釈でのトランザクション(
@Transactional)。 どこで始まってどこで終わるかが、メソッドの外にあるため
かわりに new を書きます。install(AdminController::new) と書きます。
db.transaction(tx -> { ... }) と書きます。数行増えます。
2. 起動時にクラスパスを走査しない#
コントローラもバッチも MCP のツールも、自分で登録します。
tool("search_posts", SearchPostsTool::new);
tool("create_post", CreatePostTool::new);
resource("blog://latest", LatestPostsResource::new);
走査しないので、起動が速く、何が登録されたかが起動ログで完結し、 ネイティブイメージや jlink とも喧嘩しません。 登録し忘れは、起動ログのルート一覧で気づけます。
3. 黙って間違えないこと#
いちばん高くつくバグは、落ちないバグです。 jimble は「静かに効かなくなる」状態を潰すことに時間を使っています。
例:
env=localなのにcookie.secure = trueなら、起動時に WARN を出します。 そのままだとブラウザが Cookie を返さず、セッションも CSRF も Flash も エラーなしで効かなくなるためです- トランザクションを開いたまま実行が終わったら、ERROR を出してロールバックします。 黙って接続をプールへ返すと「入れたつもりが入っていない」が残ります
- ドキュメントのコード片は実コードから抜いています。 印の付いた場所が消えたら、サイトのビルドが落ちます
4. 失敗は例外にする#
**失敗は例外(非検査)です。**戻り値で失敗を返すことはしません。 戻り値は捨てられます。捨てた失敗は、黙って素通りします。
- 0件は失敗ではありません。
selectは空のOptional、selectListは空のリスト、updateは 0 を返します - DB の失敗は
SqlExecuteExceptionです。分岐したいのは一意制約くらいなので、それだけDuplicateKeyExceptionで受けます - 書かなければ上まで飛んで 500 になります。トランザクションの中なら巻き戻ります
try (DB db = BlogExample.db()) {
/*
* 0件は空(空リスト・空の Optional・件数 0)、失敗は SqlExecuteException(2.0)。
* 書かなければ上まで飛んで 500。トランザクションの中なら巻き戻る。
*/
List<Data> rows = db.selectList(SQL.select().from(Post.instance()));
// 分岐したい失敗は一意制約くらい。それだけを受け止める
try {
db.insert(SQL.insert(Post.instance()).value(Post.title, "hello"));
} catch (DuplicateKeyException ex) {
Log.info("もうあります");
}
}
使い方を間違えたときも同じです。壊れ方はコンパイルエラーか例外だけで、黙って違うことはしません (2.0 で作り直したものは 2.0 への移行 にあります)。
5. 一つの実行 = 一つの Context#
Web のリクエスト・バッチの実行・MQ の1件・WebSocket の1メッセージ、
それぞれに Context が1つ作られます。
DB 接続もセッションもここにぶら下がり、終わりで畳まれます。
Context は ScopedValue で渡します。ThreadLocal ではありません。
仮想スレッドの上では ScopedValue のほうが安く、
かつ書き換えられないので、途中ですり替わることがありません。