コミュニティ

個人開発・スタートアップで採用すべき最強のアーキテクチャを考えた

結論

aws.png

「アジリティ」「コスト最適化」「スモールな構成」「開発スピード」という観点でWebアプリケーションのアーキテクチャを考えてみました。

  • ServerlessFrameworkを使い倒す
  • フロントエンドはS3 hosting + CloudFrontで。SSRもLambda@Edgeでできます
  • データベースはRDSは使わずにDynamoDBで
  • APIは基本的にGraphQL。必要に応じてRESTも簡単に追加できるよ。

背景 アーキテクチャに絶対の正解はない

アーキテクチャには絶対の正解はありません。
なぜなら、プロダクトやフェーズによって求められる要件が異なり、それに適したアーキテクチャを考える必要があるからです。

例えば、先日障害で証券取引が1日休場になった東証の高速取引システムは、安定性や信頼の要件に全振りをしていて、400台のサーバで負荷分散をしていたり業務系と運用系でネットワークを分けて障害の影響範囲を局所化するようなアーキテクチャになっています。
AWSやメルカリなどの成長企業では拡張性の要件が高く、イノベーションを起こし続けるために全体のアプリケーションに影響を与えずに新しいサービスを展開できるように、マイクロサービスアーキテクチャが採用されます。

では個人開発やスタートアップはどんな要件が重視されるか考えてみます。
こちらにスタートアップが失敗する理由のランキングが掲載されています。

スタートアップが失敗する理由ランキング
1位 市場ニーズがなかった
2位 資金の枯渇
3位 適切でないチーム構成
4位 競争力で負けた
5位 使いにくいプロダクト

これらはアーキテクチャで全て解決できる問題ではないですが、どんな要件が必要かが見えてきます。

1位 市場ニーズがなかった → 顧客の声に合わせて仕様を変化させていくアジリティ
2位 資金の枯渇 → コスト最適化
3位 適切でないチーム構成 → なるべく専門家を必要としないスモールな構成
4位 競争力で負けた → 開発スピードを高めて競合を抜き去る
5位 使いにくいプロダクト → 改善サイクルを高めるアジリティ

個人開発/スタートアップに必要な要件は「アジリティ」「コスト最適化」「スモールな構成」「開発スピード」ではないかという仮説が立ちます。
この観点で技術要素を検討してみました。

技術要素

背景にて、個人開発とスタートアップには「アジリティ」「コスト最適化」「スモールな構成」「開発スピード」が優れている技術要素を採用すべきだという検討を行いました。
この観点で私が優れていると思う技術要素を紹介していこうと思います。
構成は「フロントエンド」「データベース」「バックエンド(API)」の3段階で説明していきます。

フロントエンド

フロントエンドフレームワークの選定

フロントエンドフレームワークはReact,Vue,Angularの3大フレームがあります。
「開発スピード」「アジリティ」という観点で考えると、TypeScriptで型付けをすることが必須だと思います。入力補完の恩恵を受けつつ、どんなデータを受け渡せば良いかが一眼でわかることで開発効率をあげつつ、仕様変更でコードを変更した際のエラーチェックがコンパイル時にわかるためです。
そしてTypeScriptとの相性という観点ではReactが他の2つのフレームワークに比べて群を抜いているという印象で、フロントエンドフレームワークはReactを採用すべきだと思っています。
また、「開発速度」「スモールな構成」という観点で、私は素のReactを使うよりもNext.jsを使うことを推奨します。というのもNext.jsは、必要だが実装するのは面倒なことをNo Configで実現できるためです。具体的にいうとルーティングとSSR(SEO, SNSのOGPのため)です。
以上より、Next.js(TypeScript)をフロントエンドフレームワークとして推奨します。

フロントエンドのデプロイ先

