Controller:HTTP境界を薄く保つ
Controllerは画面から届くHTTPをJavaの型へ変換し、ユースケースを呼び、決められたHTTPレスポンスへ戻す境界です。金融ルールを置かない理由まで学びます。
Java / Spring Bootを入口に、金融システムのService・Repository・Transaction設計を読む。
先に学ぶ内容 — Prerequisite: ソフトウェア工学 / 銀行システム学ぶこと — LEARNING GOALS
このページで学ぶこと
- @RestControllerとルーティング
- Bean Validationとヘッダー検証
- 成功・失敗レスポンスを統一する方法
体験35 · JAVA CONTROLLER(境界を薄く保つ)
振込先の名義を、利用者が入力したそのままの値で使ってよいか確かめよう
まずは予想をひと言。本文を読んだ後、教材内の参照元で実際のDTOと例外処理を確認します。
- 01まず予想する — PREDICT
まずここだけ:思ったことをひと言書く
振込先の銀行名や名義を、利用者が入力したrequest bodyの値そのまま使ってよいと思いますか?
正解でなくて大丈夫です。「〜だと思う」から始めてみましょう。
ヒント1を見る
利用者が入力した値を、そのまま信じてよいでしょうか?
ヒント2を見る
振込先の正式な名義は、どこで確定させるべきでしょうか?
もっと詳しく:この問いで使う専門用語
- DTOは、HTTPで受け渡す項目を表す専用の型です。
- Bean Validationは、注釈で入力形式の制約を宣言する仕組みです。
- Controller Adviceは、複数Controllerの例外処理をまとめる仕組みです。
日常語で理解する — PLAIN LANGUAGE
まず、ここだけつかむ
ControllerはHTTPリクエストを受け、形式を検証し、Serviceを呼び、HTTPレスポンスへ整える境界です。
金額やOTPの形式は入口で拒否しますが、残高・KYC・所有者のような業務判断はDB側へ委ねます。
既知の失敗と想定外の失敗をGlobalExceptionHandlerで安全な共通JSONへ変換し、生の内部情報を外へ出しません。
順番に理解する — DIFFICULT PARTS, STEP BY STEP
難しいポイントを、順番にほどく
専門用語を覚えることより、「なぜ必要か」「このシステムのどこで使うか」を理解することが目標です。
DTO
HTTPで受け渡す項目を表す専用の型です。
外部契約と内部モデルを分け、公開項目を制御するためです。
CreatePaymentRequestとCreatePaymentResponseを分けています。
Bean Validation
注釈で入力形式の制約を宣言する仕組みです。
不正な形式をユースケースへ渡す前に一貫して拒否するためです。
@Digits、@Size、@Patternなどを使います。
Controller Advice
複数Controllerの例外処理をまとめる仕組みです。
エラー形式と情報公開方針を一貫させるためです。
GlobalExceptionHandlerがcode・message・requestIdを返します。
発展 — ADVANCEDもっと詳しく確認する最初の学習では読まなくても問題ありません。仕組みをもっと深く理解したい場合に開いてください。
Payment APIの2つの入口
@RequestMappingが共通パス、@PostMappingが個別操作を定義します。@RequestBodyはJSON、@RequestHeaderはHTTPヘッダー、@PathVariableはURL内の値を受け取ります。
| HTTP | 役割 | 主な結果 |
|---|---|---|
| POST /api/v1/payments | 振込依頼を作りOTP待ちにする | 201 Created |
| POST /api/v1/payments/{paymentId}/confirm | OTPを確認し振込実行へ進める | 期待される業務結果は200 OK |
入力検証は入口で早く失敗させる
@ValidによりDTOの制約が検査されます。OTPは空でなく6桁の数字、金額は1以上の整数、memoは最大200文字という形式契約があります。
形式が正しくても、本人の振込依頼か、期限内か、残高があるかは分かりません。それらはDB側の業務検査です。
public record ConfirmPaymentOtpRequest(
@NotBlank
@Pattern(regexp = "^\\d{6}$")
String otpCode
) {}認証情報と冪等キーもAPI契約
- AuthorizationはBearer形式であることをControllerが確認
- Bearer tokenの本人性と権限は転送先のPostgREST・PostgreSQLが判断
- Idempotency-KeyはUUIDとして解析し、不正な形式を拒否
- 振込先の銀行名・支店名・名義はクライアントから受け取らず、DB側で解決
例外変換を一か所へ集める
@RestControllerAdviceのGlobalExceptionHandlerが、ApiException、入力エラー、想定外例外を統一JSONへ変換します。
想定外例外はサーバーログへ記録しますが、クライアントへスタックトレースや生のPostgreSQLメッセージを返しません。requestIdによって利用者の応答と内部ログを結びます。
| 分類 | 外部応答 | 内部で残すもの |
|---|---|---|
| 既知の業務・認可エラー | 安定したcodeと安全なmessage | requestId |
| 入力形式エラー | VALIDATION_ERROR | 検証失敗の診断情報 |
| 想定外例外 | INTERNAL_ERRORの一般文言 | stack traceとrequestId |
体験学習の続き · LEARNING LOOP
ここまで読めましたか?続けて確認してみましょう
DTOとController Adviceの考え方を読んだところで、教材内の参照元で実際のDTOと例外処理を確認し、自分の言葉で説明してみます。
- 02仕組みを確かめる — CONCEPT CHECK
画面操作なしで、仕組みを確かめる
この仕組みは、Mini Bankの画面には現れません。次のEvidenceとCode Trail、教材本文だけで理解を進めます。
- 03証拠を確認する — EVIDENCE
何を見たか、1つずつ確かめる
Evidence(判断の根拠となる記録)を確認したらチェックします。各項目の「何を見るか」を手がかりにしてください。
画面を開けない項目は、Source Trail(教材の参照元)と教材の説明で確認できます。失敗を実際に起こす必要はありません。
- 04コードの処理経路を追う — CODE TRAIL
コードを書く必要はありません
ここでは「説明されている仕組みが、実際のMini Bankのどこに実装されているか」を確認します。まずは日本語の役割説明を読み、入力した依頼がどこへ渡るかをつかみましょう。コードの全文を読む必要はありません。
まずControllerが何を受け取り何を検査するかを見て、次に例外がどう安全な応答へ変換されるかを見ます。
教材の参照元を確認する — Source Trail ↓HTTP受付とユースケース呼び出し。
services/payment-service/src/main/java/com/minibank/payment/controller/PaymentController.java例外から安全なHTTP応答への変換。
services/payment-service/src/main/java/com/minibank/payment/error/GlobalExceptionHandler.java - 05自分の言葉で説明する — EXPLAIN
LEVEL 1 — やさしい説明(まずここだけ)
なぜControllerは振込先名義をrequestから受け取らず、DB側の解決結果を使うのでしょうか? 短い2文を目安に説明してください。
日常語だけで完了できます。専門用語の数や正誤を自動採点するものではありません。
ヒント1を見る
「DTO」と「Bean Validation」という2つの言葉を使って書いてみましょう。
ヒント2を見る
形式が正しいことと、内容が正しいことの違いを考えてみましょう。
もっと詳しく:LEVEL 2 — 専門用語を使った説明(任意)
DTO、Bean Validation、Controller Adviceの3つを使って、ControllerがHTTP境界をどう薄く保つか説明してみましょう。
挑戦する場合は、上の説明欄に追記できます。追加しなくても完了できます。
説明の確認ポイント — Checkpoint- 形式検査と業務判断が別の場所にあると説明できる
- 内部情報が外部応答へ漏れないと説明できる
理解を確かめる — CHECK YOUR UNDERSTANDING
3問で理解チェック
答えを頭の中で考えてから、各項目を開いて確認してください。
Q1Bearerという文字列を確認すれば認証完了ですか?
いいえ。Controllerは形式を確認し、tokenの有効性と権限はPostgREST・PostgreSQL側が判断します。
Q2なぜ振込先名義をrequest bodyから受け取らないのですか?
利用者による改ざんを避け、口座番号からDB側で正式情報を解決するためです。
Q3想定外例外のstack traceを利用者へ返しますか?
返しません。内部ログへ残し、外部には一般的なINTERNAL_ERRORを返します。
完全に暗記する必要はありません。大切な考え方を説明できそうなら押してください。
教材の参照元 — 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/controller/PaymentController.javaHTTP受付とユースケース呼び出し - 現在の実装 — CURRENT IMPLEMENTATION
services/payment-service/src/main/java/com/minibank/payment/controller/dto/CreatePaymentRequest.java振込依頼の入力制約 - 現在の実装 — CURRENT IMPLEMENTATION
services/payment-service/src/main/java/com/minibank/payment/error/GlobalExceptionHandler.java例外から安全なHTTP応答への変換