Spring Boot に入門した話〜架空のキャンプ場の予約アプリを Elastic Beanstalk にデプロイするまで〜②開発編

前回の記事「①要件定義・DB設計編」の続きです。
今回も雑なメモみたいな感じですので、よろしくお願いします。

開発

アーキテクチャ

アプリ構成

  • Java
  • Spring Boot
  • Tomcat(Embedded)
  • Thymeleaf
  • MyBatis

レイヤー分割

  • プレゼンテーション層(Controller、Form、Validator)
  • アプリケーション層(AppSevice)
  • ドメイン層(Model、Service)
  • インフラ層(Repository)

バリデーション方針

  • バリデーションは、主にController(Bean Validationと Spring Validator)層で行い、再入力を促します。Controllerを抜けてきたデータに対しては、アプリケーション層でのバリデーションでチェックし、例外を投げてエラーページに促します。
  • 本来であれば、JavaScriptで細かくバリデーションを行いますが、Springの機能を試したかったので、今回は省略しました。

スマートフォン対応

認証フレームワーク

  • 認証機能の構築には、Spring Securityを使用します。

その他方針

  • DB登録・更新時のリクエストは、PRGパターンで二重送信を防止します。
  • アプリケーションログは全て標準出力に流し、クラウド側でストリーミング、ローテーションを行います。

URL設計

  • URLパラメータを活用して、ハンドラを切り替えていきます。
    • 例: 会員登録
      • 入力画面表示:GET + /signup + ?form
      • 確認画面表示:POST + /signup + ?confirm
      • 入力画面に戻る:POST + /signup + ?redo
      • 登録:POST + /signup + なし
      • 完了画面表示:GET + /signup + ?complete

考慮事項(機能別)

【会員登録】

パスワードはhiddenで引き継がない

画面間のデータの引き継ぎには、基本的にhiddenを使用しています。一方で、会員登録には確認画面があり、パスワードをクライアント側に渡したくないという考えで、パスワードだけ一時的にセッション変数を使用しています。

【メールアドレス変更】

認証情報の更新

Spring Securityが管理する認証情報(Authentication)のメールアドレスは、会員機能のいろいろな箇所で参照されます。そのため、メールアドレスの変更後は、認証情報(Authentication)を再設定しています。

【パスワード変更】

変更時に現在のパスワードを確認

セッションハイジャックされた状態でのパスワード変更を防止するため、新しいパスワードの入力とともに、現在のパスワードの確認をしています。

【スケジュール】

スケジュール(カレンダー + API
  • カレンダーには、FullCalendarを使用しました。
  • サーバ側はRestControllerAPIのエンドポイントを作成しています。
  • next/prevボタンの切り替えで、該当月の空き状況だけ取得するように作りました。
「戻る」ボタンを最下部に

スマートフォンから見た場合に、左中央に設置した「戻る」ボタンを最下部に設置する必要がありました。今回はflex-direction: columnmax-heightを用いて縦並びを折り返したレイアウトとして作り、orderで順番を切り替えることで対応しています。

【予約(会員)】

認証後のリダイレクト先でのリロード対応

会員による予約のため、予約内容の確認画面表示の前に、一旦認証を挟みます。認証が成功すると元のURLにリダイレクトされるのですが、ここでのデータの引き継ぎには通常のセッション変数を使用しています。RedirectAttributes#addFlashAttributeを使うと確認画面のリロードに対応できないので、この方法を選びました。

消費税変更時の料金計算

料金計算では、せっかくなので消費税変更の考慮を含めました。ホテルやキャンプ場等を予約する場合には、消費税は予約日ではなく宿泊日に対して課されます。なので、サイトを2日間予約する場合に、初日は10%、2日目は15%ということが起こりえます。「端数処理は税率ごとに一回ずつ」というのがルールとのことなので、そのように実装しました。

【予約一覧】

ページネーション

ページネーションは、SQLlimitoffsetを書いて絞り込む方法をとっています。MyBatisでは他に、RowBoundsを用いた方法がありますが、ResultSetのデータスキップのコストがかかるので、この方法は採用していません。

【予約詳細・キャンセル】

予約者以外のリソースの閲覧、キャンセルを禁止

予約詳細のURLは/member/reservations/{id}の形式で、idの値を変えると他人のリソースが閲覧できてしまう恐れがあります。Spring Securityでは、URLでのアクセス制御の他に、メソッドに対するアクセス制御も用意されていて、ここではアプリケーション層のメソッドに@PostAuthorizeを付与し、メソッドの戻り値を評価してアクセス制御を行っています。

考慮事項(その他)

【メッセージ】

リソースファイルによる管理

例外発生時のメッセージは、messages.propertiesに集約させています。

【ログ】

手動でのログ出力管理はしない

ログ出力をあちこち手作業で書くのは手間なので、AOPとListener を活用して、ログ出力の記述を集約しています。

ログにはリクエストIDと会員IDを付与

ログの見通しを良くするため、MCDを利用してログにリクエストIDと会員IDを埋め込むようにしました。

【例外】

業務例外とその他の例外

例外には業務例外としてBusinessException、システム例外としてSystemException、その他@ResponseStatusを付与した例外を定義しました。ハンドリングとしては、業務例外が発生した場合はUCの適切なやり直し場所に誘導し、システム例外やその他の例外の場合は、それぞれのHTTPステータスに合ったエラーページを表示するようになっています。
 

「③デプロイ編」に続きます