Repository:PostgreSQL RPCとの境界
このPayment ServiceのRepository層はJPAでテーブルを操作しません。既存PostgREST RPCを呼ぶ薄いGatewayとして、JavaとPostgreSQLの責任境界を維持します。
Java / Spring Bootを入口に、金融システムのService・Repository・Transaction設計を読む。
先に学ぶ内容 — Prerequisite: ソフトウェア工学 / 銀行システム学ぶこと — LEARNING GOALS
このページで学ぶこと
- RepositoryとGatewayの考え方
- Spring RestClientによるRPC呼び出し
- SQLSTATEを安全なAPIエラーへ変換する方法
体験37 · JAVA REPOSITORY(DBとの境界)
Payment ServiceはJPAやJDBCでテーブルを直接読み書きしているか確かめよう
まずは予想をひと言。本文を読んだ後、教材内の参照元で実際のGatewayとエラー変換を確認します。
- 01まず予想する — PREDICT
まずここだけ:思ったことをひと言書く
Payment ServiceはJPAやJDBCで直接テーブルを読み書きしていると思いますか?
正解でなくて大丈夫です。「〜だと思う」から始めてみましょう。
ヒント1を見る
既存の振込RPCは、どこにありましたか?
ヒント2を見る
JavaからDBへ直接接続する経路と、既存RPCを呼ぶ経路の違いは何でしょうか?
もっと詳しく:この問いで使う専門用語
- Gatewayは、外部システムとの通信を内部モデルから隠す境界です。
- PostgREST RPCは、HTTPからPostgreSQL関数を呼ぶ入口です。
- SQLSTATEは、DBエラーを機械的に識別するcodeです。
日常語で理解する — PLAIN LANGUAGE
まず、ここだけつかむ
Payment ServiceのRepository層はテーブルを直接保存せず、既存PostgreSQL RPCをPostgREST経由で呼びます。
呼出者のBearer tokenをそのまま転送し、既存のauth.uid()、RLS、関数権限を維持します。
DBのSQLSTATEを安定したAPI codeと安全なメッセージへ変換し、未知の内部情報は外へ漏らしません。
順番に理解する — DIFFICULT PARTS, STEP BY STEP
難しいポイントを、順番にほどく
専門用語を覚えることより、「なぜ必要か」「このシステムのどこで使うか」を理解することが目標です。
Gateway
外部システムとの通信を内部モデルから隠す境界です。
URLやJSON形式をServiceへ漏らさないためです。
PaymentRpcGatewayがRPC引数と結果を変換します。
PostgREST RPC
HTTPからPostgreSQL関数を呼ぶ入口です。
既存のDB関数を正式な業務境界として再利用するためです。
/rpc/request_transferなどへPOSTします。
SQLSTATE
DBエラーを機械的に識別するcodeです。
人向け文言に依存せず、安全な公開エラーへ変換するためです。
TR010をINSUFFICIENT_FUNDSへ変換します。
発展 — ADVANCEDもっと詳しく確認する最初の学習では読まなくても問題ありません。仕組みをもっと深く理解したい場合に開いてください。
なぜJPAやJDBCを使わないのか
既存Minibankでは、振込の正式な入口がPostgreSQLのrequest_transfer()とconfirm_transfer_otp()です。Javaからテーブルを直接更新すると、RLS、関数権限、ロック順序、台帳記帳、監査を迂回する危険があります。
PaymentRpcGatewayは既存RPCへ必要な引数を渡し、返却行をドメインモデルへ変換する役割だけを持ちます。
RPC呼び出しの流れ
- 01Gatewayがp_*形式の引数Mapを作る
- 02SupabaseRpcClientがPOST /rpc/{fn}を呼ぶ
- 03publishable keyと呼出者のAuthorizationを送る
- 04PostgRESTがtokenを解釈しPostgreSQL関数を実行
- 05JSON配列をMapへ読み取る
- 06GatewayがPendingPaymentまたはPaymentOtpConfirmationへ変換
Bearer tokenを引き継いで認可文脈を保つ
Javaサービス自身の強い権限でRPCを呼ぶのではなく、利用者のBearer tokenを転送します。そのためauth.uid()と既存RLS・RPCの認可判断を引き続き使えます。
これはtokenを信頼するという意味ではありません。有効性はPostgREST側で検証され、Javaは形式と伝搬を担当します。
restClient.post()
.uri("/rpc/{fn}", functionName)
.header(HttpHeaders.AUTHORIZATION, bearerToken)
.body(params)
.retrieve();DBエラーを公開契約へ翻訳する
PostgreSQL関数はTR003やTR010などのSQLSTATEで失敗理由を表します。RpcErrorMapperがそれをApiErrorCodeと安全な日本語メッセージへ変換します。
未知のSQLSTATEやJSONとして読めない応答はINTERNAL_ERRORへ閉じます。内部メッセージをそのまま返さないことが、情報漏えいと不安定なAPI契約を防ぎます。
| SQLSTATE | API code | 意味 |
|---|---|---|
| TR003 | KYC_NOT_VERIFIED | 本人確認未完了 |
| TR009 | IDEMPOTENCY_KEY_CONFLICT | 同じキーで異なる依頼 |
| TR010 | INSUFFICIENT_FUNDS | 残高不足 |
| 未知 | INTERNAL_ERROR | 内部詳細を公開しない |
体験学習の続き · LEARNING LOOP
ここまで読めましたか?続けて確認してみましょう
GatewayとSQLSTATEの考え方を読んだところで、教材内の参照元で実際のGatewayとエラー変換を確認し、自分の言葉で説明してみます。
- 02仕組みを確かめる — CONCEPT CHECK
画面操作なしで、仕組みを確かめる
この仕組みは、Mini Bankの画面には現れません。次のEvidenceとCode Trail、教材本文だけで理解を進めます。
- 03証拠を確認する — EVIDENCE
何を見たか、1つずつ確かめる
Evidence(判断の根拠となる記録)を確認したらチェックします。各項目の「何を見るか」を手がかりにしてください。
画面を開けない項目は、Source Trail(教材の参照元)と教材の説明で確認できます。失敗を実際に起こす必要はありません。
- 04コードの処理経路を追う — CODE TRAIL
コードを書く必要はありません
ここでは「説明されている仕組みが、実際のMini Bankのどこに実装されているか」を確認します。まずは日本語の役割説明を読み、入力した依頼がどこへ渡るかをつかみましょう。コードの全文を読む必要はありません。
まずGatewayが既存RPCをどう呼ぶかを見て、次にDBエラーがどう安全なAPIエラーへ変換されるかを見ます。
教材の参照元を確認する — Source Trail ↓RPC引数とドメインモデルの変換。
services/payment-service/src/main/java/com/minibank/payment/repository/PaymentRpcGateway.javaSQLSTATEとAPIエラーの対応。
services/payment-service/src/main/java/com/minibank/payment/error/RpcErrorMapper.java - 05自分の言葉で説明する — EXPLAIN
LEVEL 1 — やさしい説明(まずここだけ)
なぜPayment ServiceはJPAでテーブルを直接操作せず、既存のPostgreSQL RPCを呼び出す設計にしているのでしょうか? 短い2文を目安に説明してください。
日常語だけで完了できます。専門用語の数や正誤を自動採点するものではありません。
ヒント1を見る
「Gateway」という言葉を使って書いてみましょう。
ヒント2を見る
既存RPCを迂回すると何を失うか考えてみましょう。
もっと詳しく:LEVEL 2 — 専門用語を使った説明(任意)
Gateway、PostgREST RPC、SQLSTATEの3つを使って、RepositoryとDBの境界を説明してみましょう。
挑戦する場合は、上の説明欄に追記できます。追加しなくても完了できます。
説明の確認ポイント — Checkpoint- 既存RPCの認可・lock・記帳経路を迂回していないと説明できる
- 内部DBの詳細が外部契約へ漏れないと説明できる
理解を確かめる — CHECK YOUR UNDERSTANDING
3問で理解チェック
答えを頭の中で考えてから、各項目を開いて確認してください。
Q1なぜJPAでtransfersへ直接INSERTしないのですか?
既存RPCの認可、lock、transaction、Ledger、監査を迂回する可能性があるためです。
Q2Javaはservice roleでRPCを呼びますか?
いいえ。publishable keyと呼出者のBearer tokenを使います。
Q3未知のSQLSTATEをそのまま利用者へ返しますか?
返しません。INTERNAL_ERRORへ閉じ、生のDBメッセージを隠します。
完全に暗記する必要はありません。大切な考え方を説明できそうなら押してください。
教材の参照元 — SOURCE TRAIL
根拠と関連教材
CURRENT IMPLEMENTATIONは確認したrepository内の実装です。HISTORICAL IMPLEMENTATIONは導入・修正の履歴で、後続migrationによる再定義があり得ます。RELATED LESSONは教材への案内で、実装の一次Sourceではありません。ASSUMPTIONは仮定、UNKNOWNは未確認を表します。
確認基点: d02eae8914f39cbf06bfe422dc39c71886f82b3e / 2026-09-11。稼働環境への適用確認: UNKNOWN。パス実在の自動検査と、記述した技術的事実の確認は別です。
- 現在の実装 — CURRENT IMPLEMENTATION
services/payment-service/src/main/java/com/minibank/payment/repository/PaymentRpcGateway.javaRPC引数とドメインモデルの変換 - 現在の実装 — CURRENT IMPLEMENTATION
services/payment-service/src/main/java/com/minibank/payment/repository/SupabaseRpcClient.javaPostgRESTへのHTTP通信 - 現在の実装 — CURRENT IMPLEMENTATION
services/payment-service/src/main/java/com/minibank/payment/error/RpcErrorMapper.javaSQLSTATEとAPIエラーの対応