自分がwebシステム作るときに考慮してることリスト
こんな感じの記事を自分も作りたかった・・・
はじめに
自分はエンジニアインターン込みで4年、正社員で3年、いろいろな会社でウェッブサイトの新規開発保守運用などなどしてきました。
そこらで学んだ、考えなければならないことというのは意外とどこも共通してるなーって思ってきたので、備忘録がてら、自分が今開発するにあたり考慮していることリストをここに箇条書きしていきます。あと、それらに関連するキーワードや参考ページ一覧なんかも載せておきます。皆さんの個人開発や仕事の参考になれば幸いです。
ちなみに、ここに記載しているのは過去の自分の業務での経験を元にしているので、下記の技術が前提となっている場合があります。ご容赦ください。あと、組み込みやゲーム開発とか他業種についてはあまり詳しくないのでそちらについても何卒…
カテゴリ | 言語/ツール/フレームワーク |
---|---|
フロントエンド | - React - Next.js |
バックエンド | - Golang |
データベース | - MySQL - redis - DynamoDB |
クラウド | - EKS - ECS - S3 - SQS |
モニタリング | Datadog |
CI/CD | GitHub Actions |
インフラ | Terraform |
良さそうだったらぜひいいねと感想をツイートしてみてください♫励みになります
設計編
設計はできるだけumlなどの図や表で表現して視覚的にわかりやすくする
-
図や表を使って視覚的にわかりやすく設計書を記載する
キーワード: plantuml、marmaid.js、uml、
「なぜその方針にしたのか?」「なぜその方針をとらなかったのか?」を残しておく
歴史を忘れてると、本当は理由があってその選択肢をとらなかったのに、数年後にわすれてその選択肢をとってしまうことが起きたりするので
キーワード: 議事録
ステークホルダーマップを作成しておく
-
ステークホルダーと、そのステークホルダーの関心事を調べる
そのシステムのステークホルダーがそのシステムにどういう関心を持っているのか?を把握しておきましょう。設計時に彼らの関心も考慮しないといけませんので
余談: 「Design It!」は神書ですのでおすすめです
キーワード: ステークホルダー
利用規約に違反してないか確認しておく、法務確認をしておく
- 法務確認をして問題ないか確認する
- 利用規約や、連携先のシステムのガイドライン、アプリストア(apple,googleなど)のガイドラインに準拠しているか
-
利用しているロゴは使用ガイドラインに準拠しているか
キーワード: 特定商取引法、GDPR、利用規約、Cookie同意
理想の姿と、現実的に今開発しようとしている内容とのギャップを考えておく
- 理想の状況だったらどうやって設計してるかも考えておく
-
上記から、どういった理由で妥協しなくてはならないのかを考える
「現実的なスケジュールに落とし込もうとすると良くないとは思いつつ、こういう設計にせざるを得ません」みたいな状況は多々発生するのですが、じゃああるべき構造だったらどうやって設計してるの?というのは必ずやるべきです。仮にあるべき設計にリファクタリングする機会が訪れた場合、その設計はどうやるの?に答えが出ない場合、あるべき設計へのリファクタリングを行う際に必ず障害になり得てしまうからです。
会計連携についても考慮する
-
会計連携が必要かどうか考える
webサービス系は決済処理を伴うものが多かったりすると思いますが、意外と見落としがちなのが会計処理だと思います。
あなたが提供しているサービスの特性に合わせて考慮しないといけないことがあったりすると思いますので、一度会計を担当している人に聞いてみると良いかもです。
また、新しい販売手段やサービスを追加する際にも会計への考慮が必要になるかもしれません。
設計時に異常系も考慮しておく
- どういったエラーが発生しうるのか列挙する
- そのエラーに対しての対策を検討する
正常系のみを考慮すると、いざエラーが起きた際にどうすればいいのかわからなくなります。異常系が起きた際にどうするのか?も考えて設計しよう。
ほかサービスがどう動いているのか、どんなアーキテクチャで作られているのかもちゃんと調べておく
アーキテクチャというのは大体が頭のいい人が考えたものをベースに作られています。また、あなたが作ろうとしているシステムが他にある場合、そのシステムがどうなっているのか調査してみましょう。考慮していなかった落とし穴が見つかるかも!
テーブル設計の際、アプリケーションを動かすため以外の観点、例えばビジネス的な分析観点も考慮しておく
- ステークホルダーにどういった分析をしたいのかをヒアリングする
機能要件を達成させるためだけのテーブル設計すると忘れがちですが、データというのは色々な関係者が色々な目的で関心を持っています。
簡単な例で言うと、マーケティングの人は新規会員登録数の推移や、退会者数の推移が見たかったりするかもしれません。それだけなら簡単なのですが、「新規会員登録時に𓏸𓏸だったユーザーの割合は?」等のような、その時間軸時点での特定の属性、を条件にしたデータが欲しがってくるかもしれません。そうなると結構めんどくさい
ステークホルダーによって知りたい事というのは異なるので、ステークホルダーの関心を考慮してデータ設計しないといけないと思っています。
こういった分析基盤は複数のデータソースを連携させたうえで分析することが多いので、もっと広い視点で「データ分析基盤の設計」を検討するのも良いかなと思います。
キーワード: 境界づけられたコンテキスト、ステークホルダー、ステークホルダーマップ、データパイプライン、ETL、BI
「status」属性は神属性になりがちなので気をつける
- statusカラムが追加されそうになった場合、そのカラムはなんのために存在するのか、別の表現方法がないか代替策を検討する
色々な神カラムがあると思いますが、自分の経験上辛かったのはstatusカラムです。察してください。
テーブル設計時には履歴が追えるようにしておく
- 状態の変遷が重要なデータの場合、履歴を追えるようになっているか
なうい設計だとイベントソーシングとかもありますね。
キーワード: イミュータブルデータモデル、履歴テーブル、イベントソーシング
参考
- https://scrapbox.io/kawasima/イミュータブルデータモデル
- https://learn.microsoft.com/ja-jp/azure/architecture/patterns/event-sourcing
コードのリファクタリングはやろうと思えば力ずくでどうとでもなる。テーブル設計はリファクタリングがすごいめんどくさいのでどんな事があっても1ミリも妥協してはいけない
コードのリファクタリングはテストをガッチガチに書いていれば力技でどうとでもなるかなと思うのですが、データベースは考慮しなければいけないことや準備が多すぎます。特にメンテナンス時間が取れないような365日24時間稼働のシステムの場合は。。。
簡単にできる人は教えてください。。。
よくyagniが反論として挙げられますが、yagniだよね~で意思決定したことで良かった思い出がほとんどなかったので筆者はyagniについていい印象がありません・・・(yagniが本来伝えたかったことの意義は理解できますが、人類は都合の良い使い方をしてしまう生き物なので・・・)
キーワード: online ddl、gh-ost
参考:
マーケティングツールについての確認は忘れずにしておく(Google Analyticsとか)
特に(ざっくりとした分類で恐縮ですが)ビジネス部門の人から始まった新規開発では効果計測をしたいモチベーションが強い経験が多かったです。機能開発ばかりに目を向けすぎてアナリティクスツールとかをいれるの忘れてた!となりがち
どういった方法で効果計測をするのか、事前に設計しておきましょう。
運用設計も考えること
- カスタマーサポートも巻き込んで、ユーザーからの問い合わせがあった時のコミュニケーションフローなどを整備する
- 手動オペレーションが発生するリスクがある箇所については事前に運用フローを設計しておく
実装編
共通
前提として、こちらで書かれているのは守りましょう
フレームワークのレールに大人しく乗る(オレオレの仕組みを作り込みすぎない)
- フレームワークのバージョンアップを想定した改修なのかどうか
(`ー´)「このフレームワーク全然やりたいことができないな」
(`ー´)「この天才エンジニアのワイが拡張して最強のフレームワークにしたる!」
-N年後-
('・ω・')「先輩、このフレームワークセキュリティパッチがあたってるバージョンに上げないといけないんすけど上げたらCI全部おちちゃって困ってるっす。この機能作ってた人ってだれすか?ドキュメントもないしコミットログも「feat: 最強のフレームワーク実装!!」っていうのばっかりだしで・・・」
( ̄ー ̄)「その人はもう退職したよ」
('・ω・')「え」
( ̄ー ̄)「そして俺も今週で退職や」
('・ω・')「え」
こういう、あれ
デバッガー環境を整えておく
- 想定している開発環境でvscodeやその他ideのデバッグ機能が使える
テストコードがちゃんと動いているかどうか確認する際、デバッガーが使えるように環境が整備されているとすごくデバッグがやりやすいです。
特に開発環境のアプリケーションサーバをdockerで構築しているとここらへんが結構めんどくさくなる場合が多いです。その場合はdevcontainerを導入しましょう
キーワード: devcontainer
フロントエンド
フォーム管理はできるだけピュアなhtmlの機能を使うにとどめ、ライブラリをバリバリに使いすぎないようにする
- フォーム管理ライブラリを導入する場合、リスクについて考慮しておく
筆者はreact-hook-formを使っていたのですが、使い方を間違えると簡単にformのデータが吹き飛んでapiに正しいデータが送れなくなったりしますし、単にライブラリのバグを踏んでデータが取り出せなくなったり、フォーム系のライブラリで何十回も痛い目を見てきました。
これらのライブラリを活用すればリッチなバリデーションをクライアントサイドで行えますが、フォーム周りに関しては便利さよりもリスクをより考えるべきポイントかなと思います。
筆者的にはもうできるだけフォーム管理ライブラリは使わず、ピュアなhtmlのフォーム機能だけを使うようにとどめたほうが良い。という思想になってしまいました。
クライアントサイドでバリデーションをゴリゴリ書くとサーバーサイドとロジックが2重管理になってしまったりするので、バリデーションもできるだけサーバー側にロジックをおけると良いかなと思っています。trpc?うるせぇ!
...ただここらへんはいろいろな考え方があるので強くは言いません。ただ、自分はこれらのフォームライブラリで引き起こしたバグを思い出したら胃が痛くなってくるようになっちゃいました・・・
バックエンドに送信するhttp requestのパラメータをちゃんとテストするようにする
http clientライブラリがぶっ壊れたり、フォーム管理ライブラリの使い方を間違えてぶっ壊したりした際にパラメータが喪失したりするのを検知するためにこれらのテストは必ず導入しましょう
キーワード: playwright event listener、msw
参考:
- https://zenn.dev/sdb_blog/articles/002-check_post_request_by_playwright
- https://zenn.dev/azukiazusa/articles/msw-request-assertions
DDDとかクリーンアーキテクチャとかは頭から消して、オブジェクト指向UIベースで設計する
完全に経験則で人によって意見が分かれるところですが、クリーンアーキテクチャなどで話しているようなレイヤードアーキテクチャや、ドメイン駆動設計(戦術的な部分の話、戦略的なところはプロジェクト全体通してやるべきだと思う)は、バックエンド側では効きますがフロントエンド側で無理やり当てはめようとするとなんかしっくり来ませんでした。「この実装どこレイヤーにおくの・・・?」とか「UIの部品とか、EntityでもないしServiceでもないしAggregateでもないし・・・どうすればいいんだろ・・・」となりがちな気がします。
フロントエンドはクリーンアーキテクチャのあの円でいうUI層の実装がほとんどだと思いますし、そうすべきかなと思っています。
なので、自分はどちらかというとオブジェクト指向UI中心の実装をするべきかなと思っています。
ただ、クリーンアーキテクチャやDDDの本質的な部分はフロントエンド側でも流用できる考えかなと思いますので、完全には否定しないです。ただ、これらのワードを使って実装するとだいたい「Entityが~Value Objectが~Domain Serviceが~Aggregateが~」と言い出したりしてよくわからない方向に進んじゃいそうなので、あえて「DDDとかクリーンアーキテクチャとかは頭から消して」と書きました
キーワード: タスク指向UI、オブジェクト指向UI、
ブラウザのdevtoolはものぐさせず確認すること
特にメモリタブとパフォーマンスタブです
ソースマップを出力している場合、どこで処理時間がかかっているのかを確認することができたりしますので、ステージング環境ではソースマップを出力するなど試してみましょう
キーワード: devtool、ソースマップ、
サポートするブラウザの種類、下限バージョンを決める
cssとか新しい機能たくさん出たりしますが、最新のブラウザじゃないと使えなかったりするので
バックエンド
クライアントのコネクションが切れたときのハンドリングがきちんと行えるようにしておく
(goの場合)
-
クライアントからコネクションを切られたときのハンドリングについて考慮しているか?
- context.WIthoutCancelを使うか、自前でハンドリングするかを決めているか?
参考:
- context.WIthoutCancelを使うか、自前でハンドリングするかを決めているか?
- https://ryuichi1208.hateblo.jp/entry/2023/08/28/202433
- https://future-architect.github.io/articles/20230803a/
外部システムへの疎通の歳はバックオフ戦略を考慮しておく
- バックオフ戦略を考慮した実装が書かれているか
キーワード: Exponential-backoff、バックオフ戦略(backoff-strategy)、指数バックオフ
参考:
- https://cloud.google.com/memorystore/docs/redis/exponential-backoff?hl=ja
- https://aws.amazon.com/jp/builders-library/timeouts-retries-and-backoff-with-jitter/
データ不整合が発生しないように、トランザクション、ロールバック戦略について考慮する
- API、バッチ処理が冪等な処理になっているか
- 外部システムへの登録処理が発生する場合、そのシステムへの補正トランザクションが実装されているか。または考慮されているか
ただ、すべての処理を1トランザクションにまとめたりすると、パフォーマンス問題につながったりもする可能性があるので、トランザクション境界などについても考慮しましょう
キーワード: 分散トランザクション、補正トランザクション、データ不整合、トランザクション境界
参考:
バッチ処理で大量の件数を処理する場合、ページネーションを入れて線形にメモリ使用率が上がらないように制御する
シーク法とかオフセット法とか
キーワード: ページネーション、シーク法、オフセット法、OOM
graceful shutdownを設定して突然サーバが落ちても安全にシャットダウンされるようにする
特にk8s上で動いているpodなんかは簡単にnode schedulerのスケールインに巻き込まれてpodが落とされたりするので必ず設定しましょう
N+1にならないような実装にする
後述するモニタリングツールをきちんと設定することでN+1が発生した際に視覚的にわかりやすくなるのでぜひ入れましょう。このとき、トランザクション範囲なども考慮して下さい
複数インスタンス立ち上げてスケーリングするようなシステムの場合、共通ストレージとしてredisを活用する
現代のアプリケーション実行環境はAutoScalingでpodなりインスタンスなりを複数起動させて水平スケーリングさせます。
そうなったとき困るのが、カウンターの制御などです。
例えば、特定の外部サービスの同時並行処理数の制限が10 [req/s]だったとします。アプリケーション上ではこの制限にぶつからないようにカウンターなどを使い並行処理数がオーバーしないように制御をしたいのですが、複数台でスケーリングさせてしまっている場合、グローバル変数だけで制御させるのは難しいです。
ここでredisのようなインメモリDBを活用して分散システムの共通メモリを作るとかゆいところに手が届いて便利です
キーワード: カウンター、レートリミット、排他制御
インフラ
だれが何をやったのかがわかるような、操作ログ/監査ログは必ず取る
- 踏み台サーバに監査ログ/操作ログ取得の仕組みを構築する
- rdbmsに監査ログ/操作ログ取得仕組みを構築する
参考:
無駄なコストが掛かっていないのかをモニタリングする
- 保存期間が長いログは安価なストレージサービスに格納させているか
- コストモニタリングはしているか、仕組みを構築しているか
awsだとchatbotと連携してslackに通知してくれたりしますね
意外と忘れがちなのですが、ログ周りはコストが掛かりがちです。s3などのやすいストレージにデータを移動させて、ライフサイクルポリシーを設定して何年間保存するのか?を決めておくと良いでしょう
terraformの場合、trivyなどのセキュリティスキャンツールをいれてCIに組み込む
昔はtfsecと呼ばれてたやつ
trivyはコンテナスキャンもできたりして便利なので、ぜひCIにも組み込みましょう
CI/CD(主にgithub actions)
concurrency の設定を必ずいれる
jobには必ずタイムアウトを設定する
secrets.github_tokenを使うときは必ずpermissionの設定を確認する
手動検証できるようにしたいのでon.workflow_dispatchをとりあえず脳死で入れておく
動的にシークレット情報をfetchする際にはログに出力されないように必ずマスキングする
モニタリング
エラーの監視だけでなく「正常に起動したこと」自体のモニタリングもすること
- 正常に起動していることのモニタリングについて検討していること
cronの書き方を間違えてた。モニタリングツールで拾いきれないレイヤーでエラーが出ててエラーログとして検知できなかったなどがありますので
運用
ライブラリのbump version運用はやる
小さく上げ続けていく状況が大切です。小さくバージョンを上げていく文化が醸成しなければ、いざバージョンを上げたくなったときの改修コストがエグくなります。
キーワード: bump version、dependabot、renovate
参考:
モニタリングツールでの監視は毎日やること
キーワード: datadog dashboard、監視、
スロークエリログはきちんと監視しよう
キーワード: performance insight、Slow Query Log
参考:
推測するな計測せよ、計測基盤を事前に整えておこう
datadogの回し者みたいですが違います。
datadogにはプロファイラもいれることができるので、メモリリークが起きた際にどこの処理でメモリ使用率画像化しているのかを客観的に計測できます。
ログとspanは紐づけて置こう
datadog前提の話
ログとspanを常に紐づけ、「エラーログが出たときに、関連する他のアプリケーションはどんな状態だったのか、どんなログが出ていたのか」を計測できるようにしておこう
ソースマップはモニタリングツールにアップロードしておこう
調査が便利になります
Profilerは導入しよう
datadog前提の話
ぼくはdatadogの中の人ではありません
Profilerを入れることでメモリリークがどこのソースコードで発生しているのかを追いやすくなります!
トラブルシューティング
マニュアルは作るだけでは意味ないので、ちゃんとステージング環境でトレーニングしよう
-
障害発生時の訓練の実施
マニュアルがあるから属人化せずにみんなできるようになるよね♫
んなことないです。
スタックトレース、cpu使用率、メモリ使用率、レイテンシなどを確認しよう
例えば
- メモリ使用率が以上に高かった
- メモリリークがおきていないかprofilerで確認する
- レイテンシが以上に高かった
- spanを確認して、外部システムに障害が起きていないかを確認する
- スタックトレースが出ている
- 該当のコードを確認する
など、異常が起きているメトリックを起点にすることで、どこが原因なのかを確認しやすくなります
番外編
「今は忙しいから時間ができたときに取り組む」 <- これは絶対にやらない
-
今考えよう
仕事で暇になることなんてほとんどありません。あなたが作った素晴らしいシステムはたくさんの人達に使われて人気になります。そうなると偉いおじさんが「あれやりたい」「これやりたい」と言ってきて、もっとこうしたいというのをたくさん言われますので、やる時間なんか絶対にありません。
クリーンアーキテクチャ本の最初の方にも似たようなことを言ってる記載があったりしましたし
「やろうと思えばできる」という答えから一歩踏み込んで「技術的にはできるけどやるべきではない」を説明できるようにしよう
- 解決したい問題を詳しく掘り下げる
- 技術的にはできるが、やるべきではない場合は根拠を添えて素直に伝える
偉い人から相談されると大概の人(主に自分)がこういうことを答えてしまいます。
「技術的には可能です!」と
実際問題外部システムと絡まないことなら大概の場合力ずくで実装すればどうとでもなったりします。
ただ、そのシステムにその機能を入れることが、本当に正しいのか、壊れやすい機能になっていないのか、は一度深呼吸して考えてください。そして、その機能をいれることで解決したいことはなにか?ということを一度きちんとヒアリングしてみると良いかなと思います。結果としてその機能をいれることが正解ではなかった。ということも往々にしてありますので。
※自分はここらへんのコミュニケーションは素人なので、実際のところは身の回りにいる其の道運十年のベテランの人に聞いてみてください
❌️: リスクについて事前に共有せず受動的に話を受けてしまうパターン
('・ω・')「技術的にはやろうと思えばできます!(このやり方じゃ絶対バグ起きまくるやろけど)」
(`ー´)「じゃあやって!」
('・ω・')「すみませんバグが起きてシステム完全にとまっちまいました。損失額N億円です」
(`ー´)「なんでバグなんか起こしたんや!」
('・ω・')「だってあんなやりかたじゃバグ起きるに決まってるじゃん・・・」
❌️: できないやりたくない言い過ぎる
('・ω・')「(この機能入れたら絶対ぶっ壊れちまう・・・とめなあかん)」
('・ω・')「そのやり方だとバグおきるのでやりたくないです!」
(`ー´)「そうなんですね・・・わかりました・・・」
(`ー´)「(こいつやらないできないばっかやな・・・)」
🟢代替ソリューションの提案をする
('・ω・')「技術的には可能ですが、△△ということをやりたい場合、それだけでなくXXという手段もあったりします。どちらが適している方法なのか検討したいので、一度詳しくお話聞かせていただけないでしょうか?」
(`ー´)「ぜひ会話しましょう!」
('・ω・')「ほげほげほげ」
(`ー´)「ふがふがふが」
(`ー´)「XXのほうでおねがいします!」
🟢: 素直にやりたくないと言いつつ、相手が困っていることに寄り添って解決策がないのか一緒に考えてみる
('・ω・')「そちらの機能の改修を短期的な期間で取り組んだ場合、今後のシステムの拡張性に影響が出るような修正を行わないといけなくなり、できれば他の手段で解決できないかと考えております。どんな問題を解決したいのか、他に手段がないか検討したいので、一度相談のお時間いただけないでしょうか?」
(`ー´)「ぜひ!」
('・ω・')「ほげほげほげ」
(`ー´)「ふがふがふが」
(`ー´)「別に開発しなくても既存の仕組みでいけたわ。そんな機能あるなんて知らんかったわ!あんがと!」
('・ω・')「いえいえ」
('・ω・')「(この視点での追加改修案件今後も絶対言われてくんな・・・忘れずに設計するチケット切っておくか)」
終わりに
参考になれば幸いです!
Discussion