Open4

「RustによるWebアプリケーション開発」学習記録

MasaHeroMasaHero

1章 本書で開発するもの

蔵書管理システムの要件定義を説明している。

内容

  • 作成するAPIの一覧
    • ユーザーの管理のCRUD
    • 蔵書の管理CRUD
    • 本の貸し借りCRUD
  • DBモデル
  • システム構成
    • TypeScriptでSPA、APIサーバー、PostgreSQL、Redis
    • キャッシュでアクセストークン有効性管理
      • 本書では簡易的な独自実装→応用として、Auth0やFirebase AuthnicationなどのIDaaSを使うのも良さそう
MasaHeroMasaHero

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 --from=builder ./app/target/release/app ./target/release/app

ENV PORT 8080
EXPOSE $PORT
ENTRYPOINT [ "./target/release/app" ]

(※)マルチステージビルドは、ビルド環境と実行環境を分離することで、最終的なイメージサイズを小さくする手法

  1. ビルドステージ (builder) では必要なツールやライブラリを含む大きなイメージを使用
  2. 実行ステージでは最小限のランタイムのみを含む軽量イメージを使用
  3. ビルドステージでビルドした成果物のみを実行ステージにコピー
    これにより、開発ツールなどの不要なファイルを含まない軽量な本番用イメージを作成できる。
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"]

サンプルコード

https://github.com/rust-web-app-book/rusty-book-manager

2章まで作ったプロジェクトをレポジトリ化

https://github.com/Yuki2Kisaragi/rust-web-template

MasaHeroMasaHero

3章 最小構成アプリケーションの実装

axumのデザイン

axum = tokio + hyper + tower

  • axum : ハンドラとルーターを提供する
  • tokio : 非同期処理の実行基盤
  • hyper : HTTPのサーバー/クライアントを立ち上げる機能
  • tower : タイムアウト・レートリミット・認証・ロードバランシングをService, Layerという抽象単位にしてプラグインのように提供する

tower

https://tokio.rs/blog/2021-05-14-inventing-the-service-trait

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計算は他のグリーンスレッドの実行を妨げる
      • ランタイムのオーバーヘッド
        • スケジューラーの実装が必要
        • メモリ管理の複雑さ
MasaHeroMasaHero

3章:ヘルスチェックAPI実装

cargo-nextest

  • Rustのテストランナー
  • 不安定なテスト(フレーキーテスト)に対して指定回数実行できる
  • テストケースごとに並列実行できる。