Rust 微経験のWebエンジニアが Claude Code で新機能を実装し、安定運用するまで

Rust 微経験のWebエンジニアが Claude Code で新機能を実装し、安定運用するまで

Claude Codeを使ってRustで新機能を開発し、1年以上本番運用できている私たちの歩み方を振り返ります。微経験の言語でも、設計・テスト・仕組みで堅牢性を確保する手法をお伝えします。
2026.07.07

クラウド事業統括本部 プラットフォーム事業本部 サービス開発部の金谷です。

AI、主に Claude Code を使って Rust を採用した新機能を開発し、これまで大きな不具合もなく運用できているため、どういったことを考え行動してきたのか振り返ります。

私について

私はWebアプリケーションエンジニアで、普段は TypeScript や React などフロントエンド開発をメインにしています。バックエンドはたまに書く程度で、Rust は自己学習で多少触れていた程度というレベルでした。

開発期間

そんな私が、あるプロダクトの新機能のバックエンドを Claude Code と一緒に Rust で実装しました。構想が動き出したのは2025年の前半で、本格的な開発が始まったのは2025年6月頃です。

当時はちょうど Claude Code が正式リリースされて間もない頃でした。エージェント型のコーディングツールがひととおり出そろい始めたタイミングで、「どこまで任せられるのか」を手探りしながら使い始めました。

そこから試作と方針転換を繰り返し、2026年2月に本番リリース(GA)しました。以降は機能追加と改善を続けています。開発としては1年あまり、本番の運用としては半年ほどで、いまのところ大きな障害は出ていません。

バイブコーディングで一気に実装することが正義みたいな空気も感じてはいたのですが、結構保守的に進めていたなと今振り返ると思います。(期間が長いことについては私が途中で育休に入った影響もあります👶)

技術選定

まずサーバーレスで作ると決めた

作ったシステムは、いわゆるリアルタイムETLと言われるような要件で、S3にあるデータを集計して返すAPIです。条件によって扱うデータ量に幅があり、最悪のケースでの集計では高メモリを要する処理でした。エンドポイントは少ないですが、集計のパターンが非常に多い、といった特性です。

常駐サーバーで組むと、ピークに合わせて高メモリのインスタンスを立て続ける必要があります。リクエスト数がそれほど多くない自分たちの規模では、高メモリ設定でも従量課金のLambdaのほうがコンピューティングコストをかなり抑えられそうでした。そこで、まずサーバーレスで作ると決めました。

ただ、サーバーレスだとコールドスタートに加えて、Lambdaの実行時間15分やAPI Gatewayの統合タイムアウト29秒といった、サーバーレス独特のハマりどころもあります。
当時は AI に壁打ちしながら設計を考えていても、完全に見通すことはできず、ハマりどころを目にしてから再度試行錯誤、みたいなことを繰り返していた記憶があります。
(今だと Fable にさせたりすればそんなことはないのかもしれません)

ユーザビリティ的な観点でもパフォーマンスに妥協はしたくなかったことと、ちょうど社内で Rust とサーバーレスの相性が良さそうとの情報をちょくちょく聞いていたりしたので、最終的に、Rustで書くことにしました。
他の開発メンバーもチャレンジングな選定にもかかわらず背中を押してくれて大変ありがたかったです。

余談ですが、AWSの公式ドキュメントでもRust で Lambda 関数を構築する方法が紹介されていて、以前ほどマイナーな選択肢ではなくなったのかなと思っています。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-rust.html

弊社DevelopersIOの記事も
https://dev.classmethod.jp/articles/aws-lambda-rust-ga/

axum でAWSの都合を実装から切り離す

フレームワークなしでもRustを利用可能ですが、axum というフレームワークを採用しました。
axumを使うことで通常のWebアプリケーションとして実装でき、Lambdaで動かす際はアダプタを被せるだけで済みます。
ローカルやCIでは普通のWebサーバーとして起動でき、統合テストもそこへ実際にHTTPで叩けます。「AWSの上でしか動かない、確かめられない」という状態を避けたかったからです。

トレードオフ

Rust 採用にはいくつか代償があり、それぞれ潰しながら進めました。

CI上でのビルドが重い

一般的に Rust のビルドは時間がかかり、そのままだとCIの課金が地味に効いてきます。とりあえずはsccache等のビルドキャッシュを入れて緩和しています。正直に言うと、「入れておくと速くなるらしい」という程度の理解で頼っている部分もあります。ビルドとデプロイを分離してアーティファクトを使い回したり、cargo workspace のような仕組みも検討しましたが、今のところそこまで致命的に遅くなっていないのでなんとか回せています。

学習コストはAIで埋める

