一休における C#,PythonからRustへの移行の現状と見えてきた課題

投稿日時:2025/03/20 22:30
山本 浩平のアイコン

株式会社一休 / 一休.comレストラン エンジニア

山本 浩平

Xアカウントリンク

はじめまして、一休でプロダクト開発・技術広報を行っている山本(@kymmt90)といいます。
今回は「一休.comレストラン」のバックエンドのRust移行の取り組みについて、バックエンドを中心に、背景や現在の移行状況をお伝えします。

なぜRustを選定したのか?

サービス概要

一休.comレストラン」は、上質なレストランを対象に店舗や食事コースの検索、詳細情報の閲覧、Web予約機能を提供するサービスです。2006年にローンチされ、従来はPython・C#・VBScriptといった技術スタックを用いたシステム構成となっていました。

選定背景

5年以上Pythonを中心にサービスを開発/運用してきましたが、コロナ禍や開発チームの再編成を経て、既存のコードベースのままだと事業の要求に沿った開発や運用が難しくなってきたという課題がありました。
そこで、サービスのUI自体の刷新を含めリライトしていくという方針が決まりました。

社内の技術選定方針として「シンプル、かつすばやく、それでいて堅牢に作れる」という方針があります。
その方針に基づき、Rustが以下の点からバックエンドの新たな言語として選定されました。

  • 型の表現力が高く、「一休レストラン」の扱う業務のドメインモデルを正確にコードに落とし込むことが可能。
    これにより、型システムに基づいて堅牢なシステムを構築することができる。
  • 高速かつ省リソースなバックエンドサーバーの実現が可能。
    これにより、サービス運用コストを中長期的に改善することが期待できる。

このような特徴が、社内の方針に合致すると判断し、Rustを選定しました。

Rustへの移行ロードマップ

既存システムへのRust移行は、大きく3つのプロジェクトに分類されます。

① 〜2024年12月: レストラン予約UIのバックエンド

レストラン予約UIのバックエンドは、昨年2024年12末でRustに移行完了しています。
具体的には、

  • スマートフォンビューのバックエンド
  • デスクトップビューのバックエンド

の移行を昨年末に完了しています。

② 〜2025年2月: SolrのインデクサーのRust移行

一休では全文検索エンジンとしてSolrを利用しています。
長年運用しているSolrにドキュメントデータを登録するバッチ(インデクサー)の実行速度が遅く、検索結果へのリアルタイムな反映に難がありました。この問題を解決するため、既存実装で採用している方式の改善含め、2025年2月にインデクサー実装のRustへの置き換えが完了しました。

③ 〜2025年中: 予約コアロジックのRust移行

サービスのローンチ以来、VBScriptで書かれたシステムがWeb予約サービスの根幹である予約に関するロジックを実行しています。このVBScriptの非推奨化がMicrosoftからアナウンスされており、2027年までにリプレイスが必要です。ここもRustに置き換えていく予定です。

現状は複数の言語をまたいだ開発を行っており、Rustでの開発も行いつつ、プロジェクトの状況やビジネス要求に応じてPython/VBScript側を改修することもあります。

オニオンアーキテクチャの採用

スクリーンショット 2025-03-19 14.33.43.png

一休レストランのバックエンドでは、上記のようなオニオンアーキテクチャを採用しています。
最内層には「エンティティ(クエリモデル)」が配置されており、この層は特定のデータストアや環境設定に依存しない設計になっています。エンティティは、インターフェースを通じてのみ外部要素とやり取りを行うため、データベースなどの具体的な外部依存から完全に独立しています。このアプローチにより、コードの見通しが向上し、テストの容易性も高まります。

エンティティ層の外側には「データローダーのインターフェースやユースケース層」「データアクセス層」そして最も外層に「データベース、フレームワーク、環境設定」といった具体的な実装要素が構成されており、外層レイヤーから内部のビジネスロジックに注入される構造を取っています。

このような設計は、内部のビジネスロジック層と外部要素の依存を明確に分離することでき、柔軟かつ再利用性の高い構造を実現する近年広く用いられているアプローチと一致しています。

データアクセス層の実装例

今回、データアクセス層のコードを例として取り上げ、実際の動作イメージをお見せします。
以下のコードはあくまでも実際のコードとは異なるサンプルですが、「レストランの情報を取得する」関数の一例を示しています。

このコードでは、環境設定やデータベースなどは外部から注入される仕組みになっており、関数内部では、まずクエリ文字列を生成し、それに基づいてデータベースからレコードを取得します。
その後、取得したレコードを、try_fromやfromといったRustの型変換トレイトのメソッドを活用してドメインモデルに変換しています。この変換処理により、データベースからデシリアライズしたDTO(データ転送オブジェクト)をアプリケーション内で扱いやすい形式にすることができます。

async fn fetch_restaurants<C: Config>(
    &self, database: &crate::Database<C>, keys: &[RestaurantId],
) -> Result<HashMap<RestaurantId, Result<Restaurant>>> {
    let query = format!(
        // クエリ
    );
    // レコードフェッチ、DTOからクエリモデルへ
    let restaurant_models = database
        .query_as::<dto::Restaurant>(&query, params)
        .await
        .context("failed to query restaurants")?
        .into_iter()
        .map(|d| (d.id, Restaurant::try_from(d)))
        .collect();
    Ok(restaurant_models)
}

