ねらい変えてよい遷移と残すべき遷移を自分で整理してから、ルールを直します。
この演習で扱うもの受注の状態は5つあり、進む順番が決まっています。受付前から確定へ、確定から発送済へ、発送済から納品済へ。キャンセルはいま、受付前からしかできません。
実際の業務では、確定したあとでもキャンセルは起きます。取引先の都合、在庫切れ、発注ミス。発送してしまえば取り消せませんが、その手前なら取り消せるはずです。ここを直します。
直す場所は1か所、遷移の可否を判定しているところです。そのあと、自分の画面から実際に操作できるようにします。
なぜこれをやるのか業務ルールの修正は、足すより「壊さない」ほうが難しい作業です。確定からキャンセルできるようにするだけなら簡単ですが、そのとき確定から発送済へ進む道が消えていないか。ここを見落とすと、動いているように見えて業務が止まります。
AI は頼まれた1点に集中するので、周りを巻き込むことがあります。残すべきものを先に言葉にしておくのが、ここでの練習です。
ここまで出たら完了- 確定から進める先が2つになり、消してはいけないものを言える
- 画面のボタンから id=2 を CANCELLED にでき、再読み込みで一覧が変わる
- CONFIRMED の id=3 を SHIPPED に変える操作は、今までどおり通る
- SHIPPED の id=4 をキャンセルしようとすると通らない(500 が返る)
さわるファイル| 書く | src/main/java/com/example/order/service/OrderServiceImpl.javaCONFIRMED の遷移を直す |
| 書く | src/main/resources/static/dashboard.htmlステータス変更ボタンを足す |
まず自分で考える [3min]確定(CONFIRMED)の状態から進める先を、いまと直したあとで書き出してください。
- いま、確定から進める先はどこか(1つある)
- 直したあと、確定から進める先はどこか(2つになるはず)
- この修正で、絶対に消してはいけないのはどれか
AI と詰める「残すもの」を必ず書いて送ってください。書かないと、確定の分岐ごと書き換えて発送済への道を消した案が返ることがあります。
画面側は別の依頼にします。1回に1つです。
@src/main/java/com/example/order/service/OrderServiceImpl.java
validateStatusTransition で、確定済み(CONFIRMED)から
キャンセル(CANCELLED)にもできるようにしてください。
発送済み(SHIPPED)に進める動きは今までどおり残してください。
手を動かす1
考える
case Order.STATUS_CONFIRMED に CANCELLED を足すとき、いまある SHIPPED への遷移をどう残すかを先に決めます。
2
書く
claude で validateStatusTransition の case Order.STATUS_CONFIRMED を対象に指定し、CANCELLED への遷移も許可するよう依頼します。SHIPPED は今までどおり残すと添えます。
3
書く
@src/main/resources/static/dashboard.html を対象に「行から PATCH /api/orders/{id}/status を叩いてステータスを変更できるボタンを足してください」と頼みます。叩き先が /cancel ではなく /status であることを、必ず言葉で指定します。
4
答え合わせ
Java を直したので、1枚目のターミナルで Ctrl+C を押し、mvn spring-boot:run で起動し直します。画面のボタンから id=2 を CANCELLED にし、続けて id=3 と id=4 でも試します。
考えることInvalidOrderStateException は業務エラーなのに 500 で返る。本来はどの HTTP ステータスが正しいか。
AI の出方文字列の "CANCELLED" を直に埋め込む案が出ます。Order.STATUS_CANCELLED の定数に直っているかを変更内容で確かめてください。case ブロックごと書き換えて SHIPPED への遷移を消してしまう案も出るので、そこも見ます。画面側では PATCH /api/orders/{id}/cancel を叩くボタンが出ることがあります。cancelOrder は遷移ルールを通らず、直す前でも 200 を返してしまうので、fetch のパスが /status になっているかを確かめてください。
D1-5+@RestControllerAdvice の共通エラーハンドラを足し、OrderNotFoundException を 404、InvalidOrderStateException を 400 に対応づけてください。実装後に1枚目のターミナルで Ctrl+C を押し、mvn spring-boot:run で起動し直してから SHIPPED の受注を叩くと、500 だったものが 400 に変わります。余力があれば updateOrder と deleteOrder に、PENDING 以外を弾くステータスチェックも足してみてください。
くわしく(背景・詰まったときの対処)
OrderServiceImpl の validateStatusTransition は、受注ステータスをある値から別の値へ変えてよいかを判定するメソッドです。配布時点では case Order.STATUS_CONFIRMED が SHIPPED への遷移しか許していないため、確定済みの受注をキャンセルできません。業務ルールでは PENDING と CONFIRMED からのキャンセルを認めます。出荷済み以降は認めません。モノが動いたのに帳簿だけ消える状態になるためです。
確認は PATCH /api/orders/{id}/status で行います。内部で validateStatusTransition が呼ばれるからです。PATCH /api/orders/{id}/cancel は cancelOrder という別のメソッドを呼ぶので、遷移ルールの確認には使いません。手順3で作るボタンも、押したときに叩くのは /status のほうです。
ステータス値は文字列の直書きではなく Order の定数を使います。綴り間違いをコンパイルの時点で見つけられるからです。
画面のボタンがうまく動かないときは、ターミナルから直接叩いても確かめられます。
打つのは次の1行です。curl -X PATCH http://localhost:8080/api/orders/2/status -H "Content-Type: application/json" -d '{"status":"CANCELLED"}'
返ってきた status が CANCELLED になっていれば通っています。
配布データでは id=2 と id=3 が CONFIRMED、id=4 が SHIPPED で入っています。id=2 を一度 CANCELLED にすると遷移元が変わるので、やり直すときはアプリを起動し直してください。H2 はメモリ上で動くので、起動のたびに配布時の10件へ戻ります。
新しいファイルは作りません。編集は OrderServiceImpl.java の中と、自分のダッシュボードだけです。手順4で起動し直すのは OrderServiceImpl を直したためで、ボタンを足した HTML のほうは再読み込みだけで反映されます。ボタンの見た目や文言をあとから直すときは、起動したまま再読み込みで確かめてください。