SCSKの畑です。
今更感はあるのですが、今回はこれまでの投稿で取り上げてきた Web アプリケーションのアーキテクチャについて、改めて概要や各サービスの役割などを記載していこうと思います。正直、最初の方の投稿で先に説明しておくべきだったかもしれません・・
アーキテクチャ図
いつものやつです。図内に注釈もあるので、各サービスの役割は何となくご理解いただけるものかと思います。
アプリケーションの目的・用途について
初回のエントリ含め何回か記載していますが、改めて記載するとデータベース/ DWH 上のテーブルデータをメンテナンスすることが目的のアプリケーションとなります。本事例の場合のメンテナンス対象は Redshift となります。また、実はここで初めて言及するのですが、データメンテナンスの要件として、以下2点のような機能も実装しています。
- アプリケーションのメンテナンス対象テーブルの(データ)バージョン管理機能
- 過去バージョンデータの参照や、バージョン間のデータ比較が可能
- アプリケーション経由でテーブルデータを更新する際の簡易ワークフロー機能
- 更新を承認する権限を持った別ユーザによる承認を以って Redshift 側に更新が反映される
また、アーキテクチャ概要図には載せていませんが、本事例においては DWH (Redshift) 上にトランザクションデータを投入・更新するための ETL/ELT ジョブネットをアプリケーション経由で実行する機能も実装しました。同ジョブネットは StepFunctions 及び Lambda で実装しており、 StepFunctions のステートマシンをアプリケーションから実行するような形式となります。
他、アプリケーション自体の運用管理機能として、メンテナンス対象テーブルの情報や実行対象 ETL/ELT ジョブネットの情報、Cognito のユーザ/グループなどをメンテナンスできる機能も用意しました。このあたりは当初のスコープにはなかったのですが、お客さんの要望を聞きつつ必要になりそうな機能をピックアップした形です。
アプリケーションのビルド・デプロイについて
大した内容ではありませんが、先にこちらを簡単に説明しておきます。
ビルドについては、Nuxt3 の場合サーバエンジン (nitro) の設定に Lambda デプロイ用のプリセットがあるため、それを指定してビルドするだけで OK です。このお手軽さは非常に魅力的でした。
デプロイについては、お客さん環境の都合上手動で行う必要がある関係で、CloudFront 配下の Lambda と S3 にそれぞれ AWS マネジメントコンソールから粛々とデプロイしています。ただ、8回目のエントリで説明した AppSync の手動移植と比較すると大分ラクなのが幸いでした。ただ、Web上には Serverless Framework を使用した手順が多く、手動でのデプロイ方法を調べるのに少し手間取りました。具体的にはほぼ以下サイト様の通りですが、今回の事例において少し異なる点もありましたので以下に記載しています。

- .output/public/ 配下のファイル一式を S3 にアップロード
- ビルドの度に静的コンテンツのファイル名がハッシュ値に応じたものに変わるため、デプロイ前に S3 上のファイルを削除しておく方がベターです。ファイル名は固定するような設定に変更することもできるようですが。
- .output/server/ 配下のファイル一式を Lambda にアップロード
- アップロード操作を踏まえると一式を zip ファイルにしてしまうのが良いと思います。
- Lambda のハンドラ設定については、上記のようにアップロードする前提においては index.handler のままで良いようです。当初は .output/index.mjs を実行する index.js を別途作っていたのですが、不要でした。
フロントエンド側のサービス
フロントエンド側のサービスとしては、以下3つです。
- Amazon CloudFront
- Amazon S3
- AWS Lambda
アーキテクチャ図に記載の通り、CloudFront 配下に S3 及び Lambda を配して、Nuxt.js (Nuxt3) による SSR (Server-Side Rendering) 構成で Web アプリケーションを動作させています。厳密には Nuxt3 における Universal Rendering を使用しているので CSR (Client-Side Rendering) との併用となっています。
ということで、各サービスの位置づけ・役割については概ね想像できるところだと思うのですが、少しだけ詳細な話も含めつつ以下に記載していきます。
Amazon CloudFront
アーキテクチャ概要図の通り、クライアント端末からのアクセスを受けてアプリケーションを配信しています。サーバサイド生成(動的)コンテンツは Lambda 経由、静的コンテンツは S3 経由です。サーバレスアーキテクチャとしては標準的な構成と思いますし、CloudFront 自体の設定も特にトリッキーなところはなかったため、今回は取り上げません。
ただ、お客さんの AWS 環境で構築するにあたりセキュリティ関連の設定について幾つか考慮する必要があったため、以下にまとめました。
WAF 設定
お客さんの環境ポリシーにより Web WAF を設定しています。
設定自体はお客さんの所管であったため詳細は割愛しますが、本アプリケーションはインターネット上に公開せず、アクセスできるネットワーク/IP アドレスのレンジを限定する必要があったため、その点については考慮する必要がありました。最も、概要図の通り CloudFront にアクセスする対象はクライアント端末のみであり、それらが属しているネットワーク内からのみアクセス可能とすれば OK だったので特に難しい要件ではなく、お客さん側のネットワーク構成も相まって特に問題なく設定できました。
Origin access control 設定
CloudFront の配下に配する S3 及び Lambda について、CloudFront からのアクセスのみを許可するような設定が必要でした。幸い、こちらも Web 上の情報や知見は豊富で、上記のようにいずれも Origin access control 設定により実現できました。
特に、Lambda については構成上 CloudFront からのアクセスに関数 URL 機能を使用する必要がありますが、同機能には WAF などの外部アクセス制限機能を適用できないことから、当初はお客さんの環境ポリシーに準拠せず NG が出ていました。幸い、2024/4 に Lambda の関数 URL も Origin access control をサポートするようになっていたため、その旨をお客さんに伝えてクローズと相成りました。

