Webアプリを作る上で、「いざコーディングを進める前」にやっておきたいことメモ


前書き

この記事で書くようなことは、大抵のステークホルダーは無関心です。(セキュリティや耐障害性はそうでもない)
ただ、しっかりと構成することで、システム開発の上で、資産が形成しやすいです。
また、その後の開発において、コストパフォーマンスを高め、プロジェクトを成功に導くはずです。
何番煎じだよって感じですが、自分用のメモ+誰かの指標やチェックリストとなればいいなと思います。

目次

  1. 言語
  2. ソース管理
  3. CI + CD
  4. ログ
  5. セキュリティ
  6. 耐障害性
  7. パフォーマンス
  8. 認証・認可を優先する

言語

言語の選定は非常に大事です。
処理のプロセスは変わりますし、全体的な構成にも影響を与えます。
どれで実装しても一緒、なんてことはないので注意しましょう。

観点① メモリが必要か?

私はNode.jsを好んで使いますが、メモリを大量に消費する処理が必要な場合は、Node.jsは向いていない場合があります。
例えば、大きいファイルを読み込んで、中身を書き換える等の処理はあまり向いてません。
(Stream APIで扱えるなら、その限りではないです。)
そういった処理が必要であれば、Goを採用すべきです。
並列処理を有効活用することで、大変な処理も短い時間で終わらせることができるかと思います。

観点② 緻密な計算が必要か

お金の計算は、ずれたら大変です。
はたまた、Node.jsは、JavaScriptの浮動小数点の仕様により、誤差が出る恐れがあり、あまりお勧めできません。
GoJava等の、安定した信頼性の高い言語を用いるのが良いでしょう。
ただ、C#を使う際は気を付けてください。銀行丸めという仕様があり、意図しない結果が出ることがあります。

観点③ とりあえずいっぱい数捌きたい

例えば、チャットアプリです。1文通すのに、メモリなんてほぼ使わないですよね。
普通のTODOアプリなども、自分の予定を数行出すだけなら、大したデータ量ではありません。
そこで、やっとこさNode.jsが登場です。
Node.jsはシングルスレッド+イベントループにより、C10K問題を解決している上に、動作が非常に軽量です。
また、環境準備や大量のNPMモジュールにより、他の言語より短い期間で、動くものを構築できるイメージがあります。
単純なAPIサーバーや、Webフロントエンドには最適です。

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャを用いることで、多言語化ができます。
その為、得意な奴に任せることができます。
APIサーバーは、Node.jsでバックエンド処理をGoにやらせるのは、よくある構成かと思います。
よくよく検討しましょう。

Gitでソース管理を始める

いざ、言語とフレームワークが決まり、処理を書き出すと、進んでいる感が出ますが、ソース管理について最初に決めておくのは非常に重要です。
なぜなら、後から導入すると、そこからの履歴しか残らないからです。
なので、最初の段階で導入することで、管理がままならなくなるリスクを早めに潰します。

Gitを覚える

Gitの使い方がわからないのであれば、覚えましょう。
ただ、Gitは初見だと難しく感じる部分が多いのは事実です。
そんな時は、実際に運用を始めてしまうのが手っ取り早いです。
近くにGitを使える人がいるのが一番です。
git initから教えてもらいましょう。

git-flowモデルを覚える

とりあえず履歴を残すだけなら、masterにがんがんコミットするだけで事足りますが、実際そういうわけにはいきません。
ちゃんとプロダクションリリースに備えたいのであれば、git-flowを使いましょう。
git-flowを使うメリットは、ソース管理が体系化されることではありません。
後述するリリース手順が体系化されることにあります。
会社独自のルール等を決めて、指差し確認をするより、ずっと効果的で効率的なリリースができます。
ただ、git-flowは少々冗長で、リリース間隔の短いプロジェクトには不向きという話もあります。
そういう方は、
git-daily
を使ってみるのもいいかもしれません。

CI + CD環境を整える