導入時の設計メモにもRust学習コストは懸念材料になりうると残していました。
ただ、私自身はほとんど自分でコードは書いておらず、微経験の言語で書き始められたのはAIが実装を引っ張ってくれたからなのは間違い無いです。この前提が崩れると採用理由自体が変わってきます。
一方で元々持っていた一般的なWebアプリケーション設計の知識は役に立ったと思います。
例えば、他の言語における DI に相当する実装は Rust, axum ではどう実装するのかという観点で指示すると State を使った実装を提案してくれたりしました。この辺りは知識があってよかったなーという感触でした。

サーバーレス特有の制約

検証時にレスポンスタイムの上限に当たって重いリクエストが504になったので、Regional配置にしたうえで統合タイムアウトの上限を引き上げました。レスポンスサイズの6MB制限には、大きい結果をいったんS3に書き出して署名付きURL(30分)を返す方式で対応し、サイズで「そのまま返す」「ダウンロードさせる」を分けています。

参考記事
https://dev.classmethod.jp/articles/api-gateway-integration-timeout-limit-29-seconds/
https://dev.classmethod.jp/articles/lambda-over-6mb-response-use-s3-pre-signed-url/

安定稼働を見据えて考えた・実践したこと

微経験の言語で作ったものを、リリース後も落ち着いて運用し続けるために、いくつか手を打ってきました。共通しているのは、「自分の理解力を当てにしすぎない」ことと、「できるだけ広く知られたやり方に寄せて、自分にもAIにも扱える土台にする」ことの2つです。

言語の壁は、広く知られた設計に寄せてカバーする

微経験の言語でいちばん怖いのは、自分が書いた(あるいはAIが書いた)コードの影響範囲を読み切れないことです。ここは言語の習熟で押し切るのではなく、他の言語でも通用する一般的な設計に寄せることで補いました。言語固有の作法に頼るより、広く知られた形にしておいたほうが、自分の既存知識でも読めますし、AIも学習済みの知識で扱えます。

たとえば、責務ごとにレイヤを分ける構成にしています。プレゼンテーション層・アプリケーション層・ドメイン層・インフラ層を一方向に依存させる、レイヤードアーキテクチャやクリーンアーキテクチャとして知られている形です。特定の言語の流儀ではないので、AIに実装を任せても、人間がレビューしても、同じ言葉で話せます。レイヤの依存が守れているかは、「上位のレイヤが下位を直接参照していないか(たとえばアプリケーション層のコードがインフラ層を直接importしていないか)」を検査する仕組みを用意して確かめています。AIによるレビューの観点にも入れています。

あとは副作用の扱いも同じ考え方です。現在時刻の取得や環境変数の読み出しのような副作用を、下位のロジックの中で直接呼ばせません。時刻はエントリポイントで一度だけ取得し、差し替え可能な形(Clock のようなトレイト)にして下位へ渡します。こうすると Utc::now() のような呼び出しがコードのあちこちに散らばらず、副作用の入口を型からたどれます。テストのときは時刻を固定値に差し替えられるので、日付に依存する処理も安定して検証できます。これも特別な技ではなく、依存を注入するという一般的な考え方をRustに当てはめただけです。

内部実装を隅々まで理解し続けるより、こうしたどの言語でも通じる形に寄せて、境界と副作用の位置を固定しておくほうが、私としては理解しやすかったです。

テストで外部仕様を固める

信頼の置き所も、内部実装の理解だけでなく「外から見た振る舞い」に寄せています。

統合テストを厚くする

本番に近いデータに対して実際にHTTPで叩き、返ってくる中身をアサートします。テストに使う本番相当のデータ(Parquetファイル)は Git LFS で管理していて、実データに近い入力でAPIを丸ごと通せるようにしています。テストケースは、人間が読んで何を保証しているのか分かる形で書くことを意識しています。修正量が多いときほど影響範囲の予想に関わらず必ず流す、というのをルールにしていて、PRを出す前の検証にも必ず含めています。かなり安心できます。

また、エッジケースのテストもいくつか拾っていて、例えば大きめのレスポンスを返す際S3にキャッシュデータを残すような実装があるのですが、こういったパターンはレスポンスに cacheHit: true のようなbool値をセットするような形にしています。こういったエッジケースは見逃しがちになるので、実際のアプリケーション上では使用しない値ですが、テスト堅牢性を高めるために工夫しています。

OpenAPI定義のスナップショット

APIの外形をOpenAPIとして持ち、実装から生成した定義とコミット済みの定義を突き合わせて、意図しない外部仕様の変更を検知しています。リファクタや依存更新のついでにレスポンスの形が気づかないうちに変わる、というのがいちばん避けたいところなので、外部仕様そのものをテストで凍結しています。

土台を継続的に入れ替える

