Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

開発前・開発初期に設計について検討したこと(ADR) #1

Closed
yuizho opened this issue Apr 2, 2022 · 11 comments
Closed
Assignees
Labels

Comments

@yuizho
Copy link
Owner

yuizho commented Apr 2, 2022

参考

@yuizho yuizho added the ADR label Apr 2, 2022
@yuizho yuizho changed the title 開発前に設計について検討したこと(ADR) 開発前・開発初期に設計について検討したこと(ADR) Apr 2, 2022
@yuizho
Copy link
Owner Author

yuizho commented Apr 2, 2022

バックエンドの全体的なアーキテクチャ方針について

Status

  • 作る前にこんなかんじが良さそうと頭の中で思い浮かべてるくらいのもの

Context

  • 技術的な制約事項
    • AWSの勉強したいのでAWSで作りたい
  • ビジネス上の制約事項
    • 個人開発でやっていくので、基本そんなにお金をかけたくない
  • ユーザが全然いないのにお金だけかかみたいな状態は辛いので特に避けたい
  • 幸運にもユーザが増えたらいい感じにスケールしてくれるようにしたい

Decision

  • サーバレスな設計にして、使われた分だけ費用がかかり、柔軟にスケールできるようにする
  • Lambda使うことにして、なるべく処理性能高くなりそうな言語で記述する
  • データストアにもDynamoDBを使って、使った分だけ課金するかたちにする (Lambdaとも相性がよいので)

Concern

  • スパイクしたときの課金予測とかはしにくそう (とはいえ、取らぬたぬきの皮算用してもしょうがないので、これについてはあんま考えてない)
  • Lambda使うことになると思うので、いい感じにソース管理したりローカルでテストするやり方とかは勉強が必要そう
    • 基本CDKでスタックの管理はしていく
    • SAM使ってローカルでテストできる環境とかは作れそうだけど、LambdaからAppSync叩くところのテスト書くのは大変そう

@yuizho
Copy link
Owner Author

yuizho commented Apr 2, 2022

ユーザへの通知関係のバックエンド周りの設計

Status

  • 作る前にこんなかんじが良さそうと頭の中で思い浮かべてるくらいのもの

Context

  • 技術的な制約事項
    • AWSの勉強したいのでAWSで作りたい
    • インストール不要にしたいのでブラウザベースのWebアプリケーションで構築したい
  • ブラウザベースのプランニングポーカーのアプリを作っていくことになるので、ユーザのブラウザへ通知を飛ばす機能が重要になってきそう
  • 全体アーキテクチャとしてサーバレスな設計にしようとおもっているので、そちらと整合が取れる形で設計したい
  • 自分の勉強のためにこれまであまり触ったことのない技術に挑戦したい

Decision

  • フロントからのリクエストはAppSyncで受けるようにして、データに変更があったらGraphQLのsubscriptionを使ってユーザに通知がとぶようにする
  • LambdaとDynamoDB使っていく前提なのでイベントソーシング+CQRSつかって、Query側のストアが更新されたらsubscriptionが変更を認知して、ユーザに通知を飛ばす形にすると、各Lambdaの責務も明確になりシンプルに書けそう
  • command側はユーザのオペレーションイベントがが種類ごとに登録されていくイメージ
  • 通知以外にもユーザが部屋に入ってきたときは部屋の状況をqueryテーブルから必要な情報をそのまま取得 できるようにつくる
  • CQRS採用することで、query側のテーブルは一定期間ごとに揮発させて部屋の有効期限切れとかも手軽に実装できそう
    • command側のデータは残すので調査時などはこちらをみる

Concern

  • DynamoStreamでLambdaキックするときにどれくらい遅延があるのかはよくわかってないのでこの辺は作ってみてどんな感じか(性能試験とかして)調べる必要がある

Consequences

https://github.com/yuizho/salon/blob/main/doc/architecture/backend.puml
image

@yuizho yuizho self-assigned this Apr 2, 2022
@yuizho
Copy link
Owner Author

yuizho commented Apr 16, 2022

AppSync周りの認証・認可、セキュリティ関連の設計

Status

  • 作りながらこんなかんじが良さそうと頭の中で思い浮かべてるくらいのもの

Context

  • 技術的な制約事項
    • AWSの勉強したいのでAWSで作りたい
    • インストール不要にしたいのでブラウザベースのWebアプリケーションで構築したい
  • ユーザのログインは不要で利用できるようにする
  • 機密情報などはそもそも取り扱うような類のアプリではない