JenkinsでもGitLabでもCircleCIでもなんでもいいですが、CI+CD環境はコーディングを始める前に整えておきましょう。
デプロイ可能であることは、実は非常に重要で、それを自動化しておくことは、さらに重要です。
デプロイすることで、エンドユーザーはアプリをレビューでき、チームは改善への第一歩を踏み出すことができるからです。
(ナンカヨクワカンナイと言われそう)

CIは、ビルドだけではなく、テストをする必要がある

よくCIは導入したけど、ビルドプロセスがリモートになっただけで、自動テストを流していないことがあります。
それだと効果半減どころか、75%減です。(適当)
CI環境でテストするメリットは、ずばり環境にあります。
あなたのローカルには、コミットしていない変更や、プロジェクト外のファイル・ディレクトリ及びパーミッション、PCのタイムゾーン等のあなただけの設定だらけです。
その中で流れたテストは、実際のサーバーでは不具合を起こすことがあります。
せっかく自動テストを書いて、境界値テストをしているのに、タイムゾーンが違ったせいで思わぬ動作をしては元も子もありません。
なので、CIはビルドに加えて、ちゃんとテストをしましょう。

CDをする際は、バージョン管理を合わせて考えましょう

CDにより、デプロイを自動化するときに考慮すべきことは、ずばりバージョン管理でしょう。
バージョンアップのリリースによって、旧バージョンが動かなくなるような構成をとっていないか確認しましょう。
その時気を付けるのは、APIのバージョンだけ管理しても仕方がないということです。
バックエンドには、データベース・データスキーマ・ビュー・マイクロサービスなどがあり、それらすべての整合性を取らなければいけません。
その為、バージョン管理というのは非常に大変です。
そんな時に役に立つのが、オブジェクト指向とSOLID原則です。
ただ、理解し実践するのは容易ではないかもしれません。
なので、一番いいのは、バージョン管理なんてしなくていいようにすることです。
APIを新しく作る際は、別URLを作り、バックエンドも全く別のものを作ればいいのです。
こうやって聞くとコピペコードが蔓延するように思う方もいらっしゃるでしょうが、APIを変更するような破壊的な変更をする際は、おおむね中のコードが全く違うものになることも多いのです。
難しいことを考える前に、変更をやめて、分離・追加してしまうことのがコツです。
そのためには、インターフェースをじっくりと吟味することが大事です。

Docker + Kubernetesは1つの最適解

Dockerを用いるメリットの1つとして、さくっと作って壊せることもありますが、なにより環境の統一でしょう。
疑似環境と本番環境の構成が少し違って動作しないなんてことは、よくあることです。
その点、Dockerを用いると、非常に話が楽です。
Dockerは、アプリケーションをそのままDockerイメージというものにビルドして、レジストリに保存してくれます。
そして、Kubernetesを用いることで、Dockerイメージをそのままデプロイできます。
Docker Imageにはタグがつけられるので、そこでバージョン管理も可能です。
Kubernetesでは、Deploymentという概念が存在し、Docker Imageのタグが指定できます。
最新バージョンが動かなかったら、以前のDeploymentを適用することで、瞬時にロールバックが可能です。
また、仮想化された環境をそのまま使うことで、環境差異への考慮が不要になります。
ただ、外部のSaaSを利用する場合は、SaaS自体の疑似・本番の差異は吸収できません。
その為、本番環境を使ったテストが可能であることは、非常にアドバンテージを生みます。
例えば、テストデータを作って、最後にクリーンアップするテストを書いていれば、本番環境に影響を与えません。
Kubernetesなら、Jobという仕組みを用いることで、ワンタイムのコンテナを作り、そこだけでアップデート版のテストが可能です。
そうすることで、開発・本番の2環境のみでプロダクト開発が行える為、その分、経済的です。

ログについて考える

大抵のシステムでは、ログを出すことが求められますが、いまいちイメージがつかなかったり曖昧であることが多いです。
ただ、ログというのは、大して種類がありませんし、一番大事なのは「何の為に出すのか」です。

