Vue3とAWSでWebアプリ作成～サーバーレスアーキテクチャ～

# はじめに
### 概要
フロントエンドをVue３、バックエンドをAWSでWebアプリ作ってみましたという記事です。
Vueの細かいソースやAPIのソースについては特段記載予定はありません。
どんな流れでWebアプリを作成したか、AWSのサービスはどんなものを利用したかなど、マクロ視点的な記事になります。
筆者の個人的な開発記録メモだと思って、読んでいただけたら幸いです。
目指せ、フルスタックエンジニア。

# 目次
- 題材選び
- 主な機能
- Vue3でのアプリ作成
    - 本の読み込みについて
    - PWA
- デザインについて
- バックエンドシステム構成
    - 各サービスの説明
    - LambdaとRDS
- 完成したアプリ
- 今後について

# 題材選び
Webアプリ作成しようと思いまず考えなければいけないのが、題材です。
今回は、筆者が所属するオフィス内の本棚にある書籍を管理するものとしました。
書籍管理アプリというやつです。


オフィスの本棚には数多くの書籍があり、自由に借りて読むことができます。
しかし、どんな書籍があるのかまとまっていなかったり、貸出の管理もできていませんでした。

そこで、書籍の一覧が分かり、貸出をアプリで管理できるようにしようと思い、この題材を選びました。

# 主な機能
現在実装している主な機能は以下です。
- ログイン機能
- スマホのカメラをバーコードリーダーとし、本のバーコードを読み取ることでアプリに本の登録ができる
- 本のレンタル
- 本の一覧の表示
- マイページで自分が借りている本の一覧が分かる
- PWA化

これら機能のうちポイントとなる機能を紹介していきます。

# Vueでのアプリ作成
### 本の読み込みについて
アプリで本の一覧を表示させるためには、当たり前ですが本の情報をデータベースに登録する必要があります。
タイトル、著者、概要を全て手入力・・・！というわけには当然いきませんので、どうにかこれら情報を取得できる術はないか探しました。

実は本の裏表紙にはバーコードが必ず印字されています。
これには**ISBNコード**という、書籍をユニークに判別するコードが埋め込まれているのです。
さらに便利なことに、ISBNコードから本の情報（タイトル、著者・・・）を取得できる無料のAPIがいくつか存在しています。
ここまでくれば、これを利用しない手はないので
**スマホのカメラでバーコード読み取り　→　コードを元にAPI呼び出しで本の情報を取得**
という流れで作成することにしました。