Next.jsのデプロイ先として真っ先に頭に浮かぶのは、vercel, Netlify, Herokuなどがあります。
これらは非常に楽にデプロイができて、基本無料で使えるため選択肢に入ってきます。
しかし無料枠には必ず機能的な制限があるため、スケールすることを前提にするならばできればAWSで構築したいところです。
S3 hosting + CloudFrontであればAWS無料枠で使えますし、無料枠がなくなってもほぼ無料です(S3は0.025USD/GB, CloudFrontは0.114 USD/1GB)。
vercel等のサービスで有料アカウントになると固定費がかかってきますが、AWSのこの構成であればonDemandな課金になるので、「コスト最適」という点でS3hosting + CloudFrontを推奨します
ちなみにvercelの無料枠もほぼ無制限みたいなものなので、極端なスケールを前提としないのであればvercelも全然ありです
フロントエンドのデプロイ先としてEC2やECSなどのサーバを使うことは「開発速度」「コスト最適化」という観点から論外かなと思っています。

Next.jsのSSRはLambda@Edgeで行います。
この構成のデプロイはServerlessFrameworkを使うことで、No Configで簡単に行えます。
https://github.com/serverless-nextjs/serverless-next.js

データベース

RDS vs DynamoDB

結論から言うと問答無用でDynamoDBを選択することを推奨します。
正確にいうとDynamoDBでは表現しきれないデータ構造や複雑すぎるアクセスパターンの場合はRDSにせざるをえないのですが、初期のフェーズではDynamoDBで作って無理が出てきたらRDSとの併用を考えるという形が良いと思います。
「コストの最適化」という観点からいうと、Dynamoは従量課金ができるのに対してRDSはインスタンスの起動時間で課金されます。またRDS+LambdaはRDS Proxyの導入を行わないといけません。 Aurora Serverlessもありますが、こちらはコールドスタートが致命的です。
「アジリティ」という観点からいうと、DynamoはNoSQLなのでスキーマが固定されず仕様の変更に柔軟に対応できますが、RDBはテーブル間の依存関係があるためテーブルを捨てにくくマイグレーションやSQLの作り直しが発生します。
「開発速度」という観点では、Dynamoはデータベース機能の管理が不要であることとGraphQLと相性が良いことで開発効率をあげることができます。

DynamoDBの設計

DynamoDBを推奨しますが、RDBライクにDynamoDBを設計してしまうと確実に詰みます。DynamoDBはJoinができないため、Joinが必要のないようにテーブル設計をする必要があります。
RDBとNoSQLはテーブル設計のやり方が全く異なるので、ここに必ず最初学習コストをかけてください。

お勧めのリンクを貼っておきます。
サーバーレスアプリケーション向きのDB設計ベストプラクティス
Amazon DynamoDBのデータモデリング
DynamoDB に合わせた NoSQL 設計

バックエンド(API)

REST vs GraphQL

「アジリティ」「スモールな構成」「開発スピード」という観点でGraphQLを推奨します
GraphQLは今まで持っていたバックエンドの責務をフロントエンドに移譲する側面があります。それによって、UIに変更が加えられるたびにAPI修正をする必要がなくなりアジリティが高まります
またLambdaでREST APIを記述すると、エンドポイントの数だけLambdaを作ることになる(そうしない方法もあるが)が、GraphQLは単一エンドポイントなので「スモールな構成」を維持できます
GraphQLはフロントエンドから利用する時に非常に扱いやすく、これは体感ですがRESTよりもフロントエンドの開発スピードは高くなっている気がします

とはいえ、RESTの方が向いている処理もあります。例えばファイルアップロードなどはGraphQLでやろうとするとひと工夫が必要です。そういう場合はRESTのエンドポイントを併用することは全く問題ありません。API Gatewayを間におくことで同じドメインで対応が可能です。

デプロイについて

API Gateway + Lambda(GraphQL) + Lambda(REST) + DynamoDB の構成のデプロイはServerlessFrameworkを使えば簡単に可能です。
こちらに各言語のテンプレートが用意されているので探してデプロイまでやってみてください。その手軽さに驚くと思います。
ちなみに自分はこちらのテンプレートをカスタマイズして使いやすいようにしてから使っています。

認証認可について

認証認可はログイン機能などを実装する際はCognitoを活用します。開発スピードを意識すると自前実装しちゃった方が早い時もあったりしますが、セキュリティに関わるのでマネージドサービスを使った方がアンパイでしょう。
こちらもServerlessFrameworkで追加可能です。

最終形

aws.png

以上をまとめると上の図のような構成になりました。

まとめ