何の為にログを出すか

ログは後回しにすればするほど、影響範囲が大きくなります。
早期に方式を決めておく必要があります。
Webアプリを作る上で必要なのは、大抵下記のログです。

種類 役割
アクセスログ HTTPのエンドポイントへのアクセスに対するログ
アプリケーションログ アプリケーションの機能実行に対するログ
システムログ システムの状態を記録するログ

今までアプリを作ってきて、上記以外で意図的に出さなければいけないログはあまり見かけたことがないです。
呼ばれ方は違うかもしれませんが、大事なことはトレース可能なことと、障害のトラッキングです。

第一に、トレースできる形でログを出しておくようにしましょう。
セッションを持たせるのも手ですが、ステートレスなアプリケーションを保とうとすると、難しいことが多いです。
そういった場合は、ユーザーを識別できる情報を出しておきましょう。
ただ、1点注意しなければならないことは、トレース可能ではあるが、個人の特定はできないようにしなければならないことです。
どうしても個人情報を出す必要がある場合は、暗号化するなどの手をとりましょう。
また、見逃されがちですが、フロントサイドで起きたエラーについては、エラーレポートを送信できるようにしておきましょう。

第二に、障害が発生した場合に、通知する仕組みやWebフックの呼び出しによって障害の解決ができるようにしておくことです。
どういったログが出たら、障害と判定するかは、エラーレベルの設定にかかっています。
正しいエラーレベルを考慮し、障害を見逃さないようにしましょう。

どこに出すのか

ファイルにログを書き出す処理を書くのもいいですが、ご存知の通りディスクIOの処理は重いです。
そこで、パフォーマンスが求められる場合は、ログは標準出力し、リアルタイムにストリーミング処理することをお薦めします。

どこに、いつまで置いておくか

標準出力なり、ファイルなり吐き出されたところで、そのままにされては意味がありません。
そこで、ログを収集するプロセスが必要になります。
外部からログを収集し、分析・保管しておく仕組みを考えておくのがいいです。
例えば、分析用にはElasticSearchに流し込み、保管用に耐久性の高いAmazon S3などに放り込むといった形でうs。
幸い、Amazon S3は期限を過ぎたファイルを削除したりする仕組みがあるので、有効活用しましょう。
保管期間は、IPAや法務省によって取り決めされていたり、金融機関であればさらに長く期間が決められていたりしますので、そちらに従うのがいいでしょう。
監査が必要であれば、監査の際に必要な期間分を保持しておきましょう。

セキュリティについて考えておく

あらゆる攻撃を耐える、かわすにはアプリケーション+基盤のどちらも考えておく必要があります。

アプリケーション セキュリティ

SQLインジェクションやXSSへの対策が必要です。
最低限だと、
OWASP Top10
をチェックしておくといいでしょう。
NoSQLを使用している場合においても、RDBを使用している外部システムへのデータ連携などにより、サニタイズされていないSQL文が、データに含まれていると脆弱性を提供することになってしまいます。
これらは、パブリックネットワークに公開しない、社内Webアプリについてもしっかりと考えておく必要があります。
CIプロセス上で、脆弱性スキャンを行うことで、リスクを最小限に抑えましょう。

Web Application Firewallを導入する

アプリケーションに脆弱性が無くても、DOS攻撃はできます。
そこで、リクエストが到達する前に、WAFでプロキシすることにより、安全性を高めることができます。
パブリックネットワークに公開するアプリケーションには必須といえるでしょう。
ケチって、DOS攻撃を食らったほうが大損害ですので、気を付けましょう。

耐障害性について考えておく

万が一、障害が発生した場合に継続的にサービスを提供することが求められる場合は多いです。
その為には、冗長化をしておく必要があります。
そして、サービスを継続するには、復旧プロセスが存在していることが重要です。

アプリケーション障害

