Weitere ähnliche Inhalte
Ähnlich wie Node.jsアプリの開発をモダン化するために取り組んできたこと (20)
Mehr von bitbank, Inc. Tokyo, Japan (20)
Node.jsアプリの開発をモダン化するために取り組んできたこと
- 2. Copyright © bitbank, inc.
自己紹介
❏ ビットバンクでサーバーサイドを担当
❏ Node.js, TypeScript
❏ 前職ではモバイルゲームを開発
❏ C++, C#, PHP, etc.
Daiki Yokoi
- 3. Copyright © bitbank, inc.
テーマとアジェンダ
各所をモダン化して組織の拡大に耐える
❏ ソースコード編
❏ TypeScript + Nestフレームワークの採用
❏ ユニットテスト環境編
❏ Docker + モックツールの活用
❏ インフラ(AWS)編
❏ Infrastructure as Code による サーバーレス環境の構築
- 4. Copyright © bitbank, inc.
テーマとアジェンダ
各所をモダン化して組織の拡大に耐える
❏ ソースコード編
❏ TypeScript + Nestフレームワークの採用
❏ ユニットテスト環境編
❏ Docker + モックツールの活用
❏ インフラ(AWS)編
❏ Infrastructure as Code による サーバーレス環境の構築
- 5. Copyright © bitbank, inc.
❏ 既存のアプリは Node.js (v6) + Express という構成
❏ データ構造がわかりにくく、可読性が低い
❏ 新規メンバーのキャッチアップコストが高い
❏ TypeScript化のメリット
❏ コンパイラによる静的なチェック
❏ IDEによる強力なサポート
❏ フロントエンドチームとの言語共通化
素のNode.jsからTypeScriptへ
ソースコード編 ▶ 概要
- 6. Copyright © bitbank, inc.
TypeScript化と同時により効率的な開発手法を模索
❏ DIの導入
❏ 依存関係を強く意識してよりテスタブルな構成に
❏ ルーティング効率化
❏ ハンドラの登録を自動化できないか
❏ APIドキュメント
❏ スプレッドシートでの手動管理から脱却
❏ なるべくコストをかけずSwaggerUIに繋ぎこみたい
ソースコード編 ▶ 概要
- 7. Copyright © bitbank, inc.
❏ InversifyJS [1]
❏ IoCコンテナ
❏ routing-controllers [2]
❏ デコレータでハンドラを登録できる
❏ tsoa [3]
❏ APIドキュメントの自動生成
[1] http://inversify.io/
[2] https://github.com/typestack/routing-controllers
[3] https://github.com/lukeautry/tsoa
いくつかのツールを試してみた
ソースコード編 ▶ 概要
- 8. Copyright © bitbank, inc.
これらが統合されたフレームワークが欲しくなった
❏ 各ツールの守備範囲が微妙に噛み合わない
❏ ツールごとの前提条件や制約もあり、まとめ上げるのが少し面倒
ソースコード編 ▶ 概要
- 10. Copyright © bitbank, inc.
Nestを使ってみることに
❏ もともと欲しかった機能が一通り揃っていた
❏ オリジナルのDIコンテナ
❏ デコレータでのハンドラ登録
❏ APIドキュメントの生成
❏ ポピュラーになりそう
❏ 日本語情報が全くない中、GitHubのスターは5000超え (2018/3時点)
❏ Angularのカンファレンスにも登場
ソースコード編 ▶ 概要
- 11. Copyright © bitbank, inc.
❏ 簡単に言うとクラスの集まり
❏ 例)UserModule = UserController + UserService + …
❏ 関係性の強いクラスでまとめるべき
❏ 複数のモジュールがツリー状になって一つのアプリを構成
❏ 頂点のモジュールは ApplicationModule と命名されるのが一般的
フレームワーク独自のモジュールという概念がある
ソースコード編 ▶ Nest入門
- 13. Copyright © bitbank, inc.
❏ コントローラー
❏ 各APIのリクエストハンドラを持つクラス
❏ リクエストとレスポンスを扱い、ロジックは別クラスに任せる
❏ プロバイダ
❏ コントローラー以外のクラス
❏ ロジックを持ち、コントローラーや別のプロバイダから呼び出される
モジュール内の各クラスは以下の2つに分類される
ソースコード編 ▶ Nest入門
- 14. Copyright © bitbank, inc.
コントローラーではデコレータでハンドラを登録する
@Controller(‘users’)
class UserController {
@Get(‘:id’)
get(@Param(‘id’) id: string) {
// GET /users/1 等をハンドリングする
}
@Post()
create(@Body() body: CreateUserRequest) {
// POST /users をハンドリングする
}
}
ソースコード編 ▶ Nest入門
- 15. Copyright © bitbank, inc.
プロバイダは各種ビジネスロジックを担当
UserModule
UserController UserService
UserDetailService
SessionService
Controllers Providers
ソースコード編 ▶ Nest入門
- 16. Copyright © bitbank, inc.
依存関係はコンストラクタの実装を元に解決される
@Controller(‘users’)
class UserController {
constructor(private readonly userService: UserService) {}
// 略
}
---
@Injectable()
class UserService {
constructor(private readonly userDetailService: UserDetailService) {}
// 略
}
---
class UserDetailService {
ソースコード編 ▶ Nest入門
- 17. Copyright © bitbank, inc.
❏ ツリー状のモジュール群がアプリケーションを構成
❏ トップのモジュールは Application Module と命名する
❏ モジュールは関係性の強いクラスの集合
❏ コントローラークラス => リクエストハンドラ
❏ プロバイダクラス => ビジネスロジック
❏ クラス間の依存関係はコンストラクタの実装を元に解決される
❏ モジュールをまたぐクラス間の依存関係の解決手段もある
ここまでのおさらい
ソースコード編 ▶ Nest入門
- 18. Copyright © bitbank, inc.
@Module({
imports: [UserModule]
})
export class ApplicationModule {}
---
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module.ts';
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
await app.listen(3000);
}
bootstrap();
モジュールが作成できたらサーバーを起動できる
ツリーのトップのモジュールを渡す
ソースコード編 ▶ Nest入門
- 19. Copyright © bitbank, inc.
❏ コントローラの実装を解析してドキュメントを生成する
❏ 必要に応じてデコレータで追加の情報を与えられる
APIドキュメントを自動で作成し、SwaggerUIに連携
ソースコード編 ▶ Nestレシピ集
- 20. Copyright © bitbank, inc.
ロギングやレスポンスの整形に便利なインターセプタ
❏ ハンドラの実行前後に処理を差し込むことができる
❏ IDを振ってリクエスト開始時と終了時のログを紐づけることができる
❏ レスポンスにメタデータを追加したりするのにも便利
❏ いわゆるアスペクト指向プログラミングをサポートしてくれる
ソースコード編 ▶ Nestレシピ集
- 21. Copyright © bitbank, inc.
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(contexct: ExecutionContext, call$: Observable<any>): Observable<any> {
const requestId = uuid.v4();
console.log(`[Before] requestId: ${requestId}`);
return call$.pipe(
tap(() =>
console.log(`[After] requestId: ${requestId}`)
),
);
}
}
ロギングやレスポンスの整形に便利なインターセプタ
一つのメソッド内で
前後の処理を記述できる
ソースコード編 ▶ Nestレシピ集
- 22. Copyright © bitbank, inc.
リクエストパラメータと同時に定義するバリデーション
class CreateUserRequest {
@Length(10, 100)
@IsEmail()
readonly mail!: string;
}
@Controller(‘users’)
class UserController {
@Post()
create(@Body() body: CreateUserRequest) {
// 新規ユーザーを作成
}
}
この時点では以下が全て完了している
- リクエストボティのパース
- 指定したクラスへの変換
- バリデーションの実行
各バリデーションをデコレータで定義
- class-validatorが提供するもの
- カスタマイズも可能
ソースコード編 ▶ Nestレシピ集
- 23. Copyright © bitbank, inc.
ガードによってアクセスコントロールを実現
❏ ガードは不正なリクエストを弾くための仕組み
❏ canActivateというインターフェースが用意されている
❏ リクエストデータを受けてboolean値を返すメソッドを実装するだけ
❏ 登録されたハンドラの実行前に評価される
❏ ビットバンクでの事例
❏ 起動時にNestの全モジュールをスキャンしてAPI一覧を動的に取得
❏ これを最新のマスターデータとして各ユーザーに紐付ける
❏ ガード内で呼び出そうとしているAPIの権限があるかをチェック
ソースコード編 ▶ Nestレシピ集
- 24. Copyright © bitbank, inc.
❏ TypeORMはポピュラーな O/R Mapper
❏ 基本的なユースケースはほぼカバーされており開発効率が上がる
❏ トランザクションやパーティショニングが絡むと少し手間だが許容範囲
❏ どうにもならなければSQLを直接書くこともできる
❏ マイグレーションの仕組みも用意されている
❏ 開発コミュニティも活発
TypeORMでのデータベースアクセスをサポート
ソースコード編 ▶ Nestレシピ集
- 25. Copyright © bitbank, inc.
❏ @nestjs/typeorm パッケージ
❏ TypeORMのリポジトリクラス等を他のクラスと同じように簡単にDI できる
TypeORMでのデータベースアクセスをサポート
ソースコード編 ▶ Nestレシピ集
- 27. Copyright © bitbank, inc.
その他
❏ @nestjs/testing パッケージ
❏ モジュール内の一部のクラスをモックに差し替えることができる
❏ 依存先が多いクラスをテストする際に便利
❏ コントローラー部分はREST以外にも対応可能
❏ GraphQL, WebSocket, gRPC, etc.
❏ MVCにも対応可能
ソースコード編 ▶ Nestレシピ集
- 29. Copyright © bitbank, inc.
テーマとアジェンダ
各所をモダン化して組織の拡大に耐える
❏ ソースコード編
❏ TypeScript + Nestフレームワークの採用
❏ ユニットテスト環境編
❏ Docker + モックツールの活用
❏ インフラ(AWS)編
❏ Infrastructure as Code によるサーバーレス環境の構築
- 30. Copyright © bitbank, inc.
当時のユニットテスト環境と課題
❏ ローカルにインストールしたMySQLやRedisを利用
❏ バージョンや設定が人によって異なる
❏ 人によってユニットテストが通ったり失敗したりする
❏ git管理されていない内容なのでコミュニケーションコストが発生する
❏ AWSの認証情報を使って直接AWSのサービスを利用
❏ 認証情報を全員が持たないといけない
❏ 他の人が認証情報を更新するとユニットテストが失敗するようになる
❏ 専用のS3バケットやKinesisストリームを用意する必要がある
ユニットテスト環境編 ▶ 概要
- 31. Copyright © bitbank, inc.
再現性が高く外部に依存しない構成へ
❏ 可能なものはコンテナを使用
❏ MySQL
❏ Redis
❏ LocalStack(AWS S3, Kinesis, etc.)
❏ その他はテストツールでモッキング
❏ Jest
❏ MockDate
ユニットテスト環境編 ▶ 概要
- 32. Copyright © bitbank, inc.
ユニットテストでのDockerの利用
❏ Docker Compose
❏ 単一ホスト上でのオーケストレーションツール
❏ 使用したいコンテナの情報をYAMLで記述
❏ Dockerに馴染みがない場合でもキャッチアップしやすい
❏ CI連携
❏ Dockerコンテナをテストに利用できるサービスが多く親和性が高い
ユニットテスト環境編 ▶ Docker
- 34. Copyright © bitbank, inc.
テストツールも刷新し、モックを積極活用
ユニットテスト環境編 ▶ モックツール
❏ Jest
❏ Facebook製のテスティングフレームワーク
❏ デフォルトでマルチプロセスで処理するので高速
❏ チェック用のメソッドやモック機能が豊富
❏ MockDate
❏ Dateをモック化するための軽量パッケージ
- 35. Copyright © bitbank, inc.
❏ AWS StepFunctions の実行ステータスチェックをモック化
インスタンスのメソッドをモック化するサンプル
ユニットテスト環境編 ▶ モックツール
- 39. Copyright © bitbank, inc.
テーマとアジェンダ
各所をモダン化して組織の拡大に耐える
❏ ソースコード編
❏ TypeScript + Nestフレームワークの採用
❏ ユニットテスト環境編
❏ Docker + モックツールの活用
❏ インフラ(AWS)編
❏ Infrastructure as Code によるサーバーレス環境の構築
- 40. Copyright © bitbank, inc.
ポイント
インフラ(AWS)編 ▶ 概要
❏ サーバーレス環境の探求
❏ Elastic Beanstalk から ECS Fargate への移行
❏ Infrastructure as Code の実践
❏ インフラ構成を CloudFormation / SAM で積極的にコード化
- 42. Copyright © bitbank, inc.
Elastic Beanstalk の運用
❏ Good
❏ とにかく簡単にアプリケーションの実行環境を用意できる
❏ イミュータブルなデプロイが可能
❏ EC2インスタンス削除時のオートリカバリー
❏ Bad
❏ EC2の管理コスト
❏ 色んなことを裏側でやってくれる反面、柔軟にコントロールできない
❏ アプリのアーカイブが1000件を越えるとデプロイに失敗する
インフラ(AWS)編 ▶ サーバーレス環境の探求
- 43. Copyright © bitbank, inc.
Elastic Beanstalk の運用
❏ 合うケース
❏ インフラに割くことができるリソースが少ない
❏ あれこれ設定するよりも、AWSが提案する型に乗っかりたい
❏ 合わないケース
❏ インフラを重点的に見れる人がいる
❏ より柔軟にインフラ構築していきたい
インフラ(AWS)編 ▶ サーバーレス環境の探求
- 45. Copyright © bitbank, inc.
Fargate 概要
❏ フルマネージド
❏ EC2(OS, Docker Agent, ECS Agent, etc.)の管理から脱却
❏ スケーラブル
❏ シームレスにスケールアップ・アウト可能
❏ 使用したリソースに対してのみ課金(秒単位)
❏ 他のAWSサービスとのインテグレーションが容易
❏ VPC, ELB, IAM, Cloud Watch Logs, etc.
インフラ(AWS)編 ▶ サーバーレス環境の探求
- 46. Copyright © bitbank, inc.
❏ OSや各エージェントにパッチを当てる
❏ ディスクの逼迫を防ぐためのログローテーション
コンテナ環境でも引き続きEC2環境の管理が必要
インフラ(AWS)編 ▶ サーバーレス環境の探求
- 47. Copyright © bitbank, inc.
インフラ(AWS)編 ▶ サーバーレス環境の探求
❏ 開発者はアプリケーションとコンテナを作ることに集中
❏ インフラの管理は全面的にAWSにお任せする
Fargateでより効果的な役割分担が可能に
- 48. Copyright © bitbank, inc.
ECSの構成要素について
インフラ(AWS)編 ▶ サーバーレス環境の探求
❏ ECS Task
❏ アプリケーションの実行単位
❏ 1つ又は複数のコンテナで構成される
❏ ECS Service
❏ 紐づけられた1つの ECS Task の実行数を制御
❏ ECS Cluster
❏ インフラやセキュリティの分割単位
❏ 1つ又は複数の ECS Service で構成される
- 49. Copyright © bitbank, inc.
Fargateのスケーリング
インフラ(AWS)編 ▶ サーバーレス環境の探求
❏ ECS Task
❏ 必要なコンピューティングリソース(CPU/Memory)を設定
❏ コンテナが複数存在する場合は上記がシェアされる
❏ リソースが枯渇した場合は即座に停止されるのでバッファが必要
❏ ECS Service
❏ Taskの実行数を設定してロードバランサーと連携
- 52. Copyright © bitbank, inc.
❏ ドキュメンテーションコストの増加
❏ 手動で構築するとより詳細なドキュメントを残す必要がある
❏ アプリケーション数に比例してドキュメンテーションコストも増加
❏ 権限管理を厳格化した副作用
❏ 権限を持った人が手動で構築するのを待つ必要がある
AWSコンソールのみでの運用の限界
インフラ(AWS)編 ▶ Infrastructure as Code の実践
- 53. Copyright © bitbank, inc.
❏ CloudFormation
❏ Serverless Application Model (SAM)
❏ CloudFormation の拡張命令セット
❏ Lambda とその周辺コンポーネントをより簡潔に表現できる
AWSのインフラをコード化する
インフラ(AWS)編 ▶ Infrastructure as Code の実践
- 54. Copyright © bitbank, inc.
❏ コンポーネントの構成を視覚化できる
❏ ドラッグ&ドロップによるリソース追加や依存関係の構築
❏ 各リソースのプロパティ名をオートコンプリート (Ctrl + Space)
Tip1: CloudFormation Designer を使ってみる
インフラ(AWS)編 ▶ Infrastructure as Code の実践
- 55. Copyright © bitbank, inc.
❏ 一度に大量に更新すると、失敗した際のロールバックが長い
❏ git でのバージョン管理と相性が良くなる
Tip2: 少しずつ記述してデプロイする
インフラ(AWS)編 ▶ Infrastructure as Code の実践
- 56. Copyright © bitbank, inc.
❏ そのリソースはどんなプロパティを持つのか
❏ コンソールでは意識せずに使ってきたプロパティもこの機会に確認
❏ そのリソースにはどんなアクションが紐付くのか
❏ 最小限の IAM Policy を作成する際に必要になる
❏ そのリソースに対する Ref 組み込み関数が何を返すのか
❏ ARN の場合もあれば ID の場合もある
❏ 必要に応じて GetAtt 組み込み関数と使い分ける
❏ リソース間に依存関係を持たせる場合に確認する必要がある
Tip3: リソースを見る観点を理解する
インフラ(AWS)編 ▶ Infrastructure as Code の実践
- 57. Copyright © bitbank, inc.
❏ 再作成されるリソースの有無は特に注意して確認する
❏ CLIの deploy コマンド実行前に ChangeSet を作成する
❏ --no-execute-change-set オプションをつける
❏ もしくは create-change-set コマンドで代替
❏ describe-change-set コマンドで差分を表示する
Tip4: デプロイ前に変更内容を確認する
インフラ(AWS)編 ▶ Infrastructure as Code の実践
- 59. Copyright © bitbank, inc.
テーマとアジェンダ
各所をモダン化して組織の拡大に耐える
❏ ソースコード編
❏ TypeScript + Nestフレームワークの採用
❏ ユニットテスト環境編
❏ Docker + モックツールの活用
❏ インフラ(AWS)編
❏ Infrastructure as Code による Serverless 環境の構築