Decision

  • ベストプラクティスに従ってセキュリティ周りの対策(ratelimit, xss validation, etc)は基本WAFに任せる
    • https://www.youtube.com/watch?v=f6jupY6inDM
    • そもそもログインしないのでCSRFの対策とかは不要 (部屋に入った際のUserIDだけで同じユーザかを判断する)
      • IPみるとかやってもいいんだけど、そもそも機密情報とか扱ってるわけでもないのでこの辺はゆるくても大丈夫なはず。
        • そもそも悪いことがほとんどできない (違うカードを選んだりとか) 。そもそも通話しながら使ってもらう想定なのでそいういういたずらされても対処できるはず
      • Cookieとかにuser_id設定してあげれば、同じ部屋の他のユーザのふりして送るとかできなくなるけど初手はそこまで防がなくてもいいかな
        • やるなら本人のユーザーIDとpublicなユーザIDを分けてやるといいかもね (mutate系の操作をするときは本人のユーザIDじゃないと失敗するようにしておく)
        • あるいはjoin時に本人だけにトークンを渡しておくとかも良さそう (こっちがいいかな)
    • ちゃんとわかってなかったけど、AppSyncのresolverでバリデーションできるので送られたパラメータ系は普通にresolverで文字種までバリデーションする
      • 特にユーザが自由にテキスト打ち込める機能とかないのでWAF使わなくてもこれで十分そう (Rule増やすとお金もかかるしね)
  • subscriptionにpushするためのmutate apiだけは、publicにせずIAM認証で内部的にlambdaからだけたたけるようにしておく

Concern

Consequences

@yuizho
Copy link
Owner Author

yuizho commented Apr 23, 2022

ユーザの状態の遷移やロックの設計について

Status

  • 作りながらこんなかんじが良さそうと頭の中で思い浮かべてるくらいのもの

Context

  • イベントソーシング + CQRSでユーザに状態の変更内容を通知する作りになっている
  • 機密情報などはそもそも取り扱うような類のアプリではない
  • 現状のユーザの状態遷移
    • image

Decision

  • 一般的にイベントソーシング + CQRSで業務アプリとか作るときはイベントにバージョン持たせて楽観ロックかけることが多そう
  • 以下の理由から今回は特別ロックはかけなくて良いように思っている
    • 基本自分の操作が自分のユーザ情報の情報を変えることがほとんどである (部屋を離れる、カードを選ぶなど)
    • refreshテーブルの操作だけは他のユーザの選んだカード情報とかもリセットするので影響あるけどその場合も、順番が前後してしまっても(処理対象になった順の)あと勝ちで処理してしまっても大きい問題にならないはず
      • 何らかのユーザの操作 → refreshテーブル: ユーザの操作が適用されたあと現状のユーザの状態が全部CHOOSINGになるだけ
      • refreshテーブル → 何らかのユーザの操作: ユーザの状態が全部CHOOSINGになったあと、ユーザの操作が適用されるだけ
    • DynamoStreamは順番に処理されるので、client側のsubscriptionで不整合起きることもないと思ってる (多分)
  • で、ロックを緩めているので状態遷移図で定義している以外の遷移が起きうる (leave状態のユーザをCHOOSINGにしてしまうとか)
    • RMUでroomテーブルを更新する際そこだけはちゃんと条件見てあげる必要がある
    • 基本ユーザが自分の状態変えるだけのときは競合しないはず (バグと変な不正操作とかがなければ)

Concern

特になし

Consequences

@yuizho
Copy link
Owner Author

yuizho commented Apr 24, 2022

Lambda関数のログ設計

Status

  • 作りながらこんなかんじが良さそうと頭の中で思い浮かべてるくらいのもの

Context

  • 技術的な制約事項
    • AWSの勉強したいのでAWSで作りたい
    • インストール不要にしたいのでブラウザベースのWebアプリケーションで構築したい
  • 基本個人情報とかは取らないアプリである
  • サーバレスな実装なので、複数のLambdaにまたがって処理が実行される
  • LambdaはGoで実装する
  • CloudaWatch上で見れればよい

Decision

  • Goのデフォルトのloggerはログレベルとかでなくて厳しいので、ロギングライブラリ導入する
  • そもそもidと選択したカードの情報とユーザの状態しかやり取りしないので、 parameterとかはログにそのまま出して大丈夫。なのでマスクとかは考えない。
    • 逆にそういうものは扱わないようにアプリを保つ
  • operationAPIで受けたeventIdをmutate-pokerまで共有してリクエスト横ぐしで検索できるようにしとく
    • やっぱログのためだけにroomテーブルにeventIdを保存したりしないといけなくてナンセンスな気がしてきたのでやめる (roomId, userIdはログに出るのでそれで特定はできるし)
    • ただ、lambdaのrequestIdはちゃんとログにだして、lambdad function単位では一括でログが見れるようにしておく
  • 以下のブログのようにerrorが起きた場合に、横断的にerrorログが出力されるように実装する