crate管理

先ほどのオニオンアーキテクチャでは、各層が具体的な実装の詳細を持たず、上位層から依存方向を明確に守る設計になっています。この点が、実装における基本的な構造です。

さらに、crate[1]管理ではCargo Workspaceを活用しています。
Cargo Workspaceを利用することで、1つのリポジトリ内でモジュールを分離して管理することが可能になります。例えば、オニオンアーキテクチャの各層をさらに細かい粒度に分割し、それぞれを独立したcrateとして定義することができます。

Cargo.tomlを用いて各crate間の依存関係を明示的に管理することで、アーキテクチャ上の責務分離を維持する仕組みを構築しています。

# ルートディレクトリのCargo.toml
[workspace]
resolver = "2"
members = [
    "backend/*",
]

[workspace.dependencies]
backend-query-model = { path = "./backend/query-model" }
#...
# データアクセス層のCargo.toml
[package]
name = "backend-data-access" 
version.workspace = true 
authors.workspace = true 
edition.workspace = true 
publish.workspace = true

[dependencies]
backend-query-model = { workspace = true }

API設計

GraphQL

フロントエンドとの通信にGraphQLを採用し、フロントエンド側での型生成にも活用しています。
Rustではasync-graphqlを使用し、効率的なGraphQLスキーマの定義やリゾルバーの実装を可能にしています。

REST API

社内システム連携用にREST APIのエンドポイントを提供しています。
RustではAxumを使用し、シンプルな設計でREST APIのエンドポイントを提供しています。

ライブラリ

SQL ServerやRedis、クラウドサービスのクライアント、予約業務のための24時以降の時間型など、サービスロジックに直接関連しない要素はライブラリとして分離しています。

テスト

ドメインモデルの層やリポジトリ内のライブラリのテストは、純粋なロジックの検証として書くことができます。これらのテストは、cargo test で実行できるテストとして、モデルやライブラリの実装と同じファイル内に書いています。他には、実際のSolrのレスポンスJSONのスナップショットを用いて擬似的にインテグレーションテストを実施している箇所があります。

これまでは、フロントエンドと組み合わせてGraphQLのクエリを動作確認するケースが多く、GraphQLクエリに関するテストは薄い状況でした。今後は予約コアロジックのようにデータ変更を伴う機能が入る予定なので、包括的なインテグレーションテストの整備も進める予定です。

移行開始から1年経っての振り返り、見えてきた課題

移行開始から1年経って

移行後のシステムはRustの機能を活かして実装しており、堅牢に動作しています。たとえば、バックエンド自体のロジックでエラーハンドリングを忘れて500エラーが発生するような問題が起きにくくなっています。
また、期待どおり省リソースで運用できており、通常時はCloud Runにおいて10台に満たない程度のコンテナ数で秒間最大1,000リクエスト強をさばいています。

短期的課題

依存ライブラリのアップデート

semantic versioningとしてのv1に到達しておらず、破壊的変更が入るcrateがまだまだ多いので、アップデート作業は慎重になりがちです。パッチバージョンのアップデートで破壊的変更が入ってしまったcrateもありました。このあたりはRustに詳しいメンバーを中心に、そのようなcrateにおいても問題を起こさない形でうまくアップデートするため個別の対応を進めています。

オブザーバビリティの強化

GraphQLのリゾルバー内での細かいトレースや、また社内マイクロサービスとの通信もあるので分散トレースを取るなど、よりシステムの状態がわかるようにしていきたいです。

インテグレーションテストの拡充

先述のとおり、これまではフロントエンドと合わせて動作検証をすることが多い状況でした。今後、予約コアロジックなどの移行に伴い、DBなどを伴うインテグレーションテストを導入することで大きな粒度での回帰テストを増やしていきます。そのなかで、フィードバックサイクルを早く回せるようにテストの実行速度にも気をつかっていく必要があります。

中長期的課題

予約コアロジックのRust移行: 2027年頃 VBScriptの無効化予定で、モデルの再設計から取り組む必要があります。

image1.png

We are hiring

次世代のRustシステムを支えるエンジニアを募集中です。
レガシー技術と向き合いながらも、Rustでシステム移行をサポートできるエンジニアのJoinを期待しています。

Webアプリケーションの設計について理解があり、モデルなどシステムの根幹から一緒に検討できる方を求めています。また、一休.comという事業の方向性に沿って、Rust以外のコードベースも手を入れる必要があります。そのような場面でもエンジニアとして必要な動きができることも期待します。Rustの経験がなくても問題ありません。どちらかというと技術に対して継続的にキャッチアップできる習慣をお持ちかどうかが大事だと考えています。

健全なコードベースを維持し、サービスの根幹を支える一員になっていただければと思います。[2]

【リモートOK_Rust】 会員数1,500万人超え!「一休.comレストラン」における予約動線のリプレイスを進めるサーバーサイドエンジニアを募集


脚注
  1. crate:Rustにおけるコードのパッケージやモジュールの単位。ライブラリやアプリケーションの構成要素を分離して管理するための仕組みで、他のプログラミング言語における「ライブラリ」や「モジュール」と同様の役割を果たします。

  2. 本記事は、2024年10月31日に開催されたイベントの内容を元に編集した記事になります