バーコードの読み取りには、**Quagga.js**を使用しました。
世の中便利なライブラリが溢れているものです。（感謝）
Quagga.jsの詳細は[こちら](https://github.com/serratus/quaggaJS)
カメラを起動し、対象のバーコードを枠内に収めるとISBNコードを取得できるというものです。

次にやることは、このISBNコードから本の情報を取得する処理です。
この書籍検索APIは何種類か存在しているのですが、APIによって取得できる値が異なります。
代表的なものでは以下のAPIがあります。
- 楽天ブックス書籍検索API
- Google Books APIs
- openBD

それぞれの特徴をまとめている記事もございますので、興味ありましたらどうぞ。
【参考】[書籍検索APIのリクエストパラメータ・取得値考察](https://qiita.com/kanary/items/5ec45bbc01efd4388fdb)

筆者は今回この中から**Google Books APIs**を選びました。
クエリパラメータにISBNコードをセットし呼び出すことで、本のタイトル、著者、概要、表紙画像を取得します。

#### 本の読み込みまとめ
1. Quagga.jsを使用しカメラ起動
1. バーコード読み取り
1. 読み込み完了後、Google Books APIsを呼び出し（クエリパラメータに読み取ったISBNコードをセット）
1. Google Books APIsで取得した本のタイトル、著者、概要、表紙画像をデータベースに保存

このような流れで本の読み込み処理は完成となります。

### PWA
PWAについての説明は省略します。
気になる方は参考記事を貼っておきますのでご覧ください。
【参考】[PWA（Progressive Web Apps）とは？](https://www.profuture.co.jp/mk/column/what-is-progressive-web-apps)

今回作成アプリは、メインはスマホアプリとして利用してもらいたいためPWA化することとしました。
PWA化するだけであれば、main.tsとvite.config.tsファイルに数行追記するだけでできます。

```typescript:main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
import { createPinia } from "pinia";
import { createPersistedState } from "pinia-plugin-persistedstate";
import VueSweetalert2 from 'vue-sweetalert2';
import 'sweetalert2/dist/sweetalert2.min.css';

// for PWA
import { registerSW } from 'virtual:pwa-register'
//↑PWA化するための追記コード

// Vuetify
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import '@mdi/font/css/materialdesignicons.css'

const vuetify = createVuetify({
  components,
  directives,
})

const pinia = createPinia()
pinia.use(createPersistedState());

declare global {
    interface AudioParamMap {
        get(name: string): any;
    }
}

// for PWA
registerSW()  
//↑PWA化するための追記コード

const app = createApp(App)

 app.use(router)
 app.use(vuetify);
 app.use(pinia);
 app.use(VueSweetalert2)
 app.mount('#app')
```

```typescript:vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tsConfigPaths from 'vite-tsconfig-paths'
import { VitePWA } from 'vite-plugin-pwa'


// https://vitejs.dev/config/
export default defineConfig({
  server: {
    host: true,
    port: 8080
  },
  plugins: [vue(), 
    tsConfigPaths(),
    VitePWA({
      injectRegister: null,
      registerType: 'autoUpdate',
      scope: '/',
      // srcDir: 'service-worker',
      // filename: 'sw.ts',
      // strategies: 'injectManifest',
      // includeAssets: ['assets/*.{ico,svg,png,jpg,eot,woff,ttf,woff2}', 'app-icon/*.{svg,png}'],
      devOptions: {
        enabled: true,
        type: 'module'
      },
      manifest: {
        display: 'standalone',
        name: 'Lablibrary',
        short_name: 'Lablibrary',
        description: 'Lablibrary app.',
        start_url: '/',
        icons: [
          { src: 'app-icon/icon_192px.png', sizes: '192x192', type: 'image/png' },
          { src: 'app-icon/icon_512px.png', sizes: '512x512', type: 'image/png' }
        ],
        theme_color: '#ffffff'
      },
    }),
  ],
})
```

オフライン状態での挙動や、プッシュ通知処理などの機能を盛り込む場合は、service workerを修正することでアプリとしての幅を広げることが可能です。
今回はミニマムでの開発なので、PWA化するところまでしかできておりません。

# デザインについて
ゼロからアプリを作成する醍醐味の一つに、**デザイン**があります。
今回のアプリのトンマナは、好きな色のピンクをベースにしました。
アプリのトップ画面と、PWAアイコンは共通のデザインを使用することで、アプリの印象付けをします。
これらデザインはアプリ自体の印象と直結するため、どういったデザインにするか悩みましたが、**本と筆を持った子**のイラストにしました。
（本を読むインプットと、型にとらわれず描く発散するアウトプットのイメージです）
![img1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3799289/48335075-cc6b-dc11-88e1-7cc24b20b2e7.jpeg)

イラストは流行りの画像生成AIで作成しました。

今回は数ある生成AIの中でも、[**Adobe Firefly**](https://www.adobe.com/jp/products/firefly.html)を使用しました。
Adobe Fireflyは、その名の通りAdobeが開発した、「画像生成AI」です。
日本語でのプロンプト（指示）入力が可能となっています。
Adobe Fireflyの特徴としては、著作権の心配がほぼないという点です。
Adobe独自の生成AIモデルを搭載しており、これはAdobe Stockにある、著作権者が許諾した画像やオープンライセンス画像などの、著作権侵害の心配がない画像のみを使用して学習されているため、生成された画像は商用利用が可能となっております。

# バックエンドシステム構成
バックエンドはAWSを使用し、完全サーバーレスアーキテクチャで構築しました。
AWSを使用したバックエンドシステムの構築方法はたくさんありますが、今回は以下のサービスを使用して構築しました。

- RDS
- Lambda
- API Gateway
- Cognito
- Amplify

システム構成図はこのようになっております。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3799289/b8e14d57-850b-7106-0180-ff337e188f71.png)




筆者が調べた中では、最も簡単かつセキュアに構築可能な、サーバーレスアーキテクチャのベストプラクティスです。

### 各サービスの説明
#### RDS
その名のとおり、AWSが提供するマネージドのリレーショナルデータベースサービスです。
Oracle、MySQL、MariaDB、PostgreSQL、SQL Serverなどの様々な種類のDBエンジンを、データベースインフラの運用管理の手間を削減しながら利用することができます。
またRDSには、Amazon AuroraというAWSが独自に開発したDBエンジンも利用することができます。
Auroraについて詳しく記載すると、長くなってしまうため簡潔に記載しますが、Auroraには以下の特徴があります。

**最大15個のAuroraレプリカ**
Amazon Auroraに限らず、Amazon RDSでは「リードレプリカ」と呼ばれるインスタンス（仮想マシン）を配置することにより、データ読み込みの負荷を分散させることができます。

リードレプリカは性能の向上を目的として配置されます。

Amazon Auroraはリードレプリカでありながら自動フェイルオーバー機能を備えていることや、最大15個（ほかのRDSは5個）のAuroraレプリカを配置することができます。

**インスタンスの起動や停止の自動化**
使用頻度の低いアプリケーションや開発テスト用のデータベースなどの場合、ユースケースによってはRDBMSを常に稼働させる必要がないこともあります。

このような場合に手動でRDBMSのインスタンスの起動や停止を行うこともできますが、Amazon Auroraの「サーバーレス」オプションを利用すると、起動や停止の自動化が可能になります。
インスタンスが停止している間はインスタンスの利用料金は発生しないため、コストを低減させることができます。

今回作成アプリは、Amazon AuroraでPostgresSQLを使用しました。

#### Lambda
Lambdaとは、サーバーのプロビジョニングや管理なしにコードを実行できる、AWSが提供するサーバーレスコンピューティングサービスです。
データベースへの接続・クエリ処理や、Eメール通知、バッチ処理など、様々な用途でサーバーレスに処理を作成することが可能です。
今回のケースでいうと、RDSへの接続と各APIの処理の中身を関数として作成しています。
APIの数だけ、Lambdaも作成しております。

#### API Gateway
API Gatewayはクライアント(フロント)からのリクエストをバックエンドに渡してくれる仲介役です。
Lambda関数はAPI GatewayでAPI化し、ブラウザからリクエストが呼び出された際にLambda関数（バックエンド）が動くというったような流れです。

#### Cognito
CognitoとはWeb・モバイルアプリを対象とした認証、承認、ユーザ管理をしてくれるサービスです。
サインアップやログイン機能など、Cognitoを使用することでユーザーIDやパスワード管理がセキュアに行えます。
AWSSDKを埋め込むことで、javascriptで認証承認機能を実装することができます。

#### Amplify
AWSのサービスを用いた、Web・モバイルアプリを最速でリリースするための開発プラットフォームです。
アプリのデプロイだけであれば、S3で静的ウェブアプリケーションを公開する方法もありますが、AmplifyはCI/CD環境も自動で構築してくれます。
アプリのコード管理は、GithubやAWSのCodeCommitなどのリポジトリサービスを利用する方が多いと思います。
Amplifyはそれらリポジトリサービスと連携し、リポジトリサービスの特定ブランチにpushされると自動でアプリのデプロイまで行ってくれます。
Amplifyを利用するときは、AWS環境内で完結するため、筆者は比較的CodeCommitを多用していたのですが、今年に入りCodeCommitのサービス終了が宣言されたため、今回リポジトリサービスはGithubを利用しています。
[【参考】CodeCommitについて](https://dev.classmethod.jp/articles/aws-start-to-restrict-codecommit-and-cloudsearch/)

#### LambdaとRDSの組み合わせ
記載してきた通り、筆者は今回APIの関数にLambdaを、データベースにRDSを使用しました。
このLambdaとRDSの組み合わせ、実はアンチパターンとされていました。
簡単に説明すると、Lambdaは呼び出しのたびにコンテナが起動され、そのタイミングでデータベースへのコネクションが張られます。同時に処理すべきリクエスト数分のコネクションが張られることになります。
RDBMSは同時接続数に限界があるため、データベースへの都度接続のオーバーヘッドを回避したいのですが、コンテナ間でコネクションプール（接続状態）を共有することは難しいため、スケールアップが前提のRDSにおいて、LambdaとRDSの組み合わせはアンチパターンと言われてきました。
が、2020年頃に**RDS Proxy**なるものが登場し、このアンチパターンが解消されました。
LambdaとRDSのコネクションプールの役目をRDS Proxyが担えるようになり、コネクションを有効活用してLambdaからRDSにアクセスできるようになりました。
また、Amazon Aurora Serverless v1/v2 および Amazon RDS（MySQL/PostgreSQL）においては、**Data API**なるものが、同様の役割をになうことができます。
今回はRDSでAmazon Aurora Serverlessを利用しているため、システム構成図にもあるようにData APIを利用しています。
設定自体も、AWSのGUIでRDSの作成から簡単にできるので、サーバーレスアーキテクチャを構成する場合は、必須項目です。

# 完成したアプリ
MVPで開発したアプリの一部を掲載しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3799289/3b28881d-dac9-1b34-5725-e17635a2640e.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3799289/7c0fc37c-b232-c18b-768f-24125b1214e1.png)



# 今後について
さて、ここまでツラツラと今回作成したアプリについて記載してきましたが、あくまでMVP開発でミニマムでしか作れていません。
今後載せたい機能がいくつかあるので、記載しておきます。
- マイページのプロフィール画像を任意の写真に設定
- 本返却時に、その本の☆5段階評価とレビューを付ける
- プッシュ通知
- 自分が借りた本の履歴
- 特定ロールでログインした場合の、管理者機能
- 自分が借りた本の履歴から、AIでおすすめの本を紹介する

などなど、AWSのサービスをさらに活用しながらPWAの性質を生かしたアプリにしていく予定です。

一人での開発には限界がありますので、今後はチームメンバーと一緒に共同開発をしていけたらと思っております。

# さいごに
最後まで目を通していただきありがとうございます。
まだまだ勉強中なところもあり、至らない記事ではございますが、さらなるレベルアップを目指しアウトプットしていきます。
今後もナレッジを共有していきますので、皆様のお役に立てれば幸いです。