##1. はじめに
学習に7ヶ月、開発・公開に3ヶ月掛かったポートフォリオについてお話ししていきたいと思っております。
今回は__小説投稿アプリケーション__を製作しました。
この記事では製作したポートフォリオの中身について色々と記述をしつつ、ポートフォリオを完成させるまでに__学びになった事や自分なりに気を付けた事、それからキツかった事__などについて書かせて頂きました。
少々長くなってしまった感はありますが、どうか暖かい目で読み進んで頂けたら幸いです。
##2. 軽い自己紹介 はじめまして、都内の私立大学に通う経済学部生です。 趣味は創作、好きな食べ物は納豆ご飯です。
次へ参ります!
##3. 学習・開発期間とか学習媒体とか
- 学習時間はポートフォリオ製作含め大体2700時間〜ぐらいです。期間と媒体の内容的には以下のような感じです。
期間 | 内容 | 媒体 |
---|---|---|
1ヶ月目 | HTML/CSS/JavaScript フロントエンドでサイト作成 |
ドットインストールさん Udemyさん など |
2ヶ月目 | Ruby/Rails Railsだけでアプリ製作 この辺りからQiitaでのアウトプットなんかを開始したりしました。 |
Progateさん プロを目指す人のためのRuby入門 現場で使える Ruby on Rails 5速習実践ガイド など |
3ヶ月目 | Railsチュートリアル1週目 Webに関する学習 AWS Docker ネットワーク系 Dockerは組み込むと12月までに終わらないと思ったので省きました。 |
Ruby on Rails チュートリアルさん Webを支える技術 キタミ式イラストIT塾 基本情報技術者ゼロからわかるAmazon Web Services超入門 Amazon Web Services 基礎からのネットワーク&サーバー構築 Udemyさん など |
4ヶ月目 | React環境構築(キツかった) React基礎 Reactに関しては眺めてても頭に入って来なかったので簡単なアプリを作りつつ基礎を補完する形で学習しました。 ライブラリって実践的な事しつつ学習するのがよいですよね。 |
React公式様 Webpack公式様 ESLint公式様 Babel公式様 その他YouTubeやmedium.comなど medium.comは月額会員に入ってます。 |
5 ~ 6ヶ月目 | Rails API + Reactの学習 この辺も無理矢理アプリ作ってそこに基礎補完する形で学習しました。 フロントエンドとバックエンドは分離すると決めていたので気合は十分でした。 |
React公式様 Railsガイド様 MDN Web Docs様 その他YouTubeやmedium.com など |
7 ~ 10ヶ月 | ポートフォリオ作成開始 ポートフォリオ完成 インフラ構築はAWSの学習と同時並行で進めました |
AWS公式ドキュメント様 React公式様 Railsガイド様 MDN Web Docs様 その他YouTubeやmedium.com など |
##4. アプリケーションの内容
-
Reactアプリ側を軽く解説
- アプリケーションは全てReact.jsで書いていて、全てのコンポーネントはHooksを使った関数コンポーネントで構成しています。フロントエンドアプリからバックエンドへの通信にはyarnパッケージのaxiosを使用しています。
-
Railsアプリ側を軽く解説
-
アプリケーションはRuby on RailsをAPIモードで使用しており、ヘッダーの取得を可能にするためにgemのrack-corsを使用してCORSの設定を行っています。
- ホーム画面
- シリーズ全件の表示:1件のシリーズが複数の小説を管理するという形になっており1件のシリーズ内に、タイトル、作者、あらすじ、総お気に入り数、総コメント数、登録しているタグ(カッコ内の数字はそのタグを登録しているシリーズの総数を表しています)、__所有している小説の話数__などを描画しています。
- ページネーション機能:実際にコードを書いて実装したかったので、ライブラリなどは使わずにReact側でコードをバーっと書いて実装しました。
- 並び替え機能:コチラも実際にコードを書いて実装したかったのでライブラリは未使用です。新着順、投稿が古い順、お気に入りが多い順、お気に入りが少ない順、コメントが多い順、__コメントが少ない順__でソート可能です。並び替えした後も並び替えを保持した状態でページネート可能です。
- 小説タグクラウド機能
- 小説タグクラウド:現在存在するタグを一覧で表示しています。
- 小説タグ1件:小説タグ1件をクリックすると、そのタグを登録しているシリーズを一覧で表示します。また、タグを登録しているシリーズだけで並び替えすることも可能です(ページネーションも可能です)
- シリーズ管理画面 & 小説1件表示画面
- シリーズ1件の表示:1件のシリーズに加え、このシリーズが持つ小説を全件表示させています。シリーズは作者だけが編集できるようになっています。
- 小説1件の表示:ここでは__シリーズタイトル__、小説1話分のタイトル、作者、前書き、本文の内容、__現在何話目か__などを表示させています。また、作者だけが編集可能になっています。
- お気に入り機能:お気に入りできます。数字部分のクリックでお気に入りしたユーザーがその場で描画されます。
- コメント機能:矢印をクリックするとコメントフォームが表示されるので、そのままコメントの送信が可能です。
- ページネーション機能:「前話」で1話前へ、「次話」で1話先に遷移します。「目次」をクリックすればシリーズ管理画面へ遷移します。
- 小説の作成
- シリーズ作成:タイトル(必須)、あらすじ、登録したいタグ、__公開するか非公開かの決定__を入力できます。
-
シリーズへの小説の追加:タイトル(必須)、前書き、本文(必須
)、__公開するか非公開かの決定__を入力できます。 - 公開・非公開の有無の確認:自身の作品については、__【公開】もしくは【非公開】ステータスが表示される__ので、自身の作品が公開されているかどうかは一目で確認する事ができます。
- ユーザープロフィールページ
- ユーザー情報の表示:ニックネーム、紹介文、__ユーザータグ(ユーザーの趣味をタグに登録できるようにしたもの)__が表示されます。自身のページの場合にのみ編集が可能です。
- ユーザーが投稿した小説の表示:ユーザーが投稿した小説を一覧で表示されるようにしています。(ページネーション可能)
- ユーザーがお気に入りにした小説の表示:ユーザーがお気に入りにした小説を一覧で表示されるようにしています。(ページネーション可能)
- このページ内の投稿作品部分のタブ切り替えですが、ここはmaterial-uiのTabsを使用して実装しました。
- 趣味タグクラウド機能
- 趣味タグクラウド:現在存在する趣味タグを一覧で表示します。趣味タグはユーザー自身に登録できるタグで、趣味や好みのジャンルなどをタグに登録することで、趣味や好みが近しいユーザーをすぐに発見することができます。
- 趣味タグ1件:趣味タグ1件をクリックすると、その趣味タグを登録しているユーザーを一覧で表示します。
- ユーザー情報1件:ユーザー名、フォロー・フォロワー、プロフィール、__登録している趣味タグ__を表示するようにしています。
- フォロー機能
- フォロー:自分以外のユーザーであればフォローすることが可能です。
- フォローユーザーページ:あるユーザーがフォローしているユーザーを一覧で表示します。
- フォロワーページ:あるユーザーをフォローしているユーザーを一覧で表示します。
- 認証系
- 簡単ログイン
- 新規登録
- ログイン
- ログアウト
- Set-Cookieでログイン保持
- ユーザーの認証にはセッションを使用しています。ログイン保持にはセッションストレージのActionDispatch::Session::CookieStoreを使用して、ブラウザ側にCookieのハッシュ値を保存する形を取っています。
##5. 今回使用した技術
フロントエンドアプリケーションとバックエンドアプリケーションは分離して開発しています。
-
フロントエンド
-
HTML/CSS
-
React.js(16.13.1)
-
インフラ(フロントエンド)
-
AWS(S3, IAM, CloudFront, ACM, Route53, CodePipeline, CodeBuild, CodeDeploy)
-
バックエンド
-
Ruby on Rails(APIモード)(5.2.4.2)
-
インフラ(バックエンド)
-
Amazon Linux 2
-
Nginx/Unicorn
-
PostgreSQL(12.2)
-
AWS(VPC, EIP, IAM, EC2, ALB, ACM, Route53, RDS, CodePipeline, CodeDeploy, S3)
-
テスト
-
Request Specs, Model Specs(total 238 examples)
-
コード管理
-
GitHub
-
Sourcetree
-
デバッギングツール
-
Insomnia
##6. インフラ
- インフラ構成図
-
フロントエンド側のインフラを軽く解説
- インフラではS3をストレージとしてCloudFront経由で公開しており、Route53でのDNSとACMでのSSL化をしています。加えてCodePipelineをパイプ役としてCodeBuildでアプリをビルド、CodeDeployでデプロイを自動化しています。
-
バックエンド側を軽く解説
-
インフラではEC2にELB(ALB)をアタッチし、このELBにACMを関連付けてSSL化を行っています。ELBはHTTPSをリッスンし、EC2はHTTPをリッスンしている形です。Route53でのDNSも行っています。そして CodePipelineをパイプ役とし、CodeDeployによりデプロイを自動化しており、この自動デプロイと同時にCodeDeploy内に記述したシェルスクリプトがProduction用のサーバーやDBなどを立ち上げます。
-
トータル課金額について
- トータルで日本円1000円ぐらいでした。内訳は以下のような感じです。半月でこの金額ですので1ヶ月あたり2000円前後かなと思います。
- RDSやELBの金額が短期間でこれぐらいになっているのは、12/1、12/2に動作確認のために何も考えずに多くのデータ転送をし尽くしたためです。しっかり制御すればもう少し抑えられるかと思います。現に12/3はそこまでのデータ転送を行っていないので12/1にかかった金額の1/3程度です。
- RDS、ELB、EIPはEC2を停止していても課金されるので注意。
サービス | 利用期間 | 課金単位 | 私自身の課金学 |
---|---|---|---|
EC2(EIP含む) | 11/19 ~ 12/3 | 使用時間1秒毎 その他 →Amazon EC2の料金 |
4.01$ |
RDS | 11/30 ~ 12/3 | 1GB転送/月 その他 →Amazon RDSの料金 |
2.07$ |
Route 53 | 12/1 ~ 12/3 | 25ホストゾーン/月 ドメイン名管理1件 主に従量課金制 →Amazon Route 53の料金 |
2.00$ |
ELB | 12/1 ~ 12/3 | ELBの実行/時間 その他 →Elastic Load Balancerの料金 |
1.58$ |
CloudFront | 12/2 ~ 12/3 | データ転送量(GB毎)/月 HTTPリクエスト数(1万件毎)/月 その他 →Amazon CloudFrontの料金 |
0.04$ |
S3 | 11/30 ~ 12/3 | →Amazon S3の料金 | 0$ |
CodePipeline | 11/30 ~ 12/3 | →AWS CodePipelineの料金 | 0$ |
CodeBuild | 11/30 ~ 12/3 | →CodeBuildの料金 | 0$ |
CodeDeploy | 11/30 ~ 12/3 | →CodeDeployの料金 | 0$ |
ACM | 11/30 ~ 12/3 | SSL/TLS証明書発行は無料 | 0$ |
税金 | 11/30 ~ 12/3 | 0.96$ | |
トータル | 11/19 ~ 12/3 | 10.66$ |
##7. ER図
ER図作成にはお馴染みの[draw.io](https://app.diagrams.net/)を使用しました。
テーブル | 役割 |
---|---|
User | ユーザーのデータが格納されます |
UserTag | ユーザーに対して登録するタグ名データが格納されます |
UserTagMap | UserのIDとUserTagのIDを揃えて格納します |
Relationship | UserのIDともう一件のUserのIDを揃えて格納されます |
NovelSeries | 1件のシリーズのデータが格納されます |
Novel | 1件の小説のデータが格納されます |
NovelTag | 小説に対して登録するタグ名データが格納されます |
NovelTagMap | NovelTagのIDとNovelSeriesのIDを揃えて格納します |
NovelFavorite | NovelのIDとUserのIDを揃えて格納します =ユーザーの小説に対するお気に入り |
Comment | UserのIDとNovelのIDとコメントの内容を揃えて格納します |
##8. 何故小説投稿サービスにしようと思ったのか
1. 自分が利用するサービス対する疑問・興味を自己解決したかったため。
創作サイトを結構長く利用させて頂いているのですが、長くサービスを利用していると、愛着が湧く反面、恐れ多くも、ここってこうした方がいいかも?みたいな疑問が生まれたり、プログラミングを学習し始めた事で、ここの挙動どうなってんの??どんな技術使ってるの??とか、ここは何でこんなにレスポンスが速いんだ?などの興味も生まれました。
そんな感じで、__自分で作って確かめてみよう__と思い、小説投稿サービスにしようと思いました。
## 8. Reactアプリケーション側で気を付けた・頑張ったポイント
気を付けた、また頑張ったポイントとしては主に以下の3つです。
- 同じコードの記述を避けること
- 見やすいUI・使いやすいUXを意識したこと
- なるべくライブラリは使わずに、自身で理解してコードを書くこと
- 同じコードの記述を避けること
- 当然のことではありますが、将来的に機能を追加すること、大幅な修正などすることを考慮し、同じコードの記述を避けるよう努めました。ポートフォリオの機能実装フェーズでは、ここに最も多くの時間を投下したと思います。
- 具体的な例で挙げると、同じUIを描画する際には、__1つのフォーマットとなるコンポーネントに対して任意のプロパティを渡すだけで済むようにしました。__こうすることで、新たにコンポーネントを追加、修正の際に書くコードや量や時間などが大幅に減少しました。
- 見やすいUI・使いやすいUXを意識したこと
- 見やすいUIに関しては、小説投稿サイトということで文字を読むのが主となるサービスですので、基本的な配色を2色にしてその中でも目に優しい(とされる)白と緑を使ってシンプルなものにしました。文字の色もグレーに近い黒を使ったのと、要素の大きさなどもなるべく余裕を持たせるようにしました。緑を選んだのは僕の好みだからというのもあります。
- 使いやすいUXに関しては、1つのページに含まれる情報量はなるべく簡潔にするようにして、ユーザーが辿り着きたい情報までの導線を明確にするように気を付けました。
- なるべくライブラリは使わずに、自身で理解してコードを書くこと
- これに関しては、実際の現場ではライブラリを使うというお話しを聞くのでライブラリを実際に使った方が良かったかなと製作中に思ったのですが、自分でコードを書きたいという気持ちが勝ってしまったのと、自分でコードを書いてプログラミングに対する理解を深めたかったのでこのようにしました。個人開発ですしイイかなって。
## 9. Reactアプリケーション側で苦戦したこと
苦戦したこととしては主に以下の1つです。
- アンマウントされたコンポーネントのステート変更
- アンマウントされたコンポーネントで管理しているステートに変更を加えるというのは御法度なのですが、僕はこれに気付かずに実際に実装したい機能を実装するのに割と手間をかけてしまいました。
Reactの初歩的なルールなのに見方が変わるだけでスグに見失ってしまうということは理解できていない証拠なので、今回のポートフォリオ製作を機に戒めて反省します…。
## 10. Railsアプリケーション側で気を付けた・頑張ったポイント
気を付けた、また頑張ったポイントとしては主に以下の1つです。
- 同じコードの記述を避け、高速でレスポンスを返すこと
- 具体的な例で挙げると、このアプリケーションはホームページで取得するデータの量が一番多いのですが、そこでReactからのHTTPリクエストに対し、データを取得する際の__ActiveRecordへのリクエスト回数を減らすよう努め、ControllerのAction内に記述するコードを最低1行まで減らすことができ__、機能の追加、修正が楽になりました。
- そしてレスポンスをなるべく速くすることも目標にしており、これぐらいのサービスの規模なら300~500msが個人的には許容範囲なのかなと思っていて限界でも500ms以内にはしたかったのですが、リクエストの送信からレスポンス、そしてUIとして描画されるまでを__200ms以内に抑えることができました__。__ここは実現できてとても嬉しかった__ポイントです。(動的コンテンツですのでキャッシュは無効)
## 11. Railsアプリケーション側で苦戦したこと
苦戦したこととしては主に以下の1つです。
-
データを全件取得する必要のある一覧画面で、1度のリクエストで全てのデータを取得すること
- 従来の設計
開発を始めた当初は、ホーム画面でシリーズ全件のデータを取得する際、シリーズのデータを全件取得
→シリーズ全件の総数の取得
→それぞれのシリーズが持つ小説の総数データの取得
→それぞれのシリーズのお気に入り総数データの取得
→それぞれのシリーズのコメント総数データの取得
→それぞれのシリーズが所有するタグデータの取得
それぞれを別々のリクエストで取得しようとしていました。つまり最低でも6回はReactからリクエストを送信しようとしていたわけです。
そして一度試しに、シリーズのデータと小説の総数データの2つだけでデータを取得しようとしてみました。すると取得するのに数千ms
もかかってしまいました。
これはダメだ!ということで設計を変更することにしました。
* 変更後の設計
1度のリクエストで、`シリーズデータ全件`、`シリーズ全件の総数`、`それぞれのシリーズが持つ小説の総数データの取得`、`それぞれのシリーズのお気に入り総数データの取得`、`それぞれのシリーズのコメント総数データの取得`、`それぞれのシリーズが所有するタグデータの取得`を取得する方針に決めました。
しかし1度に取得する方法が中々簡単には思い浮かばなかったので、とりあえずここは飛ばして開発を進めつつ思いついたら実装しようという方向で進める事にしました。
* ヒラメキ
上記の設計に変更してから2週間ぐらい経ってからだったしょうか、何故だかはわからないのですが、ある時ふとその方法が頭に浮かんできました。これに関しては調べるという発想が何故か頭になくて、自身のヒラメキのみを頼りにしていたのですがそのヒラメキが期待に応えてくれたようです。
今になって思うのは何故調べなかったのかという事です。エラー以外の事については確かに調べないでなるべく考えるようにはしていますが、これに関しては3日考えた時点で調べた方がよかったでしょうと今になって思います。 でも思い付いた時は本当に嬉しかったです。
最終的に生成されるJSONデータは以下のようになりました。
![スクリーンショット 2020-12-04 23.42.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606750/f18a7f4a-d015-8911-9c79-cf0fe48fba3b.png)
## 12. 選ばれたのはインフラでした
###本当にキツかった部門第1位に選ばれたインフラについてのお話しをしていきます。
- じゃあ具体的に何がキツかったか
Chromeバージョン80から正式に導入されることとなったSameSiteという属性によるCookieの仕様の変更により、SSL化していないアプリケーションではデフォルトでCSRF対策が施され、Cookieの保持が不可能になってしまったこと。つまり、フロントエンドアプリケーションと、バックエンドアプリケーションの両方にSSL化を施さなければログイン保持を行わせることができなくなってしまった
ことです。
もう少し前の段階であれば、SSL化をせずとも、ローカル環境でやっていたようにCORSやWebサーバーに対して対してオリジン許可設定をするだけで通信が可能だったのだと思いますが、7/14以降、本格的にSameSiteが導入されることとなったため、Cookieの保持を必要とするアプリケーションを作成する場合には双方のSSL化が必須
となりました。
キツかった思い出からこんな書き方をしていますが、SameSiteはセキュリティの構築にとって非常にありがたい存在です。
僕の場合はフロントエンドはSSL化しつつも、バックエンドはSSL化しない(ELBを導入するつもりがなかったので)という今思えばセキュリティ的にどうなの的な設計をしていたのですが、そうは言いつつも既にどちらのアプリもデプロイまで終了していたので、よっしゃもう終わるぞと思いつつ、本番環境のアプリケーション動作チェックのため満を辞してログイン機能を使用しました。
すると、ログイン自体は上手く行くのですが、肝心のSet-Cookieが上手く処理さずログイン保持が出来ていなかった事に気が付きました。
やばい何これどういうことやと思いつつググってみるとこのSameSiteの情報に行き着きました。SameSiteの記事を読んだ時に、完走の一歩手前だった事もあり、一気に顔面から血の気が引いてく心地がして、奈落の底に突き落とされた気分でした。正直、プログラミング学習をしてきた9ヶ月の間で最もキツかったです精神的に
。
結果的には解決出来ましたし、SamiSiteという属性が存在している事や、どういう物なのかについても多少であれ知ることが出来たので最終的にはプラスだったと思っています。
でも、初めからちゃんとSPAを製作する際のSSL化はどうすればいいかみたいな文言でググっていれば、設計段階でSameSiteの存在に気付けていたかもしれないわけですのでその部分の後悔はあります。
SameSite導入について
July 14, 2020: SameSite cookie enforcement has resumed, with a gradual rollout starting today (July 14) and ramping up over the next several weeks as we continue to monitor overall ecosystem readiness and engage with websites and services to ensure they are prepared for the SameSite labeling policy. The SameSite features are being enabled for Chrome Stable channel users on versions 80 and 81 (who should update Chrome!), 83, as well as the newly released 84.
The Chromium Projectsより
## 13. ポートフォリオに組み込めなかったこと・またできなかったこと
組み込めなかったこと・できなかったこと一覧
- ダイレクトメッセージ機能などユーザー同士のコミュニケーションツール実装
- 認証機能のメール認証
- フォローしているユーザーの創作投稿に対する通知機能
- 小説インプレッション数カウント機能
- 小説のランキング機能
- サードパーティAPIとの連携
- Dockerによる環境構築
- 継続的インテグレーションの実装
- GitHubの有効活用
14. 次のステップ
次に学習すべき・もしくは学習したいこと一覧
- SQL(すべき)
- Web(すべき)
- Rust(したい)
- Java(したい)
- Go(したい)
- Vue.js(したい)
- TypeScript(したい)
もちろん今やっている学習を継続した上で取り組みたいと思っています。
## 15. 最後に
-
ポートフォリオ製作を完走した感想
-
先ずは無事に完成させられたことにホッとしました。友人にも試しに使用してもらったのですが、
普通のサービスとしてリリースできるレベルというお墨付きを頂いたので大きな達成感を得る事ができました
。 -
次に、このポートフォリオ製作の中で
エラー対処
やデバッグ
のレベルが少しは上がったかなという風に思いました。
エラー対処については、自身の起こしてしまったエラー内容に該当する情報だけならば無数にあると思いますが、自身が今まさに陥っているエラーとは異なる状況・環境である事を考慮すべきなのは大前提として、エラーが起きるということは言わずもがな多少知ってはいるものの知識の足りなさから引き起こされるミスか、全く知らない知識である可能性が高い
です。
ですのでエラーが起きた場合には、知らない事を知れるチャンスだと思ってそのエラーが何を示しているのか多少言語化できるレベルまで落とし込むと、次回エラーに直面した際に大いに役に立つので長期的に見てプラスだなという風に感じました。 -
最後に、目的としていた事が実現できた直後は、確かに諸手を挙げる程に嬉しかったですが、少し時間が経つとあれぐらいで喜んでるようじゃダメだという気持ちになるんですよね。
そして何より自身の知識の足りなさを本当に実感させられました。かなり悔いも残っていますので、早速次の学習を始めて行きたいと思います。
以上です!長く拙い文章で大変お見苦しかったかとは思いますが、最後まで読んでくださりありがとうございました!!🙇♂️