TECH PLAY

株式会社エブリー

株式会社エブリー の技術ブログ

410

Cursor✖️iOS開発 私はこうやってます はじめに こんにちは。開発部でiOSエンジニアをしている野口です。 皆さんAI開発においてエディターは何を使っていますか? 弊社ではCursorがエンジニア、PdM全員に配布されています。 iOS開発においてはXcodeを使用しますが、XcodeのAI対応が遅いです。(Xcode26のベータ版を試しましたが、個人的にはCursorには遠く及ばない感じだったのでしばらくはCursorにお世話になるかなと思っています。) iOSでもAI開発を進めるべく、以下の記事に影響を受けて、私のCursor × iOS開発方法を紹介します。 基本的な環境構築 まず、CursorでiOS開発を行うための基本的な環境を整えます。 1. 拡張のインストール Cursorの拡張機能から以下をインストールします。 Swift : Swift言語サポートのため (swiftlang.swift-vscode: https://github.com/swiftlang/vscode-swift) 。 SweetPad : コード補完の設定を生成するために使用します。 2. コード補完 コード補完のために、以下のコマンドで xcode-build-server をインストールします。 brew install xcode-build-server Cursorでコマンドパレットを開き(Cmd+Shift+P)、「SweetPad: Generate Build Server Config (buildServer.json)」を実行します。 実行したい対象のアプリ(例: SampleApp)を選択します。 選択すると以下のような buildServer.json ファイルが生成されます。 { " name ": " xcode build server ", " version ": " 0.2 ", " bspVersion ": " 2.0 ", " languages ": [ " c ", " cpp ", " objective-c ", " objective-cpp ", " swift " ] , " argv ": [ " /opt/homebrew/bin/xcode-build-server " ] , " workspace ": " /Users/username/Repository/SampleApp/SampleApp.xcodeproj/project.xcworkspace ", " build_root ": " /Users/username/Library/Developer/Xcode/DerivedData/SampleApp-fptwmkhevrascshgcosiyntwfinh ", " scheme ": " SampleApp ", " kind ": " xcode " } 生成されてからためしに、 VStack など、SwiftUIやUIKitのコードを入力してみて、サジェストされるか確認してみてください。サジェストされればコード補完の設定は完了です。 CursorでiOS開発する際の課題 基本的な環境構築が完了したところで、実際にCursorでiOS開発を進める際に直面する課題について説明します。 CursorでiOS開発を行う場合、以下が必要になります(ローカル環境での開発のみを想定してます) Swift言語, SwiftUIなどのツールの補完 ビルド&ラン その環境設定をサポートしてくれるのがSweetPadになります。Cursorの公式でもSweetPadの使用が紹介されています。 言語やツール補完に関してはSweetPadの設定で問題なく動作しますが、ビルド&ランをするとビルド時間がネックになっていました。 弊社アプリ「 ヘルシカ 」での計測ですが、1回あたり1分以上かかります。Xcodeビルドした場合、10~20秒ほどになります。 どうやら、SweetPadのビルドで使用されている xcodebuild と、Xcodeのビルドは異なるようでそれによってビルド時間に差があるようです。 この課題を解決するため、私は Cursorでのコーディング と Xcodeでのビルド を組み合わせるハイブリッドな開発スタイルを採用しています。 3. osascriptを使用したXcodeビルド連携の設定 ここからCursorからXcodeのビルドをスムーズに行うための設定を行います。 macOSの作業自動化ツールである osascript を活用します。 具体的には、Cursorが開いている状態で Cmd+R を押すと、以下の動作が自動で実行されるように設定します。 Xcodeを開く。 Xcodeでビルド&ランする (Cmd+Rを送信)。 Cursorに戻る。 設定手順は以下の通りです。 .vscode/tasks.jsonの作成 プロジェクト直下に .vscode フォルダを作成し、その中に tasks.json ファイルを作成します。このファイルに osascript を実行するタスクを設定します。 { " version ": " 2.0.0 ", " tasks ": [ { " label ": " xcode.run ", " type ": " shell ", " command ": " osascript ", " args ": [ " -e ", " tell application \" Xcode \" to activate ", " -e ", " tell application \" System Events \" to keystroke \" r \" using {command down} ", " -e ", " tell application \" Cursor \" to activate " , ] , " problemMatcher ": [] } , ] } 各コマンドの説明: "-e", "tell application \"Xcode\" to activate" :Xcodeを開く "-e", "tell application \"System Events\" to keystroke \"r\" using {command down}" :Cmd+R(ビルド&ラン)を押す "-e", "tell application \"Cursor\" to activate" :Cursorを開く この設定により、Xcodeのアクティベート、 Cmd+R によるビルド&ラン、そしてCursorへの戻りが自動化されます。 keybindings.jsonの設定 tasks.jsonに設定したタスクをショートカットで呼べるようにします。 Cursorを開いた状態で Cmd+Shift+P を押し、「キーボードショートカットを開く」を検索して keybindings.json を開きます。 そこに以下の設定を追加し、 Cmd+R で上記の xcode.run タスクが実行されるようにします。 [ { " key ": " cmd+r ", " command ": " workbench.action.tasks.runTask ", " args ": " xcode.run " } ] これで設定は完了です! Cmd+R を押すだけで、Xcodeが起動してビルド&ランされ、その後自動でCursorに戻るようになります。エディタの行き来の手間が大幅に削減されますね! 開発体験の向上:Cursorの見た目をXcodeに! ここまでの設定で基本的な開発環境は整いましたが、XcodeとCursorを行き来する際の見た目の違いが気になることがあります。 個人的な感覚ですが、Xcodeで編集した後にCursorで同じコードを見ると、なんだか別のコードを編集している感覚になってしまい、集中が途切れてしまうことがありました。 そこで、Cursorの見た目をXcodeに近づけるために拡張機能から Xcode Theme ( mateocerquetella.xcode-12-theme )を導入しました。 「 Xcode Theme 」を利用することで、DarkやLightなどの設定が可能になります。私は普段XcodeのDarkモードを使用しているので、それに一番近い Xcode Default (Dark Customized Version) を使用しています。 ぜひお好みのテーマを見つけてみてください。 実際の開発ワークフロー(2025年7月時点) 環境構築が完了したところで、私が実際に行っている開発ワークフローをご紹介します。上記の設定を活用することで、私は現在ほぼCursorで開発を行っています。 主にCursorとXcodeを以下のように使い分けています。 Cursor : Agentモードを活用し、コーディングのほとんどを行う Xcode : 主にビルドと実行や画像追加などの作業 開発の流れ 開発は、大きく「調査」「実装」「ビルド&ラン」の3つのフェーズで進めています。 1. 調査 Cursorの優れた検索機能を使って、おおよそのコードの場所を尋ね、どこに該当のコードがあるかを把握します。 「こんなコードありますか?」といった曖昧な質問でも、Cursorはかなりの情報を見つけ出してくれます。 2. 実装 実装したい機能のおおまかな設計方針を考え、それをCursorに投げかけます。 もし何も思いつかない場合は、とりあえず漠然としたアイデアでもCursorに投げてみることから始めます。 Cursorが意図通りに実装してくれれば、そのコードを取り込んで次のステップに進みます。 3. ビルド&ラン コードの編集が一段落したら、Cmd+RでXcodeビルド&ランをします ビルドエラーが発生した場合は、Xcodeで直接対応します(Cursor側にはエラーは表示されません)。 懸念点と今後の課題 ビルドエラーの表示について ビルドエラーについてですが、Cursor側でXcodeのログを収集し、エラーを表示できるように設定することも可能です。そのために、 XCLogParser を利用します。スクリプトを設定することでXcodeのログを収集できるようにします。スクリプトは以下の記事のもを参考に使用させていただいてます。 参考記事 しかし、私の環境ではエラーが解消されたにもかかわらず、Cursorのエラーが残り続けることがあり、これがストレスだったので、現在はこの機能の使用を中止しています。ビルドエラーはXcode側で確認して修正する運用に落ち着いています。 加筆(2025/10/02時点) ビルドエラーの表示についてこちらのブログで解消したので合わせて見ていただけると嬉しいです。 tech.every.tv まとめ 本記事では、CursorとXcodeを組み合わせたiOS開発手法について、環境構築から実際の開発ワークフローまでをご紹介しました。 AI開発が主流になる中で、XcodeのAI対応が追いついていない現状では、CursorのAI機能とXcodeの高速ビルドを組み合わせるのが最も効率的なアプローチだと感じています。 少し設定は必要ですが、今回ご紹介した方法で、あなたもCursorを活用した快適なiOS開発を始められるはずです!ぜひご自身の環境に合わせて試してみてください。
アバター
はじめに デリッシュキッチンでiOSアプリ開発を担当している池田です。近年のAI技術の進歩により、コーディングやデータ分析をはじめとする様々な業務での活用が進んでいます。今回、非エンジニアを含むチーム全体での勉強会において、AIを活用してレジュメを作成することで、効果的な学習体験を実現できました。 その取り組みと成果を共有します。 背景 私たちのチームでは毎週振り返りを行っており、その中で私が「ドメインモデルを意識できていないため、サーバーエンジニアとアプリエンジニア間のコミュニケーションコストが増加している」という課題を提起しました。 ドメインモデルはユビキタス言語の一部です。そのため、エンジニアだけでなくPdMやデザイナーも理解しておくべきだという結論に至りました。こうして、職種を超えてチーム全体での勉強会が実施されることとなりました。 従来の勉強会の課題 一般的な勉強会では、発表者が事前に資料を作成する必要があります。勉強会の準備のために業務時間を確保しなければならず、発表者にとって大きな負担となります。 また、資料の質が参加者の理解度に直結するため、内容や構成に細心の注意を払う必要があります。さらに参加者のスキルレベルに合わせた調整も求められます。このように、準備には多くの時間と労力が必要で、勉強会開催の大きなハードルとなっていました。 AIでレジュメ作成 自分が説明することの負担を考えると、問題提起はしたものの勉強会の実施には躊躇していました。しかし、PdMが1週間後に勉強会をセッティングしてくれただけでなく、AIを活用して詳細なレジュメまで作成してくれました。 このAI作成のレジュメがあったおかげで、私は資料作成や構成検討などの事前準備をほぼ行うことなく勉強会に臨むことができました。 実際に使用したレジュメの一部をご紹介します。 **ドメインモデル勉強会 レジュメ** **構成** 1. はじめに 2. ドメインモデルとは何か? 3. ドメインモデルの主な構成要素 4. ドメインモデルの表現方法 5. ドメインモデル構築のヒント 6. 質疑応答・ディスカッション 7. まとめ・今後の進め方 --- 以下、一部項目の詳細内容を抜粋 --- **2. ドメインモデルとは何か? (15分)** * **ドメインモデルの定義と重要性:** * **ドメインとは?**: ソフトウェアが対象とする業務領域や問題領域のこと。(例:ECサイトにおける商品管理、注文処理、顧客管理など) * **ドメインモデルとは?**: そのドメインにおける重要な概念、ルール、関係性を表現したもの。単なるデータ構造ではなく、振る舞い(ビジネスロジック)も含む。 * **重要性**: 複雑な業務ロジックを整理し、関係者間の認識齟齬を防ぎ、変更に強いソフトウェアを構築するための基礎となる。 * **なぜドメインモデルが必要なのか?(メリット):** * **コミュニケーションの円滑化**: 開発者、ビジネス担当者、顧客など、関係者全員が共通の言葉(ユビキタス言語)でドメインについて話せるようになる。 * **関心事の分離**: ドメイン固有のロジックを他の技術的な関心事(UI、DBアクセスなど)から分離することで、コードの見通しが良くなり、変更やテストが容易になる。 * **ビジネス価値の向上**: ドメインの本質的な課題解決に集中でき、ソフトウェアがビジネス要件を正確に反映しやすくなる。 * **再利用性の向上**: ドメインモデルは特定の技術に依存しにくいため、異なるコンテキストでの再利用が期待できる。 * **ドメイン駆動設計 (DDD) との関係(概要):** * ドメインモデルは、ドメイン駆動設計 (DDD) というソフトウェア設計手法の中核となる概念であることに触れる。 * DDDは、複雑なドメインの問題を解決するために、ドメインモデルを中心に据えた設計アプローチであると簡単に紹介する。(深入りはしない) このレジュメをもとに、勉強会は以下のような流れで進行しました。 勉強会の流れ 勉強会は以下のような役割分担で進行しました。 説明役(私):レジュメに沿って実例を交えながら説明 ファシリテータ(PdM):進行管理と議論の促進 参加者:質問や確認を通じて理解を深める(エンジニア、デザイナーを含む) 私はAIが作成したレジュメの各項目について、実際のプロジェクトでの具体例を挙げながら説明しました。レジュメがあることで、必要に応じて項目をスキップしたり詳しく掘り下げたりと、参加者の反応に合わせて柔軟に進行できました。参加者からは活発に質問や確認があり、双方向的な学習を実現できました。 所感 AIレジュメ活用のメリット 準備負担の大幅軽減 最大のメリットは、事前準備をほとんど必要とせず、負担なく実施できたことです。 適切なレベル設定の自動化 どの内容をどの程度のレベル感で話せば良いのかを考える必要がなく、私だけでなく参加者にとっても利点がありました。特に初学者にとっては、体系的に整理されたレジュメによって学習の道筋が明確になり、理解が深まりやすかったと感じます。 効果的な適用場面 実践経験はあるが体系化に不安があるトピック 例えば、アジャイル開発のメンバー経験はあるものの、全体像を説明する自信がない場合でも、AIレジュメにより体系的な知識を効果的に伝えることができます。 初学者を含む多様なレベルの参加者がいる場合 今回作成されたレジュメは基礎から応用まで体系的に構成されていたため、説明役は専門知識の整理に悩むことなく、参加者のレベルに合わせた実例の提供に集中できます。 議論重視の学習スタイルを採りたい場合 構成が事前に決まっているため、説明役は安心して議論の時間を多く取ることができ、建設的な意見交換が生まれやすくなります。 使い分けの重要性 もちろん、発表や資料作成が得意で自ら取り組みたい場合は、その方が熱意も伝わりやすいでしょう。しかし、発表が苦手だったり準備時間が確保できずに勉強会の開催を躊躇している場合は、このようなAI活用が有効な解決策になると感じました。 おわりに 今回は勉強会の負担軽減のためにAIを活用した事例を紹介しました。説明役の負担軽減だけでなく、参加者にとってもより意義のある学習体験を実現できました。 この手法は、特に「知識はあるが説明に自信がない」「準備時間が確保できない」といった課題を抱えている方に有効だと考えています。AIが提供する構造化されたレジュメをベースに、自分の経験談を織り交ぜることで、質の高い勉強会を実現できます。 もし勉強会の開催を検討されているなら、準備の負担を理由に諦める前に、AIを活用した新しいアプローチを試してみてはいかがでしょうか。
アバター
はじめに こんにちは。リテールハブ開発部の池です。 昨今、AI を活用したコードレビューの方法は日を追うごとに選択肢が増えているように思います。 GitHub Copilot - GitHub 上の PR に対して レビューコメントを自動生成 Claude Code - /review コマンドを使ったローカルレビューや、Claude Code Actions による GitHub 上の PR レビュー Cursor - .cursorrules ファイルでカスタムルールを定義してのローカルレビューや、Cursor BugBot による GitHub 上の PR レビュー このように、各ツールがローカルでのレビューと PR レビューの両方に対応しており、様々なレビューの方法があります。 今回は、2025 年 5 月に無料でリリースされた CodeRabbit VSCode 拡張機能を試してみました。PR レビューで有名な CodeRabbit ですが、ローカル環境でリアルタイムにコードレビューできるようになったということで、使い方を含め紹介できればと思います。 なお、今回の検証は Cursor を使って行いました。VSCode 拡張機能として提供されていますが、Cursor でも問題なく動作することを確認しています。 CodeRabbit とは CodeRabbit は、AI を活用した自動コードレビューツールです。元々は GitHub、GitLab、Bitbucket などと連携し、PR に対して自動的にレビューコメントを付けることで知られています。 これまでは主に有料会員に対する PR ベースのレビューが中心でしたが、2025 年 5 月に VSCode 拡張機能がリリースされ、ローカル開発環境でもその恩恵を受けられるようになりました。 詳しくは 公式ページ をご参照ください。 CodeRabbit VSCode 拡張機能 CodeRabbit VSCode 拡張機能は、ローカルの IDE 内でコードレビューを受けられる拡張機能です。この機能によって、PR を作成する前に問題を発見・修正できるため、開発効率の向上が期待できます。 主な特徴として以下があげられます。詳しくは 公式ブログ をご参照ください。 コンテキストを理解した高品質なレビュー 単純な文法チェックだけでなく、コードの文脈を理解して意味のあるレビューを提供 関数の使い方、変数の命名、コードの構造など、様々な観点から改善提案 リアルタイムレビュー 設定によってはコミット時に自動的にレビューが実行される PR を作成する前に品質を向上させることができ、レビュアーの負担も軽減 未コミットの変更に対してもレビューを実行可能 AI による修正提案 問題点を指摘するだけでなく、具体的な修正案も提示 「Fix with AI」ボタンをクリックすることで、Claude Code, Cline, Roo など AI エージェントと連携して自動修正も可能 幅広い言語サポート JavaScript、TypeScript、Python、Java、C#、Go、Ruby など、主要なプログラミング言語をサポート 言語に応じた適切なレビューを提供 料金 CodeRabbit の料金体系は以下です。 ※ 2025 年 7 月 8 日時点の情報なのでご注意ください。最新の料金情報は 公式料金ページ をご確認ください。各プランの詳細な利用制限については 公式 FAQ をご確認ください。 プラン比較 プラン 月額料金 VSCode 拡張機能レビュー回数 PR レビュー 主な機能 Free 無料 1 時間あたり 1 回 基本機能のみ ・PR 要約 ・無制限のリポジトリ ・14 日間の Pro 無料トライアル Lite $12/月(年間契約) $15/月(月次契約) 1 時間あたり 1 回 拡張機能 ・無制限の PR レビュー ・カスタマイズ可能な学習機能 ・リアルタイム Web 検索 ・コードグラフ分析 Pro $24/月(年間契約) $30/月(月次契約) 1 時間あたり 5 回 高度な機能 ・Lite の全機能 ・Linter/SAST ツール対応 ・Jira/Linear 連携 ・エージェントチャット ・分析ダッシュボード Enterprise カスタム価格 要相談 全機能 ・Pro の全機能 ・セルフホスティング対応 ・SLA サポート ・専任カスタマーサクセスマネージャー ・カスタムインテグレーション VSCode 拡張機能だけを使う分には無料なので、まずは気軽に試すことができるのが大きな魅力だと思います。ただし、無料プランでは 1 時間に 1 回という制限があるため、頻繁にレビューを実行したい場合は Pro 以上のプランを検討が必要になります。 試してみる 導入手順 1. 拡張機能のインストール VSCode(今回は Cursor)の拡張機能タブから「CodeRabbit」を検索してインストールします。 2. ログイン インストール後、ログインが必要です。GitHub、GitLab、Azure DevOps、Bitbucket のいずれかのアカウントでログインできます。 3. 設定(オプション) 必要に応じて以下の設定を調整できます。 Auto Review Mode : コミット時の自動レビューの有効/無効 AI Integration : Claude Code, Cline, Roo など AI エージェントとの連携設定 Review Timeout : レビューのタイムアウト時間(デフォルト 20 分) レビューの実行方法 CodeRabbit には 2 つのレビュー開始方法があります。 自動レビュー Git コミット後に自動的にダイアログが表示され、レビューを実行できます。 手動レビュー VSCode アクティビティバーの CodeRabbit アイコンをクリックして、以下の 3 つのモードから選択してレビューを実行できます。 Review uncommitted changes : 未コミットの変更のみをレビュー Review committed changes : コミット済みの変更のみをレビュー Review all changes : ベースブランチとの差分すべて(コミット済み+未コミット)をレビュー 詳細な使い方 設定へのアクセス CodeRabbit の設定は、サイドバーの右上にある歯車アイコンからアクセスできます。 Auto Review Mode 設定 コミット後の自動レビューの動作を制御。 Disabled : 自動レビューを完全に無効化 Prompt : コミット後に確認ダイアログを表示(デフォルト) Auto : 確認なしで自動的にレビューを開始 Agent Type(AI 統合)設定 「Fix with AI」機能で提案された修正を適用する方法を選択できます。 Native : VSCode の GitHub Copilot を使用(VSCode のみ対応) Claude Code : ターミナルで Claude コマンドを実行(Claude Code のインストールが必要) Codex CLI : Codex CLI ツールを使用 Cline : Cline 拡張機能を使用 Roo : Roo Code を使用 Kilo Code : Kilo Code 拡張機能を使用 Clipboard : 修正提案をクリップボードにコピー(手動で AI ツールに貼り付け) Review Timeout 設定 レビューのタイムアウト時間を分単位で設定できます。 デフォルトは 20 分ですが、0 に設定するとタイムアウトを無効化できます。 ベースブランチの選択 レビュー時に比較対象となるブランチを手動で選択できます。デフォルトでは現在のブランチの親ブランチが自動選択されますが、任意のローカルブランチを選択可能です。 サイドバーに現在のブランチと比較対象のブランチが表示され、クリックすることで変更できます。 レビューの実行状況 レビュー実行中は、サイドバーでリアルタイムに進捗を確認できます。 「Setting up」→「Analyzing changes」→「Reviewing files」の順に処理が進み、レビュー対象のファイルがリスト表示されます。 過去のレビュー履歴 サイドバーの「Previous reviews」セクションで、過去に実行したレビューの結果を確認できます。これにより、修正の進捗を追跡しやすくなります。 過去のレビューには「cancelled」などのステータスも表示され、レビューの完了状況を把握できます。 レビュー機能を試してみました 実際に CodeRabbit がどのようなレビューをしてくれるのか、具体例を見てみましょう。 例 1: 既存コードスタイルへの統一提案と最新情報を踏まえた提案 ECS タスク定義の JSON ファイルをレビューした際の結果です。 CodeRabbit は以下の 2 つの指摘をしています。 クォートスタイルの統一 ダブルクォートとシングルクォートが混在していることを指摘 既存のコンテナ定義がシングルクォートを使用しているため、それに合わせるべきという提案 Datadog Agent のバージョン更新提案 現在のバージョン 7.67.0 から 7.67.1 へのアップデートを提案 CVE-2025-4565 と CVE-2025-49128 の脆弱性修正が含まれていることを明示(※ CodeRabbit は特定の脆弱性修正を理由として挙げていますが、実際の修正内容については公式リリースノートで確認することを推奨します。今回の指摘内容は issue で報告されている内容でした。) 特に注目すべきは、セキュリティ脆弱性情報を踏まえたバージョン更新の提案です。単純な構文チェックだけでなく、セキュリティの観点からもレビューを提供していることがわかります。(注意: AI レビューツールの指摘については、常に公式情報源での確認が重要です。) また、バージョン 7.67.1 は 5 日前にリリースされたばかりの最新のバージョンだったのですが、そのような新しい情報を踏まえたレビューを行なっていることがわかります。 例 2: 複数ファイルにまたがる一貫性の確認 別のファイル(secrets.libsonnet)でも同様の指摘がありました。 プロジェクト全体の一貫性を保つため、同じ指摘であっても複数のファイルにまたがって同じ観点でレビューしてくれます。 例 3: レビューコメントを反映する機能 レビューコメントへの対応方法は 2 つあります。 Apply suggested change - 提案された変更を直接適用する場合 Fix with AI - AI ツールを使って対話的に修正する場合 「Apply suggested change」をクリックすると、CodeRabbit が提案した修正内容がそのまま適用されます。シンプルな修正の場合はこの方法が最も効率的です。一方、「Fix with AI」ボタンをクリックすると、選択した AI ツールで自動的に修正を適用できます。 Fix with AI による修正例 以下は、PHP コードでの冗長な例外処理に関する NITPICKS レビューの例です。 この NITPICKS レビューで「Fix with AI」ボタンをクリックすると、設定した AI ツール(この例では Claude Code)がターミナルで起動します。 Claude Code は問題を分析し、以下のような修正を提案します。 冗長な match 式の削除 デフォルト値が同じケースの簡略化 コードの可読性向上 私の環境では、CodeRabbit 拡張機能の設定画面で Agent Type 設定を「Claude Code」にしているため、ボタンをクリックするとターミナルで Claude コマンドが実行され、提案された修正を対話的に確認しながら適用できます。 例 4: レビューの種類別表示 CodeRabbit は指摘の種類によって異なるラベルと色で表示します。 赤い「Potential Issue」ラベル - 潜在的な問題を示すレビューです。上記の例では、より適切なメソッドの使用に関する指摘( save() より update() を使うべき)などが含まれています。 青い「Refactor Suggestion」ラベル - コードの改善提案を示すレビューです。この例では、PHP のメソッドに型ヒントを追加することで、型安全性と IDE 支援を向上させる提案がされています。 その他にも「Verification」という緑のラベルもありました。 所感 CodeRabbit VSCode 拡張機能を実際に使ってみて、以下の点が特に印象的でした。 良かった点 レビューの的確さ - 単純な構文エラーだけでなく、コードの一貫性や保守性の観点からもレビューしてくれる 最新情報への対応 - 5 日前にリリースされたばかりの最新バージョンに関する指摘もあり、最新情報を踏まえたレビューをしてくれた レスポンスの速さ - コミット後すぐにレビューを実行できる 無料で使える - これだけの機能が無料で使えるのは驚き 実際のパフォーマンス Laravel で 34 ファイル、合計 1,000〜2,000 行程度のコード変更をレビューした際の比較です。詳細は割愛しますが、内容は単純な CRUD の REST API 実装で、複雑なロジックを含まないような実装です。 ※ この結果はあくまで Laravel で単純な CRUD の REST API 実装で試した結果であり、参考程度にご覧ください。言語、プロジェクトの規模や複雑さなど、様々な要因によって結果は異なると思います。 CodeRabbit VSCode 拡張機能 レビュー時間:5〜10 分 レビューコメント数:約 40 個 同様の問題が複数箇所にある場合も、すべて個別にコメント セキュリティ観点のレビューコメントもあり GitHub Copilot(参考) レビュー時間:CodeRabbit より高速 レビューコメント数:2 個 REST に準拠した適切な HTTP ステータスコードの使用に関するコメント 複数ファイルにおける status の整合性に関するコメント 今回の例に関しては、レビューの速度については GitHub Copilot の方が早い一方で、CodeRabbit はより網羅的で細かなレビューを提供してくれました。 まとめ CodeRabbit VSCode 拡張機能は無料プランの場合回数制限がありますが、機能を試すことは可能なので、まずは使ってみることをおすすめします。手元で実装した内容をその場ですぐにレビューできるのはとても便利で、レビュー効率を高めることができると思います。 CodeRabbit は今後も機能拡張が予定されているようなので、さらなる進化に期待したいと思います。 参考文献 公式ドキュメント CodeRabbit VSCode Extension - Visual Studio Marketplace CodeRabbit 公式サイト CodeRabbit Documentation Use the VSCode extension | CodeRabbit Configure the VSCode extension | CodeRabbit FAQs | CodeRabbit
アバター
目次 はじめに 参加レポート Amazon の事例から学ぶ生成 AI の実践的な活用と実装アプローチ AI Agent時代のソフトウェア開発の型 〜Everything as Codeで叡智を伝える〜 AI によってシステム障害が増える!?〜AI エージェント時代だからこそ必要な、インシデントとの向き合い方〜 生成AI活用で見えてきた3つの課題~精度・セキュリティ・推進体制~ まとめ 最後に はじめに 2025年6月25日(水)、26日(木)の2日間に渡り、幕張メッセにて AWS Summit Japan 2025 が開催され、 弊社の開発本部からも4名のエンジニアが参加しました。 7月11日までの期間限定でオンデマンド配信も行われているので、ぜひ公式サイトをチェックしてみてください。 aws.amazon.com 当日は非常に多くの参加者が訪れ、会場は大変盛り上がっていました。 ブースでは、様々な企業が AWS サービスの活用事例を紹介したりミニセッションを開催したりしていて、どこへ行こうか迷ってしまうほどで、 イベントの盛り上がりが強く感じられました。 本参加レポートでは、弊社のエンジニアが参加したセッションの中から、 印象に残ったものをご紹介したいと思います。 メッセージボード ステージ風景 参加レポート Amazon の事例から学ぶ生成 AI の実践的な活用と実装アプローチ 発表者: 田原 慎也さん(アマゾン ウェブ サービス ジャパン合同会社) レポート: 塚田 開発本部開発1部デリッシュキッチン開発部の塚田です。 私からは「Amazon の事例から学ぶ生成 AI の実践的な活用と実装アプローチ」の発表内容をご紹介します。 このセッションではAmazon社がどのようにAIを活用しそして改善していたっかの事例も含めて紹介されるものでした。 まず、2023年はPoCを企業としては取り組んでいたが今ではどのようにAI(生成AI)を自身のプロダクト、システムに応用するかを考えていると全体感を話されていました。 弊社でもデリッシュAI(Blog最後に資料等のリンクがあります)をはじめとしたAI活用を社内外問わず進めているので、そういった状況に置かれているのは身に染みて感じています。 初めにAmazonが実際にどのようにAIを活用しているのか AI creative studio を例に様々なデモをもとに紹介いただきました。 一つの画像から 異なるコンセプトの画像を生成する 画像の調整をする 映像を作成する これらのことが実施でき利用ユーザは本来集中するべき作業に時間を費やすことができるようになると感じました 次に、Amazonの商品ページを例にどのようにAIを活用しているかの紹介がありました。 Image Generator というシステムの内容を構成図をもとに解説いただきました。 SageMakerAIを並列で活用する構成で生成させた成果物をSageMaker Ground Truthを活用して評価を行っているとのことでした。 ここで 責任あるAI の話がありました。 昨年のAWS Summitでも責任あるAIという言葉は出ていましたが、その考え方を取り入れながら実際のプロダクトを構築していることが感じられました。 発表中2023年当時と現在の生成AIで出力された画像(映像)をサンプルとして表示されましたが、 これを実現するためには既存の処理に必要な仕組みを追加することで実現できていました。 ただし、より複雑になっていくとデータサイエンティストとエンジニアとのコミュニケーションが発生していくことになりここを改善するために オリジナルのカスタムSDKを作ったとのことでした。 これを活用することでデータサイエンティストは内部の処理はラップされやりたいことに集中できるようになったとのことでした。 こういった技術を使い利用者への影響が生産性を高くすることができていると発言がありました。 弊社内でも積極的にAIを社内外で活用していき生産性を高めていくことはもちろんですが、 責任あるAI の意識も持ちながら開発していきたいと感じたセッションでした。 AI Agent時代のソフトウェア開発の型 〜Everything as Codeで叡智を伝える〜 発表者: ⾼野 賢司さん(アマゾン ウェブ サービス ジャパン合同会社) レポート: hond 開発本部開発1部デリシュキッチンAWGのhondです。 僕からは⾼野賢司さんによる「AI Agent時代のソフトウェア開発の型 〜Everything as Codeで叡智を伝える〜」の発表を紹介します。 AI Agent時代の現在までの変化を踏まえて、これからの時代の課題として「AIが意図した通りに動いてくれない」と「AI駆動開発のスキルや価値が周囲の⼈に伝わらない」の二点を挙げ、それらに対する解決策を提案されていました。 まず初めに「AIが意図した通りに動いてくれない」の解決策としてはタイトルにあるようにEverything as Codeが提案されていました。 Everything as CodeとはInfrastructure as Codeをはじめ、Observability as Code、Database as Codeなどのあらゆるものをコードとして管理することで変更追跡を可能にする手法です。詳細については AWS Well-Architected に記述されています。 これによって再現性と一貫性が担保されるだけでなく、AIが理解できる形式になっていることで人間しか知らないコンテキストをなくす効果があります。 次に「AI駆動開発のスキルや価値が周囲の⼈に伝わらない」のパートでは、そもそもソフトウェアでビジネス価値を提供するとはどういうことかを振り返りつつ、AI駆動開発で期待すべき効果を振り返り、解決策が提案されていました。 AI駆動開発とは一朝一夕で身につくものではなく、時間をかけてマスターして初めて価値に気づくものだと言及されていて、それはまさに「型」であると紹介されていました。 「型」については 「型」の再考 からの引用で 「型」に含まれる叡智は、学習者にとって当初は未知であり、学習過程で内在化することで把握できるものである。 と定義されていて、組織としては「共通認識の醸成」と「場の提供」を行うことで習得する環境を整えられると紹介されていました。 Everything as Codeのアプローチに関しては弊社でもAtlassian MCPを使うなどしてなるべくAIとのコンテキスト差を減らすアプローチを試していましたが、Everything as Codeほど全てをコードで管理するところまでは至っていなかったのでまだまだコードやドキュメントとして落とし込んでいく必要があると感じました。 組織としてのアプローチに関しては「AIツールを活用した開発効率化勉強会」として社内でハンズオンを含む勉強会を開始したのですが、そのアプローチが間違っていないことを再確認できました。しかし、「共通認識の醸成」については加速できておらず、個人の情報収集能力に頼っている部分があるのでより一層力を入れていく必要があると感じました。 AI によってシステム障害が増える!?〜AI エージェント時代だからこそ必要な、インシデントとの向き合い方〜 発表者: 草間 一人さん(PagerDuty株式会社 プロダクトエバンジェリスト) レポート: 庄司( ktanonymous ) 資料は こちら からご覧いただけます。 開発本部開発1部トモニテ開発部所属の庄司です。 私からは、PagerDuty さんの AI エージェント時代におけるインシデントとの向き合い方についての発表をご紹介します。 こちらのセッションでは、冒頭にエレガントパズル 1 の『障害のほとんどはデプロイによって引き起こされる』という一節を引用し、デプロイに着目した時に、AI エージェント時代においてどのようなことが起こり、どのように向き合っていくべきなのかについてお話しいただきました。 AI の発展に伴い開発スピードが向上すると同時に、成果物をリリースする頻度も増加します。 すなわち、AI の発展に伴いデプロイ回数が増加していき、同時に、障害の発生頻度も増加すると言えます。 これを踏まえて、AI によって増加するインシデントに対して、AI の利用を止めるのではなく、AI を活用しながらインシデント対応していくべきだと言及されました。 ここでは、AI によるインシデント対応の考え方として、以下の3つの分類が紹介されました。 全くの未知で新しいインシデント 部分的には理解できているインシデント 十分に理解できているインシデント セッション中では、理解度が高いものであるほど AI 主導での対応が可能になるとして、 十分に理解できているような現象に対しては完全に AI で対応してしまって良いのではないかと言及されました。 一方で、未知の現象に対しては、まだまだ AI だけでの対応は難しく、人間が主導して AI を補助役として利用していくのが良いと言及されました。 AI によって生産性を向上させることは非常に重要なことですが、 それに伴ってインシデントの発生が増加することはあくまでも自然なことであると捉えられる側面もあり、 「インシデント」という確実な対応が求められる領域だからこそ、適切な AI の活用がしっかり考えていきたいと感じました。 生成AI活用で見えてきた3つの課題~精度・セキュリティ・推進体制~ 発表者:村上 博哉さん(株式会社サーバーワークス カスタマーサクセス部 CS4課 課長) レポート: 新谷 開発本部開発1部デリッシュキッチン開発部所属の新谷です。 私からは、サーバーワークスさんの「生成 AI 活用で見えてきた 3 つの課題 ~精度・セキュリティ・推進体制~」のセッションから、AI活用における3つの課題と対策についてご紹介します。 このセッションでは、多くの企業が生成AI導入で直面する3つの主要な課題として、「精度」「セキュリティ」「推進体制」が挙げられました。 精度の課題 ハルシネーションは完全には避けられない現象であり、生成AIに100%の精度を期待すべきではないという前提が示されました。対策として以下が紹介されました。 temperature パラメータによる回答の一貫性と多様性の制御 「階層的チャンキング」などの技術を活用したRAGの高度化 階層的チャンキングでは、検索対象の情報だけでなく、その周辺情報もAIに渡すことで、より文脈に沿った回答生成が可能になります。 セキュリティの課題 リスクを体系的に整理することから始めることの重要性が強調されました。具体的なアプローチとして以下が有効だと説明されました。 「生成 AI セキュリティスコーピングマトリックス」による利用形態に応じた対策範囲の明確化 「OWASP Top 10 for LLM Applications」によるLLM特有のリスクの洗い出しと脆弱性の特定 推進体制の課題 多くの企業が直面する人材・ノウハウ不足に対して、情報収集と実践経験の両輪で解決する必要があると言及されました。 重要なのは「ゴール設定 → プロトタイピング → 評価」のサイクルを短期間で素早く回すこと 小さな成功体験を積み重ねながら組織全体のノウハウを蓄積していく 外部のPoC支援サービスを活用し、初期投資を抑えつつスタートを切る 弊社でも生成AIを活用したサービスを開発していますが、このセッションを通じて「技術検証だけでなく、KPIを設定した評価まで含めたサイクルを素早く回すことが成功の鍵」だと改めて実感しました。 弊社では生成AIを活用したサービスである「デリッシュAI」を開発しており、その詳細については以下の記事をご覧ください。 corp.every.tv tech.every.tv まとめ AWS Summit Japan 2025 では、AI エージェント時代のソフトウェア開発やインシデント対応、生成 AI 活用の現場課題など、最新の技術トレンドや実践的な知見が数多く共有されました。 AI 時代ならではの開発手法やインシデント対応の考え方、生成 AI 導入における精度・セキュリティ・推進体制の課題とその対策など、今後の開発現場で重要となる視点を多く得ることができました。 また、各セッションを通じて「AI を活用しながらも人間の知見や組織的な学びをどう蓄積・共有していくか」が共通のテーマとして捉えられるような印象を感じました。 今後もこうしたイベントを通じて、最新技術の動向をキャッチアップしつつ、現場での実践に活かしていきたいと感じました。 最後に エブリーでは、ともに働く仲間を募集しています。 テックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください! corp.every.tv 最後までお読みいただき、ありがとうございました! エレガントパズル エンジニアのマネジメントという難問にあなたはどう立ち向かうのか ↩
アバター
運用中のデータレイクアーキテクチャのストレージをS3 Tables へ移行するには 背景 前提条件 要件 S3 Tablesを導入したトランザクショナルデータレイクアーキテクチャの構築 既存のデータを移行する方法を模索 問題発生 解決策 並行運用の場合 新規データを直接配信する方法 まとめ 最後に  こんにちは、開発本部 開発2部 RetailHUB NetSuperグループに所属するホーク🦅アイ👁️です。🎋七夕🎋が近いですが皆さまいかがお過ごしでしょうか。 背景  最近我々のチームで管理しているAWSサービスにおいて本番環境のS3サービスが毎月右肩上がりにコスト上昇していることがわかりました。 aws s3api コマンドや、python SDKの get_cost_and_usage_with_resources APIを駆使してコスト増の原因を調査した結果、非常に多くのGetObjectが呼ばれていることがわかりBIツールで日々分析データを出すクエリにてパーティションが効いてない状態であると推定し、クエリ見直しを行っている最中です。  ところで、先月開催されたAWS Summit 2025にて「Amazon S3 によるデータレイク構築と最適化」という興味深いセッションがありました( 参考資料 )。Athenaで発行するクエリの最適化においてパーティションの課題解決策としてS3 Tablesを選択するという話が特に注目ポイントでした。  現状運用中のアーキテクチャはS3にそのまま保存しているデータをAthena経由で検索しているのですが、そのストレージをS3 Tablesに移行すればパーティションを気にすることなくBIツールでの重いクエリ発行を抑えることができ、S3コストを結果的に減らせるのではないか、という期待からS3 Tablesに即座にかつ簡単に移行できるのか実際に調査してみることにしました。 通常S3運用に関する過去記事: tech.every.tv 前提条件 現在運用中の通常S3を使用したデータレイクアーキテクチャを崩さない移行を考える 要件 Amazon S3 Tablesを使うこと Amazon Data Firehoseを使ってS3 TablesにINSERTできること Amazon Athenaを使ってS3 TablesにSELECTできること INSERTするデータは既存のS3データと同じであること S3 Tablesを導入したトランザクショナルデータレイクアーキテクチャの構築  データ集約管理において、通常のS3を使用したアーキテクチャをデータレイクアーキテクチャと呼びます。一方で、Apache IcebergサポートのS3 Tablesの特徴であるトランザクショナルを採用するアーキテクチャをトランザクショナルデータレイクアーキテクチャと呼びます(Amazon Redshiftのような従来のデータウェアハウスとの統合も兼ねる場合はさらにレイクハウスアーキテクチャという)。  今回の調査では、Terraformを使わずAWSコンソールだけで構築を試みました。手順自体は概ね以下のリンク記事を参照したり、公式DOCにある手順を参照にするだけで問題なく設定はできました。 aws.amazon.com docs.aws.amazon.com docs.aws.amazon.com  あとは、この参照記事が2025年3月時点をベースにしていて5月14日にData Firehoseがresource linkを使わなくても直接S3 Tables用のGlue CatalogをDestinationに指定できるようになったのでその部分は省略できます。また、参照記事のCLIベースの手順部分も全てコンソール上で行えるようになっています。  ただし、1点注意があって2025年7月現在、リソースリンクを使わない場合、新規作成はできますが、ConfigurationのEditをするとSaveするときになぜかdefaultのGlue Catalogを見てしまうバグがあり、設定変更を完遂できませんので設定変更をする可能性を残しておきたい場合は、依然としてリソースリンクを使った方法で作成してください。 # Saveしようとしたら出てきたエラーメッセージ Your Firehose stream was not updated Unable to update the Firehose stream XXXXX. Wait a few minutes and try again. If the problem persists, go to AWS Support Center . API response CatalogConfiguration cannot be updated. 編集前のS3 TablesのcatalogARN 編集画面上に表示されているcatalogARN 既存のデータを移行する方法を模索 問題発生  既存のS3ファイルをGetしてファイルを読み込んでS3 TablesにINSERTできるように成形し直してData Firehoseに渡すスクリプトをpython SDK put_record_batch で実装して試してみたところ、CLIでテストしたときにBase64してからJSON形式にしてリクエストして成功したので同じリクエストデータを成形して実行するとエラーでコケてしまいました。 # 成功したときのCLIコマンド例 aws firehose put-record \ --delivery-stream-name "$DELIVERY_STREAM_NAME" \ --record "{\"Data\":\"$BASE64_DATA\"}" # Destination error logs details " errorCode ":" Iceberg.InvalidPayload "," errorMessage ":" Delivery failed due to invalid input format . Make sure that the payload provided is in JSON Format ." 解決策  試行錯誤した結果、BASE64は不要のようでそのままPlainなJSON文字列を送ると上記のエラーが出なくなり、うまくINSERTされてAthenaでSELECTできました。  余談ですが、当初はData Firehoseに渡す時点でテーブルスキーマに存在しないカラム名(JSONオブジェクトキー名)が一つでもあるとうまくINSERTされなかったので使わないKey-Valueセットをリクエストデータから省く前処理を追加していたのですが、2025年7月現在は未定義のKey-Valueセットが付随しているJSONデータでも問題なくINSERTできていました。  これで後は移行タイミングで対象prefixの全オブジェクトに対してpythonスクリプトを実行すれば過去データの全移行も可能かと思います。 並行運用の場合  次に、既存の運用も継続したまま並行してS3 Tablesも導入して徐々に移行していくというのを想定してみます。導入後の新ログだけS3 TablesにもINSERTすることでそれ以降のログは見れるようにするという場合、先ほどのpythonスクリプトをLambdaにして指定のS3バケットのprefixにPUTされたタイミングをトリガーにセットしてオブジェクトをGetするようにすればうまくいきました。 新規データを直接配信する方法  まずは、単純にCloudWatch LogsのSubscription filtersのDestination ARNをS3 Tables用のData Firehose ARNに切り替えてパイプライン接続してみました。すると、当然のように以下のエラーでINSERT失敗してました。 # Destination error logs details " errorCode ":" Iceberg.InvalidPayload "," errorMessage ":" Delivery failed due to invalid input format . Make sure that the payload provided is in JSON Format ."  そこで、次にConfigurationのTransform recordsでLambdaで生ログを加工してデータを与える必要があるのでその設定を編集してSaveしようとしたら今度は以下のエラーが出てしまいました。 2 validation errors detected: Value at 'icebergDestinationUpdate.catalogConfiguration.catalogARN' failed to satisfy constraint: Member must have length greater than or equal to 1; Value at 'icebergDestinationUpdate.catalogConfiguration.catalogARN' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:.*:glue:.*:\d{12}:catalog(?:(/[a-z0-9_-]+){1,2})?  どうやら既存のData Firehoseをコンソール上から設定追加するのは現状だと不可能のようです。。  次に新しくData Firehoseを作成し直してそこであらかじめ用意したLambda Functionをセットしておきました。すると、今後はLambdaにInvokeFunctionの許可を設定していないというエラーが出てしまいました。 " errorCode ":" Lambda.InvokeAccessDenied "," errorMessage ":" Access was denied . Ensure that the access policy allows access to the Lambda function ."  そこでData Firehoseに割り当てているIAMポリシーを編集してLambdaのInvoke権限を追加しました(前述の公式DOC参照)。そうするとlambda実行自体は成功したのですが、今度はLambdaの処理がエラーになってしまいました。 " errorCode ":" Lambda.FunctionError "," errorMessage ":" Check your function and make sure the output is in required format . In addition to that , make sure the processed records contain valid result status of Dropped , Ok , or ProcessingFailed "  試行錯誤の結果、Data Firehoseにreturnで返す時のJSON文字列にする場合は、またdataキーの値がBase64エンコーディングされていないといけないということなのでそれを対応してようやくINSERTでき、AthenaでもSELECTできました! まとめ  コンソール上での環境構築は完成できたのでS3からS3 Tablesへの移行はできることがわかりました。今後は、移行後に実際どれだけBIツールでのクエリ発行によるコスト削減に貢献したかを検討予定です。また、Terraformを使って新規プロダクト開発環境での構築を目指す予定です。  ちなみに、今回の調査で実際に半月ほどS3 Tablesにデータを流し続けていましたがBillsを見てみるとCompaction関連(コンパクションを行ったオブジェクトの数に対してのコストと圧縮処理を実行する(裏で密かにLambdaが走る?)コストの2種類)のコストも掛かっているのに気づきました。PUTのコストは通常のS3と同様でしたが、追加でCompaction関連のコストも嵩むのは注意が必要かもです。 Compacted Objects Cost Processed Bytes Cost 最後に エブリーでは、ともに働く仲間を募集しています。 テックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください! corp.every.tv
アバター
Swift 6から本格的に導入された Strict Concurrency Checking は、アプリの安定性を飛躍的に向上させる一方、既存のコードの移行や、並行処理を初めて学ぶ開発者にとってはハードルが高いという課題がありました。 この課題に対応するため、Swift 6.2では「Approachable Concurrency」というビジョンが掲げられ、その中核機能として「Default Actor Isolation」が導入されました。 参考資料 この記事はWWDC 2025の以下のセッションを参考にしています。 Swiftの並行処理の活用 Code Along:Swiftの並行処理によるアプリの強化 Approachable Concurrencyとは? これは、開発者がデータ競合のリスクを最小限に抑えつつ、必要な時にだけ段階的に並行処理を導入できるようにするための、Swiftの設計思想や機能群全体を指す言葉です。 このアプローチは、以下の3つのフェーズを想定しています。 シンプルなシングルスレッドのコードを書く : 並行処理について明示的に指定しない限り、全ての処理がメインスレッドで動作するコードになります。 データ競合のない非同期コードを書く : UIをブロックしてアプリが固まるなど問題があれば、async/awaitなどを使い、安全に非同期処理を導入します。 並列処理でパフォーマンスを向上させる : アプリのパフォーマンスをさらに高める必要が出てきたら、TaskGroupやasync letなどを使い、複数の処理を並列で実行します。 並行性を段階的に導入することで、すべての開発者が最初から複雑な並行処理について深く理解する必要がないようにしています。 Default Actor Isolationとは? これは「Approachable Concurrency」を実現するための新機能で、Xcode 26から作成する新規プロジェクトではデフォルトで有効になります。 概要 : この設定が有効な場合、コードの大部分がデフォルトでメインアクタ @MainActor 上で実行されるように隔離されます。これにより、UIの更新などメインスレッドで行うべき処理が、意図せずバックグラウンドスレッドからアクセスされてクラッシュする、といった典型的なデータ競合を防ぎます。 既存プロジェクトでの設定 : 既存のプロジェクトでは、Xcodeのビルド設定から Swift Compiler - Language にある Default Actor Isolation の項目を MainActor に変更することで、この機能を有効にできます。 オプトアウト(分離の方法) : もちろん、重い処理をバックグラウンドで実行したいケースはあります。その場合は、関数や型に対して nonisolated キーワードや、Swift 6.2で導入された @concurrent 属性を明示的に指定することで、メインアクタから処理を切り離し、バックグラウンドで実行させることができます。 Swift 6.2での主な変更点 Default Actor Isolationをサポートするために、async関数の振る舞いがより直感的になるよう、いくつかの重要な変更が加えられました。 nonisolated async関数の振る舞いの変更 Swift 6.1まで : nonisolated な非同期関数は、呼び出し元のアクタに関わらず、常にバックグラウンドのスレッドプールで実行されていました。 Swift 6.2から : デフォルトで、呼び出し元の実行コンテキスト(アクター)を継承するようになります。つまり、 @MainActor から呼び出せば、その nonisolated async 関数も @MainActor 上で実行されます。これにより、意図しないスレッドの切り替えが減り、コードの振る舞いが予測しやすくなります。 新しい属性 @concurrent の導入 「 nonisolated だけど、この処理は意図的にバックグラウンドで実行したい」という場合は、新しく導入された @concurrent 属性を関数に付与します。 これにより、Swift 6.1までの「常にバックグラウンドで実行する」という振る舞いを明示的に選択できます。 既存プロジェクトのSwift 6移行 Swift 5で開発中のプロジェクトをSwift 6に移行する場合、Swift 6.2をターゲットにすることで移行が簡単になる可能性があります。 移行プロジェクトの場合、 Approachable Concurrency = No 、 Default Actor Isolation = nonisolated が選択されています。 これを、 Approachable Concurrency = Yes 、 Default Actor Isolation = MainActor に変更することで、新規プロジェクトと同様に段階的に並行処理を導入できるようになります。 既存の考え方は、並行処理を基本としつつメインアクターへの隔離や、データ競合を避けるための制約や属性を付与することによって Strict Concurrency Checking に適合させるアプローチでした。 新しい考え方では、メインアクターでのシングルスレッド処理を基本としながら、必要な箇所にのみ段階的に並行処理を導入するアプローチに変わります。Approachable Concurrencyを採用することで、既存のコードの多くをそのまま活用しながら、段階的に並行処理を導入し、安全にパフォーマンスを向上させることができる可能性があります。 まとめ Swift 6.2のDefault Actor IsolationとApproachable Concurrencyは、並行処理をより安全で、学習しやすくするための変更です。デフォルトでメインスレッドに処理を限定することで開発者をデータ競合から守りつつ、 @concurrent のような新しい制御方法を提供することで、必要な箇所では意図的にパフォーマンスを最適化する手段を提供しています。 弊社で開発している「トモニテ」アプリは現在Swift 5で実装されており、できるだけ早くSwift 6に移行したいと考えています。しかし、Strict Concurrency Checking への対応が課題になっています。 そこで、Swift 6.2をターゲットとし、Approachable Concurrencyのアプローチを採用することで、従来よりも少ない変更量で安全に Strict Concurrency Checking に対応できる可能性があると考えています。今後、この手法を用いた移行の検証を行う予定です。
アバター
ヘルシカ、デリッシュキッチンで Android アプリの開発を担当している岡田です。 時代の流れは早いもので、日々の開発業務で AI のサポートを受けることが当たり前になってきましたね。 今回は Android Studio Narwhal Feature Drop Canary 4 以降に Android Studio Gemini の Agent Mode がついに追加されましたので、 Gemini の rules 設定と合わせて見ていきたいと思います。 この記事が、皆さんの開発体験を向上させる一助となれば幸いです。 Agent Mode とは 機能単位での実装 高度なリファクタリング 複雑なエラーの根本解決 Agent Mode を使った実装の例 rules 設定について rules の Scope について IDE 設定(グローバルルール) Project 設定(Project 固有ルール) 各設定の優先度 例) IDE 設定と Project 設定の優先度 rulesに何を書くべきか IDE 設定の rules Project 設定の rules まとめ Agent Mode とは まず、今回の目玉機能である「 Agent Mode 」から詳しく見ていきましょう。 これまでの Android Studio Gemini は、私たちが書いたコードの一部を選択して「この処理をリファクタリングして」と依頼したり、 チャットウィンドウで「〇〇の実装方法を教えて」と質問したりする、いわば「指示待ち」の優秀なアシスタントでした。 この機能は Chat Mode として残っています。 新しく登場した Agent Mode は、その名の通り「エージェント(代理人)」として、より能動的かつ自律的に動作します。 Google Developers 公式ドキュメントでは、以下の通りに紹介されています。 developer.android.com Android Studio のエージェント モードの Gemini は、Gemini とのチャットだけではできない、複雑なマルチステージ開発タスクを処理するように設計されています。 大まかな目標を記述すると、エージェントが計画を作成して実行し、必要なツールを呼び出して複数のファイルに変更を加え、バグを反復的に修正します。 このエージェント支援ワークフローにより、複雑な課題に取り組んで開発プロセスを加速できます。 複数回のやり取りを通じて、Project 全体の構造やコードベース、依存関係、さらには設計思想まで深くコンテキストを理解し、ファイルやモジュールを横断して、より文脈に沿った的確なサポートを提供してくれるのです。 これまで複数の手順に分かれていた複雑なタスクを、一度の指示で実行できるようになります。 機能単位での実装 「アプリのホーム画面に、トピックのリストに移動する "フォロー" という新しいボタンを追加して」といった自然言語での指示から、必要な ViewModel、Composable 関数、Navigation の設定、さらには Unit Test の雛形まで、複数のファイルにまたがって生成・修正を提案してくれます。 高度なリファクタリング 「 <composable name> コンポーザブルで、 <modifier name> 修飾子のパディングを減らしてください」といったリファクタリング作業も、コード差分を明示しながら行なってくれます。 複雑なエラーの根本解決 「Project のビルドエラーを修正」などのプロンプトを使用してエージェントにビルドエラーの修正を依頼すると、推奨される修正が適用され、Project がビルドされてソリューションが検証され、問題が解決するまで反復処理されます。 根本原因を推測し、より堅牢な設計への改善案まで提示してくれます。 他にも Google Developers 公式ドキュメントにて、ユースケースについて記載されていますので、合わせてご覧いただけると幸いです。 developer.android.com Agent Mode を使った実装の例 例として、テスト用の Project へカレンダー画面を作成してみましたので、その記録を以下に示します。 まずは Agent Mode を選択し、カレンダー画面を作成する旨を伝えます。 この時、 @MainActivity と記載することで、明示的に MainActivity をコンテキストとして指定しています。 Gemini が CalendarScreen.kt というファイルに CalendarScreen という名の Composable 関数の作成案を提示してくれました。 ここで左右の矢印アイコン、Open in Diff View を選択すると、Diff View を見ることが可能です。 選択肢には Accept Change , Reject Change , Auto-Approve の 3 つがあります。 Accept Change はこの提示された変更を許可します。今回の場合は Diff View で明示された変更が実行されます。 Reject Change はこの提示された変更を拒否します。 Auto-Approve はこの後に提示される変更を自動許可します。変更履歴は Gemini に常に表示されますが、ユーザの確認無しに一気に作業が実行されます。 今回は Auto-Approve を選択しました。その後、数回のエラー・警告解消のやり取りを通して出力されたものが以下になります。 デザインを見ると、確からしいものが出来ています。 正直人間がコードを見るとリファクタしたくなると思いますが、0-1 を行ってくれるのは大幅な作業短縮になる場合があります。 また今後、コーディング作業の大半を Gemini に任せるのであれば、もしかしたらリファクタリングは必要ないのかもしれません。 これはあくまで個人の感想ですが、AI にコーディングを任せる上で、「ある程度のブラックボックス化されたコードを黙認すること」に人間が慣れなければいけないのかもしれないなと思っています。 まだ全てを Agent に任せることは難しいですが、簡単なリファクタなどから仕事を任せてみてはいかがでしょうか。 rules 設定について Agent Mode の能力を最大限に引き出すために不可欠なのが、 rules (ルール) の設定です。 これまでは、Gemini に対して毎回「日本語で回答して」、「Compose で書いて」などといった前提条件を伝える必要があり手間でした。 しかしこの rules を設定することで、前提条件をコンテキストとして Gemini に恒久的に教え込めます。 この機能は Android Studio の Settings > Tools > Gemini > Prompt Library から設定できます。 rules の Scope について Gemini の rules は、IDE 全体に適用される設定と、特定の Project のみに適用される設定の 2 つのレベルで Scope を管理できます。 IDE 設定(グローバルルール) どの Project を開いても適用される、開発者個人の好みや、組織全体で共有したい普遍的なルールを設定します。 Project 設定(Project 固有ルール) その Project に特化したルールや制約、背景情報を定義します。チーム開発での利用が主目的です。 各設定の優先度 基本的には、Project 固有のルールが IDE のグローバルルールよりも優先されます。以下はその例です。 例) IDE 設定と Project 設定の優先度 IDE 設定の rules のみに「Always respond in 日本語 with utf-8 encoding.」と記述した場合、日本語で出力してくれます。 上記に加え、Project 設定の rules に「Always respond in English.」と記述した場合の出力は英語となります。 rules に何を書くべきか IDE 設定の rules 基本的には Project 設定の rules が優先されるため、本当に汎用的なものを記載するのが良いと思います。 「Always respond in 日本語 with utf-8 encoding.」 Project 設定の rules Project 設定の rules には、Project の憲法となるような、具体的で明確な指示を記述することになると思います。 1. コーディング規約・スタイルガイド 「コメントは日本語で記述すること」 「Jetpack Compose の UI は、Google の公式マテリアル3デザインガイドラインに従うこと」 「マジックナンバー(説明のない数値リテラル)は使用せず、 companion object 内で定数として定義すること」 2. 技術スタック(使用ライブラリやアーキテクチャ)の指定 「アーキテクチャは MVVM を採用する」 「状態管理は ViewModel と StateFlow を使用する。LiveData は新規に採用しない」 「DI(Dependency Injection)には Hilt を使用する。Koin は使用しない」 「非同期処理は Kotlin Coroutines を使い、RxJava は使用しない」 「画像読み込みには Coil ライブラリを使用する」 3. Project の背景や目的 「このアプリは、IT 初心者向けの学習アプリです。コメントやドキュメントは、専門用語を避け、平易な言葉で記述してください」 「ターゲットユーザーは高齢者です。そのため、フォントサイズは大きめに設定し、クリック可能な領域も広く取るようなUIを心がけてください」 4. 禁止事項やアンチパターン 「Activity や Fragment 内で直接ネットワーク通信や DB アクセスを行わず、必ず Repository 層を介して行うこと」 「XML レイアウトでは findViewById は使用せず、常に ViewBinding を使用すること」 「Composable 関数内で重い処理を実行しないこと。必要であれば rememberCoroutineScope や副作用 API を適切に使用する」 これらのルールは、使用感や Project の成長に合わせてチームで議論し、変更していくことが重要だと思います。 例えば、「Gemini を使用している中で MVP アーキテクチャで回答が提示されたら、ルールに "アーキテクチャは MVVM を採用する" を追記する」といったフローで改善してくことになると思います。 弊社も rules は試行錯誤中ですが、記載内容を更新する度に求める回答に近づいている良い感覚があります。 まとめ 今回は Android Studioに搭載された Agent Mode と rules 設定について紹介させていただきました。 まずは最新の Android Studio Preview を導入し、簡単な rules からでも設定していただけると幸いです。 Gemini を「優秀なアシスタント」から「信頼できる開発パートナー」へと育て上げ、次世代の開発体験を楽しみましょう!
アバター
タイトル 株式会社エブリーでCTOを務めている今井( @imakei )です。 今回は、弊社で2ヶ月前に導入したCursorの成果についてお話しします。 結論から言うと、Pull Request数が2倍に増加するという、予想を上回る成果が出ています。 Cursorの導入とその背景 弊社エブリーは「明るい変化の積み重なる暮らしを、誰にでも」をパーパスに掲げ、レシピ動画メディア「デリッシュキッチン」などのサービスを展開しています。AIファーストカンパニーとして、プロダクトでのAI活用はもちろん、開発現場でもAIの実用的な活用を進めています。 その中で、下記のような価値実現を目指し、Cursorを導入しました。 本質的な価値創造への集中 AIによる高度なコーディング支援によって実装にかかる時間を短縮することで、エンジニアおよびPdMがより創造的で本質的な業務に注力できる環境を構築。より早く高品質なサービスが提供できるようになることで、ユーザー体験の向上を加速する。 プロダクト開発に関わる業務全体の生産性向上 エブリー開発部では、開発生産性10倍を目標としており、「Cursor」の導入でこれら全ての業務における効率化を促進し、組織全体の生産性向上を図る。開発生産性とは、単にプログラムを実装するだけでなく、機能のアイデア創出、企画立案、仕様策定、テストなど多岐にわたる業務を含む、プロダクト開発全般に対する生産性の向上を目指す。 AI活用を前提とした組織文化の醸成とイノベーションの推進 今後、あらゆるプロダクトにおいてAIの活用が不可欠になると予測される中、エブリーでは、日々の業務でAIに触れる機会を創出することで、AIを当たり前に使いこなす組織文化を醸成。これにより、AIを活用した新たな価値創造を主導し、イノベーションを推進する。 数値が示す明確な変化:Pull Request数が2倍に増加 導入から2ヶ月が経過し、GitHubにて1人あたりのPull Request数が 約2倍 に増加しました。 エンジニア一人当たりのPR数の推移 上記は、エンジニア一人当たりのPR数ですが、Cursor導入以降、かなりの勢いでPR数が増えてるのがわかると思います。 細かい部分でCursorのrulesのためのPR数も含まれていたり、6月以降はDevinも導入していたりするので、 PRの種類や質など、厳密には比較できない部分もありますが、それを差し引いても大きく伸びたと言えそうです。 この数字の背景を自分なりに分析してみました。 1. 純粋な開発速度の向上. 「30分かかっていたユニットテストの作成が5分で完了するようになった」 このような声を頻繁に聞くようになりました。 決まったタスクについては、AIによって開発速度が大幅に向上したといえます。 2. AIを活用していくという雰囲気・文化の醸成 会社としてCursorの導入を決めたことで、AIを活用していく雰囲気が組織全体に醸成されています。 今までは、AIを活用していても個人レベルにとどまっていましたが、Cursor導入後は積極的に知見を共有するようになり、AI活用のレベルが組織全体で少しずつ向上してきました。 また最近では、有志でAI活用の勉強会を開催するなど、AI活用がより活発になってきています。 3. ドキュメントの整備 AI活用を進める中で予想外の効果として、ドキュメントの整備が進んでいます。 AIと効果的に協働するには、暗黙知になっていたコンテキストをいかにAIに伝えるかが重要になってきます。それに気づいたチームから、自発的にドキュメントの整備が始まっています。プロジェクトの背景、設計意図、実装の詳細など、これまで口頭で共有されていた情報のドキュメント化が進んでいます。 これは単にAI活用のためだけでなく、新メンバーのオンボーディング改善にもつながっています。AIに説明できるレベルで文書化することで、人間にとってもわかりやすいドキュメントが生まれたのです。 開発生産性10倍を目指して 現在、私たちは「開発生産性10倍」という野心的な目標を掲げています。これはコーディングアシストだけでなく、要件定義や仕様策定といった上流工程にもAIを活用することで実現しようとしています。 今後は、以下のような領域からまずはAI活用を進めていく予定です: 要件定義・仕様策定 :ユーザーストーリーの整理や仕様書の下書き作成 テスト設計 :テストケースの網羅的な洗い出しと自動生成 プロジェクト管理 :工数見積もりやリスク分析の支援 コードレビュー :より高度な設計レビューとベストプラクティス提案 「開発生産性10倍」は決して簡単な目標ではありませんが、AI活用によってエンジニアがより創造的で本質的な業務に集中できる環境を作り続けていきたいと思います。 生成AIでプロダクト開発をアップデートしたい方、 生成AIを活用したプロダクトを作りたい方、 ぜひ弊社で一緒に働きましょう!
アバター
去年に引き続き、エブリーは2025年6月28日(土)に大田区産業プラザPiOで開催されたPHPカンファレンス2025に参加させていただきました。 今回も参加レポートとして、会場の様子やセッションの感想についてお届けします! イベント概要 https://phpcon.php.gr.jp/2025/ PHPカンファレンスは、PHP関連の技術を主とした技術者カンファレンスです。 2000年に日本のユーザ会によってPHPカンファレンスが初めて行われ、今年で26回目の開催となります。 これからPHPをはじめる方から、さらにPHPを極めていきたい方まで幅広く楽しめるイベントになるよう様々なプログラムをご用意しております。 セッションの感想 PHPの今とこれから2025 〜30周年を迎えたPHPと最新動向〜 2025年、PHPはついに誕生から30周年を迎えました。そして、PHPカンファレンスも今年で第25回。節目の年にふさわしく、「PHPの今とこれから」をテーマに、歴史と最新動向を振り返るセッションが展開されました。 その内容を簡単にお伝えできればと思います。 PHPの誕生とその時代背景 PHPが誕生したのは1995年。当時のインターネット黎明期には、いくつもの革新的な技術が登場しています。 1993年4月30日:CERNがWWWを無償公開 1994年12月15日:Netscape Navigator(初の一般向けWebブラウザ)リリース 1995年1月21日:Apache HTTP Server誕生 1995年5月23日:MySQL 1.0 リリース 1995年6月8日:PHP 1.0 登場 つまり、Webそのものの始まりと共にPHPも誕生したと言えます。初期はPerlの代替のような簡易スクリプトとしてスタートしたPHPですが、 その後の5.x系の成熟、7.x系の大幅な高速化などを経て、現在の堅牢で高機能な言語へと進化してきました。 PHPの現状と普及率 現在のPHPは、サーバーサイド言語として依然として圧倒的なシェア約74%に上るようです。 バージョンの利用状況を見ても、7.x系と8.x系がそれぞれ約4割ずつと拮抗していますが、 依然として古いバージョンを使い続けているシステムも存在していました。 そして特に印象的だったのが、最新のPHP 8.4の利用率がまだ1%未満という点。 多くのシステムがまだバージョンアップに慎重な姿勢をとっていることがうかがえます。 11月20日リリース予定のPHP 8.5 2025年11月20日に正式リリース予定のPHP 8.5の新機能にもフォーカスが当てられました。 パフォーマンス 8.4とほぼ同等のパフォーマンスですが、環境によっては7%ほど改善されている例もあるようです。 主な新機能(一部抜粋) パイプ演算子 シェルのように値を次々にパイプ「|>」で渡して処理できる構文。コードの可読性と柔軟性が向上。 WHATWG URL・RFC3986準拠のURI生成 セキュリティや整合性を強化する目的で、標準仕様に沿ったURL/URIオブジェクトを生成可能に。 クローン時にプロパティを指定可能:clone($x, $options) NoDiscardアトリビュート 呼び出し結果を無視すべきでないことを明示。 ライフサイクルとバージョンアップの重要性 PHPは現在、年1回のメジャーバージョンアップサイクルを採用しており、サポート期間は4年(バグ修正2年+セキュリティ修正2年)とされています。 最新機能を利用するためだけでなく、セキュリティサポートを継続的に受けるためにも定期的なバージョンアップは不可欠です。 2025年のPHPカンファレンスは、バージョンアップのお話だけでなく、今までの歴史や最新のシェア状況などのお話もあり大変面白いセッションでした。 PHP初心者セッション2025 〜ChatGPTと学ぶ、新時代のPHP入門〜 今年のPHPカンファレンスでは、初心者向けのセッション「ChatGPTと学ぶPHP入門」が開催されました。 開催前に挙手によるアンケートを行ったのですが、 参加者の8割がPHP初心者、中にはプログラム初心者もいるということで、これからPHPを始める人の多さに驚かされました。 このセッションのゴールは、ChatGPTを活用しながら、PHPで簡単なWebアプリを作ること。 はじめにPHPの基本文法や環境構築、変数や配列、条件分岐などの基礎が説明され、後半ではCSV出力のミニアプリをChatGPTと一緒に作成していきました。 印象的だったのは、「AIは主役ではなく相棒。開発を加速させる壁打ち役」という考え方。 エラー調査では「何をしたか」「どんなエラーが出たか」「どう解決したいか」をセットでAIに伝えると、より正確な回答が得られるというテクニックも紹介されました。 また、マニュアルを読むよりもAIに初心者向けに説明させる方が理解しやすい場面も多いとのこと。 セキュリティチェックやコードレビューにもAIは活用でき、今後はAIを使いこなすスキル自体が重要になっていくという話もありました。 AIと共に学ぶ新しいPHPの入口として、非常に実践的かつ未来を感じさせるセッションでした。 スポンサーブースの紹介 エブリーでは、 デリッシュキッチン に代表される様々なサービスを開発・運用しております。 その中でも小売業者向けのデータ連携サービス retail HUB では開発・運用にPHPを活用しています。 retail HUBは、小売業のDXを推進するサービスです。詳細は こちら をご覧ください! PHPを活用する中で多くの恩恵を受けている私たちも、コミュニティのさらなる盛り上がりに貢献するため、スポンサーとして協賛させていただき、ブースを出展しました。 「なぜエブリーが協賛しているのか?」については、こちらのブログ記事もご覧ください! PHP Conference Japan 2025 にGOLDスポンサーとして今年も協賛します! ブース エブリーでは、今回も弊社が提供する デリッシュキッチン のサービスをイメージしたブースの雰囲気を作りました。 多くの方にデリッシュキッチンを知っていると言っていただけてとても嬉しいと思いました。 デリッシュキッチンは知っているけど、エブリーという会社名は知らないという方も数多くいらっしゃいました。 今回のブース出展を機により多くの方にエブリーという会社名も認知していただければ嬉しいです! ノベルティ 今回もデリッシュキッチンにちなんだノベルティを用意させていただきました。 ステッカー CTOブレンドのコーヒーバッグ デリッシュキッチンお料理グッズ デリッシュキッチンお料理グッズに関してはXフォローでの抽選プレゼントキャンペーンを行いました。 当選した方の中には「めちゃめちゃ料理するのでたくさん使います!」と言っていただけてこちらも嬉しい気持ちになりました。 アンケート 今回、アンケートでは『使用しているAIツール』について回答していただきました。 GitHub CopilotとChatGPTが最も投票数を集める結果となりました。 稟議の関係でGitHub Copilotの導入が最もスムーズに進んだという方、様々なAIツールを並行利用して現在評価中という方など、 会社によって使っているAIツールは様々であることがわかり、とても興味深いアンケートとなりました。 ちなみにエブリーでは全エンジニアおよびプロダクトマネージャーが「Cursor」を導入しています。 詳しくはこちらの記事をご覧ください。 エブリー、AIエディタ「Cursor」を全エンジニアおよびプロダクトマネージャーに導入 ご回答いただいた皆様、ご協力いただきまして本当にありがとうございました! 各社スポンサーブースの様子 今年も各社ともに個性あふれるブースを展開しており、会場には活気が溢れていました。 クイズやアンケートパネル、さらにはコードレビュー体験など、多彩な企画が用意されていて、来場者が楽しみながら参加できる雰囲気でした。 スタンプラリー企画 まとめ PHPカンファレンス2025の運営の皆様、そしてご参加された皆様、今年も本当にありがとうございました! 昨年の開催から半年しか経っていないにもかかわらず、PHP 8.5の最新情報や今後の進化の話題も多く、毎年確実に進化を続けているPHPに、改めてワクワクしました。 また、これまではPHP歴の長い方がメインの参加者なのかと思っていましたが、「初心者向け」と書かれているセッションでは、「PHPどころかプログラミング自体が初めてです!」という方も多く参加されていて、本当に幅広い層が集う、まさに“初心者から上級者まで”楽しめる素晴らしいコミュニティだと実感しました。 私たちも今回得られた知見を活かし、PHP 8.5の新機能を活用したプロジェクトにもどんどん取り組んでいきたいと思います。 今後もこうしたイベントや勉強会に積極的に参加し、PHPコミュニティの一員として、引き続きPHPの進化を追いかけていきます!
アバター
はじめに こんにちは。 開発本部 開発1部 デリッシュリサーチチームでデータエンジニアをしている吉田です。 エブリーではサイネージ端末を利用した広告配信サービスを提供しており、サイネージ端末からのログを収集しています。 従来はTreasureData-SDKを利用して端末からTreasureData上のテーブルにログを送信していましたが、ログ収集基盤を移行することになりました。 今回、AWSのマネージドサービスを活用して、スケーラブルなログ収集基盤を構築したので、その内容を紹介します。 背景 従来のログ収集基盤では、TreasureData-SDKを利用して端末からTreasureData上のテーブルにログを送信していました。 これはコストや処理の面で非常に便利でしたが、ログの送信がSDKに依存しているため、仮にTreasureData以外のログ収集基盤へ移行する場合には、アプリケーションのコードを変更する必要がありました。 サイネージ端末の都合上、アプリケーションのアップデートを極力行わずにログ収集基盤を変更できるようにしたい、という要望がありました。 そこで、アプリケーションの改修をすることなく、バックエンドをいつでも自由に変更できる、より柔軟なログ収集基盤の構築を目指しました。 アーキテクチャ 新しく構築したログ収集基盤は以下のコンポーネントで構成されています。 API Gateway: アプリケーションからのログ送信用エンドポイント SQS: ログメッセージをバッファリング Lambda: SQSからメッセージを取得し、処理を行う Firehose: 処理されたログをS3に配信 S3: ログの長期保存 この構成により、以下のメリットを実現しています: スケーラビリティ: 突発的な大量のログにも対応可能 信頼性: SQS によるメッセージの永続化とデッドレターキューによる失敗処理 運用負荷の軽減: マネージドサービスの活用 全体のアーキテクチャは以下のようになります。 アーキテクチャ コスト面では、リクエスト数に応じて課金されるAPI Gatewayの料金が、この基盤全体の大部分を占めることになります。 しかし今回は、そのコストよりも、将来的にログ収集基盤を柔軟に変更できるというメリットを重視し、この構成を採用しました。 詳細 API Gateway API Gatewayはアプリケーションからログを受け取るエンドポイントを提供するとともに、SQSにメッセージを送信する役割を担います。 API Gateway から SQS へのインテグレーションでは、マッピングテンプレートを使用して、受信したJSONペイロードを SQS メッセージ形式に変換します。 terraformのコードは以下のようになります。 resource "aws_api_gateway_integration" "logs_post_integration" { rest_api_id = aws_api_gateway_rest_api.this.id resource_id = aws_api_gateway_resource.logs.id http_method = aws_api_gateway_method.logs_post.http_method integration_http_method = "POST" credentials = aws_iam_role.api_gateway.arn type = "AWS" uri = "arn:aws:apigateway:$ { var.aws_region } :sqs:path/$ { var.aws_account_id } /$ { aws_sqs_queue.primary.name } " request_parameters = { "integration.request.header.Content-Type" = "'application/x-www-form-urlencoded'" } request_templates = { "application/json" = "Action=SendMessage&MessageBody=$util.urlEncode($input.body)" } passthrough_behavior = "WHEN_NO_TEMPLATES" } SQS SQSはログメッセージをバッファリングし、Lambda関数による処理を待ちます。 API GatewayとLambdaの間にSQSを挟むことで、Lambdaの処理が失敗した場合でもメッセージを保持し、後で再処理できるようになります。 また、SQSのデッドレターキューを設定し、指定回数処理に失敗したメッセージを別のキューへ送信して処理させることで、エラーの確認を容易にするとともに、ログの喪失を防ぎます。 terraformのコードは以下のようになります。 resource "aws_sqs_queue" "dead_letter" {} resource "aws_sqs_queue" "primary" { name = "$ { var.prefix } -primary-queue" delay_seconds = 0 # 配信遅延秒 visibility_timeout_seconds = 30 # メッセージの可視性タイムアウト秒 max_message_size = 262144 # 256KB message_retention_seconds = 3 * 24 * 60 * 60 # メッセージの保持期間 (最大14日) receive_wait_time_seconds = 20 # メッセージ受信待機時間秒 sqs_managed_sse_enabled = true redrive_policy = jsonencode ( { deadLetterTargetArn = aws_sqs_queue.dead_letter.arn maxReceiveCount = 3 # メッセージがDLQに移動するまでの最大受信回数 } ) } resource "aws_sqs_queue_redrive_allow_policy" "this" { queue_url = aws_sqs_queue.dead_letter.id redrive_allow_policy = jsonencode ( { redrivePermission = "byQueue" , sourceQueueArns = [ aws_sqs_queue.primary.arn ] } ) } redrive_policyは、メッセージがデッドレターキューに移動する条件を設定します。 今回は最大3回の受信失敗でデッドレターキューに移動するように設定しています。 Lambda Lambda関数はSQSからメッセージを取得し、処理を行います。 トリガーとしてSQSを設定し、メッセージがキューに追加されると自動的に起動します。 terraformのコードは以下のようになります。 resource "aws_lambda_function" "primary" { function_name = "$ { var.prefix } -log-sender" role = aws_iam_role.lambda.arn filename = data.archive_file.dummy.output_path architectures = [ "arm64" ] handler = "main.lambda_handler" runtime = "python3.13" memory_size = 128 # MB timeout = 5 # 秒 logging_config { log_format = "JSON" system_log_level = "WARN" application_log_level = "INFO" } lifecycle { # 環境変数の変更を無視する # 別リポジトリで管理 ignore_changes = [ environment, ] } } resource "aws_lambda_event_source_mapping" "primary" { event_source_arn = aws_sqs_queue.primary.arn function_name = aws_lambda_function.primary.arn enabled = false batch_size = 1 maximum_batching_window_in_seconds = 0 function_response_types = [ "ReportBatchItemFailures" ] scaling_config { maximum_concurrency = 500 } lifecycle { ignore_changes = [ # enabledを無視する # コードデプロイ後に手動で有効化する enabled, ] } } aws_lambda_event_source_mappingのfunction_response_typesで ReportBatchItemFailures を指定しているため、Lambda関数で処理に失敗したメッセージだけがSQSに戻されます。 上記のTerraformではbatch_size=1と設定しているため、この設定は現状では効果がありませんが、将来的にbatch_sizeを変更する可能性を考慮して、このように設定しています。 Data Firehose FirehoseはLambdaからデータを受け取り、S3に配信します。 Lambdaから直接S3に書き込む場合、S3のAPI呼び出しコストやマイクロファイル(小さなファイル)が多数生成されるといった問題が発生します。 特に、マイクロファイルが大量に存在すると、後段でデータ処理をする際のパフォーマンスが著しく低下する原因となります。 Firehoseを利用するとバッファリング、データの圧縮、パーティショニング、S3への書き込みなどを自動で行ってくれるため、これらの問題を解決できます。 terraformのコードは以下のようになります。 resource "aws_kinesis_firehose_delivery_stream" "primary" { name = "$ { var.prefix } -primary-stream" destination = "extended_s3" extended_s3_configuration { bucket_arn = aws_s3_bucket.this.arn role_arn = aws_iam_role.firehose.arn buffering_size = 128 # MB buffering_interval = 300 # 秒 file_extension = ".json" # プレフィックスの設定 prefix = "logs/!{partitionKeyFromQuery:event_name}/!{partitionKeyFromQuery:year}/!{partitionKeyFromQuery:month}/!{partitionKeyFromQuery:day}/!{partitionKeyFromQuery:hour}/" error_output_prefix = "errors/!{firehose:error-output-type}/" dynamic_partitioning_configuration { enabled = true } processing_configuration { enabled = true processors { type = "MetadataExtraction" parameters { parameter_name = "JsonParsingEngine" parameter_value = "JQ-1.6" } parameters { parameter_name = "MetadataExtractionQuery" parameter_value = "{year:.time | gmtime | .[0] | tostring,month:.time | gmtime | (.[1] + 1) | (. | if . < 10 then \"0\" + tostring else tostring end),day:.time | gmtime | .[2] | (. | if . < 10 then \"0\" + tostring else tostring end),hour:.time | gmtime | .[3] | (. | if . < 10 then \"0\" + tostring else tostring end),event_name:.event_name}" } } } cloudwatch_logging_options { enabled = true log_group_name = aws_cloudwatch_log_group.firehose_primary.name log_stream_name = aws_cloudwatch_log_stream.firehose_primary.name } s3_backup_mode = "Disabled" } server_side_encryption { enabled = true key_type = "AWS_OWNED_CMK" } depends_on = [ aws_iam_role_policy_attachment.firehose ] } 動的パーティショニングの設定により、ログ中に含まれるイベント名と時間を元にS3のプレフィックスを動的に生成します。 MetadataExtractionQueryでは、JQを利用してJSON形式のログデータからパーティション分割に必要なキー(イベント名、年、月、日、時)を抽出しています。 これにより、後続のETLなどで効率的にデータを処理できるようになります。 S3 S3はFirehoseによって配信されたログデータの長期保存先として機能します。 バージョニングを設定して意図しない削除に備えるとともに、ACLをプライベートに設定して外部からのアクセスを制限します。 Lambdaの実装 Lambda関数では、SQSから受け取ったメッセージを処理し、Firehoseに送信します。 以下に実装の一部を示します。 def lambda_handler (event: Dict[ str , Any], context: Any) -> Dict[ str , Any]: batch_item_failures: List[Dict[ str , str ]] = [] record: Dict[ str , Any] for record in event.get( "Records" , []): message_id: Optional[ str ] = record.get( "messageId" ) try : message_body_str: Optional[ str ] = record.get( "body" ) log_data: Dict[ str , Any] = json.loads(message_body_str) # Send to Firehose firehose_success: bool = send_to_firehose(log_data) # その他の送信 if not firehose_success: batch_item_failures.append({ "itemIdentifier" : message_id}) except json.JSONDecodeError as e: batch_item_failures.append({ "itemIdentifier" : message_id}) continue except Exception as e: batch_item_failures.append({ "itemIdentifier" : message_id}) continue if batch_item_failures: return { "batchItemFailures" : batch_item_failures} else : return { "batchItemFailures" : []} 前述のLambdaのterraformコードで function_response_types = ["ReportBatchItemFailures"] としているため、関数の戻り値は {"batchItemFailures": [失敗したメッセージID]} とします。 これにより、複数メッセージを受け取った際に、失敗したメッセージのみをSQSに戻せます。 エラーとなるメッセージがない場合は、 {"batchItemFailures": []} を返します。 Lambdaのデプロイ Lambdaのコードデプロイには lambroll を利用しています。 コードと環境変数を管理するリポジトリと、Terraformでインフラを管理するリポジトリを分離しています。 これにより、インフラの変更とアプリケーションコードの変更のライフサイクルを分け、それぞれが独立して安全にデプロイできる体制を整えています。 lambroll init --function-name {function-name} --download --profile {aws-profile}で既存のLambdaのコードと設定ファイルをダウンロードできます。 このプロジェクトでは、function.dev.jsonのようにfunction.{env}.jsonという形式で環境ごとの設定ファイルを管理しています。 コードやfunction.jsonの編集後、 lambroll deploy --function=function.{env}.json --profile {aws-profile} でデプロイを行います。 まとめ 今回、AWSのマネージドサービスを活用して、スケーラブルなログ収集基盤を構築しました。 API Gatewayを利用することで、アプリケーションのアップデートを行うことなく、ログ収集基盤を変更できる柔軟性を持たせています。
アバター
はじめに こんにちは、エブリーでサーバーサイドをメインに担当している清水です。 私のチームではPHP, Laravelを使用して小売店向けのSaaS型Webサービスの開発を行っています。インフラはAmazon ECS (Fargate)です。 このシステムではユーザーのアクションを分析するためのイベントログを出力する機能があり、こちらをAmazon S3に出力するためにfluentbitを使用することにしました。 当初の予定では1日程度で終わるはず思っていたのですが、思った以上にうまくいかず1週間ほどかかってしまいました。 本記事では、Laravel → 標準出力 → Fluent Bit → S3 → Athenaという構成で、イベントログを出力するまでのフローをコードレベルで丸ごと紹介しつつ、いくつかハマったポイントを共有します。 本記事を参考にとりあえず動く状態にしていただいて、足りない部分はカスタマイズしていただくような形で利用いただければ幸いです。 環境情報 PHP 8.3.22 Laravel 11.18.1 Fluent Bit 1.9.10 Laravelからイベントログを標準出力する機能を実装する ある処理が呼び出された時のイベントログ出力処理例 public function show(int $id) { Log::channel('event')->info('記事閲覧イベントログ', [ 'event_type' => 'show_article', 'article_id' => $id, ]); // ※ログ以外の処理は省略 } イベントログ用のフォーマッターを実装 <?php namespace App\Logging; use Monolog\Formatter\NormalizerFormatter; use Monolog\LogRecord; class EventLogFormatter extends NormalizerFormatter { public function format(LogRecord $record): string { $formatted = parent::format($record); $segments = [ 'level' => $formatted['level_name'], 'datetime' => $formatted['datetime'], 'env' => $formatted['channel'], 'message' => $formatted['message'], 'extra' => $formatted['extra'], 'log_type' => 'EVENT', ]; // コンテキストのすべてのキーを出力に含める foreach ($formatted['context'] as $key => $value) { $segments[$key] = $value; } return json_encode($segments, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).PHP_EOL; } } ※以下の記事を参考にさせていただきました。 Laravel(monolog)で構造化ログを実装する config/logging.phpにeventチャネルを定義 'event' => [ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => StreamHandler::class, 'formatter' => EventLogFormatter::class, 'with' => [ // Swooleを使用しているとstdoutの標準出力に余計な文字列がつけられるため、stderrとしました。 'stream' => 'php://stderr', ], ], ローカル開発環境にて標準出力でイベントログがJSON形式で出力されることを確認 {"level":"INFO","datetime":"2025-06-24T12:59:29.427634+09:00","env":"local","message":"記事閲覧イベントログ","extra":[],"log_type":"EVENT","event_type":"show_article","article_id":1} fluentbitを設定する Dockerfile # ベースイメージとしてAWS公式Fluent Bitイメージを指定 FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:stable # 作業ディレクトリの作成 WORKDIR /fluent-bit/etc # カスタムfluent-bit.confをイメージ内へコピー COPY fluent-bit-custom.conf /fluent-bit/etc/fluent-bit-custom.conf # カスタムパーサーファイルもコピー COPY json-string.conf /fluent-bit/etc/json-string.conf fluent-bit-custom.conf [SERVICE] Parsers_File /fluent-bit/etc/json-string.conf Flush 5 Grace 30 # 空のログをフィルタリング [FILTER] Name grep Match *-firelens-* Exclude log ^$ # JSONパース用フィルター:カスタムパーサー json_string を使用 [FILTER] Name parser Match *-firelens-* Parser json_string Key_Name log Preserve_Key Off Reserve_Data Off # event log のタグ付け [FILTER] Name rewrite_tag Match *-firelens-* Rule log_type ^EVENT$ s3.event.$event_type true # EVENTログ出力 [OUTPUT] Name s3 Match s3.event.* bucket ${AWS_S3_LOG_BUCKET} # 環境変数で出力先S3バケット名を指定する形とする region ap-northeast-1 use_put_object Off upload_timeout 60s compression gzip s3_key_format /EVENT/$TAG[2]/%Y/%m/%d/%H_%M_%S_$UUID.gz json-string.conf [PARSER] Name json_string Format json Time_Key datetime Time_Keep On Time_Format %Y-%m-%dT%H:%M:%S.%L%z →Dockerイメージをビルドして、latestタグでAmazon ECRにプッシュしておく ECSタスク定義でfluentbitを使用する設定に変更する "containerDefinitions": [ { "name": "{Webサービスの名前}", "image": "{イメージ名}", (省略) "logConfiguration": { "logDriver": "awsfirelens" } }, { "name": "log_router", "image": "XXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/{fluentbitイメージ名}:latest", (省略) "environment": [ { "name": "AWS_S3_LOG_BUCKET", "value": "{出力先となるS3バケット}" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "{log_routerのログ出力先}", "awslogs-region": "ap-northeast-1", "awslogs-stream-prefix": "ecs", "awslogs-create-group": "true", "mode": "non-blocking", "max-buffer-size": "25m" } }, "firelensConfiguration": { "type": "fluentbit", "options": { "config-file-type": "file", "config-file-value": "/fluent-bit/etc/fluent-bit-custom.conf" } } } Athenaで外部テーブルを作成する CREATE EXTERNAL TABLE IF NOT EXISTS show_article_event_logs ( level string, datetime string, env string, message string, extra array<string>, log_type string, event_type string, article_id int ) PARTITIONED BY ( year string, month string, day string ) ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' LOCATION 's3://{バケット名}/EVENT/show_article/' TBLPROPERTIES ( 'projection.enabled' = 'true', 'projection.year.type' = 'integer', 'projection.year.range' = '2025,2100', 'projection.year.digits' = '4', 'projection.month.type' = 'integer', 'projection.month.range' = '1,12', 'projection.month.digits' = '2', 'projection.day.type' = 'integer', 'projection.day.range' = '1,31', 'projection.day.digits' = '2', 'storage.location.template'= 's3://{バケット名}/EVENT/show_article/${year}/${month}/${day}/' ); 引っかかった部分 ローカルでテストする方法がわからなかった 最初は一発で上手くいくだろうと楽観視していきなりAWS上にデプロイしてテストしてみましたが、当然上手くいきませんでした。 ローカルでどうやってダミーデータを標準出力させるか?どうやってfluentbitにその標準出力を認識させるのか?といったことを考えていたら手が止まりました。 ローカルのテスト手順はこちらの記事の通りに対応して上手くいきました。 Fluent Bitを導入しました:ローカル実行・確認方法と、導入の過程でハマったこと application.conf にテストデータ投入用の[INPUT]、出力確認用の[OUTPUT]を追加します。 Name dummy Tag *-firelens-* Dummy {"date":"2022-01-23T03:10:33.317817Z","source":"stdout","log":"time:2022-01-23T03:10:33+00:00\tprotocol:HTTP/1.1\tstatus:200\tsize:1450\treqsize:150\treferer:-\treqtime:0.176\tcache:-\truntime:-\t"} Dockerを起動してターミナルを開き、以下でFluent Bitを起動します。 /fluent-bit/bin/fluent-bit -c /fluent-bit/etc/application.conf コンテナ内でAWSのアクセスキーをexportするだけでS3へのアップロード処理も問題なく実行されます export AWS_ACCESS_KEY_ID="{AWSアクセスキー}" export AWS_SECRET_ACCESS_KEY="{AWSシークレットアクセスキー}" AthenaのSELECT実行結果が全カラム空白になってしまう 色々試した結果、ログファイルの拡張子が.gzになっていないと発生することがわかった # before s3_key_format /EVENT/$TAG[2]/%Y/%m/%d/%H_%M_%S_$UUID # after s3_key_format /EVENT/$TAG[2]/%Y/%m/%d/%H_%M_%S_$UUID.gz おわりに 最後までお読みいただきありがとうございました。 今回の作業をするにあたって多くの先駆者様の記事を参考にさせていただきました。 ログ出力する方法には様々な実装方法があり、どのような方法を採用するべきか悩みながら進めました。 この記事がこれから同じことをする人に少しでも参考になれば幸いです。
アバター
PHP Conference Japan 2025 にGOLDスポンサーとして今年も協賛します! はじめに PHPカンファレンス とは? 2024年のカンファレンス概況 2025開催プラン 昨年の様子など イベント当日について はじめに  この度、株式会社エブリーは、2025 年 6 月 28 日(土)に開催される「PHPカンファレンス2025」に、ゴールドスポンサーとして昨年に続き 今年も 協賛することになりました! phpcon.php.gr.jp PHPカンファレンス とは?  日本PHPユーザ会 PHPカンファレンス実行委員会が主催となって、2000年より年に一度開催されている日本最大のPHPのイベントです。  WEBサーバにインストールされているシェア8割を超える人気言語のイベントとして、初心者から上級者まで幅広い層のWEB系エンジニアが参加します。 2024年のカンファレンス概況 参加者数 約1,150名 協賛企業数 55社 スポンサーセッション数 7セッション セッション数 52セッション 2025開催プラン 会場 大田区産業プラザPiO オンラインツール 後日簡易アーカイブを実施予定 セッション 最大並行5トラック程度※トラックは増減する可能性はあります 25~35セッション程度+LTを予定 タイムテーブルは こちら コンテンツ ・基調講演 ・公募セッション ・スポンサーセッション ・スタンプラリー(プレゼント抽選) 懇親会 実施予定(有料) イベントの参加申し込みは こちら で行っておりますのでぜひ会場でお会いしましょう 昨年の様子など tech.every.tv tech.every.tv photos.app.goo.gl イベント当日について 当日は 大田区産業プラザPiOの1F大展示ホール会場 の各スポンサーブースにて、毎年恒例のスタンプラリーが実施されます。 エブリーのブースでは弊社のXアカウントをフォローしていただいた皆様全員に当たるくじ引き景品もございますので、ぜひご興味のある方はお越しください! ノベルティ 最後までお読みいただき、ありがとうございました!
アバター
こんにちは、 @きょー です!普段はデリッシュキッチン開発部のバックエンド中心で業務をしています。 はじめに OpenAPI で API 仕様書を書く際、 null 値を許容するプロパティの表現方法はバージョンによって異なります。たとえば、ユーザープロフィールのメールアドレスのように「値が存在しない( null )」を許容したいケースはよくありますが、その書き方や推奨される方法は OpenAPI のバージョンごとに変化してきました。 この記事では、OpenAPI 3.0.0 と 3.1.0 それぞれでの null 許容プロパティの書き方や、その背景、なぜ仕様が変わったのか、どちらを使うべきかについて解説します。API 設計やスキーマ管理で迷ったときの参考になれば幸いです。 OpenAPI 3.0.0 での nullable の基本的な使い方 OpenAPI 3.0.0 では nullable: true を使用することで、プロパティが null 値を許容することを表現できます。 components : schemas : User : type : object properties : id : type : integer format : int64 name : type : string email : type : string nullable : true # emailはnull値を許容 swagger で表示すると以下のようになります。 OpenAPI 3.1.0 での nullable の表現方法 OpenAPI 3.1.0 では、 nullable キーワードが廃止され、代わりに type 配列に null を含める形で表現するようになりました。 components : schemas : User : type : object properties : id : type : integer format : int64 name : type : string email : type : [ string, "null" ] # emailはnull値を許容 swagger で表示すると以下のようになります。 なぜ変更されたのか OpenAPI 3.1.0 での nullable の廃止には、重要な背景があります。この変更は OpenAPI Specification の Proposal で詳しく議論され、主に以下のような理由が挙げられています。 nullable キーワードの曖昧さ nullable: true は「型指定されたスキーマにおいて null を許容する」という意図で導入されたが、OpenAPI 3.0 のドキュメントではその意味や他キーワードとの相互作用が十分に明確化されていなかった。 nullable: false (デフォルト値)についても、 null を明示的に禁止するのか、単に変更しないのかが明確でなかった。 詳細: https://spec.openapis.org/oas/v3.0.3#schemaNullable JSON Schema との整合性 OpenAPI の nullable は「許容範囲を拡張する」性質(expanding assertion)を持っているが、 JSON Schema には存在しなく整合性がなかった。 詳細: https://json-schema.org/specification これらの曖昧さや、JSON Schema への整合性の無さにより、バリデーターの挙動に一貫性がありませんでした。そのため OpenAPI 3.1.0 では nullable が廃止され、type に null を指定する方法に変更されました。 コード生成への影響 自分のチームでは OpenAPI で定義したスキーマを元にコードを自動生成しているプロジェクトがあります。 oapi-codegen と openapi-typescript を用いて Go のモデル定義や TypeScript の型を自動生成しています。 OpenAPI のバージョンの違いによって変わる null 値の表現方法がツールによるコード生成にどう影響を与えるか確認してみようと思います。 TypeScript での生成コード openapi-typescript を使用して TypeScript のコードを生成しようとした場合、以下のようになります。 // OpenAPI 3.0.0 での生成コード export interface components { schemas : { User : { id : number ; name : string ; email : string | null ; } ; } ; } // OpenAPI 3.1.0 での生成コード export interface components { schemas : { User : { id : number ; name : string ; email : string | null ; } ; } ; } TypeScript の場合、生成されるコードに大きな違いはなく、 nullalbe として指定したプロパティは自動生成されたコードに反映されているのがわかるかと思います。 Go での生成コード oapi-codegen を使用して Go のコードを生成しようとした場合、以下のようになります。 // OpenAPI 3.0.0 での生成コード type User struct { Email * string `json:"email"` Id int `json:"id"` Name string `json:"name"` } // OpenAPI 3.1.0 での生成コード // WARNING: You are using an OpenAPI 3.1.x specification, which is not yet supported by oapi-codegen (https://github.com/oapi-codegen/oapi-codegen/issues/373) and so some functionality may not be available. Until oapi-codegen supports OpenAPI 3.1, it is recommended to downgrade your spec to 3.0.x Go の場合、OpenAPI 3.0.0 で生成されたコードは nullable として指定したプロパティが反映されているのがわかります。一方 OpenAPI 3.1.0 ではコードの自動生成がされず 3.0.0 にバージョンを落として実行するように警告が出されます。これは oapi-codegen が依存している kin-openapi で OpenAPI 3.1 系にはまだ対応していないためこのような挙動になっているとのことでした。 https://github.com/oapi-codegen/oapi-codegen/issues/373 https://github.com/getkin/kin-openapi/issues/230 ここでは取り扱いませんが こちら で言及されている OpenAPI Overlay を使って OpenAPI 3.1.0 を 3.0.0 に downgrade する手法もあるみたいです。 まとめ OpenAPI における null 値の表現方法について、仕様の違いとツールの対応状況を確認してきました。 仕様の違い OpenAPI 3.0.0 では nullable: true による独自拡張 OpenAPI 3.1.0 では type: [string, "null"] による JSON Schema 準拠 3.1.0 での変更は、より標準的な方法での null の表現を可能に 実際のツール対応状況 TypeScript (openapi-typescript) 3.0.0, 3.1.0 ともに問題なく動作 Go (oapi-codegen) 3.0.0 では正常に動作 3.1.0 は現時点で未対応(kin-openapi の制限) 3.1.0 を使用する場合は OpenAPI Overlay による downgrade が必要 新規プロジェクトで新しく API スキーマを書く場合、JSON Schema との互換性がある 3.1.0 の方式が望ましいものの、実際の採用にあたってはツールの対応状況を十分に確認する必要がありそうです。もし既存プロジェクトで 3.1.0 に移行する場合でも使っているツールの対応状況を把握してから移行するのが良さそうです。
アバター
目次 はじめに Liam ERD とは 主な特徴 サポート状況 導入 手順 1. Liam ERD のビルドをさせるための Dockerfile を用意する 2. Docker Compose ファイルを用意する 3. テーブル構成を出力するためのコマンドを用意する 4. テーブル構成を出力する 使い勝手 まとめ 最後に はじめに こんにちは、開発本部開発1部トモニテグループのエンジニアの rymiyamoto です。 プロダクトを開発していると、DB のテーブルは常に変化していきます。 そのため、テーブルの構成を可視化することで、テーブルの関係性を把握することができるので設計や開発の効率化に繋がります。 今回は、MySQL のテーブル構成を Liam ERD で可視化してみたので、その方法を紹介します。 Liam ERD とは Liam ERD は、データベースから ER 図を生成するツールで、複雑なスキーマを簡単に可視化することができます。 liambx.com 主な特徴 公式サイトで挙げられている、Liam ERD が選ばれる理由は以下の通りです。開発者にとって嬉しいポイントが詰まっています。 Beautiful UI & Interactive(美しく、インタラクティブな UI) 洗練されたクリーンなデザインで、どんなに複雑なデータベースでも直感的に理解できる ER 図を生成します。パン、ズーム、フィルタリングといった機能により、ストレスなく目的の情報にたどり着けます。 Simple Reverse Engineering(簡単なリバースエンジニアリング) 既存のデータベーススキーマを、明確で読みやすい ER 図にシームレスに変換します。これにより、プロジェクトの全体像把握や、新しいメンバーのキャッチアップが格段に容易になります。 Effortless Setup(手間いらずのセットアップ) 複雑な設定は一切不要です。データベースのスキーマ情報を提供するだけで、すぐに使い始めることができます。 High Performance(高いパフォーマンス) 小規模なプロジェクトから、100 を超えるテーブルを持つ大規模なプロジェクトまで対応できるように最適化されており、軽快な動作を実現しています。 Fully Open-Source(完全なオープンソース) Liam ERD はオープンソースソフトウェアです。誰でも自由に利用できるだけでなく、プロジェクトに貢献し、自身のニーズに合わせてツールを形成していくことが可能です。 サポート状況 2025/06/17 現在、全面的にサポートされているものとしては以下のものがあります。 liambx.com PostgreSQL Ruby on Rails Prisma tbls MySQL は直接はサポートされていませんが、tbls 経由で可能になっています。 tbls は、データベース スキーマを文書化するためのツールで、DB 構成をドキュメント化することができます。 現在 MySQL / SQLite / BigQuery は tbls 経由でサポートされています。 github.com . @k1LoW さんの協力によりtblsとの連携が実現しました! これによりMySQLやSQLite, BigQueryなど多くのデータベースでLiam ERDによるER図の可視化ができるようになりました 🙌 https://t.co/lu4BkAS7mJ — Hirotaka Miyagi (@MH4GF) 2025年1月29日 x.com 導入 今回はコンテナで動かしている MySQL のテーブル構成を出力したいため、CLI で実行してローカル環境で動かすことを想定しています。 サンプルを用意しているので、そちらを参考にしてください。 github.com 手順 1. Liam ERD のビルドをさせるための Dockerfile を用意する tbls のインストールは go install で行っています。 ※ go と tbls のバージョンは、執筆時点でのものです。 ARG GO_VERSION=1.24.4 FROM golang:${GO_VERSION}-alpine AS liam-builder RUN apk add --no-cache nodejs npm git bash ARG TBLS_VERSION=1.85.4 RUN go install github.com/k1LoW/tbls@v${TBLS_VERSION} WORKDIR /work 2. Docker Compose ファイルを用意する ER 図を生成する builder コンテナと、生成された ER 図を表示する viewer コンテナを用意します。 profiles を設定することで、docker compose up 実行時にこれらのコンテナが起動しないようにしています。 services: db: # 省略 # ER図をビルドするためのコンテナ builder: container_name: builder image: liam-erd-sample-builder:latest build: context: . dockerfile: ./dockers/liam/Dockerfile env_file: - .env volumes: - ./erd:/work working_dir: /work depends_on: - db profiles: - builder # 生成されたER図を表示するためのコンテナ viewer: container_name: viewer image: nginx:1.27-alpine volumes: - ./erd:/usr/share/nginx/html:ro ports: - "127.0.0.1:8080:80" profiles: - viewer 3. テーブル構成を出力するためのコマンドを用意する Makefile に一連の処理をまとめます。 open コマンドは macOS 用です。他の OS をお使いの場合は、コマンド実行後に手動でブラウザを開いてください。 .PHONY: view-erd view-erd: mkdir -p ./erd rm -rf ./erd/* docker compose run --rm builder sh -c "tbls out -t json -o schema.json \"mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:3306/${DB_SCHEMA}\" && npx --yes @liam-hq/cli erd build --format=tbls --input schema.json && mv dist/* ." docker compose --profile viewer up -d open http://localhost:8080 4. テーブル構成を出力する $ make view-erd 使い勝手 テーブル選択時に外部キー制約があると関連するテーブルと色付きで表示され視覚的にわかりやすく、動作も快適な印象を受けました。 また、テーブルの詳細はサイドバーで出てくるので確認がしやすく、テーブル名のみや全カラムを表示を選べるので、必要な情報を素早く確認することができます。 他にもリンクを生成する事ができるので、共有する際にも便利です。 まとめ 今回は、MySQL のテーブル構成を Liam ERD で可視化してみました。 Liam ERD は、データベースから ER 図を生成するツールで、複雑なスキーマを簡単に可視化することができます。 ツールとして導入難易度も低く、見た目や動作が良い印象を受けました。 また、最近出たオープンソースであるため、今後の発展性も期待できます。 まだチーム内で導入したばかりなので、今後の活用方法を模索していきたいと思います。 最後に エブリーでは、ともに働く仲間を募集しています。 テックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください! corp.every.tv 最後までお読みいただき、ありがとうございました!
アバター
はじめに エブリーでデリッシュキッチンの開発をしている本丸です。 4月に新卒の開発研修を行ったのですが、配属されたチームが異なることなどもあり開発環境が揃っていないという問題がありました。 anyenvやgoenvなどを設定してもらうという手もあったのですが、それ自体の設定にも手間がかかってしまうためDev Containerの設定を用意してその場は乗り切りました。 本記事では、Dev Containerの基本的な使い方から、Docker Composeとの連携、拡張機能の設定まで、実際の開発現場で役立つ知識をお届けできればと思っています。 Dev Containerとは Dev Containerは、開発環境をコンテナ化する仕組みです。Visual Studio CodeやCursorなどで利用することができ、開発に必要なツールやライブラリ、設定をすべてコンテナに封じ込めることができます。 主な利点 環境の統一 : チーム全員が同じ開発環境で作業できる 簡単に開発環境を作成できる : 新しいメンバーも数分で開発環境を構築可能 隔離性 : プロジェクトごとに独立した環境を持てる、AIエージェントを使う上で意図しない環境の変更を防げる 使い方 基本的な使い方 1. Dev Container設定ファイルの作成 プロジェクトルートに .devcontainer/devcontainer.json を作成します。 { " name ": " Go ", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile " image ": " mcr.microsoft.com/devcontainers/go:1-1.24-bookworm ", ... // Configure tool-specific properties. " customizations ": { // Configure properties specific to VS Code. " vscode ": { " settings ": {} , " extensions ": [ " streetsidesoftware.code-spell-checker " ] } } , ... // Use 'portsAttributes' to set default properties for specific forwarded ports. // More info: https://containers.dev/implementors/json_reference/#port-attributes " portsAttributes ": { " 9000 ": { " label ": " Hello Remote World ", " onAutoForward ": " notify " } } ... } 2. 開発環境の起動 devcontainer.json の作成が終わったらあとは開発環境のコンテナを起動するだけです。 VS CodeでDev Containers拡張機能をインストール コマンドパレット(Cmd+Shift+P)で「Dev Containers: Reopen in Container」を実行 これだけで、統一された開発環境が起動します。 サンプルコードで試すことができる 基本的な設定に関してはサンプルコードを参考にすることもできます。 サンプルコードを試す方法は簡単で コマンドパレット(Cmd+Shift+P)で「Dev Containers: Try a Dev Container Sample...」を実行 開発したい言語を指定する これだけで、簡単なサーバーを実行するためのサンプルコードを試すことができます。 ちなみに、「Dev Container設定ファイルの作成」で例示したものはサンプルコードのdevcontainer.jsonを引用したものです。 開発環境と同時にローカルの実行環境も立ち上げる 基本的な使い方では、イメージを指定して実行を行っていましたがDocker Composeファイルを指定して実行することもできます。 今回は、開発環境とローカルでの実行環境を同時に立ち上げるという形で説明させていただければと思います。 Dev Containerの対応前は下記で動いていたとします。 Go Docker Compose 1. Dev Container用のDocker Composeファイルを作成 今回は、元々のDocker Composeファイルをできるだけ変更することなく進めたかったため、Dev Containerの時だけ使用するDocker Composeファイルを用意することにしました。 プロジェクトルートの devcontainer ディレクトリに compose-devcontainer.yml を作成します。(命名はわかりやすいように変更しましたが、任意の名前で大丈夫なはずです。) services: dev-env: image: mcr.microsoft.com/devcontainers/go:1-1.24-bookworm volumes: - .:/workspace:cached ports: - "9000:9000" command: sleep infinity ここでは元々のDocker Composeで動かしていたserviceに加えて新しく dev-env を動作させています。 このdev-envがDev Containerでの開発環境にあたります。 2. devcontainer.jsonをDocker Composeに対応させる devcontainer.jsonをDocker Composeを使用するように変更していきます。 基本版からDocker Compose版への変更点 イメージ指定の変更 - "image": "mcr.microsoft.com/devcontainers/go:1-1.24-bookworm", + "dockerComposeFile": [ + "../docker-compose.yml", + "compose-devcontainer.yml" + ], image プロパティを削除して dockerComposeFile プロパティを追加します。この際、複数のYAMLファイルを選択することができます。 サービスとワークスペースの指定を追加 + "service": "dev-env", + "workspaceFolder": "/workspace", ここでは、どのサービスを開発環境として使うかなどの指定を行なっています。 変更後の完全なdevcontainer.json { " name ": " Go ", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile " dockerComposeFile ": [ " ../docker-compose.yml ", " compose-devcontainer.yml " ] , ... // Configure tool-specific properties. " customizations ": { // Configure properties specific to VS Code. " vscode ": { " settings ": {} , " extensions ": [ " streetsidesoftware.code-spell-checker " ] } } , " service ": " dev-env ", " workspaceFolder ": " /workspace ", ... // Use 'portsAttributes' to set default properties for specific forwarded ports. // More info: https://containers.dev/implementors/json_reference/#port-attributes " portsAttributes ": { " 9000 ": { " label ": " Hello Remote World ", " onAutoForward ": " notify " } } ... } 拡張機能に関して Dev Containerでの拡張機能の指定の方法は2つあります。 複数のユーザーで共通で使いたい場合は devcontainer.json に " customizations ": { // Configure properties specific to VS Code. " vscode ": { " settings ": {} , " extensions ": [ " streetsidesoftware.code-spell-checker ", ... , ] } }, を追加し、 extensions に任意の拡張機能を指定すれば大丈夫です。 一人のユーザーが複数のDev Containerの環境で同じ拡張機能を使いたい場合は settings.json に " dev.containers.defaultExtensions ": [ " streetsidesoftware.code-spell-checker ", ... , ] , を追加し、 dev.containers.defaultExtensions に任意の拡張機能を指定すれば大丈夫です。 まとめ Dev Containerについて調べたり、触ってみる中でDev Containerをどのように使うのかの具体的なイメージを掴むことができました。 まだ実際の開発の現場で利用はできていないため、実際のプロダクトを開発する際にも取り入れてみたいと思いました。 参考 https://code.visualstudio.com/docs/devcontainers/containers https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers&ssr=false#overview https://www.cursor.com/ja/changelog/dev-containers
アバター
エブリーで小売業界向き合いの開発を行っている @kosukeohmura です。 エブリーでは全社的に SSH を使ったサーバーへのログインから、AWS Systems Manager Session Manager ( 以下 Session Manager ) を使った運用に切り替えました。 tech.every.tv これは私達のチームで管理している他社クラウド (AWS 以外という意味で他社です) 上に存在するサーバについても対象ですが、Session Manager を直接利用することはできません。そこで Session Manager を使った SSH 踏み台サーバーを構築し、それ経由で SSH 接続することで、他社クラウド上のマシンの 22 番ポートを開放する範囲を限定することができました。 構成 簡単に前提とする構成を説明します。 AWS VPC と他社クラウドのネットワーク同士が Site-to-Site VPN で接続されている構成です。開発者や CI/CD は、AWS SSM を通して踏み台サーバーに SSH 接続し、更に踏み台サーバーから他社クラウド上の Target Server へ SSH でアクセスします。 この構成を取ることによって、目的のサーバーの SSH ポートの開放範囲を AWS VPC の CIDR ブロックのみに限定することができます。 Session Manager を通した SSH 接続 セッション開始時には aws ssm start-session というコマンドを使いますが、その際 AWS-StartSSHSession というドキュメントを指定することによって、SSH セッションが開始できます *1 。 なおその際の接続対象 (今回の場合、踏み台サーバー) では SSH 接続をサポートするように設定する必要があります *2 。つまり SSH サーバーである必要があります。 *1 : ステップ 8: (オプション) Session Manager を通して SSH 接続のアクセス許可を付与および制御する - AWS Systems Manager *2 : セッションを開始する - AWS Systems Manager 踏み台サーバーの構築 sshd を動かすシンプルなサーバーを作ります。今回 ECS タスクとして動作させますが、環境に合わせて EC2 や EKS などでも良いかと思います。 Dockerfile は下記のようになりました。シンプルですね。 FROM debian:stable-20250520-slim RUN apt-get update && \ apt-get -y upgrade && \ apt-get -y install openssh-server net-tools && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ mkdir /var/run/sshd COPY --chmod=755 entrypoint.sh /root/entrypoint.sh EXPOSE 22 ENTRYPOINT [ " /root/entrypoint.sh " ] 次に Dockerfile 内で ENTRYPOINT としている起動スクリプトです。 # entrypoint.sh #!/bin/bash if [ -n " $SSH_PUBLIC_HOST_KEY_BASE64ENCODED " ]; then echo " $SSH_PUBLIC_HOST_KEY_BASE64ENCODED " | base64 -d > /etc/ssh/ssh_host_ed25519_key.pub chmod 644 /etc/ssh/ssh_host_ed25519_key.pub fi if [ -n " $SSH_PRIVATE_HOST_KEY_BASE64ENCODED " ]; then echo " $SSH_PRIVATE_HOST_KEY_BASE64ENCODED " | base64 -d > /etc/ssh/ssh_host_ed25519_key chmod 600 /etc/ssh/ssh_host_ed25519_key fi echo " $ROOT_PUBLIC_SSH_KEY_BASE64ENCODED " | base64 -d > /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys exec /usr/sbin/sshd -D ホストキーが起動毎に毎回生成されるのを避けるため、タスク起動時にあらかじめ生成した鍵でファイルを作成しています。なお各キーは AWS Secrets Manager に Base64 エンコードし保管してあるものを、そのまま ECS タスクの環境変数として注入している都合でデコードしています。 運用上の工夫 目的のサーバーへ踏み台サーバーを経由し SSH 接続をするためには、SSM セッションを確立後に SSH での多段接続という手順を踏む必要があり、毎回行うのは手間です。そこで簡単なスクリプトを使って、接続時の手間を軽減しています。 まず ECS タスクへと aws ssm start-session コマンドを実行する場合、 --target オプションを <cluster_name>_<task_id>_<container_runtime_id> といった形式での指定が手間なので、下記 Make ターゲットで出力できるようにしています(中身はいささか力技ですが)。 CLUSTER_NAME := '<cluster_name>' FAMILY_NAME := '<family_name>' ACCOUNT_ID := `aws sts get-caller-identity --query "Account" --output text;` TASK_ID := `aws ecs describe-tasks --cluster $(CLUSTER_NAME) --tasks \`aws ecs list-tasks --cluster $(CLUSTER_NAME) --family $(FAMILY_NAME) | jq -r '.taskArns[0]'\` | jq -r '.tasks[0].taskArn' | awk -F'/' '{print $$3}'` CONTAINER_RUNTIME_ID := `aws ecs describe-tasks --cluster $(CLUSTER_NAME) --tasks \`aws ecs list-tasks --cluster $(CLUSTER_NAME) --family $(FAMILY_NAME) | jq -r '.taskArns[0]'\` | jq -r '.tasks[0].containers[0].runtimeId'` .PHONY: echo-as-ssm-target echo-as-ssm-target: @echo "ecs:$(CLUSTER_NAME)_$(TASK_ID)_$(CONTAINER_RUNTIME_ID)" .ssh/config は下記のようにしています。少々トリッキーですが、 make コマンドを .ssh/config 内で使用しています。 ProxyJump オプションにより、 bastion 経由で target_server への多段接続が自動的に行われます。結果的に ssh target_server を実行するだけで目的のサーバーへログインできます。 # .ssh/config host bastion User root IdentityFile ~/.ssh/<bastion_key_name> ProxyCommand sh -c "aws ssm start-session --target `make -C <path_to_makefile_dir> echo-as-ssm-target` --document-name AWS-StartSSHSession --parameters 'portNumber=%p'" Host target_server ProxyJump bastion IdentityFile ~/.ssh/<target_key_name> HostName ... GitHub Actions などの CI/CD でも同様に、適切な IAM Role を渡せば SSM セッションを開始し、SSH 経由でデプロイやコマンド実行が可能です。 まとめ 今回踏み台サーバーを構築し、Session Manager を通した SSH 接続を行うことで、他社クラウドのマシンにおいても SSH ポートの開放範囲を絞ることができ、また運用上の手間も減らすことができました。似たような環境の方のお役に立てばと思います。
アバター
はじめに こんにちは。デリッシュキッチンでデータサイエンティストをしている古濵です。 2025年5月27日〜30日に開催された 第39回人工知能学会全国大会(JSAI2025) に、プラチナスポンサーとして協賛いたしました。 今年は史上最多の参加者数を更新したようで、学会としての盛り上がりを肌で感じることができました。 tech.every.tv エブリーとしても人工知能学会への参加は今回が初めてでした。 本記事では、スポンサーブースでのエブリーのAIプロダクト開発に関する紹介や、聴講した講演等についてまとめていきたいと思います。 スポンサーブース 弊社のスポンサーブースでは「AIエージェントによるレシピ提案の開発と応用事例」というタイトルで、AIプロダクトであるデリッシュAIについて事例紹介を行いました。 多くの学生、大学教員、企業の方々と交流することができ、デリッシュキッチンがどのようなサービスなのかという基本的な部分から、デリッシュAIのプロダクトの詳細や今後の展望まで、幅広くお話しすることができました。 ブースでは、実際にデモ機を用いてデリッシュAIを体験していただきました。 中には、デリッシュキッチンを普段からご利用いただいている方や、現役の管理栄養士の方ともお話しする機会がありました。 デリッシュキッチンに「こういう機能があると良い」といった具体的なご要望も含めてお聞かせいただき、非常に有意義な時間を過ごすことができました。 お立ち寄りいただいた皆様、ありがとうございました。 聴講 人工知能学会では多くの講演やセッションが開催されており、個人的に聴講して特に印象に残ったものをご紹介したいと思います。 自ら話しかけるチャットボット実現のための話題選択RAGの提案 JSAI2025/Proposal of a Topic Selection RAG for Developing Proactive Chatbots ユーザーがチャットボットとの会話を継続する意欲を維持することの課題に焦点を当てた研究です。 既存の受動的なチャットボットとは異なり、ユーザーの興味や知識に基づいて能動的に新しい話題を提供できるチャットボットを提案しています。 ユーザーの興味を刺激する未知の情報を効率的に取得するために、話題をテーマごとにデータ化し、話題選択が可能なRAGシステムを構築している点が非常に興味深く感じました。 デリッシュAIでも、ユーザーのクエリに対してレシピを提案するだけでなく、今日何を作るか決まっていないユーザーとコミュニケーションを取りながらレシピ提案を行えるようになれば良いなと考えており、この研究のアプローチは大変参考になりました。 大規模視覚言語モデルチューニングを用いた非構造ドキュメント画像向け情報抽出 JSAI2025/Information Extraction from Unstructured Document Images with Tuned Large-Scale Vision-Language Model 非構造化文書画像に対する情報抽出手法について、複数のアプローチを比較検討した研究です。 OCR+LLMによる手法では、レイアウト要素が欠落してしまうため、視覚情報も考慮できるLVLM(Large Vision Language Model)を用いた比較検討を行っています。 具体的には、「LVLMによる手法におけるFew-shotとFine-tuningの比較」と「OCRの活用有無による比較」を実施していました。 結果として、LVLMをFine-tuningした手法が最も優れており、OCRを使用しない方が良い結果となったことは興味深い知見でした。 LVLMについては、別の講演で「大規模視覚言語モデルの開発」のチュートリアルも開催されており、画像とテキストの情報を統合的に処理できるLVLMの技術的発展を実感することができました。 LLMエージェントによるエルファロル・バー問題の解析 JSAI2025/Analysis of the El Farol Bar Problem by LLM Agents エルファロル・バー問題は、バーに行きたがっている複数の人が同時に意思決定を行う状況を想定した、ゲーム理論における問題です。 この発表では、各エージェントがLLMによって発話・記憶・行動を生成することで、エルファロル・バー問題をシミュレーションし、その結果を解析した研究について紹介されていました。 ゲーム理論の問題をLLMでシミュレーションするというアプローチが研究として非常に興味深く感じました。 シミュレーション結果では、エージェントがバーに入る前に何人かで集合したり、発話内にハッシュタグが出現してエージェントのグループ内で情報が拡散されたりするなど、個人的には予想外な行動パターンが観察され驚きました。 集団インタラクションを通じて個性を分化させるという、ある種の社会的な振る舞いが、エージェントによって実現可能であることを示唆する結果は、大変興味深いものでした。 おわりに 今回、人工知能学会(JSAI2025)にプラチナスポンサーとして参加し、エブリーのAIプロダクトであるデリッシュAIについて多くの方々にご紹介することができました。 学生から研究者、企業の方々まで幅広い層の皆様と交流でき、デリッシュキッチンやデリッシュAIに対する貴重なフィードバックをいただくことができました。 個人的には、学生時代がコロナ禍だったこともあり、学会の雰囲気と盛り上がりを直接体感することができて非常に良い経験となりました。 聴講した研究発表から得られた知見を、今後のプロダクト開発にどのように応用できるか模索していきたいと思います。 最後に、学会でお話しいただいた皆様、運営スタッフの皆様に心より感謝申し上げます。 来年の人工知能学会にも、さらに発展したプロダクトとともに参加できることを楽しみにしています。
アバター
Cursor v1.0 で追加された Memories を試してみる はじめに 注意 Memories とは Generate Memoriesの設定 実際に使ってみた 検証環境 実際のやりとり 完成! 🎯 主な機能 📁 プロジェクト構成 ✨ 特徴 🚀 使用方法 保存した知識 1. MCP Server 実装パターン(Golang) 2. MCP ツール定義のベストプラクティス 生成されたMemory 考察 まとめ はじめに こんにちは、TIMELINE 開発部 Service Development をしている hond です! タイトルにある通り、Cursorの0.51から待望のMemoriesがBETA機能としてリリースされました! リリースされたばかりということで ChangeLog にも詳細はないので、自分なりに使ってみた感想や調査結果を共有できればと思います! このblog執筆時点の2025/06/02時点では、ChangeLogにv0.51やGenerate Memoriesに関しての記述はありませんでしたがv1.0リリースにてChangeLogにv1.0の内容とともにMemoriesの内容が追加されました。 注意 2025/06/02現在、BETA機能としてリリースされたMemoriesですが、privacy modeが無効でないと使うことができません。 privacy modeを無効にした場合、どのような情報がCursorに共有され、自分の環境で使う際には問題はないのか確認の上利用することをお勧めします。 Memories とは 以前の Cursor Custom modes を利用した cursor-memory-bank のワークフローを試してみる でも取り上げましたが、メモリとはセッション間での命令内容やプロジェクトの知識を構造化したドキュメントとして保存する方法です。これによって新しいセッションを開始した時や、コンテキストウィンドウが肥大化した際にも、プロジェクトの詳細や技術スタック、アーキテクチャ、現在取り組んでいる内容やそれに関する進捗を繰り返し説明する必要をなくす一つの手段です。 Clineでは Blog や 公式ドキュメント で、Memory Bankを導入する方法が紹介されていましたが、Cursorではなかったので、これらの方法を参考に運用していた人も多いのではないでしょうか。ちなみに、Cursorの forum でもMemory Bank導入に関してはFeature Requestとして投稿されて、多くのユーザーが関心を寄せていました。 Generate Memoriesの設定 0.51で追加されたGenerate Memories機能はCursor Settings > Rulesから確認することができます。 Generate Memoriesの設定 冒頭でも説明した通り、privacy modeが無効でないと機能を有効化することはできません。 設定は以上になります! 実際に使ってみた 以降、実際に行った内容は こちら のRepositoryにアップロードしてあります。 検証環境 Cursor Version : 0.51.1 Model : claude-4-sonnet User Rules : なし Project Rules : なし Repositoryの状態 : gitの設定のみ Chat Mode : Agent 目的 : Goを用いた時刻を返すMCP Serverを作成させる 実際のやりとり 下記はMCP Serverの作成とMemoryを作成するにあたり、実際に行ったやりとりです。 User golang を用いて現在時刻や引数として渡された時間分経過した時の時刻を取得できる MCP server を作成して Cursor Golang を使用して時刻を取得できる MCP server を作成いたします。まず、プロジェクトの構造を確認してから実装を進めます。 Ran tool プロジェクトが初期状態のようですね。Golang MCP server を作成していきます。まず、必要なファイル構造を作成します。 MCP Go SDK が存在しないようですね。標準的な MCP プロトコルを実装するために、JSONRPC ベースの実装を作成します。 次に、README.md ファイルを作成して使用方法を説明します。 最後に、テスト用のサンプルスクリプトを作成します。 テストクライアントのファイル名を変更して、型エラーを修正します。 Ran tool 最後に、実際にサーバーが動作するかテストしてみましょう。 go mod tidy go build -o time-mcp main.go cd test && go run client.go 素晴らしい!MCP Time Server が正常に動作しています。最後にプロジェクトの構造を確認しましょう。 cd .. && find . -type f -name "*.go" -o -name "*.md" -o -name "go.mod" | sort 完成! Golang を使用した時刻取得機能を持つ MCP server を正常に作成しました。以下が実装された機能です: 🎯 主な機能 現在時刻取得 ( get_current_time ) 現在の時刻を様々なフォーマットで取得 デフォルトは RFC3339 形式 時間加算 ( add_minutes_to_current_time ) 現在時刻に指定された分数を加算(負の値で過去の時刻も取得可能) 様々なフォーマットに対応 📁 プロジェクト構成 main.go - MCP Time Server のメイン実装 go.mod - Go module 定義 README.md - 詳細な使用方法とドキュメント test/client.go - 動作確認用のテストクライアント time-mcp - ビルドされた実行可能ファイル ✨ 特徴 標準ライブラリのみ使用 - 外部依存関係なし 豊富な時刻フォーマット対応 - RFC3339、Kitchen、JST 等 20 種類以上 MCP 準拠 - Model Context Protocol 2024-11-05 に準拠 JSON-RPC 2.0 - 標準的なプロトコル実装 エラーハンドリング - 適切なエラーレスポンス 日本語対応 - 日本語でのメッセージ表示 🚀 使用方法 # ビルド go build -o time-mcp main.go # 実行 ./time-mcp # テスト cd test && go run client.go テスト結果からも分かるように、サーバーは正常に動作し、現在時刻の取得や時間の加算機能が期待通りに動作しています。MCP プロトコルに準拠しているため、MCP 対応のクライアントから利用することができます。 User ここまでの実装でメモリに保存する必要のある内容はある? Cursor はい、このプロジェクトの実装について、メモリに保存すべき重要な内容がいくつかあります。特に、MCP プロトコルの実装パターンや技術的な決定事項は今後の参考になります。 Ran tool Ran tool メモリに保存した内容は以下の通りです: 保存した知識 1. MCP Server 実装パターン(Golang) 標準ライブラリのみでの MCP サーバー実装方法 必須の MCP メソッド(initialize、tools/list、tools/call) JSON-RPC 2.0 準拠のレスポンス構造 適切なエラーハンドリング Go の時刻フォーマット活用法 2. MCP ツール定義のベストプラクティス JSON Schema を使った InputSchema の適切な定義 日本語対応のアプローチ 引数の柔軟性とデフォルト値設定 型安全な引数解析の実装 E2E テストの戦略 これらの知識は、今後 MCP サーバーを実装する際や、既存のサーバーを拡張する際に非常に有用です。特に、標準ライブラリのみでの実装パターンや、適切なエラーハンドリング、テスト戦略などは再利用可能な重要な知見です。 生成されたMemory 下記は実際にGenerate Memories機能によって生成されたUser Ruleになります。 Golang で MCP(Model Context Protocol)サーバーを実装する際の重要なパターン: 1. **標準ライブラリのみでの実装**: 外部依存関係なしで JSON-RPC 2.0 ベースの MCP サーバーを実装可能。bufio.Scanner + os.Stdin/Stdout で標準入出力通信を実現。 2. **必須の MCP メソッド**: initialize(プロトコルバージョン 2024-11-05、capabilities、serverInfo)、tools/list(利用可能ツール一覧)、tools/call(ツール実行)の 3 つが基本。 3. **レスポンス構造**: MCPResponse は必ず jsonrpc:"2.0"、id、result/error フィールドを含む。ツール実行結果は content 配列内に type:"text"と text フィールドで返す。 4. **エラーハンドリング**: JSON-RPC 標準エラーコード(-32601: Method not found, -32602: Invalid params)を使用。 5. **時刻フォーマット対応**: Go の time 定数(time.RFC3339、time.Kitchen 等)を活用し、カスタムフォーマット(JST 等)も追加可能。 考察 一回目のプロンプトでMCP Server自体の実装は完了していたので、そのタイミングでMemoryが自動で作成されると思ったのですが、作成されなかったので「ここまでの実装でメモリに保存する必要のある内容はある?」のように明示的にメモリに保存するように指示することでMemoryが作成されました。 実際のやりとり ではわかりづらいですが、「はい、このプロジェクトの実装について、メモリに保存すべき重要な内容がいくつかあります。特に、MCP プロトコルの実装パターンや技術的な決定事項は今後の参考になります。」の後の「Ran tool」でUser Rules updateが実行されて、User Rulesに書き込まれています。 ClineのMemory Bankの設定では大事だと思った部分をMemory Bankに保存するルールがあったため、一定明確な指示をしなくても保存してくれていたので、もしGenerate Memoriesでは明確な指示が必要な場合は少し使い勝手が悪そうだなと感じました。 また、設定の際に気づいた人もいるかと思いますが、今回のGenerate Memoriesで作成されるのはあくまでUser Rulesになります。そのため、今までプロジェクトレベルでカスタマイズしたルールを共有するなどのアプローチをとっていて、Generate Memoriesの結果を共有する場合は、一度自分で設定のUser Rulesを開いて、そこから今回取得された設定を別途ファイルに書き出す必要があります。 まとめ まだBETA版ではありますが、Memory Bank機能は個人的にも期待していて、ずっとforumを追っていたので、公式で導入されたのは嬉しい限りです! Memory Bankを実装したMCP Serverを使うアプローチもありますが、それを用いない場合、必要なプロジェクトで都度Memory Bankに関するルールを記述するかグローバルに設定する必要があったので、その設定がなくなるのは一つCursorの強みになるのかなと感じました。 また、Generate Memoriesが導入されたためか、User Rulesも複数設定できるようになり、管理が容易になった点も今回のアップデートの良い点だと思います。ルール設定などまだベストプラクティスが見つけられていないので、Generate Memoriesで作成されたものを採用しつつ、より開発効率を上げていきたいと思います。
アバター
はじめに こんにちは、トモニテで開発を担当している吉田です。 サービスを運営する上で、セキュリティ対策は欠かせません。 本記事では、実際にトモニテが受けた攻撃の事例をもとに、 異常検知から調査の経緯、攻撃の詳細、そして発見された問題点や今後の対応についてまとめています。 セキュリティリスク 現代の Web サービスにおいて、セキュリティリスクは多岐にわたります。代表的なものとしては、 クロスサイトスクリプティング(XSS) 、 クロスサイトリクエストフォージェリ(CSRF) 、 ブルートフォース攻撃 などが挙げられます。これらのリスクは、サービスの信頼性やユーザーの安全を脅かす重大な問題となり得ます。 中でも、SQL インジェクションは古くから存在する攻撃手法の 1 つで、昨年にはある企業の会員制サイト内の過去に使用していたページのセキュリティ設定に不備があり、同サイトのデータベースから顧客情報などが漏洩したといった事案も発生しました。( 参考 ) このように、SQL インジェクションは今なお多くのサービスで脅威となっています。ここからは、実際にトモニテで発生した SQL インジェクション攻撃の記録と、そこから得られた教訓について紹介します。 異常検知と調査の経緯 今回、調査のきっかけとなったのは、Web サービスのレスポンスタイムが長くなっているというアラート通知と、ほぼ同時にデータベースの CPU 使用率が高くなっているというアラート通知があったことでした。 実際にログを確認したところ、多くのリクエストに対しては HTTP ステータス 400 のエラーが返されていました。 しかし、「なぜ多くのリクエストが 400 エラーで弾かれているにもかかわらず、データベースの CPU 使用率が上がっているのか?」という点に疑問を持ちました。 このことから、「もしかしたら一部の攻撃的なリクエストがアプリケーションのバリデーションをすり抜けてデータベースまで到達しているのではないか」と考え、さらに詳細な調査を進めました。 その結果、一部のクエリパラメータに対してバリデーションが不十分であったことが判明し、これが攻撃リクエストに対して HTTP ステータス 200 を返してしまう原因となっていたことが分かりました。 では、実際にどのような攻撃が行われていたのか、具体的な内容を紹介します。 受けた攻撃の詳細 詳細な調査の結果、実際に受けた攻撃はクエリパラメータに SQL 文を埋め込むことで、データベースに対して不正な命令を実行させようとするものでした。 この攻撃の特徴は、パラメータに SQL インジェクションのペイロードを仕込むことで、データベースの動作を意図的に遅延させたり、条件分岐を利用して情報を引き出そうとする点です。 特に、DBMS_PIPE.RECEIVE_MESSAGE や sleep 関数など、タイムベースのブラインド SQL インジェクションが試みられていました。 実際にログに残っていたリクエスト例をいくつか挙げます。 1. tag_id にランダムな値を指定したリクエスト /path?tag_id=dn8r1je0elkm.html&page=1&per_page=20 推測される意図: 一見普通のリクエストに見えますが、tag_id にランダムな値(dn8r1je0elkm.html)が入っています。 これは「存在しない ID」や「想定外の値」を入れることで、アプリケーションのエラーハンドリングやレスポンスの違いを観察し、脆弱性の有無や内部構造を探ろうとしている可能性があります。 2. page パラメータに OR 条件を含めたリクエスト /path&page=-1 OR 2+138-138-1=0+0+0+1&per_page=20 推測される意図: page パラメータに「-1 OR 2+138-138-1=0+0+0+1」という SQL 文が埋め込まれています。 これは SQL インジェクションの典型的なテストで、「OR」条件を使って本来の条件式を無効化し、任意の条件でデータを取得できるかを試しています。 この場合、2+138-138-1=0+0+0+1 は常に真(1=1)になるため、全件取得や認証回避などが狙いと考えられます。 もしアプリケーションがこの注入を受け入れてクエリを実行した場合、通常はデータが返されないはずの状況(例: 存在しない page -1)でも、1=1 が真になることでデータが返されるといった挙動の変化が起こる可能性があります。 このリクエストは Web アプリケーションが SQL インジェクションに対して脆弱であるかどうかを探る、初期段階の偵察攻撃と考えられます。 3. Oracle の DBMS_PIPE.RECEIVE_MESSAGE を使ったリクエスト /path?page=1'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'&per_page=20 推測される意図: page パラメータに「'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'」という Oracle 特有の関数が使われています。 これは DBMS_PIPE.RECEIVE_MESSAGE を使って、SQL の実行を 15 秒間遅延させる(タイムベースのブラインド SQL インジェクション)攻撃です。 レスポンスの遅延時間によって、SQL インジェクションが成立しているかどうかを判別しようとしています。 4. MySQL の sleep 関数を使ったリクエスト /path?page=(select(0)from(select(sleep(15)))v)/'+(select(0)from(select(sleep(15)))v)+'"+(select(0)from(select(sleep(15)))v)+"/&per_page=20 推測される意図: page パラメータに「(select(0)from(select(sleep(15)))v)」など、 MySQL 系の sleep 関数 を使った SQL インジェクションが仕込まれています。スリープが正常に(中断なしで)復帰すると、0 が返されます。 これもタイムベースのブラインド SQL インジェクションで、SQL が実行されると 15 秒間レスポンスが遅れるため、脆弱性の有無を確認できます。 いずれも「SQL インジェクションが成立するかどうか」を探るためのテストや、タイムベースの手法で脆弱性の有無を確認しようとする攻撃です。 特に 3・4 番目は、データベースの種類(Oracle か MySQL か)によって使い分けている点も特徴的です。 これらのリクエストは、アプリケーションが入力値を適切に検証・サニタイズせずに SQL クエリを組み立てている場合、データベースに対して不正な操作を行うことができてしまう危険性を示しています。 攻撃の影響とシステムの挙動 調査の結果、基本的には不正なパラメータに対しては HTTP ステータス 400 を返していました。 しかし一部の攻撃リクエストに対して HTTP ステータス 200 が返っていたことがわかりました。 幸い、クエリのビルド時に正規のパラメータのみを受け付けていたため SQL インジェクション自体は成立していなかったことが判明しています。 ただし、攻撃者が仕込んだインジェクションペイロードがそのままアプリケーションに渡っていたことは事実です。 仮に SQL インジェクションが成立していた場合、データベース内の情報漏洩や、サービスの停止、データの改ざんなど重大な被害につながる恐れがありました。 特に、タイムベースのブラインド SQL インジェクションが成立していれば、攻撃者は時間をかけてデータベースの中身を窃取することも可能です。 発見された問題点と今後の対応 今回の調査で、一部のリクエストパラメータに対してバリデーションが適切に行われていなかったことが判明しました。 特に、攻撃リクエストの中には本来不正な値であるにもかかわらず、HTTP ステータス 200 を返してしまっていたものがありました。 これは、パラメータの値に対する十分な検証が行われていなかったことが原因です。 本来、許可された文字列のみを受け付けるべきですが、現状は任意の文字列がそのまま渡ってしまう状態でした。 この問題に対し、該当するパラメータに対して正規の値のみ受け付けるようホワイトリスト(許可リスト)によるバリデーションを追加し、不正な値が渡らないように修正を行っています。 まとめ 今回の事例を通じて、たとえ SQL インジェクションが実際に成立していなかったとしても、入力値のバリデーション不足は大きなリスクとなることを改めて認識しました。 また、調査の中で一部のパラメータに対するバリデーションが不十分だったことが、攻撃リクエストに対して HTTP ステータス 200 を返してしまう原因となっていたことも判明しました。 今後は、入力値に対して適切なバリデーションを徹底し、同様のリスクが他のパラメータにも潜んでいないか定期的に見直しを行っていきます。 セキュリティ対策は一度実施すれば終わりではなく、継続的な改善と運用が不可欠です。今後も気を緩めることなく、サービスの安全性向上に取り組んでいきます。
アバター
はじめに こんにちは、リテールハブ開発部でバックエンドエンジニアをしているホシと言います。 現在、小売アプリの開発でLaravel11を利用してAPI開発を行っています。 今回はとても便利で、開発効率を大きく上げてくれるツール「LaravelのEloquent ORM」についてお話できればと思います。 ただ、Eloquentに限った話ではなくORM全体の話でもあるのですが、使い方を間違えるとパフォーマンス低下や予期しないバグを引き起こすこともあります。 実際に使用してみて、便利さの裏にある注意点や、SQLの知識・理解が非常に重要であることを実感したので、今回はその点についてお話しできればと思います。 1. Eloquentとは? Laravelでのデータ操作をシンプルにするORM Eloquentは、Laravelに標準搭載されているORM(Object-Relational Mapping)です。 これを利用することで、データベースのテーブルをPHPのオブジェクトとして扱えるようになります。 通常、データベース操作を行うにはSQLを書く必要がありますが、Eloquentを使えばSQLを使用せず、PHPコードだけでデータの取得・更新・削除などができます。 たとえば、usersテーブルのID=1のnameデータを取得する場合、SQLの場合は以下のように書きます。 SELECT name FROM users WHERE id = 1 ; ただ、上記はデータベースから取得するためのSQLというだけで、 実際のコードでは、このSQLを実行した上で、結果からnameを取り出す処理を別途記述する必要があります。 Eloquentを使用すれば、上記の処理が含まれた状態でデータ取得できます。 その代わり、Eloquentはモデルの定義が事前に必要です。 このコードでID=1のユーザのnameを取得できます。 <?php $ users = User :: find ( 1 ) ; $ users -> name ; // ユーザ名の取得 Userはusersテーブルに対応する「モデル」クラスです。 モデルを通じて、EloquentはSQLを自動で生成・実行してくれます。 このモデルを使用することで、取得したデータも簡単にアクセスできます。 モデルとテーブルの関係 Laravelでは、モデル名とテーブル名は命名規則に従って自動的に対応してくれます。 あらかじめUserモデルを定義しておくことで、上記のようなEloquentを使用したデータ取得ができます。 モデル名:User 対応テーブル名:users また、Eloquentの強みの1つが「リレーション(テーブル間の関連)」を簡単に扱える点です。 モデル間のリレーションも簡単に定義することができます。 例えば、1人のユーザが複数の投稿(Post)を持つ「1対多」の関係を定義する場合: <?php // Userモデル public function posts () { return $ this -> hasMany ( Post :: class ) ; } と定義することで、ユーザの投稿一覧も簡単に取得できるようになります。 以下のコードでID=1のユーザの投稿内容を取得できます。 <?php $ user = User :: find ( 1 ) ; $ posts = $ user -> posts; SQLのようにJOINを意識せず、オブジェクトの形で関連データを取得できます。 このように、データの取得からアクセスまでを一貫して扱える点が、Eloquentの大きな利点です。 2. Eloquentの「落とし穴」:気付きにくいN+1問題とその回避法 前述の通り非常に簡単にデータ取得ができてしまい、様々なケースでも何となくの理解で私自身使用していました。 ただ、実際は裏でどのように動いているのかを理解して使用しないと思わぬ落とし穴があることがわかりました。 そこで、Eloquentの動きを理解せず使用していると特に遭遇しやすいN+1問題についてお話します。 N+1問題とは? 例えば、ユーザ一覧とそれぞれの投稿数を表示したいとします。 usersテーブルとpostsテーブルは1対多の関係で定義しているとします。 ユーザ毎の投稿数を表示するコード: <?php $ users = User :: all () ; foreach ( $ users as $ user ) { echo $ user -> posts -> count () ; } 一見シンプルで正しく動作しているように見えますが、実際にはパフォーマンスが大幅に低下する書き方です。 データ取得の流れとして、 最初にusersテーブルを1回検索 その後、各userに対して postsテーブルへクエリ(投稿数N回)を繰り返し検索 つまり、合計で1 + N回のクエリが実行されてしまいます。 例えばユーザが1000人いれば、クエリは合計で1001回実行されることになり、パフォーマンスに大きな影響を与えます。 開発時はデータ数も限られているため、特に遅くなるわけでもエラーになるわけでもなく正常に取得できるため、動きを理解していないと問題に気付きにくいです。 ORMにおける「Eager」と「Lazy」の考え方 Eloquent(ORM)でリレーションを扱うときに登場する重要な概念が、 「Eager Loading(イーガーローディング)」 と 「Lazy Loading(レイジーローディング)」 があります。 どちらもリレーション先のデータを取得する方法ですが、 パフォーマンスに大きな影響 を与えるため、使い分けが非常に重要です。 Lazy Loading(遅延読み込み) デフォルトでは、Eloquentはリレーションを「必要になったとき」に読み込みます。 これが Lazy(レイジー)=遅延 読み込みです。 この方法の大きなメリットは上述の通り必要な時に読み込むということになります。 例えばメインデータを取得時点では不要なケースで、ある操作や表示時などの必要な時だけに読み込むようにすれば、 余計な読み込みが減り、リソースを効率的に使用できます。 しかし、その代わり繰り返し取得しないといけないケースなどではパフォーマンスに大きな懸念があります。 Eager Loading(即時読み込み) 上記を回避するために使うのが、Eager Loadingです。 リレーションを最初からまとめて読み込む方法です。 コード例: 「with」を使用して関係テーブルを記述します。 <?php $ users = User :: with ( 'posts' ) -> get () ; foreach ( $ users as $ user ) { echo $ user -> posts -> count () ; // 追加クエリなしでアクセスできる } この場合、発行されるSQLは以下の2回のみになります。 SELECT * FROM users; SELECT * FROM posts WHERE user_id IN (...); →これがN+1問題の回避につながります。 ただし、Eager Loadingにも注意すべき点があります。 上記はwith指定があるため、Eager Loadingとして、 SELECT * FROM posts WHERE user_id IN (...); を一緒に実行して、1回の検索で必要なposts情報が取得できています。 しかし、このIN句に入るIDがどの程度の規模かをしっかり把握しておかないと予期せぬ大量データの事前取得につながります。 ここがSQLのJOINを使用した考え方と大きく異なるところかと思います。 もちろんJOINでも大量データの考慮は必要ですが、JOINの場合は1クエリで取得でき、より大量データ取得とのパフォーマンスは高くなります。 こちらの件はうまく使い分けることの重要性の話でもあり、後ほど説明できればと思います。 以下のSQLのように、 select * from users inner join posts on users.id = posts.user_id この結合であれば、仮に1万のusersデータがあっても問題なく取得はできますが、 Eager Loadingでは、user_id IN (...) のように複数IDを一括で取得するため、IN句に大量のIDが含まれる可能性があり、DBの制約、メモリ使用量や処理時間に影響を及ぼします。 この事象が起きることは常に考慮してEloquentを使用する必要があります。 N+1を避けるには? リレーションをループで使うときは必ずwith()の使用を検討します。 また、例えばusersにposts、postsにcommentsが関連している場合、正しくwithを指定します。 <?php User :: with ([ 'posts' , 'posts.comments' ]) -> get () ; とそれぞれ指定する必要があります。 これも理解していれば特に問題はないのですが、私自身「'posts.comments'」だけでどちらのテーブルも入っているので十分だと思っていた時期がありました。 普通に動くので問題があることがしばらく気付けず・・・。 また、当たり前ですがプログラム上で繰り返し取得するようなケースを書いている場合もN+1問題と同様の事象になるので、 そういったコードについても対策は必ず行う必要があります。 3. Eloquentを使用する場合もSQL知識、理解は非常に重要 データを取得する際、シンプルな取得であれば前述したような1行、2行で記載でき、特にSQLを意識する必要はあまりありませんが、 少し複雑な条件であったり、複数のテーブルをまとめて取得するようなケースでは、Eloquentのコードだけでは、どのようなSQLが生成・実行されているかを把握しづらいことがあります。 少ないデータやシンプルなテーブル構成というのは実際のサービスではほとんどないと思いますので、 コードを実装する上では結局どのようなSQLを使用してデータを取得しているのかをしっかりと把握する必要があります。 特に重要なSQL理解 INNER JOIN、LEFT or RIGHT JOINなどの結合仕様 リレーション先のテーブルに対してのWHERE条件のための結合やサブクエリの知識 今後のデータ数に合わせたパフォーマンス考慮、インデックス設定の検討 実行SQLを確認する方法(何となく取得できていそうを避ける) Eloquentを使っていると、裏でどんなSQLが発行されているか分かりづらいことがあるため、 開発時は常にどのようなSQLが発行されているかを以下のログ出力を利用して確認できるようにしておきます。 私の失敗例: <?php $ query = User :: where ( 'email' , 'like' , '%@example.com' ) ; dd ( $ query -> toSql ()) ; 上記は以下のSQLが返ってきます。 select * from `users` where `email` like ? しかし、実際の値が分からないことに加え、Eloquentでは単なるSQLの実行とは異なり、 モデル経由で値を取得する際に、Lazy Loadingによって上記以外の場所で意図せず追加のSQLが実行されているケースもあります。 ここで私は他にSQL発行していることに気づけず、どのタイミングでどのようにpostsのデータが取得できているのかをしばらく調べることになりました・・・。 正しい確認方法:DB::listen()を使用する <?php use Illuminate\Support\Facades\DB; DB :: listen ( function ( $ query ) { logger () -> info ( 'SQL実行ログ' , [ 'sql' => $ query -> sql, 'bindings' => $ query -> bindings, 'time' => $ query -> time . ' ms' , ]) ; }) ; ※このログ出力は開発環境のみに限定するなど制御することで、不要なログ蓄積やセキュリティリスクを防げます。 このコードをLaravelにあるAppServiceProviderのboot()メソッドに仕込むことで、アプリ全体のクエリをログで確認できます。 これにより、どのようなSQLが実行されているかをすべてログで確認することができるようになります。 なぜこのログ出力が大事か ログ設定を行った場合に実際に出力されるログ内容です。 [2025-05-XX 12:34:56] local.INFO: SQL実行ログ { sql: "select * from `users` where `email` like ?", bindings: ["%@example.com"], time: "1.22 ms" } このログ出力は、開発において非常に重要な情報源となります。 想定しているクエリが発行されているか確認できる。 パフォーマンスのボトルネックを見つけやすくなる。(実行時間、実行回数など) 中間処理やAPIレスポンスの生成時など、意図していなかった箇所でクエリが実行されていることがわかるようになる。 他にツールの設定などでN+1問題を見つける方法などもありますので、そういったものも併用するとより解消しやすいかと思います。 ただ、上記を行うだけでも格段にSQLの問題は見つけやすくなります。 これまでの内容を踏まえ、Laravelのデータ操作の最適な方法は? Laravelには2つの主要なデータベース操作手段があります: Eloquent :LaravelのORM。モデルベースで直感的・オブジェクト指向。 Query Builder :SQL構文ベース。柔軟で高速。 両者には得意不得意があり、 場面によって使い分けることが大切 です。 以下のようなケースでは無理にEloquentにこだわらず、状況によってはQuery Builderの使用も検討すべきです。 集計・統計クエリ(GROUP BY / COUNT / JOIN)を使用したデータ取得 大量データの取得、更新処理など サブクエリ・複雑なWHERE句を使用したデータ取得 モデルが不要な一時的なテーブル操作やJOINを使用したデータ取得 それぞれの補完関係を表にしてみました。 EloquentとQuery Builderの使い分け比較 比較項目 Eloquent Query Builder 可読性 ◎ ◯(SQLに近い) モデルの活用 ◎ ✕ 複雑なクエリ構造 △ ◎ パフォーマンス △(特に大量処理) ◎ 柔軟な構文制御 △ ◎ チーム開発との親和性 ◎(モデルベースで役割明確) ◯(要コメントや命名工夫) まとめ:Eloquentを安全に、賢く使うために いかがでしたでしょうか。 Eloquentは非常に便利ですが、SQL知識、理解があってこそ真価を発揮するのではないかと思います。 それぞれの特性を活かして、最適なデータ取得ができるようにしていきたいです。 また、実際に実行されるSQLを常に意識し、必要に応じてログを確認する習慣を持つことが重要だと思います。 特にAPI作成の上でパフォーマンスを意識するなら、SQLの知識も必須かと思います。 Eloquentの便利さを活かしながらも、裏側の仕組みや発行されるSQLを意識し、より安定した高パフォーマンスな開発ができるようにしていきたいです。 もしSQLをログで確認していない方は、ぜひログ設定をして実行されるSQLを確認していきましょう! 今回の記事が少しでも皆さんの開発のヒントになれば幸いです。 最後までお読みいただきありがとうございました。
アバター