
Claude Code
イベント
マガジン
技術ブログ
はじめに こんにちは、エブリーでECの開発をしている山舗です。 この度、弊社で運営している EC サイトにて、既存の SaaS のカートに代わる自社実装を新規に開発し、全面リプレイスを行いました。扱うデータには、会員の個人情報や、定期購入の課金情報などがあります。期間は 3 か月弱、コードは約 61 万行、コミットは約 7,500 件に及ぶ開発となりました。この規模の開発を短期間で行うには、Claude Code を中心に、半自律的に開発を進めさせる必要がありました。 一方で、世の中では個人情報の漏えいのニュースが数多く流れています。2025 年に上場企業とその子会社が公表した分だけで、3,063 万人分に及びます( 東京商工リサーチの調査 )。 この記事では、こうした状況の中で、Claude に書かせる前提で開発しながら個人情報を守るために、どんな技術を選び、どう使ったかをご紹介します。 1. Claude に書かせる前提の技術選定 新規開発にあたって、まず技術選定から始めました。開発の中心を Claude に任せるので、Claude が書いたコードに読み違いや書き忘れがあっても、実行する前に機械で見つけられることを基準にしました。選んだ構成は次のとおりです。 役割 選んだ技術 言語 TypeScript(フロント / バックエンド) API Hono DB と ORM PostgreSQL、Drizzle フロント(storefront) Next.js、Tailwind フロント(admin) Vite、Tailwind まず、言語は TypeScript に統一しました。バックエンドを Go、フロントを TypeScript のように分けると、2 つの言語の間で型は自動ではつながりません。API の形を OpenAPI などの中間の定義に書き、そこからフロントの型とクライアントを生成する工程が要ります。例えば、「定期購入の配送サイクルは、決まった日数のどれか」のような値の決まりも、バックエンドの検証と画面の検証の 2 か所に書くことになります。片方だけ直すと、もう片方は古いまま動き、ずれに気付くのは実行したときです。 AI は、API だけを直して画面を直し忘れるといった、片方だけの変更をよく書きます。それが実行時まで見つからない作りは避けたかったので、API から画面までを 1 つの言語にして、型をつなげることにしました。フロントとバックエンドでドメインのロジックを共有でき、lint とコーディングの決まりを 1 言語に絞れるのも理由です。 その型を API から画面へ届けるために、API のフレームワークには Hono を選びました。Hono RPC の型推論を使うと、API の定義からフロントの呼び出しまで、型がそのままつながります。Express などより軽く、Web 以外から使う API も今は無いので、これで足りました。NestJS は DI の構造が複雑で、問題に起動時にしか気付けません。構造を静的に追えるほうが AI に向くと考えました。 DB の側も同じ考え方で、ORM には Drizzle を選びました。スキーマを TypeScript で定義し、型をそこから導くので、独自の言語やコード生成の工程がありません。Prisma は独自の言語でスキーマを書き、クライアントの生成が要ります。行ロックと入れ子のトランザクションを API で書けることも理由で、これは後で例を挙げます。管理画面の見た目には Tailwindを使っています。Tailwind は任意の値を text-[#e91e63] のように角括弧で書くので、AI が決まったスタイルから外れたことを機械で検知しやすくなります。 // 決めた色のトークンを使った書き方 <button className= "bg-primary" >申し込む</ button > // 決まりから外れた書き方。角括弧があるので、検索で見つかる <button className= "bg-[#0b794d]" >申し込む</ button > 色は決めたトークンだけを使う決まりにしていて、 -[# で検索すれば、外れた箇所を一覧にできます。 型を一貫させたときに起きること この EC サイトでは、配送サイクルの決まりは 1 か所にしかありません。 // packages/domain-values/src/subscription.ts(抜粋) export const cycleDaysSchema = z.union( [ z.literal( 7 ), z.literal( 14 ), z.literal( 21 ), z.literal( 28 ) ] ); export type CycleDays = z.infer < typeof cycleDaysSchema >; この 1 つの定義を、次の全部が参照します。 DB から読んだ値の検査(repository) API の要求と応答の契約(contracts) 画面のコード(ストアフロントと管理画面) API のルートの型は、Hono RPC でフロントのクライアント( hc<AppType> )にそのまま届きます。そのため、API の応答の項目を変えると、それを使う画面のコードが型の検査で落ちます。直し忘れは、実行する前に CI で止まります。実際にあった例を 2 つ紹介します。 例 1: 型を変えた後の直し忘れが、8 つのファイルで見つかった 顧客のメールアドレスは、最初はただの string でした。これを、前後の空白を取り、形式を確かめ、小文字にそろえた値だけが入る型に変えました。 // packages/domain-values/src/email.ts(抜粋) export const emailAddressSchema = z .string() . trim () .pipe(z.email()) .transform(( value ) => value. toLowerCase ()) .brand< "EmailAddress" >(); export type EmailAddress = z.infer < typeof emailAddressSchema >; .brand を付けた型には、このスキーマを通した値しか入りません。ただの文字列を渡すと、型の検査で落ちます。 この変更を進めている間に、main ではメールアドレスを文字列のまま渡すテストが足されていました。main を取り込んで型の検査を流すと、API・画面・ドメインの 3 つのパッケージに散った 7 つのテストが落ちました。直し方はどれも同じです。 // 直す前: 型の検査で落ちる customerContact= { { email : "member@example.com" , phone : null } } // 直した後 customerContact= { { email : emailAddressSchema.parse( "member@example.com" ), phone : null } } 型が無ければ、検証を通していない値が渡っていることに気付く手がかりがありません。型の検査は、直す場所をファイルと行で全部挙げます。 例 2: 画面に届く応答の型が広がっていたのを直し、型だけのテストで固定した 管理画面の API には、URL の番号の形式が合わないときに 404 を返す共通の部品があります。この部品が応答を { error: string } で受けていたため、エラーのコードの型が string に広がっていました。Hono RPC は応答の型をそのまま画面に届けるので、画面の側では、どのコードが返るのかが型から分からなくなっていました。 // apps/admin-api/src/middleware/validation.ts(抜粋) // 直す前 export function codeParamValidator < Schema extends ZodType >( schema : Schema , notFound : { error : string } , ) { /* ... */ } // 直した後: 渡されたコードをリテラル型のまま保つ export function codeParamValidator < Schema extends ZodType , const NotFound extends { error : string } , >( schema : Schema , notFound : NotFound ) { /* ... */ } 同じことがまた起きたら型の検査で落ちるように、画面の側に、型だけを確かめるテストを足しました。実行時には何もしないテストです。 // apps/admin/src/lib/api-client.test.ts(抜粋) expectTypeOf< InferResponseType <( typeof apiClient.customers )[ ":code" ][ "withdrawal" ][ "$post" ], 404> >().toEqualTypeOf< { readonly error : "customer_not_found" } >(); API の側でこの応答の型が変わると、画面の側のこのテストが型の検査で落ちます。 行ロックと入れ子のトランザクションを使う場面 ORM に Drizzle を選んだ理由の 1 つは、行ロックと入れ子のトランザクション(savepoint)を API で書けることです。EC では、同じデータに 2 つの操作が重なる場面が多くあります。 たとえば、会員がクーポンコードを入れた瞬間に、管理者がそのクーポンを停止することがあります。「使えるか」を確かめてから会員の保有を作るまでの間に停止が入ると、停止されたクーポンが会員の手元に残ります。そこで、クーポンの行をロックして読み、判定と保有の作成を同じトランザクションで済ませます。 // packages/repositories/src/coupons/coupon-code-entry.ts(抜粋) const query = executor .select( { id : coupons. id , grantOnly : coupons.grantOnly } ) .from(coupons) .where(sql ` ${ coupons.code } COLLATE "C" = ${ code } ` ) .limit( 1 ); const [ row ] = await (options?.forUpdate === true ? query.for( "share" ) : query); ロックはクエリの最後に .for("share") を付けるだけで、 row の型は select に書いた列から決まります。一方、Prisma のクエリの API には行ロックの指定がありません( 要望の issue は 2021 年から開いたままです)。同じことをするには、生の SQL を書くことになります。 // Prisma で同じ読み取りを書く場合の例(実際のコードではありません) const rows = await tx.$queryRaw< CouponRow []> ` SELECT id, grant_only FROM coupons WHERE code = ${ code } FOR SHARE ` ; CouponRow は自分で書いた型で、SQL が返す列と合っているかは検査されません。列の名前を変えても型の検査では落ちず、実行して初めて失敗します。行ロックを取る読み取りはリポジトリの中に 50 か所ほどあるので、そこで型が途切れないことを重視しました。 この上に、機械で確かめる決まりを重ねています。規約には「ルールは機械で守れるものから機械化する。散文の規約は機械化できないものに限る」と書いてあります。 パッケージの依存の向きは、Turborepo の boundaries が CI で確かめる。顧客向けの API と社内向けの API は、互いを import できない id は、どの表の id かを型で区別する(Zod の brand)。注文の id を顧客の id として渡すと型エラーになる 外部に触れる部品には、手書きのフェイクと本物の両方に同じテスト(契約テスト)を流し、挙動のずれを見つける 2. その型で個人情報を守る 個人情報も、「気を付ける」ではなく「書き忘れたら通らない」形に寄せました。 列を足したら、分析基盤に出すかを決めないと通らない 全部の表の全部の列を、全社のデータ基盤(Databricks)に「出す」か「出さない」かに分けた定義を、コードに置いています。分類の型を Drizzle の表の定義から導いているので、列を足して分類を書き忘れると型の検査で落ちます。表を足して書き忘れるとテストで落ちます。 // packages/db/src/analytics-exposure.ts(抜粋) export type Exposure = "analytics" | "restricted"; type ColumnExposure<T extends PgTable> = { [K in keyof T["_"]["columns"]]: Exposure }; デプロイのたびに、マイグレーションの後で、データ基盤用の DB のユーザーの権限を全部外し、「出す」列だけに列単位で付け直します。分類と実際の権限が食い違ったまま残りません。個人情報は DB の権限で守られるので、取り込みのコードを通さずに DB を読んでも取れません。 この仕組みは、入れた PR の中でさっそく働きました。main を取り込んだところ、別の PR が足した 3 つの列の分類が無く、型の検査が落ちました。一方で、型では止まらない誤りもレビューで見つかっています。サポート担当が自由記述する列(返金の理由など)が「出す」側に分類されていました。自由記述には顧客の名前や電話でのやり取りが書かれうるので、書く人が誰でも「出さない」にする決まりを足しました。 監査ログに、個人情報の写しを増やさない 管理画面の変更履歴の元になる監査ログも、同じ作りです。全部の列を「前後の値を書く」「書かない」「値を記録せず、変わったことだけ書く」などに分類し、漏れは型とテストで止まります。カードの下 4 桁と有効期限は、値を {"unrecorded": true} に差し替えて記録します。アプリの DB ユーザーは、監査ログに追記しかできません。書き換えと削除の権限を、デプロイのたびに外しています。 エラーの監視には、送る前に止める Sentry の SDK は、何も指定しないと、利用者の情報・Cookie・要求の本文・DB の引数・ローカル変数を集めます。これを止める設定を 1 か所に置き、どのアプリの初期化もそれを使います。届いてから Sentry の側で消す案は、個人情報が一度届いてしまうので採りませんでした。 ログでも同じ考え方を取っています。倉庫のシステムの API が JSON として読めない応答を返したときに、本文の先頭 500 文字を例外のメッセージに入れていたことがありました。受注や出荷の伝票の応答が壊れた形で返ると、宛先の氏名や住所がログに出ます。Claude に差分を分析させたレビューで見つかり、例外に入れるのを状態コード・種類・本文の長さだけに直しました。 id から、他人のデータを順にたどれないようにする 表の id は連番でなく UUIDv7 にしています。連番の id が URL や API に出ると、番号を 1 つずつ変えて他人の注文やお届け先を順に試せます。番号の大きさから、会員や注文の件数も読めます。UUIDv7 なら、この列挙と件数の推測が難しくなります。 ただし、id を認可の代わりにはしていません。誰のデータかの確認は別に必ず行い、他の会員のデータを指す要求には 403 でなく 404 を返して、その id があるかどうかも分からないようにしています。Claude に読ませる規約にも「外に出る識別子に、連番を新しく使わない」と書いてあります。UUIDv7 は作った時刻を含むので、時刻を知られて困るデータには別の形式を検討する決まりです。 そもそも持たないもの カード番号とセキュリティコードの入力欄は、Stripe の Payment Element をそのまま使っています。入力された値は Stripe に直接送られるので、自社のコード・ログ・DB を通りません。会員のパスワードはCognito に渡し、自社の DB には持ちません。この考え方は「 個人情報を自社のDBに持たない設計は、どこまでできるのか - every Tech Blog 」に詳しく書かれています。 3. そのほかにやったこと 型とテストで確かめられるのは、形にできた決まりだけです。そのほかに、次のようなこともしています。 Claude Code が読む規約(AGENTS.md)には、個人情報の扱いで守ることを書いています。個人情報をログや監視に出さない、渡す相手は一覧にある相手だけにする、他の会員のデータを指す要求には 403 でなく 404 を返す、外に出る識別子に連番を使わない、といった内容です。Claude Code のセキュリティレビューのプラグインも入れていて、「境界は会員個人にある」といった、このサービス固有の観点を渡しています。認証・認可・決済の境界に触る変更は、人のレビューを必ず通します。 規約そのものが実態とずれていたこともあります。以前の規約は「個人情報をログ・監視・外部 SaaS に平文で出さない」でした。しかし実際には、倉庫には宛先の氏名・住所・電話番号を、決済代行には請求先の氏名を渡しています。そこで「外部に渡すのは業務に要る相手だけ」として渡してよい相手の一覧を書き、相手を足すときはその行を直してレビューを受ける形にしました。 まとめ 今回は、EC サイトの新規開発で、Claude を中心に開発を進めるために選んだ技術と、その上で個人情報を守るためにしたことを紹介しました。 言語を TypeScript に統一し、Hono と Drizzle で DB から画面まで型をつなげたことで、Claude が書いたコードの直し忘れを、動かす前に型の検査で見つけられるようになりました。個人情報についても、分析基盤に出す列や監査ログに残す値を型とテストで確かめる形にして、書き忘れたら通らないようにしています。型で確かめられない部分は、Claude に読ませる規約とセキュリティレビュー、人のレビューで補っています。 私自身、新卒エンジニアとして入社半年でここまでの仕事を任せていただけるとは夢にも思っていませんでしたが、手でコードを書いていたときとは違った角度で技術そのものの強みや使いどころについて理解を深められ、良い機会になりました。型やテスト、DB の権限のように、AI に書かせる今だからこそ効いてくる仕組みのありがたさを、実際に作りながら感じました。 AI を中心に開発を進めようとしている方や、個人情報を扱うサービスを作っている方の参考になれば幸いです。 最後までお読みいただきありがとうございました!
2つのモデルの発表内容と評判 Gemini 4 Carbon(Google Cloud) Google Cloudは2026年10月28日にGemini 4 Carbonを発表し、11月10日に一般提供を始めました。Google Cloudの発表では、サイバーセキュリティと、長時間かかるエージェント作業(AIが手順を自分で考えて進める作業)を得意分野に挙げています。ソフトウェア開発や知識作業の大半には同じ世代のGemini 4 Argonを勧め、Carbonはより難しい課題に使う、という使い分けも示されています。
はじめに サーバーワークスの池田です。 2026年11月9日(米国時間)、Anthropic が Claude Fable 5.2 と Claude Mythos 5.2 を発表しました。5.1 の後継として性能を高め、単価20%減と Fast mode(高速モード)対応を実現しています。本記事では2026年11月10日時点の公式情報をもとに、変更点と Claude Code での使い方を解説します。 出典: Introducing Claude Fable 5.2 and Claude Mythos 5.2 この記事で分かること Fable 5.2 と Mythos 5.2 の違いと利用条件…























