【個人開発】Amazon S3に格納した音楽だけを再生するマニアックな音楽プレイヤーを、React + Typescript + AWS Amplify で作った話

## はじめに

最新の技術動向のキャッチアップを目的に、Amazon S3に格納した音楽だけを再生するWebサービス[Bucketify](https://www.bucketify.net/)を作成しました。
ある程度形になったので、「多分動くと思うからリリースしようぜ」の精神で公開してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/9d845e90-5454-168f-4ca8-63f2b90f91a6.png)


筆者は普段は事業会社で社内SEとして、社内利用の業務システムの開発/保守をしており、
フロントエンドの技術やtoC向けの開発と縁遠いところから、個人開発でWebサービスを作るにあたり色々とあった挫折やチャレンジ、得たものなどについて本記事にまとめました。
同じような境遇でこれから個人開発をする方の手助けになれば幸いです。

## 作ったもの
Amazon S3に格納した音楽を再生するWebサービス[Bucketify](https://www.bucketify.net/)です。
ユーザが管理するS3バケット名と、認証情報(アクセスキーとシークレットアクセスキー)をBucketifyに入力し、バケットのスキャンを行うと、Bucketifyが音楽ファイルの「メタデータ」と認証情報を管理し、音楽を再生できるようになります。

https://www.bucketify.net/

### アーキテクチャ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/441201e6-86a2-9060-18de-4cbe201e38f4.png)
Amplifyで全体を管理し、認証はCognito、DBはDynamodbを使用したサーバレス構成です。
ソースコードは[GitHub](https://github.com/joe-king-sh/bucketify)に公開しているので興味があればご覧ください。

### 機能
 - 自動生成されるライブラリ
    - トラック別、アーティスト別でライブラリを自動生成します。
    - 対応している音声ファイルの拡張子は'mp3'、'm4a'です。
    
 - ストリーミング再生
    - S3バケットに保管した音楽ファイルをストリーミング再生できます。
    - スマートフォンでバックグラウンド再生にも対応しています。

### こだわり
 - ダーク/ライトモード選択、多言語選択機能
 ![bucketify-image.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/f6c29214-374f-5034-4c9c-58ed59838be5.gif)
 - オープンソース
   - 仕組み上、ユーザからAWSのAccessKey,SecretAccessKeyを提供してもらう必要があります。
   - 得体の知れないアプリにそれを預けるのは怖くて自分だったら使いたくないと思い、また、Amazon S3に音楽を保存するようなエンジニアがターゲットなので、ソースコードを公開しています。

## 使用技術など
### フロントエンド
フロントは「React + Typescript」で作っています。

#### Reactを選んだ理由
個人開発なのでインフラの運用コストを下げたく、Webサーバを立てずにSPA(ReactかVue)をCloudFrontで配信しようと考えており、選定にあたり、以下の記事が参考になりました。

 - [ReactとVueのどちらを選ぶか](https://qiita.com/yoichiwo7/items/236b6535695ea67b4fbe)

1. Javascript自体あまり1からガリガリと書いたことがなく、[原理的なところ](https://qiita.com/yoichiwo7/items/236b6535695ea67b4fbe#%E5%8E%9F%E7%90%86%E4%B8%BB%E7%BE%A9%E7%9A%84%E3%81%A7%E6%98%8E%E7%A2%BA%E3%81%AA%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%81%AEreact)からおさえたかったこと
1. Typescriptとの相性がよく、Typescriptが書けるようになりたかったこと
1. React Nativeでモバイル開発もできること  

以上の理由からReact + Typescriptで作ることを決めました。

#### 状態管理
Reduxは入れず、ReactのHooksのみを使用しました。

Stateを下のコンポーネントに対してProps渡しする地獄を回避するため、当初はReduxを使用しようとしていましたが、
ファイル数が多くなり、やろうとすることに対して複雑性だけが増している印象を受けました。

そうこうしているうちにReactのHooksが台頭してきて、
コンポーネントを跨ぐ状態管理に「createContext/useContext」のHooksを使ってみたところ、
小規模なアプリでは、それだけで十分だと感じました。

#### デザイン
##### UIフレームワーク  
[MATERIAL-UI](https://material-ui.com/)を使用しています。

非常に多くのコンポーネントが用意されており、必要なコンポーネントを組み合わせて実装していくことで、比較的少ない工数でモダンチックな画面の構築ができたと思います。

コンポーネント同士の依存関係の管理にはAtomicDesignを参考にしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/e5b07c02-607a-f173-5878-d90099103f66.png)
ただし、実装していてAtomsとMoleculesを細かく明確に分けていくのが途中から面倒になり、上記のように二つは同じ粒度・階層で管理をするようになりました。。

この程度の個人開発アプリであれば、それでも特に問題なかった印象ですが、プロジェクトが大きくなるほど、AtomicDesignのコンポーネントの再利用性が求められてくると感じ、SPAのプロジェクト構成管理のベストプラクティス/ノウハウについてもっと身につけたいなと感じました。

##### ロゴの作成
[hachful](https://hatchful.shopify.com/ja/)というサービスを使用しました。
シンプルなものが多いですが、無料で商用利用できるロゴを簡単に作成可能です。

#### 学習方法
ES6のJavascriptは書いたことがなく、ReactもTypescriptも触ったことがなかったので、まずは以下で学習を進めました。

- [JavaScript 「再」入門](https://developer.mozilla.org/ja/docs/Web/JavaScript/A_re-introduction_to_JavaScript)を読む
- [React公式チュートリアル](https://ja.reactjs.org/tutorial/tutorial.html)の実施
    - ⭕️❌ゲームを作るチュートリアルで、JSXや状態管理の概念を学べます
- 公式チュートリアルに加えて、有料ですがProgateのトレーニングも実施しました
    - [Javascript](https://prog-8.com/courses/es6)
    - [React](https://prog-8.com/courses/react)
- [freeCodeCamp](https://www.freecodecamp.org/learn/front-end-libraries/#react)
    - 英語が問題なければこちらは無料なのでおすすめです。

### バックエンド
#### Dynamodb
データストアにはDynamodbを使用し、Dynamodbへのデータ取得にはGraphQL(AppSync)を使用しています。

技術選定には以下の記事が大変参考になりました  

 - [個人開発・スタートアップで採用すべき最強のアーキテクチャを考えた](https://qiita.com/yuno_miyako/items/fad33456d9c32d8f4483)

個人開発で作成したアプリで利用者がまだいない状態でも、継続的にサーバの利用料金の固定費を払いたくなく、
サーバレス構成で構築し、データストアにはDynamodbをオンデマンドモードで使用しています。

#### AWS Amplify

Amplifyで以下を管理しています。
 - Webサイトのホスティング
 - CI/CD
 - ログイン認証(Cognito)

今回の開発で一番驚きだったのは、Amplifyでした。
もともとモバイルバックエンドのイメージしかありませんでしたが、SPAのバックエンドに使えると知り、こちらでバックエンドを管理したところ、フレームワーク側で用意している機能が充実しており大幅に自前で作るものが減りました。

Cognito連携でAmplify用のReactコンポーネントが用意されているため、ログイン関連画面は一切作る必要がなく、
CI/CDやサイトのホストまで、Amplify CLIで一撃でした。

#### 学習方法
##### Dynamodb設計
DynamodbのDB設計には、NoSQL最適の設計をしたことがなかったため非常に苦労しましたが、以下が大変参考になりました。
特に、GSIオーバーローディングの考えはRDBには無く、目から鱗でした。

- [0190905_イチから理解するサーバーレスアプリ開発-サーバーレスアプリケーション向きのDB](https://pages.awscloud.com/rs/112-TZM-766/images/20190905_%E3%82%A4%E3%83%81%E3%81%8B%E3%82%89%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B%E3%82%B5%E3%83%BC%E3%83%8F%E3%82%99%E3%83%BC%E3%83%AC%E3%82%B9%E3%82%A2%E3%83%95%E3%82%9A%E3%83%AA%E9%96%8B%E7%99%BA-%E3%82%B5%E3%83%BC%E3%83%8F%E3%82%99%E3%83%BC%E3%83%AC%E3%82%B9%E3%82%A2%E3%83%95%E3%82%9A%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E5%90%91%E3%81%8D%E3%81%AEDB%20%E8%A8%AD%E8%A8%88%E3%83%98%E3%82%99%E3%82%B9%E3%83%88%E3%83%95%E3%82%9A%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9.pdf)

上記を参考に以下を一つずつ整理していきました。

1. ユースケースの洗い出し
1. 必要なテーブルの項目とインデックスを定義
1. 定義したテーブルとインデックスで、ユースケースを満たす問い合わせができるかを検証

結果として今回はGSIオーバーローディングを採用しましたが、データを縦持ちすることで必要データを取得するために問い合わせの数も増えました。
GSIを複数作成した場合のストレージなどのコストと、GSIをオーバーローディングする事で増える、オンデマンドのWCU、RCUのコストはトレードオフの関係にあり、
格納するデータの項目数や１レコードのサイズ、アクセスパターンなどから、適切に選択していく必要があると感じました。

##### AWS Amplify

以下の公式チュートリアルでサンプルのアプリやGitHubからのCI/CDを作るところから始めました。

 - [Getting started - Amplify Docs](https://docs.amplify.aws/start)
 - [ReactJS アプリケーションをデプロイしてホストします](https://aws.amazon.com/jp/getting-started/hands-on/deploy-react-app-cicd-amplify/)

Dynamodb + GraphQLのAPI追加、認証機能の追加、デプロイ、と順を追ってサンプルアプリを作る構成で、
Amplifyで何ができるのかというところを理解できます。


##### GraphQL

GraphQLとは何なのかを色々と調べた後、順番としてはAmplifyチュートリアルを先にやり、その中で初めてGraphQLを触りました。
スキーマを定義すると、AmplifyがGraphQLの生成までしてくれるため、１から作らなくていい分なかなか理解が進みませんでした。

そこで一旦AppSync(AWSのGraphQLのマネージドサービス)単体で、公式の以下のドキュメントなどを参考に、画面でAppSyncコンソールからクエリを投げてみたりして、GraphQL、スキーマ、リゾルバ、データストアなどの関係を掴みました。

 - [AWS AppSync クイックスタート](https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/quickstart.html)
 - [AWS AppSync チュートリアル Dynamodb リゾルバー](https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/tutorial-dynamodb-resolvers.html)


### プロジェクト管理

[Notion](https://www.notion.so/)でカンバンボードを利用して管理しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/02fe2b66-538c-7bfe-c88b-a3ffdf40fc31.png)

特にリリースの期日も決めていなかったので、タスクだけ思いつくままに上げ出していき、ひとつずつタスクずつ消化していきました。

仕事やプライベートが忙しくなりしばらく期間が空くと、何をどこまでやっていたかわからなくなり、そのままお蔵入りすることもあったので、
個人開発でも、最低限のタスク管理だけは入れて正解でした。

### その他使用したライブラリ
- [react-router-dom](https://www.npmjs.com/package/react-router-dom)
    - ルーティングで使用
- [music-metadata-browser](https://www.npmjs.com/package/music-metadata-browser)
    - 音楽ファイルからメタデータの抽出に使用
- [react-scroll-parallax](https://www.npmjs.com/package/react-scroll-parallax)
    - parallax(スクロールの視差エフェクト)で使用
- [react-infinite-scroller](https://www.npmjs.com/package/react-infinite-scroller)
    - トラック画面の無限スクロールに使用
- [react-spring](https://react-spring.io/)
    - アーキテクチャの画像をonhoverで傾ける効果に使用
- [react-share](https://www.npmjs.com/package/react-share)
    - SNSシェアリンクを簡単に設置できます
- [react-i18next](https://react.i18next.com/)
    - 多言語対応をjsonで定義した翻訳ファイルをもとに簡単に実装できます。

## 終わりに
これまで使用したことのない技術ばかりだったので、初めはなかなかスピードが出ませんでしたが、
一通り作り終えてみると、ReactがTypescriptでなんとなく書けるようになり、
GraphQLやDynamodb設計の考え方が身につき、大変勉強になりました。

今後の課題としては、今回の開発ではテストを全く書かなかったため、
次作るものはテストを書いて、個人開発でも継続的に改善できるアプリケーションをテーマに開発していきたいです。

最後まで読んでいいただきありがとうございました。
この記事が、これから何かを作ろうとする方の後押しになると幸いです。