読者です 読者をやめる 読者になる 読者になる

woshidan's blog

そんなことよりコードにダイブ。

Railsのコントローラのアクション間でデータの状態の管理がよく分からないとき

business transaction Rails コントローラ

こちらの記事*1の続きです。

なんか、いわゆるfat controllerの具体的事例集、みたいなかんじになってしまって途中で書く気が失せてきたのですが、ふと思い出して自分の公開鍵のメモをいま見直したら予想以上に良かったので、一応残しときます。

内容

  • アクション間でデータの状態の管理がよく分からないとき
    • アクションの間でのデータの受け渡しがよく分からない
      • 本当にややこしいのは情報の状態管理ではないか
      • 情報を送り合う時は送り合う方法をなるべく統一する
    • 登録フォームの中で外部サービスにアカウントを作るためのパラメータを入力する欄がある
      • 自サービスの登録ボタンを押した際の外部サービスへのリクエストの結果による登録失敗が嫌
        • 外部サービスのアカウントを自サービスのレコードの前に作成してしまう
      • レスポンスの結果だけを扱うクラスを用意して責務を分担する
      • そもそも失敗率はどれくらいなのか

アクション間でデータの状態の管理がよく分からないとき

複数のアクションで、1つの注文というレコード(とそれにまつわるレコード)を作ろうとした時のことを考えてみます。

商品選択 => 住所入力 => 決済情報入力 => 注文確認 => 注文確定

みたいな感じの奴です。

これらのアクションの間で入力されたデータを受け渡すのは基本的に割と難しい気がします。*2

なんで難しいのか、と外部APIへのリクエストがこの流れに含まれているとさらに難しくなる場合があるので、その場合について対策をメモしておきます。

アクションの間でのデータの受け渡しがよく分からない

まず、外部APIとかそういうことは置いておいて、自サービス内のことだけを考えてみます。

商品選択 => 住所入力 => 決済情報入力 => 注文確認 => 注文確定

の流れで情報を送る場合、あるいは、この処理について修正をする場合に億劫になる理由について、以下の本を読んでたら、何となく触れられていました。

まだ解決法まで読めていないのでそういうことがあるんだ、としか言えない感じではありますが、結構長い本なのでたどり着く前に忘れそうです。なので、いまのところ考えていたことをまとめておきます。

Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler))

Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler))

本当にややこしいのは情報の状態管理ではないか

最初に謝っておきますと、本の中で触れられていたのは、複数のコントローラのアクションを使った遷移ではないです。

本の中で触れられていたのは、アプリケーションサイドで1つのレコードを作るまでの入力 ~ 登録までの状態を管理することが難しい(そしていくつかの解決方法がある)、ということでした。

フォームの画面遷移は、レコードの管理のような側面から言い直せば、先の方の画面で登録に必要な情報を入力されてから、最後の画面で送信ボタンを押して登録するまでのトランザクションの管理、とも言えます。

やっていることは、いわゆるアプリケーションサイドでのトランザクションのような感じなんですね*3

ここをいじるのは本当にややこしいこった、みたいな文章を読んで自分だけではないのだ、と若干安心したのでした*4

情報を送り合う時は送り合う方法をなるべく統一する

とはいえ、処理がややこしいので、大きなフォームをどーんと出して情報をいっぺんに入力してくれ、とは言えません。何かしら工夫する必要があります。

なんとなくではありますが、自分がいじっててややこしかったのは、前入力した情報をどこで持っているか、ということでした。

複数のアクション間で情報を受け渡すにはいくつかの方法があります。

cookie, session, hidden要素を含んだフォームの送信, 一部だけデータベースに前もって保存してデータベースから読み込んだり、といった場合もあるでしょう。

いくつかある情報を複数の画面間で受け渡すとき、上記のものを組み合わせて用いられていて、方法が統一されていないのがしんどかったです。

なので、sessionなりフォームなり、画面遷移の際、情報を受け渡す方法を一定にしておくと少しはやりやすいのかな、と思いました。

また、最近だとJavaScriptMVCフレームワークのModel層でもバリデーションがやりやすいため、それを利用したり、フォームの切り替えをサーバサイドでなく、クライアントサイドでやるというアプローチ*5もあります。

登録フォームの中で外部サービスにアカウントを作るためのパラメータを入力する欄がある

商品選択 => 住所入力 => 決済情報入力 => 注文確認 => 注文確定

この流れの中で、実は外部サービスと提携していて、どこかで外部APIへの登録を行わないといけないということも考えられます。

自サービスの登録ボタンを押した際の外部サービスへのリクエストの結果による登録失敗が嫌

ここで厄介なのが、自サービスに入力していた情報は正常だったはずなのだけれど、それを元に外部サービスのAPIを叩いたら、通信エラーかなにかで登録に失敗した、ということです。

これを避けたいので、上の遷移だと 決済情報入力 の分で、注文は確定していないのですが注文のためのアカウントを作成してしまう、それを決済情報入力のバリデーションの一貫としてしまう、ということが考えられます。

正直な話をすると、これは悪手だと思います。

後から、ジョブなりを回して注文に至らなかったけれど作成されている一部のデータを削除する事はもちろん可能です*6

けれど、本当は注文が確定するタイミングとアカウントが作成されるタイミングは可能な限り揃えられるならその方が扱いが楽になります*7

問題は、外部サービスAPIのリクエストにこけるのが嫌、こけた場合どうしたらいいか分からないなので、まずこの2点を考えたらいいのではないでしょうか。

レスポンスの結果だけを扱うクラスを用意して責務を分担する

成功以外のレスポンスが怖くて、レスポンスが成功を受け取った場合以外処理を先に進めたくない場合、とりあえず、外部APIのレスポンスが大きい、というより本当は

「フォームを投稿してレコードを作成する一環として外部APIのアカウントを取得する」

という処理の単位が一カ所で扱うには大きすぎるのではないか、ということが考えられます。ですので、まず外部APIと通信を行う部分を外部サービスアカウント専用のモデルやクライアントを用意して対処すると良いと思います。

そのモデルやクライアントの中で、レスポンスが失敗した場合のレスポンス等を想定した、リクエストの送信と結果の受け取りをテストします。そして、コントローラのアクション側ではモデルのレスポンスが成功した場合と失敗した場合だけを考えるようにすると少しは楽になるかもしれません。

そもそも失敗率はどれくらいなのか

成功しなかった場合が怖いと言っておりますが、実際どれくらいの失敗率なのでしょう。

一回すでにそのAPIを使っている人が周りにいるなら相談してみるといいと思います。

測ったり聞いたりした結果、数百回やって1回も発生しないようであれば、UIへの影響は杞憂ではないでしょうか。

一旦実装はその例外を無視して9割方を完成させてしまって、気持ちに余裕ができてからきちんと取り組んだ方が経験的によさそうです*8

*1:アクションのコードがやたらと縦に長いとき

*2:最初に書く時はそうでもないのですが、追加や編集がね...

*3:ユーザーから見た一まとまりのデータ管理の単位をbusiness transactionと言っていたような気がします

*4:そして、レビュアーに気づいたら大きくなりすぎていたPRについてごめんなさいを言いに行ったのでした

*5:この場合、投稿される情報について、それぞれの項目ごとにオブジェクトを用意しているのかね...

*6:ただ、ジョブで本当に削除されているのか、コントローラのアクションを読んだ人は割と長い間不安になるのではないでしょうか。まあ、正直、私が苦手でしてね。

*7:うまく説明できないのだけど、トランザクションは0か無か、にしておいたほうがよいと思います

*8:一度に考える事は1つの方がよい結果を生む気がします。