じゃあ、おうちで学べる

本能を呼び覚ますこのコードに、君は抗えるか

RustでのProtocol Buffersを学習するための図書管理システム実装

はじめに

Protocol BuffersとRustの実践的な学習を目的として図書管理システムを開発しました。 システムの構築にあたってはBufによるスキーマ管理とコード生成を採用することで開発環境を実現しています。今回の実装を通じてRustにおけるgRPCサービスの構築手法について理解を深めることができました。

buf.build

フロントエンド開発については今後の課題として検討しています。 Remixが気になっているので実装したいと思ってます。本記事ではバックエンド実装に焦点を当てて解説します。 特にRustのエコシステムにおけるtonicやSQLxといったライブラリの活用方法に着目します。これらの実装を通じて得られた知見は他のRustプロジェクトにも応用可能な内容となっています。

学習目的で実装したコードなので何かに活用していただければ幸いです。

github.com

プロジェクトのセットアップ

まず、以下のようなディレクトリ構造を作成します:

library-system/
├── buf/
│   ├── buf.yaml
│   ├── buf.gen.yaml
│   └── library/
│       └── v1/
│           └── library.proto
├── library-server/
└── library-client/

Bufの設定

buf.yaml:

version: v1
name: buf.build/yourusername/library-system
breaking:
  use:
    - FILE
lint:
  use:
    - DEFAULT

buf.gen.yaml:

version: v1
plugins:
  - plugin: buf.build/protocolbuffers/rust
    out: ../library-server/src
    opt:
      - bytes=bytes
  - plugin: buf.build/community/neoeinstein-tonic-rust
    out: ../library-server/src
    opt:
      - no_client=false
      - no_server=false

APIの設計

Protocol Buffersを使用してAPIを定義します:

syntax = "proto3";
package library.v1;

import "google/protobuf/timestamp.proto";

service LibraryService {
    rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
    rpc GetUser(GetUserRequest) returns (GetUserResponse);
    rpc SearchBooks(SearchBooksRequest) returns (SearchBooksResponse);
    rpc CreateLoan(CreateLoanRequest) returns (CreateLoanResponse);
    rpc ReturnBook(ReturnBookRequest) returns (ReturnBookResponse);
}

図書管理システムの実装詳細

アーキテクチャと技術選定

システムの基盤には非同期処理による高パフォーマンスな実装を採用しました。 Rustの非同期ランタイムであるtokioを活用することでリソースの効率的な利用を実現します。サーバーの起動処理は以下のコードで示すように非同期処理を基本とした設計です。

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let service = LibraryServiceImpl::new(&database_url).await?;
    Server::builder()
        .add_service(LibraryServiceServer::new(service))
        .serve(addr)
        .await?;
    Ok(())
}

サーバーサイドの主要実装

Protocol Buffersによるサービス定義が実装の起点となります。 サーバーの中核機能はLibraryServiceImpl構造体に集約されます。SQLxを用いたデータベース操作により型安全性の高いコードを実現しました。

貸出処理のトランザクション制御は特に慎重な実装を必要としました。 以下のコードでは書籍の貸出状態確認から更新までを単一トランザクションで処理します。

async fn create_loan(&self, request: Request<CreateLoanRequest>) -> Result<Response<CreateLoanResponse>, Status> {
    let mut tx = self.pool.begin().await?;
    let book = sqlx::query_scalar::<_, bool>("SELECT available FROM books WHERE id = ?")
        .bind(&req.book_id)
        .fetch_optional(&mut *tx)
        .await?;

    if !book.available {
        return Err(Status::failed_precondition("Book is not available"));
    }
    // 貸出処理の実行
    tx.commit().await?;
    Ok(Response::new(loan_response))
}

クライアントサイドの実装

ユーザーインターフェースは直感的な操作を重視しました。 クライアントの初期化処理は以下のように簡潔な実装としています。

async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let channel = Channel::from_static("http://[::1]:50051").connect().await?;
    let client = LibraryServiceClient::new(channel);
}

エラー処理は利用者の視点を重視した実装としました。 サーバーからのエラーレスポンスを適切にハンドリングすることでユーザーへの明確なフィードバックを実現します。

データベース設計

データモデルは業務要件を正確に反映する設計としました。 ユーザー情報と書籍情報を管理する基本テーブルに加えて貸出履歴を記録するテーブルを実装します。

CREATE TABLE loans (
    id TEXT PRIMARY KEY,
    book_id TEXT NOT NULL REFERENCES books(id),
    user_id TEXT NOT NULL REFERENCES users(id),
    loan_date TIMESTAMP NOT NULL,
    due_date TIMESTAMP NOT NULL,
    return_date TIMESTAMP
);

スキーマ設計は将来の拡張性を考慮しました。 返却日時や貸出状態を管理するカラムを追加することで機能拡張への対応を可能としています。

この実装を通じて学んだ最も重要な点は型安全性とトランザクション管理の重要性です。RustとSQLxの組み合わせにより堅牢なシステムを実現できました。

主要な機能

  1. ユーザー管理

    • ユーザーの作成
    • ユーザー情報の取得
  2. 書籍管理

    • 書籍の検索
    • 在庫状態の管理
  3. 貸出管理

    • 書籍の貸出
    • 返却処理
    • 貸出状態の追跡

参考リソース

まとめ

今回は図書管理システムを題材にRustとProtocol Buffersを組み合わせた実装を検証しました。 tonicとSQLxを活用したバックエンド開発を通じて両者の親和性の高さを実感できました。Rustの型システムと所有権の概念がProtocol Buffersの型定義と自然に調和する点が特に印象的でした。

エラー処理と非同期プログラミングの実装パターンについても有意義な知見を得られました。 RustのResult型とtonicのステータスコードの組み合わせは明快なエラーハンドリングを実現します。またtokioを基盤とした非同期処理はSQLxのトランザクション管理と組み合わせることで堅牢な実装を可能にします。

今後は本実装をベースにさらなる検証を進めたいと考えています。 予約システムやユーザー認証の追加を通じてスケーラブルな設計の可能性を探ります。フロントエンド開発ではRemixとTypeScriptを採用することでエンドツーエンドの型安全性についても検証を行う予定です。またDockerコンテナ化やCI/CDパイプラインの整備を通じて本番環境での運用性も確認していきます。

RustとProtocol Buffersを主軸とした本プロジェクトは実用的なシステム開発の基盤として十分な手応えを感じる結果となりました。今回得られた知見は今後の開発プロジェクトにも大いに活用できるものと確信しています。

最後に

初春の誓い 新たに刻みしも 昨日の影が まだ私を離さず

目標という星は 遠く輝けど 手の届かぬ空に ただ揺れている

日々は川の流れのように 変わらぬ場所を 静かに過ぎゆく

されど生は進み 刻は確かに 私を育てる

明日もまた 新しい朝が来る それだけが 確かな真実