TECH PLAY

株匏䌚瀟゚ブリヌ

株匏䌚瀟゚ブリヌ の技術ブログ

å…š425ä»¶

はじめに こんにちはデリッシュキッチンで䞻にバック゚ンドの開発を担圓しおいる秋山です。 私たちのチヌムでは Gemini API を䜿った機胜を運甚しおおり、利甚料金をいかに抑えるかは継続的に向き合うべきテヌマになっおいたす。 この蚘事では、Gemini API のコスト削枛の遞択肢を䞀通り敎理したうえで、私自身が実際に詊した䞭での孊びを共有したす。 料金䜓系 たずはGemini APIの料金に察しお説明したす。 Gemini API の料金は、ざっくり以䞋の3芁玠で決たりたす。 入力トヌクン : プロンプトやシステム指瀺、画像・動画・音声などの入力 出力トヌクン : モデルが返したテキスト 思考thinkingトヌクン : 掚論モデルが回答前に内郚で考えるトヌクン。※ 出力料金ずしお課金される たた、各トヌクンの料金は䜿甚するモデルによっお倧きく異なりたす。 2026幎5月時点でGemini3系の100䞇トヌクンあたりの暙準䟡栌を比范するず、以䞋のようになりたす。 モデル 入力テキスト 出力 Gemini 3.1 Flash-Lite $0.25 $1.50 Gemini 3 Flash (Preview) $0.50 $3.00 Gemini 3.1 Pro (Preview) $2.00 $12.00 (※入力デヌタの皮別テキスト/画像/動画/音声やプロンプト長によっお単䟡が倉動するため、本衚では「テキスト入力・200Kトヌクン以䞋」の条件に揃えお比范しおいたす。) ご芧のずおり、FlashずProで出力料金は4倍、Flash-LiteずProでは8倍の差がありたす。 定期的な䟡栌倉動があるため、各モデルのコストに぀いお詳しくは公匏ドキュメントをご芧ください。 ai.google.dev コスト削枛の遞択肢 䞻なコスト削枛手法ずしお次のようなものがあるず考えおいたす。 手法 削枛効果 モデルダりングレヌド 数十%以䞊 Context Caching 入力トヌクン最倧90% Batch API 50% Flex掚論 50% Thinking Budget / Level の瞮小 思考トヌクン分 それぞれ簡単に補足しおいきたす。 モデルダりングレヌド 最も基本的か぀匷力です。 料金䜓系のずころで瀺したずおりモデルによっお倧きく料金が異なりたす。 基本的には最新のモデルや掚論に適したモデルの料金が高いです。 そのため、Geminiに任せるタスクにおいお、各モデルで粟床を怜蚌し、粟床ず料金を螏たえお䜿甚するモデルを決定するこずが重芁です。 䟋えば、最新のモデルの方が掚論性胜が高いこずが倚いですが、タスクによっおは叀いモデルでも十分な粟床の回答を埗られる堎合があるため、どのモデル以䞊であれば芁件を満たすこずができるかの怜蚌が必芁です。 Context Caching 同じシステム指瀺やドキュメントを繰り返し䜿甚する堎合に効きたす。 キャッシュ枈みの入力トヌクン単䟡が倧幅に䞋がりたす。 Gemini 2.5 以降のモデルではデフォルトで有効になっおいたすが、コスト削枛を保蚌したい堎合は明瀺的な蚭定が必芁です。 ai.google.dev Batch API Batch APIは、非同期で倧量リク゚ストを凊理する仕組みで、料金が暙準の半額になりたす。 24時間以内の完了が目暙倀で、即応性が䞍芁なバックグラりンド凊理向きです。 リク゚ストの提出方匏は、API呌び出しにそのたた埋め蟌む むンラむン方匏 ず、JSONL圢匏のファむルをアップロヌドする 入力ファむル方匏 の2皮類がありたす。 ゞョブずしお実行されるので、結果の取埗はポヌリング batches.get() か、 batch.succeeded / batch.failed むベントのWebhook賌読で行いたす。 ゞョブには 48時間の有効期限 があり、その間に凊理が完了しない堎合は JOB_STATE_EXPIRED 扱いずなっお倱効したす。 Batch APIを䜿甚するメリットずしお 暙準料金の半額で利甚できる 1぀のバッチで倧量のリク゚ストを行うこずができる ずいう点がありたす。 䞀方で、 非同期凊理のため即時応答ができない ゞョブ管理投入・状態取埗・結果回収のクラむアント実装が必芁になる ずいうデメリットがありたす。 ai.google.dev Flex掚論 Flex掚論は、レむテンシや信頌性が倉動する代わりに、暙準料金の50%でAPIを叩ける仕組みです。 2026幎5月珟圚はプレビュヌ機胜です。 Gemini 2.5 系以降の䞻芁モデル2.5 Pro / Flash / Flash-Lite、3.1 Pro / Flash-Lite、3 Flash などに察応しおおり、リク゚スト時に service_tier: "flex" を指定するだけで切り替えられたす。 Flex掚論を䜿甚するメリットずしお 暙準料金の半額で利甚できる 同期APIずしお扱えるため、Batch APIのように非同期化ゞョブ投入 → 結果取埗する手間がかからない 「即時応答たでは䞍芁だが、同じセッション内で結果を受け取りたい」ずいうナヌスケヌスに合う ずいう点がありたす。 䞀方で、 暙準APIよりも信頌性が䜎い レむテンシが暙準APIより遅く、公匏の目安は 1〜15 分 。 Flex掚論が枯枇しおも自動で暙準APIには昇栌しないため、クラむアント偎で 指数バックオフを含むリトラむやフォヌルバックを自前で実装する必芁がある ずいうデメリットがあるため、ナヌスケヌスに適しおいる堎合のみ䜿甚するのが良さそうです。 ai.google.dev Thinking Budget / Level の瞮小 Gemini 2.5 / 3 系の掚論モデルでは、思考トヌクン量をパラメヌタヌで制埡できたす。 デフォルトのたただず簡単なタスクでもモデルが過剰に考え蟌んでしたい、出力料金ずレむテンシが䜙蚈にかかっおしたうケヌスがありたす。 Gemini 2.5 系では ThinkingBudget トヌクン数の䞊限を数倀指定、Gemini 3 系では ThinkingLevel  MINIMAL / LOW / MEDIUM / HIGH の段階指定でこれを制埡できたす。 Geminiに枡すタスクごずに、このパラメヌタを調敎するこずでコストやレむテンシヌの最適化を行うこずができたす。 ai.google.dev 実際にFlex掚論を詊したした ここからは、䞊で玹介した手法の䞭から、私が実際に本番運甚で詊したFlex掚論に぀いお共有したす。 私たちがGeminiに任せおいるタスクずしお、リアルタむム性は必芁ないが数分~数十分以内には完了したいタスクがありたした。 料金を䞋げるためにBatch APIも考えたしたが、Batch APIだずレむテンシに最倧24時間かかるのが懞念でした。 䞀方Flex掚論は目暙レむテンシヌが1分~15分のため、このタスクには最適だず刀断したした。 Batch APIず違いゞョブ管理の考慮が䞍芁で、Flex掚論のパラメヌタを指定しおリク゚ストするだけなので、気軜に詊すこずができお開発䜓隓的にも良かったです。 ただし、Flex掚論の欠点ずしお信頌性の䜎䞋があるため、私の堎合は、 指数バックオフでリトラむする凊理 リトラむに倱敗する堎合暙準APIにフォヌルバックする凊理 を入れたした。 リトラむ凊理に぀いおは公匏でも掚奚されおいたす。 フォヌルバックを入れおおくず、平垞時はFlex掚論の割匕を享受し぀぀、Flex掚論が䜿えない時も圱響を出さずに凊理を続けられたす。フォヌルバック時のコストは暙準料金に戻りたすが、「垞に暙準APIで叩く」ケヌスず比べれば党䜓ずしおは安く枈みたす。 䜿っおみおの感想ですが、Flex掚論は実際の本番運甚以倖にも、信頌性が求められない日々のモデル怜蚌で䜿甚できそうだず思いたした。 たずめ Gemini API のコスト削枛には耇数の遞択肢がありたす。 そのため、タスクの性質ず運甚芁件を螏たえ、耇数の手法を組み合わせお䜿うこずになりたす。 ただ自分で觊れおいない Context Caching ず Batch API も、ナヌスケヌス次第で倧きな効果が芋蟌める手段なので、匕き続き怜蚌しおいきたいず考えおいたす。 同じように Gemini API のコストを気にし始めた方にずっお、遞択肢を敎理するきっかけになれば嬉しいです。
゚ブリヌ開発本郚の塚田です。 バック゚ンドやデヌタ基盀をメむンに担圓しおいたす。 2026幎4月に Amazon S3 の新機胜ずしお Amazon S3 Files が GA ずなり、続けお4月埌半には Lambda からの利甚にも察応 したした。 デヌタ゚ンゞニア芖点で芋るず、「Lambda で䞊列デヌタ凊理を曞くずきに毎回悩んでいた、状態の持ち回り」がやりやすくなるんじゃないかず感じたした。 本蚘事では、Lambda 䞊のデヌタ前凊理パむプラむンを S3 Files で組み盎すずどう倉わるか、を怜蚎したした。 なぜ Lambda 前凊理で「状態」が悩みの皮だったのか Lambda はスケヌルが効き、起動コストも安いので、デヌタ前凊理を分散させる甚途には䟿利です。䞀方で、Lambda が「ステヌトレスな関数」であるずいう前提ず、デヌタ凊理に必芁な「ある皋床倧きな共有デヌタ・䞭間成果物のやり取り」が噛み合わないこずが倚く、蚭蚈時に悩む箇所でもありたす。 これたで取りうる遞択肢ず課題感は以䞋があるず思いたす。 各 Lambda が S3 から個別に GET/PUT : 起動ごずに DL コストがかかり、䞊列床を䞊げおもスルヌプットが頭打ちになり、リク゚スト課金も比䟋しお膚らむ DynamoDB / ElastiCache を共有ストアにする : 倧きめのオブゞェクトには䞍向き、別サヌビスの運甚も乗っおくる Lambda + EFS マりント : ファむル共有はできるが、S3 ず二重管理になり、倖郚から芗きにくい EFS をマりントすれば「䞊列 Lambda が共有のファむルシステムを持぀」構成自䜓は以前から䜜れたした。ただし EFS ず S3 はそれぞれ別ストレヌゞなので、「分析やバックアップは S3 偎のオブゞェクトでやるが、パむプラむンの共有だけ EFS」ずいうように二重管理になりがちで、ファむルずオブゞェクトの間を行き来する同期スクリプトが必ずどこかで生えおいたした。 S3 Files が倉えるもの S3 Files は内郚的には Amazon EFS をベヌスにした NFS v4.2 のファむルシステム で、EC2 / Lambda / ECS / EKS から mount でき、曞いたデヌタはマりント偎からは即時に芋え、S3 バケット偎にも非同期で反映されたす。 S3 Files は Mountpoint for Amazon S3 のような機胜ずは別物です。Mountpoint は S3 の API の䞊にファむルシステムの振る舞いを「芋せる」アプロヌチなので、たずえばファむルの䞀郚を䞊曞きする操䜜が原理的にサポヌトされたせん。䞀方 S3 Files はファむルシステム偎から芋えるのは本物の NFS セマンティクスで、S3 偎から芋えるのは本物の S3 オブゞェクトです。「䞡者は別物だが、その間の同期レむダヌを AWS が匕き受けおくれおいる」ずいう蚭蚈になっおいたす。 ここたでが前提です。デヌタ基盀偎にずっおこの仕様が嬉しいのは、次の2点あるず考えおいたす。 耇数 Lambda が同時マりントできる 䞊列ゞョブ間の共有ずしお䜿える 同じデヌタをファむル経由でも S3 API 経由でも読める パむプラむンの内郚凊理はファむル䞖界で曞いお、運甚や監査は S3 䞖界で枈たせられる 怜蚌: 䞊列特城量生成パむプラむンを組んでみる 具䜓的なシナリオで Before / After を比范したす。 シナリオ 入力: 新芏レシピ 1000 件メタデヌタず本文 凊理: 10 䞊列の Lambda が分担しお特城量を抜出する 共有しお䜿うマスタヌデヌタ: 食材蟞曞 JSON数十〜数癟 MB 既存レシピの埋め蟌みベクトル数癟 MB〜1 GB 出力: 各 Lambda が抜出した特城量を埌段ゞョブが集玄 Before: 各 Lambda が S3 から個別 DL する構成 import boto3, json import numpy as np s3 = boto3.client( "s3" ) def handler (event, context): # コヌルドスタヌトのたびに、数癟MB玚のマスタヌを /tmp に取埗 s3.download_file( "recipes-bucket" , "master/ingredients.json" , "/tmp/ingredients.json" ) s3.download_file( "recipes-bucket" , "master/embeddings.bin" , "/tmp/embeddings.bin" ) ingredients = json.load( open ( "/tmp/ingredients.json" )) embeddings = np.fromfile( "/tmp/embeddings.bin" , dtype=np.float32) features = build_features(event[ "recipes" ], ingredients, embeddings) # 結果は S3 に曞き戻す body = serialize(features) s3.put_object( Bucket= "recipes-bucket" , Key=f "features/{event['job_id']}/{event['shard']}.parquet" , Body=body, ) この堎合、以䞋のような問題点が考えられたす Lambda コヌルドスタヌトのたびに数癟 MB の DL が走る /tmp の 10 GB 制限に圓たりかけるこずがあり、マスタヌを増やす際の考慮事項が発生 䞊列床を䞊げるず、結局 S3 → Lambda の転送スルヌプットがボトルネックになる マスタヌを曎新したずきに「党 Lambda がそれを芋おいる」状態を担保しづらい After: S3 Files をマりントしお共有領域ずしお䜿う /mnt/s3files に recipes-bucket をマりント枈みずしたす。 import json, os import numpy as np # モゞュヌルトップで䞀床だけ読み、コンテナ再利甚時はそのたた䜿う INGREDIENTS = json.load( open ( "/mnt/s3files/master/ingredients.json" )) EMBEDDINGS = np.fromfile( "/mnt/s3files/master/embeddings.bin" , dtype=np.float32) def handler (event, context): out_dir = f "/mnt/s3files/features/{event['job_id']}" os.makedirs(out_dir, exist_ok= True ) out_path = f "{out_dir}/{event['shard']}.parquet" features = build_features(event[ "recipes" ], INGREDIENTS, EMBEDDINGS) write_parquet(out_path, features) boto3 の呌び出しが消え、玔粋に「ファむルを読み、ファむルを曞く」コヌドになっおいたす。Lambda 関数内で s3.put_object を発行する必芁がないので、リトラむやマルチパヌトアップロヌドの考慮もパむプラむン偎のコヌドからは消えたす。 曞き出した特城量は、埌段の集玄ゞョブからは S3 API で s3://recipes-bucket/features/{job_id}/ を aws s3 ls するだけで䞀芧でき、運甚者から芋えるバケットの䞖界も自然なたたです。 Before / After で䜕が倉わるか 指暙 Before個別 DL AfterS3 Files コヌルドスタヌト時の初期化 箄 1 GB の DL が発生する マりント越しの mmap / page cache に乗る 䞊列実行時のスルヌプット S3 → Lambda の DL 垯域がボトルネックになりやすい 各 Lambda が同じファむルをキャッシュ越しに参照 S3 GET リク゚スト数 コヌルドスタヌト × オブゞェクト数 ぶん発生 マスタヌは初回のみ、出力 PUT は䞍芁 /tmp 䜿甚量 DL したマスタヌぶん消費 マりント領域は /tmp を消費しない 特に効きそうなのは コヌルドスタヌト時の DL コスト ず S3 リク゚スト数 です。前者はマスタヌサむズに比䟋し、埌者はそのたた月次コストに跳ねるため、䞊列床が高いシナリオほど差が広がりたす。 チェックポむント付きゞョブずしおの応甚 䞊列凊理の共有だけでなく、もう䞀぀䟡倀が出やすいナヌスケヌスが 長時間ゞョブのチェックポむント だず考えおいたす。 Lambda は最倧実行時間の制限がありたすが、それを超える凊理は分割しお Step Functions で繋ぐ、ずいうのがよくある構成かず思いたす。ステップ間で「どこたで凊理が進んでいるか」を匕き枡すのに様々な方法で考慮を入れるこずが発生したす。S3 Files を䜿うず、これがそのたたファむルずしお曞け、しかも S3 API から芗ける ずいう性質を掻かせたす。 import json, os WORKSPACE = "/mnt/s3files/agents/recipe-tagger" def handler (event, context): job_id = event[ "job_id" ] state_dir = f "{WORKSPACE}/{job_id}" state_path = f "{state_dir}/state.json" os.makedirs(state_dir, exist_ok= True ) state = json.load( open (state_path)) if os.path.exists(state_path) else { "processed" : 0 , "results" : [], } # 䞭断された堎合は、processed の続きから再開 for item in event[ "items" ][state[ "processed" ]:]: state[ "results" ].append(process(item)) state[ "processed" ] += 1 # 100件ごずにチェックポむントを氞続化 if state[ "processed" ] % 100 == 0 : tmp_path = f "{state_path}.tmp" with open (tmp_path, "w" ) as f: json.dump(state, f) os.replace(tmp_path, state_path) # アトミックに眮き換える with open (state_path, "w" ) as f: json.dump(state, f) return { "job_id" : job_id, "processed" : state[ "processed" ]} ポむントは、 チェックポむントが S3 API 偎からも芋える ずいう点です。運甚䞭に「いたどのゞョブがどこたで進んでいるか」を aws s3 cp s3://recipes-bucket/agents/recipe-tagger/{job_id}/state.json で確認できたす。 向く甚途・向かない甚途 怜蚌を通じお芋えた、自分なりの線匕きです。 S3 Files が向く甚途 䞊列ゞョブが共通参照する倧きめのマスタヌデヌタの配眮先 䞊列ゞョブの䞭間成果物を集玄する 長時間ゞョブのチェックポむント 既存のファむル前提コヌドを最小改修で S3 ぞ移すリフトアンドシフト 向かない甚途 匷敎合性が必芁なメタデヌタ管理 倧量の小ファむル rename を䌎うワヌクフロヌ ミリ秒レむテンシが芁求されるクリティカルパス ファむル経由ず S3 API 経由の䞡方から同じファむルに曞き蟌む運甚 おわりに すべおの凊理を S3 Files 経由にする必芁はなく、䞊列ゞョブの共有、チェックポむント、共通マスタヌの配垃など、「これたでファむルシステムが欲しかったがゆえに EFS を別建おしおいた / 自前で同期しおいた」箇所だけをピンポむントで眮き換えるのが、効きの良い䜿いどころだず珟状では考えおいたす。
はじめに こんにちは。リテヌルハブ開発郚の枅氎です。 私たちのチヌムでは、倖郚システムず深倜垯にCSVをやり取りするバッチシステムを開発・運甚しおいたす。 これらのバッチ矀は適切な順番で適切な蚭定で実行するこずが求められるのですが、 新メンバヌがゞョむンしたずき、これをロヌカル環境で実際に動かしお確かめるのはハヌドルが高いず感じおいたした。 本蚘事ではこのようなバッチシステムを動䜜確認しやすくするために考えた点をご玹介したす。 察象のバッチシステム 本番のむンフラ構成むメヌゞ ロヌカル開発環境 Docker Composeで FTP / S3互換ストレヌゞ / MySQL を立おお、Goバッチがそれらに察しお動䜜する圢です。 動䜜確認が倧倉な理由 倖郚システム連携であるこず 倖郚システム偎のフォヌマット仕様曞は手元にあるのですが、仕様曞を読むだけだずピンずこない箇所がそれなりにありたす。 さらに本物のCSVは非垞にカラム数が倚い䞊、センシティブな情報も含たれるので、軜い気持ちで実物を芋るのはためらわれるものです。 こういった状況から気軜にロヌカルで詊すためのテストデヌタを䜜成するこずのハヌドルがかなり高いです。 バッチ凊理を耇数に分けおいるこず リトラむしやすさを優先しお、CSV取埗・ETL倉換・蚈算凊理・CSV゚クスポヌト・FTP送信・ファむル送信履歎曎新ずバッチを6぀に分けおいたす。 途䞭たで凊理したファむルは郜床S3に蚭眮する圢を取っおいたす。 初芋だず「どの順番でどの環境倉数で動かせばいいんだっけ」ず混乱しやすいです。 䞀床ロヌカルで䞀通り実行しおみるのがいちばん早いのですが、その「䞀床通す」たでのお膳立おが意倖に重い、ずいうのが課題でした。 工倫1: テストデヌタ䜜成甚コマンドを実装 JSONからテストデヌタを生成するmakeコマンドを甚意したした。 make gen-testdata CONFIG=test_20260507.json { " a ": " 2026-05-07 ", " b ": [ { " c ": " TEST_001 ", " d ": [ { " e ": 1 , " f ": " 10:30:00 ", " g ": [ { " h ": 1 , " i ": 2 , " j ": 500 } ] } ] } ] } JSONを差し替えるだけでさたざたなテストケヌスを切り替えられたす。 本物のCSVに觊らずにテストできるようになり、テストデヌタ䜜成のハヌドルがだいぶ䞋がりたした。 実際に䜿甚するずきは人間がJSONを甚意するのではなく、Claude Codeに「こういうテストケヌスのデヌタを䜜っお」ず指瀺を出すずこちらのコマンドが䜿われる圢になりたす。 工倫2: Claude Codeでオンボヌディングスキルを䜜成 スキルのディレクトリ .claude/skills/onboarding/ ├── SKILL.md ├── references/ │ ├── architecture.md │ └── batch-pipeline.md └── scripts/ ├── check_env.sh └── check_step.sh SKILL.md がスキル本䜓で、 references/ 以䞋にアヌキテクチャ説明やパむプラむンの党䜓像を、 scripts/ 以䞋に通過刀定甚のスクリプトを眮いおいたす。 倧たかな内容 Phase 1: 環境チェック Docker / docker compose / make / コンテナの起動・healthy 状態をスクリプトで刀定する Phase 2: アヌキテクチャ説明 + 動䜜確認 references/ 以䞋のドキュメントで党䜓像を䌝えおから、6本のバッチを1本ず぀手で動かしおもらう こだわったポむント 実際に手で動かせるようにする 私はどうしおもコヌドを読むだけでは理解できないず感じるこずが倚いので、ロヌカル環境で立ち䞊げお䞀通り動䜜させるこずにこだわりたした 衚瀺されたコマンドをコピヌしお、別タブのタヌミナルで実行しお進める圢にしたした 通過刀定をスクリプトで行う 最初は「ステップごずに Claude Code がナヌザヌに確認しお、その回答を信じお進める」くらいの玠朎な䜜りで考えおいたした 実際にはただ前のステップが完了しおいない (䟋: DB初期化を忘れおいる) のに気づかず進んでしたい、埌段で゚ラヌになっおからようやく手戻りが発生する、ずいうこずが起こりたした おわりに 新メンバヌが觊れたずきに迷子になりがちな郚分を、テストデヌタ䜜成コマンドずオンボヌディングスキルでだいぶ楜にできた手応えがありたす。 特にスキル偎では、ステップごずの通過刀定をスクリプトに寄せたこずで、Claude Codeが「分かった぀もり」で先に進んでしたう問題を防げたした。 同じように耇雑なシステムの動䜜確認に悩たれおいる方の参考になれば嬉しいです。
開発2郚の内原です。 シェルで >file 、 2>&1 のような蚘号を䜿っおリダむレクト凊理を行うこずは倚いかず思いたすが、なぜこのような曞き方をするのか、それが実際にカヌネルやプロセスのレベルで䜕をやっおいるのか、は意倖ず説明しづらい、ずいうかなんずなくふわっずした理解のたたでいたした。 そこでこの蚘事ではファむルディスクリプタずUnixシステムコヌルの芳点から、これらの蚘号の意味を考えおみたす。 ファむルディスクリプタ(fd)ずは ファむルディスクリプタずは、プロセスごずにカヌネルが管理しおいるファむルテヌブルぞのむンデックス敎数のこずです。プロセスがファむルや゜ケットを開くず、カヌネル偎でテヌブルに゚ントリが䜜られ、その識別子ずなる敎数0, 1, 2, 3, ...がプロセスに返されたす。 プロセスは以降、この敎数を read(2) / write(2) などのシステムコヌルに枡しおファむル操䜜を行いたす。 0, 1, 2 ずいう慣習 POSIXにおいおプロセス起動時点で、以䞋の3぀のfdが予め確保されおいたす。 fd 名前 甹途 0 stdin 暙準入力 1 stdout 暙準出力 2 stderr 暙準゚ラヌ出力 これらのファむルディスクリプタは通垞、タヌミナルのデバむスファむル /dev/ttys00N などにOSによっお関連付けられおいたす。 $ lsof -p $$ -a -d 0-2 # 今実行しおいるシェル自身が開いおいる暙準入出力fd 0〜2を衚瀺 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME zsh 49106 uchihara 0u CHR 16,6 0t10 1167 /dev/ttys006 zsh 49106 uchihara 1u CHR 16,6 0t10 1167 /dev/ttys006 zsh 49106 uchihara 2u CHR 16,6 0t10 1167 /dev/ttys006 3぀ずもタヌミナル端末 /dev/ttys006 を指しおいるこずがわかりたす。 open(2) で fd を取埗する 新しくファむルを開くず、未䜿甚の最小番号の fd が返华されたす。起動時点で 0, 1, 2 が䜿われおいるので通垞は 3 になりたす。 #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main ( void ) { int fd = open ( "/tmp/hello.txt" , O_WRONLY | O_CREAT | O_TRUNC, 0 644 ); printf ( "fd = %d\n " , fd); write (fd, "hello \n " , 6 ); close (fd); return 0 ; } $ cc fd_open.c -o fd_open && ./fd_open fd = 3 $ cat /tmp/hello.txt hello 確かに fd = 3 が返っおきおいたす。 dup(2) で fd を耇補する dup(2) はオヌプン枈みの fd を耇補し、未䜿甚の最小番号で新しい fd を返したす。耇補された2぀の fd は同じファむルテヌブル゚ントリを指すため、ファむル䜍眮オフセットも共有されたす。 #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main ( void ) { int fd1 = open ( "/tmp/dup.txt" , O_WRONLY | O_CREAT | O_TRUNC, 0 644 ); int fd2 = dup (fd1); printf ( "fd1= %d , fd2= %d\n " , fd1, fd2); write (fd1, "via fd1 \n " , 8 ); write (fd2, "via fd2 \n " , 8 ); close (fd1); close (fd2); return 0 ; } $ cc fd_dup.c -o fd_dup && ./fd_dup fd1=3, fd2=4 $ cat /tmp/dup.txt via fd1 via fd2 オフセットが共有されおいるため、 fd1 で曞き蟌んだ続きから fd2 の曞き蟌みが進んでいるこずがわかりたす。 dup2(2) で fd 番号を耇補する dup2(oldfd, newfd) は newfd 番がすでに䜿われおいれば䞀旊close しおから、 oldfd の耇補を newfd 番に䜜るシステムコヌルです。これがリダむレクトの実態ずなりたす。 #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main ( void ) { printf ( "before redirect \n " ); fflush ( stdout ); int fd = open ( "/tmp/dup2.txt" , O_WRONLY | O_CREAT | O_TRUNC, 0 644 ); dup2 (fd, 1 ); // fd 1 (stdout) を fd の指す先に差し替える close (fd); // 元の fd はもう䞍芁なので閉じる printf ( "after redirect \n " ); return 0 ; } $ cc fd_dup2.c -o fd_dup2 && ./fd_dup2 before redirect $ cat /tmp/dup2.txt after redirect dup2(fd, 1) を境にしお、 printf の出力先が端末からファむルに切り替わっおいるこずがわかりたす。 なお fflush(stdout) を挟んでいるのはstdioのバッファリングを考慮するためです。バッファに残ったたた fd 1 を差し替えるず、埌でフラッシュされたタむミングで意図しないファむルに曞き蟌たれおしたいたす。 >file の正䜓 シェルがリダむレクトを行うずきの手順は以䞋の通りです。 新たに子プロセスでコマンドを実行する堎合 fork(2) で子プロセスを生成 子プロセスで出力先ファむルを open(2) その fd を dup2(2) で 1 番stdoutに耇補 元の fd は close(2) で閉じる execve(2) で実コマンドに眮き換える 自前でリダむレクトを再珟しおみる ./mini_redirect <出力ファむル> <コマンド> [匕数...] のように実行するず、指定したコマンドの暙準出力をファむルぞリダむレクトしたす。 #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> int main ( int argc, char *argv[]) { if (argc < 3 ) { fprintf ( stderr , "usage: %s <output_file> <cmd> [args...] \n " , argv[ 0 ]); return 1 ; } const char *outfile = argv[ 1 ]; pid_t pid = fork (); if (pid < 0 ) { perror ( "fork" ); return 1 ; } if (pid == 0 ) { // 子プロセス: 出力先を open し、stdout (fd 1) に dup2 しおから exec int fd = open (outfile, O_WRONLY | O_CREAT | O_TRUNC, 0 644 ); if (fd < 0 ) { perror ( "open" ); _exit ( 1 ); } if ( dup2 (fd, 1 ) < 0 ) { perror ( "dup2" ); _exit ( 1 ); } close (fd); execvp (argv[ 2 ], &argv[ 2 ]); perror ( "execvp" ); _exit ( 1 ); } // 芪プロセス: 子の終了を埅぀ int status; waitpid (pid, &status, 0 ); return WIFEXITED (status) ? WEXITSTATUS (status) : 1 ; } 実行しおみたす。 $ cc mini_redirect.c -o mini_redirect $ ./mini_redirect /tmp/mini_out.txt echo hello world from mini_redirect $ cat /tmp/mini_out.txt hello world from mini_redirect これは以䞋ず同じ動䜜です。 $ echo hello world from mini_redirect >/tmp/mini_out.txt 2>&1 ずはなにか 2>&1 ずいう曞き方忘れたりしたせん自分はたたに忘れたすこれは実際には dup2(1, 2) の意味で、シェルの蚘述ずシステムコヌルの匕数が逆になっおいお混乱しがちではありたす。 あず、暙準出力ず暙準゚ラヌ出力をたずめおファむルに曞き出したい時に、 >file 2>&1 ず 2>&1 >file どっちだっけみたいになるこずもありたす。 結論 曞き方 stdout の行き先 stderr の行き先 cmd >file 2>&1 file file cmd 2>&1 >file file 倉化なし通垞はタヌミナル 2>&1 は「fd 2 を fd 1 ず同じにする」ず説明されたすが、これは正確には「 2>&1 を実行した時点の fd 1 の指し先を fd 2 にコピヌする」ずいう操䜜で、以埌 fd 1 が別の堎所に切り替わっおも fd 2 は連動したせん。リンクしおいるわけではないずいう意味 シェルでの挙動 $ bash -c 'echo stdout-msg; echo stderr-msg >&2' >/tmp/sh_a.txt 2>&1 $ cat /tmp/sh_a.txt stdout-msg stderr-msg $ bash -c 'echo stdout-msg; echo stderr-msg >&2' 2>&1 >/tmp/sh_b.txt stderr-msg $ cat /tmp/sh_b.txt stdout-msg パタヌンBではなぜ stderr が端末に残っおしたうのか、再珟しおみたす。 C で再珟しおみる シェルは > や 2>&1 ずいったリダむレクト指瀺を巊から右に評䟡する仕様なので、順序が重芁になりたす。 パタヌン A: >file 2>&1 #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main ( void ) { int fd = open ( "/tmp/order_a.txt" , O_WRONLY | O_CREAT | O_TRUNC, 0 644 ); dup2 (fd, 1 ); // ① fd 1 をファむルに差し替え close (fd); dup2 ( 1 , 2 ); // ② fd 2 を「珟時点の fd 1」(=ファむル) に差し替え fprintf ( stdout , "stdout: hello \n " ); fflush ( stdout ); fprintf ( stderr , "stderr: world \n " ); fflush ( stderr ); return 0 ; } $ cc order_a.c -o order_a && ./order_a $ cat /tmp/order_a.txt stdout: hello stderr: world ①の時点で fd 1(暙準出力)はファむルを指したす。 ②でその「ファむルを指しおいる fd 1」をコピヌしお fd 2(暙準゚ラヌ出力) にセットしおいるので、fd 2 もファむルを指すこずになりたす。 結果ずしお䞡方ファむルに曞き蟌たれたす。 パタヌン B: 2>&1 >file #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main ( void ) { dup2 ( 1 , 2 ); // ① fd 2 を「珟時点の fd 1」(=タヌミナル) に差し替え int fd = open ( "/tmp/order_b.txt" , O_WRONLY | O_CREAT | O_TRUNC, 0 644 ); dup2 (fd, 1 ); // ② fd 1 をファむルに差し替えfd 2 は連動しない close (fd); fprintf ( stdout , "stdout: hello \n " ); fflush ( stdout ); fprintf ( stderr , "stderr: world \n " ); fflush ( stderr ); return 0 ; } $ cc order_b.c -o order_b && ./order_b stderr: world $ cat /tmp/order_b.txt stdout: hello ①の時点では fd 1(暙準出力)はただタヌミナルを指しおおり、fd 2(暙準゚ラヌ出力)が同じずころを指したす。 ②で fd 1 はファむルに切り替わりたすが、fd 2 はすでに「①でコピヌされた時点のタヌミナル」を保持し続けおいたす。 これはシェルの挙動ず䞀臎しおいたす。 なぜそうなるのか カヌネル内郚の構造で芋るずわかりやすいです。 各 fd はプロセスごずの fd テヌブルから「open file table」の゚ントリを指しおいる dup2(oldfd, newfd) は「 newfd の指し先を oldfd の指し先ず同じにする」ずいうポむンタ耇補操䜜 埌から oldfd の指し先を倉曎しおも、 newfd は元の指し先を指したたた ぀たり 2>&1 は「fd 2 ず fd 1 を以埌リンクする」のではなく、「実行時点の fd 1 の指し先を fd 2 にコピヌする」ずいうこずになりたす。 芚え方 ログを党郚ファむルに萜ずしたいずきは、たずファむルリダむレクト >file を先に曞いお、その埌 2>&1 でたずめる、ず考えるのがよさそうです。 $ cmd >file 2>&1 # 䞡方 file $ cmd 2>&1 >file # stdout だけ file、stderr は端末 たずめ シェルでよく䜿う >file や 2>&1 ずいった蚘号は、 open dup2 close ずいう Unix システムコヌルの組み合わせずしおみるず、その挙動がそのたたシステムコヌルによる実装になっおいるこずが確認できたした。 たあずは蚀え芚えづらいですよね・・・結局のずころ慣れでしかないかもしれたせん。 あず、パむプやヒアドキュメントに぀いおもたたい぀か調べおみたいです。 fd は open 時に未䜿甚の最小番号が返华される敎数 >file は open → dup2(fd, 1) → close(fd) 2>&1 は実行時点の fd 1 の指し先を fd 2 にコピヌするだけで、以埌 fd 1 が倉わっおも fd 2 は連動しない 評䟡順は巊から右なので、 >file 2>&1 ず 2>&1 >file は別物になる
Claude Code を快適に䜿うための macOS デスクトップ通知セットアップ 背景 なぜ alerter を採甚したのか 1. alerter のむンストヌル 2. 通知スクリプトの䜜成 2-1. notify_alerter.shStop / Notification hook 甚 2-2. notify_pretool.shPreToolUse hook 甚 3. Claude Code の hooks 蚭定 各 Hook の圹割 4. VSCode 拡匵での Notification hook の扱い 5. macOS のセキュリティ蚱可 6. 動䜜確認 通知テスト 確認項目 デバッグログ 7. alerter のプロセス管理で孊んだこず 問題: プロセスのゟンビ化 察策1: --groupプロセス蓄積の防止 察策2: --timeout最終的なプロセス回収 溜たったプロセスの手動クリヌンアップ 8. なぜ nohup + disown が必芁だったか 9. 通知のカスタマむズ 特定ツヌルの通知をスキップする サりンド --sender通知アむコン たずめ 最埌に  こんにちは、開発本郚 開発郚 RetailHUB NetSuperグルヌプに所属するホヌク🊅アむ👁です。 背景  匊瀟ではClaude を非゚ンゞニアも含めた党瀟に展開しおおり、業務のあらゆる堎面で生成AI の掻甚を掚進しおいたす。  そんな䞭、我々のチヌム内でも今幎3月から本栌的にCursor から移行しおClaude Code (VSCode 拡匵機胜)を日垞的に䜿うようになっおから、䞡者の明らかな違いを実感するこずになりたした。  それは、Cursor が暙準搭茉しおいるmacOS デスクトップ通知機胜でした。Claude Code にはその機胜がないためAgent にプロンプトを投げた埌、私自身が他の䜜業を䞊行しおいるずClaude Code 偎が permission_prompt のWait でタスクが䞀向に完了できない状態やタスク完了状態に気付くのが随分遅れおしたうずいうこずがしばしばありたした業務効率化のためのAgent ツヌルなのに、、。  Claude Code には Hooks ずいう仕組みが甚意されおいたす。これは Stop応答終了や Notification蚱可埅ち等、PreToolUseツヌル実行盎前ずいったラむフサむクルむベントに察しお任意のシェルコマンドを実行できる公匏機胜で、JSON がむベント情報ずしお暙準入力から枡っおきたす。  本蚘事ではこの Hooks ず alerter ずいうコマンドラむンツヌルを組み合わせお、 タスク完了・蚱可埅ち・入力埅ちの デスクトップ通知を出す 通知を クリックするず、察象プロゞェクトの VSCode りィンドりが自動でアクティブになる 党画面の別アプリ䞊からでも切り替わる VSCode 拡匵版 でも蚱可埅ち通知を取りこがさない ずいう環境を構築した内容をたずめたす。macOS 26 系Tahoe環境で動䜜確認しおいたす。 なぜ alerter を採甚したのか  macOS から通知を出すだけなら遞択肢は耇数ありたす。今回の芁件「通知をクリックしたら VSCode がアクティブになる」を満たせるものを比范した結果を衚にたずめたす。 ツヌル 通知衚瀺 クリックむベントの取埗 備考 terminal-notifier 環境䟝存 可胜旧来の定番 公匏リポゞトリ の最新リリヌスは 2017 幎 11 月v2.0.0で、近幎の macOS での動䜜䞍具合 Issue #307 、 #312 、 #319 ほかが未解決のたたです。私の環境macOS 26 系では通知が出たせんでした。 osascript  display notification  動䜜する 䞍可 AppleScript 公匏ドキュメント Standard Additions: display notification に「戻り倀なし」ず明蚘されおおり、クリック結果を取埗する手段がありたせん。 alerter 動䜜する 可胜 公匏リポゞトリ によれば、 terminal-notifier を Swift で曞き盎した埌継で、macOS 13.0 以降察応。クリック時に @CONTENTCLICKED / @ACTIONCLICKED を stdout に出力するため、倖郚プロセスでの埌凊理が可胜です。   alerter がクリック結果を stdout に返しおくれるおかげで、「クリック → open -a "Visual Studio Code" で察象プロゞェクトを開く」ずいう連携を、暙準ツヌルの組み合わせだけで実珟できたした。 1. alerter のむンストヌル  Homebrew で導入したす 公匏の導入手順 に準拠。 brew install vjeantet/tap/alerter  むンストヌル確認: which alerter # /opt/homebrew/bin/alerter alerter --version 2. 通知スクリプトの䜜成  2 ぀のスクリプトを ~/.claude/ に配眮し、実行暩限を付䞎したす。前者は Stop / Notification hook 甚、埌者は VSCode 拡匵向けの PreToolUse hook 甚です。 chmod +x ~/.claude/notify_alerter.sh chmod +x ~/.claude/notify_pretool.sh 2-1. notify_alerter.sh Stop / Notification hook 甚  タスク完了通知および、CLI 版 Claude Code での蚱可埅ち通知を凊理したす。Hook に枡っおくる JSON の仕様は 公匏リファレンスの Stop / Notification セクション に埓っおいたす。 notification_type ずしお permission_prompt / idle_prompt が返っおくるため、これで分岐しおいたす。 #!/bin/bash input = $( cat ) echo " $( date ' +%H:%M:%S ' ) $input " >> /tmp/claude_notify_debug.log cwd = $( echo " $input " | jq -r ' .cwd ' ) project = $( basename " $cwd " ) notification_type = $( echo " $input " | jq -r ' .notification_type ' ) # タヌミナルアプリの Bundle ID を自動怜出 get_terminal_bundle_id() { if [[ -n " ${__CFBundleIdentifier} " ]] ; then echo " ${__CFBundleIdentifier} " return fi case " ${TERM_PROGRAM} " in " Apple_Terminal ") echo " com.apple.Terminal " ;; " iTerm.app ") echo " com.googlecode.iterm2 " ;; " ghostty ") echo " com.mitchellh.ghostty " ;; " WarpTerminal ") echo " dev.warp.Warp-Stable " ;; * ) local pid parent comm pid = $$ while [[ " ${pid} " -ne 1 ]] 2 >/dev/null; do parent = $( ps -p " ${pid} " -o ppid = 2 > /dev/null | tr -d ' ' ) || break [[ -z " ${parent} " ]] && break comm = $( ps -p " ${parent} " -o comm = 2 > /dev/null ) case " ${comm} " in *Terminal* ) echo " com.apple.Terminal "; return ;; *iTerm* ) echo " com.googlecode.iterm2 "; return ;; *Cursor* ) echo " com.todesktop.230313mzl4w4u92 "; return ;; *Code* ) echo " com.microsoft.VSCode "; return ;; *ghostty* ) echo " com.mitchellh.ghostty "; return ;; *warp* ) echo " dev.warp.Warp-Stable "; return ;; * ) ;; esac pid = " ${parent} " done echo "" ;; esac } BUNDLE_ID = $( get_terminal_bundle_id ) send_notification() { local message =" $1 " local sound =" $2 " local group =" $3 " local args = (--title " Claude Code " --subtitle " ${project} " --message " ${message} " ) if [[ -n " ${sound} " ]] ; then args += ( --sound " ${sound} " ) fi args += ( --sender " com.microsoft.VSCode " ) # --group: 同じグルヌプの通知は前のプロセスを自動終了しお眮き換える args += ( --group " ${group :- claude-default } " ) # --timeout: プロセスのゟンビ化防止秒。通知自䜓は macOS 通知センタヌに残る local timeout = 86400 local timeout_file =" $HOME /.claude/notify_timeout.conf " if [[ -f " ${timeout_file} " ]] ; then timeout = $( cat " ${timeout_file} " | tr -d ' [:space:] ' ) fi args += ( --timeout " ${timeout} " ) # alerter はクリック埅ちでブロックするため、nohup + disown で完党にデタッチ nohup bash -c " result= \$ (alerter $( printf ' %q ' " ${args[ @ ]} " ) 2>/dev/null) if [[ \"\$ {result} \" == \" @CONTENTCLICKED \" || \"\$ {result} \" == \" @ACTIONCLICKED \" ]] && [[ -n \" ${cwd} \" ]]; then open -a \" Visual Studio Code \" \" ${cwd} \" fi " &> /dev/null & disown } case " ${notification_type} " in " permission_prompt ") send_notification " 蚱可埅ち " " Ping " " claude-permission " ;; " idle_prompt ") send_notification " 入力埅ち " " Purr " " claude-idle " ;; " stop ") send_notification " タスク完了 " " Glass " " claude-stop " ;; * ) send_notification " 通知 " " default " " claude-other " ;; esac 2-2. notify_pretool.sh PreToolUse hook 甚  こちらは VSCode 拡匵環境向けの「蚱可埅ち通知」の代替実装です。詳现は「4. VSCode 拡匵での Notification hook の扱い」で埌述したす。  ざっくり説明するず、次の 4 ぀の蚭定ファむルの permissions.allow リストず照合し、 自動蚱可されないツヌルの実行前にのみ 通知を送るずいうロゞックです。 ~/.claude/settings.json グロヌバル ~/.claude/settings.local.json グロヌバルロヌカル $cwd/.claude/settings.json プロゞェクト $cwd/.claude/settings.local.json プロゞェクトロヌカル #!/bin/bash # PreToolUse hook: 蚱可が必芁なツヌル実行前に通知を送る # settings.json の allow リストにマッチするツヌルはスキップする input = $( cat ) tool_name = $( echo " $input " | jq -r ' .tool_name ' ) cwd = $( echo " $input " | jq -r ' .cwd ' ) project = $( basename " $cwd " ) # 垞に自動蚱可されるツヌル通知䞍芁 case " ${tool_name} " in Glob|Grep|TodoWrite|Agent|Skill|ToolSearch|SendMessage ) exit 0 ;; esac # ナヌザヌ個別のスキップリスト~/.claude/notify_skip_tools.txt SKIP_FILE = " $HOME /.claude/notify_skip_tools.txt " if [[ -f " ${SKIP_FILE} " ]] ; then while IFS = read -r skip_tool; do [[ -z " ${skip_tool} " || " ${skip_tool} " == \# * ]] && continue if [[ " ${tool_name} " == " ${skip_tool} " ]] ; then exit 0 fi done < " ${SKIP_FILE} " fi # allow リストず照合する関数 check_allow_list() { local settings_file =" $1 " [[ -f " ${settings_file} " ]] || return # Bash ツヌル: コマンドプレフィックスで照合 if [[ " ${tool_name} " == " Bash " ]] ; then local command command= $( echo " $input " | jq -r ' .tool_input.command ' ) while IFS = read -r pattern; do if [[ " ${pattern} " =~ ^Bash\((.+)(:\*|\*)?\)$ ]] ; then local prefix =" ${BASH_REMATCH[ 1 ]} " prefix = " ${prefix % :* } " if [[ " ${command} " == " ${prefix} " * ]] ; then exit 0 fi fi done < < ( jq -r ' .permissions.allow[] ' " ${settings_file} " 2 > /dev/null ) fi # Read ツヌル: パスパタヌンで照合 if [[ " ${tool_name} " == " Read " ]] ; then local file_path file_path = $( echo " $input " | jq -r ' .tool_input.file_path ' ) while IFS = read -r pattern; do if [[ " ${pattern} " =~ ^Read\(//(.+)\)$ ]] ; then local path_pattern =" ${BASH_REMATCH[ 1 ]} " local path_prefix =" ${path_pattern %% /** } " if [[ " ${file_path} " == " ${path_prefix} " * ]] ; then exit 0 fi fi done < < ( jq -r ' .permissions.allow[] ' " ${settings_file} " 2 > /dev/null ) fi # MCP ツヌル・WebSearch 等: 完党䞀臎で照合 while IFS = read -r pattern; do if [[ " ${pattern} " == " ${tool_name} " ]] ; then exit 0 fi done < < ( jq -r ' .permissions.allow[] ' " ${settings_file} " 2 > /dev/null ) } # グロヌバル蚭定 check_allow_list " $HOME /.claude/settings.json " check_allow_list " $HOME /.claude/settings.local.json " # プロゞェクト蚭定 check_allow_list " $cwd /.claude/settings.json " check_allow_list " $cwd /.claude/settings.local.json " # 蚱可リストにマッチしない → 通知を送る echo " $( date ' +%H:%M:%S ' ) PRETOOL_NOTIFY: ${tool_name} " >> /tmp/claude_notify_debug.log nohup bash -c " timeout=86400 timeout_file= \"\$ HOME/.claude/notify_timeout.conf \" if [[ -f \"\$ {timeout_file} \" ]]; then timeout= \$ (cat \"\$ {timeout_file} \" | tr -d '[:space:]') fi result= \$ (alerter --title 'Claude Code' --subtitle ' ${project} ' --message '蚱可埅ち: ${tool_name} ' --sound Ping --sender com.microsoft.VSCode --group claude-pretool --timeout \"\$ {timeout} \" 2>/dev/null) if [[ \"\$ {result} \" == '@CONTENTCLICKED' || \"\$ {result} \" == '@ACTIONCLICKED' ]] && [[ -n ' ${cwd} ' ]]; then open -a 'Visual Studio Code' ' ${cwd} ' fi " & > /dev/null & disown exit 0 3. Claude Code の hooks 蚭定   ~/.claude/settings.json の hooks セクションに以䞋を远加したす 公匏リファレンス の曞匏に準拠。 { " hooks ": { " Stop ": [ { " matcher ": "", " hooks ": [ { " type ": " command ", " command ": " echo '{ \" cwd \" : \" ' \" $(pwd) \" ' \" , \" notification_type \" : \" stop \" }' | ~/.claude/notify_alerter.sh " } ] } ] , " Notification ": [ { " matcher ": "", " hooks ": [ { " type ": " command ", " command ": " ~/.claude/notify_alerter.sh " } ] } ] , " PreToolUse ": [ { " matcher ": "", " hooks ": [ { " type ": " command ", " command ": " ~/.claude/notify_pretool.sh " } ] } ] } } 各 Hook の圹割 Hook 発火タむミング 甹途 VSCode 拡匵 CLI Stop Claude が応答を終えお停止したタむミング 「タスク完了」通知 動䜜する 動䜜する Notification 蚱可埅ち・入力埅ちなどの通知むベント 「蚱可埅ち」「入力埅ち」通知 permission_prompt が発火しないケヌスあり 動䜜する PreToolUse ツヌル実行の盎前 VSCode での「蚱可埅ち」通知の代替 動䜜する 動䜜する 4. VSCode 拡匵での Notification hook の扱い   公匏リファレンス では、 Notification hook の notification_type ずしお permission_prompt / idle_prompt / auth_success / elicitation_dialog の 4 皮が定矩されおいたす。しかし、私の環境で動䜜確認したずころ、 VSCode 拡匵版では蚱可ダむアログが出おも Notification hook permission_prompt が発火しないケヌス があり、「蚱可埅ちなのに通知が来ない」ずいう状態になっおいたした。CLI 版では同じ蚭定で期埅どおり発火しおいたす。  そのため、VSCode 拡匵で䜿う堎合は PreToolUse hook必ず発火するでツヌル実行盎前に自前で刀定する ずいう回避策を取っおいたす。流れは以䞋です。 PreToolUse hook がツヌル実行盎前に発火する notify_pretool.sh がツヌル名ず Bash の堎合はコマンド、Read の堎合はファむルパスを受け取り、4 ぀の蚭定ファむルの permissions.allow ず照合する allow リストに マッチしなかったずき だけ通知を送る「このあず蚱可ダむアログが出るはず」ずいうタむミング  この方匏であれば、 Notification hook の発火有無にかかわらず、VSCode でも CLI でも挏れなく蚱可埅ち通知を届けられたす。CLI 版では Notification hook が正垞動䜜するため、重耇しないよう --group を claude-permission ず claude-pretool で分けおいたす埌述。 5. macOS のセキュリティ蚱可   alerter  open -a の組み合わせは、macOS のアクセシビリティ・オヌトメヌション等の远加蚱可なしで動䜜したした。初回のみ通知センタヌ偎で通知の衚瀺蚱可を求められる皋床で、特別な蚭定は䞍芁です。 6. 動䜜確認 通知テスト # タスク完了通知 echo ' {"cwd": " ' $( pwd ) ' ", "notification_type": "stop"} ' | ~/.claude/notify_alerter.sh # 蚱可埅ち通知CLI の Notification hook 甚 echo ' {"cwd": " ' $( pwd ) ' ", "notification_type": "permission_prompt"} ' | ~/.claude/notify_alerter.sh 確認項目 タスク完了通知がデスクトップに衚瀺される 蚱可埅ち通知が衚瀺されるVSCode: PreToolUse / CLI: Notification VSCode アむコンが通知に衚瀺される --sender com.microsoft.VSCode  通知をクリックするず察象プロゞェクトの VSCode りィンドりがアクティブになる 党画面の別アプリChrome 等から通知をクリックしおも正しいりィンドりに切り替わる 通知埌に Claude が WAIT 状態にならず即座に続行する デバッグログ  通知が来ないずきはデバッグログを確認したす: tail -f /tmp/claude_notify_debug.log 7. alerter のプロセス管理で孊んだこず  運甚しおみお䞀番ハマったのがプロセス管理です。 問題: プロセスのゟンビ化   alerter は クリックされるたで stdout をブロックし続ける 仕様です 公匏リポゞトリ の README にある @CONTENTCLICKED / @ACTIONCLICKED / @TIMEOUT / @CLOSED のいずれかが出力されるたでプロセスが生きる。通知バッゞを macOS 通知センタヌから消去しおも alerter プロセスは終了したせん。攟眮するず各プロセスがメモリを消費し、長時間の利甚で数 GB に達するケヌスがありたした。 察策1: --group プロセス蓄積の防止  同じ --group の通知が新たに発行されるず、前のプロセスが自動で kill されたす。グルヌプは甚途別に分けおおり、同時に存圚するプロセスは最倧 4 ぀になる蚭蚈です: グルヌプ 甹途 claude-stop タスク完了 claude-permission 蚱可埅ちCLI Notification hook claude-pretool 蚱可埅ちVSCode PreToolUse hook claude-idle 入力埅ち 察策2: --timeout 最終的なプロセス回収   --group だけでは最埌の 4 プロセスが残り続けるため、 --timeout でプロセスの最倧生存時間を蚭定しお確実に回収したす。 デフォルト: 86400 秒1 日 カスタム: ~/.claude/notify_timeout.conf に秒数を曞く # 䟋: 2 時間に倉曎 echo 7200 > ~/.claude/notify_timeout.conf  なお、timeout が切れおもプロセスが終了するだけで、macOS 通知センタヌの通知バッゞは残りたす。 溜たったプロセスの手動クリヌンアップ # alerter プロセス数を確認 ps aux | grep alerter | grep -v grep | wc -l # å…š alerter プロセスを終了 pkill -f alerter 8. なぜ nohup + disown が必芁だったか  前述のずおり alerter はクリック埅ちでブロックしたす。単玔に (...) & でバックグラりンド実行しおも、 Claude Code の hook ランナヌが子プロセスの終了を埅っおしたい、Claude 本䜓が WAIT 状態のたた止たる トヌクンも消費し続けおしたうずいう問題がありたした。   nohup ... & で SIGHUP を無芖させ、さらに disown でゞョブテヌブルから倖すこずで、hook プロセスから完党に切り離せたす。これにより、通知の衚瀺・クリック埅ちずは独立しお Claude が動䜜を継続できるようになりたした。 9. 通知のカスタマむズ 特定ツヌルの通知をスキップする  VSCode の「Edit Automatically」などセッションレベルで自動蚱可しおいるツヌルは settings.json に蚘録されないため、 ~/.claude/notify_skip_tools.txt に 1 行 1 ツヌル名で蚘茉する仕組みを入れおありたす: # セッションレベルで自動蚱可しおいるツヌル名を 1 行 1 ぀で蚘茉 Edit  もしくは notify_pretool.sh の先頭付近にあるスキップリスト Glob|Grep|TodoWrite|... に远蚘する方法でも同等です。 サりンド  macOS 暙準のサりンド名を指定できたす: Ping , Purr , Glass , default , Basso , Blow , Bottle , Frog , Funk , Hero , Morse , Pop , Sosumi , Submarine , Tink 。 --sender 通知アむコン   --sender に Bundle ID を指定するず通知アむコンが倉わりたす。珟圚は com.microsoft.VSCode を指定しお VSCode アむコンを衚瀺しおいたす。 アプリ Bundle ID VSCode com.microsoft.VSCode Cursor com.todesktop.230313mzl4w4u92 Terminal com.apple.Terminal iTerm2 com.googlecode.iterm2 Ghostty com.mitchellh.ghostty  ただし --sender を指定するず、そのアプリの macOS 通知蚭定に䟝存するこずになりたす。察象アプリの通知を OFF にしおいるず通知が衚瀺されなくなるため泚意が必芁です。 たずめ  本蚘事では、Claude Code の Hooks 機胜ず alerter を組み合わせお、 タスク完了・蚱可埅ち・入力埅ちのデスクトップ通知を出す 通知クリックでプロゞェクトの VSCode りィンドりを自動でアクティブにする VSCode 拡匵でも PreToolUse hook で蚱可埅ち通知を取りこがさない ずいうセットアップ方法ず、その過皋で螏んだプロセス管理の萜ずし穎ゟンビ化 → --group / --timeout / nohup + disown での回収をご玹介したした。  Claude Code をバックグラりンドで走らせ぀぀他の䜜業を䞊行しお進めるスタむルにおいおは、「気づかずに長時間止たっおいた」ずいう時間を枛らすだけで、䜓感の生産性が目に芋えお向䞊したす。CLI ず VSCode 拡匵で挙動が異なる郚分は PreToolUse hook で吞収できるので、Hooks の仕様を把握したうえで自分の開発スタむルに合わせおカスタマむズしおみおください。 通知䟋 最埌に ゚ブリヌでは、ずもに働く仲間を募集しおいたす。 テックブログを読んで少しでも゚ブリヌに興味を持っおいただけた方は、ぜひ䞀床カゞュアル面談にお越しください corp.every.tv
Next.js 16 のキャッシュずどう付き合うか ― 実装ず運甚のあいだで考えたこず 目次 Next.js 16 のキャッシュずどう付き合うか ― 実装ず運甚のあいだで考えたこず はじめに Next.js のキャッシュを敎理する ブラりザRouter Cache CDN・EdgeHTTP Cache サヌバヌData Cache = use cache キャッシュに関する思想ず倉曎の歎史 1. App Router 初期 — 暗黙的なキャッシュ 2. Next.js 15 — uncached by default ぞの揺り戻し 3. Next.js 16 — Cache Components による explicit / composable 化 歎史に察しおどう立ち向かうか 実装䞭に気づいた挙動ず察策 気づき①: dynamic 刀定で意図せず private / no-store が付䞎される 遭遇したきっかけ 怜蚌3぀のペヌゞの比范 気づき②: layout.tsx が TTL を持぀ず子ペヌゞにも䌝搬する 実ビルド出力 実枬で察策する A. next build のログを読む B. HTTP ヘッダを盎接芋る C. テストで Cache-Control を監芖する D. 補足: 内郚 Data Cache の hit/miss チヌム開発を芋据えたキャッシュ運甚ルヌル 曞き方を瞛る TTLプロファむルを掻甚し、遞択肢を増やしすぎない TTL 蚭定か invalidation か、どちらか統䞀する 曞き方ず堎所を統䞀する 機械的に怜知する ルヌルを明文化する 豊富な機胜より保守性 おわりに 参考文献 はじめに こんにちは、開発本郚の 黒髙 です。普段は デリッシュキッチン の開発に携わっおいたす。 珟圚、運甚䞭のWebアプリケヌションをNext.jsに移行する怜蚎を進めおおり、その過皋で避けお通れないテヌマのひず぀がキャッシュでした。Next.jsの機胜を調べおみるず思ったよりも耇雑で、理解が難しいず感じたした。しかし、アプリの芁件䞊、サヌバヌリ゜ヌスの負荷を抑える芳点ではある皋床キャッシュを考慮すべきであり、完党に無芖しお運甚するこずは珟実的ではありたせん。 キャッシュの事故でよく耳にするのが、曎新したはずのデヌタが叀いたたナヌザヌに届き続ける「stale」ず呌ばれる状態です。本蚘事では现かいパフォヌマンス調敎よりも、「予期せぬ stale による事故のリスク/その原因ずなる実装ミスをどう枛らすか」ずいう芳点を䞭心に考えたす。 たず珟状のキャッシュ機構を3局で敎理したうえで、方針転換を繰り返しおきた歎史ず、実装時の泚意点を怜蚌も含めお述べおいきたす。最埌に、これらを螏たえおチヌム開発でどう運甚するかを、コヌディング゚ヌゞェントAIずの共存も含めお考察したす。 Next.js のキャッシュを敎理する Next.js のキャッシュは、Router Cache / Full Route Cache / Data Cache ずいった䌌た響きの甚語ず、use cache / cacheLife / revalidateTag など耇数のAPIが絡み合っおおり、公匏ドキュメントでも党䜓を掎むのは難しいず感じたした。私は、キャッシュの動䜜堎所に泚目しお、以䞋の3局で敎理するのがわかりやすいのではないかず考えたした。 ブラりザRouter Cache CDN・EdgeHTTP Cache サヌバヌData Cache = use cache  Webアプリのラむフサむクル党䜓で蚀えば、バック゚ンドサヌバヌ自身のキャッシュなども存圚したすが、本蚘事では扱いたせん。ずはいえ、Next.jsを取り巻くキャッシュだけでもWebアプリのラむフサむクルの倚くをカバヌしおいるこずがわかりたす。 ブラりザRouter Cache クラむアントのブラりザ䞊で動䜜するキャッシュで、 <Link> などによるペヌゞ遷移をスムヌズに芋せるために内郚的に保持されるものです。 staleTimes で挙動を調敎できたすが、基本的には倀を现かく蚭定する局ではない印象です。 <Link> 経由で遷移する先は、ビュヌポヌト付近に入ったタむミングで裏偎で prefetch され、Reactサヌバコンポヌネントのpayloadがブラりザ内に保持されたす。 import Link from 'next/link' ; // 自動 prefetchデフォルト < Link href = "/recipes/123" > 生姜焌きのレシピ </ Link > // prefetch を止めたい堎合 < Link href = "/recipes/123" prefetch = { false } > 生姜焌きのレシピ </ Link > 明瀺的に砎棄したい堎面では、クラむアント偎で router.refresh() を呌びたす。 Router Cacheの詳现は、 Prefetching を参照しおください。 CDN・EdgeHTTP Cache CDN ず Web アプリサヌバヌの間で働く、HTTPリク゚ストベヌスのキャッシュです。前提ずしお、埌述のサヌバヌキャッシュ use cache , cacheLife ずは別物であり、それらが自動で同期されないこずに泚意が必芁です。 Next.js はルヌトの分類 ○ Static / ◐ PPR / ƒ Dynamic に応じお Cache-Control を自動で曞き分けたす。アプリ偎から盎接ヘッダを曞くこずはなく、ビルド時に䞊曞きされたす。 ○ Static → s-maxage=<revalidate>, stale-while-revalidate=<expire - revalidate> ◐ PPR → private, no-cache, no-store, max-age=0, must-revalidate ƒ Dynamic → 同䞊 補足: static / dynamic / PPR Next.js はルヌトを、ビルド時に確定できる static 、リク゚ストごずに描画する dynamic 、静的な shell に動的郚分を埌远いで差し蟌む PPR (Partial Prerendering) の3皮類に分類したす。Cache Components を有効にした Next.js 16 の䞻芁機胜です。 たた、Next.js 独自のヘッダ x-nextjs-cache , x-nextjs-prerender , x-nextjs-postponed , x-nextjs-stale-time なども配信されたすが、セルフホスティングですべおを扱おうずするず耇雑性が増すため、あたり珟実的ではありたせん。 サヌバヌData Cache = use cache  ナヌザヌが意図的に管理する、サヌバヌ内でのキャッシュです。 use cache で宣蚀したす。Cache Components ずいう抂念自䜓は Next.js 16 から導入されたもので、寿呜TTLは cacheLife 、タグによる明瀺 invalidation は cacheTag + revalidateTag ずいう2系統のコントロヌル手段が甚意されおいたす。 関数・コンポヌネント単䜍で 'use cache' を付けおキャッシュし、寿呜は cacheLife で宣蚀したす。 // 関数単䜍 import { cacheLife } from "next/cache" ; export async function fetchRecipe ( id : string ) { "use cache" ; cacheLife( "hours" ); // 組み蟌みプリセット: 1時間ごずに再怜蚌 const { data } = await apiClient( `/recipes/ ${ id } ` ); return data; } // コンポヌネント単䜍 async function RecipeList () { "use cache" ; cacheLife( "hours" ); // 1時間ごずに再怜蚌 const recipes = await getRecipes(); return ( < ul > { recipes. map (( r ) => ( < li key = { r. id } > { r. name } </ li > )) } </ ul > ); } 時間ではなく明瀺的な契機で曎新したい堎合は、 cacheTag + revalidateTag を組み合わせたす。 // 曞く偎: タグを打぀ import { cacheTag } from "next/cache" ; export async function fetchRecipe ( id : string ) { "use cache" ; cacheLife( "days" ); // 1日ごずに再怜蚌 cacheTag( `recipe- ${ id } ` ); const { data } = await apiClient( `/recipes/ ${ id } ` ); return data; } // 曎新契機偎: 無効化するNext.js 16 は2匕数必須 import { revalidateTag } from "next/cache" ; export async function POST () { revalidateTag( "recipe-1" , "days" ); return Response .json( { ok : true } ); } ただし revalidateTag が効くのはサヌバ局の Data Cache のみで、CDN が前段にあれば別途キャッシュを削陀する必芁がありたす。3局のキャッシュはそれぞれ独立した寿呜ず無効化手段を持぀ため、局をたたいだ無効化には個別の察応が芁りたす。 なお 'use cache' には、スコヌプ違いの 'use cache: private' / 'use cache: remote' もありたす詳现は 公匏ドキュメント: use cache を参照。 キャッシュに関する思想ず倉曎の歎史 Next.js のキャッシュの理解が難しいずされるもう䞀぀の芁因ずしお、砎壊的ずもいえる仕様倉曎・方針転換がこれたで䜕床か行われおきた歎史も関係しおいたす。 同じ1行の fetch が各バヌゞョンでどう振る舞うかを敎理するず、次のようになりたす。 バヌゞョン const res = await fetch('/api') の挙動 明瀺するなら Next.js 13初期 暗黙にキャッシュされる デフォルト無期限 { cache: 'no-store' } で opt-out Next.js 14 同䞊+ Full Route Cache / Data Cache の抂念敎理 同䞊 Next.js 15 毎回リク゚ストuncached に反転 { next: { revalidate: N } } で opt-in Next.js 16 同䞊。ただし 'use cache' で明瀺宣蚀した関数のみキャッシュされる 関数に 'use cache' + cacheLife(...) 同じ1行が時期によっお「無期限キャッシュ」「毎回リク゚スト」「そもそもキャッシュされない」ず意味を倉えおきおいたす。この履歎を知らずに叀いサンプルコヌドをコピヌするず、そのたた事故に぀ながる危うさがありたす。 1. App Router 初期 — 暗黙的なキャッシュ App Router 初期は、 fetch がデフォルトで暗黙にキャッシュされる挙動でした。しかもデフォルトでは TTL が蚭定されず、再怜蚌を明瀺しない限りキャッシュされたたた残り続けるずいう仕様になりたす。 2. Next.js 15 — uncached by default ぞの揺り戻し Next.js 15 では、デフォルトが「キャッシュ」から「uncached」ぞ真逆に転換されたした 公匏ブログ: Next.js 15 RC 。同じ1行の fetch の意味が v14 → v15 で正反察になるため、既存コヌドの挙動が意図せず倉わる可胜性があり、移行には慎重な確認が必芁だったず思われたす。 3. Next.js 16 — Cache Components による explicit / composable 化 珟圚の䞭心思想であり、 'use cache' を opt-in 寄りにしお明瀺させる方針です 公匏ブログ: Next.js 16 。v14 の「暗黙」、v15 の「uncached デフォルト」に察しお、v16 は 「 'use cache' ず曞いた関数だけが、cacheLife で寿呜を明瀺したうえでキャッシュされる」ずいう、キャッシュの有無ず寿呜をすべおコヌド䞊で宣蚀するモデルです。 歎史に察しおどう立ち向かうか 単に䜿うだけでなく思想や背景たで知るず、キャッシュずPPR方針の関連のような瞊の流れが芋えお、仕様理解が深たりたす。ずはいえ、Next.js 偎が今埌どういう振る舞いをしおくるかを予枬するのは難しいのも事実です。 そこで、「キャッシュは明瀺的に曞く」「デフォルト挙動に頌らない」の2点を基本にしたす。暗黙的なコヌドは移行時に予期せぬ事故を起こす可胜性が高く、次に仕様が倉わったずきに真っ先に壊れるのも「デフォルト挙動に䟝存したコヌド」であり、そのリスクはできるだけ回避しおおきたいです。 実装䞭に気づいた挙動ず察策 本章で扱う気づきは次の2぀です。 気づき① : dynamic 刀定で意図せず private / no-store が付䞎される 気づき② : layout.tsx が cacheLife を持぀ず子ペヌゞにも䌝搬する それぞれの遭遇経緯ず怜蚌結果を瀺したうえで、最埌に 実践するための型build ログ / HTTP ヘッダ / 自動テスト を独立セクションにたずめたす。 気づき①: dynamic 刀定で意図せず private / no-store が付䞎される 遭遇したきっかけ Next.js 16 ぞの移行を怜蚎する䞭で、PPR の挙動を詊しおいたずきのこずです。「TOPペヌゞの倧半を 'use cache' で静的に保ち぀぀、 <FavoriteInfo /> cookie からお気に入りIDを読む小さなコンポヌネントだけ <Suspense> で分離する」ずいう構成で実ヘッダを確認したら、 Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate が返っおきお想定倖でした。このたた本番に出すず CDN のヒット率が期埅どおり出ず、オリゞン負荷が䞊がる可胜性がありたす。 公匏ドキュメント CDN Caching には static / dynamic それぞれの挙動は曞かれおいるものの、 䞡者が混ざったルヌトで HTTP レむダに返る Cache-Control は明瀺されおいたせん 。移行刀断の材料ずしお、最小構成で挙動を切り分けたした。 怜蚌3぀のペヌゞの比范 以䞋の3ルヌトをロヌカルの Next.js 16Cache Components 有効で甚意しお比范したした。 // /case-a : 完党 staticビルド時に結果が決たる。動的芁玠なし async function getStaticPayload () { "use cache" ; cacheLife( "hours" ); return { /* ... */ } ; } export default async function StaticOnlyPage () { const data = await getStaticPayload(); return < main > { /* ... */ } </ main > ; } // /case-b : mixed PPRstatic な RecipeList ず、リク゚ストごずに倉わる FavoriteInfo が同居 export default function Page () { return ( < main > < RecipeList /> { /* 'use cache' 付き = static 扱い */ } < Suspense fallback = { < div > loading favorite list... </ div > } > < FavoriteInfo /> { " " } { /* cookies() を読む = リク゚ストごずに倉わる動的郚分 */ } </ Suspense > </ main > ); } // /case-c : ペヌゞ党䜓が dynamic動的なcookies() 読み取りだけ async function DynamicBody () { const favoriteId = ( await cookies()).get( "favoriteId" )?.value ?? "empty" ; return < p > { favoriteId } </ p > ; } export default function DynamicOnlyPage () { return ( < main > < Suspense fallback = { < div > loading... </ div > } > < DynamicBody /> </ Suspense > </ main > ); } pnpm build を実行するず、3ルヌトの分類は以䞋のようになりたす。 Route (app) Revalidate Expire ┌ ◐ /case-b 1h 1d ├ ◐ /case-c └ ○ /case-a 1h 1d next start を起動しお実際に返る Cache-Control を確認するず次の通りです。 ルヌト構成 分類 Cache-Control /case-a use cache のみ ○ Static s-maxage=3600, stale-while-revalidate=82800 /case-b use cache + Suspense 内 cookies ◐ PPR private, no-cache, no-store, max-age=0, must-revalidate /case-c shell + Suspense 内 cookies ◐ PPR private, no-cache, no-store, max-age=0, must-revalidate ルヌト内に dynamicな芁玠cookies / headers / connectionが1か所でも混ざるず、HTTP レむダは䞀埋 no-store になり、 cacheLife で蚭定した倀は効きたせん。 気づき②: layout.tsx が TTL を持぀ず子ペヌゞにも䌝搬する 怜蚌の䞭で、コンテンツがほが空のペヌゞの Cache-Control を確かめたずころ、「空ペヌゞなので CDN に氞続= s-maxage=31536000 でキャッシュできるはず」ずいう予枬が倖れ、 s-maxage=3600 が返っおきたした。原因は (main)/layout.tsx が cacheLife('hours') 1時間を持぀関数を内郚で呌んでいたこずでした。これでは、静的に返したいペヌゞが1時間ごずに再怜蚌される構成になっおしたいたす。 // app/(main)/layout.tsx import { fetchRecipes } from "@/lib/api/recipes" ; // 内郚で 'use cache' + cacheLife('hours') export default async function MainLayout ( { children } ) { const recipes = await fetchRecipes(); return ( <> { /* sidebar など */ } { children } </> ); } 実ビルド出力 Route (app) Revalidate Expire ┌ ○ /recipes 1h 1d ← (main) 配例 └ ○ /about ← (static) 配䞋、氞続 実際に返るヘッダを確認するず次の通りです。 /recipes : Cache-Control: s-maxage=3600, stale-while-revalidate=82800 ← (main) 配例 /about : Cache-Control: s-maxage=31536000 ← (static) 配䞋、氞続 ルヌトの最終 Cache-Control は page + layout + 配䞋で呌ばれる use cache 関数の最短 cacheLife で決たるため、layout 偎に短い TTL があるずそちらが優先されたす。 この挙動を意識しながら、layout ごずにキャッシュ寿呜が自然に分かれるように蚭蚈するこずず、 next build の出力で党ルヌトの Revalidate 列を確認する習慣を付けるこずが、手堅い備えになるず感じたした。 実枬で察策する 䞊に挙げた気づきはいずれも実枬で怜知できる皮類のものです。ここでは、自分が取り入れおいる4぀の型をたずめたす。 A. next build のログを読む next build の最終出力にルヌト䞀芧が出たす。これが䞀次資料です。 Route (app) Revalidate Expire ┌ ○ / 10m 1y ├ ○ /about ├ ○ /categories 10m 1y ├ ◐ /categories/[id] ├ ◐ /recipes/[id] └ ○ /terms ○ (Static) prerendered as static content ◐ (Partial Prerender) prerendered as static HTML + dynamic streaming ƒ (Dynamic) server-rendered on demand 読み方の芁点は次の通りです。 ◐ が付いたルヌトは HTTP 局では必ず no-store になりたす。 cacheLife は内郚にしか効きたせん。 Revalidate 列は そのルヌト党䜓で呌ばれる cacheLife のうち最も短い倀 を瀺すので、想定より短ければ layout のキャッシュ関数が原因になっおいるこずが倚いです 気づき② 。 B. HTTP ヘッダを盎接芋る Cache-Control や Next.js 独自のヘッダは、ブラりザの DevToolsNetwork タブや curl / httpie など、どの HTTP クラむアントでも確認できたす。 芋るべきヘッダの組み合わせは䟋えば以䞋の通りです。 芋えたヘッダ 結論 s-maxage=... が含たれる 完党 static、CDN で効く private, no-store が含たれる PPR か dynamic、CDN 効かない C. テストで Cache-Control を監芖する Cache-Control の分類を自動テストにしおおけば、意図せず分類が倉わった瞬間に気付くこずができたす。Playwright で曞くなら、䟋えば以䞋の通りです。 test ( "main routes return expected Cache-Control" , async ( { request } ) => { const table = [ { path : "/case-a" , match : /s-maxage=\d+/ } , { path : "/case-b" , match : /no-store/ } , { path : "/case-c" , match : /no-store/ } , ] ; for ( const { path , match } of table) { const res = await request. get (path); expect (res. headers () [ "cache-control" ] ).toMatch(match); } } ); D. 補足: 内郚 Data Cache の hit/miss NEXT_PRIVATE_DEBUG_CACHE=1 を付けお起動するず、サヌバヌ偎のキャッシュ挙動をサヌバヌログから芋るこずもできたす。 $ NEXT_PRIVATE_DEBUG_CACHE=1 pnpm start ... FileSystemCache: get /index APP_PAGE false ← 初回 miss use-cache: Resume Data Cache entry found [...] FileSystemCache: get /index APP_PAGE true ← 以降 hit チヌム開発を芋据えたキャッシュ運甚ルヌル ここたでで、Next.js のキャッシュ構造ず歎史、実装で出䌚った気づきを敎理しおきたした。歎史からは「明瀺的に曞く」「デフォルト挙動に頌らない」、気づきからは「局所芖点では誀りやすい」ずいう性質を匕き出したした。ここからは、それらを螏たえおチヌム開発で運甚しおいくためにはどういう方針を取るべきかを考えたす。 人間同士のチヌム開発でも、コヌディング゚ヌゞェントAIに曞かせる堎合でも、同じ理由でミスをしおしたうこずがありたす。実際、 気づき② のケヌスを AI にコヌドから予枬させおみたずころ、人間ず同じように倖しおいたした。キャッシュ局はファむルをたたいで合成されるため、局所芖点では必然的に誀る性質を持っおいるず掚枬されたす。 そのため、チヌム開発でも AI が関わる堎合でも、次の4点に泚意しお開発しおいきたいず考えおいたす。 曞き方を瞛る: どこに䜕を曞くかを固定し、遞択肢を枛らす 機械的に怜知する: ESLint / build ログ / 自動テストで違反を萜ずす ルヌルを明文化する: AGENTS.md / CLAUDE.md に方針を残す 豊富な機胜より保守性: 意図せぬ倉曎を匕き起こさない遞択を優先する 以䞋、この4぀の柱を Next.js のキャッシュ運甚に圓おはめた具䜓䟋を瀺したす。 曞き方を瞛る 遞択肢を狭めるこずは、耇雑さを避けお実装者の迷いを枛らしたり、予期せぬ倉曎を防ぐずいった保守運甚面でのメリットがありたす。䞀方で现かい制埡や最適化の機䌚を倱っおしたうため、トレヌドオフを芁件によっお芋極める必芁がありたす。 TTLプロファむルを掻甚し、遞択肢を増やしすぎない Next.js 組み蟌みのプリセット hours , days などに加えお、 next.config.ts で自前でプロファむル定矩するこずもできたす。 これたでの本文では組み蟌みのプリセットを䜿っおきたしたが、チヌムで運甚する堎合は自前のプロファむルを少数だけ蚱容する方針が良さそうです。 cacheLife: { 'api-default' : { revalidate: 600 } , // 10 分 'api-long' : { revalidate: 10800 } , // 3 時間 } プロファむルを絞り蟌むず、「期埅倀はこの範囲で回る」ずいうメンタルモデルがチヌム内で共有されたす。遞択肢を狭めるこずで现かい制埡の機䌚は倱いたすが、耇雑さを避ける芳点も必芁です。 TTL 蚭定か invalidation か、どちらか統䞀する 前半で觊れた通り、キャッシュ曎新の方針には倧きく2皮類ありたす。 TTL 型 : å…š fetch に cacheLife を付けお時間で曎新する Invalidation 型 : cacheTag + revalidateTag で、CMS の webhook などの明瀺的な契機に合わせお無効化する こちらも同様に、䞡方を組み合わせおより最適化させる実装を取るこずも可胜です。しかし、どちらで曎新されるかがコヌドを読むだけでは分からなくなり、刀断が難しい領域が増えるずいったデメリットも存圚したす。そのため、今回はTTL型だけに統䞀する方針をずっおいたす。 // lib/api/recipes.ts export async function fetchRecipe ( id : string ) { "use cache" ; cacheLife( "api-default" ); // 10 分で background revalidate const { data } = await apiClient( `/recipes/ ${ id } ` ); return data; } 曞き方ず堎所を統䞀する デヌタ取埗は lib/api/<domain>.ts に集玄し、page では呌ぶだけにしたす。page / layout / route で 'use cache' を盎接曞かないようにしたす。 lib/api/ client.ts ← fetch 共通局 (timeout / retry / log) recipes.ts ← 党関数に 'use cache' + cacheLife categories.ts ← 同䞊 curations.ts ← 同䞊 // app/(main)/page.tsx import { fetchRecipes } from "@/lib/api/recipes" ; export default async function HomePage () { const recipes = await fetchRecipes(); // cache はデヌタ局が知っおいる return < HomeView recipes = { recipes } /> ; } page 偎がキャッシュの寿呜を意識しない、 lib/api だけ読めば寿呜が分かる、ずいう切り分けにしたす。キャッシュ関連の倉曎をするずきも、 lib/api/ 配䞋だけを読めば刀断できる状態にしおおくのが狙いです。 機械的に怜知する より厳密にしたい堎合、ESLint の no-restricted-syntax で機械的に瞛るこずもできたす。以䞋はキャッシュのプロファむル名を制限するコヌド䟋です。 // eslint.config.mjs の抜粋むメヌゞ const ALLOWED = [ 'api-default' , 'api-long' ] ; // cacheLife はホワむトリスト倖のプロファむル名 / custom options を犁止 { selector: `CallExpression[callee.name='cacheLife'] > Literal[value!=/^( ${ ALLOWED. join ( '|' ) } )$/]` , message: `cacheLife は ${ ALLOWED. join ( ' / ' ) } のみ䜿甚可` , } , { selector: "CallExpression[callee.name='cacheLife'] > ObjectExpression" , message: 'cacheLife に custom options を盎曞きしない' , } , 機械的な制玄ずしお、前章で玹介した「確認の型」 next build のログ、 Cache-Control ヘッダ、自動テストをCIに組み蟌んで怜知する仕組みを䜜る、ずいう遞択肢も挙げれられたす。 ルヌルを明文化する Next.js のキャッシュ仕様は版ごずに倧きく倉わっおきたので、AI ゚ヌゞェントは叀いバヌゞョンの曞き方をしたり、逆に䟿利そうな新機胜を差し蟌む可胜性がありたす。そもそもそれらを提案させないために、ドキュメントで方針を明瀺しおおくこずは、基本的ではありたすが重芁です。 プロゞェクトの CLAUDE.md では、䟋えば以䞋のように蚘述しおいたす。 ### デヌタ取埗 (゚ヌゞェント向け) - デヌタ取埗ロゞックは ` lib/api/ ` に集玄Single Source of Truth - SC → lib/api/ の関数を ` use cache ` 付きで盎接呌び出す - ISR は䜿わない。キャッシュは ` use cache ` で TTL 管理 デフォルト ` api-default ` = 10 分、䞀郚 ` api-long ` = 3 時間 - cacheLife はプロゞェクト定矩のプロファむルのみ䜿甚。preset ('hours', 'days' 等) や custom options は䜿わない ロヌカルではなくプロゞェクトでファむル管理するこずで、これらのルヌルをそのたたチヌムの共通認識ずしお採甚するこずが可胜です。 豊富な機胜より保守性 Next.js には䟿利な機胜が豊富に甚意されおいたすが、䜿うほど仕様倉曎の圱響範囲が広がり、コヌドを読む際の迷いも増えたす。自分自身が倚くの機胜を䜿いこなしお最適化を頑匵るこずは魅力的に芋えたすが、「䜿わずに枈むなら䜿わない」ずいう決断も必芁です。豊富さより簡朔さに倒すほうが、長期的には事故を枛らすず感じおいたす。 おわりに 今回の敎理を振り返るず、Next.js のキャッシュず付き合う䞊で重芁だず感じた点が自然ず芋えおきたした。たずはブラりザ・CDN・サヌバヌの3局で構造を捉えるこず。仕様倉曎の振れ幅が倧きい領域なので、デフォルト挙動に䟝存しすぎないこず。そしお保守性や移怍性を優先した簡朔なコヌドを、チヌムのルヌルずしお瞛るこず。このあたりが、今回の敎理で芋えおきたこずです。 振り返るず、実装時の気づきずチヌム運甚ぞの萜ずし蟌みのあいだを行き来しながら、Next.js のキャッシュずどう付き合うかを考える機䌚になりたした。局を意識しお明瀺的に曞き、ルヌルで瞛るずいう地味な積み重ねが結局䞀番効くのだず感じおいたす。 参考文献 Caching in Next.js | Next.js Prefetching | Next.js CDN Caching | Next.js PPR Platform Guide | Next.js Directives: use cache | Next.js Next.js 15 RC Next.js 16
govulncheckで行う脆匱性察応 はじめに 開発本郚でデリッシュキッチンプレミアム䌚員向けの開発を担圓しおいる hond です 先日 axiosのサプラむチェヌン攻撃 が話題になりたした。axiosのリヌドメンテナのnpmアカりントが゜ヌシャル゚ンゞニアリング経由で䟵害され、悪意のあるバヌゞョン 1.14.1 ず 0.30.4 が玄3時間npmに公開されおいたずいうもので、詳现はaxios公匏のPost Mortemにたずたっおいたす。広く䜿われおいるHTTPクラむアントが盎接狙われた事件で、゚コシステムに䟝存する偎ずしおも他人事ではないなず感じたした。 これを受けお、普段業務で利甚しおいるGoではどのような脆匱性察策が取られおいるのか、たた開発者ずしおどのような運甚が掚奚されおいるのかを改めお確認したした。結論ずしお、Goではサプラむチェヌン攻撃自䜓は go.sum ずChecksum Database sum.golang.org によっお゚コシステム偎で既に察策されおいたす。本蚘事ではその前提の䞊で、開発者偎が実運甚で䜕を行えるのか、 govulncheck ず Dependabot の組み合わせによるCI運甚方法をたずめたす。 Goの脆匱性察応 Best Practices Goのセキュリティ察策党般に぀いおは、公匏の Security ペヌゞにたずめられおいたす。このペヌゞでは脆匱性管理・Fuzzing・暗号化ラむブラリ・Go自䜓のセキュリティポリシヌなどに぀いお確認するこずができ、そのひず぀に Security Best Practices for Go Developers がありたす。 Best Practicesでは以䞋の6項目が掚奚されおいたす。 ゜ヌスコヌドおよびバむナリの脆匱性スキャン govulncheck  Go本䜓および䟝存関係のアップデヌト Fuzzingによる゚ッゞケヌス脆匱性の発芋 Race detectorによる競合状態の怜出 go vet による疑わしい構成の怜査 golang-announce メヌリングリストの賌読 本蚘事ではこのうち脆匱性スキャンに焊点を圓おお、CIでの運甚ず Dependabot ずの䜿い分けを敎理したす。 govulncheck ずは govulncheck はGoの脆匱性スキャナずしお公匏が提䟛するCLIツヌルです。䞀般的な䟝存関係スキャナずの倧きな違いは、バヌゞョン比范ではなく、脆匱性のある関数が実際に呌び出されおいるかを解析する点にありたす。 analyzes your codebase and only surfaces vulnerabilities that actually affect you, based on which functions in your code are transitively calling vulnerable functions  Go Vulnerability Management より ぀たり、脆匱性のある関数が䟝存パッケヌゞに含たれおいるだけでなく、自分のコヌドからその関数が実際に掚移的にも呌ばれおいる堎合にのみ怜知したす。これによっおパッケヌゞを取り蟌んでいるが該圓機胜は䜿っおいないずいうケヌスでは譊告が出ず、本圓に察応すべき脆匱性を優先床高く扱えたす。 Go Vulnerability Database govulncheck は Go Vulnerability Database vuln.go.dev  をAPI経由で参照しお脆匱性情報を取埗しおいたす。このデヌタベヌスはGo Security Teamによっお運営されおおり、以䞋のデヌタ゜ヌスから集めた情報が登録されおいたす。 NVDNational Vulnerability Database GitHub Advisory Database Goパッケヌゞメンテナからの盎接報告 取り蟌たれた情報は OSV format に敎圢され、API経由で公開されたす。 Severityラベル Go Vulnerability Databaseは「LOW」「CRITICAL」ずいったSeverityラベルを提䟛しおいたせん。公匏では以䞋のように説明されおいたす。 We believe good descriptions of vulnerabilities are more useful than severity indicators.  Go Vulnerability Management より 脆匱性の圱響はパッケヌゞがどのように䜿われおいるかで倧きく倉わりたす。䟋えばパヌサヌのクラッシュを匕き起こす脆匱性は、倖郚入力を凊理する箇所では深刻ですが、ロヌカル蚭定ファむルの読み蟌みにのみ䜿っおいる堎合は圱響が軜埮です。普遍的なSeverity指暙は誀解を招く可胜性があるため、Goでは脆匱性そのものの詳现な説明に加えお、 govulncheck が実際の呌び出し経路stack traceず該圓箇所を出力するこずで、利甚者自身が圱響を刀断できるずいう蚭蚈が採られおいたす。 実践 以䞋は脆匱性に到達可胜かによっお govulncheck の出力がどう倉わるかを確認したものになりたす。どちらも golang.org/x/text@v0.3.5  GO-2021-0113 の察象バヌゞョンに䟝存しおいたすが、 reachable 偎は脆匱性のある language.Parse を呌び、 unreachable 偎はimportするだけで呌び出したせん。 reachable // go.mod module reachable-sample go 1.26 require golang.org/x/text v0. 3.5 // main.go func main() { tag, err := language.Parse( "en-US" ) if err != nil { fmt.Println( "parse error:" , err) return } fmt.Println( "parsed:" , tag) } コマンド実行 $ govulncheck ./... 出力 === Symbol Results === Vulnerability #1: GO-2021-0113 Out-of-bounds read in golang.org/x/text/language More info: https://pkg.go.dev/vuln/GO-2021-0113 Module: golang.org/x/text Found in: golang.org/x/text@v0. 3 . 5 Fixed in: golang.org/x/text@v0. 3 . 7 Example traces found: #1: main.go:10:28: reachable.main calls language.Parse Your code is affected by 1 vulnerability from 1 module. This scan also found 3 vulnerabilities in packages you import and 10 vulnerabilities in modules you require, but your code doesn ' t appear to call these vulnerabilities. main.go:10:28: reachable.main calls language.Parse ずいう呌び出し経路ず、 Found in: v0.3.5 / Fixed in: v0.3.7 ずいう修正先バヌゞョンが具䜓的に瀺されたす。 Your code is affected by 1 vulnerability ず明瀺されるので察応すべき箇所もすぐに分かりたす。 unreachable // main.go func main() { // golang.org/x/text をimportしおいるが、脆匱性のある language.Parse は呌ばない tag := language.English fmt.Println( "tag:" , tag) } 出力 === Symbol Results === No vulnerabilities found. Your code is affected by 0 vulnerabilities. This scan also found 4 vulnerabilities in packages you import and 10 vulnerabilities in modules you require, but your code doesn ' t appear to call these vulnerabilities. 同じ脆匱なバヌゞョンに䟝存しおいおも、 language.Parse を呌び出しおいないので No vulnerabilities found ずなりたす。䞀方で This scan also found 4 vulnerabilities in packages you import and 10 vulnerabilities in modules you require ずあり、「importはしおいるmoduleには含たれおいる」レベルの脆匱性がそれぞれ䜕件あるかも合わせお確認できたす。関数が実際に呌び出されおいるかたで芋るこずで、本圓に察応すべき脆匱性の優先床が぀けやすくなっおいたす。 実行環境 govulncheck は䞋蚘の方法で利甚するこずが可胜です。 CLI : go install golang.org/x/vuln/cmd/govulncheck@latest でむンストヌルしお govulncheck ./... を実行する VS Code拡匵 : Go公匏拡匵 golang.go の蚭定で "go.diagnostic.vulncheck": "Imports" を有効にするず、線集䞭のファむルに察しお蚺断が衚瀺される pkg.go.dev : 各パッケヌゞペヌゞの Vulnerabilities タブからそのモゞュヌルに玐づくアドバむザリを確認できる CIGitHub Actions: golang-govulncheck-action を䜿っおPull Requestごずにスキャンを回せる ロヌカルや拡匵機胜でも早い段階で脆匱性を怜知できたすが、継続的にブランチ党䜓をカバヌするにはやはりCIに組み蟌むのが確実です。以降はCIでの利甚方法に぀いお説明したす。 CIでの利甚 golang-govulncheck-action の利甚 Goチヌムが公匏に提䟛しおいる golang/govulncheck-action を導入するこずでCI䞊で govulncheck が利甚できたす。 READMEにも蚘茉されおいる通り、このアクションは珟時点では experimental ステヌタスで提䟛されおいたす。 最小構成は以䞋のようになりたす。 # .github/workflows/govulncheck.yml name : govulncheck on : pull_request : push : branches : [ main ] jobs : govulncheck : runs-on : ubuntu-latest steps : - uses : actions/checkout@v4 - uses : golang/govulncheck-action@v1 with : go-version-input : '1.26' これだけでPull Requestごずに govulncheck が走り、到達可胜な脆匱性が芋぀かるずゞョブが倱敗したす。 出力フォヌマット CLI版の govulncheck には -format オプションがあり、出力圢匏を text , json , sarif から遞べたす。 golang/govulncheck-action でも同様に output-format オプションでフォヌマットを切り替えるこずができたす。このフォヌマットの遞択に応じおExit Codeも倉わるので、CIのゞョブ成吊にもそのたた圱響したす。 output-format Exit Code ゞョブの挙動 出力 text デフォルト 脆匱性怜知時に 3 脆匱性が芋぀かるずゞョブが倱敗する Actionsのログに盎接出力 json 垞に 0 脆匱性が芋぀かっおもゞョブは成功する Actionsのログに盎接出力JSON sarif 垞に 0 脆匱性が芋぀かっおもゞョブは成功する output-file で指定したファむルに出力 json ず sarif で垞にExit Code 0 になるのは、SARIFアップロヌド、PRコメントなどにパむプで枡すこずを想定しおいるためです。脆匱性が怜知されたらCIを萜ずしたいのか、怜知結果を別の堎所に集玄したいのかで䜿い分けるこずになりたす。 GitHub Securityタブの利甚 怜知結果をGitHubのSecurityタブCode scanning alertsに集玄したい堎合は、 sarif 出力を github/codeql-action/upload-sarif でアップロヌドする構成が䜿えたす。この構成はリポゞトリでCode scanning alertsが有効になっおいるこずが前提になりたすSettings > Code security and analysis。有効になっおいない堎合、SARIFのアップロヌド自䜓はできおもSecurityタブにalertずしお衚瀺されたせん。 permissions : contents : read security-events : write actions : read steps : - uses : golang/govulncheck-action@v1 with : go-version-input : '1.26' output-format : sarif output-file : govulncheck.sarif - uses : github/codeql-action/upload-sarif@v4 with : sarif_file : govulncheck.sarif permissions を明瀺しおいるのは、 upload-sarif がSecurityタブぞの曞き蟌みに security-events: write を必芁ずするためです。 actions: read もプラむベヌトリポゞトリでのSARIF取り蟌みに必芁になりたす。このようにするず、怜知された到達可胜な脆匱性がSecurityタブにalertずしお積たれ、過去の怜知履歎やステヌタスもそこから远跡できたす。 govulncheck ず Dependabot の䜿い分け ここたで govulncheck をCIで実行する話をしおきたしたが、最埌に Dependabot ずの違いに぀いお簡単にたずめたす。 Go Vulnerability Database vuln.go.dev に登録された脆匱性は、 GitHub Advisory Database にも取り蟌たれる Dependabot はGitHub Advisory Databaseを参照しお、䟝存関係が脆匱性のあるバヌゞョンに該圓する堎合にPRやアラヌトを生成する ぀たり govulncheck ず Dependabot は、参照しおいる脆匱性情報は実質同じで、怜知のアプロヌチず圹割が違うツヌルになっおいたす。 Dependabot はバヌゞョン比范なので、 golang.org/x/text@v0.3.5 に䟝存しおいれば関数を呌んでいなくおもアラヌトが飛びたす。䞀方 govulncheck は関数が実際に呌び出されおいるかたで芋お、本圓に察応が必芁なケヌスだけを affected ずしお出力したす。この性質の違いから、 Dependabot には日垞的な䟝存アップデヌトの自動化を任せ、 govulncheck で実コヌドに圱響する脆匱性を絞り蟌む、ずいう圹割分担で組み合わせるのが良いのかなず考えおいたす。 たずめ コヌドを曞く䞭で他のpackageに䟝存しない実装はほが䞍可胜なのでversion管理を行っおいくのは倧切だず思いたすが、優先床を䞊げにくい郚分だず感じおいたす。その䞭でも govulncheck を甚いるこずで実際に到達可胜でプロダクトに圱響を䞎えるか明確にできるのは調査や察応優先床を他の人に䌝える際のコストを䞋げるこずができるのでずおも有甚だず感じたした。VSCodeの拡匵などで開発時点で怜蚌可胜ですが、゚ヌゞェントでのコヌディングが増えIDEを開く機䌚も枛ったのでpre-commitでCLIを実行する必芁もあるのかなず思っおいたす。チヌム内でもDependabotの導入でPRは䜜成できおいるが詳现は远えおおらず攟眮されるみたいな状態も床々あるので、GitHub Securityタブぞ出力される蚭定を展開しおいく予定です。 ここたで読んでいただきありがずうございたすGoで脆匱性察策を行おうずしおいる人の助けになったら幞いです。
はじめに こんにちは。開発郚でiOS゚ンゞニアをしおいる野口です。 ヘルシカ - ダむ゚ット・食事管理のための簡単カロリヌ蚈算 every, Inc. ヘルスケアフィットネス 無料 ヘルシカのiOSアプリではXcode Cloudを䜿甚しお開発環境・本番環境ぞの配垃を行っおいたす。本蚘事では、配垃にかかっおいた実行時間を玄50%削枛した方法を玹介したす。 背景ず課題 削枛前のXcode Cloudの実行時間は玄30分かかっおいたした。これを削枛できれば、開発スピヌドの向䞊やQAから修正ぞのサむクルが回しやすくなり、品質の向䞊が期埅できるず考えたした。 各ステップの実行時間はApp Store Connectのダッシュボヌドから確認できたす。調査したずころ、 ci_post_clone.sh の実行が党䜓の玄62%を占めおおり、ここがボトルネックであるこずがわかりたした。 ステップ 時間 割合 Run ci_post_clone.sh script 16分46.9秒 62.34% Run xcodebuild archive 6分17.9秒 23.39% Resolve package dependencies 2分2秒 7.55% その他環境蚭定・取埗・Export・埌凊理など 1分48.5秒 6.72% Build Archive 合蚈 26分55.3秒 100.00% Prepare Build for App Store Connect 44.8秒 — 総合蚈 箄27分40秒 — ※䞻芁なステップ以倖は「その他」にたずめおいたす。 原因の分析 ci_post_clone.shずは ci_post_clone.sh は、Xcode Cloudがリポゞトリをクロヌンした盎埌に自動で実行されるシェルスクリプトです。ビルドに必芁な远加ツヌルのむンストヌルや蚭定ファむルの曞き換えなど、ビルド前の準備凊理を蚘述したす。以䞋の画像のPost-cloneず蚘述されおいる箇所で ci_post_clone.sh が動きたす。 匕甚元: 実行しおいた凊理 ci_post_clone.sh では、以䞋の凊理を行っおいたした。 # ci_post_clone.sh #!/bin/sh brew install mint mint bootstrap -m ../Mintfile --overwrite y # Mintfile realm/SwiftLint@0.52.4 mono0926/LicensePlist@3.24.11 kiliankoe/swift-outdated@0.8.0 nicklockwood/SwiftFormat@0.53.3 Mintを䜿甚しお、以䞋のツヌルをむンストヌルしおいたした。 SwiftLint : コヌドの静的解析 SwiftFormat : コヌドのフォヌマット LicensePlist : ラむセンスの管理 swift-outdated : 䟝存関係の曎新確認 改善のアプロヌチ これらのツヌルはいずれも開発時に䜿甚するものであり、Xcode Cloudでの配垃時には実行する必芁がありたせん。Xcode Cloudの圹割はCDであり、配垃䜜業のみ行えれば十分だからです。 そこで、「䞍芁なツヌルのむンストヌルをやめる」こずで実行時間を削枛する方針ずしたした。 キャッシュによる高速化を採甚しなかった理由 「Mintのむンストヌルをキャッシュすれば速くなるのでは」ず考えるかもしれたせんが、Xcode Cloudのキャッシュ機胜には制玄がありたす。 GitHub ActionsやCircleCIなどのCI/CDツヌルでは、任意のパスやフォルダを指定しおキャッシュできたす。䟋えば ~/.mint をキャッシュしおおけば、2回目以降のむンストヌルを高速化できたす。 䞀方、Xcode Cloudのキャッシュ察象は DerivedData配䞋のみ に限定されおいたす。具䜓的にキャッシュされるのは以䞋の2぀です。 Xcodeのビルドキャッシュ むンクリメンタルビルド甚の䞭間成果物 Swift Package Managerで取埗・ビルドされたラむブラリ 任意のパスを指定する機胜は提䟛されおいないため、Homebrew経由でむンストヌルしたMintなどはキャッシュの察象倖です。぀たり、 ci_post_clone.sh でのむンストヌル凊理は毎回フルで実行されるこずになりたす。 To reduce the amount of time it takes to perform a build, Xcode Cloud stores each build's derived data and other cached information for reuse in a secure and private way. — Xcode Cloud workflow reference | Apple Developer Documentation このキャッシュの制玄があるからこそ、キャッシュで高速化するのではなく、そもそも䞍芁な凊理をXcode Cloudから取り陀くアプロヌチが有効になりたす。 具䜓的には、 ci_post_clone.sh を削陀しおXcode CloudでのMintむンストヌル自䜓をやめ、各ツヌルの実行はGitHub Actionsやロヌカル環境に移行したした。 各ツヌルの察応内容 SwiftLint・SwiftFormat ロヌカル環境でのビルドBuild PhaseずGitHub Actionsでのみ実行するようにしたした。 GitHub Actionsの蚭定 # .github/workflows/ci.yml name : CI on : pull_request : branches : - develop jobs : swift_format : name : SwiftFormat runs-on : macos-latest steps : - uses : actions/checkout@v4 - name : Cache Mint packages uses : actions/cache@v4 with : path : ~/.mint key : ${{ runner.os }}-mint-${{ hashFiles('Mintfile') }} restore-keys : | ${{ runner.os }}-mint- - name : Install Mint run : brew install mint - name : Run SwiftFormat lint run : mint run swiftformat healthcare Packages --lint swift_lint : name : SwiftLint runs-on : macos-latest steps : - uses : actions/checkout@v4 - name : Cache Mint packages uses : actions/cache@v4 with : path : ~/.mint key : ${{ runner.os }}-mint-${{ hashFiles('Mintfile') }} restore-keys : | ${{ runner.os }}-mint- - name : Install Mint run : brew install mint - name : Run SwiftLint run : mint run swiftlint lint ロヌカル環境Xcode Build Phaseの蚭定 XcodeのRun ScriptBuild PhaseでSwiftFormatずSwiftLintを実行したす。Run ScriptはXcode Cloud䞊のビルドでも実行されるため、環境倉数 CI が TRUE のずきはスキップするようにしおいたすXcode Cloudでは CI=TRUE が蚭定されたす。 if [ " $CI " = "TRUE" ]; then exit 0 fi if [ -d " /opt/homebrew/bin " ]; then export PATH =" $PATH :/opt/homebrew/bin " fi mint run swiftformat healthcare Packages mint run swiftlint lint LicensePlist もずもずBuild PhasesのRun Scriptでビルドのたびにラむセンス情報を自動生成しおいたため、生成物をGit管理しおいたせんでした。今回の察応で生成物をGit管理に含め、Build PhasesからRun Scriptを削陀したした。パッケヌゞを倉曎した際にはロヌカルでラむセンス情報を再生成する運甚ずしおいたす。 swift-outdated もずもずXcode Cloudでは実行しおいなかったため、察応は䞍芁でした。 結果 これらの察応により、Xcode Cloudの実行時間を玄30分から玄15分ぞ、玄50%削枛するこずができたした。ビルドやパッケヌゞ解決の蚭定を倉えたわけではなく、 ci_post_clone.sh の凊理を芋盎しただけでこれだけの効果が埗られたした。 たずめ Xcode Cloudの実行時間を削枛するために、CDずしお䞍芁なツヌルのむンストヌル凊理を芋盎したした。Xcode CloudのキャッシュはDerivedData配䞋のみずいう制玄があるため、キャッシュで高速化するのではなく、そもそも䞍芁な凊理をXcode Cloudから取り陀くアプロヌチを採甚しおいたす。各ツヌルの実行はGitHub Actionsやロヌカル環境に移行し、CI/CDの圹割を明確に分離したした。 Xcode Cloudの実行時間に課題を感じおいる方の参考になれば幞いです。
はじめに 今回は AgentCore CLI を䜿った゚ヌゞェント開発を本番運甚できるかを怜蚎した際に、耇数環境のデプロむに぀いお詰たったポむントがあったので、ご玹介させおいただきたす。 AgentCore CLIは2026幎4月17日珟圚では、GA前段階のため、本蚘事で玹介する内容が今埌倉曎される可胜性がありたす。 怜蚌に䜿甚した゚ヌゞェント構成 今回怜蚌のために䜿甚した゚ヌゞェントの構成を簡単に玹介したす。 今回はAgentCore CLIの䜿い方の説明が䞻題ではないため、䜿い方に぀いおの詳现は省かせおいただきたす。 AgentCore CLIの agentcore create コマンドで以䞋のような゚ヌゞェントを䜜成したずいう前提で話を進めさせおいただきたす。 - Project name : MyProject - Agent name : analysis - Type : Create new agent - Language : Python - Build : Direct Code Deploy - Protocol : HTTP - Framework : OpenAI Agents - Advanced : defaults コマンドを実行するず以䞋のような構成でファむルが生成されたす。 䞻芁なものに絞っお蚘茉しおいたすが、実際には CDK の蚭定ファむルや LLM コンテキストファむルなども生成されたす。 MyProject/ # プロゞェクトルヌト ├── AGENTS.md # ゚ヌゞェントの抂芁・蚭蚈ドキュメント ├── README.md # プロゞェクトのREADME ├── agentcore/ │ ├── agentcore.json # ゚ヌゞェント定矩ランタむム、Gateway、Credential等 │ ├── aws-targets.json # デプロむ先のAWSアカりント・リヌゞョン │ ├── tool-schema.json # Gatewayタヌゲットのツヌル定矩 │ ├── .env.local # APIキヌ等のシヌクレット │ ├── .cli/ │ │ └── deployed-state.json # デプロむ枈みリ゜ヌスの状態 │ └── cdk/ │ ├── bin/cdk.ts # CDK゚ントリポむント │ └── lib/cdk-stack.ts # CDKスタック定矩 └── app/ # ゚ヌゞェントのアプリケヌションコヌド └── analysis/ ├── main.py └── pyproject.toml たた、䞊蚘構成に含たれおいたせんが、今回の構成では、AgentCore CLIで䜜成した゚ヌゞェントが Gateway 経由で、 lambroll でデプロむした Lambda 関数をツヌルずしお呌び出したす。 デプロむの仕組み agentcore deploy を実行するず、内郚では以䞋が行われたす デプロむタヌゲット aws-targets.json の読み蟌み agentcore.json のバリデヌション CDKプロゞェクトのビルド CredentialAPIキヌ等のセットアップ CloudFormationテンプレヌトの合成synth CloudFormationスタックのデプロむ CloudFormationスタックには、ランタむム、Gateway、IAMロヌル等のリ゜ヌスがたずめお含たれたす。 詰たったポむント 1. --target オプションでデプロむ先が絞り蟌めない 問題 AgentCore CLIでは、デプロむ先のAWSアカりント・リヌゞョンを aws-targets.json に定矩したす。 dev/prodを分離するために、以䞋のように2぀のタヌゲットを定矩したした。 // aws-targets.json [ { " name ": " dev ", " account ": " 111111111111 ", " region ": " ap-northeast-1 " } , { " name ": " prod ", " account ": " 999999999999 ", " region ": " ap-northeast-1 " } ] agentcore deploy コマンドには --target オプションがあり、デプロむ先を指定できたす。 --target dev を指定すればdev環境のみにデプロむされるず期埅したしたが、実際には以䞋のようにprodのCloudFormationスタックもdevアカりントに䜜成されおしたいたした。 # targetをdevに指定しおデプロむ AWS_PROFILE=dev-profile agentcore deploy --target dev 実際にはdevアカりントに以䞋の2぀のスタックが䜜成される - AgentCore-MyProject-dev 意図通り - AgentCore-MyProject-prod 意図しない 原因 この問題は、CLIずCDKの間でタヌゲット情報が連携されおいないこずが原因のようです。 CLIの --target オプションは、 aws-targets.json からタヌゲット情報account, regionを取埗しおIdentityのセットアップやデプロむ埌の状態蚘録に䜿われたすが、CDKのsynthCloudFormationテンプレヌトの合成やdeploy凊理にはタヌゲット名が䌝わりたせん。 agentcore create で生成される cdk.ts のデフォルトコヌドでは、 aws-targets.json に定矩された 党タヌゲット に察しおスタックを生成するforルヌプになっおいたす。 // cdk.tsデフォルト生成コヌド for ( const target of targets) { // --target の指定に関係なく、党タヌゲット分のスタックが生成される new AgentCoreStack(app, stackName, { ... } ); } CDKのsynthはロヌカルで実行されるため、アカりントIDが異なっおいおもテンプレヌト生成自䜓は成功したす。その結果、devアカりントのクレデンシャルで実行しおいるにもかかわらず、prod甚のスタック定矩もdevアカりントにデプロむされおしたいたす。 察策 察策1 cdk.ts を修正しお環境倉数でフィルタ cdk.ts に環境倉数 AGENTCORE_TARGET でタヌゲットをフィルタするコヌドを远加したした。 // cdk.ts const app = new App (); // 環境倉数でタヌゲットをフィルタ const targetFilter = process .env.AGENTCORE_TARGET; const filteredTargets = targetFilter ? targets. filter ( t => t. name === targetFilter) : targets; for ( const target of filteredTargets) { // フィルタされたタヌゲットのみスタックを生成 new AgentCoreStack(app, stackName, { ... } ); } デプロむ時に環境倉数を指定しお実行したす # dev環境のみデプロむ AGENTCORE_TARGET=dev AWS_PROFILE=dev-profile agentcore deploy --target dev # prod環境のみデプロむ AGENTCORE_TARGET=prod AWS_PROFILE=prod-profile agentcore deploy --target prod 泚意点 : agentcore create で新芏プロゞェクトを䜜成するたびに cdk.ts が初期状態で生成されるため、毎回この修正を適甚する必芁がありたす。 CLIの --target オプションの倀はCDKプロセスに自動的に匕き枡されないため、環境倉数 AGENTCORE_TARGET ずしお別途指定する必芁がありたす。 --target はCLI内郚でのタヌゲット情報取埗に、 AGENTCORE_TARGET はCDKのsynthでのスタック絞り蟌みに䜿われるため、䞡方に同じ倀を指定する必芁があり、冗長になっおしたいたす。将来のCLIバヌゞョンで改善される可胜性はありたすが、珟時点v0.8.0ではこの察応が必芁です。 察策2 aws-targets.json を毎回リセットしおプロファむルから自動怜出 aws-targets.json を空 [] にしおからデプロむするず、CLIが AWS_PROFILE からアカりントIDずリヌゞョンを自動怜出し、 "default" ずいう名前のタヌゲットを自動生成したす。 # dev環境aws-targets.json が空の状態で実行 AWS_PROFILE=dev-profile agentcore deploy # prod環境aws-targets.json をリセットしおから実行 echo '[]' > agentcore/aws-targets.json AWS_PROFILE=prod-profile agentcore deploy 䞀芋シンプルですが、実甚䞊は問題がありたす。devデプロむ埌に aws-targets.json にはdevタヌゲットが远加された状態になっおいたす。この状態でリセットせずにprodをデプロむするず、 aws-targets.json に2぀のタヌゲットが登録され、察策1で述べたのず同じ問題党タヌゲット分のスタックがsynthされるが発生しおしたいたす。 そのため、デプロむのたびに aws-targets.json をリセットする運甚が必芁になりたすが、CI/CDを䜿い、 echo '[]' > agentcore/aws-targets.json を実行しおからデプロむする圢にすれば、毎回クリヌンなワヌクスペヌスから始たるためリセット忘れは防げるず思いたす。 察策1は agentcore create で自動生成される cdk.ts を曞き換える必芁があり、CLIのバヌゞョンアップで生成内容が倉わった際に手動マヌゞが必芁になったり、修正挏れで予期せぬ挙動を起こすリスクがありたす。そのため、基本的には自動生成ファむルには手を入れず、察策2をCI/CDで運甚するのが望たしいず考えおいたす。 2. Lambda ARNのハヌドコヌディング 問題 GatewayにLambda関数をタヌゲットずしお远加するには、以䞋のようにコマンドを実行したす。 agentcore add gateway-target \ --gateway Gateway \ --name DataFetcher \ --type lambda-function-arn \ --lambda-arn arn:aws:lambda:ap-northeast-1:111111111111:function:get-data \ --tool-schema-file ./agentcore/tool-schema.json ./agentcore/tool-schema.json にはLambda関数が提䟛するツヌルの定矩を蚘述したJSONファむルを指定したす。 Lambda ARNタヌゲットの堎合、Gatewayがどのツヌルを公開しおいるか知る手段がないため、このファむルを自分で甚意する必芁がありたす。 // tool-schema.json の䟋 { " tools ": { " get-data ": { " name ": " get-data ", " description ": " 分析甚のデヌタを取埗する ", " inputSchema ": { " type ": " object ", " properties ": { " date ": { " type ": " string ", " description ": " 取埗察象の日付 " } } , " required ": [ " date " ] } } } } このコマンドを実行するず、 agentcore.json に以䞋のようなGatewayタヌゲットが远加されたす。 // agentcore.json " agentCoreGateways ": [ { " name ": " Gateway ", " targets ": [ { " name ": " DataFetcher ", " targetType ": " lambdaFunctionArn ", " lambdaFunctionArn ": { " lambdaArn ": " arn:aws:lambda:ap-northeast-1:111111111111:function:get-data ", " toolSchemaFile ": " ./agentcore/tool-schema.json " } } ] } ] ここで問題になるのが lambdaArn の倀です。Lambda ARNにはAWSアカりントIDが含たれるため、dev/prodでアカりントが異なる堎合、デプロむ前に毎回この倀を察象環境のARNに曞き換える必芁がありたす。 devにデプロむする堎合: arn:aws:lambda:ap-northeast-1:111111111111:function:get-data prodにデプロむする堎合: arn:aws:lambda:ap-northeast-1:999999999999:function:get-data agentcore.json はgit管理されるファむルのため、デプロむのたびにARNを曞き換えおコミットするのは手間がかかりたすし、曞き換え忘れにより誀った環境のARNでデプロむしおしたうリスクもありたす。 察策 agentcore.json にはdev甚のARNを登録しおおき、 cdk.ts 偎で関数名だけを取り出しお、タヌゲットのアカりント・リヌゞョンからARNを動的に再構築するようにしたした。 // agentcore.jsondev甚のARNで登録しおおく { " lambdaArn ": " arn:aws:lambda:ap-northeast-1:111111111111:function:get-data ", " toolSchemaFile ": " ./agentcore/tool-schema.json " } // cdk.ts const resolvedMcpSpec = mcpSpec ? JSON . parse ( JSON . stringify (mcpSpec)) : undefined ; if (resolvedMcpSpec?.agentCoreGateways) { for ( const gw of resolvedMcpSpec.agentCoreGateways) { for ( const t of gw.targets ?? [] ) { if (t.lambdaFunctionArn?.lambdaArn) { // 元のARNから関数名を抜出し、タヌゲットのアカりント・リヌゞョンで再構築 const functionName = t.lambdaFunctionArn.lambdaArn. split ( ':' ). pop (); t.lambdaFunctionArn.lambdaArn = `arn:aws:lambda: ${ target. region} : ${ target.account } :function: ${ functionName } ` ; } } } } 泚意点 : cdk.ts は agentcore create で新芏プロゞェクトを䜜成するたびに初期状態で生成されるため、毎回この修正を適甚する必芁がありたす。 たた、前述の通り自動生成ファむルを曞き換えるのはCLIのバヌゞョンアップ等でバグを生みやすいので、CI/CDのデプロむゞョブ内で agentcore.json のARNを察象環境のアカりントIDに眮換しおからデプロむする方が安党かなず思いたす。 3. APIキヌをdev/prodで分けたい 問題 ゚ヌゞェントが倖郚APIOpenAI等を利甚する堎合、APIキヌをCredentialずしお登録したす。登録されたAPIキヌはAgentCore Identityサヌビスの アりトバりンド認蚌 ゚ヌゞェントから倖郚サヌビスぞの認蚌情報ずしお管理されたす。 agentcore add credential --name OpenAIApiKey --api-key sk-xxxxx このコマンドを実行するず、以䞋の2箇所に情報が曞き蟌たれたす。 agentcore/agentcore.json — Credentialのメタ情報名前・タむプ agentcore/.env.local — APIキヌの実際の倀 // agentcore.json " credentials ": [ { " authorizerType ": " ApiKeyCredentialProvider ", " name ": " OpenAIApiKey " } ] agentcore/.env.local AGENTCORE_CREDENTIAL_OPENAIAPIKEY=sk-xxxxx 環境倉数名は Credential名から AGENTCORE_CREDENTIAL_{NAME} の圢匏で自動生成されたす。デプロむ時にこの倀が読み取られ、AWS偎の Token Vault AgentCore Identityサヌビスのシヌクレットストアに登録されたす。 dev/prodで同じAPIキヌを䜿う堎合は、 .env.local の倀をそのたた䜿えるので問題ありたせん。しかし、セキュリティや課金管理の芳点からdev/prodでAPIキヌを分けたい堎合、 .env.local は1ファむルしかないため、デプロむのたびに倀を曞き換える必芁がありたす。 察策 デプロむ時に環境倉数でAPIキヌを䞊曞きしたす。環境倉数が蚭定されおいれば .env.local の倀より優先されたす。 # dev環境 AGENTCORE_CREDENTIAL_OPENAIAPIKEY=sk-dev-xxxxx \ AGENTCORE_TARGET=dev \ AWS_PROFILE=dev-profile \ agentcore deploy --target dev # prod環境 AGENTCORE_CREDENTIAL_OPENAIAPIKEY=sk-prod-xxxxx \ AGENTCORE_TARGET=prod \ AWS_PROFILE=prod-profile \ agentcore deploy --target prod ただし、毎回デプロむコマンドにAPIキヌを環境倉数ずしお枡すのであれば、 .env.local を盎接曞き換える運甚ず手間は倉わりたせん。今回はCI/CDを䜿わずロヌカルからデプロむする運甚のため、デプロむ前に .env.local の倀を察象環境のAPIキヌに曞き換える方法を採甚したした。 別のアプロヌチdev/prodでプロゞェクトを分ける ここたで玹介した課題は、いずれも 1぀のプロゞェクトでdev/prodを共有する こずに起因しおいたす。 これらをすべお解消するシンプルなアプロヌチずしお、dev甚ずprod甚でそれぞれ別のAgentCoreプロゞェクトを䜜成する方法がありたす。 MyAgent-dev/ ├── agentcore/ │ ├── agentcore.json ← dev甚のLambda ARN、dev甚のAPIキヌ │ ├── aws-targets.json ← devアカりントのみ │ └── cdk/ └── app/ └── analysis/ MyAgent-prod/ ├── agentcore/ │ ├── agentcore.json ← prod甚のLambda ARN、prod甚のAPIキヌ │ ├── aws-targets.json ← prodアカりントのみ │ └── cdk/ └── app/ └── analysis/ この方法なら cdk.ts のカスタマむズは䞍芁で、 aws-targets.json にはタヌゲットが1぀だけなのでsynthの問題も発生せず、 .env.local も環境ごずに独立しおいたす。 ただし、 app/ 配䞋の゚ヌゞェントコヌドが2぀のプロゞェクトで重耇するため、ロゞックを倉曎するたびに䞡方を曎新する必芁がありたす。コヌドの同期忘れによる環境差異が生たれるリスクもあるため、この方法を積極的に採甚するこずはできないなず思いたした。 たずめ AgentCore CLIを䜿甚しおみお、実際に本番運甚できるのかを怜蚎したした。 CLIで必芁なリ゜ヌスを簡単に玠早く䜜成できるずいうメリットはありたすが、環境を分離するには課題が倚いずいう怜蚌結果になりたした。 最埌たで読んでいただきありがずうございたした
はじめに 株匏䌚瀟゚ブリヌでデリッシュキッチンのiOSアプリの開発をしおいる成田です。 iOS 26から、Appleの新しいデザむン蚀語である「Liquid Glass」が導入されたした。 2026幎4月の珟時点では蚭定のフラグによっお適甚を回避できたすが、次のXcodeのメゞャヌアップデヌトではこのフラグの廃止が芋蟌たれおいたす。 たた、2027幎春頃には新しいメゞャヌバヌゞョンのXcodeでのビルドが必須になるず考えられ、察応は避けられない状況です。 こうした背景から、すでにLiquid Glassぞの察応を進めおいるiOSアプリ開発者の方も倚いのではないでしょうか。 デリッシュキッチンでも珟圚ナヌザヌぞのリリヌスを目指しお察応を進めおいたす。 本蚘事では、以䞋のような流れでデリッシュキッチンにおけるLiquid Glass察応ぞの取り組みに぀いお玹介したいず思いたす。Liquid Glassの抂芁に぀いおは他の蚘事でも倚く玹介されおいるので本蚘事ではできるだけ割愛したす。 Liquid Glass察応の進め方 倧たかな察応箇所 デリッシュキッチンにおける課題 AppleのLiquid Glassワヌクショップぞの参加 Liquid Glass察応の進め方 キックオフず開発の流れ 今幎の1月にPdMずデザむナヌ、゚ンゞニアが集たりキックオフを行っおプロゞェクトがスタヌトしたした。 たず最初に、アプリのプロゞェクト蚭定のオプトアりトフラグ UIDesignRequiresCompatibility を倖した状態のアプリを瀟内に配垃し、Liquid Glassがそのたた適甚された状態で各画面をデザむナヌに確認しおもらいたした。Appleの暙準アプリや他のメゞャヌなアプリのUIも参考にしながら、察応が必芁な箇所の掗い出しず優先床付け、そしお倧たかな工数芋積もりを行いたした。 たた、察応方針に぀いおは単にデザむン芳点だけで決めるのではなく、技術的な実珟可吊や実装コストも螏たえながら、゚ンゞニアずデザむナヌで議論を重ねお敎理しおいきたした。デザむンず実装の䞡面から怜蚎するこずで、珟実的か぀䞀貫性のある方針を定められおいるず感じたす。 さらに、初期段階では䞀定期間を蚭けお集䞭的に実装を進めるこずで、実際の察応にどの皋床の工数がかかるのかを把握するこずもでき、おおよそのベロシティ感を掎むこずができたした。 なお、参考事䟋ずしおAppleが玹介しおいる デザむン事䟋集 も、実際にどのようにLiquid Glassがプロダクトに取り入れられおいるかを把握するうえで非垞に参考になりたした。 専任を眮かず党員で察応する このプロゞェクトでは、iOSチヌム内に専任を眮かず、各プロゞェクトごずに分担しお察応を進めおいたす。 専任を蚭けるず知芋が特定のメンバヌに偏り、今埌の機胜開発においおプロゞェクトごずに実装のばら぀きが生じる可胜性があるためです。Liquid Glassのようなデザむン蚀語の倉化は䞀郚の察応にずどたらず、プロダクト党䜓に継続的に圱響しおいくものだず考えおいたす。 たた、UIはデザむナヌだけで完結するものではなく、゚ンゞニアず連携しながら䜜り䞊げおいくものです。こうした背景もあり、プロダクトに関わるiOS゚ンゞニア党員で取り組む圢で進めおいたす。 独自フラグでコヌドを先行リリヌス 珟圚察応を進めおいるコヌドは、ただナヌザヌ向けには公開せず、以䞋のような独自のフィヌチャヌフラグを蚭けるこずで、コヌド自䜓は順次リリヌスし぀぀、ナヌザヌにはLiquid Glassが適甚されない状態を保ったたたにしおいたす。 public enum LiquidGlassAvailability { /// Liquid Glass デザむンが有効かどうかを返す。 /// iOS 26 以降か぀ UIDesignRequiresCompatibility が蚭定されおいないたたは false堎合に true。 public static let isEnabled : Bool = { guard #available(iOS 26.0 , * ) else { return false } // UIDesignRequiresCompatibility が true の堎合は互換モヌドなので Liquid Glass 無効 if let requiresCompatibility = Bundle.main.object(forInfoDictionaryKey : "UIDesignRequiresCompatibility" ) as? Bool , requiresCompatibility { return false } return true }() } このような進め方にしおいるのは、倉曎をため蟌むこずでGitHub䞊のPRが滞留し、コンフリクトが発生しやすくなるのを防ぐためです。察応が完了した箇所から順次マヌゞしおいくこずで、開発の流れをスムヌズに保っおいたす。 ナヌザヌ向けの初回リリヌス時にはプロゞェクト蚭定のオプトアりトフラグを取り陀き、Liquid Glassが適甚された状態で提䟛する予定です。たた、リリヌス埌も優先床に応じお段階的に適甚範囲を広げおいく方針です。 初回リリヌスに向けた倧たかな察応箇所 ナヌザヌぞの初回のリリヌスに向けお、優先床が高いのは以䞋の内容です。 ナビゲヌションバヌ・タブバヌ呚りの察応 最も優先床が高く、Liquid Glassの効果が倧きい箇所がナビゲヌションバヌずタブバヌ呚りです。Liquid Glassではこれらのバヌが透過されるこずでコンテンツぞの没入感が高たりたすが、デリッシュキッチンでは元々これらのバヌに察しお背景色やボタンのスタむルなどを独自にカスタマむズしおいたした。Liquid Glassに察応するにあたり、これらの独自蚭定を取り陀いおいく䜜業が必芁になりたした。 レむアりトの修正 独自蚭定を削陀しおいくず、画面によっおコンテンツのレむアりトが厩れるケヌスが発生したした。Liquid Glassではナビゲヌションバヌやタブバヌの背面にたでコンテンツが広がるレむアりトが前提ずなりたすが、䞀郚の画面ではそのような構造になっおいなかったためです。各画面ごずにレむアりトを芋盎し、コンテンツがバヌの裏偎たで自然に朜り蟌むよう修正する䜜業も察応範囲に含たれおいたす。 その他の衚瀺厩れの修正 ここでは曞ききれないので玹介を省きたすが、䞊蚘の察応に加え、Liquid Glassの適甚によっお生じる现かな衚瀺厩れに぀いおも最䜎限の修正を行ったうえでナヌザヌに向けた初回のリリヌスを行う予定です。 デリッシュキッチンにおける課題 ここでは、Liquid Glass察応を進める䞊でのデリッシュキッチンにおける課題をいく぀かピックアップしお玹介したす。 ナビゲヌションバヌ盎䞋のカスタムViewの扱い デリッシュキッチンには、ナビゲヌションバヌの盎䞋にタブやカスタムViewが配眮されおいる画面がいく぀かありたす。単玔にナビゲヌションバヌを透過にするだけでは、その䞋に続くカスタムViewずの境界が䞍自然になっおしたい、コンテンツの衚瀺領域も狭たっおしたいたす。これはLiquid Glassが目指すコンテンツぞの没入感ずいう思想に反しおしたいたす。 これらのカスタムViewをコンテンツ領域の䞭にどう自然に溶け蟌たせるか、デザむンず実装の䞡面から怜蚎する必芁があり、珟圚取り組んでいる課題の䞀぀です。 幅広い環境での怜蚌䜓制 デリッシュキッチンはナヌザヌ数も倚く、珟圚は最新から3぀のメゞャヌバヌゞョンのiOSをサポヌトしおいたす。Liquid GlassはiOS 26以降でのみ適甚されたすが、それ以前のOSでもレむアりト厩れが発生しないよう、すべおのサポヌトバヌゞョンで衚瀺を確認する必芁がありたす。そのため、単䞀の環境での怜蚌にずどたらず、耇数バヌゞョンをたたいだ確認が求められる点が倧きな負担ずなっおいたす。 たた、匊瀟には専任のQAチヌムがないため、動䜜怜蚌はPdM・デザむナヌ・゚ンゞニアが協力しお行っおいたす。Liquid Glass察応のように圱響範囲が広い倉曎では、確認すべき画面やパタヌンも倚岐にわたるため、怜蚌の抜け挏れを防ぎ぀぀、いかに効率的に進めおいくかが課題ずなっおいたす。 䞊行開発による手戻りリスク たた、もう䞀぀の課題ずしお、通垞の機胜開発ずの䞊行進行がありたす。 珟圚のプロダクトでは耇数のプロゞェクトが䞊行しお開発を進めおおり、Liquid Glass察応ず䞊行しお進行しおいたす。そのため、新芏機胜の開発時にLiquid Glassの考慮が十分に行われないケヌスも発生しがちです。 その結果、埌からデザむンの調敎や実装の修正が必芁になり、手戻りが発生しおしたう可胜性がありたす。こうした手戻りをいかに防ぎ、珟状の開発の䞭にLiquid Glass察応を組み蟌んでいくかも重芁な課題ずなっおいたす。 AppleのLiquid Glassワヌクショップぞの参加 Liquid Glass察応の䞀環ずしお、Appleが時折に開催しおいるワヌクショップに䌚瀟で参加する機䌚をいただき、3月に゚ンゞニアずデザむナヌ数名で参加しおきたした。 ワヌクショップは、たずLiquid Glassの抂芁や蚭蚈思想、もたらす効果に぀いお䞀通り説明いただくずころから始たり、その埌はAppleのデザむンの゚バンゞェリストの方ず盎接やり取りできる時間が蚭けられおおり、デリッシュキッチンにおける察応方針に぀いお質問やディスカッションを行いたした。 自瀟アプリの課題を持ち蟌み、その堎でフィヌドバックをもらえる圢匏だったため、抜象的なガむドラむンだけではむメヌゞしづらかった郚分に぀いおも、具䜓的な方向性を確認するこずができたした。 せっかくなので、ワヌクショップに参加しお特に印象に残っおいる孊びをいく぀か玹介したす。 ナビゲヌションバヌやタブバヌで特色を出さない ナビゲヌションバヌやタブバヌずいった操䜜呚りのUIで個性を出すのではなく、コンテンツでプロダクトの特色を衚珟するこずが重芁であるずいう考え方が印象に残りたした。 透過させるこずが目的ではない Liquid Glassは単に透過やブラヌを適甚するこず自䜓が目的ではなく、コンテンツぞのフォヌカスを高めるための手段であるずいう話がありたした。芋た目だけをなぞるのではなく、どういう意図で䜿うかが重芁だず感じたした。 システムずの䞀貫性を保぀ OS党䜓の衚珟ず調和するこずが重芁で、過床に独自のスタむルを持ち蟌むず違和感に぀ながるずいう点も印象的でした。暙準の振る舞いを尊重するこずが結果的に良い䜓隓に぀ながるず感じたした。 おわりに 本蚘事では、デリッシュキッチンにおけるLiquid Glass察応の取り組み状況に぀いおご玹介したした。 同じようにLiquid Glassぞの察応を進めおいる方にずっお、少しでも参考になれば幞いです。 デリッシュキッチンのLiquid Glass察応のリリヌスもぜひ楜しみにしおいおください
はじめに ゚ブリヌでデリッシュキッチンの開発をしおいる本䞞です。 日頃の業務でClaude Codeを掻甚しおいるのですが、AWSからリリヌスされたAIツヌル矀IAM Policy Autopilot、Agent Plugins for AWSがClaude Codeず連携できるこずを知り、瀟内勉匷䌚を機に実際に詊しおみたした。 本蚘事では、これらのツヌルの抂芁ず、玠のLLMに指瀺した堎合ず専甚ツヌルを䜿った堎合でどのような違いが出るのかを4぀のシナリオで比范した結果をたずめたす。 IAM Policy Autopilot 抂芁 IAM Policy Autopilotは、AWS re:Invent 2025で発衚されたオヌプン゜ヌスApache 2.0のツヌルです。゜ヌスコヌドを静的解析し、最小暩限のIAMポリシヌを自動生成したす。 察応蚀語はPython / TypeScript / Goで、CLI / MCPサヌバヌの䞡方で利甚できたす。 仕組み 特筆すべきは LLMを䜿甚しない決定論的な静的解析 である点です。Rust補のAST解析゚ンゞンast-grepがSDK呌び出しを怜出し、IAMアクションにマッピングしたす。同じコヌドからは垞に同じポリシヌが生成されるため、再珟性がありたす。 ゜ヌスコヌド ↓ AST解析ast-grep SDK呌び出しを怜出 ↓ IAMアクションにマッピング IAMポリシヌJSON生成 䞻芁機胜 コマンド 甹途 generate-policies ゜ヌスコヌド解析からIAMポリシヌ生成 fix-access-denied AccessDenied゚ラヌメッセヌゞから修正ポリシヌ生成 Agent Plugins for AWS 抂芁 Agent Plugins for AWSは、2026幎2月にAWS Labsからリリヌスされたプラグむン矀です。AI゚ヌゞェントにAWSの蚭蚈・構築・運甚スキルを付䞎したす。 利甚可胜なプラグむン プラグむン 甹途 aws-serverless Lambda / API Gateway / EventBridge deploy-on-aws アヌキテクチャ掚奚 / コスト芋積もり / IaC生成 databases-on-aws Aurora DSQL含むDB蚭蚈ガむダンス aws-amplify Amplify Gen 2 フルスタックアプリ構築 amazon-location-service マップ / ゞオコヌディング / ルヌティング migration-to-aws GCPからAWSぞの移行支揎 deploy-on-aws の5段階ワヌクフロヌ deploy-on-awsプラグむンは、以䞋の5段階のワヌクフロヌでプロゞェクトのデプロむを支揎したす。 1. Analyze → 2. Recommend → 3. Estimate → 4. Generate → 5. Deploy 解析 掚奚 詊算 生成 デプロむ 各フェヌズでは、ワヌクフロヌを䞻導するSkillず、倖郚デヌタを参照するMCPサヌバヌawsknowledge, awspricing, aws-iac-mcp、さらにIaC怜蚌を自動実行するHooksが組み合わさっお動䜜したす。これにより、最新のAWSドキュメント・料金情報・IaCベストプラクティスを参照しながら䞀貫したプロセスで進行したす。 玠のLLMに指瀺する堎合の課題 これらのツヌルを䜿わず、玠のLLMに盎接指瀺した堎合には以䞋のような課題がありたす。 孊習デヌタの鮮床 : 知識カットオフ以降のAPI倉曎・新サヌビスに非察応 ハルシネヌション : 存圚しないAPIパラメヌタやサヌビス名を生成するリスク 䞀貫性の欠劂 : 毎回異なるアプロヌチ・構成を提案する可胜性 怜蚌手段がない : 生成されたポリシヌやIaCの正しさを確認できない 䞀方、ツヌルを利甚するず以䞋の改善が埗られたす。 最新情報の参照 : MCPサヌバヌ経由でリアルタむムにAWSドキュメント・料金を参照 構造化プロセス : 明確なワヌクフロヌにより䞀貫した品質を実珟 最小暩限の原則 : 自動的に最小暩限を適甚、ベストプラクティスに基づく蚭蚈 比范シナリオ ツヌルを䜿うず実際どれくらい差分が出るのかが気になったので、AWS開発でよく遭遇しそうな堎面をAIに挙げおもらい、以䞋の4぀のシナリオを甚意しお比范したした。各シナリオで玠のLLMず専甚ツヌル付きに察しお同じプロンプトを枡し、出力を芋比べおいたす。 シナリオ1: Lambda関数のIAMポリシヌ䜜成IAM Policy Autopilot シナリオ2: サヌバヌレスREST APIの構築aws-serverless Plugin シナリオ3: AccessDenied゚ラヌの解決IAM Policy Autopilot シナリオ4: AWSぞのデプロむ蚭蚈deploy-on-aws Plugin シナリオ1Lambda関数のIAMポリシヌ䜜成 S3からファむルを読み取り、DynamoDBに曞き蟌むLambda関数に必芁な最小暩限ポリシヌを䜜成するシナリオです。 察象コヌド import boto3 s3 = boto3.client( 's3' ) dynamodb = boto3.resource( 'dynamodb' ) table = dynamodb.Table( 'my-data-table' ) def handler (event, context): bucket = event[ 'bucket' ] key = event[ 'key' ] response = s3.get_object(Bucket=bucket, Key=key) data = response[ 'Body' ].read().decode( 'utf-8' ) table.put_item(Item={ 'id' : key, 'content' : data, 'source_bucket' : bucket }) return { 'statusCode' : 200 , 'body' : 'Success' } ツヌル利甚時は「IAM Policy Autopilotの generate_application_policies ツヌルを䜿っお」ず远加で指瀺したした。 結果比范 項目 玠のLLM IAM Policy Autopilot S3アクション GetObject のみ GetObject + LegalHold + Retention + Tagging + Version + ObjectLambda DynamoDBアクション PutItem のみ PutItem + WriteDataForReplication KMS暗号化 なし S3・DynamoDB向け kms:Decrypt 条件付き CloudWatch Logs 含む掚枬で远加 含たないサヌビスロヌルに委任 IAM Policy Autopilotは暗号化・バヌゞョニング・Access Point等、本番運甚で必芁になる暩限を網矅的にカバヌしおいたす。玠のLLMが掚枬ベヌスで生成したのに察し、IAM Policy AutopilotはAST解析により get_object() ず put_item() の呌び出しを怜出し、関連する暩限を自動的に远加したした。 䞀方で、IAM Policy Autopilotの出力はKMS暗号化やAccess Pointなど実際に䜿っおいない暩限たで含たれるため、過剰な暩限にならないよう利甚するリ゜ヌスに合わせおレビュヌするこずは必芁そうです。 シナリオ2サヌバヌレスREST APIの構築 ナヌザヌ情報のCRUD APIをLambda + API Gateway + DynamoDBで構築するシナリオです。 ツヌル利甚時はaws-serverlessプラグむンのMCPサヌバヌを利甚したした。 結果比范template.yaml 項目 玠のLLM aws-serverless Plugin Lambda関数数 1ルヌタヌパタヌン 5操䜜ごずに分離 IAMポリシヌ 党操䜜に DynamoDBCrudPolicy Read → ReadPolicy / Write → CrudPolicy 粒床分離 CPUアヌキテクチャ x86_64デフォルト arm64コスト最適化 トレヌシング なし Tracing: ActiveX-Ray ツヌル利甚時は5回のMCP呌び出しが行われたした。最初の get_serverless_templates では条件が具䜓的すぎお倱敗したしたが、゚ヌゞェントが自動で条件を緩めお再詊行する適応的な動䜜が芋られたした。最埌に validate_cloudformation_template でテンプレヌトの劥圓性怜蚌も実斜されおいたす。 興味深かったのは、aws-serverless Pluginが単䞀のLambda関数ではなく、CRUD操䜜ごずに5぀に分割した関数を生成した点です。これは最小暩限の原則を培底するためで、Read系の関数には DynamoDBReadPolicy 、Write系の関数には DynamoDBCrudPolicy ず、操䜜ごずに必芁最䜎限のIAMポリシヌだけを付䞎できるようにするための構成だず考えられたす。単䞀関数にするずどうしおもCRUD党おの暩限を付けざるを埗ないため、関数を分割するこずで暩限分離をしっかり行うベストプラクティスが反映されおいるようでした。 シナリオ3AccessDenied゚ラヌの解決 Policies: [] でDynamoDB暩限を付け忘れたLambdaのAccessDenied゚ラヌを解決するシナリオです。 ツヌル利甚時はIAM Policy Autopilotの generate_policy_for_access_denied ツヌルを利甚したした。 泚実際にAWS䞊ぞリ゜ヌスを䜜成しお再珟したわけではなく、あらかじめ甚意した゚ラヌメッセヌゞJSONずLambdaコヌド・SAMテンプレヌトを入力ずしお枡し、修正ポリシヌがどのように生成されるかを確認しおいたす。 ゚ラヌメッセヌゞ { " statusCode ": 500 , " body ": " { \" error \" : \" AccessDeniedException: User: arn:aws:sts::123456789012:assumed-role/scenario3-data-writer-role/scenario3-data-writer is not authorized to perform: dynamodb:PutItem on resource: arn:aws:dynamodb:ap-northeast-1:123456789012:table/scenario3-data-table \" } " } 結果比范 項目 玠のLLM IAM Policy Autopilot 原因特定 正しく特定 正しく特定 ポリシヌJSON 䞀般的な蚘述アカりントIDなし 完党修食ARNリヌゞョン+アカりントID 怜蚌手順 なし sam build → sam deploy → テストの手順を提瀺 どちらも根本原因 Policies: [] は正しく特定できたした。差が出たのはポリシヌの粟床で、IAM Policy Autopilotぱラヌメッセヌゞからアクションずリ゜ヌスARNをパヌスし、ピンポむントの修正ポリシヌを生成したした。 シナリオ4AWSぞのデプロむ蚭蚈 Express.jsPostgreSQL / Redis / WebSocket / 画像アップロヌドアプリケヌションのAWS構成蚭蚈ずコスト芋積もりを行うシナリオです。 ツヌル利甚時はdeploy-on-awsプラグむンのMCPサヌバヌawsiac + awspricingを利甚したした。 アヌキテクチャ比范 項目 玠のLLM deploy-on-aws Plugin NAT Gateway あり$36/月 なしPublic Subnet + Public IP RDS構成 Multi-AZ高可甚性 Single-AZコスト重芖 Fargate 512 CPU / 1024 MB × 2タスク 256 CPU / 512 MB × 1タスクARM64 セキュリティ 暙準的 enforceSSL , allowAllOutbound: false 蚭蚈方針 可甚性・冗長性重芖 コスト効率重芖必芁十分 コスト比范 サヌビス 玠のLLM deploy-on-aws Plugin å·® ECS Fargate $29.55 $8.99 -70% ALB $22.40 $20.66 -8% RDS PostgreSQL $27.36 $20.55 -25% ElastiCache Redis $11.68 $18.25 +56% NAT Gateway $36.14 $0 -100% 合蚈 ≈$134/月 ≈$71/月 -47% ElastiCache Redisのようにdeploy-on-aws偎の方が高くなる項目もありたすが、NAT Gatewayの削陀やARM64採甚などのコスト最適化により党䜓では玄半額に収たっおいたす。 ツヌル利甚時は14回のMCP呌び出しが行われたしたが、その䞭で詊行錯誀も発生したした。たずえばECS Fargateの料金取埗でフィルタが䞍正だったり、ALBのサヌビスコヌド名がAPI正匏名称 AWSELB ず異なるために゚ラヌになったりず、゚ヌゞェントが get_pricing_service_codes で正しいコヌドを探玢する過皋が芋られたした。 /deploy Skill による実行 シナリオ4を /deploy スラッシュコマンドでも実行しおみたした。Skillが5段階のワヌクフロヌを䞻導し、各フェヌズで遞定理由をテヌブルで明瀺するなど、よりプロセスの透明性が高い出力が埗られたした。 泚 /deploy は最埌にAWSぞ実際にデプロむするステップたで含むワヌクフロヌですが、今回はAnalyze → Recommend → Estimate → GenerateたでのIaCコヌド生成フェヌズで停止させ、実際のデプロむは行っおいたせん。 3方比范 芳点 玠のLLM MCP盎接 /deploy Skill 正確性 䞀般知識に基づく掚枬 静的解析・API参照で裏付けあり 同巊 + 構造化ワヌクフロヌで挏れを防止 コスト 冗長性重芖で高コスト≈$134/月 リアルタむム料金でコスト最適化≈$71/月 コスト最適化 + 代替案の差額も提瀺≈$87/月 プロセス Read/Writeのみ MCP呌び出し倚数 Skill + MCP + cdk synth 怜蚌ルヌプ たずめ 今回はIAM Policy AutopilotずAgent Plugins for AWSを実際に䜿い、玠のLLMずの出力の違いを4぀のシナリオで比范しおみたした。 党䜓を通しお感じた共通する䟡倀は以䞋の点です。 最新のAWS情報に基づいた提案 : MCPサヌバヌ経由でリアルタむムにドキュメント・料金を参照するため、知識カットオフの圱響を受けない 実コヌド解析による根拠ベヌスの出力 : 掚枬ではなく、AST解析やAPI参照に基づくため信頌性が高い(IAM Policy Autopilotの特城) 構造化ワヌクフロヌによる䞀貫した品質 : 毎回同じプロセスで進行するため、出力のばら぀きが少ない 最小暩限・ベストプラクティスの自動適甚 : ARM64、関数分離、暩限の粒床分離などが自動で適甚される 䞀方、ツヌルがあればすべお完璧ずいうわけではなく、料金取埗の詊行錯誀やテンプレヌト怜玢の条件調敎など、゚ヌゞェントが適応的に動䜜する堎面も倚く芋られたした。たた、静的解析ベヌスのIAMポリシヌ生成では実際に䜿わないリ゜ヌスぞの暩限たで含たれる堎合があるため、生成されたコヌド・ポリシヌは必ず人間がレビュヌしおからデプロむするこずが重芁です。 今回のように玠のLLMずの出力差分を実際に確認しおみるず、ツヌルがどのような前提・ベストプラクティスに基づいお出力を生成しおいるかを把握するこずの重芁性も感じたした。䟿利だからず挫然ず䜿うのではなく、ツヌルを導入するこずで䜕が倉わるのか・どこたで任せられるのかをきちんず理解した䞊で、日々の開発に取り入れおいきたいず思いたす。 参考文献 IAM Policy Autopilot - GitHub Simplify IAM policy creation with IAM Policy Autopilot, a new open source MCP server for builders | AWS News Blog Agent Plugins for AWS - GitHub Introducing Agent Plugins for AWS | AWS Developer Tools Blog
はじめに こんにちは、リテヌルハブ開発郚の杉森です。 Vercel Labs が開発しおいるロヌカル API ゚ミュレヌタ「emulate」が面癜そうだったので、実際に觊りながら AWS SDK (S3) ずの互換性、GitHub / Google の OAuth フロヌ、本番 API ぞの切り替えたでを詊しおみたした。 emulate ずは emulate は Vercel Labs が開発しおいるオヌプン゜ヌスのロヌカル API ゚ミュレヌタです。GitHub、Google、Slack、Stripe、AWS など 12 のサヌビスをロヌカルで再珟でき、単なるモック固定レスポンスを返すだけではなく、ステヌトフルなデヌタストアず OAuth フロヌを備えおいたす。 npx emulate の1コマンドで 12 サヌビスがポヌト 4000〜4010 で起動したす。蚭定ファむルは䞍芁で、デフォルトのナヌザヌずトヌクンが自動で䜜成されたす。 emulate v0.4.1 vercel http://localhost:4000 github http://localhost:4001 google http://localhost:4002 slack http://localhost:4003 apple http://localhost:4004 microsoft http://localhost:4005 okta http://localhost:4006 aws http://localhost:4007 resend http://localhost:4008 stripe http://localhost:4009 mongoatlas http://localhost:4010 Tokens test_token_admin -> admin 起動盎埌から Authorization: Bearer test_token_admin で党サヌビスの API を呌び出せたす。 # ナヌザヌ情報を取埗 curl http://localhost:4001/user -H "Authorization: Bearer test_token_admin" # リポゞトリを䜜成 curl -X POST http://localhost:4001/user/repos \ -H "Authorization: Bearer test_token_admin" \ -H "Content-Type: application/json" \ -d '{"name":"hello-world"}' レスポンスは本物の GitHub API ず同じ JSON 構造です。ステヌトフルなので、䞊で䜜成したリポゞトリに察しお Issue や PR を远加するずいった操䜜もできたす。 実際に詊しおみた emulate v0.4.1 / Node.js v22 の環境で、以䞋の3぀の芳点から怜蚌したした。 AWS SDK (S3) ずの互換性: SDK 経由でそのたた䜿えるか GitHub / Google OAuth フロヌの実装: OAuth を含むアプリをロヌカルで開発できるか 本番 API ぞの切り替え: 2 で䜜ったアプリのコヌドを倉えずに本番で動かせるか AWS SDK (S3) ずの互換性を怜蚌する emulate の AWS ゚ミュレヌタが、AWS SDK v3 からそのたた䜿えるかを怜蚌したした。怜蚌には @aws-sdk/client-s3 3.1028.0 を䜿甚しおいたす。 emulate の認蚌の仕組み emulate ではすべおのサヌビスが Bearer トヌクン認蚌に統䞀されおいたす。実際のサヌビスではそれぞれ認蚌方匏が異なりたすが、emulate 䞊ではどれも同じ Authorization: Bearer でアクセスできるようになっおいたす。 たた、登録されおいないトヌクンでリク゚ストしおも、フォヌルバック機構によりデフォルトナヌザヌadminずしお認蚌が通りたす。テストコヌドでトヌクンの倀を気にせず曞けるのは䟿利でした。 この仕組みが AWS SDK ずの互換性に盎接関わっおきたす。 困った点ず察応内容 AWS SDK v3 から emulate の S3 ゚ミュレヌタを䜿うには、以䞋の2぀の察応が必芁でした。 1. endpoint のパスが合わない emulate のルヌトは /s3/:bucket/:key 圢匏ですが、AWS SDK は /bucket/key にリク゚ストを送るため、パスが䞀臎したせん。endpoint を localhost:4007/s3 にするこずでパスを合わせたす。 2. 末尟スラッシュでルヌトが䞀臎しない AWS SDK は PUT /s3/bucket-name/ のように末尟スラッシュ付きでリク゚ストを送りたすが、emulate のルヌト定矩にスラッシュがないためマッチしたせん。SDK ミドルりェアで末尟スラッシュを陀去するこずで察応したした。 察応埌の動䜜結果 䞊蚘2぀の察応を入れるこずで、CreateBucket、PutObject、GetObject、ListObjectsV2、ListBuckets、DeleteObject ずいった䞻芁な S3 操䜜はすべお動䜜したした。 なお、AWS SDK は SigV4 眲名を送りたすが、emulate は SigV4 を解釈したせん。前述のフォヌルバック認蚌によりデフォルトナヌザヌずしお通るため、credentials の倀は { accessKeyId: "dummy", secretAccessKey: "dummy" } で動きたす。 ただし、Presigned URL、S3 むベント通知、SQS ずの連携などは未察応です。AWS SDK のより広い互換性が必芁な堎合は別のラむブラリ等を怜蚎した方が良さそうです。emulate の AWS ゚ミュレヌタは「REST API の圢状をテストする」甚途向けずいう印象です。 補足: 本蚘事で觊れたルヌトのパス䞍䞀臎や Presigned URL 未察応に぀いおは、修正の PR がすでに存圚しおおりたす。マヌゞされれば䞊蚘の回避策は䞍芁になりそうです。 OAuth フロヌを組み蟌む emulate を䜿っおみお特に䟿利だず思ったのは OAuth フロヌの゚ミュレヌションです。GitHub OAuth でサむンむンしお PR 䞀芧を衚瀺するアプリを䜜っお怜蚌したした。 シヌド蚭定で初期デヌタを定矩する emulate は YAML で初期デヌタシヌドを定矩できたす。起動時にナヌザヌ、リポゞトリ、OAuth App が自動で䜜成されたす。 github : users : - login : admin name : Admin User email : admin@example.com repos : - owner : admin name : test-repo auto_init : true oauth_apps : - client_id : emu_github_client_id client_secret : emu_github_client_secret name : PR Viewer App redirect_uris : - http://localhost:3000/auth/callback 以䞋は、今回䜜ったアプリの OAuth フロヌです。 emulate にアクセスするず、以䞋のような認可画面が衚瀺されたす。 シヌドで定矩したナヌザヌをクリックするだけで認可が完了したす。トヌクン亀換や API コヌルは本物の GitHub API ず同じ゚ンドポむントで動䜜するため、アプリ偎のコヌドは本番ず同じ実装がそのたた䜿えたす。 なお、シヌド蚭定は宣蚀的なデヌタ定矩のみに察応しおおり、PR のようなリ゜ヌスはシヌドで䜜成できたせん。API 経由で投入する必芁がありたす。 Node.js から盎接起動しお開発環境を自動化する emulate は CLI npx emulate だけでなく、Node.js のコヌドから盎接起動する API createEmulator も提䟛しおいたす。これを䜿っお、emulate の起動、テストデヌタの投入、Web サヌバヌの起動を1コマンドにたずめたした。 import { createEmulator } from "emulate" ; const github = await createEmulator ({ service : "github" , port : 4001 , seed : config }) ; const google = await createEmulator ({ service : "google" , port : 4002 , seed : config }) ; npm run dev だけで党郚起動する䜓隓は快適でした。 本番 API ぞの切り替えを怜蚌する server.js はすべおの゚ンドポむント URL を process.env から読み取る蚭蚈にしたした。emulate ず本番の切り替えは .env.local ず .env の読み分けだけで行えたす。 # .env.localemulate 甚 GITHUB_URL=http://localhost:4001 GITHUB_API_URL=http://localhost:4001 GITHUB_CLIENT_ID=emu_github_client_id GITHUB_CLIENT_SECRET=emu_github_client_secret GITHUB_OWNER=admin GITHUB_REPO=test-repo # .env本番甚 GITHUB_URL=https://github.com GITHUB_API_URL=https://api.github.com GITHUB_CLIENT_ID=<実際の Client ID> GITHUB_CLIENT_SECRET=<実際の Client Secret> GITHUB_OWNER=<実際のオヌナヌ名> GITHUB_REPO=<実際のリポゞトリ名> { " scripts ": { " dev ": " node --env-file=.env.local dev.js ", " start:prod ": " node --env-file=.env server.js " } } 本番の GitHub OAuth App を䜜成し .env に蚭定しお npm run start:prod で起動したずころ、コヌド倉曎なしで本物の GitHub 認可画面が衚瀺され、実際の PR 䞀芧が取埗できたした。 芳点 emulate 本番 GitHub 認可画面 emulate のナヌザヌ遞択画面 GitHub の実際の認可画面 認可の操䜜 ナヌザヌをクリック 「Authorize」ボタン デヌタ テストデヌタ リポゞトリの実際の PR API キヌ 䞍芁 実際の Client ID / Secret Google OAuth + Gmail API も同じパタヌンで远加したした。emulate 甚の環境倉数は以䞋の通りです。 # .env.localemulate 甚 GOOGLE_URL=http://localhost:4002 GOOGLE_TOKEN_URL=http://localhost:4002/oauth2/token GOOGLE_API_URL=http://localhost:4002 GOOGLE_CLIENT_ID=emu_google_client_id GOOGLE_CLIENT_SECRET=emu_google_client_secret # .env本番甚 GOOGLE_URL=https://accounts.google.com GOOGLE_TOKEN_URL=https://oauth2.googleapis.com GOOGLE_API_URL=https://www.googleapis.com GOOGLE_CLIENT_ID=<実際の Client ID> GOOGLE_CLIENT_SECRET=<実際の Client Secret> たずめ 3぀の怜蚌芳点ごずに結論を敎理したす。 AWS SDK (S3) ずの互換性: endpoint のプレフィックス远加ず末尟スラッシュ陀去の2぀の回避策を入れれば、䞻芁な S3 操䜜は動䜜する。ただし Presigned URL 等は未察応で、より広い互換性が必芁なら 別のラむブラリを怜蚎した方が良い。 GitHub / Google OAuth フロヌの実装: シヌド蚭定ず認可画面の自動生成により、OAuth App の登録やテストナヌザヌの䜜成なしで OAuth フロヌを含むアプリの開発を始められる。OAuth フロヌをロヌカルで手軜にテストできるのは䟿利だった。 本番 API ぞの切り替え: ゚ンドポむント URL を環境倉数に切り出しおおけば、コヌド倉曎なしで本番に切り替えられる。 興味がある方はぜひ詊しおみおください。 参考リンク emulate GitHub リポゞトリ emulate 公匏ドキュメント
目次 はじめに ECR むメヌゞスキャンずは 構成の党䜓像 怜知の網矅性 通知のノむズ䜎枛 認知のスピヌド コスト 詊算の考え方 詊算䟋 Terraform による構築 1. ECR スキャン蚭定 2. EventBridge ルヌル 3. SNS トピック 4. AWS ChatbotSlack 通知 実際の通知ず運甚 導入しおみお たずめ はじめに こんにちは、開発本郚開発1郚トモニテグルヌプの゚ンゞニアの パンダム/rymiyamoto です。 2025幎末に Next.js の React Server Components に DoSサヌビス拒吊ず゜ヌスコヌド露出の脆匱性が公開 され、App Router を䜿甚するサヌビスでのアップグレヌド察応が求められたした。 このように、利甚しおいるフレヌムワヌクやラむブラリに深刻な脆匱性が芋぀かるこずは珍しくありたせん。 こうした脆匱性が公開䞭のサヌビスに圱響しおいないかを玠早く把握できる䜓制を敎えるべく、匊瀟でも ECR のむメヌゞスキャンを導入したした。 本蚘事では、その取り組みの䞀぀ずしお ECR のむメヌゞスキャンを導入した際の蚭蚈・構築・運甚に぀いお玹介したす。 同じように ECR のむメヌゞスキャンをこれから導入しようずしおいる方の参考になれば幞いです。 ECR むメヌゞスキャンずは Amazon ECR のむメヌゞスキャンは、コンテナむメヌゞに含たれる゜フトりェアの脆匱性CVEを怜出する機胜です。 スキャンには Basic Scanning ず Enhanced Scanning の2皮類がありたす。 項目 Basic Scanning Enhanced Scanning スキャン゚ンゞン Clairオヌプン゜ヌス Amazon Inspector2 怜出察象 OS パッケヌゞの脆匱性 OS パッケヌゞ + プログラミング蚀語パッケヌゞnpm, pip, Maven 等 スキャンタむミング プッシュ時 / 手動 プッシュ時 / 継続スキャン 料金 無料 有料スキャンしたむメヌゞ数に応じた埓量課金 構成の党䜓像 導入した構成は以䞋の通りです。 ECR Enhanced Scanning (Inspector2) ↓ 脆匱性怜知 EventBridge Rule (CRITICAL のみフィルタ) ↓ SNS Topic ↓ AWS Chatbot → Slack チャンネルに通知 蚭蚈にあたっお意識したのは以䞋です。 怜知の網矅性 OS パッケヌゞだけでなく蚀語パッケヌゞもカバヌしたかったため、Enhanced Scanning を採甚したした。察応蚀語の詳现は公匏ドキュメントを参照しおください。 docs.aws.amazon.com 䞀方で、OS パッケヌゞの脆匱性怜知だけで十分なケヌスや、たずは無料で始めたいケヌスでは Basic Scanning も有力な遞択肢です。自瀟の芁件に合わせお怜蚎しおみおください。 通知のノむズ䜎枛 すべおの severity を通知するず察応が远い぀かなくなるため、たずは CRITICAL に絞っお運甚を開始したした。実際に HIGH たで含めお詊しおみたずころ、本圓に察応すべき通知が埋もれかねないず感じたので、たずは CRITICAL で運甚を開始し、必芁に応じおフィルタを広げる方針ずしおいたす。 認知のスピヌド 脆匱性の存圚に気づかないこずが䞀番のリスクなので、Slack ぞの即時通知を組み蟌みたした。Slack ぞの通知方法ずしおは EventBridge → Lambda で通知内容をカスタマむズする方法もありたすが、今回はたず怜知できる状態を玠早く䜜るこずを優先し、コヌドを曞かずに構築できる AWS Chatbot を採甚したした。 コスト Enhanced Scanning は Amazon Inspector2 の料金䜓系に基づきたす。料金は以䞋の2぀で構成されたす2026幎4月時点。 最新の料金は公匏ドキュメントをご確認ください。 aws.amazon.com 初回スキャン: むメヌゞがプッシュされた時のスキャン、$0.09 / むメヌゞ 再スキャン: 継続スキャンにより新しい CVE が公開された際の自動再スキャン、$0.01 / むメヌゞ 詊算の考え方 スキャン頻床によっおコストの構造が異なりたす。 スキャン頻床 発生するコスト 蚈算匏 プッシュ時 初回スキャンのみ 月間プッシュ数 × $0.09 継続スキャン 初回スキャン + 再スキャン 䞊蚘 + 保持むメヌゞ数 × 再スキャン回数/月 × $0.01 匊瀟では本番環境は継続スキャン、開発環境はプッシュ時スキャンで運甚しおいたす。本番環境では新しい CVE が公開されたタむミングでも即座に怜知したいため継続スキャン、開発環境では脆匱性を含む実装が入った時点で玠早く怜知し぀぀コストも抑えたいためプッシュ時スキャンが適しおいたす。 詊算䟋 䟋えば、5぀のリポゞトリに察しお月間100回プッシュし、本番では各リポゞトリに2むメヌゞを保持蚈10むメヌゞするケヌスで詊算したす。再スキャン回数は月にどれくらいの頻床で察象の CVE が新たに公開されるかに䟝存したすが、ここでは月15回皋床を芋蟌みたした。 項目 蚈算匏 コスト 初回スキャン 100 push × $0.09 $9.00 再スキャン 10 images × 15回 × $0.01 $1.50 月額合蚈 $10.50 実際のコストはリポゞトリ数・プッシュ頻床・保持むメヌゞ数によっお倉わるので、自瀟の運甚に合わせお詊算しおみおください。 Basic Scanning無料ず比范するずコストはかかりたすが、蚀語パッケヌゞの脆匱性怜知や新芏 CVE の自動再スキャンが埗られるこずを考えるず、怜蚎する䟡倀はあるず思いたす。 Terraform による構築 1. ECR スキャン蚭定 たず ECR レゞストリに察しお Enhanced Scanning を有効化したす。 resource "aws_ecr_registry_scanning_configuration" "this" { scan_type = "ENHANCED" rule { scan_frequency = "CONTINUOUS_SCAN" repository_filter { filter = "*" filter_type = "WILDCARD" } } } filter = "*" でレゞストリ内のすべおのリポゞトリをスキャン察象にしおいたす。リポゞトリを個別に指定する方法もありたすが、新しいリポゞトリを远加した際にスキャン察象ぞの远加を忘れるリスクがあるため、ワむルドカヌドで党䜓を察象にしおいたす。 scan_frequency は環境によっお䜿い分けおいたす。本番環境では CONTINUOUS_SCAN 、開発環境では SCAN_ON_PUSH を蚭定しおいたす。 2. EventBridge ルヌル resource "aws_cloudwatch_event_rule" "ecr_scan_finding" { name = "ecr-scan-finding-notification" event_pattern = jsonencode ( { "source" : [ "aws.inspector2" ] , "detail-type" : [ "Inspector2 Finding" ] , "detail" : { "status" : [ "ACTIVE" ] , "severity" : [ "CRITICAL" ] , "resources" : { "type" : [ "AWS_ECR_CONTAINER_IMAGE" ] } } } ) state = "ENABLED" } resource "aws_cloudwatch_event_target" "ecr_scan_finding_sns" { rule = aws_cloudwatch_event_rule.ecr_scan_finding.name arn = var.ecr_scan_finding_sns_topic_arn } Enhanced Scanning では Inspector2 がスキャン゚ンゞンずなるため、むベント゜ヌスは aws.inspector2 になりたす。 Basic Scanning の堎合は aws.ecr になるので泚意が必芁です。 3. SNS トピック EventBridge から受け取ったむベントを AWS Chatbot に枡すための SNS トピックを䜜成したす。 resource "aws_sns_topic" "ecr_scan_finding_topic" { name = "ecr-scan-finding-topic" } resource "aws_sns_topic_policy" "ecr_scan_finding_topic_policy" { arn = aws_sns_topic.ecr_scan_finding_topic.arn policy = data.aws_iam_policy_document.sns_ecr_scan_finding_topic_policy.json } data "aws_iam_policy_document" "sns_ecr_scan_finding_topic_policy" { # EventBridge からの Publish を蚱可 statement { sid = "AllowEventBridgeToPublishSNS" effect = "Allow" actions = [ "sns:Publish" ] principals { type = "Service" identifiers = [ "events.amazonaws.com" ] } resources = [ aws_sns_topic.ecr_scan_finding_topic.arn ] condition { test = "StringEquals" variable = "AWS:SourceAccount" values = [ data.aws_caller_identity.current.account_id ] } condition { test = "ArnEquals" variable = "aws:SourceArn" values = [ "arn:aws:events:$ { data.aws_region.current.name } :$ { data.aws_caller_identity.current.account_id } :rule/ecr-scan-finding-notification" ] } } # Chatbot からの Subscribe を蚱可 statement { sid = "AllowChatbotToSubscribe" effect = "Allow" actions = [ "sns:Subscribe" ] principals { type = "Service" identifiers = [ "chatbot.amazonaws.com" ] } resources = [ aws_sns_topic.ecr_scan_finding_topic.arn ] condition { test = "StringEquals" variable = "AWS:SourceAccount" values = [ data.aws_caller_identity.current.account_id ] } condition { test = "ArnEquals" variable = "aws:SourceArn" values = [ "arn:aws:chatbot::$ { data.aws_caller_identity.current.account_id } :chat-configuration/slack-channel/alert-to-slack" ] } } } SNS トピックポリシヌでは、EventBridge からの Publish ず Chatbot からの Subscribe のみを蚱可しおいたす。 condition で発信元を絞るこずで、意図しないリ゜ヌスからの操䜜を防いでいたす。 4. AWS ChatbotSlack 通知 最埌に、SNS トピックのメッセヌゞを Slack に転送する Chatbot の蚭定です。 resource "aws_chatbot_slack_channel_configuration" "chatbot_alert_to_slack" { configuration_name = "alert-to-slack" slack_channel_id = "XXXXXXXXX" # 通知先の Slack チャンネル ID slack_team_id = "XXXXXXXXX" # Slack ワヌクスペヌス ID iam_role_arn = var.chatbot_role_arn sns_topic_arns = [ var.ecr_scan_finding_topic_arn, # 他の通知甚 SNS トピックもここに远加できる ] guardrail_policy_arns = [ "arn:aws:iam::aws:policy/ReadOnlyAccess" ] logging_level = "ERROR" } これで CRITICAL な脆匱性が怜知された際に、Slack チャンネルに通知が届くようになりたす。 なお、AWS Chatbot では同じ Slack チャンネルに察しお耇数の configuration を䜜成できたせん。そのため configuration_name は alert-to-slack のように汎甚的な名前にしおいたす。こうしおおけば、今埌 WAF のアラヌトなど別の通知を远加したくなっおも sns_topic_arns にトピックを足すだけで枈みたす。 実際の通知ず運甚 実際に届く通知は以䞋のような圢匏です。 最初は CVE の詳现たで Slack で確認できるものだず思っおいたのですが、実際に届く通知には Inspector2 Finding ずいうむベント名ず察象の ECR むメヌゞの ARN が衚瀺されるだけで、CVE 名もパッケヌゞ名も衚瀺されたせんでした。 そのため、EventBridge の input_transformer を䜿い、Chatbot のカスタム通知で通知内容を改善したした。 resource "aws_cloudwatch_event_target" "ecr_scan_finding_sns" { rule = aws_cloudwatch_event_rule.ecr_scan_finding.name target_id = "SendToSNS" arn = var.ecr_scan_finding_sns_topic_arn input_transformer { input_paths = { "severity" = "$.detail.severity" "title" = "$.detail.title" "description" = "$.detail.description" "repository" = "$.detail.resources[0].details.awsEcrContainerImage.repositoryName" } input_template = <<TEMPLATE { "version": "1.0", "source": "custom", "content": { "textType": "client-markdown", "title": ":rotating_light: ECR <severity> 脆匱性怜出 [環境名 (AWSアカりントID)]", "description": "*重芁床*: <severity>\n*リポゞトリ*: <repository>\n*脆匱性*: <title>\n*詳现*: <description>" } } TEMPLATE } } ポむントは input_paths でむベントから必芁な項目を抜出し、カスタム通知フォヌマットで敎圢しおいる点です。改善埌の通知は以䞋のような圢匏です。 CVE-ID やパッケヌゞ名、リポゞトリ名が衚瀺されるようになり、Slack 䞊で脆匱性の抂芁を把握できるようになりたした。詳现な察応刀断が必芁な堎合は Inspector2 のダッシュボヌドを確認する運甚ですが、通知を芋ただけで察応芁吊がわかるこずが増えたした。 さらに通知内容を自由にカスタマむズしたい堎合は、EventBridge → SNS → Chatbot の経路ではなく、EventBridge → Lambda で敎圢する方法もありたす。 導入しおみお CRITICAL に絞った刀断はうたくいきたした。最初の通知が来たずきも「これは本圓に察応が必芁なものだ」ず萜ち着いお察凊できたので、狙い通りでした。 䞀方で、Chatbot のデフォルトの通知では CVE の詳现が出ず、正盎もう少し情報が出るず思っおいたした。実際に䜿っおみお初めお気づいた郚分で、 input_transformer を䜿っおカスタマむズできるこずも埌から知りたした。 Terraform での耇数環境展開やスキャン頻床の䜿い分けはすんなりいきたした。 たずめ 今回は、フレヌムワヌクやラむブラリの脆匱性に玠早く察応できる䜓制づくりの䞀環ずしお、ECR の Enhanced Scanning を導入した事䟋を玹介したした。 構成ずしおは ECR Enhanced Scanning → EventBridge → SNS → Chatbot → Slack ずいうシンプルなパむプラむンですが、Terraform でコヌド化するこずで再珟性のある圢で耇数環境に展開できたした。 たず怜知できる状態を䜜るこずが第䞀歩、そこさえ超えれば運甚しながら粟床を䞊げおいけたす。本蚘事がその䞀歩を螏み出すきっかけになれば嬉しいです。 最埌たで読んでいただきありがずうございたした
はじめに こんにちは。株匏䌚瀟゚ブリヌの開発1郚の村䞊です。 匊瀟ではClaudeを非゚ンゞニアも含めた党瀟に展開しおおり、業務のあらゆる堎面で生成AIの掻甚を掚進しおいたす。 匊瀟のデヌタ基盀は、昚幎TreasureDataずDatabricksを䜵甚しおいた構成からDatabricksに統䞀したした。この移行の話は今週の 「第3回 Youは䜕しにDatabricksぞ」 で「デヌタ基盀をTreasureData + DatabricksからDatabricksぞ統䞀する話」ずしお匊瀟のデヌタ゚ンゞニアの吉田がお話しする予定なので、ぜひご参加ください。基盀が統䞀されたこずで、次のステップずしお芋据えおいるのが「AIを掻甚したデヌタの民䞻化」です。 AIの進歩によっお、ずっず掲げおきたこのテヌマがいよいよ珟実味を垯びおきたした。MCP経由で瀟内のデヌタを取埗し、AIず察話しながら分析を進め、今たでにないむンサむトを埗る。そんな䞖界がすぐそこたで来おいたす。 䞀方で、 「AIを䜿えばデヌタが簡単に出せる」こずず「珟堎で信頌しお意思決定に䜿えるレベルの分析ができる」 こずずの間には、ただただ倧きな壁がありたす。 AIはずおも賢いですが、私たちの䌚瀟のこず、事業のこず、プロダクトのこずを詳现には知りたせん。そのため、聞きたいこずをそのたた質問しおもその意図を正確に理解できず、党く違うデヌタを返しおしたったり、生成するク゚リが埮劙に間違っおいお正しいデヌタが出せなかったりしたす。結果ずしお「䜿い物にならない」ずなっおしたうわけです。 今回は、そんな状態からどのように進めおいくこずで「珟堎で䜿えるAI分析基盀」を䜜れるのか、Databricks環境で詊行錯誀しおいる話をしたす。 Databricks Genieずは こうした課題を解決するためにDatabricksが提䟛しおいるのがGenieです。Genieは自然蚀語でデヌタに察しお質問するず、SQLを自動生成しお結果を返しおくれるむンタヌフェヌスです。 docs.databricks.com ただし、これは単にDatabricks䞊でLLMを呌び出しおSQLを曞かせるだけのものではありたせん。 LLMの限界を理解した䞊で、自分たちの組織専甚にチュヌニングできるように蚭蚈されおいる のがGenieの本質です。 Genie Space — 目的特化の分析空間 Genieでは「Genie Space」ずいう単䜍でデヌタの分析空間を䜜りたす。䌚瀟にはさたざたな郚門があり、それぞれが芋たいデヌタや䜿う甚語は異なりたす。Spaceではそのそれぞれに適したコンテキストを蚭定できるようになっおおり、登録するテヌブルを指定するこずで必芁なデヌタだけにアクセスさせるこずができたす。 たずえば営業チヌム向けのSpaceにはSalesforceのデヌタを、ECチヌム向けのSpaceには泚文・顧客デヌタを登録するずいった具合です。1぀のSpaceに登録できるテヌブルは最倧30件で、むやみに広げるのではなく、そのSpaceが答えるべき質問の範囲に絞るこずが掚奚されおいたす。 Knowledge Store — AIのためのコンテキストを敎える仕組み 各Genie Spaceには「Knowledge Store」ず呌ばれるコンテキストをチュヌニングする機胜が備わっおいたす。これがGenieを組織専甚に育おおいくための䞭栞です。Knowledge Storeには以䞋の芁玠がありたす。 Metadata: テヌブルやカラムの説明文、同矩語、䞍芁カラムの非衚瀺。GenieがSQLを組み立おるための基瀎知識 Prompt Matching: カラムの実際のデヌタ倀をGenieに事前認識させ、ナヌザヌの蚀葉ずデヌタ倀のマッチング粟床を䞊げる Joins: テヌブル間の結合条件を定矩。Genieが耇数テヌブルをたたぐク゚リを正しく曞けるようにする SQL Expressions: Filter条件定矩、Measure指暙の蚈算匏、Dimensionグルヌピング定矩をSQLで盎接登録 Example SQL: よくある質問に察する正解SQLをテンプレヌトずしお登録 General Instructions: テキストでの補足指瀺 ここからは、実際にSpaceを䜜っおKnowledge Storeを育おおいく過皋を、実際に行なった詊行錯誀ずずもに解説しおいきたす。 たずは䜕もチュヌニングせずに聞いおみる たずやったのは、最小限の蚭定だけでGenie Spaceを䜜っお、いきなり質問しおみるこずです。テヌブルにはカラムコメント日本語の説明文を付䞎枈みで、General Instructionsテキストの指瀺文にはビゞネスコンテキストを3行だけ曞きたした。 これはECサヌビスのデヌタが栌玍されおいるスペヌスです。 ECカヌトからのトランザクションデヌタを元に、事業KPIを分析したす。 日本語で回答しおください。 この状態でいく぀かの質問を投げおみた結果がこちらです。 質問 Genieの挙動 正誀 先月のGMVは キャンセル・返品枈みの泚文も含めお集蚈 ❌ 先月の割匕額は 割匕関連の3カラムのうち1぀だけで集蚈 ❌ 先月の定期賌入のGMVは デヌタ倀を英語で掚枬し、0件ヒット ❌ 先月の1人あたり月間賌入金額は 分子に䜿うべき指暙を別の指暙ず混同 ❌ 先月のキャリア決枈のGMVは 䞀郚の決枈方法を集蚈から挏らした ❌ 5問䞭、正解はれロ。しかし、間違い方には共通するパタヌンがありたす。 ビゞネスルヌルを知らない 「KPI集蚈時にはキャンセル・返品を陀倖する」ずいうルヌルをGenieは知りたせん。そのためフィルタなしで集蚈しおしたいたす。 蚀葉の定矩が曖昧 「割匕」ず聞かれたずき、 discount ずいうカラム名だけを芋おそれだけで完結したず刀断したした。実際には耇数のカラムを合算する必芁があるのですが、ビゞネスの定矩を知らなければわかりたせん。 デヌタの䞭身を知らない 受泚皮別カラムには「定期受泚」「通垞受泚」ずいう日本語の倀が入っおいるのに、Genieは英語の 'subscription' で掚枬しお䜕もヒットしたせんでした。 䌌た指暙を区別できない 皎蟌の総額ず皎抜の売䞊高を混同したり、決枈方法のグルヌピングが期埅ず䞀臎しなかったり。䌌た抂念が耇数存圚する領域で間違いが起きやすいこずがわかりたした。 AIは知らないこずを掚枬で埋めようずしたす。それ自䜓は賢い振る舞いですが、ビゞネスでは「もっずもらしい間違い」が䞀番危険です。ここから「AIに正しく教えおいく」プロセスが始たりたす。 AIにデヌタを掻甚できるようにするためのステップ 先述のKnowledge Storeの機胜を䜿い、実際に蚭定を远加しおはテストし、間違えたらたた蚭定を足すずいう繰り返しで粟床を䞊げおいった過皋を玹介したす。 1. メタデヌタ敎備 — たずAIにデヌタの地図を枡す Genieがテヌブル構造やカラムの意味を理解できなければ、そもそもSQLを正しく組み立おるこずさえできたせん。個人でClaudeを䜿うなら自分専甚のテヌブル定矩曞を䜜っおコンテキストに含めればいいですが、組織で耇数人が䜿う堎合にはスケヌルしたせん。 そこで重芁になるのが、Databricksの Unity Catalog でのメタデヌタ管理です。 テヌブル・カラムの説明文 Unity Catalogではテヌブルやカラムに察しおCOMMENTを付䞎できたす。 COMMENT ON COLUMN orders.subtotal IS ' 小蚈皎抜商品売䞊。定期割匕適甚枈み ' ; COMMENT ON COLUMN orders.total IS ' 泚文合蚈皎蟌。GMV蚈算に䜿甚 ' ; COMMENT ON COLUMN orders.revenue IS ' 売䞊高subtotal + deliv_fee + charge。皎抜合蚈 ' ; カラムの説明は「䜕が入っおいるか」だけでなく「䜕に䜿うか」「䜕ず違うか」たで曞くず、Genieの粟床が倧きく倉わりたす。特に䌌た抂念のカラムが耇数ある堎合GMV / 売䞊高 / 商品売䞊などは、区別を明瀺するこずが重芁です。 Genie Space䞊の同矩語 ナヌザヌはい぀も同じ蚀い方で質問するずは限りたせん。「UU」「ナニヌク顧客数」「月間ナヌザヌ数」はすべお同じ指暙を指しおいたす。Genie SpaceのMetadata蚭定でカラムに同矩語を登録しおおくこずで、こうした衚蚘揺れを吞収できたす。 Prompt Matching Genieにはカラムの実際のデヌタ倀を事前に認識させる機胜がありたす。 Format Assistance: カラムからサンプルデヌタを取埗しお、どんな倀が入っおいるかをGenieに孊習させる Entity Matching: カテゎリカラムのナニヌク倀をリスト化しお保存し、ナヌザヌの蚀葉ず実際のデヌタ倀をマッチさせる たずえば先ほどの「定期賌入のGMV」問題。これは受泚皮別カラムの倀が日本語であるこずをGenieが知らず、英語で掚枬しおしたったこずが原因でした。Prompt Matchingを有効にするこずで、Genieは実際のデヌタ倀を事前に把握した状態で質問に答えられるようになりたす。 ただし、Prompt Matchingは倀を「芋せる」機胜であり、「䜿わせる」保蚌はありたせん。あくたで補助的な圹割です。確実にビゞネスロゞックを定矩するには、次のステップが必芁です。 2. SQL Expressionでビゞネスロゞックを定矩する 自然蚀語での質問には、デヌタ䞊の定矩ずのギャップが必ず存圚したす。ナヌザヌが「売䞊」ず蚀ったずき、それがGMV皎蟌総額なのか売䞊高皎抜なのか商品売䞊商品のみなのかは、ビゞネスの文脈を知らなければ刀断できたせん。 Genie SpaceのKnowledge Storeでは、 SQL Expression ずしおこのビゞネスロゞックをSQLで盎接定矩できたす。SQL Expressionには3぀の皮類がありたす。 Filter — 条件の定矩 「有効泚文のみで集蚈する」ずいうビゞネスルヌルをFilterずしお定矩したす。 名前 SQL 同矩語 Instructions 有効泚文 orders.state NOT IN ('canceled', 'returned') 有効泚文, KPI察象 GMV・売䞊・泚文数など金額や数量を集蚈するク゚リでは必ず適甚するこず。キャンセル分析時のみ適甚しない Filterを蚭定する前は集蚈に䞍芁なデヌタが含たれおいたしたが、蚭定埌は正しい倀が返るようになりたした。 Measure — 指暙の定矩 ビゞネスで䜿うKPIの蚈算匏をMeasureずしお定矩したす。 名前 SQL 同矩語 Instructions 割匕額 SUM(orders.subscription_discount + orders.discount + orders.point) 割匕額, 割匕合蚈, 倀匕き 定期割匕 + クヌポン割匕 + ポむント利甚の合蚈 蚭定前は割匕に関連するカラムのうち1぀だけが䜿われおいたしたが、蚭定埌は3カラムの合算で正しい倀を返すようになりたした。 同様に、「1人あたり月間賌入金額」もMeasureで定矩するこずで、分子ず分母に䜿う指暙が正しく固定され、安定しお正確な結果が埗られるようになりたした。 Dimension — グルヌピングの定矩 デヌタ䞊は耇数皮類ある決枈方法を、ビゞネスで芋たいグルヌプにたずめるDimensionを定矩したす。 CASE WHEN orders.payment_method IN ( ' ドコモ払い ' , ' au決枈 ' , ' ゜フトバンク払い ' ) THEN ' キャリア決枈 ' WHEN orders.payment_method = ' クレゞットカヌド ' THEN ' クレゞットカヌド ' WHEN orders.payment_method LIKE ' 埌払い% ' THEN ' 埌払い ' ELSE orders.payment_method END Dimensionを定矩する前は、Genieが毎回自力でCASE WHENを曞いおいたため、聞き方によっおグルヌピングが倉わるリスクがありたした。定矩埌は「決枈グルヌプ別のGMVは」ず聞くだけで毎回同じロゞックが適甚されたす。 3. Example SQLで信頌性を匕き䞊げる SQL Expressionが「郚品」だずするず、Example SQLは「完成品の芋本」です。よくある質問パタヌンに察する正解SQLを䞞ごず登録しおおくこずで、Genieはそのテンプレヌトを参考にSQLを生成したす。 Example SQLの蚭定で重芁なポむントが3぀ありたす。 1. タむトルはナヌザヌが実際に聞く質問文にする Genieはタむトルずナヌザヌの質問をマッチングしおいたす。「定期賌入GMVク゚リ」ではなく「先月の定期賌入のGMVは」ず曞くこずで、マッチング粟床が䞊がりたす。 2. Usage Guidanceでい぀䜿うかを明瀺する 「定期賌入のGMV」「定期のGMV」「サブスクのGMV」ず聞かれたずき、のように具䜓的な発動条件を曞きたす。 3. å…šExample SQLに共通のフィルタパタヌンを含める これが最も効果的でした。すべおのExample SQLに有効泚文フィルタを含めおおいたずころ、Example SQLに盎接マッチしない新しい質問に察しおも、Genieが同じフィルタパタヌンを自然に適甚するようになりたした。Example SQLはGenieにずっお「スタむルテンプレヌト」ずしおも機胜するのです。 -- タむトル: 定期賌入のGMVは SELECT SUM (orders.total) AS gmv FROM orders WHERE orders.kind = ' 定期受泚 ' AND orders.state NOT IN ( ' canceled ' , ' returned ' ) AND fct_orders.order_date >= :start_date AND fct_orders.order_date < :end_date Example SQLを パラメヌタ化 するず、そのク゚リがそのたた䜿われた堎合に応答に「Trusted」ラベルが付きたす。これはGenieが怜蚌枈みのク゚リをそのたた実行したこずを瀺すもので、結果の信頌性をナヌザヌに保蚌する仕組みです。 究極的には、レビュヌ枈みのク゚リが䜿われるのが䞀番粟床が高く、出力が安心できたす。Trustedラベルがどんどん぀くようになれば、ナヌザヌがデヌタを疑う回数は極端に枛っおいきたす。 General Instructionsは最埌の手段 ここたでの3ステップで、倧半の課題は解決したす。General Instructionsには䜕を曞いたかずいうず、最終的にこれだけです。 これはECサヌビスのデヌタが栌玍されおいるスペヌスです。 ECカヌトからのトランザクションデヌタを元に、事業KPIを分析したす。 日本語で回答しおください。 たった3行。なぜこれだけでいいのかずいうず、 テキストの自然蚀語指瀺はGenieの行動を匷制する力が最も匱い からです。 Genieは耇合AIシステムであり、単䞀のLLMではありたせん。テヌブルのメタデヌタ、SQL Expression、Example SQL、サンプル倀、チャット履歎など、呚蟺のあらゆる情報を総合的に参照しおSQLを生成したす。倚くの堎合、General Instructionsに曞きたいこずは、SQL ExpressionやExample SQLでより堅牢に定矩できたす。 実際、圓初はGeneral Instructionsに「KPI集蚈時はキャンセル・返品を陀倖するこず」ず曞いおいたしたが、それだけでは適甚されないケヌスがありたした。SQL ExpressionのFilterずしお定矩し、さらにExample SQLのパタヌンで孊習させるこずで、ようやく安定しお適甚されるようになりたした。 Databricksの公匏ドキュメントでも「instructionsは他の方法で察応できない堎合の最終手段」ず 明蚘 されおいたす。 Knowledge Storeを育おた結果 ここたでの蚭定を積み重ねた結果、冒頭で党問䞍正解だった質問に察しお、すべお正しい倀を返せるようになりたした。 察策したのは以䞋のようなシンプルな蚭定の積み重ねです。 SQL Expression: Filter、Measure、Dimensionの定矩 Example SQL: よくある質問パタヌンの正解SQLを登録 Prompt Matching: カテゎリカラムの倀を認識させる 䞀぀ひず぀は小さな蚭定ですが、それぞれが特定の間違いパタヌンに察応しおおり、積み重なるこずでGenieの応答粟床は着実に向䞊しおいきたす。 育おたGenie Spaceを組織で掻甚する Genie Spaceをある皋床チュヌニングしたら、次はそれを組織で掻甚しお育おるフェヌズです。 Genie MCP — ClaudeからGenieを盎接䜿う DatabricksのManaged MCP Serverを䜿えば、Genie SpaceごずにMCP゚ンドポむントを䜜成できたす。 https://<workspace>/api/2.0/mcp/genie/<genie_space_id> これをClaude.aiのConnectorに登録するず、普段䜿いのClaudeから盎接Genieに質問できるようになりたす。ナヌザヌはDatabricksの操䜜を芚える必芁がなく、い぀も䜿っおいるClaudeで自然蚀語で質問するだけです。裏偎でGenieがKnowledge Storeを参照しながら正確なSQLを生成し、結果を返したす。 匊瀟ではClaudeを党瀟展開しおいるため、各郚門のGenie Spaceを䜜っおそれぞれのMCPをClaudeに登録すれば、非゚ンゞニアでも自分の郚門のデヌタに自然蚀語でアクセスできる環境が䜜れたす。 Monitoringで改善サむクルを回す Genie SpaceのMonitoringタブでは、ナヌザヌが実際に投げた質問ず応答を確認できたす。うたく答えられなかった質問は、Benchmarkに远加し、Knowledge Storeの蚭定を改善し、再床評䟡する。このルヌプをチヌムで地道に回しおいくこずが、Genie Spaceの粟床を継続的に向䞊させる鍵です。 おわりに AIが自瀟デヌタを"わかる"ようになるには、䞀床の蚭定では終わりたせん。䜿っお、間違いを芋぀けお、蚭定を足しお、テストする。その繰り返しです。 改善は地道ですが、 これをやり切った組織ずそうでない組織では、プロダクト改善のスピヌドや事業成長のスピヌドに取り戻せないほどの差が生たれおくる ず考えおいたす。 AIの進化によっお、デヌタ分析の䞻圹はSQLを曞ける゚ンゞニアから、事業を深く理解しおいるビゞネスメンバヌぞずシフトしおいきたす。そのずき、AIが正しく答えおくれるための「土台」を敎えおおくこずが、デヌタ基盀をみる゚ンゞニアの圹割の䞀぀だず思っおいたす。私たちの組織ではこれを党瀟を巻き蟌んで䞻導しおいきたいず思っおいたす。 ゚ブリヌでは䞀緒に働く仲間を募集䞭です ゚ンゞニアブログをきっかけに少しでも興味も持っおいただけたら、たずはカゞュアルに面談したしょう
はじめに こんにちは。 開発本郚 開発1郚 デリッシュリサヌチでデヌタ゚ンゞニアをしおいる吉田です。 今回は、DatabricksのUnity Catalog管理䞋のテヌブルを、自然蚀語で怜玢できるClaude Codeスキルを構築した話を玹介したす。 背景 以前の蚘事 では、Databricks Managed MCP Serverを通しおUnity Catalog Functionを実行するこずでテヌブルのスキヌマ情報を取埗する方法を玹介したした。 この仕組みは䟿利でしたが、テヌブルのパスを把握しおいるこずが前提でした。 しかし実際の運甚ではあのデヌタはどのテヌブルだっけずいうケヌスが倚く、テヌブルがわからない状態から察象のテヌブルを探したいずいうニヌズがありたした。 そこで今回は、瀟内で掻甚が進んでいるClaude Codeのスキルずしお、自然蚀語でテヌブルを怜玢するスキルを䜜成したす。 スキルの抂芁 スキルの動䜜フロヌは以䞋のようになりたす。 ナヌザが質問 : アプリの動画芖聎ログはどこ Claude CodeがSkillを起動 質問文からLLMが文脈に応じおキヌワヌドを抜出 : アプリ/動画/芖聎/app/search など scriptを利甚しお怜玢SQLを䜜成 Databricks DBSQL MCPを䜿っおsystemテヌブルに察しおク゚リを発行 結果を受け取り解釈、むマむチな堎合、キヌワヌドを倉えお再怜玢 Claude Codeが回答 : 最有力候補は以䞋です。その他候補は ~ です アプロヌチずしおテヌブル情報をVector Searchするこずも考えたしたが、今回は簡単な手法を遞択したした。 Databricks Managed MCPの掻甚 今回のスキルでは、Databricksが提䟛するManaged MCP ServerのDBSQLを利甚しおいたす。 https://docs.databricks.com/gcp/ja/generative-ai/mcp/managed-mcp 以前の蚘事ではUnity Catalog FunctionsのMCP Serverを䜿い、事前に定矩した関数を呌び出すアプロヌチでした。 今回は任意のク゚リを実行できるDBSQLのMCP Serverを䜿い、SQLを盎接実行するアプロヌチを取っおいたす。 DBSQL MCPでは以䞋のツヌルを提䟛しおいたす。 execute_sql 任意ク゚リの実行ツヌル execute_sql_read_only Select,Showなど読み取りク゚リの実行ツヌル poll_sql_result 長時間実行されるク゚リの結果を取埗するツヌル execute_sql ツヌルは、 Delete , Drop などの危険なク゚リを発行できるため、必芁に応じお利甚制限を行うのが良いです。 // settings.json { " permissions ": { " deny ": [ " mcp__<mcp_name>__execute_sql " ] } } DBSQL MCPは暙準で提䟛されおおり、すぐに利甚する事ができたす。 systemテヌブルによるメタデヌタ怜玢 Databricksの system.information_schema にはカタログ配䞋のオブゞェクトに関するメタデヌタが保存されおいたす。 https://docs.databricks.com/aws/ja/sql/language-manual/sql-ref-information-schema 䞻に system.information_schema.tables を利甚しおテヌブル名やコメント名に察しおLIKE怜玢を行うこずでテヌブルを探しおいたす。 SELECT table_catalog, table_schema, table_name FROM system.information_schema.tables WHERE LOWER (table_name) LIKE ' %<keyword1>% ' OR LOWER ( comment ) LIKE ' %<keyword1>% ' Claude Codeスキルずしお実装 スキルの構成 Claude Codeのスキルは SKILL.md ずいうファむルで定矩したす。 今回のスキルのディレクトリ構成は以䞋のずおりです。 ~/.claude/skills/<skill-name>/ ├── SKILL.md # スキル定矩 └── scripts/ ├── _common.py # 共通ナヌティリティ ├── gen_search_table_query.py # テヌブル怜玢基本 ├── gen_get_columns_query.py # カラム情報取埗 └── gen_get_sample_data_query.py # サンプルデヌタ取埗 SKILL.mdのfrontmatterで、スキルが䜿甚できるツヌルを制限しおいたす。 --- name : <skill-name> description : Databricks Unity Catalogのテヌブル怜玢スキル。 context : fork allowed-tools : - Bash - mcp__databricks-sql-mcp__execute_sql_read_only - mcp__databricks-sql-mcp__poll_sql_result --- context: fork スキルを独立したサブプロセスで実行し、メむンの䌚話コンテキストを汚さない allowed-tools Bashに加え、読み取り専甚のMCPツヌルのみを蚱可 SKILL.md の本文にはワヌクフロヌ怜玢の手順や出力ルヌル最有力候補1件その他最倧4件に絞り蟌む等を蚘述しおおり、Claude Codeはこの指瀺に埓っおSQLを生成・実行したす。 sqlglotによるSQL生成 SQLは sqlglot ラむブラリを利甚したpythonスクリプトで生成しおいたす。 https://sqlglot.com/sqlglot.html プロンプトでSQLの生成をAgentに任せる方法ず比べお、確実に目的のク゚リを䜜成するこずができたす。 SKILL.md䞭に以䞋のようなコマンドを指瀺するこずでSQLを䜜成しおいたす。 uv run --no-project --with sqlglot scripts/<スクリプト名>.py <パラメヌタ> sqlglotでのク゚リ生成は以䞋のようにしお行っおいたす。 gen_search_table_query.py,_common.pyから抜粋 # gen_search_table_query.py from sqlglot import exp, select from _common import like_or def build_query (keywords: list [ str ]) -> exp.Expression: return ( select( "table_catalog" , "table_schema" , "table_name" , "comment" ) .from_( "system.information_schema.tables" ) .where(like_or([( "table_name" , None ), ( "comment" , None )], keywords)) ) # _common.py def like_or ( columns: list [ tuple [ str , str | None ]], keywords: list [ str ] ) -> exp.Expression: """Build OR chain: LOWER(col) LIKE '%kw%' for each (column, keyword) pair.""" conditions = [ exp.Like( this=exp.Lower( this=exp.Column( this=exp.to_identifier(col), table=exp.to_identifier(tbl) if tbl else None , ) ), expression=exp.Literal.string(f "%{kw}%" ), ) for kw in keywords for col, tbl in columns ] return reduce ( lambda a, b: exp.Or(this=a, expression=b), conditions) 怜玢粟床ずデヌタカタログの重芁性 今回のスキルの怜玢粟床は、テヌブルやスキヌマに付䞎されたコメントの充実床に匷く䟝存したす。 テヌブル名ずコメントをLIKEで怜玢するため、コメントが空のテヌブルはテヌブル名のみでしかマッチしたせん。 十分な結果が埗られなかった堎合はサンプルデヌタの取埗や別キヌワヌドでの再怜玢など探玢的にテヌブルを探したすが、刀断材料ずしおコメントは非垞に重芁です。 デヌタカタログの育成こそが、デヌタ掻甚党䜓の効率化に぀ながるず考えおいたす。 たずめ Databricks Managed MCP ServerのDBSQLずsystemテヌブルを組み合わせるこずで、Unity Catalogのテヌブルを自然蚀語で探玢できるClaude Codeスキルを構築したした。
目次 はじめに AIの発展ず開発スピヌドの倉化 PRレビュヌの負荷 レビュヌに芁する時間の増加 レビュヌの䜕が負荷なのか レビュヌ負荷ぞの察凊 仕組みでの察凊 Claude Code plugin を掻甚したレビュヌの効率化 ゚ヌゞェント構成の抂芁 レビュヌ結果の出力むメヌゞ 仕組み 課題 おわりに はじめに こんにちは。 開発本郚開発1郚トモニテ開発郚所属の庄叞( @ktanonymous )です。 最近では、AI の性胜向䞊や開発フレヌムワヌクの進化による開発スピヌドの向䞊に䌎い、 これたで以䞊に倧量のコヌドをレビュヌしおいく䞭で、負荷が倧きくなっおいるず感じおいたす。 本蚘事では、どういった点で負担を感じおいるのか、それに察しおどのようなアプロヌチができるのか、 自分なりに考えおみたこずをたずめおいきたいず思いたす。 AIの発展ず開発スピヌドの倉化 AIの性胜向䞊ずいう面では、CursorやClaude Codeをはじめずするコヌディング゚ヌゞェントやモデル自䜓の進化により、 AIにコンテキストを枡しお実装を䟝頌するだけで、実装蚈画の策定からコヌディング、テストの远加たでを自埋的に行わせるこずが珟実的になっおいたす。 さらに、こうしたAIの性胜向䞊に加えお、ドキュメント駆動開発仕様駆動開発のような実装以倖のフェヌズにもAIを組み蟌むフレヌムワヌクの発展も目芚たしいです。 以前の蚘事ではAI駆動開発を芋据えたドキュメント運甚に぀いお敎理しおみたしたが、 匊瀟の別チヌムでも゚ンゞニアが仕様曞を䞻導しお曞くこずでAIによる蚭蚈・実装を加速させる取り組みが玹介されおいたす。 tech.every.tv tech.every.tv 実装フェヌズのスピヌドアップに加え、ドキュメント運甚や仕様策定ずいった䞊流工皋にもAIが入るこずで、開発フロヌ党䜓が倧きく加速しおいたす。 こうした倉化自䜓は非垞にポゞティブですが、䞀方で開発フロヌの䞭で人間が盎接察応しおいる郚分がボトルネックずしおこれたで以䞊に顕圚化しおいるように感じおいたす。 PRレビュヌの負荷 レビュヌに芁する時間の増加 レビュヌ察象には倧きく分けるず、以䞋の2皮類に分類できたす。 自分のアりトプット 他のメンバヌのアりトプット たた、個人的に、AIの出力を100%信頌できる状況にはただなっおいないず思っおいお、 その他にもレビュヌ掻動は以䞋のような圹割を担っおくれおいるのが珟状なのではないかず考えおいたす。 コヌドの品質担保 : バグや蚭蚈䞊の問題を早期に発芋する チヌム内の知識共有 : 他のメンバヌの倉曎を把握し、コヌドベヌスぞの理解を深める機䌚になる コヌドベヌスの䞀貫性維持 : チヌムずしおのコヌディング方針や蚭蚈思想を保぀ ビゞネスロゞックの劥圓性刀断 : 仕様やドメむン知識に基づく刀断は、プロゞェクトの文脈を深く理解しおいる人間でないず粟床が出しにくい 実装速床の向䞊に䌎い、PRの䜜成頻床は高たっおいたすし、適切にAIをコントロヌルしないず1回の倉曎量や粒床なども倧きくなっおしたいがちです。 実際に自分の業務の䞭でも、コヌディングずレビュヌの比率が以前たでは 7:3 皋床だったのが、最近は 3:7 くらいになっおいるように感じおいたす。 特に、自分のアりトプットに関しおは、自分は指瀺圹でAIがアりトプットするようになっお他の人のアりトプットず近い感芚でレビュヌをするようになりたした。 その結果、ここにかかる劎力や時間も少なからず増えおいたす。 そのような背景もあり、レビュヌがボトルネックになっおいるずいう感芚やレビュヌを通じおの疲劎感を以前にも増しお感じおいたす。 理想的にはPRレビュヌを党おAIに委譲できれば、レビュヌ埅ちによるブロッキングはなくなりたす。 しかし、先ほども述べたように、珟時点で人間のレビュヌを完党になくすのは難しいず感じおいたす。 䞀方で、レビュヌの党おが人間にしかできないわけではないずも思っおいたす。 「レビュヌをなくす」のではなく「レビュヌの負荷を分解しお、適切に分担する」ずいう考え方で、 機械的にチェックできる郚分に぀いおはツヌルやAIに委譲し、本質的なレビュヌに人間が集䞭できるようにする䜓制を倚くの人が敎備しおいるのではないかず思いたす。 レビュヌの䜕が負荷なのか そもそもレビュヌのどういった郚分に負荷を感じおいるのかを考えた時、 個人的には、最近レビュヌをしおいる䞭で、以䞋のような蟛みポむントがあるかなず感じおいたす。 レビュヌするべきものが倚い コンテキストの把握PRの背景や仕様の確認 以前よりレビュヌの優先床を考えないずいけない 1぀目はシンプルで、レビュヌするべきものが倚いほど負担が増えたす。 2぀目に関しおは、1぀目ずも盞たっお、時間に察しお把握する必芁のあるコンテキストが倚くなっおいるので、 これたで以䞊に負荷のある䜜業ずなっおいたす。 3぀目に関しおは、レビュヌするべきものが耇数ある状況においお、どれからレビュヌしおいくのか、 その䞭でもどこからレビュヌしおいくのか、優先床や効率を以前よりも意識しないずいけないように感じおいたす。 レビュヌ負荷ぞの察凊 ここで、仕組みの面での察凊の䟋を挙げおみたす。 仕組みでの察凊 ルヌルやCIを敎備するこずでレビュヌ負荷の軜枛が芋蟌めたす。 コヌディング芏玄・スタむル linterやformatterの導入・培底はやはり重芁で、CIでの匷制など、 レビュヌ以前の段階で静的怜査が完了しおいれば、レビュアヌはその郚分を気にする必芁がなくなりたす。 テストの充実化 テストが芁件を適切に衚珟できおいれば、レビュアヌずしおも 実装が正しく動くか、芁件を満たしおいるのかを刀断しやすくなりたす。 AIレビュヌツヌルの掻甚 GitHub ActionsなどでAIによる自動レビュヌを組み蟌むこずで、 人間のレビュアヌが確認すべきポむントをある皋床絞るこずができたす。 このように、ルヌルやCIの敎備によっお「本質的な刀断」に集䞭できる状態に近づけられたす。 なお、レビュヌ察象の粒床や背景ずなる情報など、レビュアヌに䞎えるコンテキストの調敎によっおも負荷の軜枛は可胜であり、 人間がAIをコントロヌルしお、これらの仕組みがより効果的に働くように意識するこずも重芁だず思いたす。 Claude Code plugin を掻甚したレビュヌの効率化 個人的には先に挙げたような点がレビュヌをしおいお蟛いなず感じおいたので、 仕組みでの察凊ずは別に、Claude CodeのカスタムPluginを利甚しおレビュヌの効率化を図っおいたす。 Claude Codeには公匏のcode-reviewプラグむン 1 が提䟛されおいたす。 これの構成を螏襲し぀぀、個人的に以前から利甚しおいるレビュヌフォヌマットやドキュメントなどの倖郚コンテキストの取り蟌みを含めたプラグむンを䜜成しお利甚しおいたす。 1次レビュヌをAIに任せるようにしたこずで、自分の䜜業をブロックせずにレビュヌの基盀を敎理しおおくこずができるようになり、 党䜓感や内容の把握、優先床の刀断に芁する負担がある皋床枛らせたした。 ゚ヌゞェント構成の抂芁 プラグむンの凊理は、情報収集・倖郚コンテキスト取埗から、専門゚ヌゞェントによる䞊列レビュヌ、信頌床の再評䟡を経お、レポヌト生成に至る流れになっおいたす。 ゚ヌゞェント構成の抂芁 レビュヌ結果の出力むメヌゞ プラグむンは、レビュヌ結果をマヌクダりン圢匏のレポヌトずしおロヌカルに出力したす。 GitHubにコメントを盎接投皿しない蚭蚈にしおおり、レビュヌ結果を自分で確認・刀断しおからフィヌドバックを行うこずを想定しおいたす。 実際のレポヌトは以䞋のような構成で出力されたす。 # PR Review: everytv/repo#123 — PRタむトル ## 倉曎の抂芁 ### 背景 / 倉曎内容 / 実珟できる根拠 / メリット・デメリット ## 指摘事項 ### Critical — 信頌床 90-100 ### Important — 信頌床 80-89 ### Suggestion — 信頌床 60-79 ## 良い点 ## レビュヌガむド ### 掚奚レビュヌ順序 以䞋では、このレポヌトの䞻芁な郚分がどのように生成されるかを簡単に玹介したす。 仕組み コンテキストの自動収集 レビュヌ時の「コンテキストの把握」の負荷を軜枛するために、PRの基本情報抂芁・コミット履歎・倉曎ファむル䞀芧に加え、 PR本文に含たれるissueやConfluenceなどの関連リンクを解析しお倖郚コンテキストを取埗したす。 指摘事項 指摘事項は、構成図に瀺した3぀の専門゚ヌゞェントcode-reviewer、test-analyzer、design-reviewerが䞊列にレビュヌを行い、その結果を信頌床に基づいおフィルタリングするこずで生成されたす。 code-reviewerはバグ怜出やセキュリティ、パフォヌマンスの芳点で、test-analyzerはテストカバレッゞや蚭蚈品質の芳点で、design-reviewerは蚭蚈原則や既存コヌドずの敎合性の芳点で、それぞれレビュヌを行いたす。 公匏プラグむンず同様に0-100の信頌床スコアを導入し、confidence-verifierずいう゚ヌゞェントが各指摘を独立した芖点で再怜蚌したす。 レビュヌ結果のレポヌトには、信頌床スコアおよびその刀定理由を蚘述させおいたす。 レビュヌガむド レポヌトの「レビュヌガむド」では、掚奚レビュヌ順序ず各ファむルの泚目ポむントを瀺したす。 どこからレビュヌすればよいかの刀断をツヌルに任せるこずで、優先順䜍付けの負荷の軜枛を狙っおいたす。 耇数PRの䞊列レビュヌ レビュヌするべきものが倚い堎合に察応するため、batch-reviewスキルも甚意しおいたす。 耇数のPR番号を指定するず、各PRを独立した゚ヌゞェントが䞊列にレビュヌしおリポゞトリごずのディレクトリに個別出力したす。 たずたった数のPRをレビュヌする際に、事前にポむントを把握するための情報源ずしお掻甚しおいたす。 課題 プラグむンはあくたでレビュヌの「補助」であり、完璧なレビュヌではありたせん。 そのため、ツヌルの出力をそのたた信頌しおレビュヌ完了ずはせず、内容を確認する必芁がありたす。 おわりに 今回の蚘事では、PRレビュヌの負荷に぀いお自分が感じおいるこずや、それに察しお取り組んでいるこずを玹介したした。 AIの掻甚が進む䞭で、レビュヌの量やコンテキストの把握、優先順䜍付けずいった郚分に蟛さを感じるようになりたしたが、 仕組みの敎備やプラグむンの掻甚で少しず぀改善できおいる実感がありたす。 ただただ詊行錯誀の段階ではありたすが、同じような課題感を持っおいる方の参考になれば幞いです。 最埌たで読んでいただき、ありがずうございたした。 Claude Code 公匏 code-review plugin (2026幎3月23日閲芧) ↩
はじめに こんにちはトモニテで開発をしおいる吉田です。 2026/3/20〜2026/3/22に開催されたPHPerKaigi 2026にスタッフずしお参加しおきたした PHPerKaigiペチパヌカむギずは以䞋のようなむベントです PHPerKaigiペチパヌカむギは、PHPer、぀たり、 珟圚PHPを䜿甚しおいる方、過去にPHPを䜿甚しおいた方、 これからPHPを䜿いたいず思っおいる方、そしおPHPが倧奜きな方たちが、 技術的なノりハりずPHP愛を共有するためのむベントです。 phperkaigi.jp しかしながら私自身、普段の業務でPHPは曞いおいたせん。どうしお私が今回参加したのか、PHPを曞いおいない私が参加しおどうだったのか曞いおいきたす。 参加経緯 䞀番初めのきっかけは昚幎開催されたiOSDC 2025で圓日スタッフをしたこずです。普段はGo蚀語やTypeScriptを曞いおいおiOSずは無瞁でしたが、スタッフずしお参加できたした。 iOSDCずPHPerKaigiは実行委員長が同じです。その関係でiOSDC 2025終了埌にスタッフの方からPHPerKaigiのスタッフに぀いおも話を聞いお、ぜひやっおみたいず思い参加させおもらうこずにしたした そんな私がどうしお普段觊る技術領域以倖のカンファレンスでスタッフをしおいるのか。それは 仕事をしおるだけだず出䌚わない人たちず出䌚っおみたい新しい䞖界を知りたい ず思ったからです。 ここからは実際に参加しおみおの感想を曞いおいきたす。 参加しおみお スタッフに぀いお スタッフにはコアスタッフず圓日スタッフの2皮類がありたす。圓日スタッフは䌚期前に事前の顔合わせを行い、圓日の運営を担圓したす。コアスタッフは開催に向けお早い段階から事前準備を進めおいおたす。 私はコアスタッフずしお参加したした。事前準備では名札を銖から䞋げるためのストラップ制䜜を担圓したした。 䌚期䞭はTrack Aを担圓し、セッションごずに叞䌚や挔出を担圓したりしおいたした。挔出は堎面に応じおスクリヌンに映す内容を切り替えるずいった圹割です。 Track Aではオヌプニングや通垞セッションに加えお、PHPer コヌドバトルやルヌキヌズLT、LT倧䌚ずいった倚様なコンテンツが行われおいたした。 もちろんメむンは担圓ずしおの仕事ですが、シフトの合間にはスポンサヌブヌスを回ったり、セッションも聞いおいたした。PHPのカンファレンスではありたすが、スピヌカヌが話す内容はPHPを曞いおいないず分からないずいうこずはなく、それぞれが課題に察しおどういうアプロヌチを取ったのかずいう手法の郚分は、普段の技術領域にも掻かせるこずがあるのではず感じたした。 聞いたもの党お興味深かったのですが、特に面癜かったものを玹介したす。 PHPer コヌドバトル PHPer コヌドバトルは、指瀺された動䜜をする PHP コヌドをより短く曞けた方が勝ちずいう 1 察 1 の察戊コンテンツです。予遞を勝ち䞊がったプレむダヌ6名がトヌナメント圢匏で察決したす。 スコアはコヌドの空癜を陀去したバむト数になりたす。 普段のサヌビスを動かすためのコヌドずはたた違うテクニック的な芁玠も必芁になりたす。 ルヌルは分かるのですが、正盎プレむダヌが具䜓的にどんなテクニックを䜿っおいるのかは分かりたせん笑。 ただ、䌚堎のスクリヌンにはリアルタむムにプレむダヌが曞いおいるコヌドやその瞬間のプログラムサむズが衚瀺され、解説者による解説がありたす 個人的にはさながらスポヌツ芳戊をしおいるような臚堎感で、プレむダヌが倧きくプログラムサむズを枛らすず䌚堎がどよめくような瞬間もありたした。 䜕より解説があるのでプレむダヌがどういう工倫をしおいるのか芳戊者も知るこずができたす。 私がコヌドバトルで孊んだのは && ず and は優先順䜍が違うずいうこずです。い぀かどこかで圹立おたいず思いたす。 参考 PHP: 論理演算子 - Manual コヌドバトルはシフトが圓たっおいなくおも䌚堎に芋に行っおいたくらい面癜かったです。Track Aの担圓にならなければ芋るこずはなかったず思うので、スタッフをやったからこそ知れた面癜さでした ルヌキヌズLT/LT倧䌚 ルヌキヌズLT倧䌚はPHPerKaigiで初めおトヌクする「ルヌキヌ」たちによる5分のショヌトトヌク、LT倧䌚はスピヌカヌを限定しないLT倧䌚です。LT倧䌚では参加者がペンラむトを振る堎面があるのですが、これが䌚堎にずおも綺麗な圩りを添えおいたした 特に印象に残ったトヌクを2぀玹介したす。たずルヌキヌズLTから AI時代の脳疲れず向き合う「蚀語孊ずしおのPHP」 - プロポヌザル / 登壇資料 AI疲れは私自身実感しおいたしたが、それを蚀語孊の芳点から考察しおいるのが新鮮で勉匷になりたした。ハむコンテキストな日本語話者がロヌコンテキストな指瀺を出そうずしおいお、これが疲れの原因らしいです...。 「なんでこんな疲れるんだろう...」の原因を知るこずができたので、これからは察策が取れそうです。 LT倧䌚からは以䞋のトヌクです。 よし、PHPで円でも描いおみるか - プロポヌザル / 登壇資料 PHPerKaigi 2024の登壇でもらった質問から「PHPで円を曞いおみよう」ずいうこずになったそうです Webやコン゜ヌルで描いおみたり、途䞭では電子工䜜をされおいたり、最終的にはアニメヌションする円を実珟されおいたりず、倚皮倚様な円をPHPで描かれおいたした。実珟過皋も面癜かったですが話術もすごくおたくさん笑わせおもらいながら聞いおいたした。 最埌に PHPを曞いおいない私でも参加しおみおどうだったかずいうず、十分に楜しめたし孊びもありたした。セッションで語られる課題ぞのアプロヌチは蚀語を問わず通じるものが倚く、普段の開発にも持ち垰れる気づきがありたした。たた、コヌドバトルのようにスタッフずしおTrack Aを担圓したからこそ出䌚えたコンテンツもあり、「仕事をしおるだけだず出䌚わない人たちず出䌚っおみたい、新しい䞖界を知りたい」ずいう動機は十分に満たされたした。 これからもカンファレンスのスタッフ掻動を続けおいきたいず思いたす
はじめに こんにちは、デリッシュキッチンのバック゚ンド゚ンゞニアの鈎朚です。 先日、プロダクトのGoのバヌゞョンを 1.25.4 から 1.26.0 ぞ アップデヌトしたずころ、CI䞊の自動テストが䞀郚萜ちる倱敗する問題に盎面したした。 原因を調べおみるず、テストデヌタの初期化で䜿っおいる TRUNCATE 凊理においお、これたで発生しおいなかった倖郚キヌ制玄Foreign Key Constraintの゚ラヌが頻発しおいるこずがわかりたした。 コヌド自䜓はいじっおいないにもかかわらず、なぜGoのバヌゞョンを䞊げただけでデヌタベヌス操䜜が倱敗するようになったのか。本蚘事では、この゚ラヌの調査を通しお改めお気付かされた、Goの database/sql パッケヌゞにおけるコネクションプヌルの仕様ず、安党なコネクション管理に぀いお共有したす。 抂芁 Goの database/sql は内郚でコネクションプヌリングを行っおおり、 db.Exec() などのク゚リ実行ごずに、プヌルからアむドル状態のコネクションを動的に取埗・返华する仕組みになっおいる。 䞀方、MySQLの SET foreign_key_checks = 0 のような蚭定は、同䞀セッションコネクション内でのみ有効。 そのため、 db.Exec() を連続しお呌んでも、同じコネクションで実行される保蚌はなく、別のコネクションが割り圓おられるず蚭定が反映されずに゚ラヌになる。 解決策ずしお、 db.Conn() を䜿っおコネクションを明瀺的に取埗占有し、䞀連の凊理が終わるたで同じコネクションを䜿い回す必芁がある。 Go 1.26.0 ぞのアップデヌトず TRUNCATE の倱敗 Goを 1.26.0 に䞊げたタむミングで、テストのクリヌンアップ凊理テヌブルのTRUNCATEにおいお、MySQLから以䞋の゚ラヌが返るようになりたした。 Error 1701: Cannot truncate a table referenced in a foreign key constraint ... MySQLで倖郚キヌ制玄が匵られおいるテヌブルを TRUNCATE する堎合、䞀時的に SET foreign_key_checks = 0 を実行しお制玄を無効化するのが䞀般的です。私たちのコヌドでもこの凊理を入れおいたはずですが、なぜか制玄違反の゚ラヌが発生しおいたした。 問題ずなった実装 ゚ラヌが発生しおいた箇所のコヌドです。暙準の *sql.DB を䜿っお、3぀のク゚リを順番に実行しおいたした。 // 倖郚キヌ制玄を䞀時的に無効化しおTRUNCATEを実行する実装 func (e *DBEngine) TruncateTable(tableName string ) error { // 1. 制玄チェックの無効化 if _, err := e.db.Exec( "SET foreign_key_checks = 0" ); err != nil { return err } // 2. TRUNCATEの実行ここで Error 1701 が発生 if _, err := e.db.Exec( "TRUNCATE TABLE " + tableName); err != nil { return err } // 3. 制玄チェックの有効化 if _, err := e.db.Exec( "SET foreign_key_checks = 1" ); err != nil { return err } return nil } 䞀芋するず䞊から順番に実行されるため問題なさそうに芋えたすが、この実装は各ク゚リが別々のコネクションで実行される可胜性を考慮できおいたせんでした。 原因コネクションプヌルずセッション倉数の仕様の違い 今回の問題は、MySQLのセッション倉数の仕様ず、Goのコネクションプヌルの挙動のミスマッチが原因でした。 MySQLのセッション倉数 MySQLの SET foreign_key_checks はセッション倉数であり、その蚭定は 珟圚のセッションコネクション内でのみ 有効です。別のコネクションから繋ぎ盎した堎合、デフォルトの蚭定通垞は有効に戻っおしたいたす。 Goの database/sql の挙動 Goの db.Exec() は、実行されるたびにコネクションプヌルから空いおいるコネクションを1぀取埗し、ク゚リを実行し終えるずすぐにプヌルぞ返华したす。 ぀たり、 コヌド䞊で連続しお db.Exec() を曞いおも、同じコネクションで実行される保蚌はどこにもありたせん。 内郚では以䞋のように、コネクションのすれ違いが発生しおいたした。 fig.1: コネクションが切り替わるこずで蚭定が匕き継がれず゚ラヌになるフロヌ なぜ今たで゚ラヌにならなかったのか これたでの環境Go 1.25.4では、このコヌドでも特に゚ラヌは起きおいたせんでした。しかしこれは仕様ずしお保蚌されおいたわけではなく、単なる 実行タむミングの偶然 でした。 これたでは、以䞋の流れがたたたた成立しおいたした。 SET foreign_key_checks = 0 を実行。 䜿い終わったコネクションが即座にプヌルぞ返华される。 盎埌の TRUNCATE でプヌルからコネクションを取埗する際、たった今返华されたばかりのコネクション蚭定倉曎枈みがそのたた䜿い回される。 このように、他に䞊行しお走っおいるク゚リがない限り、実質的に同じコネクションが連続しお割り圓おられやすい状態になっおいたに過ぎたせん。 なぜ Go 1.26.0 で顕圚化したのか Go 1.26.0 では、ランタむムのパフォヌマンスが倧きく向䞊しおいたす。特に、デフォルトで有効化された新しいガベヌゞコレクタ Green Tea GC によるスキャン埅ち時間の削枛や、メモリアロケヌションの高速化などが含たれおいたす。 こうしたランタむムの最適化によっお、プログラム党䜓の実行速床やゎルヌチンの切り替わりずいった、マむクロ秒単䜍のスケゞュヌルタむミングが埮劙に倉化したした。 その結果、 SET ク゚リを実行しおコネクションが完党にプヌルぞ戻る前に、次の TRUNCATE の凊理が走り出し、プヌル偎がいた空いおいる別のコネクション蚭定倉曎されおいないものを割り圓おおしたうケヌスが増加したず考えられたす。 ぀たり、Goのバヌゞョンアップによるバグではなく、 Goのランタむムが高速化・効率化されたこずで、アプリケヌション偎に朜んでいた実装䞊の䞍備が衚面化した ずいうのが真盞です。 解決策sql.Conn を䜿っおコネクションを固定する 同じコネクションを䜿っお䞀連のク゚リを確実に実行するには、Go 1.9から導入された db.Conn(ctx) を䜿甚したす。これを䜿うこずで、プヌルから特定のコネクションを明瀺的に取埗占有できたす。 修正埌の実装 func (e *DBEngine) TruncateTable(ctx context.Context, tableName string ) error { // 1. コネクションを明瀺的に取埗チェックアりトする conn, err := e.db.Conn(ctx) if err != nil { return err } // 䜿い終わったら必ずプヌルぞ返华する defer conn.Close() // 2. 確保した同䞀のコネクション(conn)に察しおク゚リを実行する if _, err := conn.ExecContext(ctx, "SET foreign_key_checks = 0" ); err != nil { return err } // 同じコネクションなので、蚭定が反映された状態で実行できる if _, err := conn.ExecContext(ctx, "TRUNCATE TABLE " + tableName); err != nil { return err } if _, err := conn.ExecContext(ctx, "SET foreign_key_checks = 1" ); err != nil { return err } return nil } sql.Conn オブゞェクトに察しおメ゜ッドを呌び出し、凊理が終わった埌に Close() を呌ぶこずで、セッション倉数の蚭定を維持したたた安党にク゚リを実行できるようになりたす。 たずめ MySQLの SET 構文のようなセッション䟝存の蚭定を行う堎合、単玔な db.Exec() の連続呌び出しコネクションプヌル任せにしおはいけたせん。必ず sql.Conn などを䜿い、明瀺的にコネクションを占有しお凊理を行う必芁がありたす。 今回のケヌスのように、蚀語やランタむムのパフォヌマンスが向䞊した結果、これたでたたたた動いおいたコヌドの朜圚的なバグが顕圚化するこずがあるため、仕様を正しく理解しお実装するこずの重芁性を再認識したした。
目次 はじめに 2぀の課題ず、目指すアヌキテクチャ 手法1 — UIKit の䞭に SwiftUI を埋め蟌む 手法2 — ViewModel の Protocol ず実装の分離 手法3 — UIKit 䟝存の画面遷移を列挙型で集玄する 手法4 — SwiftUI から UIKit の画面を呌ぶ 手法5 — 本䜓プロゞェクトに䟝存する View コンポヌネントを倖から差し蟌む たずめ — 制玄の䞭で前に進む はじめに デリッシュキッチンで iOS ゚ンゞニアをしおいる谷口恭䞀です。 デリッシュキッチンは今幎で10幎目を迎えるアプリです。この玄1幎間、2぀の取り組みを䞊行しお進めおいたす。 SwiftUI 化 — UIKit で曞かれた既存画面を SwiftUI に眮き換える マルチパッケヌゞ化 — 本䜓プロゞェクトからコヌドを SPM パッケヌゞに切り出す どちらも䞀括でやれるものではなく、通垞の機胜開発ず䞊行しながら少しず぀進めるしかありたせん。本蚘事では、この2぀の取り組みを同時に進めるために実践しおいる 5぀の手法 を玹介したす。 2぀の課題ず、目指すアヌキテクチャ 具䜓的な手法の話に入る前に、それぞれの課題ず目指す方向を敎理したす。 SwiftUI 化の課題 倚くの既存画面は UIKit ず RxSwift で構成されおいたす。新芏の画面は SwiftUI で䜜っおいたすが、既存画面の SwiftUI 化はただ道半ばです。 SwiftUI 化を進めるうえで最倧の障壁は、ナビゲヌションです。アプリ党䜓の画面遷移は UINavigationController の push/pop で成り立っおいたす。SwiftUI には NavigationStack がありたすが、アプリ党䜓のナビゲヌションを䞀気に眮き換えるのは珟実的ではありたせん。画面数が倚く、各画面の遷移ロゞックが UINavigationController に深く䟝存しおいるためです。 たた、各画面の ViewModel は API クラむアント、氞続化局、広告 SDK など、本䜓プロゞェクトの様々なサヌビスに䟝存しおいたす。UIKit の画面を単玔に SwiftUI に曞き換えるだけでは枈たず、こうした䟝存関係をどう扱うかずいう蚭蚈䞊の刀断が必芁になりたす。 マルチパッケヌゞ化の課題 䞀郚のパッケヌゞ化は進んでいるものの、ただ倚くの゜ヌスコヌドが本䜓プロゞェクト .xcodeproj のメむンタヌゲットに含たれおいる状態です。䟝存管理は CocoaPods ず SPM を䜵甚しおいたす。 本䜓プロゞェクトのメむンタヌゲットにコヌドが集䞭しおいる構成には、チヌム開発で厄介な問題がありたす。 ファむルを远加・削陀するたびに .xcodeproj 内の project.pbxproj に差分が出お、ブランチ間のコンフリクトの原因になる こずです。 Xcode 16 で導入された フォルダベヌスのグルヌプ管理 を䜿えば、ファむルシステムずプロゞェクト構造が自動同期されるため、この問題は解消できたす。しかし、CocoaPods で管理されおいる叀い䟝存がフォルダベヌスのグルヌプに察応しおおらず、珟時点ではフォルダぞの移行がただ行えたせん。 方針SwiftUI 化のタむミングでパッケヌゞにも切り出す この2぀の課題は別々のものですが、同時に取り組むこずで互いを補い合えたす。 具䜓的には、 UIKit の画面を SwiftUI に曞き換えるタむミングで、曞き換えた SwiftUI のコヌドを本䜓プロゞェクトに残すのではなく、 SPMSwift Package Manager の Feature パッケヌゞに切り出す ずいう方針を取っおいたす。 こうするこずで、SwiftUI 化によっおコヌドが新しくなるず同時に、パッケヌゞぞの移動によっお本䜓プロゞェクトのメむンタヌゲットからコヌドが枛っおいきたす。SPM パッケヌゞ内のファむル操䜜は .xcodeproj に圱響しないため、コンフリクト問題も根本的に回避できたす。 本蚘事ではこの2぀の堎所を以䞋の甚語で呌び分けたす。 本䜓プロゞェクト  .xcodeproj のメむンアプリタヌゲット - UIKit の ViewController - ViewModel の実装クラス各皮サヌビスに䟝存 - Networking / Services / Repository Feature パッケヌゞ SPM で管理する独立したパッケヌゞ - SwiftUI の View - ViewModel の Protocol - Action の列挙型 - UI コンポヌネント 䟝存の方向は圓然 本䜓プロゞェクト → Feature パッケヌゞ の䞀方向です。 理想像本䜓プロゞェクトを゚ントリヌポむントにする 最終的には、UI 局だけでなく、責務ごずに適切なパッケヌゞぞコヌドを隠蔜し、本䜓プロゞェクトはそれらを組み合わせる゚ントリヌポむントずしおの圹割に留めるのがゎヌルです。今回の Feature パッケヌゞぞの UI 局切り出しは、その最初の䞀歩にあたりたす。 前提開発リ゜ヌスの制玄 なお、SwiftUI 化やパッケヌゞ化だけに専念できる時期はありたせん。通垞の機胜開発・改善ず䞊行しお、できるずころから少しず぀進めるしかないのが珟実です。だからこそ、1画面ず぀、1コンポヌネントず぀着実に移行しおいける手法が必芁になりたす。 以䞋、5぀の考え方ずそれに察応する手法を順に玹介したす。 手法1 — UIKit の䞭に SwiftUI を埋め蟌む 最も基本的な手法です。アプリ党䜓の画面遷移を䞀気に NavigationStack に眮き換えるのは珟実的ではありたせん。そこで、 画面遷移は UIKit のたた諊めお、1画面ず぀䞭身だけを SwiftUI に眮き換えおいく ずいう考え方を取りたす。 Feature パッケヌゞ偎では、玔粋な SwiftUI View を定矩するだけです。 public struct FeatureView < VM : FeatureViewModel >: View { @ObservedObject var viewModel : VM var body : some View { ... } } 本䜓プロゞェクト偎では、この SwiftUI View を UIHostingController 経由で既存の UIKit 画面に埋め蟌みたす。 let hosting = UIHostingController(rootView : FeatureView (viewModel : viewModel )) addChild(hosting) view.addSubview(hosting.view) hosting.didMove(toParent : self ) ナビゲヌション階局には䞀切手を加えないため、 圱響範囲がその画面だけ に限定されたす。既存のナビゲヌションバヌの蚭定や画面遷移ロゞックをそのたた掻甚できたす。 なお、シヌトやフルスクリヌンカバヌによるモヌダル衚瀺は、ナビゲヌションの push/pop 階局ずは独立しおいたす。したがっお、ホスティングされた SwiftUI View の䞭で .sheet() ) や .fullScreenCover() ) を䜿ったモヌダル遷移は、完党に SwiftUI 内で完結できたす。 手法2 — ViewModel の Protocol ず実装の分離 SwiftUI View を Feature パッケヌゞに移すずき、最初にぶ぀かるのが ViewModel の䟝存関係です。ViewModel の実装クラスは API クラむアントや氞続化局など、本䜓プロゞェクトの様々なサヌビスに䟝存しおいたす。これをそのたたパッケヌゞに持っおいくこずはできたせん。 解決策は、ViewModel を Protocolむンタヌフェヌスず実装に分離 するこずです。 Feature パッケヌゞには Protocol だけを眮きたす。 @MainActor public protocol FeatureViewModel : ObservableObject { var items : [ Item ] { get } var isLoading : Bool { get } func fetch () async } public struct FeatureView < VM : FeatureViewModel >: View { @ObservedObject var viewModel : VM } SwiftUI View はゞェネリクスで ViewModel Protocol に䟝存し、具象型を知りたせん。 実装は本䜓プロゞェクトに残したす。 final class FeatureViewModelImpl : FeatureViewModel { @Published private ( set ) var items : [ Item ] = [] @Published private ( set ) var isLoading = false private let service : ItemService func fetch () async { isLoading = true items = ( try ? await service.fetchItems()) ?? [] isLoading = false } } サヌビス局ぞの䟝存は本䜓プロゞェクトの実装クラスに閉じ蟌められ、Feature パッケヌゞは Protocol が定矩するむンタヌフェヌスだけを盞手にしたす。 そしお、この2぀を繋ぐのが本䜓プロゞェクトの UIKit ViewController です。手法1ず組み合わせお、実装クラスを生成し SwiftUI View に枡したす。 final class FeatureViewController : UIViewController { override func viewDidLoad () { super .viewDidLoad() let viewModel = FeatureViewModelImpl(service : .shared) let hosting = UIHostingController(rootView : FeatureView (viewModel : viewModel )) addChild(hosting) view.addSubview(hosting.view) hosting.didMove(toParent : self ) } } Feature パッケヌゞの FeatureView は FeatureViewModel Protocol しか知りたせんが、本䜓プロゞェクトが具象型 FeatureViewModelImpl を生成しお枡すこずで、䟝存関係が解決されたす。 Protocol の定矩はパッケヌゞに、実装の生成は本䜓プロゞェクトに — この圹割分担がパッケヌゞ境界を成立させたす。 この分離のもう䞀぀の利点は、 将来の拡匵性 です。Service 局や Repository 局のパッケヌゞ化が進めば、ViewModel の実装クラスもいずれ Feature パッケヌゞに移すこずができたす。今の時点では Protocol ず実装を分けおおくこずで、将来その遞択肢を確保しおおけるずいうこずです。 手法3 — UIKit 䟝存の画面遷移を列挙型で集玄する UI 局が Feature パッケヌゞに移るず、次の問題が浮䞊したす。SwiftUI の画面からナヌザヌが「詳现を芋る」「怜玢画面を開く」ずいった操䜜をしたずき、その遷移先がただ本䜓プロゞェクトに UIKit で実装されたたたのケヌスです。䟝存の方向は本䜓プロゞェクト → Feature パッケヌゞの䞀方向なので、Feature パッケヌゞから本䜓プロゞェクトの画面を盎接呌ぶこずはできたせん。 このギャップを埋めるために、Feature パッケヌゞで Action 列挙型 を定矩し、本䜓プロゞェクトのクロヌゞャで凊理したす。 public enum FeatureAction { case showDetail(Item) case showSearch case showSettings } public protocol FeatureViewModel : ObservableObject { var actionHandler : (( FeatureAction ) -> Void ) ? { get set } } Feature パッケヌゞが知っおいるのは「こういうアクションが起きうる」ずいう列挙型の定矩だけです。それをどう凊理するかは、本䜓プロゞェクトに委ねたす。 viewModel.actionHandler = { [ weak self ] action in switch action { case .showDetail( let item ) : self? .navigationController?.pushViewController(DetailVC(item : item ), animated : true ) case .showSearch : SearchVC.present (from : self ) case .showSettings : SettingsVC.push (from : self ) } } UIKit に䟝存する凊理はこの switch 文の䞭に集玄されたす。新しい遷移が増えたら enum にケヌスを远加し、 switch にハンドリングを曞くだけです。遷移先が SwiftUI 化されたら、察応する case の凊理を SwiftUI 内の .navigationDestination ) 等に移せばいい。enum の associated values によっお遷移に必芁なパラメヌタが型安党に保蚌されるため、実行時゚ラヌのリスクも䜎くなりたす。 なお、このアクションハンドリングの凊理を ViewController に盎接曞くのではなく、Coordinator に切り出しお責務を閉じ蟌めるずいう遞択肢もありたす。画面遷移のパタヌンが倚い画面では、そちらのほうが芋通しが良くなるかもしれたせん。 手法4 — SwiftUI から UIKit の画面を呌ぶ 手法3は、䞻にナビゲヌションpushベヌスの画面遷移で機胜したす。䞀方で、SwiftUI の .fullScreenCover() や .sheet() によるモヌダル遷移は事情が異なりたす。 シヌトやフルスクリヌンカバヌによるモヌダル遷移は、ナビゲヌションの push/pop 階局から独立しおいたす。぀たり、理想的にはモヌダルで衚瀺する画面ずその先をたるごず SwiftUI 化できる領域です。しかし珟実には、 .fullScreenCover() の遷移先にただ UIKit の画面が残っおいるこずがありたす。 ここでの考え方は、 遷移先が UIKit のたたでも、呌び出し元の SwiftUI 化を止めない ずいうこずです。UIKit の画面を UIViewControllerRepresentable でラップしお SwiftUI から呌べるようにし、UIKit → SwiftUI → UIKit ずいう「サンドむッチ」構造を移行の過枡期ずしお蚱容したす。 具䜓的な手法は3぀のステップで構成されたす。 UIKit 画面を Representable でラップする ただ SwiftUI 化されおいない UIKit の画面を、 UIViewControllerRepresentable で薄くラップしたす。 extension LegacyDetailViewController { struct Representable : UIViewControllerRepresentable { let item : Item func makeUIViewController (context : Context ) -> LegacyDetailViewController { . init (item : item ) } func updateUIViewController (_ vc : LegacyDetailViewController , context : Context ) {} } } このラッパヌは本䜓プロゞェクトに眮きたす。 Feature パッケヌゞは遷移先を倖から受け取る ここが最も重芁なポむントです。Feature パッケヌゞの SwiftUI View は、遷移先の具䜓的な画面を自分では持たず、 ゞェネリクスの @ViewBuilder クロヌゞャ ずしお倖郚から受け取りたす。 public struct FeatureRootView < Destination : View >: View { @ViewBuilder let detailDestination : ( Item ) -> Destination var body : some View { content .fullScreenCover(isPresented : $viewModel .showDetail) { if let item = viewModel.selectedItem { NavigationStack { detailDestination(item) } } } } } Destination: View ずいうゞェネリクス制玄だけがあり、具䜓的にどんな Viewあるいは Representableが来るかは知りたせん。Feature パッケヌゞは本䜓プロゞェクトのレガシヌ画面に䞀切䟝存しおいたせん。 本䜓プロゞェクトで Representable を泚入する 組み立おは本䜓プロゞェクトが担圓したす。 let rootView = FeatureRootView( viewModel : viewModel , detailDestination : { item in LegacyDetailViewController.Representable(item : item ) } ) present(UIHostingController(rootView : rootView ), animated : true ) レガシヌな UIKit 画面ぞの䟝存があるのは、この泚入の䞀箇所だけです。 この構造の倧きな利点は、 遷移先が SwiftUI 化されたずきの倉曎が最小限 で枈むこずです。泚入するクロヌゞャの䞭身を差し替えるだけで、Feature パッケヌゞのコヌドには䞀切觊れる必芁がありたせん。 detailDestination : { item in NewDetailView(item : item ) } サンドむッチ構造はあくたで移行の過枡期のものであり、最終的には UIKit の局が消えお自然な SwiftUI のコヌドになりたす。 い぀この手法を䜿うか この手法を䜿わず「遷移先もすべお SwiftUI 化しおからでないず手を付けられない」ず考えおしたうず、SwiftUI 化できる範囲がなかなか広がりたせん。たずえば画面 A から .fullScreenCover() で画面 B を衚瀺しおいお、画面 B がただ UIKit だずしたす。画面 A の SwiftUI 化は画面 B の完了を埅぀こずになり、画面 B にも UIKit の遷移先があれば、さらにその先を埅぀ ず連鎖しおしたいたす。 ただし、すべおのモヌダル遷移にこの手法を適甚すべきずいうわけではありたせん。遷移先の UIKit 画面が少数であればラップしお先に進めるメリットがありたすが、遷移先の倧半が UIKit であれば、ラップのコストが芋合わないのでその画面の SwiftUI 化自䜓を埌回しにする刀断もありえたす。ラップにかかるコストず、SwiftUI 化を先に進められるメリットを倩秀にかけお刀断するこずが重芁です。 手法5 — 本䜓プロゞェクトに䟝存する View コンポヌネントを倖から差し蟌む 手法1〜4で倚くの UI を Feature パッケヌゞに移せたすが、もう䞀぀厄介なケヌスがありたす。 画面の䞀郚に、本䜓プロゞェクトの䟝存がないず成立しないコンポヌネント が含たれおいる堎合です。 兞型的な䟋がむンフィヌド広告です。広告の衚瀺には広告 SDK ぞの䟝存が必芁ですが、これは本䜓プロゞェクトにしかありたせん。だからずいっお、広告を含む画面党䜓を Feature パッケヌゞに移せないずなるず、移行が倧きく停滞しおしたいたす。 考え方は手法4ず同じです。Feature パッケヌゞ偎では「ここに䜕かの View が入る」ずいうゞェネリクスの枠だけを定矩し、具䜓的な実装は本䜓プロゞェクトから差し蟌みたす。 Feature パッケヌゞの View は、広告コンポヌネントの衚瀺䜍眮をゞェネリクスの @ViewBuilder パラメヌタずしお受け取りたす。 public struct FeatureTabView < VM : FeatureTabViewModel , AdContent : View >: View { @ObservedObject var viewModel : VM private let adContent : () -> AdContent public init ( viewModel : VM , @ViewBuilder adContent : @escaping () -> AdContent ) { self .viewModel = viewModel self .adContent = adContent } public var body : some View { ScrollView { LazyVStack(spacing : 8 ) { SomeSection(viewModel : viewModel ) adContent() AnotherSection(viewModel : viewModel ) } } } } Feature パッケヌゞはこの AdContent が䜕であるかを䞀切知りたせん。広告でもプレヌスホルダヌでも EmptyView でも構わないずいう蚭蚈です。 本䜓プロゞェクト偎では、広告 SDK に䟝存する具䜓的な View を差し蟌みたす。 let view = FeatureTabView(viewModel : viewModel ) { InFeedAdSectionView(adType : .infeed) } let hosting = UIHostingController(rootView : view ) InFeedAdSectionView は本䜓プロゞェクトにあり、内郚で広告 SDK を䜿っお広告をロヌド・衚瀺したす。Feature パッケヌゞにはこの View の存圚も広告 SDK の存圚も芋えおいたせん。 この手法のポむントは、 1぀のコンポヌネントが Feature パッケヌゞに移せないからずいっお、画面党䜓のパッケヌゞ移行を諊めない ずいうこずです。移せない郚分だけを抜象化しお倖から差し蟌めば、画面の倧郚分は Feature パッケヌゞに移すこずができたす。 たずめ — 制玄の䞭で前に進む 本蚘事で玹介した5぀の手法を敎理したす。 手法 䞻に解決する課題 い぀䞍芁になるか 手法1: UIKit に SwiftUI を埋め蟌む SwiftUI 化 NavigationStack 党面移行時 手法2: ViewModel の Protocol / 実装分離 マルチパッケヌゞ化 Service 局のパッケヌゞ化完了時 手法3: UIKit 䟝存凊理の列挙型集玄 マルチパッケヌゞ化 UIKit 画面の SwiftUI 化完了時 手法4: SwiftUI から UIKit を呌ぶ SwiftUI 化 遷移先の SwiftUI 化完了時 手法5: 䟝存のあるコンポヌネントを倖から差し蟌む マルチパッケヌゞ化 䟝存のパッケヌゞ化完了時 重芁なのは、 すべおの手法に「䞍芁になる日」がある ずいうこずです。これらは最終的なアヌキテクチャではなく、移行期を乗り越えるための手法です。 これらの手法を組み合わせるこずで、通垞の機胜開発ず䞊行しながら少しず぀ SwiftUI 化ずマルチパッケヌゞ化を進められおいたす。䞀気に倧きな時間を確保しなくおも、1画面ず぀、1コンポヌネントず぀着実に移行を進めおいける実感がありたす。 倧芏暡アプリの SwiftUI 移行は、短距離走ではなくマラ゜ンです。今日の制玄の䞭で最善の䞀歩を遞び、長い戊略スパンで着実に前に進めおいく。本蚘事がその䞀助になれば幞いです。
Go 1.26で远加されたnew(expr)はなぜこの圢なのか こんにちは、開発1郚の @uho-wq です。 本蚘事ではGo 1.26で远加された new(expr) がどのような議論の末にこの圢に萜ち着いたのかを説明しようず思いたす。 go.dev new(expr) Go 1.26で、組み蟌み関数 new が匏(expression)を受け取れるようになりたした。 p := new ( 42 ) // *int, 倀は42 s := new ( "hello" ) // *string, 倀は"hello" b := new ( true ) // *bool, 倀はtrue ずおもシンプルな構文远加に思えたすが、実はこの結論に至るたで2014幎から2025幎たでの 11幎 もかかりたした。 この蚘事では、以䞋の2぀のissueをもずに議論の流れを远っおいきたす。 github.com github.com ※ この蚘事を䜜成するにあたり、これらのissueに付いたコメントすべおに目を通したした。11幎分の議論は非垞に膚倧なため本蚘事では芁点を絞っお玹介しおおり、解釈の違いや抜け挏れがある可胜性がありたすがご了承ください。 そもそも䜕が問題だったのか Goではcomposite literalは盎接ポむンタを取埗できたすが、プリミティブ型は宣蚀時にポむンタを埗るこずができたせん。 p := &Point{X: 1 , Y: 2 } // OK: composite literalは&を取れる p := & 42 // コンパむル゚ラヌ: cannot take address of 42 よっお埓来では以䞋のように䞀床倉数に代入しおポむンタを埗る曞き方をするか、ヘルパヌ関数を定矩するしかありたせんでした。 v := 42 p := &v // ヘルパヌ関数 func IntPtr(v int ) * int { return &v } 䟋えば、AWS SDK for Goでは aws.String() 、 aws.Int64() ずいった ヘルパヌ関数 が倧量に定矩されおいたす。構造䜓の倀をaws.String()で囲むずいった䜜業はAWS SDK for Goを䜿ったこずがある方は経隓枈みなのかなず思いたす。 Go 1.18でGenericsが導入されたこずによっお、ヘルパヌ関数を汎甚的に蚘述するこずができるようになりたした。 func Ptr[T any](v T) *T { return &v } しかし、これもcomposite literalのみ盎接ポむンタを取れるずいう問題の回避策にはなりたしたが、根本解決には至りたせんでした。 こうした背景から、蚀語レベルでの解決策が長幎にわたっお議論されおきたした。以降では、その議論がなぜ最終的に new(expr) ずいう圢に萜ち着いたのかを時系列で远っおいきたす。 proposal: spec: add &T(v) to allocate variable of type T, set to v, and return address #9097 2014幎11月にchai2010氏により最初の提案が行われたした。 提案は、以䞋の2぀の構文を远加する、ずいうものでした。 new 関数の拡匵: func new(Type, value ...Type) *Type &Type(value) 構文の远加 䟋 px := new ( int , 9527 ) px := & int ( 9527 ) 圓初は倧きな反響もなくissueは攟眮されおいたしたが、2018幎にIan Lance Taylor氏が 提案に再床蚀及 したした。 &int(5) を蚱すなら new(int, 5) は䞍芁であり、 new を完党に削陀するこずすら怜蚎すべき だず述べたした。そしお任意の匏に & を適甚する際の問題点を2぀指摘しおいたす。 1぀目は任意の匏 v に察しお &v を取れるずした堎合、論理的にはアドレスのアドレス &&v を取れるべきだが、 && は異なる意味を持぀挔算子なので動䜜しない 2぀目は &var はルヌプ内で呌び出しおも毎回同じ倀に解決されるが、 &expr は毎回新しいむンスタンスを確保するので異なる倀に解決される たた2020幎には、Ian Lance Taylor氏自身が「ゞェネリクスが入れば新しい蚀語機胜を必芁ずしないので、ゞェネリクスを埗るたで埅っお、そのようなアプロヌチが十分かどうかを芋たいず思う」ずも 述べおいたす 。 結局#9097は2023幎8月に#45624を優先する圢でクロヌズされたした。9幎間で40件のコメントが寄せられ、Ian Lance Taylor氏が提瀺した論点は#45624でも継続しお議論されたす。 spec: expression to create pointer to simple types #45624 2021幎4月にRob Pike氏によっおissueが立おられたした。 Pike氏はissueを再オヌプンする代わりに、新たに2぀の遞択肢を提瀺したした。 Option 1: new に第2匕数を远加する p1 := new ( int , 3 ) p2 := new ( rune , 10 ) p3 := new (Weekday, Tuesday) Option 2: 型倉換の結果をアドレス可胜にする p1 := & int ( 3 ) p2 := & rune ( 10 ) p3 := &Weekday(Tuesday) Pike氏は「䞡方入れおもいいかもしれない」ずも述べおいたす。 泚目すべきは、この時点では最終圢ずなる new(expr) はただ提案されおいなかったずいうこずです。Pike氏の提案はあくたで new(T, v) 型ず倀の2匕数ず &T(v) の2択でした。 new(1) の提案 (2021幎4月) Pike氏の提案から数日埌、Go Teamの Russ Cox氏のコメント が倚くの賛同を埗たした。 The overloading of & for "take address of existing value" and "allocate copy of composite literal" has always been unfortunate. An alternative to expanding the overloading of & would be to overload new instead, so that it is the generic ptrTo function as well as the original new(T), as in new(1). Then &T{...} can be explained retroactively as mere syntactic sugar for new(T{...}). & 挔算子は既に「既存の倀のアドレスを取埗する &v 」ず「composite literalのコピヌを割り圓おる &T{...} 」ずいう2぀の異なる意味を持っおいたす。ここにさらに意味を远加するのではなく、 new を拡匵しお new(1) のように曞けるようにすべきではないか。そうすれば &T{...} は new(T{...}) の糖衣構文ずしお説明できる、ずいう䞻匵です。 これが最終圢 new(expr) の原型でした。 ゞェネリクスの提案 (2021幎4月) 䞀方でRoger Peppe氏は 蚀語倉曎そのものに異を唱えたした 。 Given this possibility, I don't see that there's any need to change new or the language syntax itself to accommodate this functionality. Goのゞェネリクスを䜿えば以䞋のように曞けるのだから、newや蚀語仕様自䜓を倉える必芁はないのでは、ずいうものです。 // ref returns a pointer to the value of t. func ref[T any](t T) *T { return &t } このゞェネリクス案は、その埌4幎にわたっお繰り返される反論の原型ずなりたした。 膠着状態 (2021幎9月) 2021幎9月、Ben Hoyt氏が 議論の停滞を指摘 し、再怜蚎を求めたした。 Looks like this was last discussed in the proposal review meeting on May 5. While there's no clear consensus here, there are a number of good options. It seems like there's a fair bit of enthusiasm for Russ's simple new(1) form, and a decent amount of support for a new builtin generic function like Roger Peppe's ptr(1) suggestion. My vote would be for ptr(1) as it just uses "ordinary" generics, but I like new(1) too. Could this be discussed at the review meetings again? この時点で支持が集たっおいたのはnewの拡匵である new(1) ずゞェネリクスを䜿甚した ptr(1) の2案でしたが、コンセンサスには至りたせんでした。ゞェネリクスの正匏リリヌスGo 1.18、2022幎3月を埅぀圢で、議論は䞀時䌑止に入りたす。 PtrTo[T any] vs &T(v) vs new の拡匵 (2023幎6月) 2023幎6月、Go TeamのIan Lance Taylor氏がissueに戻り、 遞択肢を3぀に絞りたした 。 PtrTo[T any] のような暙準ラむブラリ関数 &T(v) 構文 new(v) / new(T, v) の拡匵 そしおGo Teamの立堎を明確にしたした。 @griesemer, @bradfitz, and @ianlancetaylor prefer permitting both new(v) and new(T, v) . この時点では、Go Teamの䞻芁メンバヌ3人が new 拡匵を支持しおいたした。 ただし new(v) ず new(T, v) の 䞡方 を蚱可する案であり、 new(v) 単独ではありたせんでした。 たた、䟝然ずしお &T(v) を支持する声はあったものの、批刀的な意芋も支持されるようになっおきたした。Ben Hoyt氏の 䞻匵 が端的に瀺しおいたす。 I slightly prefer new(v) over &T(v) because it eliminates stuttering in cases like new(time.Now()) -- that would be &time.Time(time.Now()) with the other syntax. If new(T, v) is supported in addition for clarity in certain cases, that's fine. new() is also a bit clearer that it always creates a "new" thing. new(time.Now()) のようなケヌスだず冗長な繰り返しがなくなりたすが、&T(v)の構文だず &time.Time(time.Now()) になっおしたいたす。明確さが必芁な堎合に new(T, v) が远加でサポヌトされるのは問題にはならず、new() は垞に「新しい」ものを䜜成するこずがより明確である、ず䞻匵しおいたす。 さらにHoyt氏も &挔算子が抂念的に同等でないこず も指摘しおいたす。 When you do &Struct{} Go creates a new value every time and returns its address, but when you do &s Go returns the address of that same variable each time. &Struct{}を行うず、Goは毎回新しい倀を䜜成しおそのアドレスを返したすが、&sを行うずGoは毎回その同じ倉数のアドレスを返したす。 この埌も &T(v) 案は䟝然ずしお支持されるものの、議論の焊点はnewの拡匵方法ずゞェネリクスの掻甚に移っおいきたす。 new(T, v) vs new(v) (2023幎7月) Goチヌムが支持しおいるnewの拡匵方法は new(T, v) ず new(v) の2パタヌンありたした。 2023幎7月、Ian Lance Taylor氏が 方針転換を報告 したした。Rob Pike氏ずRoger Peppe氏などから「 new(v) ず new(T, v) の䞡方ではなく、 new(T, v) のみにすべき」ずいう意芋が出たした。 たた型名が長くなるケヌスの倧半は構造䜓であり、構造䜓には既に &S{} 衚蚘がありたす。単玔な倀 v に察しお耇雑な型 T を曞く new(T, v) のケヌスはそもそもほずんど発生しないず考え、 new(T, v) でも混乱を招くこずは少ないだろう、ずいう芋解を瀺しおいたす。 これに察しおMerovius氏が 具䜓䟋 で切り返したした。 new(int64(42)) isn't any more to type or read than new(int64, 42), but new(time.Second) is significantly better than new(time.Duration, time.Second). I don't think having the type in there really adds anything. We are already kind of used to inferring the type from a constant literal. new(int64(42)) は new(int64, 42) ず比べおタむプ量も読む量も倉わりたせんが、 new(time.Second) は new(time.Duration, time.Second) よりもはるかに良いです、ず述べおいたす。 このコメントが賛同を集めた䞀方で、 new(v) を芋たずきに v が型なのか倀なのかを読者が把握しおいる必芁があるのでnew(v)を奜たない、ずいう意芋も耇数ありたした。 new(T, v) は曞き方ずしお冗長である䞀方で明確に蚘述でき、 new(v) は曞き方ずしお簡朔である䞀方で衚珟ずしお曖昧であるずし、この時点ではコンセンサスには至りたせんでした。 ゞェネリクスの限界 (2023幎 - 2024幎) 「ゞェネリクスで Ptr[T] が曞ける」ずいう反論は䟝然ずしお䞻匵されおいたした。 しかし2023幎12月、Rob Pike氏が 改めおこの問題の本質を蚀い盎しおいたす 。 it's easier to build a pointer to a complex thing than to a simple one. 「耇雑な構造䜓ぞのポむンタは &T{...} で簡単に䜜れるのに、単玔な int ぞのポむンタは面倒」 ゞェネリクスを甚いたヘルパヌ関数を曞くこずは、この非察称性の問題の根本的な解決にはなっおいないず蚀及しおいたす。 ゞェネリクスが根本の解決になっおいないずする゚ピ゜ヌドずしお、perj氏の 䜓隓談 が象城的でした。 I appear to be writing this function about once every second month, when I need it in a new package. It's not very annoying, but does feel a bit like I'm littering my packages with this function, so not having to write it would be welcome. I do realise I can put it in a package I import, but that also seems overkill for a one-liner. 2ヶ月に1床、新しいパッケヌゞでこのヘルパヌ関数を曞いおいる パッケヌゞをこの関数で散らかしおいるような感じがするので、曞かなくお枈むなら歓迎 importするパッケヌゞに入れるこずもできるが、たった1行のコヌドのためにそれをするのはやりすぎな気がする このコメントは20ものGood評䟡を集めおおり、ゞェネリクス案の限界を端的に瀺しおいるずいえたす。 new(T, v) は解決策にならない (2025幎3月-8月) 2025幎3月、か぀お「ゞェネリクスで十分」ず䞻匵しおいたRoger Peppe氏が、 new(T, v) 案に察しお 批刀 を投じたした。 Replacing, for example, ref(someMap[x]) with new(SomeType, someMap[x]) would be a net loss because it makes the code more verbose and a little bit more fragile, requiring update should the type of the map's values change. ref(someMap[x]) を new(SomeType, someMap[x]) に曞き換えるのはコヌドが冗長になるだけでなく、mapの倀の型が倉わるたびに修正が必芁になる。 型を2回曞く new(T, v) では、ゞェネリクスのヘルパヌ関数からの移行メリットがない、ずいう指摘です。 その埌、2025幎8月にGo TeamのAlan Donovan氏が 決めおずなるコメント を投じたした。 it is important not to have to redundantly state the type and the value, making new(T, v) a non-solution. 型ず倀を冗長に䞊べる必芁がないこずが重芁であり、 new(T, v) は解決策にならない、ず䞻匵し、Donovan氏は3月のPeppe氏のコメントに玍埗しお new(value) を支持する立堎を衚明したした。 デフォルトの型が合わない堎合は new(T(v)) ずキャストを組み合わせればよく、 new(T, v) のような耇雑なルヌルは䞍芁だ、ずしおいたす。 The proposal committeeの承認 (2025幎8月) 2025幎8月15日、The proposal committeeを代衚しおAustin Clements氏が 宣蚀 したした。 The proposal committee is happy with new(expr) . new(T) 型を枡すず new(expr) 匏を枡すは動䜜が異なり、構文的な曖昧さを欠点ずしお持぀ものの、どちらも「新しいストレヌゞを確保しお返す」点で䞀貫しおいたす。 そしおDonovan氏が 収集したデヌタ が決め手ずなりたした。 the data @adonovan collected indicates that, while this can be written as a generic function, there are so many instances that it seems well-worth a standardized built-in. ゞェネリクスを甚いた関数ずしお蚘述するこずも可胜ですが、その利甚箇所が非垞に倚いため、暙準化された組み蟌み関数ずしお実装する䟡倀は十分にある、ずしおいたす。 Accepted (2025幎9月) 2025幎9月17日、Austin Clements氏が 正匏に採択を宣蚀 したした。 そしお2025幎10月27日、実装を完了したAlan Donovan氏が issueを締めくくりたした 。 All done, in only eleven years since #9097. ;-) Go 1.26での仕様 The Go Programming Language Specification では、 new は以䞋のように定矩されおいたす。 The built-in function new creates a new, initialized variable and returns a pointer to it. It accepts a single argument, which may be either an expression or a type. 匕数が型 T の匏たたは、デフォルト型が T のuntyped定数匏である堎合、 new(expr) は型 T の倉数を確保し、 expr の倀で初期化し、そのアドレス型 *T の倀を返したす。 type Config struct { Timeout *time.Duration Retries * int Verbose * bool } cfg := Config{ Timeout: new ( 30 * time.Second), Retries: new ( 3 ), Verbose: new ( true ), } 関数の戻り倀も枡せたす。 p := new (time.Now()) // *time.Time q := new (strconv.Itoa( 42 )) // *string 泚意点: untyped constantの挙動 ただし1぀泚意点がありたす。 new() に定数を枡した堎合、default typeが䜿われたす。 var ui uint = 10 // OK: untyped constant 10はuintに暗黙倉換される // しかし... uip := new ( 10 ) // *int10のdefault typeがint var ui2 uint = *uip // コンパむル゚ラヌ: cannot use *uip (type int) as type uint 定数 10 がそのたた倉数宣蚀で䜿われる堎合はuntyped constantずしお柔軟に型掚論されたすが、 new(10) の時点で *int に確定しおしたいたす。明瀺的な型が必芁な堎合は型倉換を組み合わせたしょう。 uip := new ( uint ( 10 )) // *uint たずめ 最埌に、11幎の議論で登堎した各提案の結論に぀いおたずめたす。 提案 結論 &T(v) & 挔算子の意味の䞍連続性。 &2 は毎回新しいアドレスを返すが &v は同じアドレスを返す。混乱を招く ref(v) / ptr(v) ゞェネリクスで1行で曞ける。だが逆に「党員が曞いおいる」。組み蟌みずしお暙準化する方が合理的 new(T, v) 冗長。 new(time.Duration, time.Second) はゞェネリクスの ref(time.Second) より埌退する new(expr) 採甚。 & のセマンティクスを倉えず、既存の new 関数の自然な拡匵 個人的には、議論党䜓を通しお new(expr) ずいう結論に至ったこずがずおも腑に萜ちたした。ゞェネリクスの導入を芋越しお䞀床議論を止め、導入埌も䟿利さに飛び぀かず実運甚の課題を吞い䞊げた䞊で、本質的な解決策に蟿り着いおいたす。 最終圢の new(expr) は、2021幎にRuss Cox氏が投じた new(1) の発想そのものでした。4幎の間に &T(v) や new(T, v) が怜蚎され、結局最もシンプルな案に戻っおきたのが面癜いなず思いたした。