「アジリティ」「コスト最適化」「スモールな構成」「開発スピード」という観点でWebアプリケーションのアーキテクチャを考えてみました。

  • ServerlessFrameworkを使い倒す
  • フロントエンドはS3 hosting + CloudFrontで。SSRもLambda@Edgeでできます
  • データベースはRDSは使わずにDynamoDBで
  • APIは基本的にGraphQL。必要に応じてRESTも簡単に追加できるよ。

がポイントだと思います。
個人開発/スタートアップに関わる方々の参考になればと思います。
もし興味がある人がいれば自分のテンプレートも公開しようかなと思います。
ありがとうございました。

追記 DynamoDBについて

DynamoDBをメインのDBにするところに非常に批判が殺到しているので少し追記します。
言いたいことは3つあります。

1、RDSのインスタンスコストは従量課金できないこと

全くアクセスがなくても月2000円~くらいかかってしまうのが惜しいところで、ほぼ無料で始められるサーバレスの1番のコストペインポイントだと思っています。個人開発だとここだけでDynamoを採用する大きな理由になると思います。事業としてやるならこれくらいのコストは誤差、というのは理解できます。

2、DynamoDBは思っている以上に表現力が豊か

「Dynamoは制約が多いから採用は慎重にすべき」は部分的に正しいと思います。ただ、DynamoDBの設計のプラクティスがあって、例えばAdjecent Patternを使えばMany to Manyの関係でも全く問題なくアクセスすることが可能です。検索条件が複雑でGSIを使い切るとか複合キーが複雑になるなど特殊な状況でない限りRDSを渇望するような事態にはならないと思っていて、十分にDynamoDBのポテンシャルを活かせていない可能性が高いと思っています。

3、DynamoDBは最初にサクサク開発したいから

記事に書いているようにDynamoDB採用の主な理由はアジリティ・開発速度・コストです。
プロダクトマーケットフィットしていないプロダクトは後の技術的負債を心配することよりも、小さく素早く失敗できるようにしておくことを重視すべきと思っています。

とは言ったもののRDBもいいよね

本心を言うとRDB+ORMで十分に開発速度とアジリティは出ると思っていて、インスタンスコストをケチりたかったと言うのが本心かもしれません。なのでDynamoの部分をRDS Proxy + RDSに置き換えるのは全く藪さかではありません

その他の意見について

「firebase, Netlify, Heroku, Amplifyでいいんじゃないか」説は、そうかもです。
ServerlessFrameworkとAWSを使うことのメリットは、「プロバイダ側の制約がない(無料枠だとコールドスタートがあるとか、アクセスレートがあるとかがない)」ことと「リソースに対して自由度が高い(CloudFormationで書けるので)ので発展性がある」というところかなと思います。

yuno_miyako
TypeScript 好き。
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント

サーバサイドをserverless、dbをdynamoにした場合ってテストはどうするのが一般的なんでしょうか?
個人開発なのでテストは書かない、みたいな感じでしょうか?

@sateen
単体テストについては自分も手探りです。
・ LambdaのテストはServerlessFrameworkでローカルに実行が可能です。
・ Dynamoのテストはローカルでモックすることも可能ですが、自分はテーブルの向き先を変えてテストします。

あとはおっしゃる通り、個人開発だとGraphQLのPlaygroundで手動テストして終わりってことはよくありますね。

@yuno_miyako
返信ありがとうございます。なるほどー。
servelessでlambdaだとユニットテストのフレームワークは何を使うのが一般的なんですか?(JSなのかpythonなのかも気になります)

手動テストだと、個人開発なら良いですが、ベンチャーだと、記事中にある開発スピード、アジリティの観点ではあんまりだと思うので最低でも正常系はテスト書いておかないとなかなか厳しいように感じますね。

@seteen
ServerlessFrameworkを使えばlambdaだから特別なユニットテストが必要ということはないです。開発体験自体はサーバありの場合とそう変わりないです。なのでTypeScriptならmochaを使ったり、Pythonならpytestを使ったりと、普通と変わりません。

そうですね。瞬間風速的なスピードが重要なケースを除けば、ユニットテストを書くことによって開発スピードとアジリティが高まるので必須ですね。