作って終わりではなく、依存している土台の入れ替えも続けています。範囲が広くて手数のかかる作業ですが、まさにAIが得意なところなので、フル活用して進めました。

CDK → Terraform への移行

インフラ定義を移行しました。きっかけはサプライチェーンの懸念です。私の母語がほぼTSなので、振り返るとCDKのメリットもあった気はしますが現状問題なく稼働しています。
こちらはGA前の話なので、特に大きなハマりどころはなかったです。

Bruno → Hurl への移行

統合テストのランナーも移行しました。こちらもサプライチェーン懸念をきっかけに検討しました。(BrunoがNode依存するため)
Hurlはlibcurlベースの単一バイナリで、推移的なnpm依存がなく、CIでそのまま回せます。
BrunoはJSを書いてアサーションができるので細かな検証が可能だったのですが、Hurlの実装ベースに寄せました。書き方が変わるのでちゃんと意図した通りにテストができているかは検証が必要でしたが、ほぼ同等のテストができていることを確認してから移行しました。(これは結構大変だった)

https://docs.usebruno.com
https://hurl.dev/

MinIO → LocalStack → VersityGW への移行

ローカルのS3互換ストレージを、有償化などの事情に合わせて乗り換えてきました。

https://gigazine.net/news/20251023-minio-stops-distributing-free-docker-images/
https://blog.localstack.cloud/the-road-ahead-for-localstack/
https://github.com/versity/versitygw/

どれも派手ではありませんが、依存先のライセンスやリスクの変化に追随して土台を差し替える、という地味な運用の一部です。

固めてあるから、後から大きく作り直せる

外部仕様をスナップショットで凍結し、統合テストを厚くしてあったので、内部を大きく作り直しても壊れていないとすぐ確認できます。この安心感があったからこそ、リリース後でも大きめのリファクタに踏み切れました。

具体的にはドメインモデルを再設計しました。事前にやっとけというツッコミを受けそうですが、集計のパターンが想定よりも多く、改めて整理した方がいいと考え実施しました。これはGAした後の話です。理解負債といったワードも流行り始めていたという背景もあったと思います。
https://zenn.dev/sukedachi_dev/articles/20260205-db241e37ec3a52

最初は前述のレイヤ構成を保持するのみで組んでいたのですが、機能を足すたびに全体の見通しが少しずつ悪くなっていきました。
大まかにレイヤーは分かれているが、その内部の役割がなにがなにやら…。
AIに任せる範囲が広いぶん、人間側が全体像を掴めなくなるのは避けたいところです。

オペレーションを言語化する

人間のチームなら暗黙のうちに揃っていた進め方も、AIに任せる範囲が広がると、言葉にしておかないと共有されません。それまで口頭や頭の中で回していた決めごとを、なるべく明文化しました。具体的には、CLAUDE.md や .claude/rules に決めごとを書き、コーディング規約も文章にしています。最初から用意していたわけではなく、運用しながら少しずつ書き足してきたものです。

ただ、書いておけばAIが従うかというと、そんなことはありません。平気で無視します。なので言語化はあくまで期待値をそろえるためのもので、本当に守らせたいことは、AIの遵守を当てにせず機械で止める側に寄せています。前に触れたレイヤ依存のgrepチェックや、外部仕様(OpenAPI)のスナップショットも、同じ考えで機械的に止めている例です。運用面でも、PRを出す前に lint とテストを一通り流す手順(pre-pr)でゲートを通し、マージ操作は人間がやる、というふうに、最後は仕組みと人間で止めています。PR本文は、放っておくと説明が過剰になりがちなので、簡潔に書くよう明示しています。

もうひとつ、AIがらみのPRには ai-config というラベルを付けています。今でも効いていて、AIに何をどう任せてきたかを後から振り返る目印になっています。(今もこれを見ながら記事を書いています)

「AIに任せてよい範囲」と「人間が最後に止める範囲」の線引きを、失敗のたびに決めごとと仕組みに落としていく。この足場づくり自体が、開発を通していちばん手を動かした作業だったかもしれません。

まとめ

微経験の言語でも、広く知られた設計に寄せて影響範囲を追えるようにし、テストで外部仕様を固め、AIの遵守を当てにせず要所を仕組みと人間で止めておけば、本番で運用し続けられる状態は作れました。
一気に作る勢いは魅力的ですが、ここまで落ち着いて運用できているのは、地味に保守的な手を積んだからだと思っています。


Claudeならクラスメソッドにお任せください

クラスメソッドは、Anthropic社とリセラー契約を締結しています。各種製品ガイドから、業種別の活用法、フェーズごとのお悩み解決などサービス支援ページにまとめております。まずはご覧いただき、お気軽にご相談ください。

サービス詳細を見る

この記事をシェアする

関連記事