Concern

特になし

Consequences

@yuizho
Copy link
Owner Author

yuizho commented Apr 25, 2022

OperationAPIに現状のroomの状態に適さないパラメータが渡されたら?

Status

  • 作りながらこんなかんじが良さそうと頭の中で思い浮かべてるくらいのもの

Context

  • 技術的な制約事項
    • AWSの勉強したいのでAWSで作りたい
    • インストール不要にしたいのでブラウザベースのWebアプリケーションで構築したい
  • OperationAPIにありえない条件が渡ってきたらどこでエラーにするかがちゃんと決まってなかった
    • エラーにしてやらないと、例えばいかのようなケースで変なレコードが room テーブルに入ってしまう
      • JOIN の 操作で指定した room_id に一致するレコードが room に存在しない (その部屋は有効でない)
      • PICKED 操作で指定した room_id, user_id に一致するレコードが room に存在しない (そのユーザは有効でない)

Decision

  • OperationAPIはOperationテーブルに登録を行うだけなので、room テーブルは見に行かない
  • RMU で operationの内容を受け取って room テーブルを更新する前に、それが room テーブルの状態に適合するかチェックするのが良さそう
  • operationごとに以下のような内容がチェックできればいいはず
    • image
  • RMUでエラーにできれば、room テーブルにもデータはできないので、参照などもできなくなる

Concern

  • これちゃんとチェックしないと、JOIN に任意の room_id を渡して、好きな room_id の変な部屋を作れてしまう
    • 基本 room_id や user_id は 適切なAPI(OPEN_ROOM, JOINなど)で ULIDを生成して、推測しにくくする

Consequences

@yuizho
Copy link
Owner Author

yuizho commented May 16, 2022

DynamoDBのテーブル設計

Status

  • 作りながらこんなかんじが良さそうと頭の中で思い浮かべてるくらいのもの

Context

  • 技術的な制約事項
    • AWSの勉強したいのでAWSで作りたい
    • DynamoDBでのCQRS&イベントソーシングでの実装前提なのでcommand側とquery側でテーブルを分ける

Decision

command側

  • https://miro.com/app/board/o9J_kma1TB8=/?moveToWidget=3458764521387339699&cot=14
  • command操作ごとにulidで発行されるevent_idをpartitionキーにする
    • ランダム値なので適当にパーティションもバラけて保存されるはず
  • 基本こちらはデータを削除とかしない前提
  • 将来的にOLAP処理とかやりたくなったらこちらの情報を溜め込むイメージ

query側

Concern

  • Hot keyはやっぱり心配だったけど、一応分散されるように指定しているし、起用要件的に同一パーティション内の同時操作もそこまで同時にならないはずなので問題ないと踏んでいる
  • やっぱり (特にquery側)はテーブルの設計どっかに書き出さないとわけわからんくそうなので後でdocにちゃんと書く
    • ユースケースリスト、スキーマ定義 、クエリ条件定義をまとめておく
    • 最初queryがわroomとuserとかにテーブル分けようと思ったけど複数テーブルの同期撮ったりするの無理なので結局一つがベストな気がする (この辺の感覚はawsのdocでベストプラクティスとされていることと揃った)
  • サーバでその部屋のユーザが全員カードをめくったとかの状態は持たないので、基本正しくクライアント側に状態の変更がpushされるのが前提になる

Consequences

https://github.com/yuizho/salon/blob/main/doc/database/database_spec.md

@yuizho
Copy link
Owner Author

yuizho commented May 17, 2022

各部屋・ユーザのステート管理について

Status

  • 作りながらこんなかんじが良さそうと頭の中で思い浮かべてるくらいのもの

Context

  • 技術的な制約事項
    • フロントエンドは Next.js で実装するつもり
      • CSR中心なのでReactでもいいんだけど、ルーティングで迷わなくて良かったり色々最適化の恩恵が受けられるので
    • バックエンドは Appsync で実装するので GraphQLでやり取りする
    • ログイン機能とかは作らないつもり (部屋のID知ってたら部屋に入れて、userIdが振られる作り)