@yuno_miyako

なるほど!普通にmochaとかなんですね!
DynamoDBの場合、データ用意したり消したりとかを良い感じにやってくれるものとかあれば良い感じになりそうですね!! この当たりの知見ないので調べないとな…。

興味深く読ませていただきました。
CloudFrontの料金ですが、正しくは0.114 USD/GB (10TBまで)ですね。
10TBを超えるとGBあたりが安くなります。
https://aws.amazon.com/jp/cloudfront/pricing/

@motoy3d
ありがとうございます!深刻なミスですね、修正いたします。

最近も似たようなアーキテクチャ考えて、記事と一致しすぎて驚きました。
Next.jsにもSSGが追加され&推奨されているので、SSG+S3 Hostingもありかも知れません。
あとはBOTの対策としてWAFをAPI Gatewayに掛けるのもいいかも。

ネイティブアプリのアの字も出てこない潔さに感服です!!

(編集済み)

学習コストを開発速度の観点に置いて軽視しすぎでは?と思いました
本当に開発スピード重視の状況なら学習コストなんてかけてられず手持ちの知識で戦うしかないわけです

そうしたときにRDBしか扱ってない人間がDynamoDBの設計や使い方を学習するのはかなりコストが高いと感じています
両方未経験の場合でも、RDBの方が直感的で学習しやすいのではと思います(ここは完全に個人の意見ですが)
また、学習しながらで作成したシステムは色々な妥協やアンチパターンなどの負債が残ることが多いです

RDBでもDynamoDBでも同程度に扱える人がどっちを選ぶか?というときにランニングコストの観点からDynamoDBを推すのには納得しますが(もちろん要件によるっていうのはありますがそこは一旦置いといて)、
じゃあ学習コストをかけてでも選ぶべきか?というと、学習コストには開発速度の問題だけでなく学習期間の人件費という金銭コストの面の問題もあるので、結局コスト比較でもメリットはそこまでかなと思いました

@seteen
dockerで処理仮想化させてローカルで全部検証するっていうのはありですかね。
DynamoDBまでdockerイメージありますし

記事中のほぼすべての技術に触れたことのない人(わたしです)が、
この記事に感化されて記事通りの構成でサービスを作りたいとなった場合に最適な、資料と学習フローがありましたら教えていただきたいです。

(編集済み)

私はgraphqlに反対します!
frontendもbackendも学習費用が高くなります
backendプログラマーはN+1問題とcache問題を考えなければならないです
graphqlを実際に経験してみたら後悔しか残らないです

最強はawsではなく、firebaseなのでは...

サービス利用規約に基づき、このコメントは削除されました。
サービス利用規約に基づき、このコメントは削除されました。

hasura(Postgresqlデータベースを基にしたGraphql APIサーバーがコーディングレスで作れるdockerコンテナ)使えばDynamoDB学習コストとAPIサーバー作成コストが大幅に削減できる気がする(適当

(編集済み)

2年くらい前ですが似たような構成の実例です。

https://qiita.com/asmsuechan/items/17f168f151346ac5cf65

ちなみにLambdaで動かしてて確かにコストは非常に安かったのですが、LambdaでSSRするとコールドスタートで最悪10秒くらい待たされることがあったので結局フロントエンドはFargateに乗せ換えました。

何が最強なもんか。そもそも正解はない、という前提ならせめて最強の意味ぐらい定義しよう。DynamoDBが使いこなせないスタートアップはダメなのか? 正解のない最強なら、せめて選択肢を示すとか、実体験や実サービス、実コストを明記すべき。もう釣りタイトルには本当に飽き飽きしているんですよ。

@asmsuechan
Lambdaのコールドスタートについて初耳でした。
非常にためになりました。貴重な情報をありがとうございます。

この記事を読んでみたところ、現在はすこし改善はされているようですが、
それでも意識しとかないとな、と思いました。

@sta1 一応定義というか何の要件を優先して選定しているのかは書いてるとは思うよ(選定が妥当だとは言ってない

@seteen

jestを使えば、dynamodbをローカルで起動させながら、unitテストを実行することができます。

https://jestjs.io/docs/ja/dynamodb

あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした