Open4
「RustによるWebアプリケーション開発」学習記録
1章 本書で開発するもの
蔵書管理システムの要件定義を説明している。
内容
- 作成するAPIの一覧
- ユーザーの管理のCRUD
- 蔵書の管理CRUD
- 本の貸し借りCRUD
- DBモデル
- システム構成
- TypeScriptでSPA、APIサーバー、PostgreSQL、Redis
- キャッシュでアクセストークン有効性管理
- 本書では簡易的な独自実装→応用として、Auth0やFirebase AuthnicationなどのIDaaSを使うのも良さそう
2章 環境構築の開発
普段はローカルでRustプログラムを実装していたので、コンテナ内でのRust開発は初だった。
コンテナ
実行ファイルサイズの削減のため、ビルド用コンテナと実行用コンテナを分離する。
# マルチステージビルド(※)を使い、Rustのプログラムをビルドする
FROM rust:1.78-slim-bookworm AS builder
WORKDIR /app
ARG DATABASE_URL
ENV DATABASE_URL=${DATABASE_URL}
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
WORKDIR /app
RUN adduser book && chown -R book /app
USER book
COPY ./app/target/release/app ./target/release/app
ENV PORT 8080
EXPOSE $PORT
ENTRYPOINT [ "./target/release/app" ]
(※)マルチステージビルドは、ビルド環境と実行環境を分離することで、最終的なイメージサイズを小さくする手法
- ビルドステージ (builder) では必要なツールやライブラリを含む大きなイメージを使用
- 実行ステージでは最小限のランタイムのみを含む軽量イメージを使用
- ビルドステージでビルドした成果物のみを実行ステージにコピー
これにより、開発ツールなどの不要なファイルを含まない軽量な本番用イメージを作成できる。
services:
app:
build:
context: .
dockerfile: Dockerfile
network: host
redis:
image: redis:alpine
ports:
- 6379:6379
postgres:
image: postgres:15
ports:
- 5432:5432
volumes:
- db:/var/lib/postgres/data
environment:
# DBに接続するユーザー名
POSTGRES_USER: app
# DBに接続するためのPASSWORD
POSTGRES_PASSWORD: passwd
# DB名
POSTGRES_DB: app
volumes:
db:
driver: local
cargo-make
タスクランナーとしてcargo-make
を導入する。
tomlファイルでRUNコマンドやWATCHコマンドなどを登録できる。
cargo install --force cargo-make
# cargo-makeで実行するコマンドで共通の環境変数
# println!("global:{}", std::env::var("GLOBAL").unwrap());
[env]
GLOBAL = "global env"
# extendでタスク毎で追加できる環境変数
# println!("local:{}", std::env::var("LOCAL").unwrap());
[tasks.set-env-local.env]
LOCAL = "local env"
[tasks.run]
extend = "set-env-local"
command = "cargo"
args = ["run"]
サンプルコード
2章まで作ったプロジェクトをレポジトリ化
3章 最小構成アプリケーションの実装
axumのデザイン
axum = tokio + hyper + tower
- axum : ハンドラとルーターを提供する
- tokio : 非同期処理の実行基盤
- hyper : HTTPのサーバー/クライアントを立ち上げる機能
- tower : タイムアウト・レートリミット・認証・ロードバランシングを
Service
,Layer
という抽象単位にしてプラグインのように提供する
tower
tower::Service
- トレイト
- tower内の抽象的概念
- 任意のRequestを受け取り、任意のResponseを返す
- このトレイトに様々な機能を実装させる
- tower::layer::Layerでサービス群を管理する
- axumは、複数のサービスやレイヤーが重なってできている
非同期プログラミング
非同期プログラミングとは、実行フローを止めずに、時間がかかる処理を実行できるプログラミング手法。
- ランタイム内に軽量なスレッド(グリーンスレッド)を生成・管理させる
- I/Oの多重化
- 複数のI/Oを並行処理して、I/Oの待ち時間にCPUに別の処理をさせる
RustはFutureトレイトを使って非同期タスクを生成し管理させる。
参考:https://blog.ojisan.io/server-architecture-2023/
グリーンスレッド
- グリーンスレッド=OSが生成するネイティブスレッドより軽量なスレッド
- メリット
- スケーラビリティ
- 数万単位の同時実行が可能
- システムリソースの効率的な使用 - パフォーマンス
- コンテキストスイッチが高速
- メモリオーバーヘッドが小さい
- 開発の容易さ
- 同期的なコードスタイルで非同期処理を記述可能
- デッドロックの検出が容易
- デメリット:
- システムコール時のブロッキング
- 1つのグリーンスレッドがブロックすると、同じOSスレッド上の他のグリーンスレッドも影響を受ける可能性
- CPU負荷の高い処理への不適合
- 長時間のCPU計算は他のグリーンスレッドの実行を妨げる
- ランタイムのオーバーヘッド
- スケジューラーの実装が必要
- メモリ管理の複雑さ
- システムコール時のブロッキング
- スケーラビリティ
3章:ヘルスチェックAPI実装
cargo-nextest
- Rustのテストランナー
- 不安定なテスト(フレーキーテスト)に対して指定回数実行できる
- テストケースごとに並列実行できる。