Decision

  • 部屋の状態(部屋が開いているかどうか)、 各ユーザの状態 (カードを選んでる? どのカードを選んだ? 退出中?)はサーバ側で状態をもつ
  • 各ユーザ全体の状態 (カードを全員が選択した?) などの状態は GraphSQL の subscription などで取得した各ユーザの状態をもとに各クライアントサイドで 状態を持つ
    • 細かいユーザの状態に応じてポーカーの状況が決まるみたいな感じなので recoil を使うとマッチしそう (atom と selector の仕組がそのまま使えそう)
  • クライアントサイドの状態として自分のuserIdなどを持つわけだけど、それらは localstorage などへの永続化は行わない
    • 特にその部屋に参加している間は一つのuserIdで居続けないと行けない要件などはないので、永続化はしない
    • その代わりブラウザを更新したり部屋から出た場合を契機に、退出する処理などを実施する
      • 完璧に上記のイベントをハンドルすることはできなさそうなので別途、退出状態にならずサーバの状態上は部屋に残り続けてしまっているユーザをなんとかできるような機能は別途実装する

Concern

  • 各くユーザの状態からなるポーカーの状態は、サーバからpushされてきた情報をもとに各クライアントで作成して管理することになるので、正しく subscriptionなど経由でクライアントがサーバから情報を取得できている前提となっている
    • 万一の場合に補正できるようにサーバと同期取るとかもやってもいいんだけどひとまずそこまではやらなくてもいいかな (あまりに起こるとかなら考えたいけど)
    • その場合はブラウザ更新とかしてもらえれば同期とれるのでまぁ回避もしてもらえる気がする (ユーザは通話などしながら使ってる前提のこともあるので)

Consequences

@yuizho
Copy link
Owner Author

yuizho commented May 17, 2022

ゾンビユーザ(退出しているのに状態は状態)の対処について

Status

  • 作りながらこんなかんじが良さそうと頭の中で思い浮かべてるくらいのもの

Context

  • 技術的な制約事項
    • フロントエンドは Next.js で実装するつもり
    • バックエンドは Appsync で実装するので GraphQLでやり取りする
    • ログイン機能とかは作らないつもり (部屋のID知ってたら部屋に入れて、userIdが振られる作り)
    • 各ユーザの状態はクライアントサイドで永続化しない。ユーザがへやから移動したら、クライアントでイベント拾って退出状態にサーバへ更新リクエストを投げる。ただし、このイベントは完璧に拾えないのでゾンビユーザが作られる場合がある。

Decision

  • 部屋内の特定のユーザをキックする機能を作る
    • 部屋にいるユーザであればキックできるようにする
    • 部屋内のユーザは別途通話などでコミュニケーションをとってる前提なのでいたずらみたいなことはおきにくいはず
    • 間違えてキックしてしまってもすぐ戻ってこれる
    • 通話してない不審者が何らかの理由で入ってきてしまった場合は部屋を作り直してもらう感じで

Concern

  • 本当はゾンビ化したら自動でキックしたい。キックでの対処は結構最小限の対応だと思う。
    • そのためにはユーザのヘルスチェック的な実装が必要でサーバとの通信回数が増えそう。
    • 初手ヘルスチェックみたいな仕組みを作り込むのもオーバーエンジニアリング感があるので一旦キックの実装で十分なように考えている

Consequences

@yuizho
Copy link
Owner Author

yuizho commented May 31, 2022

リポジトリの管理について

Status

  • 作りながらこんなかんじが良さそうと頭の中で思い浮かべてるくらいのもの

Context

  • 技術的な制約事項
    • フロントはNext.jsでサーバはCDK(TypeScript)で管理してる
    • GraphQLに使ってて、スキーマベースでクライアントのコード生成したり、AppSyncを構築しようとしている

Decision

  • yarn workspaces使ってmonorepoで管理する
  • GraphQLのスキーマはルート側に持ってきて、frontendからもbackendからも参照しやすいようにしておく
  • Prittierやhuskyの設定とかはfrontend, backendで共通で良いのでルート側に持ってくる
  • CIはGitHub Actionsをつかってるので、triggerのせっていに paths の設定を入れてやれば特定のフォルダ配下が変更されたときだけCI走らすとかも楽にできる

Concern

Consequences

@yuizho
Copy link
Owner Author

yuizho commented Jun 18, 2022

一通りの必須機能は実装できたのでこちらのADRのIssueは閉じる。
以降考えたことは別のIssueにまとめると思います。

@yuizho yuizho closed this as completed Jun 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant