日本語版Redditを作ってみた

## きっかけ

私はこれまでRedditを使いエンジニアリングや投資に関しての情報を得ていたが、主に米国で起きていることしか議論の対象になっていない。。日本語で”色んな人による議論”が起きている場所といえば5chだが、まとまってなく中傷的な発言が目立つ。Quoraは質問オンリー、Noteやブログは一方向の発進媒体で双方向ディスカッションができない。Twitterはタイムラインが流れるので過去の情報にアクセスしづらい。**日本語でも投票制かつ多数の人が双方向コミュニケーションに参加でき、人々のマインドをコミュニティを通じ繋げれば良いなと思った。**

## TL;DR

リリース版が[キドク][22]です。なんでも質問受け付けてます。

[https://kidok.app][22]

## 要件&スタック

#### スタックを選ぶ前にいくつかの要件の洗出しと仮説を立ててみた

- コミュニティーを作ること、投稿できること、良いコメントを上位にあげること、中傷的なコメントを排除できることなど基本機能を実装する。
- リリースまでのスピードを早くする。リリース後のバグ修正や改善などを早く行えるプロセスにする。
- コードに対しテストを書く。動かない機能をたくさん作るより、必要で確実に動く機能をリリースする。
- コードが増えるとメンテナンスも増えるのでSaaSで置き換えられるところはお金で買う。
- コメント数が飛び上がる、アクセスが増えるなど、負荷がかかってもスケールができ、可用性を維持する。
- クエリが今後変わるのでフレキシブルなモデルにする。クエリの柔軟性も重視する。
- システムの状態の監視は常に行う。
- 固定費を抑える。ユーザーが増えればコストが上がっても問題なし。
- セキュリティやデータロスが行われないよう重視する。
- DBやフロントエンドなどデザインは後から変えれる。

#### フロントエンド

フロントエンドは主にReactJS + TypeScript。[Create React App][13]のTypeScriptテンプレートを使い、`eject`するとwebpackのメンテナンスが増えるのでそれをしないように頑張る。
VueやAngularも使えたがReactJSが一番慣れているだけで、早く作れるのが分かっていたので選んだ。
TypeScriptを選んだ理由はランタイムエラーを防止するためでモデルを定義できるから。モデルが今後変わっていくので、その度にビルドでエラーを吐いて欲しい。（もちろんTypeScriptで`any`などは使わない。）


その他使って良かったライブラリー

- [**react-query**][15] : 非同期処理（APIコールなど）をラップするHookで、キャッシュやステート管理を簡易に行ってくれる。
- [**zustand**][14] : これはReduxをHook化したglobal store。Redux + sagaを使うよりコードがかなり短縮され（かつテストも書きやすい）、`immmerjs`が内部で使われるのでobjectはimmutableにされる。
- [**react-testing-library**][16] : enzymeはハッキーな回避策が多すぎて苦痛した。`react-testing-library`はユーザーイベントなど問題なく動くのでスムーズにテストが書ける。


#### データベース

データベースはMySQL/PostgreSQL、[DynamoDB][1] や[FireStore][2]などが使えたがMySQL/PostgreSQLは自ら立ち上げな上、データマイグレーションやスクリプトの管理が必要なので選ばなかった。[AWS Aurora][3]（SQLのクラウドマネージ版）で立ち上げてコストを抑えたり、[AWS]によってスケールを管理してもらうこともできるが、コラムのIndexの追加など時間かかるので現時点では見送りする。[DynamoDB][1]はクエリの柔軟性が無いので、[FireStore][2]を選んだ。

| DB | セキュリティ | ACID | 可用性 | スケール |
|--|--|--|--|--|--|
| MySQL系 | サーバーが必要でパスワード/VPCの設定が必要。 | ACID | 自己責任 | 自らShardingなどが必要。(Auroraだと自動） |
| DynamoDB |  サーバーが必要でパスワード/VPCの設定が必要。 | CD | APIの可用性は自己責任。DynamoDBは[99.99%のSLA](https://aws.amazon.com/jp/dynamodb/sla/?nc1=h_ls)が適用される。 | Capacity UnitsをUIで増やせばスケール可能。 | 
| Firebase | Firebase Auth + Firestore Securityで認証や権限などの設定が可能。 | ACID | [99.95%でGoogleが対応](https://firebase.google.com/terms/service-level-agreement) | 自動でスケール |

| DB | クエリの柔軟性 | モデルの柔軟性 | コスト |
|--|--|--|--|--|--|
| MySQL系 | インデックスを追加すれば柔軟に対応。インデックスのスケール(write)は困難。 | 毎回スクリプトを作りその管理が必要。 | ユーザーが少なくても固定費がかかる。ユーザーが増えれば安くなる。 |
| DynamoDB | PKとSKのみ。複数のコラムでイコールフィルタするのも不可。 | NoSQLなのでPK/SK以外は簡単に変えれる。 | データのRead/Writeで課金。安い。| 
| Firebase | クエリは柔軟で | NoSQLなので柔軟。データモデルの変更はアプリ側で対応が必要。 | 無料枠は大きいが、それ以降はコスパ高め。データのRead/Writeで課金。 | 



Firestoreの欠点は二つ:

1. 範囲operatorを二つのコラムに対してクエリを実行できない。（[ドキュメント][6]には`複合クエリにおいて、範囲（<、<=、>、>=）と不等値（!=、not-in）の比較は、すべて同一のフィールドに対するフィルタである必要がありますある。`と書いてある) 例えば、`.where('createdAt', '>=', oneWeekAgo).where('upvote', '>', 50)`は使用不可。この場合は、`.where('createdAt', '>=', oneWeekAgo).where('upvoteTier', 'in', ['high', 'med'])`と指定する必要があり、サーバー側で`upvoteTier`を定期的にインデックスする必要がある。
2. [Writeの制限が１ドキュメント１write/秒がsoft limit][7]なので、ユーザーアクションが増えるとFirestoreから移行する必要がある。

#### サーバー

サーバーは[Digital Ocean](https://www.digitalocean.com/)を使う。FireStore DBを使うのでAPIの管理は必要なく、サーバー側アプリはCron系のプロセスとJSバンドルを配信するぐらい。Herokuなども検討したが使用量の予測ができないので固定費が安いDOにした。ドメイン、SSL、dockerをセットアップして設定完了。

スケールが必要になると、[CloudFlare](https://www.cloudflare.com/ja-jp/hp/)を挟んでグローバルキャッシュ対応とDDOS対策が完了。[Rate limit][5]などで荒らしユーザーの対処法にもなる。

#### サーバーアプリ

フロントエンドのバンドルをサーバーから配信するのみなのでDockerで実行できる。

サーバーアプリはCron系のプロセスを回していて、主に`upvote`などのデータのインデックスを行うためである。
このアプリはGo言語で作成する。




#### 認証

認証は[Firebase Authentication][11]や[Auth0][10]などを検討した。

|  | コスト | 第三者ログイン | ロックインリスク | メンテナンス |
|--|--|--|--|--|
| Firebase Auth | 無料 | 対応 | 認証データ以外export可能  | 無し | 
| Auth0 | ユーザー数に比例し、かなり高い | 対応 | 認証データ以外export可能 | 無し | 
| 自作 | データベースとAPIが必要 | 全て自分でセットアップ | 無し | バグや機能など開発が必要 | 

メンテナンスの方がコストが高いので自前で作るのは辞め、Firebase Authenticationを選んだ。ロックインリスクがあるが徐々にユーザーを移行すれば回避できるので、あまり問題視していない。



#### デザイン

デザインはミニマムの[これ][12]を使っている。tailwind、bootstrap、material-uiなども選択できたが、CSSとHTMLをいじるだけでデザインができちゃうのでメンテナンスが少ない。SASSを使い変数をいじれば色やフォントの設定も綺麗にできる。もちろんresponsive対応。

ロゴの色はデザイナーの友達にアドバイスをもらい、それを元に他にアウトソースした。

#### CI/CD

使い慣れていた[TravisCI][8]をセットアップしたが、無料枠を使い切ると「もうクレジット無料枠はチャージされないよー」と出てきたので、毎週無料クレジットが与えられる[Circle CI][9]を導入。キャッシュなどが簡易にできるのでビルド時間がかなり短縮される。Codecovも導入して、GitHubのプルリクに反映させてる。

#### プロジェクト管理

GitHub Issueでラベル(`p0`, `p1`, `p2`, `p3`)を優先度順に設定する。Issueを作る際はテンプレートを用意してちゃんと答える。これにより客観的に重要性を判断できる。


```markdown
**これを完了できない場合のインパクト**

**完了の合否基準**

```

PRを上げる場合は以下のテンプレートを使い、人為的ミスを減らす。

```markdown

**Issueへのリンク**

- [ ] テストが書かれているか
- [ ] データ移行などはあるか
- [ ] Firestoreの移行は必要か

```



#### モニタリング

[Sentry][17]: アプリケーションエラーを通知してくれる。エラーの量が少なく、かっこいい事しなければ無料枠で収まる。
[Mixpanel][18]: ユーザーのイベント可視化ツール。お願いすれば最初の２年は無料。
[UptimeRobot][19]: URLエンドポイントを定期的に呼び、ダウンしたら通知してくれる。（無料）

フィードバックは[crisp.chat][20]が一番コスパが良いサポートツールだが、設定が少し大変。
ウェブの右下にこのボタンを設置して、ユーザーの声を意見を聞く。

![Screen Shot 2021-09-09 at 16.12.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997673/c9e6f7af-7e5c-eb1b-9aee-30f7f9c15bd1.png)

### 完成品

完成品が以下の画像です。[ここから][22]アクセス可能です。

![Screen Shot 2021-09-11 at 17.21.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997673/55bddf56-69f0-c1e6-7f54-aef49c436792.png)


### まとめ

今のテクノロジーとツールで早く進めることができたので数週間でリリースが完了した。
これからもユーザーのフィードバックを元に改善を行っていき、それに応じてスタックを見直す必要があれば行う。
今は開発スピードを重視できるセットアップができたので目的は達成したと考えている。

興味ある方、加わって行きたい方はコメントか[Twitter][21]でDMください。
応援したい方は[ウェブサイト][22]で色々投稿するか、Qiitaと[Twitter][21]のフォローお願いします！


[1]: https://aws.amazon.com/jp/dynamodb/
[2]: https://firebase.google.com/docs/firestore
[3]: https://aws.amazon.com/rds/aurora/
[4]: https://aws.amazon.com/jp/ec2/
[5]: https://support.cloudflare.com/hc/ja/articles/115001635128-Cloudflare%E3%83%AC%E3%83%BC%E3%83%88%E5%88%B6%E9%99%90%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B
[6]: https://firebase.google.com/docs/firestore/query-data/queries#query_limitations
[7]: https://firebase.google.com/docs/firestore/quotas#soft_limits
[8]: https://travis-ci.com
[9]: https://circleci.com/
[10]: https://auth0.com
[11]: https://firebase.google.com/docs/auth
[12]: https://bulma.io/
[13]: https://create-react-app.dev/
[14]: https://github.com/pmndrs/zustand
[16]: https://testing-library.com/docs/react-testing-library/intro/
[15]: https://react-query.tanstack.com/
[17]: https://sentry.io/
[18]: https://mixpanel.com/
[19]: https://uptimerobot.com/
[20]: https://crisp.chat/
[21]: https://twitter.com/kidokapp
[22]: https://kidok.app/thread/0ZZ6GEvbVnMSDwFnxmfV/%E3%81%AA%E3%82%93%E3%81%A7%E3%82%82%E8%81%9E%E3%81%84%E3%81%A6/post/cg3Q92pYBeCvDOONiz2h?u=qiita