Amazon S3
CloudFront の項目で説明した通り、アプリケーションから使用する静的コンテンツを配置しています。
構成上必要となる設定も前述の Origin access control くらいで、正直特に言及することはありません。せいぜい、デプロイ時に AWS マネジメントコンソール経由だとネットワークの具合次第でアップロードに失敗することがあり、その場合のオペレーションが面倒だったくらいですが、まあこれって環境依存の話なので正直関係ないですね・・。
AWS Lambda
CloudFront の項目で説明した通り、サーバサイドで動的なコンテンツを返すために使用しています。具体的には、ビルドしたアプリケーションのコード一式を Lambda にデプロイするような形となります。デプロイ手順としては先述した通り難しくはないのですが、S3 と比較すると幾つか躓いた点がありましたので備忘も含めて以下にまとめました。
- 先述した内容の繰り返しとなりますが、ハンドラ設定は index.handler で OK でした。
- Lambda 実行ロールについて、当初はサーバサイド側で実行され得る機能に対応した権限(Cognito なり AppSync 関連)が必要になると考えていたのですが、いずれも不要でした。
- サーバサイドで認可が必要な処理が行われるとしても、それを実行するための権限は2回目のエントリで書いたように Cognito 経由で付与されるロール側で付与されるためと理解しています。
- これは余談寄りですが、アプリケーションで使用している pdf ファイル表示用のライブラリで不具合があった関係で、Node.js を 22 系に上げたかったのですが、Lambda におけるリリースタイミングが 2024/11 と思ったより遅かったため、不具合の解消にやや時間を要しました。(Node.js の 22 系リリースは 2024/4)
- https://aws.amazon.com/jp/about-aws/whats-new/2024/11/aws-lambda-support-nodejs-22/
- このへんのスピード感(早い or 遅い)が肌感覚として良くわからないですね。リリースから半年と考えると標準的な気もしますし、Node.js の 22 系が安定版と考えるとちょっと遅いような気もしますし・・
余談など
Next.js(React) ではなく Nuxt.js(Vue) を使用した理由をたまに問われるのですが、最終的には自分の好みです。特に、Vue ファイル内で HTML テンプレートライクに書けるのが、昔から HTML タグ打ちしていたような人間にとっては馴染みやすかったというのが大きかったです。それから調べた限りだと、今回のような少人数開発のアプリケーションにおいてはある意味どちらを選んでも大差ないと感じたのも理由の一つです。なので最終的に自分の好みを優先させてしまっても良さそうだな、と・・
また、今振り返ると、SSR 構成にする必要性は機能要件上はなかったかもしれません。当初はサーバサイドで重い処理をさせてクライアント側に返すようなことがアーキテクチャ上できるようにしておいた方が良いと考えていましたが、アプリケーションの特性上クライアント側で処理する内容が多い上、重い処理も結局のところは AppSync 経由で実行されるバックエンド処理用の Lambda が担うことがほとんどであったためです。とは言え、アクセス時のレスポンス向上などの効果はあったと思いますし、この構成にしたことによるアプリケーション実装上のデメリットが本アプリケーションについては正直思っていたほど出なかったこともあり、悪い選択でもなかったとは思っていますが。
まとめ
今振り返ると、当時は自分なりに色々調べた上での判断とはいえ、ちょっと考慮が足りていなかったかもという点があるというのが正直なところです。特に SSR 構成を前提としてアーキテクチャを検討していた点については、もう少し深堀りして調査できていればなお良かったかもしれません。このあたりは次回以降の案件などで活かしていければと思っています。
そして、、思ったより長くなってしまったので、バックエンド側の話は後編に回したいと思います。
本記事がどなたかの役に立てば幸いです。