アプリケーション障害に対処しておくためには、サーバーを冗長化しておく必要があります。
ここでいうサーバーは、物理というよりは仮想です。
1つの物理サーバー内で、複数のサーバーを立ち上げることも可能だからです。
そういった場合、あるサーバーで障害が起きても、別のサーバーが処理を継続します。
しかし、それ以上に、復旧プロセスが重要です。
ログによるトラッキングと、定期的なヘルスチェックの仕組みがあれば、起きた障害に対して、復旧プロセスを自動化することができます。
例えば、ログによるトラッキングで、Web Hookを呼び出し、データの修正を行うなどです。
自動化することで、保守・運用のコストを減らせますし、面倒な作業が減ります。
アプリのリリース初期では想定していなかったことが起きます。
障害が起こるのは当然です。
それらを資産として、後世に残すためにも、自動化しやすい構成を選定しましょう。

物理的な障害

また、アプリケーション障害のみならず、物理サーバーや、データセンターの障害も起こり得ます。
オンプレミスなら、多拠点を用意したレプリケーション、クラウドならマルチリジョン対応が必要になります。
意外と、クラウドは落ちます。
早い段階で、構成しておきましょう。

パフォーマンス

Webアプリケーションのパフォーマンスは、マシンスペックと処理の内容だけでなく、ネットワークという観点が大きく影響します。
パフォーマンスを上げるためにマシンスペックを上げることを、スケールアップといいますが、スケールアップだけでは限界(ムーアの法則)が来ます。
その為、スケールアウトが必要になります。
それを実現するのが、ロードバランシングとレプリケーションです。

ロードバランシングとレプリケーション

ロードバランシングとは、ラウンドロビン等のルールを基準に、処理を割り振る仕組みです。
割り振り先は、すべて同じ動作(InとOutが同じ)をする必要があります。
同じ動作をするサーバーを増やすのが、レプリケーションです。
データベースとかだと大抵は、複数の物理サーバーで、データをミラーリングするような仕組みがあったりします。
では、APIサーバーはどうでしょう。
同じ動作をするためには、APIサーバーは、状態を保持してはいけません。
つまりステートレスな実装にする必要があります、
例えばセッションやキャッシュを持てません。
単にリクエストを加工し、別のサービスに中継して、帰ってきたデータ加工して返す。
そんな実装にする必要があります。
セッションやキャッシュを持ちたい場合は、RedisやMemcachedなどを、共有データベースとしてバックエンド配置する必要があります。

Kubernetesによるロードバランシングとレプリケーションについて

Kubernetesの場合は、Istoを導入することで、エンドポイントの前にロードバランサを挟まずとも、ロードバランシングの仕組みが実現できます。
そのほかにも、あらゆる機能を提供していて、それらを実現することをサービスメッシュとか呼ぶらしいです。
また、Kubernetesには、レプリカセットという概念が存在し、オートスケーリングを導入することが可能です。
うまく活用しましょう。

認証・認可を優先する

認証や認可の仕組みは、とりあえず動けばいいものを素早くプレゼンテーションしたいときは、目の上のたんこぶのような存在です。
ただ、認証・認可に不備があることは、セキュリティリスクと同等の危険性を孕む場合がありますし、ログと同じで後回しにすればするほど、影響範囲が大きくなります。
早期に、方式を決定しておくことが大事です。
OAuth等を用いて、認可可能な仕組みを事前に整え、完璧に穴を防ぎましょう。

あとがき

文章ばかりになってしまった上、肝心の具体例に欠けるので、実際にアプリケーションを作り、それを元に説明を加える予定があるとかないとか。
私はアーキテクトではなく、プログラマーと名乗っているので、プラグラムせねば。
今の時点で、指摘や加筆してほしいことがあれば、コメントしてもらえると嬉しいです。
これらをすべて、開発初期段階に行うことは、そう簡単ではありませんし、中盤以降も継続的に考慮するべきことばかりですが、昨今はツールもそろい踏みで、クラウドを使って気軽に導入できるメリットがあります。
それらを生かして、機敏で、革新的なコストパフォーマンスの高いサービスを、エンドユーザーに提供できるよう頑張りたいものです。