TECH PLAY

株式会社エブリー

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

410

タイトル 目次 はじめに SRE Kaigi 2026 とは? 参加レポート 生成AI時代にこそ求められるSRE SRE とプロダクトエンジニアは何故分断されてしまうのか 開発チームが信頼性向上のためにできること: 医療SaaS企業を支える共通基盤の挑戦 おわりに はじめに こんにちは。2025年4月にソフトウェアエンジニアとして新卒入社した 黒髙 です。普段は デリッシュキッチン の開発に携わっています。 2026年1月31日(土)に中野セントラルパーク カンファレンスで開催された SRE Kaigi 2026 に参加してきました。本記事では、特に印象に残ったセッションをご紹介します。 SRE Kaigi 2026 とは? 2026.srekaigi.net SRE Kaigi は、Site Reliability Engineering(SRE)コミュニティの活性化と技術的な交流を促進することを目的としたカンファレンスです。第2回となる今回は「Challenge SRE!」をテーマに掲げ、SREを前に進めるための挑戦を応援することを目指して開催されました。 弊社にはSREエンジニアや専任のSREチームは存在せず、プロダクトごとの開発チームがそれに近い役割を担っています。私自身もその組織の一員として知見を高めたく、「SREの考え方を開発チームとしてどう取り入れるか」という視点で参加しました。 入場時には様々なノベルティをいただきましたが、『わかばちゃんと学ぶSRE』という冊子は、初めてSREに触れる私にとって理解の助けになりました。 ノベルティ 会場には3つのセッションルームに加え、多様なスポンサーブースや書籍販売コーナーがありました。さらにマッサージブースや屋台での軽食提供、コーヒー提供などバラエティ溢れる企画があり、一日を通してとても賑わっていました。 参加レポート 生成AI時代にこそ求められるSRE 発表者: 山口能迪 さん speakerdeck.com AWSの山口能迪さんによる、AI時代におけるSREの価値を再定義するセッションでした。「AIがコードや設定を書く時代に、SREは不要になるのか?」という問いに対して、「SREの重要性は、かつてないほど高まっている」と明確に答える内容でした。 セッションでは「AIは組織の能力を増幅するアンプである」という表現が使われていました。優れた組織はより強化され、課題のある組織は弱点を増幅させる。開発速度が上がる一方で、システムの不安定さや変更失敗率も増大しうるということです。その上で、SREの責務を「AIの爆発的な生産性を、カオスではなく、持続可能なユーザー価値へと変換する」と定義していたのが印象的でした。 また、SREがAIにもたらす価値は「コンテキスト」と「ガードレール」という2つの軸で整理されていました。 コンテキストとは、AIがより良く動作するための下地のことです。LLMは学習時点の一般的な情報しか知らないため、システム固有の情報はコンテキストとして与える必要があります。具体的にはオブザーバビリティによるテレメトリーの収集や、Infrastructure as Codeによる設定のコード化、ポストモーテムの整備などが挙げられていました。 一方、ガードレールはAIの失敗を予防・回復するための保険です。AIが生成したコードに存在しないライブラリが含まれるリスクへの対策としてのサプライチェーンセキュリティや、組織ポリシー違反を防ぐPolicy as Code、SLOに基づいた自動ロールバックなどが紹介されていました。 SRE自体がAI開発の文脈の中心にいる印象はあまりありませんが、AIが当たり前のように受け入れられ開発プロセスが成熟した今だからこそ、次のフェーズとしてSREを一連のデリバリーパイプラインに適切に組み込めるかどうかが今後の鍵になると実感しました。 SRE とプロダクトエンジニアは何故分断されてしまうのか 発表者: 渡邉美希パウラ さん speakerdeck.com ワンキャリアの渡邉美希パウラさんによる、SREチームとプロダクトチームの間に生じる「分断」の構造と解消アプローチについてのセッションでした。渡邉さん自身がSREチームからフロントエンドエンジニアへ異動した経験を持ち、両方の立場を知る当事者として語られていたのが印象的でした。 セッションでは、分断を引き起こす構造的要因として「受発注関係の固定化」「目指すベクトルのズレ」「1対多が生む情報の非対称性」の3つが挙げられていました。SREが横断的に複数プロダクトを担当する体制では、「パフォーマンス改善はSREの仕事」という意識が生まれやすく、受発注関係が固定化してしまう問題があります。また、プロダクトチームは「価値提供・スピード」、SREは「信頼性・安定」を重視するため、本来は同じユーザー起点であるはずなのに対立構造を生みやすいとも言えます。こうした分断は仲の良し悪しではなく、役割分担が生む必然的な帰結として整理されていました。私たちの組織にはSREこそいないものの、「過度な役割分担が心理的な壁になる」「受発注関係が固定化する」という現象はありありと想像できました。 解決アプローチとしては「バウンダリー・スパニング」- 境界を意図的になくし、人や情報を繋ぐことで価値を創造するというリーダーシップ理論を参考に、Reflecting(反射)、Mobilizing(結集)、Transforming(変形)という3つのステップで実践されていました。具体的には、インフラ変更のリポジトリをプロダクトチーム側に統合して境界線を排除したり、SLOを両チーム共通の評価指標として定例で議論したり、チーム間で人材を異動させたりといった施策が紹介されていました。 当事者として経験しながら、同時に観察者として構造を見抜いているような視点の鋭さに感銘を受けました。「足並みが揃いづらい」という課題感を、再現性のあるフレームワークで整理し、フェーズに分けてアクションを起こしていることも学び深い点でした。最後に「結局は全員が視座高くオーナーシップを持てば分断は問題にならない」と締められていましたが、シンプルながら本質を捉えた言葉であると感じ、自分自身も心がけていきたいと思いました。 開発チームが信頼性向上のためにできること: 医療SaaS企業を支える共通基盤の挑戦 発表者: kosui さん talks.kosui.me カケハシのkosuiさんによる、医療SaaS企業の認証権限基盤チームが信頼性向上に取り組んだ事例紹介でした。キーメッセージは「Embedded SRE不在でも、開発チームが設計を"自分ごと"として運用し続けることで信頼性は向上できる」というもので、自分たちの状況にも通じる内容でした。 医療SaaSは患者情報という極めて機密性の高いデータを扱うため、コンプライアンス、高可用性、トレーサビリティ、テナント分離といった厳しい品質要求があります。セッションでは、小規模チームがこれらの要求を満たすために採用した具体的な設計として、DBレベルでテナントを強制分離する行レベルセキュリティ(RLS)、過去データへの即座のアクセスを可能にするDelta Lakeのタイムトラベル機能、「何が起きたか」を完全に記録するドメインイベント、強い整合性と独立デプロイを両立するサービスベースアーキテクチャなど、詳細な設計内容が紹介されていました。これらの導入により、障害時の原因特定が2〜3時間から30分以内に短縮されるなど、具体的な成果も示されていました。 SREの役割や組織についてのセッションが多かった今回ですが、その中での具体的なアーキテクチャ設計の話は新鮮で聞き応えのあるものでした。ドメインごとに異なる品質要求を、限られた人的・時間的リソースの中でどう満たすか。その試行錯誤やトレードオフの判断は興味深く、同時に設計一つがプロダクトの今後を大きく左右する責任の重さも感じました。自分が設計に携わる際にも、SRE的な観点と「設計意図の浸透」を意識していきたいと思います。 おわりに AI時代におけるSREの再定義、チーム間の分断を防ぐ組織設計、開発チーム自身が信頼性を担うアーキテクチャ設計と、切り口はそれぞれ異なりますが、共通して感じたのは「信頼性は誰かに任せるものではなく、自分ごととして向き合うもの」というメッセージでした。 SRE Kaigi 2026全体を通して、SREという領域の幅広さと奥深さを知ることができました。また、「受発注関係の固定化」「目指すベクトルのズレ」「情報の非対称性」といった構造的な課題感は他のセッションでも度々取り上げられており、多くの組織で共通していることを実感しました。 個人的には、山口さんのセッションで語られた「AIの爆発的な生産性を、カオスではなく、持続可能なユーザー価値へと変換する」という言葉が印象に残っています。開発速度の向上も、最終的にはユーザーへの安定した価値提供があってこそ意味を持つ。その視点を忘れずに、これからは開発ライフサイクル全体の改善にも寄与していきたいと感じました。
アバター
はじめに こんにちは、リテールハブ開発部の杉森です。 近年、AIを活用した開発ツールが急速に普及しています。私たちのチームでも積極的にAIツールを導入し、要件定義でのユーザーストーリー作成、設計ドキュメントの生成、コードの自動補完、テストコードの生成など、各開発フェーズの作業効率化を図ってきました。 しかし、個々の作業は確かに早くなっているのに、プロダクト開発フロー全体を見ると期待したほどの生産性向上を実感できないという課題に直面しました。 本記事では、この課題に対するアプローチとして導入を検討しているAI-DLC(AI-Driven Development Lifecycle)について紹介します。 AI-DLCとは AI-DLCは、AWSが提唱するAIネイティブな開発方法論です。方法論のホワイトペーパーで理論的な枠組みが定義されており、これを実装するためのワークフローがaidlc-workflowsとしてGitHub上で公開されています。 AWSの公式ブログでは、現在のAI活用における2つのアンチパターンが指摘されています。 AI-Assisted: 人間が設計を主導し、AIはコード補完など狭い範囲の支援にとどまる。生産性向上は限定的で、AIの能力を十分に引き出せない AI-Managed: 複雑な問題をAIに丸投げし、自律的にすべてを解決することを期待する。出発点が曖昧なためAIが多くの仮定を立て、プロトタイプ以外ではほぼ機能しない AI-DLCは、これらのアンチパターンに対するアプローチとして設計されています。AIが作業計画の作成やタスク分解を主導し、人間がその内容を検証・承認し、AIが承認された計画に基づいて実行するというサイクルで、開発ライフサイクル全体を進めます。 AI駆動開発ライフサイクル(AWS公式ブログ) aidlc-workflows(GitHub) 従来の開発手法との違い AI-DLCは、既存の開発手法にAIを後付けするのではなく、AIを前提とした開発プロセスをゼロから設計しています。ホワイトペーパーでは、以下の設計思想が示されています。 AIが会話を主導する: 従来は人間がAIに指示を出していたが、AI-DLCではAIがタスク分解や提案を行い、人間は承認・判断に集中する Intent / Unit / Bolt: ビジネス目標(Intent)を作業単位(Unit)に分解し、数時間〜数日の短いサイクル(Bolt)で実装を回す。ScrumのSprintに近いが、サイクルが短い 各ステップで人間がチェックする: AIの出力を段階ごとに検証し、誤りを早期に検出する。ホワイトペーパーでは「損失関数のように機能する」と表現されている 設計技法を方法論に組み込む: ScrumやKanbanがチームに委ねていたDDD等の設計技法を、方法論の一部として標準化する aidlc-workflowsの設計原則 aidlc-workflowsは、上記の設計思想を実装するにあたり、以下の5つの設計原則に基づいています。 原則 説明 No Duplication 設定やルールを一箇所で管理し、重複を排除する Methodology First 特定のツールに縛られず、方法論そのものを軸にする Reproducible ルールを明文化し、使うAIモデルが変わっても結果がぶれないようにする Agnostic IDE・エージェント・モデルを問わず動作する Human in the Loop 重要な判断には必ず人間の承認を挟む 3フェーズ構成 AI-DLCは、以下の3つのフェーズで構成されています。 INCEPTION PHASE: WHATとWHYの決定 CONSTRUCTION PHASE: HOWの実装 OPERATIONS PHASE: デプロイと監視(aidlc-workflows上は未実装) INCEPTION PHASE 「何を作るか(WHAT)」「なぜ作るか(WHY)」を決定するフェーズです。方法論のホワイトペーパーでは「Mob Elaboration」というプラクティスとして定義されており、共有画面を使ってチーム全体でAIの質問と提案を検証します。AIがビジネス意図(Intent)を明確化する質問を投げかけ、ユーザーストーリー、非機能要件、リスク記述を生成し、凝集度の高い作業単位(Unit)へ分割します。 aidlc-workflowsでは、このフェーズが以下のステージに細分化されています。 ステージ 説明 Workspace Detection プロジェクトの状態を分析(新規/既存の判定) Reverse Engineering 既存コードベースの理解(Brownfieldの場合) Requirements Analysis 要件の収集と整理 User Stories ユーザーストーリーの作成 Workflow Planning 実行計画の策定 Application Design アプリケーション設計 Units Generation 作業単位への分割 CONSTRUCTION PHASE 「どう作るか(HOW)」を決定し、実際にコードを生成するフェーズです。方法論のホワイトペーパーでは、Domain Design(ビジネスロジックのドメインモデリング)→ Logical Design(非機能要件を含むアーキテクチャ設計)→ Code & Unit Tests(コードとテストの生成)→ Deployment Units(デプロイ可能な成果物の構築)という流れで進みます。「Mob Construction」でチームがリアルタイムで技術的決定とアーキテクチャの選択を行います。 aidlc-workflowsでは、このフェーズが以下のステージに細分化されています。 ステージ 説明 Functional Design 機能設計(ユニットごと) NFR Requirements/Design 非機能要件の設計 Infrastructure Design インフラ設計 Code Generation コード生成 Build and Test ビルドとテスト OPERATIONS PHASE デプロイと監視を担当するフェーズです。方法論としては定義されていますが、aidlc-workflowsには含まれておらず、将来的にワークフローが追加される予定です。 対応プラットフォーム 公式では以下のプラットフォームがサポートされています。 Kiro CLI Amazon Q Developer IDE plugin Kiro IDE(Coming Soon) 試してみる 導入の背景 私たちのチームの課題は、まさにAI-Assistedパターンに該当します。AIを個々の作業の効率化には活用できているものの、生産性向上は限定的にとどまっていました。 私たちのチームでは、Kiro CLIをすぐに使える環境ではなかったため、Claude Code向けにカスタマイズして使用しました。AI-DLCはツールに依存しない設計を謳っているため、ルールファイルを調整すれば他のAIツールでも問題なく適用できると考えています。 Claude Code向けのカスタマイズ 以下のようにClaude Code向けにカスタマイズしました。 1. カスタムコマンド(スキル)の作成 .claude/commands/aidlc.md にワークフロー定義を配置し、 /aidlc コマンドで起動できるようにしました。 .claude/ ├── commands/ │ ├── aidlc.md # メインワークフロー │ ├── aidlc-pr.md # PR作成用 │ └── aidlc-archive.md # アーカイブ用 └── aidlc-rule-details/ ├── common/ # 共通ルール ├── inception/ # INCEPTIONフェーズ ├── construction/ # CONSTRUCTIONフェーズ └── operations/ # OPERATIONSフェーズ 2. ルールファイルの分割 各ステージの詳細指示を .claude/aidlc-rule-details/ 以下に分割配置しています。これにより、AIが必要なタイミングで必要なルールのみを読み込み、コンテキストを効率的に使用できます。 クーポン機能を題材にした検証 AI-DLCの有効性を検証するため、小売向けアプリのクーポン機能開発を題材に検証を実施しました。 検証概要 対象システム: Flutter + Laravel + Vue.js + Goで構成されたマルチプラットフォームアプリ 題材: ポイント後付けクーポンと即時値引きクーポンの2種類 検証範囲: モバイルアプリ、管理画面、バックエンドAPI、バッチ処理 チーム構成: PdM1人+エンジニア2人 検証の進め方 「クーポン機能を追加したい」というビジネス意図(Intent)を起点に、AI-DLCのフェーズに沿って進めました。 INCEPTION PHASE(3人で実施): AIが要件を深掘りする質問を投げかけ、ユーザーストーリーや非機能要件を生成。PdMとエンジニアがその内容を検証・修正し、作業単位(Unit)に分割 CONSTRUCTION PHASE(1人で実施): Unitごとにドメイン設計、コード生成、テスト生成を実施。各ステージでAIの出力を確認し、承認・修正を繰り返した 今回は検証目的だったこともあり、各フェーズ半日ずつの計1日で実際に動くものまで作成できました。Inceptionフェーズの要件・設計をより作り込み、Constructionフェーズではガードレールの整備やAIが自律的に改善できる体制を組むことで、さらに短縮できる余地があると感じています。 実際の様子 AIからの深掘り質問(Inceptionフェーズ) Requirements Analysisでは、AIが要件の曖昧な部分を選択肢付きで質問してきます。以下はその一例です。 AI : クーポン利用状態の管理について確認させてください。ユーザーがクーポンを「利用」した後の状態管理はどうなりますか? A) 1回利用したら即座に使用済みになる(再利用不可) B) 有効期限内であれば何度でも利用可能 C) クーポンごとに利用回数を設定可能(1回、3回、無制限など) AI : クーポンの種類と適用範囲について確認させてください。 A) 全ての種類が両方のクーポンタイプで使用可能 B) クーポンタイプごとに使用可能な種類が決まっている このように、AIが仕様の選択肢を提示し、人間が判断するというサイクルでRequirements Analysisが進みます。初回の質問10問、追加の深掘り質問6問を経て、要件定義ドキュメントが生成されました。 人間による設計修正(Inceptionフェーズ) Application Designでは、AIが設計の選択肢を提示し、人間が判断するケースがありました。 人間 : アクティブユーザーではないユーザーにもレコードが作成されてしまいませんか? AI : 2つの選択肢があります。 A) クーポン公開時に全ユーザー分のuser_couponsレコードを作成 B) クーポン利用開始時にのみuser_couponsレコードを作成 人間 : B 生成されたユーザーストーリー(Inceptionフェーズ) User Storiesでは、管理者向け6件、会員ユーザー向け6件、システム向け1件の計13件が生成されました。以下はその一部です。 US-01: クーポン新規作成 As a 管理者 I want to 管理画面から新しいクーポンを作成したい So that 会員ユーザーに対してキャンペーンを提供できる Acceptance Criteria: クーポンタイプ(ポイント後付け/即時値引き)を選択できる クーポン名と説明文を入力できる 有効期限(開始日・終了日)を設定できる 対象店舗を選択できる 生成されたコード(Constructionフェーズ) Constructionフェーズでは、Unitごとにドメイン設計 → コード生成 → テスト生成が進みます。最終的に以下の規模のコードが生成されました。 Unit 対象 生成ファイル数 主な成果物 Backend Laravel 51ファイル Enum, Model, Migration, Service, Controller, Test Dashboard Vue.js 16ファイル Composable, Component, Schema, Page Mobile Flutter 38ファイル Entity, Repository, Provider, Widget, Page わかったこと / 今後の展望 良いと感じた点 実際にAI-DLCを触ってみて、以下の点が良いと感じました。 Human in the Loopの実現: AIが実行し、人間が監視するという関係性が明確。各ステージで人間の承認が必要なため、重要な意思決定は人間がコントロールできる コンテキストの保存と再開: aidlc-state.md でプロジェクトの状態を追跡しているため、セッションが途切れても前回の続きから再開できる ドキュメント化による追跡可能性: audit.md にすべてのやり取りが記録されるため、なぜその決定をしたのかを後から追跡できる 適応的なワークフロー: プロジェクトの複雑さに応じて、実行するステージが自動的に調整される 試した上で見つかった課題 Inception前の準備の必要性 今回「クーポン機能を追加したい」というリクエストからInceptionを開始しましたが、背景知識や「なぜこの機能が必要なのか」がアウトプットに反映されにくいことがわかりました。また、要件の解像度が低い状態でInceptionを始めると、議論が発散しやすくなります AI-DLCのInceptionに入る前に、ビジネス背景や目的を整理するステップが必要だと感じました。 仕様とAI実装のギャップ Inceptionフェーズで仕様を決め切った上でも、以下の2つの問題が発生しました。 仕様の記載漏れ: Inceptionフェーズで決めた仕様に漏れがあり、Constructionフェーズで初めて気づくケース。例えば、APIレスポンスのラッパー形式やお気に入り店舗のパラメータなど、実装段階で判明した考慮漏れがありました 仕様通りに実装されない: 仕様として記載されているにもかかわらず、AIが異なる実装をするケース。例えば、既存の認証方式と異なるパターンで実装したり、既存のアーキテクチャパターンに従わない実装が生成されることがありました 前者は要件定義やアプリケーション設計の精度を上げていく必要があります。今回検証だったので細部まで確認できていないところがありました。そのため、実業務に導入した場合はよりこの部分に時間を使うべきだと思いました。 後者はモデルの進化を待ちつつ、コンテキストの渡し方の工夫や、実装が仕様に準拠しているかを監査するサブエージェントの整備など、ガードレールを張っていくことが必要だと感じました。 コンテキスト管理の課題 AIツール固有の課題として、コンテキスト管理の難しさがあります。実装フェーズではコードの読み書きが多く発生するため、auto-compact(コンテキストの自動圧縮)が頻発しました。その結果、audit.mdへの書き込みが不安定になったり、要件定義ファイルへの指摘を繰り返してもアウトプットに反映されないことがありました。 対策として、コンテキストの使用量を抑えるためにルールファイルを分割して必要なタイミングでのみ読み込む方式にしたり、サブエージェントを活用して処理を分散させるなどの工夫が必要です。 レビュー負荷への対応 AIのアウトプット量が増えることで、人間のレビュー負荷が増大するという課題があります。この課題に対しては、以下のアプローチを検討しています。 レビューを軽減するプロセスの構築: 自動テストやLintの活用 AIの出力品質を上げる工夫: プロンプトの改善、ルールの整備 段階的なレビュー: 各ステージでの承認による分散 これらの最適解は、チームやプロダクトによって異なるため、継続的に改善していく必要があります。 最後に AI-DLCは、AIを活用した開発における「ボトルネックを特定し、解消していく」ためのフレームワークとして有望だと感じています。 今回見えてきた課題はAI-DLCのフレームワーク自体の問題ではなく、AIと人間が協働する上で必然的に発生する問題です。今後も継続的に活用しながら、チームに最適な形にカスタマイズしていきたいと考えています。
アバター
はじめに こんにちは。開発部でiOSエンジニアをしている野口です。 ヘルシカiOSアプリの開発を担当しており、アプリ内にはすでに「ヘルシカ」をはじめとしたキャラクターが実装済みです。 これらのキャラクターを生かしてよりユーザーに愛着を持っていただけるようにするため、アニメーションを導入したいと考えています。 Riveとは Riveとは、Webやアプリ、ゲーム向けの「インタラクティブなアニメーション」を作成・実装するためのデザインツールです。 Riveについて調べたところ、アニメーションの作成コストが低く、パフォーマンスがいいという記事を何記事か拝見し、導入を検討しました。 Riveの導入を断念した理由 Zombie Objectの発生 Riveの検証の際に、Zombieが発生していることがわかりました。(画像はDebug Memory Graphを使っています) Zombie とは、通常であれば削除されるはずの不要なオブジェクトが、何らかの理由でメモリに残り続けてしまう状態のことです。 つまり、プログラムのどこからも参照されていないにもかかわらず、メモリを占有し続けている状態です。この状態でアプリを使うと、思わぬクラッシュが発生するリスクがあります。 なお、XcodeのデバッグビルドでZombie Objectsをオンにしていたところ、アプリがクラッシュしてしまい、Zombieが発生していることが判明しました。 Zombie Objectsを有効にするには、XcodeでプロジェクトのScheme設定から[Product] > [Scheme] > [Edit Scheme]を選択し、[Run] > [Diagnostics]タブ内の"Enable Zombie Objects"にチェックを入れます。 Duolingoをはじめ他の企業が使用しているため、実際はこのZombieはそこまで問題ないのかもしれませんが、どうしてもRiveでないとできないことがある場合でない限りは使わないほうが良いと判断し、今回は導入をやめました。 Riveのissueを見るとクラッシュの報告はいくつか上がっており、まだ不安定なパッケージなのかもしれません。 代替アニメーション形式の検討 Riveの導入を断念したため、代替となるアニメーション形式を検討しました。 Riveでのアニメーションは断念しましたが、Riveでのアニメーション作成はAfter Effectsを使う場合と比べてデザイナーの学習コストが低いため、アニメーションの作成自体はRiveで行います。その上で、iOSアプリへの組み込み方法として以下の形式を検討しました。 GIF CPU: 低 メモリ: 多 懸念点: 24fpsで実験したところカクつきを感じるが、フレーム数を増やすとパフォーマンスに影響が出る可能性あり APNG CPU: ほぼ0 メモリ: 多 懸念点: 透過する場合はパフォーマンスが悪くなる可能性があるらしい(要調査)、Riveから直接出力できず、変換用のアプリケーション(After Effects)を使用する必要がある MP4 CPU: 再生時に上昇 メモリ: 少 懸念点: Riveから直接出力できず、変換用のアプリケーション(After Effects)を使用する必要がある。また、デザイナーがAfter Effectsに慣れていないため、作成時の学習コストや工数がAPNGよりもかかるらしい 以下の画像の通り、MP4は動画再生時に一瞬CPUが上がり、再生中はほぼ0になります。 Rive(参考) CPU: 高 メモリ: 中 懸念点: 開発側でZombie問題あり 検証結果まとめ 検証結果を以下の表にまとめました。 先ほどの項目にデザイン工数を追加しています。 Riveでアニメーションを作成した際のデザイン工数を1として、各形式のデザイン工数を比較しています。工数はデザイナーの主観です。 GIF : Riveから直接出力できるため、デザイン工数は1 APNG・MP4 : Riveから直接出力できず、変換用のアプリケーションを使用する必要がある。 項目 GIF APNG MP4 Rive CPU 低 ほぼ0 再生時に上昇 高 メモリ 多 多 少 中 懸念点 24fpsでカクつきあり 透過時の性能低下(要調査) After Effects学習コスト Zombie問題 デザイン工数 1 1.2 1.3 1 結論:APNGを採用 エンジニアとデザイナーが協議した結果、 APNGを採用 することにしました。 APNGを選んだ理由 CPU負荷がほぼ0で、パフォーマンスへの影響が最小限 GIFと比較して 滑らかなアニメーション が可能 Riveで作成したアニメーションを変換する必要はあるがMP4よりもわかりやすいため、デザイナーの工数は低いらしい 透過が必要になった場合にも対応可能(要調査) 他形式を採用しなかった理由 GIF - 検証時点のフレームレート(24fps)では若干カクつきを感じる - 他形式と同様のパフォーマンスを出すには、CPUやメモリ使用量が増える MP4 - デザイナーがAfter Effectsに慣れていないため、作成時の学習コストや工数がAPNGよりもかかるらしい - 透過ができない Rive - アニメーションの動きは一番綺麗だが、他形式との差は誤差の範囲 - Zombieが発生しており、Xcodeビルドではクラッシュする(TestFlight配布では問題なし) - 「Riveでないとできない」という理由がなければ使わない方がいいと判断 終わりに 今回はRiveの導入を見送り、APNGを採用することにしました。 Riveはアニメーションの動きが綺麗で魅力的なツールです。しかし、iOS版ではZombie Objectの発生によるクラッシュリスクがあり、現時点では安定性に不安が残ります。今後のアップデートでこの問題が解消されれば、再度導入を検討したいと思います。 アニメーション形式の選定は、パフォーマンス・開発工数・デザイン工数など、さまざまな観点から総合的に判断する必要があります。この記事が同じような課題を抱えている方の参考になれば幸いです。
アバター
はじめに こんにちは! 開発1部デリッシュキッチンの蜜澤です。 現在はデリッシュリサーチという、食トレンド分析ツールの開発を行っています。 本記事では、デリッシュリサーチで提供するデータの品質担保をするために行なったことを紹介させていただきます。 データ品質担保の必要性 デリッシュリサーチは食トレンドを分析するために、ダッシュボードで様々なデータを提供しています。 データが間違っていると、誤った意思決定につながるおそれがあるため、データの正確性に細心の注意を払う必要があります。 また、提供するデータが多岐にわたるため、テーブルの依存関係が複雑になっていきつつあり、放置しておくと集計のロジックを間違えてしまう可能性や、似たようなデータで整合性が保たれない可能性があります。 実際のER図を一部抜粋すると以下のようになっており、結構複雑です。 複雑な集計の中でも安心してサービスをご利用していただくために、データの品質の担保には特に注力しています。 実際に取り組んだこと 以下の2つの取り組みを行いました。 テーブル作成処理の単体テストの作成 元となる検索ログデータの増減に対するアラート作成 1つずつ詳細を紹介させていただきます。 テーブル作成処理の単体テストの作成 前述のER図を見てわかるようにテーブル数が多く、ETLが複雑になってしまうだけではなく、各テーブル作成の処理でも複雑なことをしているため、各テーブル作成の処理ごとに単体テストを行うようにして、テーブルごとに品質を担保できるようにしました。 入力データと期待される出力データを作成し、入力データを使用して実際のテーブル作成処理を行い、出力されたデータと期待される出力データを比較し、完全に一致しているかどうかを確認します。 具体例を用いて、どのようにテストを行なったのか紹介します。 ETLの作成はdatabricksを使用しています。 処理内容 検索ログデータから指定した期間(2024-01-01~2025-12-31)のデータを抽出し、検索ワードカラムの前後のスペースを削除する。 ※今回は簡単な例にしていますが、実際にはもっと複雑な処理を行なっています。 コードの実装例 実際に作成したテストのコードを簡略化したものを紹介します。 期待される出力データを作成して、データフレームに格納します。 最終的にデータフレームが一致しているかのテストを行うため、全てのカラムでソートを行なっています。 columns = [ 'event_date' , 'user_id' , 'search_word' ] expected_data = [ ( '2024-01-01' , 2 , 'キャベツ' ), ( '2024-01-01' , 5 , 'キャベツ' ), ( '2024-01-01' , 6 , 'キャベツ' ), ( '2024-01-01' , 7 , 'キャベツ' ), ( '2024-01-01' , 8 , 'キャベツ' ), ( '2024-01-01' , 9 , 'キャベツ' ), ( '2024-01-01' , 10 , 'キャベツ' ), ( '2024-01-01' , 11 , 'キャベツ 豚肉' ), ( '2024-01-01' , 12 , 'キャベツ 豚肉' ), ( '2025-12-31' , 3 , 'キャベツ' ) ] expected_df = pd.DataFrame(expected_data, columns=columns).sort_values([ 'event_date' , 'user_id' , 'search_word' ], ascending=[ True , True , True ]).reset_index(drop= True ) 入力データ(検索ログデータ)を作成します。 以下の項目を確認できるように作成します。 2024-01-01~2025-12-31のデータのみが抽出されるか 前後のスペースが削除されるか 前後ではない場所にスペースが入っている場合に削除されないか columns = [ 'event_date' , 'user_id' , 'search_word' ] input_data = [ # 期待通りの期間のデータが入るかの確認 ( '2023-12-31' , 1 , 'キャベツ' ), ( '2024-01-01' , 2 , 'キャベツ' ), ( '2025-12-31' , 3 , 'キャベツ' ), ( '2026-01-01' , 4 , 'キャベツ' ), # スペース削除の確認 ( '2024-01-01' , 5 , ' キャベツ' ), ( '2024-01-01' , 6 , 'キャベツ ' ), ( '2024-01-01' , 7 , ' キャベツ' ), ( '2024-01-01' , 8 , 'キャベツ ' ), ( '2024-01-01' , 9 , ' キャベツ ' ), ( '2024-01-01' , 10 , ' キャベツ ' ), ( '2024-01-01' , 11 , 'キャベツ 豚肉' ), ( '2024-01-01' , 12 , ' キャベツ 豚肉 ' ) ] input_df = pd.DataFrame(input_data, columns=columns) spark_input_df = spark.createDataFrame(input_df).createOrReplaceTempView( "spark_input_df" ) 作成した入力データを開発環境のDeltaテーブルへ書き込みます。 input_data = spark.sql((f """ SELECT event_date, user_id, search_word FROM spark_input_df """ )) input_data \ .write \ .format( "delta" ) \ .mode( "overwrite" ) \ .partitionBy( "event_date" ) \ .save(delta_table_path/search_log) 実際のETLの処理を開発環境で実行します。 処理内容の詳細は後述します。 args = { "env" : "dev" , "date" : "2025-12-31" } dbutils.notebook.run( "../01_SearchLog" , 0 , args) 上記の処理で書き込まれたデータを読み込み、データフレームに格納します。 データフレームの完全一致比較を行うため、全てのカラムでソートしています。 output = spark.sql((f """ SELECT event_date, user_id, search_word FROM delta_table_path/search_data ORDER BY event_date, user_id, search_word """ )) output_df = output.toPandas() 出力データと期待出力データを比較します。 def assert_output_equals_expected (output_df: pd.DataFrame, expected_df: pd.DataFrame): output_df = output_df.reset_index(drop= True ) try : assert_frame_equal(output_df, expected_df) print ( "データフレームが完全に一致しています。" ) except AssertionError as e: dbutils.notebook.exit(f "データフレームが一致しませんでした: \n {e}" ) assert_output_equals_expected(output_df, expected_df) 実際に実行した01_Searchの処理の内容はこちらになります。 dbutils.widgets.text( "env" , "dev" ) dbutils.widgets.text( "date" , "yyyy-MM-dd" ) end_date = dbutils.widgets.get( "date" ) env = dbutils.widgets.get( "env" ) search_data = spark.sql(f """ select event_date, time, user_id, regexp_replace(search_word, '^[ \\ u0020 \\ u3000]+|[ \\ u0020 \\ u3000]+$', '') as search_word --前後の半角スペースと全角スペースを削除 from delta_table_path/search_log where event_date >= '2024-01-01' and event_date <= '{end_date}' """ ) search_data \ .write \ .format( "delta" ) \ .mode( "overwrite" ) \ .partitionBy( "event_date" ) \ .save(delta_table_path/search_data) 課題・改善点 1つの処理を変更したことで、他の処理にも影響が及ぶ可能性があるため、基本的には全ての処理に対してテストを実行するのですが、量が多いため、現状だと全て完了するまでに1時間半ほどかかってしまいます。 依存関係を整理して、必要最低限のもののみテストを実施するようにすれば改善できるとは思います。 元となるログデータの増減に対するアラート作成 テーブル作成処理の単体テストの作成によって、デリッシュリサーチのために作成するテーブルのデータの品質は担保されるようになりますが、元の検索ログ自体に不具合が起きた場合には対処することができません。 例えば、検索ログデータのETLが遅延して、件数が正常ではなかった場合は、今回紹介した単体テストのみでは、対処することができません。 そこで、元の検索ログデータを使用して、検索ログ数の前週比を毎日集計して、閾値を上回る増加率・減少率だった場合にアラートを出す仕組みを作成しました。 アラートはPdMの人でも触れるようにするために、redashで作成しました。 以下のようなクエリを作成して、alert_flag=1となるレコードがあった場合にアラートを出すようにしました。 アラート用のSQL 実際にはユーザー属性ごとのログ数も確認しますが、今回は全体のログ数のみを確認するクエリを紹介します。 WITH search_log AS ( SELECT event_date, user_id, search_word FROM search_log WHERE event_date >= DATE_SUB(FROM_UTC_TIMESTAMP( CURRENT_DATE (), ' Asia/Tokyo ' ), 8 ) AND event_date < FROM_UTC_TIMESTAMP( CURRENT_DATE (), ' Asia/Tokyo ' ) ), daily_count AS ( SELECT event_date, COUNT (*) AS count FROM search_log GROUP BY event_date ) growth_rate AS ( SELECT event_date, count AS current_count, LAG( count , 7 ) OVER ( ORDER BY event_date) AS prev_week_count, ROUND (( count - LAG( count , 7 ) OVER ( ORDER BY event_date)) * 100.0 / LAG( count , 7 ) OVER ( ORDER BY event_date), 2 ) AS growth_rate FROM daily_count ) SELECT event_date, current_count, prev_week_count, CASE WHEN growth_rate < -20 OR growth_rate > 20 THEN 1 ELSE 0 END AS alert_flag FROM growth_rate 課題・改善点 現状は過去のデータを元に1年のうち年n回程度発生する増加率・減少率をalert_flagの閾値に設定していますが、このnが決め打ちになってしまうので、適切な閾値を設定するのが難しいです。 閾値を厳しくし過ぎると本当に問題が起きている時に気付けず、閾値を緩くしすぎてしまうと頻繁にアラートが鳴り信憑性がなくなってしまうので、運用していく中でちょうど良い閾値を見つけていきたいです。 まとめ データの品質担保をするために取り組んだことについて紹介させていただきました。 課題や、レビューとテスト作成にかなりの時間がかかるといった辛い点はありますが、データの品質担保ができているため、実施してよかったと思っています! 同じような課題を持っている方の参考になれたら幸いです。 最後まで読んでいただきありがとうございました。
アバター
はじめに 開発本部でデリッシュキッチンアプリ課金ユーザー向けの開発を担当している hond です! 2025年6月から社内勉強会の一つとして開催している「AIツールを活用した開発効率化勉強会」が開催から半年かつ現状の参加メンバーで一周したので、そもそもどのような勉強会だったのかやアンケートの結果からどのような成果が得られたのかについて振り返ろうと思います。 AIツールを活用した開発効率化勉強会 現在エブリーの開発部では、入社時に振り分けられる勉強会グループで開催する定期的な勉強会と勉強会のテーマに興味があるメンバーが集まって行う任意の勉強会があります。今回紹介する「AIツールを活用した開発効率化勉強会」は後者にあたり、AIに興味があるメンバー18人ほどが集まり開催しているものになります。 勉強会の目的 勉強会の目的は以下の3点として運営しました。 AIに関するインプット意欲を向上 AIツールをとりあえず試すマインドの向上 実務の中で活用していく意欲向上 2025年4月に 開発部とPdMにCursorが導入されました 。当時はCursor以外にもClaude CodeやClineなど多様な選択肢があり、AIツールは進化が早く個人でのキャッチアップには限界がありました。 そこで勉強会では、参加メンバーの多角的な視点でいろいろなツールや活用方法を吸収できる場にしたいと考えました。 具体的には、ハンズオン形式で実際に使ってみることや、他のメンバーから便利なユースケースを共有してもらうことで、活用の促進とユースケースの増加を狙いました。 また、新しいツールの導入には一定のハードルがあるため、「とりあえず試す」ことでそのハードルを緩和することも意識しました。 実施形式 実施形式は以下のように設計しました。 隔週1回で開催 発表者は参加者で順番に行う 事例紹介形式&ハンズオン形式 導入してみたけどうまくいかなかった例、うまく導入できていない例紹介タイムを設ける Cursorに限らずClaude CodeやRoo Code、ClineなどもOK 目的にある「とりあえず試す」を実現するため、勉強会ではなるべくハンズオン形式を採用するよう設計しました。 基礎的な内容よりも「〇〇をしたら△△になる」といった具体的なユースケースにフォーカスすることで、実務への応用をイメージしやすくしています。 また、うまくいった事例だけでなく、うまくいかなかった事例も積極的に共有してもらうようにしました。変化が激しく簡単に正解に辿りつけるフェーズではないと感じていたので失敗例を参加メンバーで考察することで、より深い理解に繋げることを狙っています。 Coding Agentにはそれぞれ良し悪しがあるため、ツールを絞らずそれぞれの長所や短所を学び、自分が使っているツールにどう活かせるか考えられる場にしました。 発表内容 半年間で行われた発表テーマは以下の通りです。 発表日 テーマ 6/16 MCP使い倒してコンテキストスイッチ最小限に 6/30 Cursor × iOS開発私はこうやってます 6/30 A/Bテストの実験設定をcursorに任せてみる 7/14 Claude Codeは言語化ムズイがいい感じという話 7/14 MCPサーバーを自作してみる 7/28 KiroでSpec駆動開発 7/28 Claude Codeのサブエージェント 8/25 CodeRabbitについて調べてみた 8/25 LLMで爆速論文検索 9/8 spec-kitを使ってみよう 9/8 コンテキストエンジニアリング 10/20 Amazon Bedrock AgentCoreでエージェント開発を加速させよう 11/17 いろいろあるよ!AWS MCP Servers 11/17 コンテキストエンジニアリングについて真面目に考える 12/1 Google Workspace Flows アルファ版 12/1 AIカンパニーをつくって遊んでみる 12/15 AWS Transform Custom Kiroやspec-kitをはじめとする開発体験を向上するツールを試す発表や、普段業務で使っているClaude CodeやCodeRabbitについての深掘りなど、幅広いジャンルの発表が行われました。 Amazon Bedrock AgentCoreを実際に参加者全員で作ってみるハンズオン形式の回もとても好評でした。 発表テーマを振り返ると、改めてAIが開発に与えるインパクトの大きさを感じます。同時に、いろいろなツールが出過ぎて手探りな期間だったなとも思います。 これだけのテーマを個人でキャッチアップするのは難しいので、勉強会という形で吸収できてよかったです。 アンケート結果 勉強会の振り返りとして、参加メンバーにアンケートを実施しました。14人から回答があり、以下の3項目については5段階評価で回答してもらいました。また、いくつか自由記述を設けて今後の改善に向けた意見を募りました。 5段階評価については下記の結果が得られました。 項目 1 2 3 4 5 総合満足度 0人 0人 3人 7人 4人 AIツール活用の参考になったか 0人 0人 3人 5人 6人 開発効率化の助けになったか 0人 1人 2人 9人 2人 全体的に高評価でしたが、特に「AIツール活用の参考になったか」に関してはNPS 21.5%という高い数字を出すことができました。 自由記述でのポジティブな意見としては、「自分ではキャッチアップしきれない部分について情報をインプットすることができた」や「色々なAIツールがある中でどのようなものがあるのかを知るとっかかりになったのが良かった」といった声が上がりました。どちらも勉強会を開催するにあたって目的にしていた部分が反映できたことが確認できる意見でした。 一方で、改善点として「回を重ねるごとに難易度が上がっていき、発表ネタを作るのが大変になっていた」という意見が複数見られました。確かに、勉強会を開始した当初と比較すると新しいツールや大きな開発体験の変化を伴うことは減ってきたのでテーマ的に一歩深掘りしたものが必要になっていました。あくまで、任意の勉強会なので負担にならないようある程度コントロールできるとよかったのではないかと思っています。 まとめ 他の勉強会と比較してデファクトスタンダードが出ていなかったりとても変化が激しい期間の勉強会の運営だったので、参加メンバーが満足いくものに設計できるか不安でしたが満足度もそれぞれ目的としていた指標に対しても高い評価が得られた結果となりよかったです。 ハンズオン形式に関しては発表者教材を準備しないといけないので負荷になっていたとは思いますが、聞いた内容を確実にアウトプットする機会や勉強会中にメンバー同士で議論できる場の提供になりプラスに働いたのではないかと思っています。 今後はより各チームのメンバーのユースケースの共有を活性化することで、よりお互いに刺激し合える勉強会を設計していきたいと思います。 社内勉強会の設計や運営の参考になったら幸いです!
アバター
はじめに デリッシュキッチンのiOSアプリを開発している成田です。 デリッシュキッチンではデザイン管理にFigmaを利用し、実装時にはDev Mode MCPサーバーを活用して精度を高めています。しかし、実際にビルドして確認してみると、レイアウト崩れが生じたりで期待するUIになっておらず、手動での「スクショ撮影→Cursorへ添付→指示→確認」という反復作業が発生していました。 この課題を少しでも解決するため、XCUITestによるスクリーンショット自動取得とCursorを組み合わせたUI自己改善ワークフローを構築しました。 概要 今回自動化したのは主に以下の2つです: スクリーンショットの取得 : UI実装後に、自動的にアプリをビルド、画面をナビゲーションしてスクリーンショットを取得 画像の読み込み : スクリーンショット取得後、画像を読み込んで分析し、レイアウト崩れなどを検出して実装を修正する 実現したワークフロー: UI実装依頼 ↓ AIがUIコードを生成 ↓ xcodebuildでビルド・UITestの実行 ↓ UITestが画面をナビゲーションしてスクリーンショット取得 ↓ スクリーンショットパスを一時ファイルに保存 ↓ 画像を読み込んで分析 ↓ 必要に応じてレイアウト崩れなどを自動修正 手順 このワークフローを実現するために必要な設定や実装手順は以下の通りです。 1. XCUITestの実装 XCUITestの標準API( XCUIApplication の screenshot() メソッド)を使用してスクリーンショットを取得します。 実装すべき内容は以下の通りです: スクリーンショット取得テスト : 画面をナビゲーションしてスクリーンショットを取得するテストメソッドを実装します。このテストメソッド内で以下の2つのヘルパーを使用します スクリーンショットヘルパー : XCUIApplication の screenshot() メソッドを使用してスクリーンショットをファイルに保存するヘルパークラスを実装します UI要素待機ヘルパー : UI要素が表示されるまで待機するユーティリティを実装します。XCUITestには自動待機機能がありますが、ネットワーク通信や非同期処理による画面更新の遅延がある場合などには、明示的な待機処理が必要です。これにより、テストの安定性を確保できます これらのファイルをUIテストのターゲットに追加します。 2. スクリーンショット取得スクリプトの作成 スクリーンショット取得は、 xcodebuild コマンドを使ってコマンドラインからビルド・テストを実行することで実現します。 スクリプト( scripts/cursor-screenshot.sh )で実装する処理は以下の通りです: 画面名の受け取りと環境変数の設定 : スクリプトは引数として画面名を受け取り、環境変数として設定します。この環境変数はUITestに渡され、どの画面にナビゲーションするかを決定します アプリのビルド : xcodebuild build でアプリをビルドします UIテストの実行 : xcodebuild test で DynamicScreenshotTests.testTakeScreenshotForScreen を実行します。UITestは環境変数を参照して、画面名に応じて適切な画面にナビゲーションしてからスクリーンショットを取得します スクリーンショットファイルの検索とパスの保存 : テスト実行後、最新のスクリーンショットファイルを検索し、パスを /tmp/cursor_screenshot_info.txt に保存します。このファイルを参照してスクリーンショットを読み込むようにします 3. Cursorのルール設定 - .cursor/rules/my-custom-rule.mdc にUI実装後の自動ワークフローのルールを追加 - UI実装後のワークフロー - UIを新規実装または大幅に変更した後は、必ず以下を実行すること: - 大幅な変更の定義: - 大幅な変更の定義: レイアウト、UIコンポーネント、View構造、スタイルなど、レイアウトや見た目に影響を与える変更 1. 当該の画面に対して`scripts/cursor-screenshot.sh`を実行 2. 取得した画像をもとに元のデザインと比較してレイアウト崩れなどがあれば修正する 3. 修正後、再ビルドして確認 4. 必要に応じて、上記のステップ(1-3)を繰り返す(最大n回まで) これによって期待するワークフローが実現できました。 おわりに UIを実装した後は、手動でビルドして確認し、レイアウト崩れなどを発見したら修正依頼を投げるという作業を繰り返す必要がありました。 XCUITestの仕組みを使ったワークフローによってUI実装から自己改善までを自動化することができるようになったので、プロンプトで修正依頼を投げる量を最小限に留めることができるようになりました。 とはいえ、UITestは壊れやすくワークフローの途中でこけることも度々起こりうるので、その辺はデメリットかなと思います。
アバター
目次 はじめに 注意事項 Echo v5の主な変更点 Echo v4からv5への移行しながら変更点を確認する バージョン更新とecho.Contextの変更 Routerのカスタマイズ (Interface + DefaultRouter) StartConfigを用いたサーバー起動 デフォルトロガーがslog.Loggerに変更 レスポンス情報の取得方法 (UnwrapResponse) URLパラメータの埋め込み方法変更 echo.POSTのような http.MethodXXX のヘルパーが廃止 まとめ はじめに こんにちは、開発本部開発1部トモニテグループのエンジニアの パンダム/rymiyamoto です。 2026/01/18 より Echo v5 がリリースされました 🎉 弊社プロダクトの多くが依存しているフレームワークなだけに、最新バージョンへの移行パスをいち早く探っておきたいところ。 さっそく、主要な変更点や所感をレポートします。 Echoを使って開発している方も多いと思いますので、その手助けになればと思います。 github.com 注意事項 本記事はEcho v4の基本的な知識がある読者を対象としています。 また執筆は2026/01/22時点のもので、Go 1.25.6をベースにしています。 リリース直後のv5には、今後も破壊的な変更が加わる余地が残されています。そのため公式からは、商用利用については 2026/3/31まで待機すること が推奨されている点に注意が必要です。 (ちなみにv4のサポートは2026/12/31までです) 公式からの引用↓ v5.0.0 was release on 2026-01-18. v4 will be supported with security updates and bug fixes until 2026-12-31. Until 2026-03-31, any critical issues requiring breaking API changes will be addressed, even if this violates semantic versioning. If you are using Echo in a production environment, it is recommended to wait until after 2026-03-31 before upgrading. Echo v5の主な変更点 今回の変更点をまとめてみると、主に以下の5点に集約されます。 【破壊的変更】 echo.Context がインターフェースから構造体に変更 将来的な機能追加を容易にする ための大きな設計変更です 【新機能】Router のインターフェース化による拡張性の向上 独自のルーター実装が可能になり、 正規表現ルーティング なども導入しやすくなります 【仕様変更】サーバー起動設定の構造体化 ( StartConfig ) アドレスやタイムアウト設定が構造体に集約され、 設定の可読性が向上 します 【破壊的変更】Go標準ライブラリ log/slog をネイティブサポート サードパーティ製ライブラリなしで、 モダンな構造化ログ が扱えるようになります 【破壊的変更】APIの一貫性向上とメソッド整理 全体的なシグネチャが見直され、より 洗練された開発体験 を提供します 概要からみてこれまでv4の期間が長かったのもあり大きな進歩だなと感じました。 今までは標準で対応しづらい部分はサードパーティを利用していたので、ログ周りが自分としては一番ありがたいです。 他にも設定周りが統一化されているのでよりコードの可読性が向上しそうな予感がしますね。 もちろん他にもいくつかAPIの変更点がありますが、詳細については実際にv4で動いているコードの修正をしながら気づいた部分を紹介していきます! (紹介しきれていない部分は下記のドキュメントを読んでみてください) github.com Echo v4からv5への移行しながら変更点を確認する バージョン更新とecho.Contextの変更 公式に記載されている通りの方法でバージョン更新や最低限の修正を行うことができます。 # v4 から v5 への移行 go get github.com/labstack/ echo /v5 # サードパーティの更新(対応されているサードパーティ系のライブラリは更新) go get github.com/labstack/echo-contrib go get github.com/labstack/echo-jwt/v5 # 一括置換 # echo.Context -> *echo.Context find . -type f -name " *.go " -exec sed -i ' s/ echo.Context/ *echo.Context/g ' {} + # echo/v4 -> echo/v5 find . -type f -name " *.go " -exec sed -i ' s/echo\/v4/echo\/v5/g ' {} + github.com 実際のコードの変更は以下のようになり、大多数の変更箇所になるかと思います。 // v4 func MyHandler(c echo.Context) error { return c.JSON( 200 , map [ string ] string { "hello" : "world" }) } // v5 func MyHandler(c *echo.Context) error { return c.JSON( 200 , map [ string ] string { "hello" : "world" }) } これだけだと型定義が変わっただけか〜となりますが、この変更による恩恵は新機能追加時に破壊的な変更にならないようにするための大事な変更と言えます。 まずecho.Contextがインターフェースだった場合、将来的に新しい機能(メソッド)を追加すると、そのインターフェースを実装しているすべてのコード(自作のContextラッパーやモックなど)がコンパイルエラーになります。 これは「破壊的変更」にあたるため、次のメジャーバージョンアップ(v6~)まで機能追加がしにくくなります。 構造体( *echo.Context )に変更することで、本体に新しいメソッドを追加しても、既存の利用側のコードはそのまま動作します。 これにより、v5の期間中にマイナーアップデートで便利な新機能をどんどん追加できるようになります。 Routerのカスタマイズ (Interface + DefaultRouter) Routerのインターフェース化により、独自のルーター実装が可能になりました。 これにより、正規表現ルーティングや、特定のアプリケーションでは不要な機能(ワイルドカードや 405 Method Not Allowed のハンドリングなど)を削ぎ落とし、より効率的なルーティング処理を実現できるようになるのではないかと思います。 まだこれからだとは思いますが、 RouterConfig が拡張されていくことにより様々な恩恵を受けやすくなりそうです。 // v4 type Router struct { ... } func NewRouter(e *Echo) *Router func (r *Router) Add(method, path string , h HandlerFunc) func (r *Router) Find(method, path string , c Context) func (r *Router) Reverse(name string , params ... interface {}) string func (r *Router) Routes() []*Route // v5 type Router interface { Add(routable Route) (RouteInfo, error ) Remove(method string , path string ) error Routes() Routes Route(c *Context) HandlerFunc } type DefaultRouter struct { ... } func NewRouter(config RouterConfig) *DefaultRouter func NewConcurrentRouter(r Router) Router // NEW type RouterConfig struct { NotFoundHandler HandlerFunc MethodNotAllowedHandler HandlerFunc OptionsMethodHandler HandlerFunc AllowOverwritingRoute bool UnescapePathParamValues bool UseEscapedPathForMatching bool } github.com StartConfigを用いたサーバー起動 新しく追加された echo.StartConfig 構造体を使用する形式でもサーバーを起動することができます。 もちろんこれまで通りの echo.Start を使用することもできますが、アドレス指定が引数ではなく設定から指定できるのは使いやすそうです。 以下は StartConfig を使用した場合の例です。 // v5: echo.StartConfig を使用する func main() { e := echo.New() e.Use(middleware.RequestLogger()) e.GET( "/" , func (c *echo.Context) error { return c.String(http.StatusOK, "Hello, World!" ) }) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() sc := echo.StartConfig{ Address: ":1323" , GracefulTimeout: 10 * time.Second, } if err := sc.Start(ctx, e); err != nil { log.Fatal(err) } } $ go run server.go { " time " : " 2026-01-20T23:28:54.719092+09:00 " , " level " : " INFO " , " msg " : " Echo (v5.0.0). High performance, minimalist Go web framework https://echo.labstack.com " , " version " : " 5.0.0 " } { " time " : " 2026-01-20T23:28:54.719326+09:00 " , " level " : " INFO " , " msg " : " http(s) server started " , " address " : " [::]:1323 " } { " time " : " 2026-01-20T23:29:11.2848+09:00 " , " level " : " INFO " , " msg " : " REQUEST " , " method " : " GET " , " uri " : " / " , " status " :200, " latency " :3000, " host " : " localhost:1323 " , " bytes_in " : "" , " bytes_out " :13, " user_agent " : " curl/8.7.1 " , " remote_ip " : " ::1 " , " request_id " : "" } github.com デフォルトロガーがslog.Loggerに変更 これまではGoの標準のログを少し拡張した シンプルなloggingパッケージ を使っていましたが、slogを使うようになり、ログの設定でそのままslogの設定が利用できるようになりました。 これによりこれまで slog-echo のようなサードパーティを使ってslog利用していたように拡張する必要がなくなり、ロギング設定がより直感的になって個人的には一番嬉しいポイントになりました。 // v4 type Echo struct { Logger Logger // Custom interface with Print, Debug, Info, etc. } // v5 type Echo struct { Logger slog.Logger // slog.Logger interface } 実際にslogを使ってログ設定をカスタマイズする場合は以下のようになります。 skipper := func (c *echo.Context) bool { return c.Request().URL.Path == "/health" } e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ Skipper: skipper, // ヘルスチェックのリクエストはログに含めない LogStatus: true , LogURI: true , LogError: true , HandleError: true , // エラーをグローバルエラーハンドラに転送し、適切なステータスコードを決定できるようにします。 LogValuesFunc: func (c *echo.Context, v middleware.RequestLoggerValues) error { if v.Error == nil { logger.LogAttrs(context.Background(), slog.LevelInfo, "REQUEST" , slog.String( "uri" , v.URI), slog.Int( "status" , v.Status), ) } else { logger.LogAttrs(context.Background(), slog.LevelError, "REQUEST_ERROR" , slog.String( "uri" , v.URI), slog.Int( "status" , v.Status), slog.String( "err" , v.Error.Error()), ) } return nil }, })) // start server... echo.labstack.com レスポンス情報の取得方法 (UnwrapResponse) ミドルウェア等でレスポンスの書き込みサイズやステータスコードを参照する場合、これまでは c.Response() のフィールドに直接アクセスしていましたが、v5では echo.UnwrapResponse ヘルパーを使用する必要があるので修正が必要になります。 // v5 resp, err := echo.UnwrapResponse(c.Response()) if err == nil { // resp.Size <-- 書き込まれたバイト数 // resp.Status <-- ステータスコード // resp.Committed <-- クライアントに送信済みかどうか fmt.Printf( "Status: %d, Size: %d \n " , resp.Status, resp.Size) } github.com この変更に伴う修正には正直、かなり手こずりました。いざv5の変更を当ててビルドしてみると、あちこちでヘルパーがエラーを吐き出しレスポンスの中身を見ている処理の見直しがいるのかなと疑問に思い、一体どうすれば…と右往左往する羽目になりました 😇 しかし、APIの変更点を見直したり実際のv5のコードを追ってみるなどの試行錯誤の末にこの UnwrapResponse の存在に気づきました この変更には最初こそ戸惑いましたが、 val, err := func() というGo言語の標準的なエラーハンドリングパターンに統一されたことで、変更の意図が腹落ちしました。 v4までではフィールド直接参照は手軽な反面、内部実装への依存度が高く、将来的な変更に弱い側面があります。 今回ヘルパー関数を経由する形になったことで、内部構造が隠蔽され、仮に取得に失敗してもエラーとして安全に検知できる(堅牢性が高まる)ようになっています。 また、このアプローチはGo1.20で標準ライブラリに追加された http.ResponseController の設計思想とも通じる部分があり、EchoがGoのモダンな作法に追従しようとしている姿勢がうかがえます。手間は増えましたが、長期的な保守性を高めるための洗練された変更と言えそうです。 URLパラメータの埋め込み方法変更 テストのとき、URL内にリソースのIDを埋め込んで実行する際によく使用していたコードが変更されています。 v5のほうが1行でまとまり直感的に書けるようになっており、これまでのコードよりも可読性が向上しています。 // v4 c.SetParamNames( "id" ) c.SetParamValues( "1" ) // v5 c.SetPathValues(echo.PathValues{{Name: "id" , Value: "1" }}) github.com こちらもぱっと調べて出てこなかったので実際に修正してみて気づいた部分になりました。 echo.POSTのような http.MethodXXX のヘルパーが廃止 これまでHTTPメソッドのヘルパーがあったのですが、v5では廃止されています。 そのため、 echo.POST などのヘルパーを http.MethodPost のように書き換える必要があります。 ( 何故かここはドキュメントにもAPI変更にも記載がないところでした ) 追記(2026/01/29) 公式のドキュメントの方にも記載されました github.com 以下がv4の時のコードです。 // HTTP methods // NOTE: Deprecated, please use the stdlib constants directly instead. const ( CONNECT = http.MethodConnect DELETE = http.MethodDelete GET = http.MethodGet HEAD = http.MethodHead OPTIONS = http.MethodOptions PATCH = http.MethodPatch POST = http.MethodPost // PROPFIND = "PROPFIND" PUT = http.MethodPut TRACE = http.MethodTrace ) github.com 追記(2026/01/29) 公式のドキュメントの方にも記載されました github.com まとめ 今回は Echo v5 がリリースされたので触れてみました。 v5の新しい機能に触れてみて、その利便性やコードの読みやすさが格段に向上しているのを肌で感じました。これは開発体験を大きく変える可能性を秘めていると思います。 実際に社内のプロダクトで実験しながらアップデート内容に触れてみましたが、試した結果、まだまだ修正が必要な箇所が多く、エラーの解消や動作検証に膨大な時間を費やしました。 正直なところ、完全に商用利用できる状態にするには、まだ多くの修正と検証が必要だと痛感しています。 この道のりは決して楽ではありませんが、v5がもたらす開発体験の向上を考えると、乗り越える価値は十分にあると感じています。 ガッツリとした変更ありつつもv4での記法がすべて変わるわけではないので、地道に修正していく必要があります。 まだまだリリースして日が浅く、公式含めサードパーティ系も更新が活発になってくるかと思うので最新の情報においていかれないように食らいついていこうと思います。 最後まで読んでいただきありがとうございました!
アバター
目次 はじめに エンジニア内定者研修について エンジニア内定者研修の概要 エンジニア内定者研修の目的 エンジニア内定者研修カリキュラム 前回からの改善点 ターミナルおよび Git/GitHub の基礎・プログラム基礎 ネットワーク/インフラ基礎 DB 研修 Web 基礎・Web アプリケーション開発基礎 AI開発基礎 受講者のフィードバック おわりに はじめに こんにちは。 開発本部開発1部トモニテ開発部所属の庄司( @ktanonymous )です。 弊社では、内定者向けとしては2回目となる内定者研修を2026年新卒のエンジニア内定者向けに実施し、 2025年中に全ての講義を完了しました。 今回の記事では、その内容について紹介したいと思います。 エンジニア内定者研修について 弊社では昨年度、エンジニア向けの内定者研修を初めて実施しました。 (昨年度の内定者研修の詳細については以下の記事をご覧ください) tech.every.tv 昨年度の研修では、入社後の新卒研修をよりスムーズに進められるよう、受講者の知識のベースラインを揃えることを目的として実施しました。 今年度も引き続き同様の目的のもと、昨年度の経験やフィードバックを活かしてカリキュラムを改善し、2回目の内定者研修を実施しました。 エンジニア内定者研修の概要 ここでは、エンジニア内定者研修の目的やカリキュラム、それぞれの講義概要について紹介したいと思います。 エンジニア内定者研修の目的 先述の通り、内定者研修では来年度入社する新卒のエンジニアメンバーが、入社後の研修を通じてよりスムーズに開発組織にジョインできるように、 ベースとなる基礎知識を学べる機会を提供することを主な目的としています。 具体的には、以下のような目的と方針を設定しました。 目的 入社前に基本的な技術や知識をキャッチアップする環境を提供する 方針 入社前に身に着けてほしい技術や知識のキャッチアップをサポートする 基礎知識を早期にキャッチアップすることで入社後の研修・オンボーディングをよりスムーズに進められるようになる 上記の目的と方針を踏まえ、4月に入社した新卒メンバーが中心となり資料の作成から当日の講義までを担当してもらいました。 最終的な資料としては社内メンバーのレビューを経て内容を担保するようにしています。 エンジニア内定者研修カリキュラム 今回の研修では以下のテーマで講義を行いました。 各回 90 分を目安に、2 週間に 1 回程度のペースで実施しました。 今回は、昨今のAI技術の発展に伴う開発環境の変化を踏まえて、AI開発基礎を新たなテーマとして取り入れました。 ターミナルおよび Git/GitHub の基礎・プログラム基礎 ネットワーク/インフラ基礎 DB 研修 Web 基礎・Web アプリケーション開発基礎 AI開発基礎 また、遠方から参加する方もいるため、全ての講義はオンラインで実施して録画を残すようにしました。 さらに、今回は講義で使用した資料を以下のリンクの speakerdeck に公開しています。 講義資料だけでなくイベントの登壇などで使用した資料などもご確認いただけますので、この機会にぜひご覧ください。 speakerdeck.com 前回からの改善点 前回の研修では受講者から以下のようなフィードバックがありました。 エンジニアとして知っておいた方が良いことを知ることができた 開催時期に対してスケジュールがタイトに感じられた また、運営面では担当の負荷が偏っていたり講義の準備のスケジュール感に対するフィードバックがありました。 これらのフィードバックを踏まえ、今回の研修では以下のような変更を取り入れました。 テーマ設定は昨年度の良さを引き継ぎつつ、関連性の高かったテーマを統合するとともに、昨今の開発環境の変化を踏まえてAI開発基礎を新たなテーマとして取り入れました。 約半年間の研修スケジュールで間延びしないように2025年内に全ての講義を完了するようにしました。 担当者の負荷が偏らないようにテーマごとの割り振りを均等に調整し、サポート体制なども明確になるように整備しました。 ターミナルおよび Git/GitHub の基礎・プログラム基礎 speakerdeck.com ターミナルおよび Git/GitHub の基礎・プログラム基礎の講義では、2024年度の「ターミナルおよび Git/GitHub の基礎」と「プログラム基礎」を統合し、 CLI(ターミナル)やチームでの開発を行うにあたり弊社でも利用している Git/GitHub の基本的な使い方、およびプログラムの基本的な構造やデータ構造、アルゴリズムについて学びました。 具体的には以下のトピックを取り上げました。 Linux コマンド Git とは? 木構造 配列とリスト ハッシュ ソート 探索 ネットワーク/インフラ基礎 speakerdeck.com ネットワーク/インフラ基礎の講義では、OSI 参照モデルを中心に、 ネットワークやインフラの基礎知識について学びました。 具体的には以下のトピックを取り上げました。 プロトコル TCP/IPとOSI参照モデル 関連するAWSリソース DB 研修 speakerdeck.com DB 研修では、DB の基本概念やバックエンド/データ系それぞれの視点での利用について学びました。 具体的には以下のトピックを取り上げました。 「データ」の種類と構造 SQLによるデータ操作 正規化・インデックス データ基盤の概要 Web 基礎・Web アプリケーション開発基礎 speakerdeck.com Web 基礎・Web アプリケーション開発基礎の講義では、2024年度の「Web 基礎」と「Web アプリケーション開発基礎」を統合し、 API や Web アプリケーションの基本構成や仕組み、バックエンド/フロントエンドそれぞれの役割、アーキテクチャやテスト、コーディング時に意識することなど、 組織/チームでの開発に携わるうえで重要となってくる考え方について学びました。 具体的には以下のトピックを取り上げました。 Web アプリケーションの構成要素 ブラウザが表示するHTMLの取得元について 外部データソースを利用した動的レンダリングについて フロントエンドとバックエンドの役割 開発に必要な知識 アーキテクチャ テスト CI/CD AI開発基礎 speakerdeck.com AI開発基礎の講義では、AIの概要やAIを活用した開発について学びました。 具体的には以下のトピックを取り上げました。 AIの基礎知識 AIの定義と分類 言語モデル(LLM)の仕組み トークン化とコンテキスト長 埋め込み(Embeddings) LLMの要素技術 プロンプトエンジニアリング 推論時のプロンプト手法 RAG(検索拡張生成)技術 LLMの拡張・統合技術 コンテキストエンジニアリング 開発ツールと活用事例の紹介 受講者のフィードバック 研修の改善のために、受講者からのフィードバックをアンケートで収集しており、その中でも以下のようなポジティブな意見が見受けられました。 図解や具体例(例:デリッシュキッチンだとどうか)が豊富だったため、とてもイメージしやすかった 昨今のAIによる開発環境の変化や具体的なモデルの構造などについて振り返ることができてよかった 基本的なことが丁寧に解説されていて分かりやすかった 一方で、「内容のボリュームに対して時間がタイトに感じられた」「実例や背景をより詳細に説明してほしい」といった意見もあり、 テーマに対して取り扱う内容の範囲を適切に設定する難しさを改めて実感しました。 得られたフィードバックを踏まえ、今後の研修運営をさらにブラッシュアップしていきたいと考えています。 講義風景① 講義風景② おわりに 今回の記事では、エンジニア内定者向けの研修についてご紹介いたしました。 内定者研修を通じて、今後入社するエンジニアのメンバーが入社後のオンボーディングをよりスムーズに進められるようにサポートすることができたと考えております。 また、研修の企画・運営に携わった若手メンバーにとっても、 知識の整理や研修の主要メンバーとしての新たなチャレンジの機会となり、貴重な成長の場にできたと感じています。 今回のような取り組みを含めて、今後もエンジニアの成長を支援する取り組みを続けて発信していきたいと思います。 最後まで読んでいただき、ありがとうございました。
アバター
目次 はじめに セッション紹介 zerobus Ingest Agent Bricks [企業セッション] イオンにおけるマルチエージェントシステムの開発 まとめ 最後に はじめに 2025年11月28日に開催された「Databricks DATA + AI WORLD TOUR」に参加させていただきました。 今回は参加レポートとして、セッションの感想をお届けします! dataaisummit.databricks.com セッション紹介 zerobus Ingest 開発1部の吉田です。 Databricks Sessionで紹介されたzerobus ingestについてまとめます。 zerobus ingestは、クライアントからDatabricksへデータを直接ストリーミングできるマネージドサービスです。 従来のような複雑なメッセージバス(KafkaやKinesisなど)を介することなく、レコード単位でのデータ取り込みを実現します。 docs.databricks.com 中間インフラを排除できるため、運用管理の手間とコストが削減できます。 エブリーでは現在、サイネージ端末やWebアプリケーションのログ収集のために専用のインフラを構築・運用しています。 このインフラをzerobus ingestで置き換えることができるか、検証を進めていく予定です。 Agent Bricks こんにちは! 開発1部デリッシュキッチンの蜜澤です。 僕からは、Databricks Sessionで紹介されたAgent Bricksについてまとめます! Agent BricksはノーコードでAIエージェントの構築、評価、最適化できる機能になっています! AIエージェントを作成する際の下記のような課題に簡単に対応することができます。 調整すべき項目が多すぎる 評価するのが難しい コストと品質のバランスを取るのが難しい Agent Bricksではタスクを選び、エージェントの役割をざっくり指定するだけで自動でエージェントを作成してくれるので、調整するべき項目が少なくなっているようです。 ドキュメントを元に質問に答える「ナレッジアシスタント」や、要約・分類などのカスタムテキスト変換を行う「カスタムLMM」、Genieスペースとエージェントを統合する「マルチエージェントスーパーバイザー」などのユースケースがあるようです。 エージェントの評価に関しては、自動でベンチマークを作成し、エージェントを自動で最適化できるようです。 また、good/badのような評価しかできないツールもありますが、Agent Learning from Human Feedback(ALHF)によって、自然言語での指示に基づき、システムを自動調整することもできます。 コストと品質に関しては、「コスト最適化」モデルと「品質最適化」モデルのどちらかを選択することができ、多くの場合は両立も可能なため、自前で構築するよりも高品質かつ低コストなエージェントを作成できることが多いようです。 Agent Bricksを使用してAIエージェントを作成すると、自前で作成する際に課題となる点を簡単に解決し、気軽にAIエージェントを作成できるのが魅力だと感じました。 デモパートでは、マルチエージェントスーパーバイザーを使用して、複数のエージェントを簡単にまとめる方法をご紹介いただきました。 PowerPointやWord形式のドキュメントと役割を与えられたナレッジアシスタントと、テーブルを読み取るためのGenieスペースを登録することで、ユーザーからの質問に対して、ナレッジを元に必要な情報を考え、必要な情報をGenieがクエリを作成しテーブルから取得するというマルチエージェントが簡単に作成できていました。 個人的に特に便利だなと感じたのは、ナレッジを元に回答できないパターンの質問に関しては、ガイドラインを登録し回答を準備しておくことが簡単にできることでした。 2025年11月時点ではasia-northeast1リージョンではAgent Bricksはまだ使用できないのですが、使用できるようになるのがとても楽しみになりました! [企業セッション] イオンにおけるマルチエージェントシステムの開発 開発1部の岩﨑です。私からはイオン様におけるマルチエージェントシステムの開発について紹介します。 イオン様ではマルチエージェントシステムを「業務特化型エージェント」と「顧客向けAIエージェント」に大別して紹介されていました。 中でも業務特化型エージェントでは、自然言語の質問に基づいたクエリを実行して情報抽出するエージェントが紹介されました。 具体的には、データ抽出エージェントや可視化エージェントといったそれぞれのタスクに対するエージェントがあり、それをまとめるマルチエージェントスーパーバイザーをユーザとLLMとのインターフェースとしておくような構造となっています。 これによってクエリの生成から可視化、整合性の評価まで一気通貫で実行するようなマルチエージェントシステムを構築しています。 このエージェントはコンテキストが曖昧だった場合は推測で答えるのではなく、ユーザに聞き返すような仕組みとなっています。 また大規模なクエリをAIが実行しないように、システムリソースを大量に消費するクエリが生成された場合にはエージェントが主体となって実行しない意思決定が行われるようになっていたり、クエリをキャッシュする戦略など大規模データを扱っている企業ならではの工夫点などもみられました。 クエリの実行はDatabricks GenieとUnity Catalogを使用してAPIとして公開することでシームレスかつ低レイテンシで連携することができます。 また、Unity Catalogはガバナンスを意識した設計になっているためAIエージェントによる不正なデータアクセスなどの心配もありません。 さらにエージェントを作って終わりではなく、Genieへのリクエストを収集、フィードバックの作成、最適化のループを回すことによって精度改善にも取り組んでいるそうです。 他にも色々なエージェントの工夫点が紹介されており、非常に有意義なセッションでした。 まとめ 今回のDatabricks DATA + AI WORLD TOURでは、zerobus IngestやAgent Bricksといった機能の紹介から企業様の活用事例まで、幅広いセッションを聴講することができました。 AIエージェントに関するセッションが多く、ノーコードでのエージェント構築から大規模な業務システムへの適用までAIエージェントの活用が急速に広がっていました。 今回得られた知見を活かし、Databricksの機能の検証やAIエージェントの活用を進めていきたいと思います。 最後に エブリーでは、ともに働く仲間を募集しています。 テックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください! corp.every.tv 最後までお読みいただき、ありがとうございました!
アバター
AppleとLINEのネイティブ認証をつくる(サーバー編) この記事は every Tech Blog Advent Calendar 2025 の 29 日目の記事です。 前提 アプリ側のAppleとLINEのネイティブ認証実装 Apple LINE IDトークンと nonce サーバー側の実装 Appleの細かいポイント Bundle ID と Service ID LINEの細かいポイント API IDトークンの検証アルゴリズム 最後に 参考資料 こんにちは!開発1部で食事管理アプリ ヘルシカ のサーバーサイドの開発をしている 赤川 です。約1ヶ月にわたって続いたアドベントカレンダーも最終日となりました。 本記事では、ヘルシカiOS で Apple と LINE のネイティブ認証を導入した経験をもとに、ネイティブ認証のサーバー側の実装と周辺知識、実装時の細かいポイントについてお話しします。 iOS側の実装については、昨日公開の AppleとLINEのネイティブ認証をつくる(iOS編) をご覧ください。 tech.every.tv 前提 ヘルシカではもともと Web アプリベースの認証方法が採用されており、アプリ内で WebView が開きそこでIDやパスワードを入力して サインアップ / サインイン を行います。以下、AppleやLINEのIDプロバイダーを外部IdP、ヘルシカの認証サーバーを単に認証サーバーと呼ぶことにします。認証サーバーではOpenID Connectに則った実装がされており、おおまかには以下のような流れです。 ログインボタンを押す アプリ内のWebViewで外部IdP(AppleやLINE)の入力画面が開く 入力成功後、外部IdPから認証サーバーにコールバック 認証サーバーが外部IdPとToken Exchangeを行い、認証サーバーが外部IdPのトークンを受け取る 外部IdPのトークンの検証に成功した後、ヘルシカAPIサーバー用のトークンを発行し、安全にClientに渡す アプリはほぼWebViewを開くだけで、IdPとメインでやり取りするのはサーバーであることがわかります。 上記の形だと、例えばヘルシカにWebアプリが増えた、という場合でも同じエンドポイントが使え、認証サーバーの追加実装なしに拡張が可能なのがメリットと言えます。しかし、パスワードなどの入力は面倒ですし、忘れがちです。そこで、ユーザーにより簡単にサインアップ・サインインをしてもらえるように、Appleなら顔認証などiOSネイティブな方法、LINEならLINEアプリが開いてワンタップで認証できる方法を新しく採用することになりました。 Appleの認証画面 LINEの認証画面 アプリ側のAppleとLINEのネイティブ認証実装 (サーバー編)とタイトルに入れましたが、サーバー側の設計をするためにはまずアプリ側でネイティブ認証を実装する方法を知らなければなりません。AppleではASAuthorizationAppleIDProviderなどのクラス、LINEではLINEログインSDKというものが用意されています。 developer.apple.com developers.line.biz 元のフローを思い出してみると、外部IdPのトークンを受け取るのは認証サーバーでした。しかし、上記を使用した場合、IDトークンを受け取るのはアプリになります。それぞれについて、少し深掘りしてみましょう。 Apple まず、リクエスト ASAuthorizationAppleIDRequest でAppleIDを含む情報をIdPに送信します。 ASAuthorizationAppleIDRequest は ASAuthorizationOpenIDRequest を継承しており、オプションとして state と nonce を含めることができます。 成功すると、レスポンスとして ASAuthorization を受け取ります。これには ASAuthorizationAppleIDCredential が含まれており、 さらにその中身を見ることで IDトークン や state が手に入ります。 LINE LINEでは、 LoginManager を使います。 LoginManager.shared.login でリクエストを送信しますが、Appleの時と違い nonce と state はSDK内で自動的に生成、検証されます。ただし、 nonce は独自に指定することも可能です。 成功すると、 LoginResult を受け取ります。この中にアクセストークンやIDトークンなどが含まれています。 比較すると、以下の2つが共通していることがわかりました。 リクエストには独自の nonce を設定することができる。 成功すると、アプリがIDトークンなどを受け取ることができる。 IDトークンと nonce サーバー側の設計に進む前に、IDトークンと nonce について復習しておきます。ご存知の方は無視して次のパートに進んでいただいて問題ありません。 まず、IDトークンは次のような形をしています。 eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ. ewogImlzcyI6ICJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsCiAic3ViIjogIjI0ODI4OTc2MTAwMSIsCiAiYXVkIjogInM2QmhkUmtxdDMiLAogIm5vbmNlIjogIm4tMFM2X1d6QTJNaiIsCiAiZXhwIjogMTMxMTI4MTk3MCwKICJpYXQiOiAxMzExMjgwOTcwLAogIm5hbWUiOiAiSmFuZSBEb2UiLAogImdpdmVuX25hbWUiOiAiSmFuZSIsCiAiZmFtaWx5X25hbWUiOiAiRG9lIiwKICJnZW5kZXIiOiAiZmVtYWxlIiwKICJiaXJ0aGRhdGUiOiAiMDAwMC0xMC0zMSIsCiAiZW1haWwiOiAiamFuZWRvZUBleGFtcGxlLmNvbSIsCiAicGljdHVyZSI6ICJodHRwOi8vZXhhbXBsZS5jb20vamFuZWRvZS9tZS5qcGciCn0. NTibBYW_ZoNHGm4ZrWCqYA9oJaxr1AVrJCze6FEcac4t_EOQiJFbD2nVEPkUXPuMshKjjTn7ESLIFUnfHq8UKTGibIC8uqrBgQAcUQFMeWeg-PkLvDTHk43Dn4_aNrxhmWwMNQfkjqx3wd2Fvta9j8yG2Qn790Gwb5psGcmBhqMJUUnFrGpyxQDhFIzzodmPokM7tnUxBNj-JuES_4CE-BvZICH4jKLp0TMu-WQsVst0ss-vY2RPdU1MzL59mq_eKk8Rv9XhxIr3WteA2ZlrgVyT0cwH3hlCnRUsLfHtIEb8k1Y_WaqKUu3DaKPxqRi6u0rN7RO2uZYPzC454xe-mg https://openid.net/specs/openid-connect-core-1_0.html#id_tokenExample 2つのドットがあり3つのパートに区切られていることがわかります。これらは前から順にヘッダー、ペイロード、署名と呼ばれていて、このような形式のトークンを署名付き JWT (JSON Web Token) と言います。それぞれについて詳しく見ていきましょう。 まずヘッダーを Base64url デコードすると、以下のようなJSONが得られます。 { " kid ":" 1e9gdk7 "," alg ":" RS256 " } alg はJWTの署名に使用されたアルゴリズムを表します。 kid は署名検証用の公開鍵の識別子で、公開鍵暗号方式の署名の場合に含まれます。 type: "JWT" というフィールドが含まれている場合もあります。 次に、ペイロードを検証、デコードしてみると、だいたい以下のような JSON が得られます。 { " iss ": " https://server.example.com ", " sub ": " 248289761001 ", " aud ": " s6BhdRkqt3 ", " nonce ": " n-0S6_WzA2Mj ", " exp ": 1311281970 , " iat ": 1311280970 , " name ": " Jane Doe ", " given_name ": " Jane ", " family_name ": " Doe ", " gender ": " female ", " birthdate ": " 0000-10-31 ", " email ": " janedoe@example.com ", " picture ": " http://example.com/janedoe/me.jpg " } 下の方はプロフィール的な情報なので無視して、上側のフィールドの説明を書き込むと以下のようになります。 { " iss ": " トークンを発行したサーバーの識別子 ", " sub ": " ユーザーの識別子 ", " aud ": " トークンを受け取るアプリの識別子(クライアントIDなど) ", " nonce ": " nonce(下で説明します) ", " exp ": " トークンの有効期限(UNIXタイムスタンプ形式) ", " iat ": " トークンの発行日時(UNIXタイムスタンプ形式) " } nonce フィールドがありますね! nonce があることで、どのような利点があるかを以下で説明します。 登場人物は以下です。 IdP: IDトークンを発行する Relying Party: 認証のサービスを使う(アプリや、そのサーバー) ユーザー: ログインしようとしている 攻撃者: IDトークンを盗んで、不正にログインしようとしている まず、 nonce がない場合です。 nonceがない場合 IDトークンが正しく、有効期限内であれば、誰でも何回でもログインできてしまうことがわかります。このような攻撃を、リプレイ攻撃と呼びます。 次に nonce がある場合です。 nonceがある場合 ユーザーのログイン後、保存されていた nonce はすでに削除されているので、攻撃者が盗んだIDトークンは使えなくなっています。 nonce が "Number used once" の略である、ということが納得できるかと思います。 最後に署名です。署名は、ヘッダーとペイロードから、IdPだけが持っている秘密鍵を使って計算、付与されます。IDトークンを受け取ったクライアントは、 alg に書いてある方法に従って署名を検証します。ここで検証に失敗すれば、何者かによってヘッダーまたはペイロードが書き換えられているということになります。 サーバー側の実装 それでは、サーバー側の実装について考えていきましょう。結果的に、エンドポイントは2つになりました。 IDトークンの検証は nonce を含めサーバー側で行います。 LINEログインSDK のように nonce をアプリ側で検証をすることも可能ですが、結局それではIDトークンを何度も使えてしまい、サーバーから見ると nonce を含めていないのと同じ状態になってしまいます。そもそも、クライアントから送られてきたものをサーバーがそのまま信じることはよろしくありません。 そうすると、必然的に nonce の生成もサーバー側が行うことになります。 LINE と Apple の比較で、リクエストには独自の nonce を設定することができる、ということを確認したので、サーバー側から nonce を渡すエンドポイントを作れば良いですね。また、 nonce を生成してキャッシュするのにキーが必要なので、その認証認可セッションの ID もランダムに生成して返却します。これが1つ目のエンドポイントになります(わかりやすさのため、一部フィールドを省略しています)。 リクエスト { app_id: " アプリの識別子 ", service_id: " IdPの識別子(line or apple) ", type : " signup or signin " } レスポンス { nonce : " nonce ", session_id: " その認証認可セッションの識別子 " , } クライアント側はこの送られてきた nonce を使い、IdPからIDトークンを取得し、セッションIDとともにサーバーに送ります。これが2つ目のエンドポイントです。 リクエスト { app_id: " アプリの識別子 ", session_id: " その認証認可セッションの識別子 ", id_token: " IDトークン ", authorization_code: " 認可コード(アクセストークンなどの取得に使う、Appleのみ) " } サーバー側はセッション ID からキャッシュしていた nonce を取り出し、IDトークンの検証時に一致を確認します。検証に成功後、サーバーのDBにユーザーを作成、または更新し、アプリのIDトークン、アクセストークン、リフレッシュトークンを発行して返却します。アプリのトークンの発行部分については Amazon Cognito のカスタム認証を使っているのですが、ここでは触れないことにします。 レスポンス { access_token: " アプリのアクセストークン ", id_token: " アプリのIDトークン ", refresh_token: " アプリのリフレッシュトークン ", expires_in: " トークンの有効期限 " } nonce を使うことで、安全にIDトークンをやり取りしてネイティブ認証を実現できました!最終的な流れは以下の通りです。 ネイティブ認証のフロー LINE の公式ドキュメントでも、同様の方法が推奨されています。図も付いていてわかりやすいので、合わせてご覧ください。 developers.line.biz Appleの細かいポイント Bundle ID と Service ID Bundle ID はアプリ固有の識別子、 Service ID はWebアプリなどがサインインなどのWebサービスを使う際に使用する識別子です。 元々の Web アプリベースの認証方法では Service ID が使われていましたが、ネイティブ認証では Bundle ID が使われます。 この使い分けが必要になるのは、トークンの Exchange の処理、つまり、クライアントから送られた authorization_code を使ってアクセストークンやリフレッシュトークンを取得する処理です。 取得のためには 専用のエンドポイント を叩きますが、リクエストのパラメータの一つに client_secret というのがあります。詳しい作成方法については以下を参照ください。 developer.apple.com client_secret は署名した JWT で、 sub フィールドを持ちます。ここに入るものが、 Web アプリベースの認証なら Service ID 、ネイティブ認証なら Bundle ID となります(上記リンク先には App ID と書いてありますが、Bundle ID でも機能します)。 LINEの細かいポイント API SDKは自動で nonce や state を生成、検証してくれたりとなかなかリッチでしたが、APIも充実しています。特に、IDトークン検証用のエンドポイントが用意されています。 developers.line.biz ローカルでIDトークンを検証するのは少しだけ大変なので、これは有難いです。今回はAppleとIDトークンの検証処理を共通化させたかったので採用しませんでしたが、LINEのみ実装する場合には良い選択肢かと思います。 IDトークンの検証アルゴリズム LINEでは、ネイティブアプリと Web アプリで署名アルゴリズムが異なります。 公式ドキュメント で以下のように記されています。 ネイティブアプリやLINE SDK、LIFFアプリに対してはES256(ECDSA using P-256 and SHA-256)が、ウェブログインに対してはHS256(HMAC using SHA-256)が返されます。 ES256 は公開鍵暗号方式、HS256 は共通鍵暗号方式で、ES256の方が鍵管理のリスクが低いです。私も実装後に知ったのですが、一般に MPA (Multiple Page Application) の Web アプリなどはAPIサーバーとIdPが同一管理下にあることが多く、クライアントで署名検証をすることがないため共通鍵暗号方式でも十分、ネイティブアプリや SPA (Single Page Application) の Web アプリは IdP と API サーバーが分離していることが多く、場合によってはクライアント側で検証をすることもあるため公開鍵暗号方式が推奨されている、という背景があるようです。 最後に 既に動いているサービスで安全な認証認可を実現するために、いろいろな記事、動画を参考にさせていただきました。受け売りですが、認証認可や課金などの実装は、いろいろな方の集合知の上に成り立つ類のものだと考えています。この記事がまた、これから認証認可を実装する方の一助となれば幸いです。 それでは少し早いですが、皆様、今年も1年お世話になりました。 来年もどうぞよろしくお願いいたします。 参考資料 Authentication Services | Apple Developer Documentation アプリとサーバーの間で安全なログインプロセスを構築する | LINE Developers LINEログイン v2.1 APIリファレンス | LINE Developers OAuth 2.0 Threat Model and Security Considerations Final: OpenID Connect Core 1.0 incorporating errata set 1 OAuth 2.0 Threat Model and Security Considerations RS256 vs HS256 What's the difference? | Auth0
アバター
この記事は every Tech Blog Advent Calendar 2025 の 28 日目の記事です。 はじめに こんにちは!開発1部で食事管理アプリ ヘルシカ の開発をしている新谷です。これまでサーバーサイドを担当していましたが、直近ではiOS開発にも携わっています。 ヘルシカiOSでは、これまでWebViewベースの認証を採用していましたが、AppleとLINEのネイティブ認証を導入しました。ネイティブ認証では、Appleなら顔認証やパスコード、LINEならLINEアプリでのワンタップ認証が可能になり、ユーザー体験が大きく向上します。 本記事では、iOS側の実装について解説します。認証の仕組みやサーバー側の設計については、明日公開予定の「サーバー編」をご覧ください。 ネイティブ認証の全体像 ネイティブ認証のフローは以下のようになります。 ポイントは、認証サーバーが生成したnonceをSDKに渡すことです。これにより、サーバー側でID Tokenの検証時にリプレイ攻撃を防ぐことができます。nonceの役割や検証の詳細については、明日の「サーバー編」で解説します。 Sign in with Appleの実装 Sign in with AppleにはAuthenticationServicesフレームワークを使用します。 developer.apple.com ASAuthorizationAppleIDProviderの使い方 import AuthenticationServices func signInWithApple (nonce : String ) { let provider = ASAuthorizationAppleIDProvider() let request = provider.createRequest() request.requestedScopes = [.fullName, .email] request.nonce = nonce // サーバーから取得したnonceを設定 let controller = ASAuthorizationController(authorizationRequests : [ request ] ) controller.delegate = self controller.presentationContextProvider = self controller.performRequests() } Delegateでの結果受け取り extension AppleNativeAuthProvider : ASAuthorizationControllerDelegate { func authorizationController ( controller : ASAuthorizationController , didCompleteWithAuthorization authorization : ASAuthorization ) { guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential , let identityTokenData = credential.identityToken, let idToken = String(data : identityTokenData , encoding : .utf8), let authorizationCodeData = credential.authorizationCode, let authorizationCode = String(data : authorizationCodeData , encoding : .utf8) else { // エラーハンドリング return } // idToken と authorizationCode をサーバーに送信 } func authorizationController ( controller : ASAuthorizationController , didCompleteWithError error : Error ) { // ユーザーキャンセルやその他のエラー処理 } } 取得できるもの Sign in with Appleからは以下の情報を取得できます。 項目 説明 ID Token JWTフォーマット。nonceが含まれる Authorization Code サーバーでのトークン取得に使用 User Identifier ユーザーの一意な識別子 Full Name 初回認証時のみ取得可能 Email 初回認証時のみ取得可能 LINE SDKの実装 LINE LoginにはLINE SDK for iOS Swiftを使用します。 developers.line.biz LINE SDKのセットアップ Swift Package Managerで以下のURLを追加します。 https://github.com/line/line-sdk-ios-swift.git Info.plistにも設定が必要です。 <key> LineSDKConfig </key> <dict> <key> ChannelID </key> <string> YOUR_LINE_CHANNEL_ID </string> </dict> <key> CFBundleURLTypes </key> <array> <dict> <key> CFBundleURLSchemes </key> <array> <string> line3rdp.$(PRODUCT_BUNDLE_IDENTIFIER) </string> </array> </dict> </array> <key> LSApplicationQueriesSchemes </key> <array> <string> lineauth2 </string> </array> LoginManagerの使い方 import LineSDK func signInWithLine (nonce : String , from viewController : UIViewController ) { LoginManager.shared.login( permissions : [ .profile, .openID ] , in : viewController , parameters : . init (IDTokenNonce : nonce ) // サーバーから取得したnonceを設定 ) { result in switch result { case .success( let loginResult ) : guard let idToken = loginResult.accessToken.IDToken else { // エラーハンドリング return } // idToken をサーバーに送信 case .failure( let error ) : // ユーザーキャンセルやその他のエラー処理 } } } Appleとの違い LINE SDKとSign in with Appleの主な違いは、Authorization Codeの有無です。Appleではサーバーでのリフレッシュトークン取得にAuthorization Codeが必要ですが、LINEではリフレッシュトークンがSDK内部で管理されます。 共通点として、どちらも独自のnonceを設定でき、ID Tokenを取得できます。 最初の設計と問題点 クリーンアーキテクチャでの設計 ヘルシカiOSではクリーンアーキテクチャを採用しています。アーキテクチャの詳細については ヘルシカiOSアプリのアーキテクチャについて をご覧ください。 当初、ネイティブ認証も既存のアーキテクチャに従って以下のように設計しました。 Feature層(ViewModel) ↓ UseCase層 ↓ Repository層 ↓ Infra層(SDK呼び出し) ↓ 外部SDK(LINE SDK / AuthenticationServices) 問題点:認証処理がViewに影響を与える 実装を進める中で、この設計には問題があることがわかりました。 Sign in with Appleは ASAuthorizationController で認証処理を実行すると認証UIが表示され、 ASAuthorizationControllerPresentationContextProviding で表示先のWindowを指定します。 // Sign in with Apple:認証UIを表示するためにWindowを指定 extension AppleAuthProvider : ASAuthorizationControllerPresentationContextProviding { func presentationAnchor ( for controller : ASAuthorizationController ) -> ASPresentationAnchor { return window } } LINE SDKも同様に、認証処理を呼び出すとLINEアプリまたはWebViewが起動し、Viewに影響を与えます。 // LINE SDK:認証処理を呼び出すとLINEアプリまたはWebViewが起動 LoginManager.shared.login( permissions : [ .profile ] , in : viewController , parameters : . init (IDTokenNonce : nonce ) ) つまり、これらの認証処理を呼び出すとViewレイヤーに影響を与えることになります。 Infra層は本来、外部APIやLocalStorageなど、UIに依存しない外部リソースへのアクセスを担当する層です。 認証処理がViewに影響を与えるものをInfra層に配置するのは、アーキテクチャとして適切でないと考えました。 解決策:NativeAuthパッケージの分離 この問題を解決するために、認証処理をクリーンアーキテクチャの外に独立したパッケージとして分離しました。 新しいアーキテクチャ Feature層(ViewModel) │ ├──────────────────────> NativeAuthパッケージ(独立) │ ├── LineNativeAuthProvider │ └── AppleNativeAuthProvider ↓ UseCase層 ↓ Repository層 ↓ Infra層 ポイント NativeAuthパッケージをクリーンアーキテクチャとは独立した位置に配置 ViewModelから直接NativeAuthProviderを呼び出す構成に変更 UseCase/Repository/Infra層はサーバーとの通信(nonce取得、ID Token検証)に専念 この設計には、UIに依存する処理をInfra層に置かずに済み、認証処理を独立パッケージとして管理できるというメリットがあります。一方で、ViewModelが認証処理を直接呼び出すため、Feature層の責務が増えるというデメリットもあります。 ただ、Infra層にUI依存のコードを置くことの違和感の方が大きかったため、今回はこの設計を選びました。 まとめ 最近サーバーサイドからiOS開発も担当するようになったので、モバイルアプリ特有のアーキテクチャには苦戦しました。 特にViewはサーバーでは意識しない概念だったので、今後も適切な場所に配置できるよう気をつけていきたいです。 明日は「サーバー編」として、nonceの役割やID Tokenの検証など、サーバー側の実装についての記事が公開されます。ぜひそちらもご覧ください。 参考資料 Implementing User Authentication with Sign in with Apple | Apple Developer Documentation ASAuthorizationAppleIDProvider | Apple Developer Documentation GitHub - line/line-sdk-ios-swift: Provides a modern way of implementing LINE APIs. LINEログインSDK | LINE Developers
アバター
この記事は every Tech Blog Advent Calendar 2025 の 27 日目の記事です。 はじめに こんにちは。リテールハブ開発部の清水です。 私たちは小売向けサービスをLaravelで開発しています。 このプロジェクトではGit hooksのpre-commit設定を使用してコミットのタイミングでLaravel Pint, Larastanを呼び出すことでコード品質を整えるための仕組みを使用しています。 この仕組みのベースは、プロジェクト初期に整備されたものを引き継いだもので、今回その内容を見直しながら整理しました。 ちょうど良い機会でしたので、本記事で私たちが使用している設定内容をご紹介いたします。 Git hooksとは? Git hooksとは、git commit や git push などの Git 操作をきっかけに、自動でスクリプトを実行できる仕組みです。 コミット前にチェック処理を挟むなど、人の操作ミスを防ぐための自動処理を組み込む用途で使われます。 https://git-scm.com/docs/githooks Laravel Pintとは? Laravel Pint は、Laravel公式が提供している PHPコードの自動フォーマッターです。 決められたコーディング規約(Laravel / PSR-12 など)に従って、PHPコードの書き方を自動的に統一します。 https://laravel.com/docs/12.x/pint Larastanとは? Larastan は、PHP の静的解析ツール PHPStan を Laravel 向けに拡張したツールです。 コードを実行せずに解析し、存在しないプロパティや型の不整合などの問題を事前に検出します。 https://github.com/larastan/larastan コミットを行った時の処理の流れ git commitを実行すると、Git hooksのpre-commitフックが起動 コミット対象のPHPファイルを取得 Laravel Pintによるフォーマットチェック Larastanによる静的解析 3 or 4のチェックでエラーが検出された場合、コミットを中断 全てのチェックをパスした場合のみ、コミットが完了 pre-commit設定内容 #!/bin/sh set -eu # --- 1) コミット対象(ステージ済み)のPHPファイルだけ拾う --- php_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.php$' || true) # PHPファイルがなければ何もしない if [ -z "$php_files" ]; then exit 0 fi echo "コミット対象のPHPファイルをチェックしています..." # --- 2) Pint:フォーマットチェック(修正はしない) --- echo "Pintでフォーマットをチェックしています..." if ! echo "$php_files" | xargs ./vendor/bin/pint --test; then echo "" echo "❌ フォーマットエラーがあります。" echo " 以下のコマンドで修正してください:" echo " make lint" exit 1 fi echo "✓ フォーマットチェック OK" # --- 3) Larastan:静的解析 --- if echo "$php_files" | grep -qE '^app/'; then echo "Larastanで静的解析を実行しています..." if ! ./vendor/bin/phpstan analyse --no-progress --memory-limit=1G; then echo "" echo "❌ 静的解析エラーがあります。" echo " エラーを修正してから再度コミットしてください。" exit 1 fi echo "✓ 静的解析 OK" fi echo "" echo "✓ 全てのチェックが完了しました。" exit 0 Makeコマンドの紹介 コミットのタイミングだけではなく、手動でLaravel Pint, Larastanを実行したい場面もあります。 以下のMakeコマンドで実行できるようにしています。 # コードスタイルチェック&修正 lint: @files=$$(git diff --cached --name-only --diff-filter=ACM | grep "\.php$$" | sed "s|^$$(basename $$(pwd))/||"); \ if [ -n "$$files" ]; then \ docker compose -f $(COMPOSE_FILE) exec -T $(CONTAINER_PHP) sh -c "./vendor/bin/pint $$files"; \ else \ echo "ステージされたPHPファイルがありません"; \ fi # コードスタイルチェック lint-check: @files=$$(git diff --cached --name-only --diff-filter=ACM | grep "\.php$$" | sed "s|^$$(basename $$(pwd))/||"); \ if [ -n "$$files" ]; then \ docker compose -f $(COMPOSE_FILE) exec -T $(CONTAINER_PHP) sh -c "./vendor/bin/pint --test $$files"; \ else \ echo "ステージされたPHPファイルがありません"; \ fi # 静的解析 larastan: docker compose -f $(COMPOSE_FILE) exec $(CONTAINER_PHP) ./vendor/bin/phpstan analyse --memory-limit=1G 実際にコミットする流れ Laravel Pint, Larastanで弾かれる内容のコードを作成 <?php namespace App\Http\Controllers; // importが名前順になっていない use Retailapp\Common\Models\User; use Illuminate\Http\JsonResponse; use App\Http\Controllers\Controller; class TestController extends Controller { public function index () : JsonResponse { $ user = User :: find ( 1 ) ; $ name = $ user -> undefined_property; // 存在しないプロパティを呼び出している return response () -> json ([ 'name' => $ name ]) ; } } コミットを試みると、フォーマットエラーで弾かれる % git commit -m '弾かれてほしいコミット' コミット対象のPHPファイルをチェックしています... Pintでフォーマットをチェックしています... ⨯ ──────────────────────────────────────────────────────────────────── Laravel FAIL ............................................. 1 file, 1 style issue ⨯ app/Http/Controllers/TestController.php no_unused_imports, ordered_import… ❌ フォーマットエラーがあります。 以下のコマンドで修正してください: make lint pintで自動的に修正 % make lint ✓ ──────────────────────────────────────────────────────────────────── Laravel FIXED ...................................... 1 file, 1 style issue fixed ✓ app/Http/Controllers/TestController.php no_unused_imports, ordered_import… もう一度コミットすると、今度はLarastanで弾かれる % git commit -m '弾かれてほしいコミット' コミット対象のPHPファイルをチェックしています... Pintでフォーマットをチェックしています... ──────────────────────────────────────────────────────────────────── Laravel PASS ............................................................ 1 file ✓ フォーマットチェック OK Larastanで静的解析を実行しています... ------ -------------------------------------------------------------------------- Line Http/Controllers/TestController.php ------ -------------------------------------------------------------------------- :14 Access to an undefined property Retailapp\Common\Models\User::$undefined_property. 💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property ------ -------------------------------------------------------------------------- [ERROR] Found 1 error ❌ 静的解析エラーがあります。 エラーを修正してから再度コミットしてください。 Larastanに違反する部分を修正 <?php namespace App\Http\Controllers; use Illuminate\Http\JsonResponse; use Retailapp\Common\Models\User; class TestController extends Controller { public function index () : JsonResponse { $ user = User :: find ( 1 ) ; $ name = $ user -> name ; // 修正 return response () -> json ([ 'name' => $ name ]) ; } } コミット成功 % git commit -m '通ってほしいコミット' コミット対象のPHPファイルをチェックしています... Pintでフォーマットをチェックしています... . ──────────────────────────────────────────────────────────────────── Laravel PASS ............................................................ 1 file ✓ フォーマットチェック OK Larastanで静的解析を実行しています.. [OK] No errors ✓ 静的解析 OK ✓ 全てのチェックが完了しました。 おわりに 本記事では、私たちの Laravel プロジェクトで使用している Git hookのpre-commit 設定と、その中で Laravel Pint・Larastan をどのように組み込んでいるかをご紹介しました。 同様の仕組みを検討されている方の参考になれば幸いです。 最後までお読みいただきましてありがとうございました。
アバター
Go 1.26で変わるgo fix この記事は every Tech Blog Advent Calendar 2025 の 26 日目の記事です。 はじめに go fixとは Go 1.26での変更点 modernizeとは 使い方 実行例 inlineとは 使い方 実行例 IDEでの修正 新しいgo fixでできること まとめ はじめに 開発本部でデリッシュキッチンアプリ課金ユーザー向けの開発を担当している hond です! 先日2026年2月にリリース予定のgo1.26のRelease Candidate 1であるgo1.26rc1がリリースされました。もうrc1は確認できたでしょうか?確認がまだの方は こちら から確認できるのでぜひ! 今回はGo 1.26でgo fixが大幅に変更されるとのことだったのでそちらについて説明しようと思います。 go fixとは go fix はGo 1リリースにて破壊的変更を含む古いAPIを特定し、新しいものに修正するツールとして追加されました。 go fix によって大まかな修正はできるため、私たちはコアな部分の修正に集中できるようになっていました。 Go 1リリースの際は go fix での修正対象は多く Go 1のRelease Notes で Updating: Running go fix と調べるだけでも14件ヒットし、 ソースコード を確認すると37件ものルールがあることが確認できます。その後も go fix のルールは追加削除が繰り返されていきましたが、Goがメジャーバージョン内での後方互換性を担保しているので最近はなかなか変更が行われず使わないコマンドとなっていました。 Go 1.26での変更点 github.com Go 1.26では cmd/go: fix: apply fixes from modernizers, inline, and other analyzers にて go fix が大幅に変更される旨のProposalがAcceptされました。 この変更の背景は、元のissueである cmd/fix: remove all functionality とGopherCon 2025でのAlan Donovanの発表( Analysis and Transformation Tools for Go Codebase Modernization )から確認できます。具体的には以下の3点が挙げられています。 「go fixとは」でも触れた既存 go fix の機能である context や buildtag の修正が不要になったこと コードレビュー時間の短縮や開発者の教育のためにモダン化が必要だったこと AI/LLMが生成するコードの品質改善のためにモダン化が必要だったこと これらの背景を踏まえ、 modernize や //go:fix inline のようなGoのコードのモダン化を目的とするアナライザーを go vet 同様に go fix でも利用可能にすることがこのProposalの目的となっています。 go vet と go fix の使い方は似ていますが、あくまで go vet はアナライザーを用いて静的解析した診断結果を 報告 するツール、 go fix は検出した問題をアナライザーが提案する修正案に 修正 するツールという使い分けになるようです。 以降では、Go 1.26の go fix で利用可能になる modernize と inline について詳しく説明します。 modernizeとは pkg.go.dev modernize は golang.org/x/tools/go/analysis/passes/modernize で公開されているアナライザーのスイートです。Goのコードを最新の構文や標準ライブラリの機能を利用して、より簡潔でモダンな形式に修正することを目的としています。 現在24個のアナライザーが登録されており、その中から代表的なものを紹介します。Go 1.26で追加される errors.AsType のアナライザーも既に登録されています! Analyzer 説明 any interface{} を any に修正する errorsastype errors.As をGo 1.26で追加された errors.AsType に修正する 使い方 以下のコマンドでmodernizeを実行することができます。 $ go run golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest -fix ./... また、特定のアナライザーを有効・無効化することも可能です。 # anyアナライザーを有効化 $ modernize -any =true -fix ./... # anyアナライザーを無効化 $ modernize -any =false -fix ./... 基本的には修正を適用しても動作が変わらないよう設計されていますが、 bloop のように完全に動作を保証できないアナライザーもあり、それらはデフォルトでは無効化されています。 実行例 以下では実際に any アナライザーを使って interface{} を any に修正してみます。 修正前のコード: package main import "fmt" type OldInterface interface {} func main() { old := OldInterface( 1 ) fmt.Println(old) } コマンド実行: $ go run golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest -fix ./... 出力(差分): import "fmt" -type OldInterface interface{} +type OldInterface any func main() { old := OldInterface(1) このように、 interface{} が any に自動で修正されたことが確認できます。 inlineとは pkg.go.dev inline は golang.org/x/tools/go/analysis/passes/inline で公開されているパッケージで、 inline と gofixdirective の2つのアナライザーが含まれています。 inline アナライザーは //go:fix inline コメントディレクティブに基づいて修正を行います。主に非推奨になった関数や定数を新しいものに置き換えるために使用され、 ioutil package などで利用されています。 具体的な使い方は以下の通りです。 関数の場合 :修正したい関数をラップした関数を作成し、その関数にコメントディレクティブを加えることで修正が可能です。 //go:fix inline func Square(x int ) int { return Pow(x, 2 ) } 定数の場合 :修正したい変数を左辺に、修正後の変数を右辺に記述し、コメントディレクティブを加えることで修正が可能です。 //go:fix inline const Ptr = Pointer 使い方 以下のコマンドで inline を実行することができます。 $ go run golang.org/x/tools/go/analysis/passes/inline/cmd/inline@latest -fix ./... また、goplsを用いているIDE上では対象をホバーすることでも修正が可能です。 実行例 以下では、 OldFunction と NewFunction を定義し、 OldFunction を NewFunction に修正する際の例を示します。 修正前のコード: package main import "fmt" func main() { OldFunction() NewFunction() } //go:fix inline func OldFunction() { NewFunction() } func NewFunction() { fmt.Println( "NewFunction" ) } コマンド実行: $ go run golang.org/x/tools/go/analysis/passes/inline/cmd/inline@latest -fix ./... 出力(差分): import "fmt" func main() { - OldFunction() + NewFunction() NewFunction() } このように、 OldFunction が NewFunction に修正されるのが確認できます。 IDEでの修正 コマンドを使わずに、IDE上で対象をホバーすることでも修正が可能です。 IDEでホバーした時の画像 Quick Fixを選択すると、修正候補が表示されクリックすることで修正が可能です。 Quick Fixを押した時の表示 新しいgo fixでできること ここまで説明した modernize と inline は、Go 1.26からは go fix を実行するだけで適用できるようになります。今までは破壊的変更を修正するためのツールだった go fix が、Goのコードをモダン化していくためのツールへとシフトしました。 実際にgo1.26rc1を使って go fix を実行してみます。 $ go1.26rc1 fix ./... modernizeの結果: import "fmt" -type OldInterface interface{} +type OldInterface any func main() { old := OldInterface(1) inlineの結果: import "fmt" func main() { - OldFunction() + NewFunction() NewFunction() } このように、 go fix を実行するだけでコードをモダン化された状態に持っていくことができます。コード品質の担保がより容易になるため、pre-commitフックやCI、AI/LLMの出力後に適用するルールとして定義するのが良さそうです! まとめ 私自身 go fix を使う機会がなかったので、今回のアップデートで既存の go fix とモダン化の為のツールとして進化した go fix を会社のコードに適用するいい機会になりました。既存の go fix での修正点は一部の context のみでしたが、新しい go fix を実行したところ1249の修正点が確認されました。10年もののサービスという事もありこれらのコードを全て手動で特定し、修正していくとなると莫大な労力を要するので公式からこのようなツールが出ていることはとても有り難いなと思いました。 上記で修正されたのは modernize に定義されたアナライザーやそれぞれのpackageにて //go:fix inline が定義されているものですが、 //go:fix inline に関しては普段の開発でも非推奨化したがバージョン互換性担保の観点で削除できないコードを管理する際にとても有用なので積極的に利用して行きたいです。 AIはどうしても既存のコードベースに品質が左右されてしまうので新しくなった go fix を用いて、継続的な品質改善が重要だと感じました。また、公開されたコードベースを元にAIは学習していくので今後の自分を含めたGopherがAIを通してより良いコードを書くためにも積極的にモダン化を行おうと思います。 ここまで読んでいただきありがとうございます。Go 1.26で go fix がどう変わるか迷っている方の助けになれたら幸いです!
アバター
この記事は every Tech Blog Advent Calendar 2025 の25日目の記事です。 目次 はじめに 設計から軌道修正まで 1. 何を目指していたか 2. 運用して顕在化した問題 検索クエリの生成が安定しない 見出しに合うレシピが必ず存在するかどうかはわからない 条件に合わないレシピが含まれてしまう 3. 問題の原因 プロンプトの肥大化 不要な思考(ニーズ分析)を挟んでいた ベクトル検索に対して除外の前後処理を入れていなかった 根本にあった認識の甘さ 4. どう軌道修正したか ニーズ分析のステップを排除 見出しの自由生成・クエリ生成を排除 レシピをベクトル検索からOpenSearch利用に変更 LLM-as-a-Judgeの導入 5. 前提条件の見直し 振り返りから見えたワークフロー設計の勘所 ステップごとに切り分けて進める AIに任せる部分・ルールベースにすべき部分を見極める 運用を見据えたガードレール設計 今後の取り組みと展望 参考文献 はじめに こんにちは。2025年4月にソフトウェアエンジニアとして新卒入社した 黒髙 です。普段は デリッシュキッチン の開発に携わっています。 デリッシュキッチンにはレシピだけでなく、料理に関する情報をまとめた 記事 や レシピ特集 など、多様なWebコンテンツがあります。コンテンツディレクターの業務効率化の一環として、ユーザーニーズを満たすコンテンツ作成をAIでサポートする試みを、私は設計から実装までを一貫して担当しました。 当初はAIに創造的なコンテンツ生成を任せようと意気込んでいたのですが、実運用では想定外の課題が次々と顕在化し、最終的にはルールベースの制約(ガードレール)を多く追加することになりました。 以下画像は、実際に生成された アボカドサーモンの魅力を引き出すレシピ集 | デリッシュキッチン のコンテンツの一部です。 ページの一例 実際に自動生成して公開しているコンテンツは、 セレクション一覧 からもご覧いただけます。 また、記事生成に関わる処理の全体像は次の通りです。AIワークフローの詳細は後述します。 処理の流れとアーキテクチャ 本記事では、AIワークフロー設計で何を期待し、何が起き、どのように軌道修正したかを振り返ります。LLMや生成AI活用の文脈では基本的な話も含みますが、現場で使えるものにするためにどう整えていったのかの判断を中心にまとめます。 設計から軌道修正まで 本章では設計段階から運用に至るまで、AIワークフローに関する部分の振り返りを行います。 1. 何を目指していたか 理想は、AIがWebページの構成を考え、分析結果に応じて組み替えられることです。ただし今回は、早期にコンテンツを公開することを優先したPoC(初期検証フェーズ)として、ページ構成を「見出し+説明文+レシピリンク集」の繰り返しに限定しました。 - 見出し1 - 見出し1の説明文 - 見出し1のレシピリスト - レシピA - レシピB - レシピC - 見出し2 ... (以下繰り返し) 当初は、ニーズ分析→見出し・説明文の生成→関連レシピの検索までをAIが一貫して行うことを目指しました。具体的には、次の5ステップのLLMパイプラインを設計しました。 当初のワークフローと具体例 また、初期検証をできるだけ早く進めるため、 デリッシュAI で利用しているレシピ一覧を埋め込みベクトルに変換したテーブルを使いました。 2. 運用して顕在化した問題 数パターンのキーワードで試したところ、課題は残るものの、記事として形になることは確認できました。そこでバックエンド/フロントエンドを実装し、キーワード一覧をもとにバッチで自動生成を開始しました。 ところが「キーワードに沿ったレシピが選ばれているか」の観点の公開前レビューでは、公開できる記事が想定より少ない結果になりました。さまざまな入力パターンを事前に十分検証し、試行段階で見えていた課題にこの時点で対処しておくべきでした。 具体的な問題としては以下のようなものがあります。 検索クエリの生成が安定しない 今回のフローでは画像の通り、見出しに基づいたレシピ検索クエリをLLMに生成させています。例えば 簡単に作れるごぼうと牛肉レシピ を入力しても、 簡単レシピ や 簡単牛肉 のように意図が欠けたクエリになることが稀にありました。結果として後続のLLMにも意図したリクエストを送れず、適切な記事が生成できない問題がありました。 見出しに合うレシピが必ず存在するかどうかはわからない 検索キーワード ごぼう 牛肉 から ごぼうと牛肉のローストビーフ のような見出しが生成されても、デリッシュキッチンにそのようなレシピがあるとは限りません。具体的に絞り込む見出し提案に従った結果、レシピが登場しないといった問題が発生しました。 条件に合わないレシピが含まれてしまう 例えば 簡単に作れるごぼうと牛肉のレシピ に対して、 味付け簡単! 牛すじとごぼうの塩麹煮 のような適切なレシピが出る一方で、 簡単で本格的な味! 豚バラ肉とごぼうの和風煮 といった、響きは似ているものの材料が欠けているレシピも混ざることがありました。 3. 問題の原因 プロンプトの肥大化 デリッシュキッチンでは、信頼性の高いコンテンツ品質を維持するため、多くの制作ルールが存在しています。以下は一例ですが、実際には数十個程度の記載NG項目があります。 - 健康・栄養に関する過度な表現禁止: ダイエット, ヘルシー ... - 食欲・体調への直接効果を謳う表現禁止: 食欲, 体力, 元気 ... 料理系コンテンツでは連想語がNGになることも多く、AIにとって制約の強い要求です。さらに、これらをすべてのステップで指示していたため、禁止事項を追加しても一度で要求を満たし切れないことがありました。 不要な思考(ニーズ分析)を挟んでいた ニーズに合致した構成にするためにStep 1としてニーズ分析を入れましたが、禁止事項を守らせることや、ヒットするレシピ数を担保するために無難な見出しに寄せる要件とは相性が悪く、逆にノイズになっていました。 ベクトル検索に対して除外の前後処理を入れていなかった レシピ検索としてベクトル検索とコサイン類似度によるスコアリングを用いていましたが、関係のないレシピも検索結果に含まれてしまう問題が発生していました。ベクトル検索は意味的な類似性を捉えるのには優れていますが、それ単体で材料として実際に使われているかどうかを判定することはできません。 根本にあった認識の甘さ これらの問題に共通していたのは、以下の点に対する認識の甘さでした。 運用では検証段階より多種多様なキーワードを扱うこと LLMによる不安定な出力が次段階の生成内容にも影響をもたらすこと 当初は創造的なWebサイト構築を漠然と想像していましたが、実際に必要だったのはAIが生成した記事をできるだけ早く公開し、生成から公開までのサイクルを回しながら検証を進めることでした。 しかし、少数のキーワードではそれなりに記事が生成できたことで安心してしまい、安定運用できるかという観点が抜け落ちたまま次の開発に進んでしまっていました。 4. どう軌道修正したか 問題を踏まえて、以下のような軌道修正を行いました。 以前のステップ 変更点 1. キーワードのニーズ分析 削除 2. 小見出し生成 LLMによるテンプレート選択 3. 検索キーワード抽出 ルールベースで生成 4. コンテンツ生成 変更なし 5. レシピ検索 OpenSearch + LLM-as-a-Judgeの追加 ニーズ分析のステップを排除 前述の通り、ノイズとなっていたStep 1(キーワードのニーズ分析)を削除しました。 将来的には分析データが揃い次第、数値に基づくニーズ分析を復活させ、LLMによる構成検討も行いたいと考えています。 見出しの自由生成・クエリ生成を排除 対象を絞り込まない抽象的な見出しにしたいこと 禁止ワードを含めたくないこと を踏まえると、見出しのパターンはある程度限られてくることが分かりました。ヒットするレシピを安定して出すために、LLMに自由生成させるのではなく、見出し候補を用意して選択してもらう方針に転換しました。 SUBHEADING_OPTIONS = [ { "kind" : 1 , "template" : "簡単に作れるxxのレシピ" }, { "kind" : 2 , "template" : "xxとxxの絶妙な組み合わせ" }, { "kind" : 3 , "template" : "xxを使ったアレンジレシピ" }, { "kind" : 4 , "template" : "新しいxxの料理アイデア" }, # ... ] KIND_KEYWORD_MAPPING: dict [ int , str ] = { 1 : "{keyword} 簡単" , 2 : "{keyword} 組み合わせ" , 3 : "{keyword} アレンジ" , 4 : "{keyword} アイデア" , # ... } また、見出しに基づくクエリ生成もLLMに任せるのをやめ、(検索キーワード+見出しの特徴)をルールベースで組み立てる方針に戻しました。 HyDE(Hypothetical Document Embeddings) のような手法も検討しましたが、1見出しに最大9個のレシピを網羅的に取得したい要件には合わないと判断し、簡易的で汎用的なクエリで対応することにしました。 レシピをベクトル検索からOpenSearch利用に変更 構想段階の試用では、デリッシュAIの基盤を一部利用したベクトル検索が手軽で都合が良かったので採用していました。 しかし、ワークフローを見直し、扱うキーワードや見出し表現をある程度固定化した結果、検索に求める要件も変化しました。 以下の理由から、レシピ検索機能で既に用いているOpenSearchを利用する方針としています。 多くの対象キーワードにおいて、通常検索の方が期待したレシピが安定してヒットすること レシピ検索機能で用いられているシノニム辞書をそのまま活用できること 数パターンに固定化した見出し表現においては、ベクトル検索の強みである意味的類似度の必要性が相対的に薄れたこと なお、今後扱うキーワードの幅や要件が変わった場合には、再びベクトル検索を含めた見直しを行う余地があると考えています。 LLM-as-a-Judgeの導入 更なるガードレールとして、レシピが見出しの意図に合っているかを審査するステップを追加しました。この判定を導入することで、条件に合わないレシピをより厳格に除外できることが期待できます。 { " recipe_id ": " 206162500360602656 ", " accept ": true , " reason ": " タイトル・材料に「牛すじ(牛肉)」とごぼうが含まれており、サブ見出しの「ごぼう 牛肉 簡単」に合致します(味付けが簡単な点も明記)。調理手順は煮込み工程があるものの、小見出しの意図と矛盾していません。 " } , { " recipe_id ": " 163718318442676716 ", " accept ": false , " reason ": " ごぼうは使われていますが、主な肉材が豚バラ肉であり、小見出しが想定する『牛肉』と一致しません。 " } , まとめると、フローは次の通りです。 ワークフロー 初期段階では1記事あたり3〜10件以上の修正指摘が発生していたものが、これらの改善によって修正指摘が0件のケースが大半となり、あっても1〜2件程度に収まるようになりました。 5. 前提条件の見直し ワークフローの見直しを進める中で、そもそもどのキーワードを生成対象とすべきかという前提条件にも課題があることが分かりました。 当初は、食に関連するキーワードを一律に処理していましたが、キーワードによって求められる判断や前処理の難易度が大きく異なることが次第に明らかになりました。 例えば カニ レシピ では、カニカマを許容するかどうかの意思決定が必要で、LLMには難しいです。また、 酒のあて のように抽象度の高いキーワードでは、どの切り口で掘り下げるかという追加設計が求められます。 こうした性質の異なるキーワードを同一の前提でAIワークフローに流していたことが、後段のLLM-as-a-Judgeや人手レビューの負荷を高め、全体の不安定さにつながっていました。 ひとまずは、AIワークフローの前段としてキーワードの取捨選択を行い、判断コストが高そうなものや、追加設計が必要なものは対象外とする方針で生成していきます。 振り返りから見えたワークフロー設計の勘所 これまでの試行錯誤を振り返ると、技術的な工夫以上に ワークフロー設計そのものの考え方 が重要だったと感じています。 ステップごとに切り分けて進める 今回のようにLLMの出力結果が後続の処理に大きく依存するワークフローでは、各ステップが単体で正しく機能するかをまず確認する必要があります。想定動作・実装方針・失敗時のハンドリングまで詰めたうえで次に進むことが、品質担保と手戻り削減の観点で重要であると感じています。 AIに任せる部分・ルールベースにすべき部分を見極める 本取り組みを通じて、LLMに任せる部分/任せない部分を明確にすることが、ワークフロー全体の安定性に直結することが分かりました。 特に意識したのは、次の3点です。 決定的に定義できる処理は、LLMに任せない 出力の揺れが後段に大きく影響する箇所では、強いガードレールを設ける 曖昧さを許容できる工程のみ、LLMの生成能力を活かす このように役割分担を整理したことで、LLMにすべてを任せる構成から、壊れにくいパイプラインへと移行できました。 運用を見据えたガードレール設計 最終目標(ユーザーニーズを満たすコンテンツ制作の工数をできるだけ減らすこと)自体は当初から変わっていません。一方でビジネス側には、まず記事を公開し、ランキングの変化を見ながら改善したいという目的がありました。そのためには、改善サイクルを回せるだけの安定した記事生成が前提になりますが、創造性を高める工夫を優先してしまったことが手戻りの原因でした。 今後の取り組みと展望 今回生成している記事は、文章の整合性や、検索キーワードに紐づくレシピになっているかという点では、一定の信頼性を確保できています。一方で、訪問ユーザーのニーズを満たすコンテンツとしては、まだ伸びしろがあると感じています。 一例ですが、以下を検討しています。 Self-Reflection によるLLMの自動改善サイクルの導入 幅広い検索キーワードに対応するルーティングの追加 ベクトル検索の再利用とレシピのスコアリング レシピページ以外のアセット活用(記事、特集、カテゴリページ) トレンドや訪問ユーザーのログを活用した記事の再生成 これらの改善により、更に品質の高い記事生成を目指していきます。 参考文献 Precise Zero-Shot Dense Retrieval without Relevance Labels Self-Reflection in LLM Agents: Effects on Problem-Solving Performance
アバター
この記事は every Tech Blog Advent Calendar 2025 の 24日目の記事です。 はじめに Swift 5.9で導入された Observation フレームワークは、 @Observable マクロを用いた簡潔な記述が可能で、特にSwiftUIのView更新において高いパフォーマンスを発揮します。 一方で、既存の Combine フレームワーク( ObservableObject )からの移行を検討する際、課題となる点がありました。それは ViewModel や Service など、 UI以外の場所での値の監視 です。Combine では @Published プロパティのProjected Valueを用いて値の変化をストリームとして扱えますが、それと同等の標準的な手段がこれまでの Observation フレームワークには不足していました。 Swift 6.2では、この点が解消されています。 @Observable クラスのプロパティの変化を AsyncSequence として監視する機能( Observations )が追加され、Combine に依存することなく、標準APIのみで値の監視が可能になりました。 本記事では、この新しい監視手法について整理します。 ObservableObject による値の監視 Observation フレームワークが登場する以前、SwiftUI では ObservableObject と Combine を用いた状態管理が一般的でした。 @Published プロパティの projected value( $ )を利用することで、値の変化をストリームとして扱える点は大きな利点でした。 import Combine class CounterViewModel : ObservableObject { @Published var count : Int = 0 private var cancellables = Set < AnyCancellable > () } let viewModel = CounterViewModel() // $をつけることで Publisher として扱える viewModel. $count .sink { value in print( "count changed:" , value) } .store( in : & viewModel.cancellables) この仕組みにより、View の更新だけでなく、ViewModel や Service など UI 以外のレイヤーでも、値の変化を一貫した方法で監視できていました。この点は、ObservableObject が持つ強みの一つです。 Observation フレームワークと withObservationTracking Swift 5.9 で Observation フレームワークが導入されると、View 更新のパフォーマンスと記述の簡潔さは大きく改善されました。一方で、View 以外で値の変化を検知する方法として利用されているのが withObservationTracking です。 import Observation @Observable class Counter { var value : Int = 0 } let counter = Counter() func observe () { withObservationTracking { // 1. ここで値を読む(アクセスする)ことで監視対象にする print( "Current value: \( counter.value ) " ) } onChange : { // 2. 変更前(willSetタイミング)に呼ばれる print( "Value will change..." ) Task { // 3. 再帰的に監視を続ける observe() } } } withObservationTracking は、クロージャ内で読み取られたプロパティへの依存関係を自動的に追跡し、それらが変更された際に onChange を呼び出します。これは SwiftUI の View 更新を支える中核的な仕組みです。 withObservationTracking の課題 しかし、この方法をロジック層で使うには、いくつかの問題がありました。 値そのものを直接受け取れない onChange で通知されるのは「変更される」という事実のみで、変更後の値は再度読み取る必要があります。 再登録が必要 変更を継続的に監視するためには、onChange の中で再び withObservationTracking を呼び出す再帰的な実装が必要です。 非同期処理との相性 withObservationTracking 自体は同期的であり、Callbackベースの記述になるため、async/await のフローと組み合わせる際に直感的な記述が難しくなります。 Swift 6.2: AsyncSequence による監視の導入 Swift 6.2 では、こうした課題を解消する形で、 @Observable クラスのプロパティの変化を AsyncSequence として監視できる Observations 型が追加されました。 import Observation @Observable class Counter { var value : Int = 0 } let counter = Counter() // 監視対象を定義 let counterChanges = Observations { counter.value } Task { for await value in counterChanges { print( "value changed:" , value) } } withObservationTracking が持っていた課題は解消され、簡潔な記述で安全に値の変化を監視できるようになりました。 Combine フレームワークに依存せず、自然な形でSwiftの並行処理モデルに直接統合されています。 まとめ Swift 6.2 の Observations 導入により、ObservableObject が持っていた「Combine による値の監視」という優位性は解消されました。 ただし、この機能を利用するためには iOS 26以降の環境が必要となるため、サポートOSの要件によっては、すぐにプロダクションコードで全面的に採用することは難しいかもしれません。 とはいえ、「View 以外での監視」という最大の課題に対する標準的な解法が示された意義は大きく、安心して Observation への移行を進められる環境が整ったと思います。
アバター
Goエンジニアになって半年経ったので振り返る この記事は every Tech Blog Advent Calendar 2025 の 23 日目の記事です。 はじめに こんにちは!デリッシュキッチンで主にバックエンドの開発を担当している秋山です。 私は今年の6月にエブリーへバックエンドエンジニアとして中途入社し、そこから実務でGo言語を使い始めました。 それまでは約3年間主にRuby on Railsを触っていました。 この記事では、Goに転向して半年経った今感じていることや学びを振り返りたいと思います。 Goを使い始めて感じたこと 他の言語から来ると最初は戸惑う部分もありますが、使っていくとGoの良さが見えてきます。 ここでは、他言語から移ってきた人の視点で感じたことを書きます。 Goバージョン間の後方互換性が嬉しい 2012年にGoの1系が出てから毎年2回メジャーアップデートされますが、Goでは後方互換性の維持を考慮されています。 そのため、比較的容易にGoのバージョンアップを行うことができます。 業務の中でGoのバージョンアップを行わなければならないことがありました。 Goに触れる前は「メジャーアップデートに破壊的変更はつきもの」だと勝手に認識していたので、Goのバージョンアップの容易さに驚かされました。 後方互換性が保たれているおかげで「アップデートしたら動かなくなるかも」という不安が少なく、安心して最新バージョンを追えるのが良いポイントだと思います。 エラーハンドリングに違和感があった Goで書かれたアプリケーションコードを初めて見た時の話です。 try/catch(rubyの場合はbegin/rescueですが)のエラーハンドリングに慣れていたこともあり、下記のようにnilチェックを行うエラーハンドリングに当時違和感がありました。 x, err := call() if err != nil { return err } ちなみに、rubyでは通常このようにエラーハンドリングが行われます。 begin x = call() rescue => e # call()で例外が発生した時にここの処理が実行される 今ではGoの書き方に慣れてきまして、エラーを値として扱うことで明示的に処理フローを追いやすくて良いなと思います。 GoのFAQが便利 他にもRuby/RailsからGoに移った時に クラスってないの? 継承ってないの? などのような疑問が出ましたが、下記のFAQに回答がありました。 go.dev このFAQを読んだだけでもGoの思想に対する理解を1歩進められそうです。 FAQ以外にもgithub上には議論が白熱しているissueやdiscussionもあるので、Goの機能追加の背景なども知れてより理解が深められそうです。 エラーハンドリングについても議論が白熱したみたいです。 github.com go.dev go.dev AI活用で転向のハードルは下がっている? AIにコードを書いてもらう機会が増えたり言語理解にAIを使用することができるようになり、新しい言語を学ぶハードルは確実に下がっていると思います。 ただ、Goを始める前「AIにコードを書いてもらうから転向もすんなりいけるかな」のように思っていた節がありましたが、実際にはそんなことはありませんでした。 日々の業務ではCursorを使わせてもらっており、Goの知識が少なかった私にとってはCursorは非常に強力なツールです。 しかし、AIが書いたアプリケーションコードを人間が全く確認せずにそのままリリースすることは現時点でリスクが高いので、AIが出力したコードをまずは自分でレビューします。 この時結局Goの知識がないとAIコードのレビューに時間がかかってしまいます。 また、チーム内でのレビューもGoの理解なしには難しいところがあるので、結局はGoの理解が大事だなと思いました。 Goエンジニアの成長を促進するエブリーの環境 Goエンジニアへの転向において、会社の環境も大きな助けになりました。 外部イベントへの積極的な参加 エブリーでは外部イベントの積極的な参加や登壇を推奨しており、実際に登壇を行っているメンバーもいます。 私自身も今年の9月に行われた Go Conference 2025 に参加しました! ちなみに、エブリーは今年もGo ConferenceにプラチナGoルドスポンサーとして協賛しました。 Go Conferenceの様子は下記ブログをご覧ください。 tech.every.tv 会社としてこのようなイベントに積極的に協賛しているのは私自身モチベーションにつながります。 また、つよつよエンジニアの方と話す機会もあり、僕もつよつよに成長したいと感じました。 定期的なGo勉強会 エブリーでは現在 2週に1回の頻度でGoの勉強会 が開催されています。 この勉強会では、持ち回りで担当者がテーマを持ってきて使い方を学ぶだけでなく、なぜそうするのか・どうしたら良さそうかなど参加者それぞれが疑問に思ったことをどんどん深掘りしていくスタイルになっています。 この勉強会に参加することで、日頃の業務で使っているだけでは得られないGoの知識をの取得や理解を深めることができています。 また既存のコードに対しても「本当はこの書き方の方が正しいよね」という気づきも多く、実務のコード品質向上にも繋がっています。 今後も日々邁進 AIが活躍する現在も言語理解や日頃の情報キャッチアップは大事だと思っています。 そのため、GoももちろんですがGoに限らずこれからも技術向上に邁進していきます! 参考 https://github.com/golang/go/issues/32437 https://go.dev/blog/error-syntax https://go.dev/issue/71460 https://tech.every.tv/entry/2025/09/28/195717
アバター
この記事は every Tech Blog Advent Calendar 2025 の 22 日目の記事です。 こんにちは @きょー です! 先日 Go Workshop Conference 2025 IN KOBE に参加してきました。とても楽しかったので記事として皆さんにも共有できればなと思います! 会場の様子 はじめに Go Workshop Conference とは? gwc.gocon.jp 公式の HP にも書いてありますが、聞くだけでなく実際に手を動かすワークショップを中心とした Go 言語のイベントです。ソフトウェアからハードウェアなど幅広いワークショップがあり、Go 言語に関心のある方は楽しめる内容になっていました。 ワークショップは午前と午後に分けられ以下のようなものがありました。(資料は自分が見つけられたものを記載してます。既にアップロードされているもの等ありましたら @きょー まで連絡お願いします!) 午前に開催されたワークショップ 低レベルコンテナランタイム自作講座 ~コンテナ技術の地盤を理解する~ オーガナイザー: @Takuto Nagami 資料: https://gwc2025.logica0419.dev/ Gopher のための「自由な話し合い」ワークショップ オーガナイザー: @chihiro TinyGo Keeb Tour at GWC オーガナイザー: @さご 資料: https://github.com/sago35/keyboards/blob/main/conf2025badge/build/build.md はじめての Go 言語教室 オーガナイザー: @Ryuji Iwata 資料: https://www.docswell.com/s/qt-luigi/ZLVDPP-the-first-go-room-setup-20251213, https://www.docswell.com/s/qt-luigi/5PGDP1-the-first-go-room-intro-20251213, https://www.docswell.com/s/qt-luigi/5X6X7X-the-first-go-room-basic-short-20251213, Go カードゲームで遊ぼう オーガナイザー: @瀬上祐匡 並行処理スピードアップコンテスト オーガナイザー: @kuro 資料: https://github.com/nnnkkk7/go-concurrency-workshop 午後に開催されたワークショップ 動かして理解する適材適所のプロファイリング オーガナイザー: @task4233 資料: https://gwc-profiling.vercel.app/docs/01_workshop/ Gopher のためのチームビルディングするインプロワークショップ オーガナイザー: @ysaito Gopher くん基板を作って TinyGo で遊ぼう オーガナイザー: @satoken 資料: https://github.com/sat0ken/gopher-board-workshop/blob/main/README.md Go Doc Comments 完全理解ハンズオン オーガナイザー: KOBE.go オーガナイザー @たくてぃん @uji @is_ryo 資料: https://docs.google.com/presentation/d/1mWg8TbfQPbOEjtmh1wmddzEZmWSsDmDA3U_K0sij9LA/edit?slide=id.p#slide=id.p, https://docs.google.com/presentation/d/1toI89vJrF68C4Gfb6XF0BPHnyTTUtOyPujh4mN8p4hc/edit?slide=id.g3952349bba0_0_0#slide=id.g3952349bba0_0_0 Go with AI オーガナイザー: @tenntenn 資料: https://github.com/gohandson/adk-ja はじめての Go 体験!そしてあなたのキーキャップがおしゃれになる! オーガナイザー: @micchie 資料: https://github.com/mi-bear/name-contract-go, https://github.com/mi-bear/keycap-nail-art-workshop/ どれも面白そうなワークショップですよね!自分は TinyGo を触ってみたいという思いから午前は「TinyGo Keeb Tour at GWC」午後は「Gopher くん基板を作って TinyGo で遊ぼう」に参加してきました。 何か選ぶということは何かを選ばないということ、魅力的なワークショップが集まっている中で二つしか選べないというのはとても苦しいものですね... 参加したワークショップ 午前の部、TinyGo Keeb Tour at GWC まず席に着くと視界に映るハンダゴテ。普段の開発では触る機会が全くないためこの時点でハードをいじることへのワクワクと「Go Workshop Conference にきたんだ...!!」という実感が湧いてきて小躍りしそうになったのを覚えています。 以下の写真に写っているものを最初に配られました。 (余談ですが、この Gopher の基板デザインがとにかく可愛くて、TinyGo Conference の基板を再利用しているのもエコでいいなと思いました。) ハンダゴテしやすいように台座(3D プリンターで作られたらしい)も用意していただいたのでそれを組み立てたりすると ↓ のような感じに。 裏側にも Gopher 発見! 実際にハンダ付けしている様子。 これは LED。こんな小さいけどお前、いけるのか、、、? だんだん形になってきた。 ディスプレイ、スピーカーをつけて(さらば真ん中の Gopher) キーボードもつけたら完成!! 実際に動かしてはないですが、ここまで形になるとなんかもう動きそうですね(というか動いてくれ...!!) 今回プログラムを載せる場所は普通のサーバーとは異なり ↑ の写真の左上のマイコンです。 言語は C? C++? いえ、ここは Go Workshop Conference。もちろん Go です。ただ普段使っている Go のバイナリを置こうとするとマイコンのスペック的に容量が足りなかったりするため、より小さいバイナリを作れる TinyGo というコンパイラを使っていきます。 tinygo.org 少し悲しくなりますがここからはハンダゴテは使わないのでさよなら、、、 パソコンと ↓ の README をもとに今まで作ってきた基盤を動かしていきます。 https://github.com/tinygo-keeb/workshop-conf2025badge サンプルプログラムには LED を光らせたり、音を鳴らしたりするものだけでなく、ディスプレイとキーボードを使ってゲームをできるようにするものもありました。 無事自分の基盤でも LED を光らせることができました!感動!!(あの小さかった LED がこんなに発光するのすごい) オーガナイザーの @さご さん、サポートしてくださったスタッフの方々、ハンダゴテをたくさん貸し出ししてくださった @ysaito さんありがとうございました! 昼の部、はじめての Go 体験!そしてあなたのキーキャップがおしゃれになる! お昼ご飯を食べ終え会場に戻ってみると何やら面白そうなものを発見。キーキャップをデコってオシャレにできるらしい 自分も挑戦してみることに 最初にキーキャップのベースとなる色を決めていきます。いろんな色がありワクワクしてきますね。「こんな綺麗にできるのか?」という不安もありましたが一番下の右から二番目の色を選びました。直感です。 塗ったらライトを当てて固めていきます(硬化というらしい) 固まったらデコるために好きな素材を選んでいきます(写真撮るの忘れてしまったのですが、めっっっちゃ色々な素材がありました!) 自分は月、星、キャンディー、そして今の時期話題のクマを選びました。各素材が大きいのでキーキャップからはみ出てしまい載せるのが結構難しかったです。ただそこは運営の方々のサポートもあったのでなんとか思った形に完成させることができました! テーマは「ゆめかわ」。我ながら満足のいく作品ができたと思っています。え、可愛くないですか??? 午前中に作った基盤と合わせるとこんな感じ。ん〜〜〜可愛すぎて押せない!! オーガナイザーの @micchie さん、 @mikichin さんをはじめスタッフの方々ありがとうございました! 午後の部、Gopher くん基板を作って TinyGo で遊ぼう 午後も基盤をいじっていきます! もう基盤が Gopher になっている時点でテンション上がりますね。このほかにも色々な基盤がありました。(写真撮り忘れた...orz) オーガナイザーの satoken さんのブログに新作の武将 Gopher をはじめたくさんの基盤が載っているのでぜひ見にいってみてください! zenn.dev 午前に比べるとハンダ付けも慣れたものでどんどん進めていくことができました。 まずはボタンをつけて ディスプレイやスピーカー、マイコンなども取り付けて完成! TinyGo で LED が光るプログラムをマイコンに置いて動作確認!( 資料: https://github.com/sat0ken/gopher-board-workshop/blob/main/README.md ) 光らず。原因を探っていきます。 ソフトウェアだけでなく、ハードウェアのデバッグもしていく必要があるため怪しいところを一つ一つ潰していきました。 ハンダ付けがしっかりできているか確認 プログラムは問題なくビルドできているか、マイコンに置けているか確認 ブレッドボードは問題ないか、使っているジャンパー線や LED は問題ないか確認 色々調べていくうちにジャンパー線が悪いことがわかり、交換してみると、、、 光った!!! ただ LED が光るだけではありますが、ソフトウェア・ハードウェア全て繋がり思った挙動をしてくれるとめちゃくちゃ嬉しいですね。 何も分からなかったデバッグを一緒に手伝っていただいた方々本当にありがとうございました...! ディスプレイに「私は Gophers!」と表示させてみたり Gopher の画像を表示させてみたりしてたくさん遊ばせていただきました!楽しい! 最終的にここの画像は自分の推し日本酒である写楽を表示させたりして遊んでました。 写真からだとわかりずらいかもですが目の LED を高速でチカチカさせることもでき、その時の Gopher がかなりカオスで面白かったです。 オーガナイザーの @satoken さん、スタッフの方々ありがとうございました! 展示 会場には展示ブースもありました。 Go 製のゲームエンジンと、TinyGo を使ったハンコンを組み合わせた展示 こちらは @のぼのぼ さんの展示です。 Go 製のゲームエンジンで開発されたレースゲームを、ハンドルコントローラーで操作できるようにする内部の仕組みを作られたそうです。コントローラーから送る信号も、ゲームから返ってくる信号も双方向でやり取りできるようにしているため、ゲーム内のアクションが手元にダイレクトに伝わってきて臨場感がすごかったです。 実際に運転してみた動画もあるのでよければ見てみてください! https://x.com/Keyl0ve_/status/1999731494458392895?s=20 TinyGo と 3D プリンターなどの展示 こちらは @さご さんの展示です。 TinyGo と 3D プリンターを組み合わせた展示がいろいろ並んでいました。特にキーボード周りが面白く、手前に見える「Go」と表示されているものも自作とのこと。5x5 のキーボードを 4 つ組み合わせた基盤に合わせてプログラムを書く必要があり、こだわりが詰まっていました。 0 次会 懇親会まで時間があったので知り合った方々と 0 次会!こういう交流があるのもカンファレンスならではでいいですよね 懇親会 肝心の懇親会の写真を撮り忘れてしまいました。楽しいとついつい写真撮るの忘れてしまいますよね(他責) Go Workshop Conference の参加者や運営の方々で集まりわいわい楽しく過ごせました!希望者で2次会にいったりもして本当に楽しい1日を過ごすことができました。 終わりに 改めてものづくりって楽しいなと思いました。普段のソフトウェア開発ももちろん楽しいですが、ハードウェアが組み合わさり目に見えてものが出来上がっていく様子は本当に楽しかったです。(小学校の図工の時間を思い出しました。) また、今回はワークショップのみで構成されていたので、座学中心のカンファレンスとは違って「自分の手で体験を作る」感覚がとても強かったです。同じ枠に参加していても完成するものは人それぞれで、自分だけのワークショップカンファレンスを形にしているようで新鮮でした。 TinyGo についても学びが多かったです。Go のソースはそのままにコンパイラを TinyGo に切り替えるだけでバイナリのサイズが小さくなることや、ツールチェーンもほぼ Go と同じ感覚で触れることを学べた点が良かったです。 運営をはじめ、関わっていただいたスタッフの方々、一緒に楽しんでくださった参加者の皆様本当に楽しいカンファレンスをありがとうございました!
アバター
この記事は every Tech Blog Advent Calendar 2025 の 21日目の記事です。 はじめに こんにちは。 開発本部 開発1部 デリッシュリサーチチームでデータエンジニアをしている吉田です。 今回はコンピュートシステムテーブルとDatabricks Genie Research Agentを利用して、Jobのコンピュートリソースの最適化を試みた事例をご紹介します。 背景 これまで、Databricks Jobに割り当てるコンピュートリソースの最適化は、実際の実行メトリクスをUI上で確認しながら手動で調整を行う必要があり、手間のかかる作業でした。 しかし、 system.compute.node_timeline テーブルが追加されたことにより、コンピュートリソースの利用状況に関する詳細なメトリクスをSQLで直接取得できるようになりました。 このテーブルをGenieに連携させることで、メトリクスに基づいた分析が可能になり、Genieを活用して最適なコンピュートリソース構成の提案を受けることができるようになります。 system.compute.node_timelineテーブル system.compute.node_timeline テーブルは、Databricks上のコンピュートリソース(All-Purpose Compute, Jobs Compute等)におけるノードレベルのリソース使用状況を記録するシステムテーブルです。 各ノード(ドライバーおよびワーカー)について、1分粒度でCPU使用率、メモリ使用率、ディスクI/O、ネットワークトラフィックなどの主要なメトリクスが格納されています。 コンピュート システムテーブル リファレンス 主なスキーマは以下の通りです(一部抜粋)。 カラム名 説明 cluster_id クラスターID instance_id インスタンスID node_type ノードタイプ(例: i3.xlarge ) driver ドライバーノードか否か (boolean) cpu_user_percent ユーザーランドでのCPU使用率 (%) mem_used_percent メモリ使用率 (%) start_time / end_time 計測期間(1分間隔) これまでGangliaなどのUIでグラフとして確認していた情報が、SQLで直接クエリ可能なテーブルとして提供されるようになった点が特徴的です。 これにより、特定のJob実行時のリソース使用率の平均やピーク値を集計したり、リソース余剰が常態化しているクラスターを抽出したりといった分析が容易になります。 今回はこのテーブルのデータをGenie(DatabricksのAIアシスタント機能)に参照させることで、人間がグラフを目視で確認する代わりに、AIに最適なリソース構成を提案してもらうフローを構築してみます。 Genie Research Agentとは Databricks Genie Research Agent(以下、Research Agent)は、従来のGenieの機能を拡張し、多段階の推論と仮説検証を用いて複雑なビジネス上の質問に取り組むことができるAIエージェントです。 Genie spacesのResearch Agent 通常のGenieがユーザーの質問に対して単発のSQLクエリを生成・実行して回答するのに対し、Research Agentは以下のような高度なプロセスを実行します: 調査プランの作成: 質問に対する最適なアプローチや検証すべき仮説を立案します。 反復実行: 複数のSQLクエリを実行し、その結果(中間データ)を分析して、必要に応じて次のクエリを調整するループ処理を行います。 包括的なレポート: 最終的に、調査結果のサマリー、根拠となるデータ、可視化グラフ、そして具体的な推奨事項を含む詳細なレポートを生成します。 今回の「Jobのリソースを最適化するにはどうすればよいか?」という問いは、単一のクエリで解決する問題ではなく、「CPUの使用状況はどうだったか?」「メモリに余裕はあるか?」「ボトルネックはどこか?」といった複数の観点での深掘りが必要となるため、Research Agentの強みが活かせます。 Research Agentを利用する 今回、以下のテーブルを登録したGenie Spaceを利用します。 system.lakeflow.jobs Jobの実行履歴 system.billing.usage 課金リソースの使用状況 system.billing.prices SKU価格 system.compute.node_timeline ノードレベルのリソースメトリクス 実際に以下の質問を投げてみます。 〇〇Jobについて、コンピュートリソースを最適化したいです 現在のリソース使用量からドライバーノードとワーカーノードそれぞれに最適なインスタンスタイプを教えて下さい また、ストレージが付属しないインスタンスタイプの場合、最適なEBSボリュームの容量も教えて下さい するとResearch Agentは以下のように、複数のクエリを実行し探索的なデータ分析を行います。 内容をみると、まずはJobの実行IDを特定し、次にその実行におけるクラスターIDを取得、そして node_timeline からCPU/メモリ使用率を集計する、といった多段階の推論が行われていることがわかります。 最終的に以下のような詳細なレポートと提案が出力されました。 レポートによると、対象Jobはメモリ使用率が平均25%程度と低く、CPUリソースも余裕があることが判明しました。 この結果に基づき、Research Agentからは以下のような具体的な構成変更が推奨されました: インスタンスタイプの変更: メモリ最適化インスタンス( r5d.large など)から、より安価な汎用インスタンス( m5d.large )への変更 ワーカー数の削減: オートスケーリングの最大数を8台から4台へ縮小 特筆すべきは、単に「使用率が低い」という指摘にとどまらず、実際のAWSインスタンスタイプ名を挙げて具体的な代替案を提示してくれる点です。 まとめ system.compute.node_timeline の登場により、Databricks上のコンピュートリソースの実際の利用状況が詳細に可視化されるようになりました。 さらに、Genie Research AgentのようなAIエージェントを活用することで、膨大なメトリクスデータの中から「どこを改善すべきか」というインサイトを自動で抽出し、具体的なアクションプランにまで落とし込むことが可能になります。 これまでUIを確認する必要があったリソース調整を、効率的に最適化できるこの手法は、コスト削減と運用効率化の両面で非常に有用であると感じました。
アバター
この記事は every Tech Blog Advent Calendar 2025 の 20 日目の記事です。 はじめに こんにちは、リテールハブ開発部の杉森です。 小売向けサービスのインフラ基盤を管理している中で、マルチテナント対応を行うことになりました。 本記事では、既存のTerraformコードをマルチテナント対応させた際の取り組みと、意識したポイントについて紹介します。 実施概要 変更前の構成 既存のTerraformの構成は以下のような形になっていました。 この構成では、新しいテナントを追加する際に variable.tf から全てのファイルをテナントごとに複製して作成する必要がありました。 各テナントごとに共通化している部分と独立しているリソースがあり、独立したリソースの追加や共通化しているリソースの変更が困難な状態でした。 変更後の構成 以下のような構成に変更しました。 この変更により、 variables.tf にテナント情報を追記して terraform apply するだけでテナントの追加ができるようになりました。 以下は簡略化したコード例です。 # variables.tf variable "tenants" { default = { tenantA = { ... } tenantB = { ... } } } # main.tf module "s3" { ... tenants = var.tenants } # s3.tf resource "aws_s3_bucket" "tenant_media" { for_each = var.tenants ... } 意識したこと 大幅なTerraformのコード修正をする上で、意識したことを紹介します。 ※マルチテナント対応する上で、各リソースをどのように分離と共通化をしたかという設計部分の内容は記載しておりません。 1. AIエージェントで徹底的に疑問を解消する 入社して間もなかったため、既存のインフラ構成に詳しくありませんでした。また、AWS自体は触ったことがありましたが、Terraformは未経験でした。 そこで「既存のインフラ構成について」と「Terraformについて」の2つについて、気になることがなくなるまでClaude Codeに壁打ちをしました。例えば「この設定は何のためにあるのか」「この書き方はTerraformとして適切か」といった疑問を一つずつ解消していきました。 十分な知識がない状態でいきなりコードを書き始めると、表面的な動作確認だけで終わってしまいがちです。土台や前提を理解することで、自分が書いたコードに責任を持てるようになると考えています。 2. 不必要にリファクタリングをしない コード全体に影響する対応をしていると、「ここも直したい」という改善点が次々と見えてきます。しかし、逐一対応してしまうと本来のゴールから外れ、タスクの完了が後ろ倒しになってしまいます。 そこで今回は、マルチテナント対応の障壁にならないリファクタは実施せず、Asana上にチケットとして起票して後回しにしました。 「ついでに直す」は一見効率的に見えますが、スコープが曖昧になりやすいです。裁量のあるエンジニアであればあるほど曖昧になりやすいため、目の前のタスクに集中し、改善点は別途管理する意識が大切だと考えています。 3. AWS側のリソース変更を最小限にする 今回の対応ではすでに本番稼働しているテナントAと新規で追加するテナントBが存在します。 そのため、可能な限りテナントAのリソース変更やダウンタイムは最小限になるようにしました。 その施策の一つに、 moved ブロックの活用があります。 terraformはデフォルトの挙動として、リソース自身の構成が同一の内容であったとしても、アドレスが変更された場合、破棄と作成が実施されます。 リリースをした際に適切に moved ブロックを実施し、破棄が発生しないようにしました。 また、将来的に移行した内容をgithub上で追跡できるように、 state mv コマンドではなく、 moved ブロックを利用しました。 以下は moved ブロックの例です。 # リソースアドレスの変更を追跡 moved { from = aws_s3_bucket.tenant_media to = aws_s3_bucket.tenant_media [ "tenantA" ] } 参考リンク https://developer.hashicorp.com/terraform/language/modules/develop/refactoring 4. AIコードレビューを活用する 今回の対応では、CodeRabbitとGitHub Copilotを活用してコードレビューを実施しました。 大規模なコード変更では、人間のレビュアーだけでは見落としが発生しやすくなります。AIコードレビューを併用することで、以下のようなメリットがありました。 変更漏れの検出: 大量のファイル変更の中から、意図しない変更や修正漏れを指摘してもらえた ベストプラクティスの提案: Terraformの書き方について、より良い記述方法を提案してもらえた AIによるコードレビューを効果的に活用するためには、些細な指摘や精度の低い指摘であっても無視せず、一つ一つの内容を精査し、意図を汲み取ろうとすうる姿勢が大切だと感じました。 5. 最終確認をしっかりする 大規模な変更では、PRレビューだけでは全体の整合性を見落としやすくなります。特にインフラの変更は、Terraformのコードだけでなく、バックエンドやフロントエンドのコード、リリース手順書など、関連する様々な領域に影響を及ぼすことがあります。 そこでリリース前に、チームメンバー全員で変更内容を横断的に確認する会を設けました。実際に考慮漏れを発見でき、各メンバーの理解も深まったため、時間をかけた価値がありました。 最後に 今回の対応により、新規テナントの追加が variables.tf への追記と terraform apply だけで完結するようになりました。 まだまだ改善の余地がありますが、予定通りリリースでき、運用負荷の軽減を実現できました。 今後もより良い構成を目指して改善を続けていきます。
アバター
はじめに こんにちは。株式会社エブリーの開発1部の村上です。 この記事は every Tech Blog Advent Calendar 2025 の 19日目の記事です。 弊社では各チームでアラートやインシデントの対応をしており、発生から調査までを各エンジニアが自ら行なっています。その調査自体はナレッジが溜まりつつあるものの、この時代であればよりAIを活用して、迅速な調査やサービス復旧ができないのかという疑問は常にありました。 そんな中で、Datadogから 最近GAされたBits AI SRE という機能が気になったので他の機能のトライアル期間中に検証した内容を共有します。 Bits AI SREとは Bits AI SREは、Datadogが2025年12月2日にGAした、システム障害発生時に自律的にアラートを調査し、数分以内に根本原因を特定するAIエージェントです。 特徴 迅速な根本原因特定 明確な根本原因を数分以内に特定・提示 平均修復時間(MTTR)を最大90%短縮するとも言っている 経験豊富なSREレベルの推論能力 複数の仮説を並行して検証 システム全体の膨大なシグナルを数秒で分析 Datadogの広範なデータセットと数千の実際のインシデントを基に学習 対話型のインシデント対応 チャット形式で質問に回答 調査結果を明確に説明し、推奨アクションを提示 期待される効果 従来のインシデント対応では、エンジニアが深夜に大量のログやメトリクスを手動で調査する必要がありましたが、Bits AI SREの導入により以下のことが期待できます。 エンジニアが創造的な活動(顧客価値創出、システム信頼性向上)に集中可能 迅速かつ自信を持ったインシデント解決 オンコール負荷の大幅軽減 使い方 基本的にBits AI SREはmonitor設定のアラートからトリガーできるようになっており、monitor詳細画面から「Investigate With Bits AI SRE」のボタンで該当のアラートを選択することでチャット画面に移り、自動で調査を開始します。 Investigate With Bits AI SREボタンでのメニュー 基本的には何も指示を出さなくてもこれまでの調査履歴や学習したデータをもとに自動で調査してくれますが、対話形式でアラート調査を依頼したり、回答に対して質問を行うこともできます。Bits AI SREの特徴的な機能として、調査の思考過程が完全に透明化されています。チャット画面は以下のような構成になっています 左側: チャット欄 ユーザーとの対話形式でのやり取り 日本語での指示や質問が可能 調査結果の要約と推奨アクションを表示 右側: 思考過程パネル 調査フローの可視化: AIが実行している各ステップがリアルタイムで表示 仮説検証プロセス: 複数の仮説を立て、それぞれを検証していく過程を表示 データソースアクセス: どのメトリクス、ログ、トレースにアクセスしているかを明示 判断根拠の詳細: なぜその結論に至ったかの論理的根拠を段階的に表示 信頼度の表示: 各結論の確信度やさらなる調査の必要性を提示 思考過程パネルは日本語のネイティブサポートにまだなっていないため、思考過程や調査詳細を閲覧する際は英語での表示となります。ただ、技術的な内容が多いため、エンジニアであれば十分理解可能なレベルです。 Bits AI SREのチャット画面 料金形態 公式の 料金ページ には年間契約の場合は1ヶ月20調査あたり625ドルになっていました。これを高いとみるかどうかはプロダクトやチーム状況によっても変わってくると思いますが、調査単位での従量課金なので何でも見てもらうというよりは何を調査してもらうかは人間がある程度判断しないとコストが増えていきそうに思います。 調査シナリオを作って検証してみる では、実際に実務レベルで使うことができる機能でしょうか。 AWS上に構築した検証環境で、2つの典型的な問題シナリオを意図的に作り出し、Bits AI SREの調査能力を検証しました。 ① 攻撃による5xxエラー急増 検証設定 項目 内容 監視対象 ALBの5xxエラー急増アラート 連携データ ALBメトリクス、ALBアクセスログ 再現方法 複数IP・偽装UserAgentによる大量アクセス攻撃 Bits AI SREの調査フロー 「アクセス傾向を分析して、DDoS攻撃の可能性などを分析してください」と調査依頼を出したところ、以下のステップで分析から回答が生成されました。 Step 1: メトリクス分析 アラート期間のアクセス数、新規コネクション数、応答時間を自動分析 リアルタイムで調査内容を可視化し、詳細確認用のリンクも提供 ALBのメトリクス分析 Step 2: ログパターン分析 ALBログから取得可能フィールドを自動特定 ClientIP、User-Agentに着目したアクセスパターン解析 ALBログからパターン分析 Step 3: 調査結果 これらの調査から実際に以下のような一次回答を出してくれました。アウトプットとしては全体で1-2分ほどで事象をよく理解した回答がもらえました。 ALBへのアクセス傾向とDDoS攻撃の可能性を分析しました。 **アクセスパターン分析結果** **リクエスト量の推移:** - アラート発生時(13:29 UTC)前後、2,200〜4,300リクエスト/分で推移 - 13:39以降、リクエストが急激に停止(0リクエスト) - アクティブ接続数は480〜650接続で安定的に推移 **エラー傾向:** - 30分間で合計43,223リクエストのうち、**41,977件(97%)が5xxエラー** - 4xxエラーはわずか13件 - バックエンドレスポンスタイムは1〜5秒で、極端な遅延は見られない - ALBレベルでの拒否接続は0件 **トラフィック送信元の特徴:** 分析したエラーログから以下の特徴が判明: 1. **少数のIPからの集中アクセス** - `xx.xxx.xxx.xx`: 頻繁に出現(最多送信元) - `xx.xx.xxx.xx`: 2番目に頻繁 - `xx.xx.xxx.xx`: 散発的 2. **User-Agentパターン** - 多様なクローラー/ボット: BLEXBot, MJ12bot, Python requests, Java, Wget, PostmanRuntime等 - 正規ブラウザを模倣したUA: Chrome, Firefox, IE等 - **単一IPから複数の異なるUser-Agentを使用** → 典型的な攻撃パターン 3. **リクエストの特徴** - すべてのリクエストがルートパス `/` への GET - 全て同一ホスト宛 - バラエティに富んだポート番号(短時間での接続・切断を繰り返す) **DDoS攻撃の可能性: 高** **根拠:** - ✅ 少数IP(3〜5個程度)からの大量リクエスト - ✅ 単一IPから複数User-Agentを使い分け(偽装) - ✅ 同一パターンのリクエスト(GET / のみ) - ✅ 97%がバックエンド5xxエラー → バックエンドが過負荷で応答できない状態 - ✅ クローラーを装った自動化ツールの使用 **攻撃タイプ:** アプリケーション層DDoS(Layer 7攻撃)の可能性が高い。少数のIPから大量のHTTPリクエストを送信し、バックエンドリソースを枯渇させる手法。 **推奨対策:** 1. 疑わしいIPアドレス(`xx.xx.xxx.xx`, `xx.xx.xxx.xx`, `xx.xx.xxx.xx`)のブロック 2. AWS WAFでのレート制限ルール設定 3. User-Agent検証ルールの追加 4. ALBアクセスログの継続監視とAmazon GuardDutyの有効化 この攻撃により `targetgroup:targetgroup/xxxxxxxx/xxxxxxxx` のバックエンドが過負荷状態になっていると判断されます。 基本的には数値的根拠をベースに、攻撃元IPの特定、User-Agentパターンの分析まで、手動調査で行う項目を過不足なくカバーしています。 対策提案についても、疑わしいIPの具体的なブロックから予防策(AWS WAF、GuardDuty)まで実行可能な内容が提示されており、解決策の検討も一緒に行うことができます。さらにここから疑わしいIP一覧や疑わしいIPアドレスごとのUser-Agent使用パターンを依頼するとそれらを列挙する形で会話を進めることができました。 基本的にはチャットでの回答の方が理解しやすく、不足もないですが右側のパネルでも結論に至った背景を見ることができます。 ② データベース処理によるレイテンシー悪化 検証設定 項目 内容 監視対象 APIレスポンスタイムの悪化 連携データ ALBメトリクス、APMトレース 再現方法 DB接続数制限による接続枯渇状態での負荷テスト Bits AI SREの調査フロー 「レイテンシーが悪化している原因となっているエンドポイントの特定とボトルネックとなっている箇所の詳細分析をしてください」と調査依頼を出したところ、以下のステップで分析から回答が生成されました。 Step 1: エンドポイント特定 APMトレースからレイテンシーの高いエンドポイントを自動特定 /api/* への負荷を正確に検出 エンドポイントの特定 Step 2: ボトルネック分析 最もレイテンシーが高いトレースの詳細分析 SQLクエリやDB接続に関するエラーログを自動検索 ボトルネック分析 Step 3: 調査結果 実際に一次回答ではコネクションの枯渇や接続確立の遅延を問題視している回答となり、期待に近い回答になりました。こちらもレイテンシーの悪化を単なる「遅い」という表現ではなく、具体的な処理時間の内訳まで詳細に分析してくれています。 トレース分析の結果、レイテンシー悪化の原因とボトルネックが特定できました。 **問題のエンドポイント:** - `GET /api/v1/search` - 検索APIエンドポイント **レイテンシー状況:** - 最大レイテンシー: **129ms** (通常時13-16ms) - アラート時のp95レイテンシー: **84-102ms**に急増 - 通常時の約**6-8倍**のレイテンシー **ボトルネックの詳細分析:** 完全なトレース分析により、129msのリクエストにおける処理時間の内訳: 1. **データベース接続確立: 94.3ms** (73%) - `postgres.query` (Connect) - PostgreSQLへの接続処理 - RDSインスタンス: `bits-ai-sre-postgres.cmm4mneym0sd.ap-northeast-1.rds.amazonaws.com` 2. **メインクエリ実行: 18.4ms** (14%) - 複雑なCTE (WITH句) を使用した検索クエリ - ILIKE パターンマッチング、ROW_NUMBER() ウィンドウ関数、複数JOIN 3. **トランザクション開始: 13.5ms** (10%) - `Begin` トランザクション処理 4. **その他の処理: 2.9ms** (3%) - 集計クエリ、Commit処理 **根本原因:** **データベースコネクションプールの枯渇または接続確立の遅延**が主要因です。接続確立に94ms (全体の73%) かかっており、これは明らかに異常です。通常、コネクションプーリングが適切に機能していれば、接続は再利用されるため数ms以下になるはずです。 **推奨される対策:** 1. **コネクションプール設定の見直し** - 最大接続数を増加 - アイドルタイムアウトの調整 - 接続の事前ウォームアップ 2. **SQLクエリの最適化** - 複雑なCTE + ILIKE + ウィンドウ関数の見直し - インデックスの追加 (`name`, `description`, `category`列) - フルテキスト検索エンジン(Elasticsearch等)への移行検討 3. **RDSパフォーマンスの確認** - CPU/メモリ使用率 - ネットワークレイテンシー - スケールアップまたはリードレプリカの追加 今回はアプリケーションのログまで連携できていませんが、アプリケーションのログやSentryのエラーまでもDatadogに連携できればより精度の高い問題の分析を行うことができそうです。 検証結果の所感 応答速度の速さ 今回の検証を通じて、1-2分で深い分析と具体的な対策提案まで完了するBits AI SREの応答速度には驚かされました。従来の手動調査と比較して圧倒的な時間短縮です。 データ集約による精度向上の可能性 Datadogにログを集約していけばいくほど、より広いコンテキストを共有でき、分析精度が向上していく可能性を感じました。今回はALBログとAPMトレースを中心とした検証でしたが、より細かい情報まで漏らさずに連携できれば、さらに精密な問題分析が期待できそうです。 エンジニアの調査業務への影響 人間では数十分かかる調査や、複数のメトリクスを横断して初めてわかるような見つけにくい課題を数分で解決できる可能性が見えてきました。SREがいる組織ではSRE自体のパフォーマンスを向上させるサポーターとしての役割を果たし、インフラ経験が浅いエンジニアにとっても頼れるパートナーのような存在になりえると感じます。 コスト面の課題 一方で以下のコスト面は組織によって負担が大きく、導入効果とコストバランスの慎重な検討が必要そうです。 Datadogへの全ログ集約コスト AI利用コスト(月20調査で625ドル) ただし、AIコストは技術の発展でより安価に提供される可能性もあるでしょう。 終わりに 今回は、Datadogが新しく発表したBits AI SREについてお話ししました。 生成AIによるインパクトはすでにコーディング業務においてはかなり大きいですが、今後こうしたアラート調査などのSRE領域でも大きな変化が起こりそうです。エブリーでは今後もこうした変化を柔軟に取り入れながら、より自社プロダクトの品質担保やさらなるパフォーマンス向上に努めていきたいです。 エブリーでは一緒に働く仲間を募集中です! エンジニアブログをきっかけに少しでも興味も持っていただけたら、まずはカジュアルに面談しましょう!
アバター