TECH PLAY

株式会社マイナビ デジタルテクノロジー戦略本部

株式会社マイナビ デジタルテクノロジー戦略本部 の技術ブログ

224

このシリーズでは、当社のデジタルテクノロジー戦略本部(デジ戦)の職場環境やチームの雰囲気、日常のコミュニケーションを社員の視点で紹介します。大切にしている価値観やサポート体制、チームの多様性をお伝えし、働く日常を具体的にイメージしていただける内容です。 コンテンツプランニング統括部2部2課のT.Mです。 マイナビ転職のSEO記事の制作を担当しています。 今の部署に異動してから、もうすぐ1年が経ちます。 振り返ってみて、私が一番強く感じているのは 「とても安心して働けている」ということです。 今回は、その理由を日常のエピソードを交えながらお話ししたいと思います。 定例ミーティングに流れる、穏やかな空気 日々の業務の中で「雰囲気がいいな」と感じる瞬間は、いくつもありますが、 私が特にそれを実感するのが、週に一度の課の定例ミーティングです。 定例には、終始とても穏やかな空気が流れています。 ギスギスした感じは一切なく、仕事の話はもちろん、 ちょっとした雑談も自然に交わされる雰囲気です。 「会議だから身構える」というより、 「みんなで状況を共有する時間」という感覚に近いかもしれません。 失敗を責めない、共有を大切にする文化 定例では、全体数字や成果、各自の進捗状況だけでなく、 思い通りにいかなかったことや、想定外の結果に着地したことについても、 積極的に共有されています。 「良いことも、うまくいかなかったことも、きちんと共有しよう」 このスタンスが、課の共通認識として強く根付いていると感じています。 もちろん共有したからといって、責められることはありません。 結果だけでなく、そこに至るまでのプロセスや背景を大切にしてくれるからこそ、 失敗も隠さず話すことができ、次につながる前向きな議論が生まれているのだと思います。 上長・先輩との距離が近く、相談しやすい環境 私が所属している部署では上長との距離がとても近いです。 相談するとすぐにレスポンスをいただけて、 アドバイスもとても具体的。 アドバイスを踏まえて自分なりに検討し、実行してみた結果、数字がしっかり改善した。 そんな経験を、これまで何度もしてきました。 先輩方も同じで、質問すると気さくに、そして丁寧に答えてくれます。 「怖くない」「一人で抱え込まなくていい」と思えることは、 日々働くうえで、想像以上に大きな安心材料になっています。 提案を「まず受け止める」姿勢 定例は、進捗を共有するだけでなく、 自分なりの考えやアイデアを提案する場でもあります。 どんな内容であっても、 提案を最初から「難しいから無理」と切り捨てられることはありません。 まずはきちんと聞いてもらえる。 だからこそ、「せっかくだから発表してみよう」と、 自然と積極的にプレゼンできるようになります。 この「まず受け止める」空気は、 この課だけでなく、デジ戦全体に共通している雰囲気だと感じています。 柔軟な働き方が、日々の安心感につながる デジ戦では、月の半分を在宅勤務にすることが可能です。 また、課内では時差出勤を活用しているメンバーもいます。 私自身も、将来通勤ラッシュが厳しい路線に引っ越した場合などは、 迷わず制度を使うと思います。 無理をせず、自分の生活に合わせて働き方を選べること。 それが、日々の安心感につながっていると感じています。 安心できるチームで、自分の考えを生かしたい人へ 自分なりの考えはあるけれど、 これまでトップダウンの環境でなかなか提案できなかった人。 そもそも、意見を出す機会がなかった人。 そんな人にとって、 人の意見を否定せず、前向きに受け止めてくれるデジ戦は、 とても相性のいい職場だと思います。 「安心できるチームで、自分の考えをちゃんと仕事に生かしたい」 そう思っている方には、ぜひ一度知ってほしい職場です。 採用情報について デジ戦では、現在一緒に働くメンバーを募集しています。 詳細な仕事内容や募集職種、働く環境については、 採用ページ をご確認ください。
アバター
このシリーズでは、当社のデジタルテクノロジー戦略本部(デジ戦)に所属する社員が、「なぜ当社を選んだのか」「入社して何を感じたのか」を率直にお届けします。入社前の期待や不安、入社後のギャップや魅力を通じて、働く環境を具体的にイメージしていただける内容です。 ■執筆者プロフィール職業:PdM,PjM社会人歴:7年目(※2026年現在)マイナビ歴:1年目(※2026年現在)所属組織:プロダクトマネジメント統括本部前職:フードデリバリープラットフォーム運営会社 はじめに PdMやPjM・デジタルサービスの企画・ディレクション職種の希望者向け マイナビへの転職を検討されている方に気になるだろうポイントを載せていきます 結論 デジ戦を選んでよかったなと思っている なんで転職しようとしたの? 仕事をするからには社会に良い影響を与えたい。 良い影響を与えるからには、より大きなインパクトを出したい。 そんな動機から、toC向けデジタルサービスの企画・ディレクション職からキャリアをスタートしました。 ステークホルダー(社内外)やデザイン・開発者等と協業して、成果物が世に出て、直接的に数値で結果を確認でき、 スキルが伸びればそのサイクルがどんどん大きくなる、領域が深まり広がっていく...という、 短期のやりがい、中長期の成長実感や明確なキャリアパス(企画→ディレクション→PJM→PdM)がなんだかゲーム感覚で、世の同職種の皆さんは程度の差はあれそういったポイントモチベに業務を楽しんでいるのかなと思います。 私としては、PdMとして残りの仕事人生どんなキャリアを描こうか迷っている時期で、 プライベートの転機、前職の転機など、さまざまなファクターが重なったこともあり、転職を考えた次第でした。 転職の軸は? メインテーマ 前職を継続して、より上位職(マネジメント等)や経営サイド等の別領域へのチャレンジなど考えましたが、 より大きいプロダクトを担当したい・できるようになりたい気持ちが大きな軸でした。 ここでいう大きなプロダクトとは、 より利用者が多く、利用シーンも多く、総合的にユーザーの人生に与える影響が大きいサービスを指します。 例えばGoogleで言えば、検索も音楽も動画も、端末や企業のクラウド、企業の各サービスの機能など様々シーンで活用され、多くの方に影響しています(Amazonとか色々ありますよね。日本だと楽天、LINEヤフー、リクルートなど様々)(いわゆるスーパーアプリ)。 こういったサービスの特徴としては、1個のプロダクトにとどまらず、シナジーのある他プロダクトも続々立ち上げて、連携、総合的で連続したサービスをユーザーに提供しています。 一個のプロダクトを磨きこむことも当然大事ですが、頭打ちは必ずあります。(最終的にボタンの大きさとか色味のABテストくらいしかやることなくなるな...とかは常々思っていた) より大きな価値を生み出すためのプロダクト間連携、シナジーの創出は今後のトレンドになるだろうな、そういったシナジー創出の経験は一PdMとしての将来的な生き残り戦略として大事だなという目論見と、単純に複雑で面白そう+よりインパクト出したい自分の仕事軸とも合うため、志向するポイントでした。 サブテーマ もう一個、こちらはサブ的なテーマですが、 AIの活用がトレンドの今、業務内にとどまらず、AIを活用した新規プロダクト・既存プロダクトの機能の改善を志向し、実践している企業に飛び込んで、知見を吸収、あわよくば実践・経験を詰めることも志向ポイントでした。 色々な職業のAIによる代替可能性が示唆、文字通り日進月歩で変わるAIトレンドなどなど、日々目の前の業務に取り組んでいる中で、AIに触ったり・業務・サービス活用を考える時間は前職だと環境的にあまり取れませんでしたから、焦燥感は募るばかりでした。 AI活用の専門部署があって、昨今のAIトレンドを専属でキャッチアップし、ナレッジを組織横断でシェア、場合によっては業務で関係できるような企業は大きな魅力を感じました。 なんでデジ戦を選んだの? 大きなプロダクトの0→1を経験できること AI関連の専門部門がありナレッジが組織を跨いで共有・協業できること 仕事環境が良いこと 1.大きなプロダクトの0→1を経験できる プロダクト統括部では、toBのプロダクトをメインに大小さまざまな規模の案件を扱っています。 それらの特徴は、新規の立ち上げ・運営改善、そして各種マイナビサービスとの連携によるシナジー創出にあります。 例えば、担当しているマイナビ TalentBaseというサービスは、SmartHRに代表されるようなHR総合プラットフォームです。 企業の人材管理および、それに派生した教育研修、採用等に派生し、各種マイナビサービスとの連携創出を目下の目標としています。 (その他にも、将来的なマイナビサービスの非連続的成長の糧として、先進的な事例の研究開発・PoCを実施、結果如何で新規実装などの、ボトムアップ型の0→1案件なども担当しています) 就職や転職、アルバイトや医療福祉、採用管理、教育研修など、マイナビグループは事業の軸をなす複数のプロダクトがあり、それらの非連続的な成長のため、グループ間のシナジー創出は中長期計画の柱の一つです。 多様なステークホルダーと協業して、複雑かつ影響の大きな案件を担当できることは、大きな魅力でした。 2.AI関連の専門部門がありナレッジが組織を跨いで共有・協業できる AI戦略室という部門があり、日夜AIのトレンドを追いつつ、各種マイナビサービスに取り込んだり、業務に取り入れたりなど実践している部門です。 同じデジ戦内の部門であり、距離感も近く、ナレッジを得る機会、そもそもナレッジが蓄積される環境でもあって、非常に貴重です。 そもそも、全社的にAIツールの導入、利活用の推進、ガバナンス・運用ルールの策定浸透と、かなりの熱量で取り組んでいることも大きなポイント。 3.仕事環境が良い 現実味な話ですが、仕事環境も外せない要素ですね。 定時は7時間30分なので、8時間の企業と比べると毎日30分短いことはとんでもないことです。 給与水準は同職種・職能帯でも高水準にあたると思いました。 リモートワークもあり、かつ取得も容易。在宅比率50%の目安こそあれ、子育て世代の突発的な事由などにおいては柔軟に調整可能なため、運用ルールの見た目の堅さに対して実態は非常に優しいです。 とはいえ、職場環境は非常に充実している(デュアルモニター、モニターアーム、個人ブース多数でオンライン会議容易)ので、むしろ業務効率向上のため意欲的に出社できる下地は整っています。 福利厚生が前職・前々職比較ですが、非常に多く、色々活用させてもらっています。 大規模ECプラットフォームを運営する他社とも、最後まで迷いましたが、最終的にデジ戦を選びました。 入社前の不安は?(典型的なJTCで、システムはレガシーだよと又聞きしていて不安だった) 入社を決めてから、前職の上長に言われたこと 「とはいえ、マイナビは結構レガシーだよ(上長のかつての部下がマイナビ出身だったそう)」の言葉はかなり響いて、最後まで悩んだポイントでした。 国内大手のチャットサービス企業での経験を持つ上長の助言もあり、不安に感じる点はありました。 あなた程の人が言うなら...と 仕事の進行等で前職とは比にならない開発面、企画面でのハードルがあり、大変なのかな... ちゃんと能力を発揮したり、キャリアを積んでいけるだろうか...と不安でした。 とはいえ、ビジネスサイドに寄って、経営レイヤーの能力を強化していきたい思惑もあり、 上述の仕事内容や環境面から、マイナビに飛び込むことを決意。 結果、 実際そうだけれども、全社的に課題と捉え、解決に向けて絶賛稼働している過程だったとわかりました。 今この瞬間、名だたるテックカンパニーのような超モダンな環境、とは言えないですが、 経営層レベルで問題意識をもち、現場レベルで解決に向かい実行段階にあることは、不安を抱えてジョインした一中途入社人間として非常に安心できる材料です。 システム 一例として、システム面について。 今までは一個のプロダクトをそれぞれ担当の部門が磨きこむという経営方針の都合、 プロダクト数がとても多く、かつマスタがプロダクトごとに独立しているなど、あるあるな特徴があります。 また、開発におけるベンダー依存の構造(今までの経営合理性に基づく話なので、課題というよりか前提)など。 これについては、エンジニアブログの記事等がより参考となるのでざっくり申し上げると、 それを解消し、よりマイナビサービスの成長に貢献することがデジ戦の存在理由です。 現在進行形で様々な取り組みがラインナップされ、着実に強力に実行されているところです。 組織風土 また、組織風土的な面ですが、 社員1万人をかかえる大企業特有の特徴かなと思い、 むしろ中途入社社員においては、他企業での経験を求められるシーンかなと思ってます。 例えばステークホルダーが単純に多いです。 これについては、新卒入社から長年貢献して在籍されている社員の比率が多いため、 ある事象の確認・承認については誰に決裁・浸透を図るべきかサポートいただけます。 さらに、マイナビでの経験が長く、他企業のやり方を知らない社員が多いということは、 旧来のやり方の踏襲が多いということで、 そういったシーンに中途入社社員の経験を絡めてアップデートしていくような、 まさに組織の変化を求められているなと感じることが多いです。 なので、体制や組織文化的にも既存社員と中途入社社員で二人三脚で頑張っていこう! リスペクトしています!な空気を肌身に感じます。 統括 マイナビは全社・組織レベルで、ポジティブな変化を望み、実際に変化していると感じます。 停滞感や固執といった空気はなく、唾棄されます。 むしろ、レガシーをモダナイズする過程にノーリスク(ボトムアップで個人のリソース頼りではないという意味)でジョインできる環境は、むしろ一社員として大きな糧になるのではと皮算用しちゃうくらいです。 マイナビはそういった、意欲旺盛でチャレンジングなあなたのエントリーを心待ちにしています。 是非、一緒に働きましょう! (他にも気になる点、深堀りしたい点ありましたら、人事の方経由で質問いただいても構いません。気軽にアプローチしてくださいね!) おまけ - 入社しておどろいたこと 社内コミュニケーションがかなり豊富です。 社内報、teamsのエンゲージ機能、懇親会、組織・組織横断の勉強会など。 懇親会が四半期レベルで1回以上あり、2個上のレイヤー層との接点も豊富です。 1万人ほどの社員がいますが、さまざまなチャンネルで組織を超えた交流の機会があるので、様々な知見を吸収できる風通しの良さはすごく驚きました。 採用情報について デジ戦では、現在一緒に働くメンバーを募集しています。 詳細な仕事内容や募集職種、働く環境については、 採用ページ をご確認ください。
アバター
このシリーズでは、当社のデジタルテクノロジー戦略本部(デジ戦)の社員が、業務やプロジェクトを通じて感じている「仕事の価値」と「社会的意義」を紹介します。顧客や社会への貢献実感、手応えを覚えた瞬間を取り上げ、働く意味をより具体的にお伝えします。 はじめに 筆者がこのブログを執筆しているのは1月下旬。 ”そんなに雪が降らない方”の岐阜にある実家の両親からは、どこか自慢げに「雪降ったわ」とLINEメッセージを頂く季節となりました。 私はマイナビにデータサイエンティストとして新卒入社し、雪解けの頃にはデジ戦在籍歴が丸4年になる人間です。 「私がデジ戦で見つけたやりがい」というテーマに併せて、やりがいって?生み出す価値って?に対する現時点での考えを共有できればと思います。 筆者プロフィール 所属:デジタルテクノロジー戦略本部 AI戦略室 AIソリューション部 入社時期:2022年4月 新卒入社 職種:データサイエンティスト 業務内容: 主にマーケティング領域のAI・データ活用プロジェクト推進・実装を担当。 その他、社内データ利用ユーザ向けの技術支援・生成AI関連のガバナンス整備に関する業務も行った経験がある。 その他特記事項: 一般社団法人データサイエンティスト協会 スキル定義委員会に所属。 社外イベント・大学講義での登壇頻度が高く、過去は四半期に1回ほど。 価値の定義 「私がデジ戦で見つけたやりがい」シリーズでは、デジ戦メンバーが「自分の仕事が社会や顧客にどんな価値を生み出していると感じるか」をアウトプットしています。 どんな価値、の前に業務上の価値は何なのか?について私の考えを共有します。 よく「IT職は成果が売上に直結しないから業績評価が難しい・評価されない」と言われると思います。これは営業職などの目標が定量的に示しやすい職種との比較で語られる話ですが、売上が何によって生まれているかについて考えると、価値に近づく気がします。 営業職は顧客の「購入判断」によって「売上」が生まれる ↓抽象化 業務は対象者の「ビジネス上ポジティブなアクション」を引き出せたら「価値」になる 営業職は、顧客の心を動かして「買う」という行動を引き出し、その対価として売上を得ていると考えられます。 そう考えれば、我々の仕事も全く同じです。 「すごいモデルを作った」「分析レポートを出した」こと自体が価値なのではありません。それを見た社内の誰かが「計画を変更しよう」「この施策を始めよう」と意思決定し、具体的なアクションを起こした瞬間にこそ、価値が生まれるのではないかと考えます。 つまり、「対象者のポジティブなアクションを引き出すこと」。 これこそが、職種を問わず共通するビジネスの価値であると定義できると私は考えます。 「イメージのしやすさ」 では、どうすれば相手のアクションを引き出せるのでしょうか? 最近、そのヒントになる出来事がありました。冒頭でも少し触れた「雪」の話です。 ちなみに導入の小噺を読んだ皆さんは、「雪降ったわ」がどれくらいの降雪量だったと想像しましたか? そんなに降らない方なら、5㎝? そんなにとは言っても、白川郷はめっちゃ(3mとか)積もるから1m? このテキストでは降雪量を具体的にイメージすることは難しいように感じます。 一方、私が最近見かけたJR東日本の運転計画の案内を見ていただきたいです。 出典:『JR東日本なるほどQ&A Guide 自然災害(雪)』 https://www.jreast.co.jp/saferelief/operationguide/pdf/snow.pdf 降雪はみられるがすぐ溶ける程度・地面にうっすら積もる程度といった「程度」の表現 2つのケースを示す「パターン」の提示 イラストで雪による地面の透過度を見せる「可視化」 があることによって、私たちは瞬時に降雪の状態をイメージすることができます。 イメージができると、リモートワークへの切り替え判断などのアクションも取ることができます。 この話と同じく、マイナビのデータサイエンティストとしては、 社内ユーザの「状況をイメージできる状態」を引き出せたら「価値」になる という場面が多いように私は感じています。ユーザが担当しているサービスの状態や施策の効果、直近の未来といった見えていない状況を晴らすことが役割のひとつであり、それができたら価値になるというのが、私の現状の回答です。 前提整理が長くなりましたが、マイナビDSの価値をイメージできたでしょうか? ここからは私の業務を紹介し、価値を求める上での意識とやりがいを共有できればと思います。 業務紹介 この章では 太字 はすべて「イメージ→価値」に繋がる要素になるように記載しています。 MMMのモデル構築による広告運用最適化 Web広告やTVCMなど、多岐にわたる広告施策の中で「本当に効いている広告はどれか?」というのは、意外と見えにくいものです。 そこで私は、MMM(マーケティング・ミックス・モデリング)を用いて、 広告効果 の推定を行いました。 具体的には、メディアごとの 貢献度 を数値で可視化し、さらに「来月、予算配分をこう変えればKPIはこれくらい伸びる」というシミュレーション提示を行いました。 ※このプロジェクトに関して登壇した過去のイベントレポートは こちら 結果として、マーケティング担当者が「なんとなく」ではなく「根拠を持って」予算配分を変更するという、アクションを引き出す足がかりにはなったのではと思います。 社内生成AI利用ガイドラインの更新 生成AIは強力なツールですが、セキュリティリスクや社会的責任があるため、現場は利用に二の足を踏みがちでした。私は、ガイドラインの策定を通じて「どんなケースなら利用していいのか」「どのツールなら業務利用OKか」という 境界線 を明確に定義しました。 ※この業務に関して振り返ったブログは こちら 「この条件なら利用可能」という 基準 をクリアにしたことで、社員が迷わず安全に生成AIを活用できる環境(アクションできる状態)を整えることができました。 日々のデータ活用に関する問い合わせ 社内からは日々、「データを使ってこんなことがしたい」という相談が寄せられます。 (過去を遡ると100回以上相談会を実施したと思います。) しかし、初期段階ではアイデアが抽象的で、実現可能かどうかも分からないことがほとんどです。 これに対し、 そのデータは取得できていないから、まずは計測から始めましょう その目的であれば、AIではなく集計で十分です といったアドバイスを行い、 実現への道筋 を具体化します。 相談者の頭の中にあったイメージを「 実行可能な計画 」へと解像度を高めることで、プロジェクトの着手や、あるいは早期の撤退判断といった次の一歩を後押ししています。 まとめ ここまで、私の考える「価値」と具体的な業務についてお話ししてきました。 ビジネスの世界は、まるで雪の日のように視界が悪く不確実なことばかりです。 (上手いこと言った風) 「本当にこれでいいのか」と誰もが判断に迷い、立ち止まりそうになる瞬間があります。 私たちデータサイエンティストの仕事は、データによって「今の状況」や「進むべき道」を、誰もがイメージできる形にして届けることではないかと思います。 そしてイメージを受け取った相手のアクションが見えたとき、それは価値となり、成果であり、やりがいに繋がると思います。 ここまでの話が、マイナビDSのキャリア”イメージ”に繋がれば幸いです。 採用情報について デジ戦では、現在一緒に働くメンバーを募集しています。 詳細な仕事内容や募集職種、働く環境については、 採用ページ をご確認ください。
アバター
このシリーズでは、当社のデジタルテクノロジー戦略本部(通称:デジ戦)で働く社員が、どのようにスキルを伸ばし、キャリアの幅を広げてきたのかを紹介します。キャリアチェンジの背景や挑戦のプロセス、日々の学びを通じて、デジ戦で描けるキャリアの可能性をお伝えします。 「スキルアップをしよう」 近年よく耳にする言葉ですよね。 VUCAの時代と呼ばれる変化の激しい現代において、ますます重要視されるようになっています。 これを読んでくださっている方の中には、スキルアップのためにデジ戦への転職を考えている方もいるかもしれません。 でも、よく考えるとスキルアップって結局なんのためにするんだったっけ?とふと思います。 業務を効率化するため。仕事の幅を広げるため。キャリアの選択肢を増やすため…… 理由は人それぞれですが、私にとっては 「自分の世界が広がっていく面白さを感じるため」 なのではないかと思い始めています。 今回は、私がそう思えるようになったきっかけとなった、あるプロジェクトに挑戦したお話をしてみたいと思います。 この記事で分かること デジ戦にある「挑戦を歓迎する」風土 部門をまたいだプロジェクトで、どんな仕事に触れられるのか 未経験でも挑戦してみたら「意外とできた!」になる話 スキルアップを「義務」ではなく「世界が広がる体験」と捉えられるようになったプロセス プロジェクトに挑戦したきっかけ 私は通常業務でWEBディレクター職として、自分の担当サイトの応募数向上を目的に、メールマガジンやサイトのUI/UX改善に向けた施策の企画・設計および改善に携わっています。 毎日PCと向き合いながら自サイトと数値やデータを見比べながら、施策を練る日々。 そんな日々を過ごしながらも、私の頭の中にはじわじわと違和感が広がっていきました。 「このサイトを使うユーザーの顔を、私は見たことがない……」 数字だけ見て判断して良いのだろうか? 数字が上がったことを喜んで、それで本当にユーザーのためになっているのだろうか? ずっと前に作られたペルソナを頼りに、なんとなく施策を進めてしまっていないだろうか……。 そんなことをぐるぐる考えていた頃、CXマーケティング統括本部の全体会議で「ユーザーリサーチを行うプロジェクトを立ち上げるのでメンバーを募集します」という案内を聞きました。 気が付いた時には、 勢いで手を上げていました 。 プロジェクト概要 このプロジェクトは、「 インタビューやアンケートを通したユーザーの生の声を聞き、ユーザーが本当に求めているものを探ることで、サービス改善につなげること 」を目的として発足しました。 最終的にはデジ戦全体にリサーチの風土を根づかせることもゴールとしています。 メンバーは課長1名+一般メンバー2〜3名の少人数。 全員が自ら手を挙げて参加し、約1年活動しました。 実施内容(一部抜粋) ■インタビュー 設問設計 リクルーティング オンラインインタビュー:27名 オフラインインタビュー:1名 結果の社内共有 ■アンケート 設問設計 アンケート:3回 結果分析・共有 ■KSF策定・分析 3C / PEST / 5Forces KSFの策定 ■風土醸成 勉強会・ワークショップ開催 事例共有(社内記事) インタビュー伴走支援 ※上記はチーム全体での実績になります 手探りで進むプロジェクト 私を含め、メンバー全員がリサーチは未経験。 それでも、誰かがやり方を教えてくれるわけではなく、 自分たちで調べて、やってみて、改善する という進め方でした。 活動内容は多岐にわたり、3〜4人という体制でそのすべてに関われるのは大きなやりがいでした。 勘違いから始まるユーザーインタビュー この活動範囲の中でも特に自分にとっての大きな挑戦となったのが、ユーザーインタビューでした。 実は、プロジェクトに立候補した時点では、このプロジェクトをアンケート調査だけをやるものだと勘違いしていました。 プロジェクト開始後に「どうやらインタビューもやるらしい。むしろインタビューがメインらしい……」と気がついたのです。 もともと人見知りで話すことに自信がない私は、場をリードする必要のあるインタビューに戦々恐々。 なぜプロジェクトに自薦したんだろうと後悔した時期もありました。 しかしながら自薦した手前、怖気づいてやらない訳にもいきません。 社内で練習相手を探し、Youtubeで学び、原稿をつくり、緊張で震えながら第1回目のインタビューに臨みました。 未知の自分との遭遇 初回は緊張で言葉がつっかえながらも、なんとか乗り切った…という感じで、自分がインタビューしている動画を見返すのもかなり苦痛でした。 ですが、プロジェクトメンバーと毎回振り返りをし、学びを共有しあい、2回目、3回目と重ねるうちに不思議と慣れていきました。 そして次第に「 あれ?これ意外と自分に合っているのかも……? 」と思い始めたのでした。 ユーザーインタビューを通して数字やデータでは分かりきらない生の声に触れられることや、相手の話を聞きながらユーザーが潜在的に何を望んでいるのか、今の自分たちのサービスに足りないものは何かを考えながら深掘りしていくプロセスは、非常に興味深いものでした。 そして、こうやってユーザーと向き合うことで、競合の後追いをするだけではないサービスを作っていくことができるのではないか、とわくわくしたのでした。 プロジェクトへの挑戦を経た学び ①苦手と思い込んで避けがちなものに、面白さがあるかもしれない 私は、仕事において新しいことに挑戦してみたいという欲がある一方、これまでは自分ができそうだと事前に確信できる範囲のことに挑戦してきたように思います。 ですが今回は、「ユーザーインタビュー」という、本来なら自分の苦手意識から避けていそうな領域に、思いがけず飛び込むことになりました。 それが、結果的にたまたま新しく面白いと思えるものに出会えることになったのです。 「自分はこれが得意」「自分はこれが苦手」と思い込んでいるだけなのかもしれない。 思い込みで、まだ見ぬ面白いものに出会えないのは勿体ない 、と思えるようになりました。 ②挑戦は一人でやらなくてもいい このプロジェクトを一人でできたかというと、やはり難しかったと思います。 ユーザーインタビュー以外にも、アンケートや社内ワークショップなど全てが初めてのことでしたし、何よりユーザーインタビューがあまりにも自分にとって心理的にハードルに思えたからです。 その中でも、同じプロジェクトのメンバーと共に勉強し改善点を共有しあい進めていくことで、一人で挑戦しているわけではない、と思えました。 自分から学び実践する動きは大事ですが、でもすべてを一人でやろうと思う必要はない。 むしろ、個人個人の挑戦を軸に互いに共有しあうことで、チーム全体で助け合って進むことができる。 組織で働くということは、互いの得意分野を発揮しながら、一人ではできないことを協力しあって成し遂げていくこと だということに、改めて気が付く機会となりました。 ③キャリアは気になったことに手を伸ばしていたら自然と形になるもの 以前はこの先のキャリアを明確に決めなくてはならないのではないか、という気持ちがありました。 そして、何をやればいいか、何をやりたいか上手く決めきれない状態にもどかしさを感じていました。 しかし、今回、自分が苦手だと思っていたことに面白さを見出す、という経験をして、 今この先のキャリアを明確に決める必要などないのではないか と思い始めました。 「キャリアは、気になったことに手を伸ばしていたら自然と形になるもの」という感覚に変わったことで、これからも自分の「やってみたい」という気持ちを大事にしていければそれで良いような気がしています。 VUCA時代に、楽しみながら自分だけのキャリアを築く 「自分ひとりでスキルアップができるのか」 これは、私が中途入社して1週間ほどの頃、OneNoteに残した言葉です。 当時は、変化が激しい時代の中で自律的に学び続けなければならないという、静かな焦燥感のようなものがあったのかもしれません。 しかし今思うのは、スキルアップは必ずしも「自分ひとり」でやるものではないということ。 だから私はいま、「とにかく自分が面白くて楽しいと思える仕事をしていきたい」というシンプルな気持ちを軸にしています。 自分の興味を軸に楽しみながら仕事をすれば、いつの間にかスキルはついてくるし、これまでやったことのない業務や関わったことのない人と仕事ができることが、純粋に嬉しいです。 そうやって自分の世界が少しずつ広がっていくことを楽しんでいれば、いつの間にかその積み重ねが、自分にしかないキャリアになっていくと信じています。 最後に 私の例が、「デジ戦で挑戦してみたい」と思っている方の参考になれば嬉しいです。 あなたの世界が広がるきっかけが、デジ戦にあることを願っています。 お待ちしております! ■執筆者プロフィール職業:WEBディレクター社会人歴:5年目(※2026年現在)マイナビ歴:3年目(※2026年現在)所属組織:CXマーケティング統括本部前職:WEB制作会社 ■執筆者プロフィール 職業:WEBディレクター 社会人歴:5年目(※2026年現在) マイナビ歴:3年目(※2026年現在) 所属組織:CXマーケティング統括本部 前職:WEB制作会社 採用情報について デジ戦では、現在一緒に働くメンバーを募集しています。 詳細な仕事内容や募集職種、働く環境については、 採用ページ をご確認ください。
アバター
このシリーズでは、当社のデジタルテクノロジー戦略本部(デジ戦)の社員が、業務やプロジェクトを通じて感じている「仕事の価値」と「社会的意義」を紹介します。顧客や社会への貢献実感、手応えを覚えた瞬間を取り上げ、働く意味をより具体的にお伝えします。 本記事の対象者 マイナビのSE業務に興味がある方 新卒がどのような業務に取り組んできたのか興味がある方 もちろんそれ以外の方も 大・大・大歓迎 です!! 自己紹介 2023年4月に新卒入社したTです。今は社会人3年目です!(2026年1月時点) 現在は、スカウト系の転職サービスである 「マイナビスカウティング」の運用・保守 を担当しています! ♦ 執筆者による概要 この記事の執筆者は、入社後から現在までの担当業務を振り返りながら入社時の動機を果たすことが出来たのか、についてまとめています。 この記事を読むことで、株式会社マイナビにおける 「裁量を持った仕事への取組み」 や 「具体的な業務内容」 について知ることができ、マイナビでSEとして働くことの魅力をより深く知ることが出来ます。 入社時の動機 ・toC向けのサービスを持つ事業会社でシステム設計・企画に携わりたい ・裁量を持って働きたい 執筆者の業務 ・業務要件→システム要件の作成 ・セキュリティ対応 ・データ可視化 ・社内からの問い合わせ対応 ・ベンダーコントロール マイナビで働くことの魅力 ・裁量をもってto C向けサービスの改善に携われる ・自らが得た知識・経験をもとした提案が歓迎される風土がある ・知識を得るための補助(書籍購入など)もある 👇👇👇👇👇 記事を読んでさらに詳しく知る 👇👇👇👇👇 それではここから入社前~現在に至るまで時系列順に取り組んできたことなどを振り返っていきます。 入社する前 私は23卒の新卒として入社しました。 入社するからには、何かしら会社に入る動機がありますよね? 私が当時掲げていた入社動機は以下2点でした。 ① toC向けサービスにシステム設計・企画として携わることが出来ること ② 年次の若いうちから裁量を持って業務に取り組めること ① toC向けサービスにシステム設計・企画として携わることが出来ること 元々大学時代に、イベント企画などをしていたこともあり、 1人1人のユーザーが使いたくなるようなサービスに関わる業務がしたい という気持ちがありました。 そのため、toB向けよりもtoCのサービスに携わりたいな、と思って就活していました。 また、受託開発をするよりも自社サービスを持つ事業会社で 「 システム担当としてサービスを担当して育てていってみたい」 という気持ちがあったので、どちらも達成できるマイナビに入社を決めました。 (当時就活で使っていたマイナビ2023が使いやすいなぁ、と思ったのもあります) 私が入社したころはエンジニア職はSE職とプログラマー職がありました。 (データやデザイン職もありますがいったん省略) プログラミングの経験は入社前にもありましたが、個人的には細かいコードの最適化が好きでも得意でもなかったんです。。 そこで、もっと 上流側で範囲広くシステムを設計したり企画するような仕事に携わりたい と思い、SE職を希望していました。 ② 年次の若いうちから裁量を持って業務に取り組めること 私は、ただ言われたことをこなしていくよりも、 自身でどのようにしたらいいのかを自分ごととして考えて改善していく機会がある方が仕事にやりがいを感じる タイプでした そのため、ユーザー目線で考えた際に「もっとこうだったらいいのに...」と思ったことを、能動的に提案・実行できるような環境を求めて就活していました。 その時に、マイナビの就活生向けの記事を読んでみると、新卒数年目の先輩方が取り組んでいる業務内容が自身のやってみたいシステムの企画・提案業務だったんです...! 「この会社なら自分のやりたいことが出来るかも!?」と思って入社を決めました。 果たして「この2つの動機(期待)が現在達成できているのか」については、後ほど触れさせていただこうと思います! ちなみに、入社前の不安は私はほとんどありませんでした。 (プログラミング経験の無い同期もいましたが、新卒研修で教えてくれるので心配はしなくて良いと思います!) 現在の業務について ここからは、私が新卒入社してから現在に至るまでの業務を振り返っていこうと思います。 1年目 ~ システムへの理解を深める ~ 新卒研修後の2023年8月に、転職スカウトサービスである「マイナビスカウティング」の担当として配属させていただきました。 研修中にデジタル部門本部長と面談する機会があり、その中で 「toC向けのシステム設計や企画に携わりたい」という意志を反映 して頂けたのかな...と思っています さて、配属後早々にしてですが、 「マイナビスカウティング」のリニューアル業務 に携わらせていただきました。 といっても、開発自体はほとんど終わっていて、受入テスト(マイナビ側の要件が満たされているかの確認)をメインに担当していました。 受入テストでは、 UX部分の指摘が実際にサービスに指摘・提案内容が反映されていく体験 を早速しました。 特に印象に残っているのが、スマホ用画面の下部メニューサイズへの提案です。 (Androidの場合、iPhoneに比べて画面が大きく、メニューが小さいと操作がしづらいんですよね...) 所属している課ではシステム設計以外にもインフラ周り(AWS)の運用保守も担当しています。 (おそらく他の課と比べても業務範囲は広いと思います) そのためAWSの理解を深めるためにAWS SAAの資格取得もしました。 当時の期末評価においてこの資格取得が評価されたことで 「正当に頑張りが認められる会社なんだな」 と思ったのを覚えています。 2年目 ~ 頼るのではなく頼られる提案をする ~ リニューアル対応も一段落したので、今度は同サービスの 月次リリースにおけるシステム要件の作成 を担当していました。 (マイナビスカウティングは毎月リリースしています) 主に、営業サイドから上がって来る 「○○をしたい!」という要望を元に、どのような機能を実装するか を考えていました。 最終的にはExcelなどで資料の形に落とし込み、開発会社にシステム改修を依頼しています。 ただ、営業サイドの要望をただ鵜呑みすると、パフォーマンス低下などのシステム影響が起きたりします... そのため、 「本当に営業サイドの要望として求めていることは何か」 「システム機能と要望との着地点はどうするべきか」 「システム担当として追加で機能提案できることは無いか」 を考え提案することにやりがいをもって業務に取り組んでいました (部内でも「営業の御用聞きにはならないようにしよう」という話は度々上がっています) また、開発会社にも頼りっきりにならないように、javaやSQLのソースコードを自ら確認・理解することにも注力していました。 これにより、バグが起きても 調査を開発会社に依頼する前にバグの箇所を推測・改善案を提案 することが出来るようになったことで成長を実感していました。 3年目 ~ さらに広い範囲での提案と実装 ~ 3年目からサービスのメイン担当となりました。 サービスのメイン担当となったことで、 ベンダーコントロールといったマネジメント業務 にも携わっています。 スケジュール遅延や工数超過が起きないように、開発会社と密にコミュニケーションを取ったり、営業サイドとの要件のすり合わせにも積極的に関わることでマネジメント力も付いてきているかな、と思っています。 また、システム企画業務に加えて、 インフラ周りやデータ可視化の業務 についても携わるようになりました。 特に、印象的な業務はAWS WAFルールの最適化です。 サービスをDoS攻撃(大量アクセスによるサイバー攻撃)から防御するためにWAFルールの最適化をしました。 AWS SAAの資格取得で得た知識や1年目からの経験がシステムの実装へうまく活用出来た 例かな、と自負しています。 (この後、大量アクセスによるサーバダウンは起きていないです!) データ可視化の業務ではTableauというデータ可視化ツールを用いて、 未活用のまま埋もれているデータを活用したり、営業の方々が必要なタイミングでいつでもデータ活用できる環境を整備 しています。 なんと、これにより月次のデータ抽出業務が無くなりました! システムも営業もwin-winですね! 3年間を振り返ってみて... 改めて 「新卒で入ったころからサービスへの提案をしやすい環境だったなぁ」 としみじみと感じています。 ただ、これってどこの会社でもある環境でも無いんじゃないかな、とも思うんです (新卒なのに他の会社を語るのも変な話ですが...) これは、そのような業務に携わらせてもらえたこともありますし、 デジ戦(マイナビのシステム部門)が「提案や挑戦を積極的に受け入れる」 風土があるからじゃないかな、と... 提案をする→良い提案であればサービスに反映→また提案したくなる という環境があるからこそ 「こんな機能が必要じゃないか」 「システムでこの観点が必要じゃないか」 「このようなデータを連携した方がいいんじゃないか」 といった提案を能動的に発信することが出来た のかな、と思うんです。 もちろん新人の頃は「最初は分からないことだらけで提案とかできないよ!」と人もいますよね。 ただ、私の課では質問を拒否されたりしたことは無かった(周りでも質問して怒られている人は見ていない)ので、最初は徐々に仕事に慣れていきつつ提案とかしていけばいいんじゃないかなぁ、と思っています。 入社前の期待は満たされたのか? ① toC向けサービスにシステム設計・企画として携わることが出来ること → 満たせています! 2年目から現在に至るまで、 非常に広範囲(UX、セキュリティ対応、バックエンド処理、データ可視化....)にいたるシステム設計や企画に携わる ことが出来ています。 まだ足りない知識は無限にあるので、貪欲に知識・経験の取得に取り組んでいきたいです! ② 年次の若いうちから裁量を持って業務に取り組めること → 満たせています! 配属直後から「もっとサービスをよくするにはどのようにしたらいいのか」を考え提案 することが出来る環境にいさせてもらっています。 これからも積極的に業務に取り組んでいきますし、後輩が出来た際には取り組みやすい環境を作っていきたいと思います。 まとめ 「ユーザー視点に立ったシステム業務に裁量をもって取り組みたい」という自分の入社時の動機とガッチリ合った仕事 をさせてもらっています! もちろん私自身のマインドや知識によるものもあるかもしれないですが、 ・自ら提案することをよしとする風土 ・幅広い業務範囲と個人の裁量の大きさ ・自主学習しやすい環境 など、環境面としての影響も大きかったなぁ...と改めて振り返って気付きました笑 (勉強する際には書籍購入制度を利用したり、デジ戦社員用のUdemyを活用しています!) ここまで読んでくれたあなたへ 読んでくれてありがとうございました! マイナビは 「ただ与えられた業務をこなす日々は嫌だ!」 「私ならもっといいサービスにできるのに!」 「自分で課題を見つけて改善していきたい!」 という方にはぴったりな会社だと思います。 それは、事業会社として自社サービスを持っているからということもありますが、 「提案や挑戦を受け入れる」という風土 があるからだと思います。 自ら担当しているサービスに課題感を持って自分ごととして改善案を考えていくことで、より知識・経験が身についていきますし、マイナビにはその環境があります。 自分から積極的に取り組んだことが認められると仕事も楽しくなってくるし、そういう人が世の中に増えていけばいいなぁ、と思っています。 この記事がマイナビに応募・入社しようか迷っている方々の背中を押してあげるような記事になっていれば幸いです。
アバター
このシリーズでは、当社のデジタルテクノロジー戦略本部(通称:デジ戦)で働く社員が、どのようにスキルを伸ばし、キャリアの幅を広げてきたのかを紹介します。キャリアチェンジの背景や挑戦のプロセス、日々の学びを通じて、デジ戦で描けるキャリアの可能性をお伝えします。 はじめに エンジニアとして様々な業務を経験してみたい方 エンジニア以外の職種の経験をお持ちの方 向けになります。ぜひ参考になれば嬉しいです。 ──────────────────  筆者プロフィール ────────────────── 所属:ビジネスイノベーション統括本部 ITソリューション第4統括部 ディレクション1部 入社時期:2019年 新卒入社 職種:システムエンジニア 経歴: 営業として新卒入社。入社半年後からはキャリアアドバイザーとして学生の就職活動を支援。 2023年10月に社内公募を利用し、システムエンジニアへ異動。 理系学生の専門性に触れ、痛感した「手に職」の重要性 私のキャリアのスタートは、エンジニアとは少し離れた場所にありました。 以前は「理系学生担当のキャリアアドバイザー」として、就職活動を行う学生さんの支援をしていました。 理系の学生さんは、自身の専攻や研究分野という確固たる「武器」を持っています。彼らのキャリアと向き合い、「どんな技術で社会に貢献したいか」という熱い話を聞いているうちに、私自身もこう思うようになりました。 「彼らのように、私自身もこれからの時代を生き抜くための『手に職』をつけたい」 「技術を使って、サービスを裏側から支える側に回ってみたい」 私自身も理系出身だったため今の仕事でも経験や知識を活かすことはできていましたが、 CAとして人のキャリアを考える日々が、結果として自分自身のキャリアを見つめ直すきっかけとなり、社内の制度を利用してデジ戦への異動を決意しました。 会議が「宇宙語」に聞こえ、何も頭に入ってこなかった日々 意気揚々とエンジニアの世界に飛び込んだ私ですが、現実は想像以上に過酷でした。 異動して最初にぶつかった壁、 それは 「圧倒的な知識不足」 でした。 会議に参加しても、飛び交う単語が全くわからない。 「API」「カラム」「クエリ」……まるで宇宙語を聞いているような気分でした。 さらに辛かったのは、知識が追いついていない状態で、部長や事業部長といった上のレイヤーの方々へ説明する機会が増えたことです。 準備をしても不安が消えず、緊張しっぱなしでした。 開発会社様との会議でも、自分の理解が浅いためにその場で適切な回答ができず、持ち帰りになってしまう。 「CA時代はあんなにスムーズに仕事ができていたのに」 と、あまりの不甲斐なさに落ち込むこともありました。 それでも心が折れなかったのは、周囲の粘り強いサポートがあったからです。 初歩的な質問にも丁寧に答えてくれる周囲の存在に加え、 「悔しい、次は絶対に答えられるようになりたい」という思いが原動力となり、 必死に技術を吸収していきました。 別のSFA開発で基礎を築き、希望を出して「古巣」のシステムへ デジ戦に異動してすぐ、私が現在の担当業務に就いたわけではありません。 最初は全く別の営業支援システム(SFA)のチームに配属され、そこでゼロからエンジニアとしての基礎を叩き込まれました。 数年が経ち、設計や実装の基礎体力がついてきた頃、自分の中で一つの思いが強くなりました。 「エンジニアとしてのスキルと、元CAとしての業務知識。  この2つを掛け合わせれば、もっと大きな価値が出せるはずだ」 そこで私は、「かつて自分がCA時代に使っていた業務システム」を担当したいと自ら希望を出し、デジ戦内での異動を叶えました。 自分の強みが活かせる場所へ手を挙げれば、チャンスをもらえる。 これもデジ戦の魅力的な風土の一つです。 「元ユーザー」だからこそ、守れるシステムがある 念願叶って担当することになった「古巣」のシステム保守運用。 ここで、私の狙いは的中しました。 システムの保守運用では、現場(CA)から「エラーが出た」といった問い合わせが届きます。 純粋なエンジニア視点だけでは「仕様通り動いています」で終わってしまうようなことでも、私には「その向こう側の景色」が見えていました。 「現場の業務フロー的にここの情報は必ず必要だと思う」 「取引先の企業側はこういうことで不安を感じているはず」 業務の流れや、ユーザー(CA)の焦り・痛みが手に取るようにわかる。 だからこそ、「どこを優先的に直すべきか」「どう改修すれば喜ばれるか」を、解像度高く判断することができました。 異動してまだ3か月ほどですが、新たなプロジェクトのリーダーとして業務を進めています。 異動直後のあの「不甲斐なさ」を感じた経験があったからこそ、今、自信を持ってプロジェクトを推進できているのだと思います。 これからの挑戦 現場の営業やCA、学生、企業様と様々なユーザーが利用するシステムを担当し、 以下の技術に触れながら業務を行っています。 言語・フレームワーク: SQL インフラ・クラウド: AWS ツール: Tableau 、Dify CAとしての業務理解をベースに、今後はさらに技術力を磨き、 システムの「守り(保守)」だけでなく「攻め(新規機能開発)」にも積極的に関わっていきたいと考えています。 現場の声を一番理解しているエンジニアとして、本当に使われる機能を自分の手で実装することが今の目標です。 デジ戦は、私のように全く異なる職種からの挑戦も温かく受け入れ、成長を待ってくれる組織です。 最初は知識不足で悔しい思いをするかもしれません。しかし、これまでのキャリアで培ってきた「業務知識」や「顧客視点」は、エンジニアになっても決して無駄にはなりません。 一度エンジニアとしての基礎を身につければ、社内の多様なプロジェクトの中から、あなたの過去の経験が最も輝く場所を自ら選び取ることも可能です。 ぜひチャレンジしてみてください!
アバター
はじめに ITディベロップメント第1統括部1部開発3課のS.Kです! 普段は HugWag というトリミングサロンと飼い主のマッチングアプリの内製開発をしています! この度、ラスベガスにて開催されたAWS re:Inentにデジ戦メンバー4人で参加してきましたのでレポートします! 前の記事 では概要に関しての説明をしました。 ここでは、セッションに関してまとめてみようと思います。 セッションについて re:Inventでは期間中に2500以上のセッションが行われます。 セッションは複数の会場で並行して行われていて、会場間の移動には遠いと30分以上かかるので、どのようにセッションを受けていくかの戦略が大事になってきます。 セッションはその属性ごとに以下のようなSession Typeが設定されています。 Bootcamp Breakout session Builders' session Chalk talk Code talk Event service Examp prep Expo Featured experience Gamified talk Interactive training Keynote Lightning talk Meetup Self-paced training Workshop この中で、今回僕は主にBreakout Session, Builders' session Gamified talk, Workshopのセッションに参加しました。(Keynoteは動画で視聴) 会場 re:Inventでは以下の会場にてセッションが行われます。 Caesars Forum Mandalay Bay MGM Grand Venetian Wynn 隣り合う会場でも歩いて10~20分かかるイメージです。 他の会場と離れているMGM Grand, Mandalay Bayはシャトルバスかモノレールで移動します。 どちらもre:Invent参加者は無料です。 シャトルバスは会場内の案内に従っていけば特に苦労なく乗れて、5分刻みくらいの短い間隔で出発するのでかなり便利でした。 モノレールは駅によっては入り口の場所が分かりにくかったりしますが、駅に入りさえすれば駅員さんにre:Inventのパスを見せるだけで入れてくれます。 シャトルバス モノレール 事前の予約 各セッションは事前に予約することができます。 基本どのセッションにも当日参加の枠があって事前に並ぶことで予約なしでも参加することができますが、人気のセッションでは30分前とかには並んでいる必要がありそうなので、予約するのが確実です。 今年の場合は10/15の早朝2時からセッションの予約が開始されたので、参加メンバーでリモート会議しながら予約をしました。 人気なセッションだと5分後にはすでに予約が締め切られていたりするので予約開始と同時に予約するのがオススメです。 なお、予約するセッションの検索には こちらのサイト を使用しました。 ここら辺に関しては O.Kさんの記事 に詳しく書かれています。 各セッションについて 僕が参加したセッションについてまとめていきます。 せっかくなので求められる英語レベル、求められる技術レベル、総合おオススメレベルを5段階で書いてみます。 僕の英語レベル(読むのは少しできる、聴くのは苦手)と技術レベルを基準にした時の評価です。 どのセッションタイプに関しても難易度によって違うので一概には言えないのですが、何となくのイメージを掴んでもらえればと思います。 Breakout Session 求められる英語レベル: ⭐️⭐️⭐️⭐️(4/5) 求められる技術レベル:⭐️(1/5) 総合オススメレベル:⭐️⭐️(2/5) 講義形式で発表者のプレゼンを聞く形式のセッションです。 時間は大抵1時間程度です。 会場によっては1つの大部屋の中で同時に複数のセッションが行われるので、その際はヘッドフォンをして話を聞きます。 英語を聞き取る必要があるので求められる英語レベルは高いですが、スライド見てるだけで内容結構分かるものもあります。 会場内でリアルタイムで文字起こししてくれるモニターがあるのですが、ちょっとラグがあって話を聴きながらだと混乱して僕はあまり上手に使えなかったです。 また、後日録画映像がyoutubeにアップされるので、そちらを確認すれば内容を確認できるし、文字起こしも見やすいです。 後述のハンズオン系のセッションと比べて現地で受けるメリットは少ないですが、1時間と短く疲労感も少ないので、空いた時間などに入れていく形が良いかと思います。 (とはいえ、僕はもうちょっとハンズオン系のセッション増やした方が良かったなと思っています。。) 参加したセッション What’s new in fullstack AWS app development (DVT204) Building Enterprise-Ready Agentic Speech AI Pipelines on AWS (sponsored by NVIDIA) (AIM280-S) Lucid Motors: Building an AI-native Finance Function to Power Growth (sponsored by PwC) (AIM274-S) Session Details Long-Horizon Coding Agents: Complex Software Projects with Claude (sponsored by Anthropic) (AIM3316-S) Building Production-Grade Workflow Patterns with AWS Step Functions (API313) Anthology boosts contact center efficiency with AI (BIZ212) From Punch Cards to Pair Programming and Beyond: The Future of Copilot (sponsored by GitHub) (AIM294-S) Generative AI, agents, MCP, and the future of AI-powered software development (DVT217) Building software like never before with Agentic AI (DVT220) ヘッドフォンなし ヘッドフォンあり Builders' Session 求められる英語レベル: ⭐️⭐️⭐️(3/5) 求められる技術レベル:⭐️⭐(2/5) 総合オススメレベル:⭐️⭐️⭐️⭐️⭐️(5/5) 8~10人くらいの卓ごとに一人のメンターがつき、メンターの全体に向けての簡単な説明の後にドキュメントを見ながら各々で作業をする形式のセッションです。 時間は1時間のものが多いです。 作業の進捗が確認されることなどはないので、のびのびと作業に集中できます。 メンターの説明や質問をするときには英語のリスニング・スピーキングが必要ですが、それらがなくてもドキュメントを読んで作業さえできれば良いので求められる英語レベルは3としました。 技術レベルは3としました。レベルの高いセッションだと何も分からないということもあるかもしれませんが、だからと言って何か晒されるということもないと思うので全然大丈夫です! 参加したセッション Session Details Automate your software tasks with agent hooks [REPEAT] (DVT302-R) Learn new development skills with Kiro [REPEAT] (DVT201-R) Workshop 求められる英語レベル: ⭐️⭐️(2/5) 求められる技術レベル:⭐️⭐️⭐️(3/5) 総合オススメレベル:⭐️⭐️⭐️⭐️⭐(4/5) 全体に向けての簡単な説明の後にドキュメントを見ながら各々で作業をする形式のセッションです。 時間は2時間が多いです。 こちらから積極的にメンターに質問しなければ、黙々と作業をすることになります。 Builders' sessionと同様で、進捗を管理されることもないのでのびのびと作業することができます。 黙々と作業している時間が長すぎてラスベガスに来ていることを忘れかけた瞬間があったのでオススメレベルは4としました。 参加したセッション Building generative AI-powered full-stack applications [REPEAT] (ARC201-R) Rapid prototyping with Kiro CLI [REPEAT] (ARC308-R1) Create secure, production-ready code with Amazon Q Developer (DVT309) GameDay 知らない人との混合チームで参加する場合 求められる英語レベル: ⭐️⭐️⭐️⭐️(4/5) 求められる技術レベル:⭐️⭐️⭐️⭐️(4/5) 総合オススメレベル:⭐️⭐️⭐️⭐️⭐️(3/5) 知り合いでチームを作って参加する場合 求められる英語レベル: ⭐️⭐️(2/5) 求められる技術レベル:⭐️⭐️(2/5) 総合オススメレベル:⭐️⭐️⭐️⭐️⭐️(5/5) タスクを達成するたびにポイントが加算されていき、チームごとに順位を競う形式のセッションです。 時間は3時間程度のものが多いです。 知り合い同士でチームを作ることも、1人で行って知らない人との混合チームを作ってもらうことも可能です。 知り合い同士だったら出来なくてもなんでもないし、日本語で会話すれば良いので敷居はかなり低いです。 一方1人で参加して知らない人との混合チームになる場合は英語でメンバーと会話する必要があるので敷居がちょっと高いかなと思います。 ただタスクを達成される度にポイントが入ってランクが上がっていく感覚はかなり楽しいので、ぜひ経験してみていただきたいです! 参加したセッション AWS Jam: DevOps & Modernization - Sponsored by LaunchDarkly (GHJ303) 終わりに 各セッションのイメージ分かりましたでしょうか? 行ってみて初めて分かることも多々ありますが、事前の計画が大切です! re:inventを100%楽しめるように、事前に調査してしっかり計画立てていきましょう! 参照元メモ 【AWS re:Invent 2025】AWS re:Invent 2025 に参加してきました! | マイナビエンジニアブログ
アバター
イベント概要とこの記事の目的 法人ディベロップ課のS.Sです! 日々、アプリケーションエンジニアとして、法人ソリューション事業部向き合いで、サービスの開発・保守を行なっております。 先日、AWS のセキュリティイベント 「Security for App Builders @ Loft #1」  に参加してきました。 私は、セキュリティ専門ではないアプリケーションエンジニアですが、 このイベントで、セキュリティへの関心がよりより湧き、 未然に防ぐ具体的なアクションに取り組まねばな、と改めて思わされました。 この記事では、 「Security for App Builders @ Loft #1」 で、何を学んだのかを記すとともに、 「アプリケーションエンジニアで、セキュリティについて気にはなっている」という方の参考になれば幸いです。 改めて、この記事の内容・目的は、以下の 4 点です。 イベント内セッションの概要を記載しつつ、学びと理解を整理する アプリケーションエンジニアのセキュリティ意識や理解を深めてもらう セッション内容の共有と、背景知識やちょっぴりの深掘り情報の共有 セキュリティ専門じゃないアプリエンジニアとして、「これから試してみたい脅威の洗い出しのやり方」のたたき台をまとめる そこそこの分量になっているので、気になるタイトルをポチポチとかいつまんで見ていただけると幸いです。 なぜ、今、アプリケーションエンジニアがセキュリティを強く意識すべきなのか OWASP Top 10 とアクセス制御の不備 冒頭で触れられていたのが  OWASP Top 10  です。 OWASP Top 10 は、Web アプリケーションの代表的なリスクを 10 項目に整理したもの 直近の正式版は  OWASP Top 10: 2021  で、たとえば A01:2021 – Broken Access Control(アクセス制御の不備) A02:2021 – Cryptographic Failures… といった分類があります セッションでは、 OWASP のデータに基づき、「アクセス制御の不備」が一定割合(3.73%)で見つかっている という話がありました。 プロのエンジニアの手で開発されたサービスにおいて、アクセス制御の不備という重大な脆弱性が、およそ4%(100件に4件)も起こり得てしまうという点に驚いたとともに、エンジニアとしてお仕事を続けていれば、誰しも1度は発生させてしまうくらいの可能性があるなと背筋が伸びました。 参考: OWASP Top 10:2021 (英語) https://owasp.org/Top10/ Broken Access Control の解説 (英語) https://owasp.org/Top10/A01_2021-Broken_Access_Control/ シフトレフト:後になるほどコストが跳ね上がる 工数やスケジュールが限られた中での開発で、往々にしてセキュリティ対応は後回しにされてしまいます。 けれども、セキュリティ対応は、 開発ライフサイクルの早い段階で取り込むほど「コストは低い」「被害は小さい」 ので、むしろ開発サイクルのはやい段階で取り組むべしというお話でした。 仕様検討(要件定義)で気づく → 仕様の一部修正で対処可能 実装中に気づく → 実装の修正で対応可能 リリース後に顧客やインシデントで気づく → 影響調査、データ復旧、顧客への説明、信用回復など大きなコスト 絵にするとこんな感じです。 この「セキュリティを開発プロセスの左側に寄せる」考え方が 、 Shift-left(シフトレフト)  です。 矛盾しているように聞こえますが、工数やスケジュールが限られているからこそ、開発サイクルの中でも、なるったけ早くに、セキュリティ対応をすべしということですね。 参考: DevSecOps とは何ですか? https://aws.amazon.com/jp/what-is/devsecops/ AWS Well-Architected Framework – Security Pillar https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/welcome.html ビルダーのための脅威モデリング さて、ここからが、今回のタイトルに直結する部分です。 (サビです) 「脅威モデリング」とは何か こちらのセッションでは、まず「脅威モデリング」の定義から整理されました。 脅威モデリングとは システムに対する脅威を特定・列挙し、 それぞれにどう対応するかを検討し、優先順位をつけるプロセス 開発ライフサイクルで置く場所は、 計画 → 設計(ここで脅威モデリング) → ビルド → テスト → デプロイ → 保守 です。 絵にするとこうです。 参考: OWASP Threat Modeling Cheat Sheet (英語) https://cheatsheetseries.owasp.org/cheatsheets/Threat_Modeling_Cheat_Sheet.html AWS Prescriptive Guidance – Threat modeling on AWS (英語) https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/threat-modeling.html 脅威モデリングの進め方 セッションで紹介されていた流れは、以下の 4 ステップでした。 「何を作っているか」を明確にする アーキテクチャ図 データフロー図 何が問題になり得るか洗い出す チームで脅威を議論する 対応方針を決め、優先順位をつける ここでのポイントは、 可視化をして、PJメンバー全員で前提・問題の認識を揃えること → 図がないと、どこに攻撃面があるか議論できない 1 回きりではあまり意味がなく、ライフサイクルに組み込む必要がある という点です。 絵にするとこうです。 STRIDE など、代表的なフレームワーク 脅威モデリングでは、次のようなフレームワークが紹介されました。 STRIDE DREAD PASTA(Process for Attack Simulation and Threat Analysis) Trike VAST(Visual, Agile, and Simple Threat) OCTAVE(Operationally Critical Threat, Asset, and Vulnerability Evaluation) 今回、取り上げられていたのは  STRIDE  でした。 網羅性が高く、チームでの議論の「型」として使いやすい のが理由です。 STRIDE の 6 要素 S: Spoofing identity(なりすまし) 攻撃者が正当なユーザーやシステムであるかのように振る舞うこと 例: 攻撃者が他人のIDとパスワードを不正に入手し、そのユーザーになりすましてシステムにログインする 例: 偽のWi-Fiアクセスポイントを設置し、正規のネットワークであるかのように見せかけて接続させる T: Tampering with data(改ざん) データや通信内容を許可なく変更すること 例: オンラインバンキングの通信を傍受し、送金金額や送金先口座番号を書き換える 例: Webサイトの設定ファイルを書き換え、訪問者を悪意のあるサイトへリダイレクトさせる R: Repudiation(否認) ユーザーが行った操作やアクションを「やっていない」と主張できる状態、またはその証拠がないこと 例: ログ機能が不十分なシステムで、従業員が不正なデータ削除を行ったが、「自分はやっていない」としらを切られ、証拠が出せない 例: 電子署名がないメールで契約の承認を行い、後になって「そのようなメールは送っていない」と主張される I: Information disclosure(情報漏えい) 機密情報が権限のない人間に閲覧・公開されること 例: データベースの設定ミスにより、顧客のクレジットカード情報がインターネット上で誰でも閲覧できる状態になる 例: エラーメッセージに詳細なシステム内部情報(パスやDB構造など)が表示され、攻撃者にヒントを与えてしまう D: Denial of service(サービス拒否) 正当なユーザーがサービスやリソースを利用できない状態にすること 例: Webサーバーに大量のアクセス(DDoS攻撃)を送りつけ、サーバーをダウンさせて一般ユーザーが閲覧できないようにする 例: アカウントロック機能を悪用し、わざとパスワードを何度も間違えて特定ユーザーのアカウントをロックさせる E: Elevation of privilege(権限昇格) 本来持っている権限以上の操作ができるようになること 例: 一般ユーザーとしてログインした攻撃者が、システムの脆弱性を突いて管理者(root/admin)権限を奪取する 例: URLのパラメータを書き換えることで、本来アクセスできない他人の注文履歴ページを閲覧・操作する これを「自分たちのシステム構成図・データフロー図」に対して当てはめていくことで、脅威モデリングができます。 参考: Microsoft – The STRIDE Threat Model (英語) https://learn.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats OWASP Threat Modeling Cheat Sheet 内の STRIDE 解説 (英語) https://cheatsheetseries.owasp.org/cheatsheets/Threat_Modeling_Cheat_Sheet.html#stride ドメイン特有の脅威に目を向ける 一般的に、下記のような 共通の脆弱性  は見つけやすいですが、 SQL インジェクション XSS 典型的な認証バイパス ビジネス/ドメイン特有の脅威  は見逃されがちです。 例: EC サイト 在庫数や価格の改ざん ポイント/クーポンの不正利用 B2B SaaS テナント分離の不備による他社データ閲覧 管理権限の誤委譲・誤設定 サブスクリプションサービス 無料トライアルの不正延長 請求先のなりすまし 脅威モデリングでは、こういった「そのサービス特有のリスク」を意識的に洗い出せる点が大きな価値です。(ドメイン理解の深い事業会社のエンジニアの価値発揮できるポイントですね) チームでやることの意味 脅威モデリングは、セキュリティ専門家だけの作業ではない、「チームでやるべきことである」ということが強調されていました。 プロダクトオーナー アプリケーションエンジニア インフラ/SRE セキュリティ担当 など複数ロールで実施することで、 「多様なペルソナ(攻撃者像・利用者像)」を出し合える 見落としが減る という効果があります。(今の時代は、更に、AI というもう一人の存在の力も借りるのも有効ですね) セキュリティ専門じゃない自分が「脅威の洗い出し」をどう始めたいか 正直な所、私はまだ、日々の開発の中で、 本格的な脅威モデリングを回せていないです。 (新規開発や保守をしていく上での脆弱性診断や対策は行ってはいますが) このセッションを聞いて、「まずはこのくらいのスコープからなら試せそう」と感じたものがあったので、簡単に流れを書いておこうと思います。 新しい機能や API を作るときだけでもよいので対象を絞る ざっくりした構成図・データフロー図を描く(ホワイトボードや Miro 等の書き出せるものでOK) STRIDE の 6 つを観点にして 、「なりすましは?」「改ざんは?」「情報漏えいは?」… を見ていく 出てきた脅威のうち 「すぐ直せそうなもの」 「影響が大きいもの」 を対策に落とす AI を活用した脅威モデリング支援 後半戦では、 AI を活用して脅威モデリングの手間を減らす という話が出ていました。 登場したのは「AI エージェント」的なコーディング/解析支援のイメージで、AWS でいえば  Amazon Q Developer  ですね。(今は、 kiro  ですね) AI にやらせる部分 vs 人間がやる部分 スピードや網羅性に関しては、AI の方が人間よりも優れていることが多いので、うまくAIと人間で分業をして協業することが重要そうだなと感じました。 AI に任せやすい部分: ワークロードの図式化 アーキテクチャ説明文から簡易図を生成させる 想定される脅威の洗い出し 「この構成に対して STRIDE 観点で脅威を列挙して」 想定される脅威への一般的な対応策の候補出し リスク評価のたたき台 「影響度が高そうな順に並べて」 人間が担うべき部分: 脅威モデリングそのものの意義と目的の理解 事業やサービスのドメイン周りの脅威洗い出し 「何を対象に、何のために」脅威モデリングするかの設定 事業戦略・リスク許容度に基づく  優先順位付けと最終判断 「 AI は“脅威のたたき台”を出してもらうところまで 」と割り切り、 最後の判断と、プロダクト側への落とし込みは人間がやる といった線引きで使っていきたいと感じました。 セキュリティのシフトレフトと AI 活用 攻撃は 100% 防げないが、被害は限定できる このセッションは、 「リスクをゼロにはできないが、 被害を限定し、復旧を容易にすることはできる」 という前提から出発していました。 例として挙げられたランサムウェア攻撃の典型フロー: ネットワークスキャン 脆弱性の調査・悪用 ランサムウェアの設置 ランサムウェアがターゲットシステムで動作 情報の窃取や暗号化 どこか一段階でも早く検知・防御できれば、 「完全な被害」から 「一部のシステムだけ」「一部データだけ」で済む可能性が高まる という話です。 DevSecOps と AI の進化 2015年ごろから、セキュアコーディングについて、叫ばれていたが、業務に落とし込むことが物理的な制約のため難しかったけれど、2025年現在のAI の進歩により、実現可能になったので、今こそ改めて、セキュアコーディングへチャレンジをしていけるのではないかというお話でした。 2015 年ごろ DevSecOps という言葉が広がり始める ただしセキュアコーディングの学習コストが大きい 静的解析ツールなどから出る大量のアラートを人力でさばくのは現実的でない 2025 年 AI コーディングエージェントが静的解析結果を解釈し、「どこをどう直すか」まで提案 AWS の例では Amazon Q Developer による 調査(現状のコード理解、セキュリティチェック) ビルド(修正案の提案・コード生成) テスト(テストコード生成) デプロイ(PR レビュー支援、ドキュメント生成) といったフローが紹介されていました。 確かに、やりたい気持ちはあれど、なかなか手を出せていなかったですが、AI エージェントの誕生によって、良い意味で、もうそんな言い訳もできなくなったなと思いました。(← もうやってないは、怠慢かも?) 参考: DevSecOps とは何ですか? https://aws.amazon.com/jp/what-is/devsecops/ Amazon Q Developer でのコード解説や修正の例 https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/what-is-q-developer.html セキュリティトレーニングのテーマ アプリケーションエンジニア向けに、特に挙げられていたテーマは、 ガバナンスとコンプライアンス セキュアコーディング リスクマネジメント ここでも AI を「学びの相棒」として使う例として、紹介されていました。 利用 OSS やライブラリについて 「このライブラリに既知の脆弱性はあるか?」 「この CVE(脆弱性)の影響度と対策を教えて」 自分のコードに対して 「ハードコードされたパスワードやシークレットがないか探して」 「この関数にセキュリティ上の問題はありそうか?」 これについては、既に行なっていましたが、すぐに試せて、かつ、有効な使い方だなと改めて思いました。 IAM 権限設計と IAM Access Analyzer IAM の権限設計に関しても重要なポイントがありました。 「最小権限を人間だけで完全に設計しきろうとすると、どうしても穴が出やすい」 そこで活用が推奨されていた AWS サービスが  IAM Access Analyzer  です。 AWS Identity and Access Management Access Analyzer https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html 機能の例: IAM ポリシーを解析し、「外部アカウントからアクセス可能なリソース」を検出 ポリシーの提案(Policy generation) 実際のアクセスログを分析して、「このロールに必要な最小権限」のポリシー案を生成 ここでも AI を組み合わせると、 「このポリシー JSON はどの権限をどこまで許可しているか、自然言語で説明して」 「最小権限化するために、削れそうなアクションはどれか?」 といった質問を投げることで  理解と設計の速度を上げられる 、という話でした。 自分自身、インフラ側への苦手意識が強いですが、こういった活用法を用いることで、AIエージェントの力を借りつつ、知識習得と堅牢な実装の両方を猛スピードで行なっていけるのでは?とワクワクしました。 マネージドサービスと認証・認可はビジネス差別化になり得る AI が書いたコードのセキュリティオーナーは誰か? 登壇者の方の問いかけ: 「AI が生成したコードをそのまま本番投入してよいのか? その結果に対するセキュリティの責任は誰が持つのか?」 結論としては、 「 全員がセキュリティのオーナー 」 あくまで、AI は「候補」を出してくれる 存在 それを採用するかどうか、どうレビューするかは人間と組織の責任 ということでした。 本当にこの通りで、最後に出す判断を下すのは我々人間なので、これまで以上に、出すものは、本当にこれで問題がないのかは、厳格に見極める必要があるなと感じました。 Culture of Security AWS では、社内外で継続的に、 セキュリティに関する発信 勉強会 事例共有 インシデントからの学びの共有 を行い、トップダウンで  「Culture of Security」  を作っているという紹介がありました。 組織やPJ のトップが、セキュリティへの関心や発信を積極的に行うことが、セキュリティへの意識・行動を想起するには重要とのことでした。 参考: AWS セキュリティ文化 https://aws.amazon.com/jp/security/culture-of-security/ AWS Security Blog 一覧 https://aws.amazon.com/blogs/security/ 認証・認可設計は「ビジネス差別化」にもなる 特に印象に残ったのが、 「優れた認証・認可の仕組みは、 セキュリティ対策であると同時に、ビジネスの差別化要因になる」 というお話でした。 各サービスごとにバラバラなユーザー管理をしている ユーザー体験:ログインが面倒、アカウントが分散 事業側:サービス横断の体験・クロスセルが難しい 統合された認証・認可基盤がある 一度ログインすれば複数サービスをシームレスに利用可能 ユーザー行動を横断的に把握できる セキュリティ設計がそのまま UX と事業戦略に効いてくるという例ですね。 認可アーキテクチャの用語整理 認可ロジックをアプリコードにべた書きせず、外部の仕組みに任せやすくするための概念として、以下の用語が紹介されていました。 PEP: Policy Enforcement Point 実際のアクセス要求をブロック/許可する「ゲート」 PDP: Policy Decision Point ポリシーに基づき「許可/拒否」の判定を行うコンポーネント PAP: Policy Administration Point ポリシー(ルール)を管理・編集するコンポーネント PIP: Policy Information Point 判定に必要な属性情報(ユーザー属性、リソース属性など)を提供するコンポーネント この分離により、アプリ側コードは「PEP と対話するだけ」で済み、 認可ルール自体は外部で一元管理しやすくなるとのことでした。 参考: AWS – Amazon Verified Permissions(ポリシーベースの認可サービス) https://aws.amazon.com/jp/verified-permissions/ セキュリティ専門じゃないアプリケーションエンジニアとして、始めてみたい「脅威の洗い出し」(+AI 活用) 最後に、これまでのセッション内容を踏まえて、 実際に現場で「脅威の洗い出し」をどう始めてみたいか を、具体的な手順として書いてみます。 これから試してみたい「ざっくり手順」 まず、 自分用の「脅威の洗い出し」のざっくり手順(案) はこんなイメージです。 対象を決める いきなり全システムではなく、「これから作る 1 機能 / 1 API」くらいにスコープを絞る ざっくり図を描く 画面遷移 or API と外部サービスの関係、データフローを簡単に図にする 守りたいものを挙げる ユーザー情報、決済情報、社内の機密データ、ポイント残高 など STRIDE を観点に「脅威のたたき台」を AI に出してもらう AI の結果を見ながら、チームで 60〜90 分だけ議論する 「今すぐ対応するもの」を 2〜3 個だけ決めて、実装・レビューに反映する 仕様書を AI に渡して「脅威のたたき台」を作る 要件定義書・仕様書・画面遷移図などを AI に渡して、 「この仕様から想定されるセキュリティリスクを、STRIDE の観点で列挙して」  と依頼する その出力をもとに、チームで 60〜90 分の脅威モデリングミーティングを行う 「AI は“思いつきの網羅”担当、人間は評価と意思決定担当」  くらいに役割分担するイメージで進められたらと思います。 依存ライブラリの脆弱性を AI に要約させる これもすぐにできて、セキュリティリスクを抑える有効な手段なので、書いておきます。 npm audit ​ などの結果や、SCA ツールのレポートを AI に渡し、 「特に優先度が高いものはどれか?」 「どのライブラリは自分たちのアーキテクチャでは影響が小さそうか?」 を整理してもらう これにより、 「アラートの山」から「今すぐ直すべき山」を特定 して、優先度の高い脆弱性を潰せる可能性を高めます。 既存コードベースの「簡易セキュリティ健診」 重要なモジュールや API ハンドラを中心に、AI に次のような観点で探してもらう ハードコードされたパスワード/API キー 認証・認可チェックが抜けていそうなエンドポイント 危険な関数( eval ​ など)の利用 その結果から「怪しい箇所リスト」を作り、 人間が一つずつレビューしていく AI には広く粗く見てもらって、最後の判断は人間で、という分業で進められたらと思います。 セキュリティ文化を根付かせる動き 脅威の洗い出しを 一度きりのイベントにしない ために、 次のような小さな仕掛けをチーム内で提案してみたいと思っています。 大きな機能追加のたびに 「脅威モデリングをやったか?」をチェックリストに入れる 月 1 回程度の 「インシデント/ヒヤリハット共有会」を開く 上長に、セキュリティ文化を根付かせる発信やアクションを提案する (この記事を読んで頂こう) ここにあげた手段を提案し、発信や実際に定期的イベントを行う いきなり、大きく完璧にやろうとすると、大抵続かない、うまくいかないので、まずは 「新しい機能を作るタイミングで、脅威モデリングを行う」 ところから始めてみたいと考えています。 ゆくゆくは、組織単位で、例えば上記のようなことが浸透していけることが最終ゴールにできればなと思います。 現時点では、ここに書いた「脅威の洗い出しのやり方」はあくまで これから試してみたい案 です。 実際に回してみてうまくいった点・うまくいかなかった点が見えてきたら、続編としてアップデートしていきたいと思います。 追記 開発で携わっているPJ で、早速、新規API を作成する機会ができたので、早速、脅威モデリングをお試しでやってみようとなりました。 初回なので、あまりに厳密にやりすぎず、脅威モデリングにおいて、重要な要素をシンプルに抽出して、進めていきます。 「実際に、やってみた」の記事も書けたら、書きます。
アバター
はじめに 2025年はAIエージェントによるコーディングがかなり浸透した1年だったのではないでしょうか。Claude Code、GitHub Copilot、Cursor をはじめとするAIエージェントによるコード生成ツールが普及する中、現場のエンジニアは実際にどのような効果を実感し、どのような課題に直面しているのでしょうか。 本レポートでは、2025年12月末に社内のエンジニア・マネージャー約100名を対象に実施したアンケート調査の結果をもとに、 満足度・生産性・コード品質・スキル向上 の4つの観点からAIツールの活用実態を分析します。 主な発見 観点 結果 満足度 約9割が満足、NPS +32と高い推奨意向 生産性 約65%が1日1時間以上を節約、バグ対応・コード理解で特に効果を実感 コード品質 「やや高い」が最多だが、約9割が何らかの修正を実施 スキル 新技術習得は加速する一方、「自力で書く力」への懸念も約4割 経験年数による傾向 若手〜中堅層(1〜5年) :満足度・生産性向上の実感が高い一方、スキル低下への懸念も強い ベテラン層(10年以上) :慎重な評価だが、確立されたスキルの上でAIを活用 以下、各セクションで詳細を見ていきます。 回答者の基本情報 社内エンジニア・マネージャー約100名から回答を得ました。 項目 概要 職種 バックエンド・フロントエンド中心、インフラ・マネジメント・モバイルも多数 経験年数 1〜10年がボリュームゾーン(詳細は下図) 使用言語 TypeScript/JavaScript最多、Ruby・PHP・Pythonが続く 利用ツール GitHub Copilot と Q Developer/Kiro で約8割 利用頻度 約7割が「ほぼ毎日」、週3〜4日含め9割超が日常的に活用 経験年数 使用言語 利用頻度 AIツールへの満足度 AIツールに対する満足度を「全体満足度」「期待との比較」「同僚への推奨度(NPS)」の3軸で調査しました。結果として、 全体的に高い満足度 が確認されましたが、一部に注意すべき声も見られました。 約9割が「満足」と回答 全体満足度では、 合計約90%が満足 と回答しました。「非常に不満」はゼロであり、AIツールが日常業務に定着していることを裏付けています。 期待を上回る効果を実感 導入前の期待と比較して、 約65%が期待以上の効果を実感 しています。「期待通り」を含めると96%以上がポジティブな評価であり、導入判断が正しかったと言えます。 NPS +32:高い推奨意向 同僚への推奨度を問うNPS調査では、平均スコアが 約8.0 、NPSは +32 となりました。これは「良好」とされる水準であり、多くのエンジニアが同僚にも利用を勧めたいと考えています。 推奨の声と懸念の声 推奨する理由(抜粋) 壁打ち相手として最適 AIツールがなかった時代にはもう戻れない 懸念・注意点(抜粋) 使い方を誤ると逆効果。リテラシーが必要 自分で考える時間が減り、成長が滞る可能性 PRが増えてレビュー負荷が高まっている これらの声から、AIツールは 「使いこなせば強力な武器」 である一方、 組織としての運用ガイドラインや教育 が求められていることが読み取れます。 経験年数による回答の傾向 経験年数 平均スコア 推奨者(9-10) 中立者(7-8) 批判者(0-6) 1年未満 8.4 50% 43% 7% 1〜3年 8.2 50% 35% 15% 3〜5年 8.3 45% 45% 10% 5〜10年 8.0 42% 46% 12% 10年以上 6.9 22% 45% 33% 経験年数別に観察しますと、 若手〜中堅層(1〜5年)は特に高い満足度 を示していました。「非常に満足」の割合は38〜43%、NPS平均も8.0〜8.4と高水準でした。学習効率の向上や開発スピードの改善を実感している声が多く見られました。 ベテラン層(10年以上)はやや慎重な評価 となりました。「非常に満足」は22%にとどまり、NPS平均も6.9と他層より低い結果です。自由記述では「AIの出力を見抜く力が必要」「本人ができないことをAIで補う使い方には懐疑的」といった声があり、 AIツールの限界を理解した上での活用 を重視していることがうかがえます。 生産性への影響 AIツールの導入により、エンジニアの日常業務はどのように変化したのでしょうか。時間節約効果、業務品質の変化、そして残された課題について詳しく見ていきます。 1日1〜2時間の節約が最多、「2時間以上」も4人に1人 「AIツールにより1日あたりどの程度の時間を節約できているか」を尋ねたところ、 最も多かったのは「1〜2時間」で約38% でした。さらに 「2時間以上」と回答した人も約24% に達し、AIツールが単なる補助ではなく、業務時間を大幅に短縮する「生産性を向上させるツール」として機能しています。 ただし「ほとんど節約できていない」という回答も存在し、ツールの活用度合いや業務内容によって効果に差があることも示唆されています。 5つの業務指標すべてで改善傾向 導入前後の変化を5段階評価で調査した結果、 すべての項目で「向上」「やや向上」が過半数 を占めました。 ただし「集中状態(フロー)への入りやすさ」は改善効果が限定的 で、「向上」は約45%にとどまり、「やや悪化」も約12%存在します。AIとの対話や提案の確認作業が集中を中断させること、また生成待ち時間に別作業を行うようになったことが要因と考えられます。 課題の中心は「AIの限界」と「検証コスト」 AIツール利用時に感じる課題を複数選択で尋ねたところ、 上位3つは以下の通り でした。 コンテキストの理解が不十分 (約45%) AIの出力を検証する負担が大きい (約40%) セキュリティ面での不安 (約38%) これらの課題は相互に関連しています。AIがプロジェクト固有の文脈を十分に理解できないと、ロジックの誤りや不適切なコードが生成されやすくなります。その結果、開発者はAI出力の検証に多くの時間を割く必要があり、見落としによるセキュリティリスクへの不安にもつながっていると考えられます。 AIが最も力を発揮するのは「バグ対応」と「コード理解」 「生産性向上に最も貢献している作業」を上位3つまで選択してもらった結果、 問題解決(バグ対応)や理解支援 といった「思考を補助する」用途で高く評価されています。エンジニアがAIを「コードを書かせる道具」ではなく、 「一緒に考えるパートナー」 として活用し始めていることを示しています。 コード品質への影響 生産性の向上が確認された一方で、「品質は犠牲になっていないか?」という懸念もあります。本セクションでは、AIツールが生成するコードの品質評価と、導入後の品質指標の変化を詳しく見ていきます。 品質評価は「やや高い」が最多——ただし検証は必須 AIが生成するコードの品質について、 「やや高い」が約50% で最多となりました。一方で「普通」も約40%を占め、 「非常に高い」はわずか約3% にとどまっています。 この評価を裏付けるように、出力の検証・修正状況では 約9割が何らかの修正を加えている と回答。「ほぼそのまま使用」はわずか2%でした。 注目すべきは、 「大幅な修正が必要」がゼロ だった点です。AIの出力は「使い物にならない」レベルではなく、 「手直しすれば十分使える」品質 といえます。エンジニアはAIを「完璧なコードを生成する」ものではなく、 「たたき台を素早く作るツール」 として活用しているようです。 経験年数による傾向:若手ほど「改善」を実感 経験年数別に品質評価を分析すると、 若手〜中堅層(1〜5年)は「やや高い」の割合が高く 、AIツールの品質に対して好意的な評価を示しています。一方、 ベテラン層(10年以上)は「普通」「やや低い」の割合が相対的に高く 、より厳しい目で品質を評価していることがうかがえます。 これは満足度セクションで確認された傾向と一致しており、ベテラン層が 「AIの出力を見抜く力が必要」 と考えていることの表れと考えております。 学習・スキルへの影響 AIツールの活用は、エンジニアのスキル向上にどのような影響を与えているのでしょうか。本セクションでは、スキルへの影響、依存への懸念、そして今後の利用意向について詳しく見ていきます。 新技術の習得速度が最も向上、一方で「自力で書く力」には懸念 スキル向上への影響を4つの観点から調査した結果、 項目によって明暗が分かれる 結果となりました。AIツールが「学習のハードルを下げる」役割を果たす一方で、頼りすぎによる基礎力低下のリスクが懸念されています。 自由記述では以下のような声が見られました。 学習効率向上を実感する声: 他チームメンバーに聞かなくても、壁打ち相手になってくれるので、自身の理解向上やチームの生産性向上に大きく貢献しているため スキル低下への懸念を示す声: 仕事自体は進むが、成長の側面で考えると良くないと感じる部分も多いため スピードや品質は向上するが、深く理解しないままAIで一定のコードや正解らしき情報が出力されるため、自分で深く考えたり調査したりする時間が減り、技術的な成長が滞るため 約4割が依存に「懸念あり」——ただし意見は二分 AIツールへの依存について尋ねたところ、 約45%が何らかの懸念を示しました 。一方で、 「あまり懸念なし」「全く懸念なし」も約39% と拮抗しており、エンジニアの間で意見が分かれています。 自由記述では、懸念の有無にかかわらず 「使い方次第」 という認識が共通して見られました。 利用できるのであれば、使わない理由はないと思っている。ただし、エンジニアとしての今後の成長を考えるのであれば、利用範囲、パターンは考えた方が良いとは思っている また、経験年数による活用の違いを指摘する声もありました。 エンジニア歴が非常に浅い人には鵜呑みにしてほしくないと思っています。理由は書いたコードの説明ができない程、自分の頭を使わないようになってコードに責任を持てない可能性がある為です。歴が長い人であれば生成されたコードが期待通りなのかそうではないのか判断がつくのでお勧めできます 一見万能に見えるが、正しい使い方を知らなければかえって効率が悪くなる。また、手作業の方が早いタスクもある。ただ使うだけで他メンバーに迷惑をかけるような人材には勧めない。リスクやデメリットもしっかりあるものだと理解している人だけに勧めたい 経験年数による傾向:若手ほど「自力で書く力」への悪影響を実感 経験年数 自力で書く力に「悪影響」「やや悪影響」と回答した割合 1年未満 約40% 1〜3年 約50% 3〜5年 約55% 5〜10年 約40% 10年以上 約35% 興味深いのは、 1〜5年目の若手〜中堅層で「自力で書く力への悪影響」を感じる割合が高い 点です。基礎スキルを習得・定着させる時期にAIツールに頼りすぎることへの自覚があると考えられます。 一方、ベテラン層(10年以上)は「悪影響」の割合が相対的に低く、 すでに確立されたスキルの上にAIを活用している ため、悪影響を感じにくい傾向がうかがえます。 継続利用意向は97%——「なくてはならないツール」へ 今後の継続利用意向を尋ねたところ、約97%が継続利用を希望しています。スキルへの懸念がありながらも継続利用を希望する声が圧倒的多数であることから、AIツールは 「課題を認識しつつも手放せない存在」 になっていることがわかります。 組織への要望:「ガイドライン整備」が最優先 AIツールの活用促進のために組織に取り組んでほしいことを尋ねた結果、 上位は以下の通り でした。 順位 取り組み内容 選択率 1位 利用ガイドライン/ベストプラクティスの整備 約55% 2位 セキュリティポリシーの明確化 約40% 3位 効果的な活用事例の共有 約38% 4位 社内勉強会・ナレッジ共有の機会 約32% 5位 ライセンスの追加付与 約30% 6位 新しいツールの試験導入 約28% 「利用ガイドライン/ベストプラクティスの整備」が過半数を超え、最も強く求められている ことがわかります。これは生産性セクションで確認された「社内ルール/ガイドラインとの整合性」への課題意識と一致しています。 自由記述でも、組織としての取り組みを求める声が見られました。 間違いなくAIを使った方がいいが、各個人単位ではなくPJや組織としてのAIを活用する前提での開発フローの共有が欲しい 今後に向けて AIツールの効果を最大化し、リスクを最小化するためには、以下の取り組みが求められます。 重点施策 1. 利用ガイドラインの整備 サイボウズ株式会社の新人研修資料 では、「裏取りは必ず行うこと」と明記し、AI出力を盲信しない姿勢を研修段階からエンジニアに伝えています。 弊社でも、セキュリティポリシーの明確化に加え、AI出力の検証プロセスをガイドラインとして整備していきます。 2. ナレッジ共有の促進 READYFOR株式会社のアンケート では、組織に求めるサポートとして「チーム内での経験・課題共有の場」が67%と高い割合となっています。 弊社のアンケートでも同様の声が見られました 。 また、 株式会社エブリー では、AIツールを用いた開発効率化の勉強会を開催し、知見を共有しています。 弊社でも、効果的な活用事例や失敗事例を共有する場を定期的に設けていければと考えております。 3. スキル育成との両立 本アンケートでは、1〜5年目の若手〜中堅層で「自力で書く力への悪影響」を感じる割合が高い結果となりました。この課題に対し、他社では以下のようなアプローチを取っています。 フリー株式会社 : 新卒研修を「前半6週間はAI禁止」「後半5週間はAI全力活用」の2段階構成とし、基礎スキル習得とAI活用のバランスを設計 クラウドエース株式会社 : AIの出力に対する「なぜこの実装なのか」を問う習慣化を徹底し、AIの言いなりにならない開発者を育成 弊社でも、特に若手層に対する基礎力強化の機会を確保しつつ、AI活用スキルを高める両輪のアプローチを検討していきます。 継続的な改善に向けて 定量的な効果測定 今後はアンケート結果だけでなく、ツールの使い方を定量的にメトリクスをもとに分析していきます。 フリー株式会社のAI駆動開発2025レポート では、トークン消費量やリクエスト数などの定量データを継続的に測定しています。 組織的な推進体制 組織戦略としては、 合同会社DMM.comのAX戦略 や フリー株式会社の組織設計 も参考に、組織全体での利活用を促進する仕組みを整えていきます。 おわりに AIは「完璧なコードを書く」ツールではなく、 「たたき台を素早く作り、一緒に考えるパートナー」 です。この認識のもと、組織として適切な活用体制を整えていきます。 最後までお読みいただき、ありがとうございました。
アバター
はじめに ITディベロップメント第1統括部1部開発3課のS・Kです! 普段は HugWag というトリミングサロンと飼い主のマッチングアプリの内製開発をしています! この度、ラスベガスにて開催されたAWS re:Inentにデジ戦メンバー4人で参加してきましたのでレポートします! AWS re:Inventとは AWS re:Invent とは、AWS(Amazon Web Services)がアメリカのラスベガスにて毎年開催する世界最大級の技術カンファレンスです。 今年は12/1から12/5までの5日間(実質的には12/4まで)で開催されました。 講義形式(Keynote, breakout session...etc)で最新情報が取得できたり、演習形式(builders's session, workout session)で実際に手を動かして学ぶことができます。 マイナビからは2023年から参加しており、今年で3年目になります。 参加レポも毎年上がっているので、ぜひ併せてお読みください。 (各記事大変参考になりました、ありがとうございます) 【AWS re:Invent 2024】ラスベガスで開催のAWS re:Invent 2024 に現地参戦してきました! | マイナビエンジニアブログ 【AWS re:Invent2024】S3バケットくん徹底解剖 | マイナビエンジニアブログ ラスベガスにてAWS re:InventのKeynoteに現地参加してきました!!! | マイナビエンジニアブログ スケジュール 大まかなスケジュールを示します。 なお、日本はラスベガスより17時間進んでいることに注意してください。 日本が日曜日の10:00の時、ラスベガスは土曜日の17:00です。 ラスベガス到着まで 移動時間が長いので、どうしてもハードなスケジュールになってしまいます。 飛行機で寝れるかどうかが勝負ですが、席の予約競争に敗れて4列シートの内側だったのもあって全然眠れませんでした。。 時間 説明 羽田空港に到着 11/30 13:00 絶対に遅刻できないので早めに空港に行っておきます。JTBの集合時間までマイナビメンバーでだらだらしてました。 JTB指定の場所に集合、チェックイン 11/30 15:05 JTB指定の場所に集合して、チケットの控えなどもらいます。そのまま流れでチェックインしました 羽田空港発 11/30 18:05 ほぼ定刻通りに出発!機内では事前にダウンロードしたネトフリの映画見てました。機内食は2回出ました。 サンフランシスコ空港着 11/30 10:15(日本時間12/1 03:15) 無事到着!イスが固くてお尻が痛いです、、ここから乗り継ぎでしばらく空港で待機します サンフランシスコ空港発 11/30 17:15 予定より30分遅れて飛行機が出発。さっき乗ったJAL機より今回のアラスカ航空の飛行機の方がイスが良い気がする ラスベガス空港着 11/30 19:00 空港内にスロットがあって、ラスベガスって感じです ホテル着 11/30 20:00 空港からホテルまではJTBのバスで移動します 機内食1 機内食2 サンフランシスコ入国審査 夕焼け ラスベガス空港 ラスベガス滞在中 1~4日目のスケジュールの例です 時間 説明 起床 7:30 時差ボケで浅い眠りだった気がしますが、起床 Venetianに移動 8:30 複数ある会場のホテルのうち、本日はVenetianに滞在します。宿泊しているホテルから10分くらい歩いて移動します 朝食 8:45 各会場では朝食と昼食を食べることができます。ビュッフェ形式です。 breakout sessionに参加 10:00 1時間のbreakout sessionに参加します EXPOをぶらぶら 11:00 中途半端に時間が空いたので、EXPOをぶらぶらします 昼食 12:00 昼食もビュッフェ形式です workshopに参加 13:00 2時間のworkshopに参加します 休憩 15:00 会場内はコーヒーと軽食・おやつが置いてあって食べ放題です builder's session 15:30 1時間のbuilder's sessionに参加します breakout session 17:00 1時間のbreakout sessionに参加します KIROのお化け屋敷(?)に行ってみる 18:00 なんかKIROのお化け屋敷みたいなのがあったので並んで入ってみます 19:00 ラスベガス探索 帰りがてら、周辺をぶらぶら歩いてみます 20:00 夕食 ハンバーガーショップで夜ごはんを食べます 21:00 ホテル到着 ホテルに到着します 24:00 就寝 明日に備えて寝ます 朝食・昼食の雰囲気 コーヒー 軽食・おやつ ラスベガスの街並み 日本帰国 早朝というか深夜に出発なので、re:Play(最終日に夜通しやっているイベント)に参加したらほとんど寝る時間なくなっちゃいます。。 でも、帰りはもう残りの体力について考えなくて良いので気持ちは楽でした。 時間 説明 ホテル出発 12/5 03:50 ホテルの受付あたりで集合 ラスベガス空港発 12/5 07:00 帰りもアラスカ航空 サンフランシスコ着 12/5 08:50 乗り継ぎで3時間ほど待機します サンフランシスコ発 12/5 12:50 アメリカとお別れです 羽田空港着 12/6 17:20(ラスベガス時間 12/6 0:20 ) 行きよりも2時間くらい長くて11時間くらいのフライトでした。帰りも2回機内食が出ました よく分かんない機械で検査されます めっちゃ砂漠 機内食 セッションなどについて 会場にいる時間の大部分はセッションを受けていました。 事前に予約しているセッションを軸にしながら、当日参加のセッションも詰め込んでいく感じです。 また、セッションの予約に関してはO.K.さんがまとめてくださっているので、 そちら もご確認ください! セッション以外の展示 セッション以外にもいろいろな展示があって、それをみているだけでもかなりの時間を取られてしまいます! EXPO AWSに関連した様々な企業の展示ブースがあります。 ブースではバッジやシールなどが配られているので、それを集めてお土産にしても良いかもです。 時間帯によっては軽食・おやつなども置いてあります。 KIROのお化け屋敷 KIROのお化け屋敷みたいなのがありました。 音で驚かすとかはなく、メカめかしい雰囲気でたまにKIROの説明が書かれたモニターがあるみたいな感じです。 外観 滑り台 re:Invent名物(?)のDATADOGの滑り台にも行ってきました! 横から 正面から Self-placed Training 開いた時間にBuilders Labsなどで学習を行うことができるエリアとかもありました。 場所の雰囲気 モニター re:Play 最終日の12/4にはre:Playという大規模なパーティ(?)イベントが行われます! DJやバンドの演奏、滑り台などのアトラクションやゲーム、食事・飲み物などが提供されてお祭り騒ぎって感じです。 ただ、ここで調子のって騒ぎすぎるとそのまま寝ずに長時間にフライトとなってしんどいので注意が必要です。 ブランコ DJ その他 開いた時間にいろいろ見てきました! サブウェイ 調子乗ってサブウェイ行ってみました! 全然注文の仕方が分からなくてめちゃくちゃ妥協したサンドイッチが出来上がりました、、 関係ないけど、英語が分からないのかサブウェイの注文が分からないのかっていうのは、cdkが分からないのかawsが分からないのかっていうのと同じ構造だと思いません? スタバ ラスベガスには日本以上にあちらこちらにスタバがありました。 せっかくなので注文してみました。 意外とサイズは大きくない(そして値段は高い)。 N.Yさんが「オススメで」って注文して抹茶ラテを飲んでました、絶対にそのチョイスは間違っている。 謎のプリクラ awsとは多分関係ないですが、会場のホテルの入り口近くにプリクラみたいなのがあったので撮ってみました($10)(ソロ) 外観 内観 おわりに 5日間、エンジニアとして、あるいは人間として、とても貴重な体験をさせていただきました。 決して少なくない金額を出していただいた会社、いない期間の仕事の調整に協力していただいた皆様、参加を許可していただいた上長の皆様にはこの場を借りてあらためて感謝させてください。ありがとうございました。 今後より一層僕が仕事に励むことによって、re:Inventへの投資が価値あるものだと思ってもらえるように頑張ります! ここまで記事をご覧いただいていただきありがとうございました。 もう何本か記事を書くと思うので、そちらもぜひどうぞ!
アバター
本記事は【 Advent Calendar 2025 】の19日目の記事です。 はじめに マイナビの2年目データエンジニアのItです 今回はSnowflakeデータベースに蓄積されるデータ(ACCOUNT_USAGEスキーマなど)を用いて、コストの可視化をしてみました。 背景 Snowflakeの利用者(主にビジネスサイドの人たち)に対してコストの可視化をし、コストの透明化を図りたいと考えていました。 「コストの可視化、そんなもんSnowsightで見ればいいでしょ!」 と言われてしまいそうなので、現在の条件を記載します。 マイナビはデータ利用者が denodo(仮想統合基盤) を経由してSnowflakeにアクセスします(マイナビの社員はdenodoのユーザアカウントを保持) 裏側の処理として、denodoは1つのSnowflakeのユーザアカウントでデータにアクセスします 利用者はSnowflakeのユーザアカウントを保持しないため、Snowsightにアクセスできません(もちろんコスト管理も確認できません) タグの作成 Snowflakeのタグ はKey-Value型で作成できます。今回はCOSTというキーに対して、部署名をバリューとして作成します。 --今回はTAG用のデータベースとスキーマを用意していますUSE DATABASE TAG;USE SCHEMA TAGS;CREATE TAG IF NOT EXISTS COST ALLOWED_VALUES 'ORG-1', 'ORG-2', 'ORG-3'; --今回はTAG用のデータベースとスキーマを用意しています USE DATABASE TAG; USE SCHEMA TAGS; CREATE TAG IF NOT EXISTS COST ALLOWED_VALUES ' ORG-1 ' , ' ORG-2 ' , ' ORG-3 ' ; タグの付与 ウェアハウスやスキーマに作成したタグを付与することにより、タグごとでコスト管理をすることができるようになります。 ALTER SCHEMA IF EXISTS DB-a.SCHEMA-1-A SET TAG TAG.TAGS.COST = 'ORG-1';ALTER SCHEMA IF EXISTS DB-a.SCHEMA-1-B SET TAG TAG.TAGS.COST = 'ORG-1';ALTER WAREHOUSE IF EXISTS WH-1-A SET TAG TAG.TAGS.COST = 'ORG-1';ALTER WAREHOUSE IF EXISTS WH-1-B SET TAG TAG.TAGS.COST = 'ORG-1'; ALTER SCHEMA IF EXISTS DB - a.SCHEMA - 1 - A SET TAG TAG.TAGS.COST = ' ORG-1 ' ; ALTER SCHEMA IF EXISTS DB - a.SCHEMA - 1 - B SET TAG TAG.TAGS.COST = ' ORG-1 ' ; ALTER WAREHOUSE IF EXISTS WH - 1 - A SET TAG TAG.TAGS.COST = ' ORG-1 ' ; ALTER WAREHOUSE IF EXISTS WH - 1 - B SET TAG TAG.TAGS.COST = ' ORG-1 ' ; SQLでの確認 利用するデータの確認 BIツールでの可視化の前に、公式ドキュメントを参考にどのようなテーブルが必要かを調べ、SQLでたたき台を作ります。以下のように、ウェアハウスやスキーマの情報とタグの情報をそれぞれ取得し、結合して出力します。 ウェアハウスの情報を WAREHOUSE_METERING_HISTORY ​から取得 スキーマの情報を SCHEMATA ​から取得 タグ情報を TAG_REFERENCES ​から取得 WITH schema_cost AS ( SELECT SCHEMA_ID, SUM(CREDITS_USED_CLOUD_SERVICES) AS TOTAL_CREDITS FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY WHERE START_TIME >= 'YYYY-MM-DD' AND END_TIME < 'YYYY-MM-DD' GROUP BY SCHEMA_ID),schema_info AS ( SELECT SCHEMA_ID, SCHEMA_NAME, CATALOG_NAME FROM SNOWFLAKE.ACCOUNT_USAGE.SCHEMATA),schema_tags AS ( SELECT OBJECT_ID, TAG_NAME, TAG_VALUE FROM SNOWFLAKE.ACCOUNT_USAGE.TAG_REFERENCES WHERE DOMAIN = 'SCHEMA'),warehouse_cost AS ( SELECT WAREHOUSE_ID, WAREHOUSE_NAME, SUM(CREDITS_USED) AS TOTAL_CREDITS FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY WHERE START_TIME >= 'YYYY-MM-DD' AND END_TIME < 'YYYY-MM-DD' GROUP BY WAREHOUSE_ID, WAREHOUSE_NAME),warehouse_tags AS ( SELECT OBJECT_ID, TAG_NAME, TAG_VALUE FROM SNOWFLAKE.ACCOUNT_USAGE.TAG_REFERENCES WHERE DOMAIN = 'WAREHOUSE')SELECT st.TAG_VALUE, 'SCHEMA' AS OBJECT_TYPE, SUM(sc.TOTAL_CREDITS) AS CREDITSFROM schema_cost scJOIN schema_info si ON sc.SCHEMA_ID = si.SCHEMA_IDJOIN schema_tags st ON sc.SCHEMA_ID = st.OBJECT_IDGROUP BY 1,2UNION ALLSELECT wt.TAG_VALUE, 'WAREHOUSE' AS OBJECT_TYPE, SUM(wc.TOTAL_CREDITS) AS CREDITSFROM warehouse_cost wcJOIN warehouse_tags wt ON wc.WAREHOUSE_ID = wt.OBJECT_IDGROUP BY 1,2ORDER BY 1,2; WITH schema_cost AS ( SELECT SCHEMA_ID, SUM (CREDITS_USED_CLOUD_SERVICES) AS TOTAL_CREDITS FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY WHERE START_TIME >= ' YYYY-MM-DD ' AND END_TIME < ' YYYY-MM-DD ' GROUP BY SCHEMA_ID ), schema_info AS ( SELECT SCHEMA_ID, SCHEMA_NAME, CATALOG_NAME FROM SNOWFLAKE.ACCOUNT_USAGE.SCHEMATA ), schema_tags AS ( SELECT OBJECT_ID, TAG_NAME, TAG_VALUE FROM SNOWFLAKE.ACCOUNT_USAGE.TAG_REFERENCES WHERE DOMAIN = ' SCHEMA ' ), warehouse_cost AS ( SELECT WAREHOUSE_ID, WAREHOUSE_NAME, SUM (CREDITS_USED) AS TOTAL_CREDITS FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY WHERE START_TIME >= ' YYYY-MM-DD ' AND END_TIME < ' YYYY-MM-DD ' GROUP BY WAREHOUSE_ID, WAREHOUSE_NAME ), warehouse_tags AS ( SELECT OBJECT_ID, TAG_NAME, TAG_VALUE FROM SNOWFLAKE.ACCOUNT_USAGE.TAG_REFERENCES WHERE DOMAIN = ' WAREHOUSE ' ) SELECT st.TAG_VALUE, ' SCHEMA ' AS OBJECT_TYPE, SUM (sc.TOTAL_CREDITS) AS CREDITS FROM schema_cost sc JOIN schema_info si ON sc.SCHEMA_ID = si.SCHEMA_ID JOIN schema_tags st ON sc.SCHEMA_ID = st.OBJECT_ID GROUP BY 1 , 2 UNION ALL SELECT wt.TAG_VALUE, ' WAREHOUSE ' AS OBJECT_TYPE, SUM (wc.TOTAL_CREDITS) AS CREDITS FROM warehouse_cost wc JOIN warehouse_tags wt ON wc.WAREHOUSE_ID = wt.OBJECT_ID GROUP BY 1 , 2 ORDER BY 1 , 2 ; SQL実行結果の確認 SQL実行結果の値がSnowsightのコスト管理の消費クレジットに対して大きな誤差がないかを確認します。 Snowsightのコスト管理のキャプチャは載せていませんが、誤差は数クレジットでした。そのため、今回のSQLで問題ないと判断しました。 誤差が生じる要因:WAREHOUSE_METERING_HISTORYテーブルのCREDITS_USED CREDITS_USEDはCREDITS_USED_COMPUTEとCREDITS_USED_CLOUD_SERVICESの合計であり、クラウドサービスの調整を考慮していないためです。(請求されるクレジットよりも大きくなる場合があります) https://docs.snowflake.com/ja/sql-reference/account-usage/warehouse_metering_history BIツールでの可視化 今回はTableau Prep Builderを用いてSQLで行ったことと同様の条件でデータ加工し、Tableau Desktopでダッシュボードを作りました。(詳細は省きます) ※ダッシュボード内の凡例について、厳密にはSCHEMAはストレージコストのみでもなく、WAREHOUSEもコンピューティングコストのみではありません まとめ Snowsightでコスト管理のほかにもガバナンス(Snowflakeのユーザやロール)やセキュリティ(トラストセンターやネットワークポリシー)なども確認できるため、SQLを書くことがないかもしれません。しかし、Snowflakeデータベースに蓄積されるデータを直接参照したいときも少なからずあると思います。 SnowsightのUIへの反映に数分かかるのでSQLで直ぐ確認したいときや、今回のようなBIツールで可視化したいといったときにはSQLやBIツールからSnowflakeデータベースを参照することになります。そういった際に少しでもACCOUNT_USAGEなどのSnowflakeデータベースについて知っておくと便利だと思います! Snowflakeを利用している方はぜひSQLでSnowflake内のデータを探索してみましょう 番外編:今回の開発における気付き こっそり、コストダッシュボードの開発した際の気付きについて残しておきます タグの粒度をシステム単位やテーブル単位のように小さくすることも検討したい ユーザにクレジット消費量の意識づけをし、より適切なコスト削減対策につながるのでは 使ってもらえるダッシュボードにするための工夫(定期的な周知など)も必要ですが… ユーザに対して、頻繁に使われているテーブルの紹介もできそう AWSのインフラ利用費などもSnowflakeのクレジット消費量に基づいた計算をしたい Tableau Exchangeに Tableau公式のSnowflakeコストダッシュボード がありました 実はダッシュボードをSharePointサイトに埋め込むためにPowerBIで作る予定でした…
アバター
本記事は【 Advent Calendar 2025 】の18日目の記事です。 こんにちは。マイナビでデータサイエンティストをしているH・Yです。 2022年に新卒入社し、AI戦略室に所属しております。 データサイエンティストと聞くと、皆さんはどのような業務を想像するでしょうか? 機械学習のモデルを構築するためにデータとにらめっこしている 生成AIを駆使してAIエージェントを開発している など、技術を駆使した何かしらの構築・運用をしているイメージを持つ方が多いと思います。 今回は少し毛色が異なる「AIガバナンス」に関する業務を紹介します。 AI戦略室では、社内での生成AIの活用を安全かつ効果的に進めるための「生成AI利用ガイドライン」を整備・展開しています。 私は最近の業務で、そのガイドラインのアップデートを担当しました。 この記事では、 なぜ生成AI利用ガイドラインの更新が必要だったのか なぜその役割をデータサイエンティストが担うべきだと思ったのか ガイドライン更新において意識した点 について、振り返りを兼ねて共有できればと思います。 なぜ生成AI利用ガイドラインの更新が必要だったのか ガイドラインを更新した背景には、大きく2つの理由がありました。 理由1:昨今の早すぎる技術進歩に適応するため ここ2〜3年のAI領域の進歩は、従来の「年単位の変化」の感覚とはまったく異なります。 大規模言語モデルの性能向上と多様化(汎用モデルから特化モデルまで) 画像・動画・音声・コードなど、生成対象のマルチモーダル化 プロンプトベースの利用から、ツール連携・エージェント化へのシフト クラウドサービスやSaaSとしての提供により、誰でもすぐに利用可能になったこと といった変化が、 数ヶ月単位 で起きています。 このスピードに対して、旧来のガイドラインは、 想定している利用パターンが古い カバーできていないリスクが増えてきた 実態に合わない制約がボトルネックになりつつある という状態になっていました。 そこで、「いま現場で実際に起きていること」「これから数年の技術トレンド」を踏まえたうえで、 技術進歩に追いつきつつ、今後も耐えられる前提条件を再定義する必要がありました。 理由2:現場社員の関心・問い合わせに応えるため もう1つの大きな理由は、現場からの熱量の高い声です。 IT部門のエンジニアだけでなく、営業職・制作職(デザイナー・ライターなど)・マーケティング部門など様々な部門から、「この生成AIツールを使ってみたい」「この技術を使えば、こういう業務が効率化できそう」といった相談や問い合わせが、短期間で一気に増えました。 この状況でガイドラインが不十分だと、「結局どこまでやっていいのか分からない」「グレーならやめておこう」とせっかくのアイデアが止まってしまいます。 また一方で、「誰にも聞かずにとりあえず使ってみる」といったリスクの高いケースも発生しかねません。 現場の生成AIへの関心の高さに応えるために、ガイドラインをアップデートすることが必要でした。 なぜデータサイエンティストがガイドライン更新を担当したのか 「ガイドラインの更新って、コンプライアンス部門や法務の仕事では?」という声もあるかもしれません。 もちろん、最終的なリーガルチェックやリスク判断は法務をはじめとした専門部署の方々に担っていただいています。 一方で、「現場で本当に運用できるガイドライン」を設計するためには、実際に生成AIを業務で使っている・推進しているチームの感覚や、日々の試行錯誤を深く理解している人間が設計の中心にいた方が、よいと私は考えます。 データサイエンティストとして日々、 社内外のさまざまな業務に対してPoCやAI導入の相談を受ける 「どのタスクに、どの程度までAIを任せられるか」を一緒に設計する 実際に生成AIを組み込んだワークフローを動かし、運用上のつまずきを目の当たりにする という経験を積む中で、 「ここにルールがない・規制されたままだから、みんな困っている」 「この書き方だと、現場は多分こう解釈してしまう」 といった 解像度の高い現場感 を、比較的早い段階で察知できます。 今回のアップデートでは、こうした現場感覚を起点に、 技術的な実態・運用上のボトルネック それに対するリスクや法的観点 を整理したうえで、「運用と整合する実践的なルール」に落とし込むことを重視しました。 その橋渡し役となることが、データサイエンティストがガイドライン更新を担当する理由だと思います。 ガイドライン更新において意識した点 今回のアップデートでは特に2点を意識しました。 1. 原則の策定 今回のガイドラインでは、まず最初に「原則」をつくるところから始めました。 原則とは、生成AIを安全かつ有効に活用するための、最上位の判断基準です。 シンプルに10個の原則を設定し、企業としてのAI倫理=社会的に望ましい行動を選択し続けるための基準としました。 次のようなねらいがあります。 方針の一貫性と変化耐性の担保 個別事例の変化や生成AIの技術進歩があったとしても、判断のベースラインがぶれないような共通解釈を持てるようにするため 現場社員の自律的で迅速な判断の支援 様々な社員がそれぞれの業務を行う中で、具体的な手順ではカバーしきれないグレーなケースにおいても、現場社員が適切な判断を行えるようにするため ガイドラインの要約 難解な法律・制約を含むガイドライン本文を、現場での運用に落とし込める価値判断基準に置き換えるため(社内外に説明できる状態を確保するため) このように、「ガイドライン=ただ行動を制限するもの」ではなく、「 現場社員のコンパス 」として位置づけることを意識しました。 2. 利用範囲の軸設計 もう1つ重視したのが、利用範囲の軸設計です。 生成AIの活用領域が急速に拡大する一方で、生成AIを活用する際に起きうるリスクは利用場面ごとに大きく異なります。 全てのケースにおいて一律のルールでは、十分に安全かつ効率的な運用は難しいと思います。 この現実に対応するため、以下のように生成AI利用マトリクスを作成しました。 職種・担当業務別 これまで当該の業務を遂行してきた実績があり、生成AIによる生成物に対しても品質評価や制約判断が可能である範囲での利用と定めました。 公開先・用途別 生成AIによる生成物が社外公開されるか・コーポレートイメージに直結する用途か・社内利用のみかといったケース別に分類しました。 これによって、 リスクと価値創出の最適化 を図ることを目指しています。 おわりに 生成AIやAIエージェントの技術は、これからもしばらくは「落ち着くことなく変わり続ける」領域だと思います。 その中で、今回のガイドライン更新は、完成形をつくるためのゴールではなく、むしろ 「変化が当たり前の前提で、どうやって安全かつ攻めの利活用を続けていくか」 を探るためのスタートラインに近いと感じています。 ガイドラインは、紙の上でどれだけきれいに整っていても、 現場で読まれない 使われない 相談フローが機能しない のであれば、意味がありません。 今回のアップデートをきっかけに、 現場からの相談やフィードバックがもっと集まりやすくなること 「このケースってどうですか?」という対話を通じて、ガイドライン自体も育っていくこと AI戦略室が、より実務に寄り添った伴走役として機能していくこと を目指していこうと思います。
アバター
本記事は【 Advent Calendar 2025 】の17日目の記事です。 こんにちは!オペレーション開発部開発課のT・Uです。 今回はWorkatoの機能の1つである Workbot について紹介します! はじめに Workatoとは? さまざまアプリケーション(Excel、SharepointなどのMicrosoft製品やBox、Salesforceなど)を連携させて業務を自動化できる、iPaaS(Integration Platform as a Service)と呼ばれる統合プラットフォームです。 豊富なコネクションを利用して、上記で列挙したようなアプリケーションと接続して、ワークフローを構築・自動化できます。 ※Wokratoで自動化できるのはAPIで実現できる範囲なので、APIの機能が備わってるアプリケーションであることが前提となります。 Workbotとは? Workatoの機能の1つで、SlackやMicrosoft Teamsなどのコミュニケーションツールで利用できる、カスタマイズ可能なチャットボットプラットフォームです。 ユーザーはチャットボットを通じてWorkatoのワークフローを直接実行し、様々なビジネスアプリケーションからデータを取得、作成、更新することが可能です。これにより、複数のアプリケーションを切り替えることなく、単一のインターフェースで業務を効率化できます。 以上はざっくりとした概要になりますので、興味がある方は下記サイトから詳しい概要についてご覧ください。 Workatoについて Workbotについて 実際に作成してみる この章では、Workbotの作成画面についてお見せしていきたいと思います。 具体的なユースケースについては、次の章にて紹介します。 設定画面 この設定画面はBotを呼び出すときに必要なものとなります。 主な設定項目について Command name:Botを呼び出すときに必要なコマンド Command hint:Botの概要 Parameters:ユーザーに入力させる項目の設定 Text、Textarea、Selectの3つから選択できて、ユースケースに沿うような項目に設定できます。 今回は、 ユーザー名、年齢、所属部署 を質問項目にしてみました。 呼び出し方法 Botをチームに招待しておくなど、事前準備は必要ですが、呼び出すこと自体はとても簡単です。 Botを招待しておいてあるチームにて、 「メンション+作成したコマンド」 のメッセージを送るだけ! 実際に呼んでみると このような返信がくるので、Nextボタンをクリックすると 先程設定した項目が、質問事項として現れました! (分かりにくいですが、所属部署の箇所はプルダウンになっています) 後は、質問に答えてSubmitボタンを押すだけです! Submitを押した後について Submitボタンをクリックすると、回答内容がWorkato内に送信されて、データピルと呼ばれる引数となって後続の処理で使用できるようになります。 Workbotでは、「メッセージに返信する」といったアクションもあるので、今回はそれを用いて「回答内容を返信する」といったことを実装してみます。 このようにテキスト形式で名前を表示させるような設定ができます。 ユーザー名|Step1 となっている部分が先ほど記載したデータピルで、中身は「テスト太郎」になっています。 他の項目についても同様に設定して、実装すると このように返信がきました! 他にもさまざまな返信スタイルにできます。例えば、 メンション付きで返信 画像やボタン付きで返信 追加で質問を行う 他のBotを呼び出す などなど…列挙したらキリがないです。 (興味ある方は、 Workbot詳細設定 をご覧ください) 実装したBotについて この章では、弊課で利用しているユースケースについて紹介します。 ユースケースは下記のとおりです。 Teamsの新規チームを作成するBot Botに対して、 ・追加するメンバーのメールアドレス ・チーム名 ・チームの説明 を回答して、開発課メンバー+追加する人のチームを作成する さらにこのとき、「開発課」というタグが設定されているようにする 早速Botを呼び出してみます! ※外部サイトに公開する関係で一部情報を伏せています ※存在しないユーザーで登録しようとしているので、本来はエラーになりますが、デモということでご了承ください ユースケースに記載した項目について答えるよう要求されるので、項目を埋めてSubmitを押すと メンション付き で、確認するよう通知してくれます。 (このように承認のフェーズをはさむことも可能となります。) 「チーム情報」をクリックすると、 チーム情報の詳細を確認することができます。 情報を見て問題がないのを確認できたので、 「承認」 を押してみると 処理完了の旨を通知してくれます。さらに「新規チームはこちら」というボタンを押してみると チームの画面に遷移しました!きちんとチームが作成されています! タグを確認すると タグについても問題なく作成されています! ちなみに、確認のフェーズで 「中断」 を選択すると という通知をしてくれます。 終わりに 簡単ではありますが、以上がWorkbotの紹介となります。 Workbotの便利性を伝えられていれば幸いです。 Workbotですが、他にも様々なケースで活躍できると考えております。 一部他社様の事例もありますが、例として 案件内容をBotが質問→Backlogで課題のチケットを作成する 他の自動化の処理と組み合わせて、Botで承認フェーズを再現する(このとき承認できる人のデータを別で用意しておけば、承認者の制限もできます) など挙げられます。こちらについても列挙したらキリがなさそうです。 この記事を読んで、興味を持ってくださる方がいたら幸いです。 最後までお読みいただきありがとうございました! イベント告知 12月23日にイベントを開催します!申し込みはこちらから▼ https://mynaviit.connpass.com/event/376769
アバター
本記事は【 Advent Calendar 2025 】の16日目の記事です。 こんにちは!AI戦略室 AIソリューション1課 新卒のS.Hです! 2025年に買ってよかった本を紹介したいと思います。 あくまで個人的な感想ですので、参考程度に読んでいただければ幸いです。 タイトル 値段 ジャンル 1. ソフトウェアエンジニアガイドブック ―世界基準エンジニアの成功戦略ロードマップ 4,180円 ビジネス 2. Airbnb Story 大胆なアイデアを生み、困難を乗り越え、超人気サービスをつくる方法 2,465円 ビジネス 3. (模擬問題付き)徹底攻略 AWS認定 ソリューションアーキテクト − アソシエイト教科書 第3版[SAA-C03]対応 2,860円 クラウド 4. 基礎から学ぶ統計学 3,520円 統計 5. 改訂新版[エンジニアのための]データ分析基盤入門<基本編> データ活用を促進する! プラットフォーム&データ品質の考え方 3,300円 データ 6. ゼロから作るDeep Learning ❺ ―生成モデル編 3,960円 AI 7. Pythonで動かしながら学ぶ コンピュータネットワーク (KS情報科学専門書) 4,180円 ネットワーク ソフトウェアエンジニアガイドブック ―世界基準エンジニアの成功戦略ロードマップ 購入のきっかけ 1on1でキャリアパスついて聞かれたときに、「将来どうなりたいか」についてうまく答えられませんでした。エンジニアにどのような選択肢があるの、キャリアを進む中でどのような行動が必要なのかを体系的に理解したいと思い、購入しました。 内容 「キャリアパス」「役割ごとに求められるスキル」「学習方法」など、エンジニアが歩むべき道や成長に必要な要素を体系的に解説している本です。 読んでみて アメリカのテック企業を前提としているため、日本企業の組織構造とは合わない部分も多少あります。しかし、エンジニアが成長するための「心構え」や「行動指針」など、ためになることがたくさんありました。初心者から上級者まで全員におすすめできる一冊です。 Airbnb Story 大胆なアイデアを生み、困難を乗り越え、超人気サービスをつくる方法 購入のきっかけ 技術的なスキルだけでなく、「なぜそのプロダクトが成功したのか」というビジネス的な視点も身につけたいと思い購入しました。 内容 Airbnbの創業から現在に至るまでの軌跡を描いたノンフィクションです。 創業者たちが資金難に苦しみながらシリアルで食いつなぎ、投資家から何度も断られながらもアイデアを磨き続けた過程が詳細に描かれています。 読んでみて ユーザーが「なぜサービスを利用するのか」「どんな体験があればリピートしたいと思うのか」を徹底的に分析し、着実にニーズを掴んでいく様子が印象的です。 (模擬問題付き)徹底攻略 AWS認定 ソリューションアーキテクト − アソシエイト教科書 第3版[SAA-C03]対応 購入のきっかけ AI戦略室でもクラウドを多用してサービス開発を行うため、体系的にAWSを学んでおきたいと思い購入しました。 内容 AWS認定ソリューションアーキテクト アソシエイト試験(SAA-C03)に対応した教科書です。EC2、RDS、S3などの基本サービスから、VPC、Lambda、CloudFrontなど、実務でよく使うサービスを網羅的に解説しています。各章末には理解度を確認できる演習問題があり、巻末には本番形式の模擬問題も収録されています。 読んでみて 初心者に優しい内容で各サービスの説明が丁寧に書かれており、クラウドを体系的に学ぶ上でオススメの本です。ただし欠点として、各サービスのより具体的な説明やユースケースが不足しているため、実務に即した問題が出題されるSAA試験の合格は難しいかもしれません。 基礎から学ぶ統計学 購入のきっかけ AI・機械学習のプロジェクトに関わる中で、データ分析の結果を正しく解釈したり、モデルの評価指標を適切に理解したりするには、統計の基礎が不可欠だと感じ購入しました 内容 統計学の基礎を体系的に学べる教科書です。記述統計(平均、分散、標準偏差など)から始まり、確率分布、推定、検定、回帰分析まで、統計学の主要なトピックを網羅しています。数式だけでなく、図やグラフを使った視覚的な説明が豊富で、各章末には理解度を確認できる演習問題も用意されています。 読んでみて 統計学を基礎から学びたい初学者、数学が苦手だけど統計を理解したいエンジニアにおすすめです。データサイエンスや機械学習には記述統計や確率分布が必ず使われるため、土台を学ぶことができます。 改訂新版[エンジニアのための]データ分析基盤入門<基本編> データ活用を促進する! プラットフォーム&データ品質の考え方 購入のきっかけ データはAIの学習や分析の土台となります。その背景知識を理解し、より高品質なデータを収集することで、高性能なAIモデルの構築や精度の高い分析が可能になると考えました。特に、Extract(抽出)・Transform(変換)・Load(ロード)といったデータパイプラインの基礎を体系的に学びたいと思い、購入しました。 内容 データ分析基盤の設計・構築に必要な知識を体系的に解説した入門書です。データレイク、データウェアハウス、データマートといった基本概念から、ETL/ELTパイプラインの設計、データ品質管理、メタデータ管理まで幅広くカバーしています。 読んでみて データ品質の重要性やデータガバナンスの考え方など、技術面だけでなく運用面の課題についても触れられているが特に印象に残りました。またETLとELTの違いや、バッチ処理とストリーム処理の使い分けなど、基本的な知識が身に付きます。 ゼロから作るDeep Learning ❺ ―生成モデル編 購入のきっかけ 最新の画像生成AIは拡散モデル(Diffusion Models)よばれるアーキテクチャが主流となっているため、その仕組みを理論だけでなく実装レベルで理解したいと思い購入しました。「ゼロから作る」シリーズは過去にも読んでおり、実装を通して学ぶスタイルが自分に合っていたことも決め手でした。 内容 生成モデルの基礎から最新の拡散モデルまでを段階的に学ぶことができます。正規分布、多次元正規分布、混合ガウスモデル、ニューラルネットワーク、変分オートエンコーダ(VAE)、拡散モデルという順に、ステップを踏んで理解を深めていける構成になっています。 読んでみて 実装を通して学ぶことで、拡散モデルの数式の意味や各ステップの役割が深く理解できました。特に、ノイズを段階的に除去していくプロセスを実装することで、「なぜこのアルゴリズムが機能するのか」を理解することができます。 ただし欠点として、text-to-imageやimage-to-imageといった条件付き生成には対応していません。扱っているのは主に無条件の画像生成で、実務で使われているStable DiffusionやMidjourneyのようなテキストプロンプトからの生成については触れられていません。また、全体的にボリュームが少なく感じられ、もう少し応用的な内容や最新モデルの解説があっても良かったと思います。 Pythonで動かしながら学ぶ コンピュータネットワーク (KS情報科学専門書) 購入のきっかけ Webアプリケーション開発やクラウドを利用する中で、ネットワークの知識不足を感じる場面が増えてきました。HTTPやTCP/IPといった用語は知っているものの、実際にどのように通信が行われているのか、パケットレベルでの理解が曖昧だったため、実装を通して学べる本書を購入しました。 内容 コンピュータネットワークの基礎をPythonで実装しながら学べる技術書です。TCP/IP、UDP、HTTP、DNSといったプロトコルの仕組みを、Pythonを使って実際に動かしながら理解していきます。 読んでみて 実際にコードを書きながら学ぶことで、抽象的だったネットワークの概念が具体的にイメージできるようになりました。特に、TCPの3ウェイハンドシェイクやHTTPリクエストの送受信を自分で実装することで、インターネットの裏側を少し理解できた感覚がありました。 最後に 振り返ってみると、2025年は興味が発散しがちで、あれもこれもと手を出してしまった一年でした。 2026年は焦点を絞って、クラウド(特にGoogle Cloud)を中心に学んでいこうと思います。 イベント告知 12月23日にイベントを開催します!申し込みはこちらから▼ https://mynaviit.connpass.com/event/376769
アバター
本記事は【 Advent Calendar 2025 】の15日目の記事です。 はじめに ITディベロップ第2統括部ITディベロップ2部開発1課のH.Yです。 私は新卒2年目のエンジニアとして、少人数の開発チームを中心に、AIチームやインフラなど複数部門と連携するプロジェクトでPM(プロジェクトマネージャー)を任されました。 厳密には、最初は「PL(プロジェクトリーダー)をやりたい」と課長に相談し、了承をもらっていました。ところが、開発を進めるために必要なことをどんどん進めていたら、気づけばPMの役割を担っていました。 実際、統括PMからも「タスクをどんどん巻き取ってほしい」という意図があり、そのままPMとして動くことになった、という流れです。 初めての経験でしたが、「若手だからサポートしてもらえるだろうし、せっかくだから色々挑戦しよう」という気持ちで臨んでいたため、不安のようなネガティブな感情は特にありませんでした。 その分、プレイヤー脳のまま突っ走り、結果として多くの失敗を経験しました。 この記事では、その失敗と学びをまとめています。 同じく若手でPMを始めた人やPMをやりたい人の参考になれば幸いです。 プロジェクト体制 PO:要件定義、受け入れ基準の策定、仕様変更の意思決定 統括PM:全体の進行管理 PM(私):進捗管理、調整、スケジュール策定 開発チーム(5名) Yさん:業務委託ベテランエンジニア(FE見積もり担当) Tさん:フロントエンド実装担当 Hさん:フロントエンド実装担当 Dさん:バックエンド実装担当 Pさん:バックエンド実装+小タスク担当 他部署連携 AIチーム:DB作成、AI機能用のデータ整備、外部API作成 インフラチーム:デプロイ手順作成、インフラ構成変更対応 デザイナー:仕様変更に伴うデザイン修正 失敗談と学び 1. 情報共有は“全部”ではなく“行動可能な形”で 状況 ステークホルダーとの方針変更や重要なやり取りがSlackで発生した際、私はメンバー全員をメンションしてリンクを貼り、「これの把握お願いします」と連絡していました。 しかし、そのような連絡は膨大になり、結果として把握漏れが生じました。 原因 PMとメンバーが把握する情報の性質が異なる PM:浅く広く把握 メンバー:狭く深く把握 人には処理できる情報量に限界があるため、メンバーにPMと同じ情報量の把握を期待するのは間違い メンバー目線では「把握してください」と言われても、何をすればいいのか分からず、行動に結びつかない 対応 まだメンバーに対応してもらわない情報は共有しない PMの仕事は「メンバーが動ける状態に情報を整理すること」 現状・課題・完了条件・PMが考える手段・備考を整理し、Issueに落とし込む 学び メンバーが動けるように情報を変換するのがPMの仕事 PMが処理できる情報の深さに限度があるように、メンバーが処理できる情報の広さにも限度がある 2. スケジュール認識ズレで焦った話 状況 Yさんと進捗確認をした際、期限の認識が大きくズレていることが判明。 私の想定:期限は10月14日 Yさんの認識:期限はその2週間後 さらに、Yさんから「そのスケジュールではかなり無理がある」と指摘され、焦りました。 原因 統括PMが提示したスケジュールは「理想値」だった 私はそれを「絶対値」と誤解し、Slackで共有したが通知過多で見落とされていた 認識合わせを全員で行う仕組みがなかった プロジェクト理解が浅く、ガントチャートを作れる状態ではなかったため、「とりあえず進めれば見通しがつく」というプレイヤー的発想で動いていた 対応 Yさんと統括PMを交えて緊急ミーティングを実施。結果、スケジュールは調整可能であることが分かり、事なきを得ました。 学び ガントチャートを作るべきという理解はあったが、プロジェクト初期は情報不足で作れないこともある ガントチャートを作る能力は、プロジェクトの性質をもとにリスク要因を見抜く力に依存する 「とりあえず進める」だけでは不確実性が増すため、進めながらも認識合わせの仕組みを早期に作る 重要な認識合わせはSlackだけに頼らず、確実に確認できる仕組みを用意する 3. 要件の裏にある課題を理解することの重要性 状況 既存システムにない新しい機能の用途を勘違いし、POの想定とは異なる仕様で実装してしまったため、手戻りが生じました。 原因 POが定義した受け入れ基準に仕様が記載されていなかった 要件は理解したが、事業部の業務フロー上「なぜ必要なのか」を理解しないまま進めた 対応 「本当にその機能必要なの?」「より適切な課題解決手段はないのか?」と疑う意識を持つこと 学び 言われたからやるのではなく、自分の言葉で「なぜやるか」を説明できる程度に理解しないと手戻りが生じる可能性がある 4. PJ完了の定義を明確にすることの重要性 状況 プロジェクト序盤にPOに受け入れ基準を定義してもらい、仕様変更のたびに合意を取りながら更新していました。 しかし終盤ではPJ完了の定義が曖昧になり、関係者間で「何をもってプロジェクト終了なのか」が不明確になり、メンバーを不安にさせてしまいました。 原因 仕様変更が高頻度で発生し、受け入れ基準更新の優先度が下がった 「受け入れ基準を満たすものを作る」という合意があったため、基準が増えるほど負担が増える心理的ハードルがあった 対応 現状をPOに報告し、「受け入れ基準を最新化するのでレビューしてほしい」と依頼 合意形成を再度取り直し、受け入れ基準を最新化してプロジェクト完了条件を明確化 学び PJ完了の定義は、関係者全員が同じ認識を持てるように仕組み化する PMになりたい人へ プレイヤーの時もPMの時も、 自主性・課題解決能力・目的意識 といった能力はとても重要です。 ただし、PMになると、それらの能力を 活かす方法が大きく変わります 。 プレイヤーは「自分で成果を出す」ことに集中しますが、PMは「チームが成果を出せる状態を作る」ことが役割です。 PMとして働くのは簡単ではありません。調整や意思決定、リスク管理など、プレイヤー時代には経験しなかった難しさがあります。 しかし、その分、 刺激が多く、学びも大きい仕事です 。 この記事で紹介した私の失敗は、すべて成長の糧になりました。 もしあなたがPMに興味を持っているなら、ぜひ挑戦してみてください。
アバター
本記事は【 Advent Calendar 2025 】の14日目の記事です。 デジタルプラットフォーム統括本部データソリューション統括部データアーキテクト部アナリティクス推進課のA・Kです。 そういえば自分でも久しく忘れていたのですが、ミステリーや推理小説、叙述トリック物の小説が好きで昔よく読んでいました。タイトルで分かった方もいるかもしれませんが、好きな作家は工学博士の森博嗣先生です。 ということで、今回はBIツールTableauのIF文にまつわるミステリーを書こうと思います。 0. プロローグ Tableauには「計算フィールド」という概念があります。計算フィールドを使うと、定数・変数を交えた算術計算や文字列操作などの結果をフィールドに出力することができ、主に動的な出力を得たい場合に利用します。 例えば、顧客への商品販売履歴が記録されている以下のようなテーブルがあるとします。 注文ID 注文日 ユーザーID ユーザー名 都道府県 定価 購入額 2025-645614 2025/1/30 U-562596 鈴木 花子 富山県 40000 40000 2025-748109 2025/3/29 U-585788 吉田 直樹 新潟県 180000 180000 2025-388360 2025/4/18 U-577077 高橋 健 佐賀県 260000 208000 2025-355111 2025/5/3 U-750671 斎藤 真紀 青森県 250000 250000 2025-420588 2025/5/4 U-362222 中村 真由 宮城県 200000 200000 2025-290629 2025/5/16 U-013025 佐藤 太郎 沖縄県 130000 104000 2025-277039 2025/6/22 U-715673 渡辺 彩 茨城県 200000 40000 2025-864082 2025/7/28 U-577077 高橋 健 佐賀県 300000 300000 2025-973922 2025/9/30 U-909473 小林 誠 東京都 180000 180000 2025-091362 2025/11/22 U-446022 加藤 結衣 栃木県 30000 24000 このテーブルをTableauで読み込み、「どのくらいの割引率で購入してもらったのか」の列を追加して可視化する場合、以下の計算フィールドを作成し新たな列として追加することで実現できます。 割引率([定価] - [購入額]) / [定価] 1. 双頭の悪魔 まず、以下の記述をご覧ください。 IF SIZE() = 1 THEN (SUM([定価] - [購入額])) / SUM([定価])ELSE (SUM([定価] - [購入額])) / SUM([定価])END Tableauの計算フィールドにおけるIF文のフォーマット(今回のケースに相当するもの)は、以下のとおりです。 IF <条件> THEN <条件に合致する場合の出力>ELSE <条件に合致しない場合の出力>END また、SUM関数は指定したカラムの値をさまざまな粒度(全体、一部のグループにおいて、など)で合計することができます。 つまり、はじめに掲載した記述は、条件「SIZE() = 1」に合致する場合 (SUM([定価] - [購入額])) / SUM([定価]) (=複数レコードの割引率) を返し、条件に合致しない場合 (SUM([定価] - [購入額])) / SUM([定価]) を返します。 …って、え?これどっちの計算式も同じくない????? はい、そうです。この条件に合致する場合としない場合の計算式は、 まったく同じ計算式 です。 「分かった!これ本当はIF文を使わなくても実現できるけど、無理やりIF文にしてるだけだろ!」と予想した方がいるかもしれませんが、残念ながら今回のケースでは IFを使い条件分岐させないと正しい結果を得られません 。 5. ここでいきなりエピローグ …こうして作成した計算フィールドを使い、以下のような集計結果の表示を実現することができました。めでたしめでたし。 総計という行が追加されていることが分かると思います。総計はTableauがデフォルトで持つ集計機能で、各レコードをさまざまな粒度で合計したり平均したりすることができます。 今回は定価や購入額の平均単価、平均割引率を表示したかったので、「平均により集計を行った総計」を最終行に表示することにしています。 2. 真夏の総計式 さて、総計はTableauのデフォルトの機能とお伝えしましたが、実はトラップがあります。以下の表は、平均による総計機能を単純に適用したときの集計結果です。 一見どこがおかしいのか分かりにくいですが、前項で掲載したキャプチャとの違いは割引率の総計です。前項キャプチャでは13.8%だったのに対し、今回は14.0%となっています。これはどちらが正しいのでしょうか? ということで正しい計算をしてみます。割引率の平均とは、いわば全体に均した(ならした)ときの割引率にあたるので 割引金額の合計 ÷ 定価の合計 で算出することができます。実際に計算してみます。 割引金額の合計 = 244,000 定価の合計 = 1,770,000 割引金額の合計 ÷ 定価の合計 = 0.1378 ≒  13.8% ということで、正しい値は13.8%でした。え…?では、14.0%というのはどこから来た数値なのでしょうか? 実は、総計機能の平均は以下赤枠部分の平均を集計しています。 (0 + 0 + 20 + … + 20)÷ 10 =14.0 ですね。このように、総計機能は各レコードの値を単純に算術計算して集計を行うのですが、 割合の平均は算術平均で求めることができません 。これが総計のトラップです。 では、定価と購入額の集計には算術平均を使い、割合の平均を集計するときは別の計算を行うにはどうすればいいのでしょうか? 3. GYOTH あらためて、冒頭に出てきた計算式を再度見てみます。 IF SIZE() = 1 THEN (SUM([定価] - [購入額])) / SUM([定価])ELSE (SUM([定価] - [購入額])) / SUM([定価])END ここで気になるのが  SIZE() = 1  という条件です。SIZE() とはどのような関数なのでしょうか? 結論、 SIZE関数は 各レコードが属するパーティション内の行数を返します 。パーティションという単位がどこを表すかというと、以下の図のとおりです。 従って、各行に SIZE関数を適用すると、以下の値が返ってきます。 犯人が見えてきましたね。 4. イニシエーション・サム 従って、 割引率IF SIZE() = 1 THEN (SUM([定価] - [購入額])) / SUM([定価])ELSE (SUM([定価] - [購入額])) / SUM([定価])END この計算フィールドを作成することにより、SIZE() = 1 、つまり総計の行において各レコードの平均を計算せず (SUM([定価] - [購入額])) / SUM([定価]) を計算し、また SIZE() ≠ 1 、つまり総計以外のすべての行においても (SUM([定価] - [購入額])) / SUM([定価]) を計算することができるようになります。 ここで1つ、最後の疑問が残ります。「総計以外の行はすべて1行ごとに割引率を計算するんだから、わざわざSUMを使って合計する必要はないのでは?」 その答えは2つあります。1つは、そういう仕様だからです(SUMなど集計関数をつけないとエラーが出ます)。なんだそりゃ、と思う方もいるかもしれませんが、これは合理的な仕様です。その合理性が分かるもう1つの理由が、Tableauはデータ分析に使うツールなので、以下のように別の切り口でも分析してみたいというニーズがありえる点です。この場合、地域ごとに定価の合計、割引額の合計を算出するためにSUMを行う必要があるのは明白です。 5. エピローグ …こうして作成した計算フィールドを使い、以下のような集計結果の表示を実現することができました。めでたしめでたし。 6. あとがき ということで、Tableau IF文ミステリィでした。今回のケースは、実際に業務でのレポート作成中に直面した問題がベースになっています。私自身、IF文の条件に合う場合と合わない場合でまったく同じ計算式を使う、という経験がこれまでなかったので、このような方法で解決できたことが非常に興味深かったです。 Tableauはこのような形で計算フィールドを活用し、集計を行うことができます。このミステリーもどきでTableauに興味を持たれた方は、さまざまな機能や計算フィールドを使ってデータの可視化にチャレンジしてみてください!新たな発見があるかもしれません。 以上、ヴァン・ダインでした。 イベント告知 12月23日にイベントを開催します!申し込みはこちらから▼ https://mynaviit.connpass.com/event/376769
アバター
本記事は【 Advent Calendar 2025 】の13日目の記事です。 Autogen とは? Autogenは、AIを活用したマルチエージェントシステム構築のためのフレームワークです。 簡単に言うと AIエージェントを相互に会話させ、協働させ、タスクを自動的に解決させることができます。 1つのAIがすべてを行う代わりに、複数の専門化されたエージェントが存在します。 これらのエージェントは、複雑なワークフローを完了するために通信し、情報を共有し、協力して作業できます。 特徴 LLM統合 :GPTなどのモデルと連携します。 スケーラブルなアーキテクチャ :複雑な多段階プロセスを処理します。 カスタムエージェント :ニーズに合わせたエージェントを構築します。 歴史 Autogenは当初、Microsoftのみで開発されていました。 その後、AG2とバージョン0.4に分割されました。 AG2は全く異なるフレームワークであり、別個に管理されています。 ChatGPTやその他のコード生成エージェントはAG2向けのコードを提供します。 Autogenのコードを生成するようChatGPTに依頼しても正しいコードが得られないです。 AIから作成したAutogenのコード ユースケース ソフトウェア開発アシスタント エージェント: 1)コード生成エージェント(初期コードを生成) 2)コードレビューエージェント(バグとベストプラクティスをチェック) 3)ドキュメントエージェント(使用方法ドキュメントを作成) 目標 :開発を加速し、品質を維持する。 旅行計画 エージェント: 1)旅程プランナー(旅行の日ごとの旅程を作成します) 2)費用見積もり担当者(旅行のおおよその費用を計算します) 3)レビュー担当者(計画が現実的かつ実践的かどうかを確認する) 目標 :予算内で現実的な旅行計画を作成する。 コンテンツ制作 エージェント: 1)ライター(ブログや記事の草稿作成) 2)編集者(文法やトーンのチェック) 3)SEOスペシャリスト(キーワードの最適化) 目標 :高品質でSEO対策に優れたコンテンツを作成する。 データ分析と報告 エージェント: 1)データコレクター(APIからデータを取得) 2)アナリスト(インサイトとチャートを生成) 3)レポートライター(調査結果を平易な言葉で要約) 目標 :エンドツーエンドのレポート作成を自動化する。 APIキーについて OpenAI APIキーは数年前は無料でしたが、現在は無料ではありません。 推奨アプローチ Gemini APIキーを使用する( https://ai.google.dev/gemini-api/docs/api-key ) Ollamaを使用する(LLMモデルをローカルで実行するために使用) OpenRouter APIキーを使用する Ollama は自分の所属する会社ルールに従って利用して下さい。 一定期間無料で使用できるGemini APIキーまたはOpenRouter APIキーを使用するのが最適です。 私はGeminiキーを使用します。Geminiキーを使用する場合、以下のコードでAutogenを試すことができます Geminiにログインし、パーソナルアカウントを使用してAPIキーを生成してください。 重要なポイント Autogenでは非同期でエージェントへの呼び出しが行われる。Pythonライブラリで非同期コーディングを実現するためにAsyncioが使用される。 Autogenでは、各エージェントがどの分野の専門家であるか私たちが指定します 初めてのオートジェンエージェントを作成しましょう 手順 Autogen用の環境を作成する (Autogent用の環境の作成) python3 -m venv autogensource autogen/bin/activate requirements.txt ファイルを作成する (必要なパッケージをすべて1つのファイルで定義する) requirements.txt autogen-agentchatautogen-coreautogen-extasynciodotenvopenaitiktoken autogen - agentchat autogen - core autogen - ext asyncio dotenv openai tiktoken pip install -r requirements.txt GEMINI APIキーの環境変数を設定する(ご自身のAPIキーを追加してください) (APIキーの定義) .env GEMINI_API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" GEMINI_API_KEY = " xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx " 単一エージェントの作成 (質問に対して出力結果を返すシンプルな単一エージェントを作成する) agent.py import asynciofrom autogen_agentchat.agents import AssistantAgentfrom autogen_ext.models.openai import OpenAIChatCompletionClientfrom autogen_agentchat.messages import TextMessagefrom autogen_core.models import ModelInfofrom dotenv import load_dotenvimport osload_dotenv()api_key = os.getenv("GEMINI_API_KEY")model_client = OpenAIChatCompletionClient( model="gemini-2.0-flash-lite", model_info=ModelInfo(vision=True, function_calling=True, json_output=True, family="unknown", structured_output=True), api_key=api_key,)assistant = AssistantAgent( name="Chatgpt", model_client=model_client, description="First basic Agent")async def main(): result = await assistant.run(task="What is the capital of Japan?") print(result.messages[-1].content)asyncio.run(main()) import asyncio from autogen_agentchat . agents import AssistantAgent from autogen_ext . models . openai import OpenAIChatCompletionClient from autogen_agentchat . messages import TextMessage from autogen_core . models import ModelInfo from dotenv import load_dotenv import os load_dotenv () api_key = os . getenv ( " GEMINI_API_KEY " ) model_client = OpenAIChatCompletionClient ( model = " gemini-2.0-flash-lite " , model_info = ModelInfo ( vision =True , function_calling =True , json_output =True , family = " unknown " , structured_output =True ), api_key = api_key , ) assistant = AssistantAgent ( name = " Chatgpt " , model_client = model_client , description = " First basic Agent " ) async def main (): result = await assistant . run ( task = " What is the capital of Japan? " ) print ( result . messages [ - 1 ]. content ) asyncio . run ( main ()) python3 agent.py アウトプット マルチエージェントAIを作成しましょう (指定された予算と指定期間内で旅行の旅程を生成するマルチエージェントシステムを構築する。) itinerary_agent - 指定された都市と期間に対して、詳細な日ごとの旅行計画を作成します。 cost_agent - 旅程を受け取り、おおよその費用を計算します(交通費、食費、観光費、宿泊費)。 review_agent - 旅程と費用見積もりが現実的かどうかを確認する 三人のエージェントが互いに連携し、最終的に完璧な旅程を生成する multiAgent.py import asynciofrom autogen_ext.models.openai import OpenAIChatCompletionClientfrom autogen_agentchat.agents import AssistantAgentfrom autogen_agentchat.teams import RoundRobinGroupChatfrom autogen_agentchat.messages import TextMessagefrom autogen_core.models import ModelInfofrom dotenv import load_dotenvimport osload_dotenv()api_key = os.getenv("GEMINI_API_KEY")model_client = OpenAIChatCompletionClient( model="gemini-2.0-flash-lite", model_info=ModelInfo(vision=True, function_calling=True, json_output=True, family="unknown", structured_output=True), api_key=api_key)itinerary_agent = AssistantAgent( name="itinerary_agent", model_client=model_client, system_message=( "あなたは旅行日程の専門家です。 " "特定の都市と滞在日数が与えられた場合、詳細な日別旅行計画を作成してください。 " "時間割、訪問場所、交通手段の提案、およびその理由を含めてください。" ))cost_agent = AssistantAgent( name="cost_agent", model_client=model_client, system_message=( "あなたは旅行費用見積もり担当者です。" "旅程が与えられた場合、交通費、食費、観光費、宿泊費のおおよその費用を計算してください。 " "合計がユーザーの予算を超えた場合、より安価な変更案を提案してください。" ))review_agent = AssistantAgent( name="review_agent", model_client=model_client, system_message=( "旅程と費用見積もりを検証する審査員です。 " "計画が現実的か確認してください(移動時間、営業時間、順序)。 " "問題がある場合は、前の担当者に修正を依頼してください。" ))team = RoundRobinGroupChat( participants=[itinerary_agent, cost_agent, review_agent], max_turns=6)async def run_team(): user_task = TextMessage( content="京都への3日間の旅行を5万円以内で計画してください。予算内に収めつつ、楽しめるプランにしましょう。内容は200語以内に収めてください。", source="user") result = await team.run(task=user_task) for msg in result.messages: print(f"{msg.source}:\n{msg.content}\n")async def main(): await run_team()asyncio.run(main()) import asyncio from autogen_ext . models . openai import OpenAIChatCompletionClient from autogen_agentchat . agents import AssistantAgent from autogen_agentchat . teams import RoundRobinGroupChat from autogen_agentchat . messages import TextMessage from autogen_core . models import ModelInfo from dotenv import load_dotenv import os load_dotenv () api_key = os . getenv ( " GEMINI_API_KEY " ) model_client = OpenAIChatCompletionClient ( model = " gemini-2.0-flash-lite " , model_info = ModelInfo ( vision =True , function_calling =True , json_output =True , family = " unknown " , structured_output =True ), api_key = api_key ) itinerary_agent = AssistantAgent ( name = " itinerary_agent " , model_client = model_client , system_message = ( " あなたは旅行日程の専門家です。 " " 特定の都市と滞在日数が与えられた場合、詳細な日別旅行計画を作成してください。 " " 時間割、訪問場所、交通手段の提案、およびその理由を含めてください。 " ) ) cost_agent = AssistantAgent ( name = " cost_agent " , model_client = model_client , system_message = ( " あなたは旅行費用見積もり担当者です。 " " 旅程が与えられた場合、交通費、食費、観光費、宿泊費のおおよその費用を計算してください。 " " 合計がユーザーの予算を超えた場合、より安価な変更案を提案してください。 " ) ) review_agent = AssistantAgent ( name = " review_agent " , model_client = model_client , system_message = ( " 旅程と費用見積もりを検証する審査員です。 " " 計画が現実的か確認してください(移動時間、営業時間、順序)。 " " 問題がある場合は、前の担当者に修正を依頼してください。 " ) ) team = RoundRobinGroupChat ( participants = [ itinerary_agent , cost_agent , review_agent ], max_turns = 6 ) async def run_team (): user_task = TextMessage ( content = " 京都への3日間の旅行を5万円以内で計画してください。予算内に収めつつ、楽しめるプランにしましょう。内容は200語以内に収めてください。 " , source = " user " ) result = await team . run ( task = user_task ) for msg in result . messages : print ( f " { msg . source } : \n{ msg . content }\n " ) async def main (): await run_team () asyncio . run ( main ()) python3 multiAgent.py アウトプット user:京都への3日間の旅行を5万円以内で計画してください。予算内に収めつつ、楽しめるプランにしましょう。内容は200語以内に収めてください。itinerary_agent:承知いたしました。京都3日間5万円以内の旅程をご提案します。**1日目:*** **午前:** 伏見稲荷大社へ。朱色の鳥居が圧巻です。* **午後:** 清水寺周辺を散策。清水の舞台からの眺めを堪能し、二年坂・産寧坂でお土産探し。* **夕食:** 予算を考慮し、駅周辺の定食屋で。* **交通:** 市バス(1日乗車券利用がお得)**2日目:*** **午前:** 金閣寺へ。金箔が輝く姿は必見。* **午後:** 嵐山へ。竹林の道や渡月橋を散策。人力車は予算に応じて。* **夕食:** 嵐山で食べ歩きや、手頃な価格のカフェで夕食を。* **交通:** 嵐電、JR**3日目:*** **午前:** 錦市場で食べ歩き。* **午後:** 京都御所を散策(事前予約推奨)。* **夕食:** 京都駅周辺で新幹線に乗る前に、お土産を購入し、駅ビル内のレストランで最後の食事を。* **交通:** 市バス、電車、新幹線**宿泊:** ゲストハウスや格安ホテルを選択。食事は、安価な店やコンビニを活用することで予算内に収まるでしょう。cost_agent:素晴らしいプランですね! 伏見稲荷大社、清水寺、金閣寺、嵐山と、京都の主要な観光スポットをバランス良く網羅しています。市バスの一日乗車券を利用するなど、交通費を抑える工夫も素晴らしいです。**詳細な費用概算*** **交通費:** * 新幹線(往復):約20,000円 (出発地によります) * 市バス1日乗車券:1,100円 × 2日 = 2,200円 * 嵐電、JRなど:3,000円 * **合計:約25,200円*** **宿泊費:** ゲストハウスまたは格安ホテル (3泊):1泊あたり5,000円と仮定して、15,000円* **食費:** 1日あたり3,000円 × 3日 = 9,000円* **観光費:** 各施設の入場料、お土産代など:約5,000円* **合計:約54,200円**この概算では、残念ながら5万円の予算を若干オーバーしてしまいます。 そこで、いくつか調整案をご提案します。**調整案**1. **交通費の見直し:** * 新幹線の代わりに、夜行バスを利用する (往復10,000円程度で済む可能性があります)。2. **宿泊費の見直し:** * カプセルホテルや、より格安なゲストハウスを探す。 * Airbnbなどを利用する。3. **食費の見直し:** * 自炊できるゲストハウスを選ぶ。 * ランチは軽食にする。4. **観光費の見直し:** * 入場料が無料の場所も活用する(例:伏見稲荷大社)。 * お土産は、少なめにするか、リーズナブルなものを選ぶ。これらの調整を行うことで、5万円の予算内に収まる可能性があります。review_agent:ありがとうございます。詳細な費用概算と、的確な調整案のご提示、大変助かります。確かに、新幹線を夜行バスに変更することや、宿泊費を抑えることが、予算内に収めるための重要なポイントですね。**修正後のプラン(調整案を反映)****1日目:*** **午前:** 伏見稲荷大社へ。* **午後:** 清水寺周辺を散策。清水の舞台からの眺めを堪能し、二年坂・産寧坂でお土産探し。* **夕食:** 予算を考慮し、駅周辺の定食屋で。* **交通:** 市バス(1日乗車券利用)**2日目:*** **午前:** 金閣寺へ。* **午後:** 嵐山へ。竹林の道や渡月橋を散策。人力車は今回は見送り。* **夕食:** 嵐山で食べ歩きや、手頃な価格のカフェで夕食を。* **交通:** 嵐電、JR**3日目:*** **午前:** 錦市場で食べ歩き。* **午後:** 京都御所を散策(事前予約推奨)。* **夕食:** 京都駅周辺で新幹線に乗る前に、お土産を購入し、駅ビル内のレストランで最後の食事を。* **交通:** 市バス、電車、夜行バス**費用の再概算(調整案反映後)*** **交通費:** * 夜行バス(往復):約10,000円 * 市バス1日乗車券:1,100円 × 2日 = 2,200円 * 嵐電、JRなど:3,000円 * **合計:約15,200円*** **宿泊費:** カプセルホテルまたは格安ゲストハウス (3泊):1泊あたり4,000円と仮定して、12,000円* **食費:** 1日あたり2,500円 × 3日 = 7,500円(ランチは軽食中心)* **観光費:** 各施設の入場料、お土産代など:約10,300円 (伏見稲荷大社、清水寺、金閣寺は入場料がかかりますが、それ以外の場所では無料の場所も活用し、お土産代を調整)* **合計:約45,000円**これで、5万円の予算内に収まりそうですね!このプランは、夜行バスの利用、宿泊費の節約、食費の見直し、観光費の調整によって、予算をクリアできる可能性が高くなりました。素晴らしいです。itinerary_agent:完璧ですね!修正案が反映され、5万円以内の予算で、京都の主要観光地を巡る3日間の旅程が実現可能になりました。**補足事項:*** **夜行バスの予約:** 確実に予約を確保するため、早めの予約をお勧めします。* **宿泊施設の予約:** ゲストハウスやカプセルホテルも、人気のある施設はすぐに満室になることがあります。こちらも早めの予約を。* **京都御所の予約:** 事前予約が必要ですので、忘れずに手続きしてください。* **観光施設の開館時間:** 各施設の開館時間を事前に確認し、効率よく観光できるように計画を立てましょう。* **移動時間:** 市バスや電車での移動時間を考慮し、無理のないスケジュールを立てましょう。* **持ち物:** 歩きやすい靴、防寒具、雨具などを忘れずに準備しましょう。このプランで、京都の魅力を存分に楽しんでください! 良い旅になりますように!cost_agent:ありがとうございます! 完璧なプランニングですね。補足事項も非常に的確で、旅行者が安心して旅を楽しめるよう、細やかな配慮がされています。このプランを参考に、楽しい京都旅行を満喫してください! 何か他に質問があれば、お気軽にお尋ねください。review_agent:ありがとうございます! こちらこそ、素晴らしいプランを一緒に作り上げることができ、大変光栄です。何かお役に立てることがあれば、いつでもお声がけください。 良い旅になりますように! user : 京都への3日間の旅行を5万円以内で計画してください。予算内に収めつつ、楽しめるプランにしましょう。内容は200語以内に収めてください。 itinerary_agent : 承知いたしました。京都3日間5万円以内の旅程をご提案します。 ** 1日目 : ** * ** 午前 : ** 伏見稲荷大社へ。朱色の鳥居が圧巻です。 * ** 午後 : ** 清水寺周辺を散策。清水の舞台からの眺めを堪能し、二年坂・産寧坂でお土産探し。 * ** 夕食 : ** 予算を考慮し、駅周辺の定食屋で。 * ** 交通 : ** 市バス( 1日乗車券利用がお得 ) ** 2日目 : ** * ** 午前 : ** 金閣寺へ。金箔が輝く姿は必見。 * ** 午後 : ** 嵐山へ。竹林の道や渡月橋を散策。人力車は予算に応じて。 * ** 夕食 : ** 嵐山で食べ歩きや、手頃な価格のカフェで夕食を。 * ** 交通 : ** 嵐電、JR ** 3日目 : ** * ** 午前 : ** 錦市場で食べ歩き。 * ** 午後 : ** 京都御所を散策(事前予約推奨)。 * ** 夕食 : ** 京都駅周辺で新幹線に乗る前に、お土産を購入し、駅ビル内のレストランで最後の食事を。 * ** 交通 : ** 市バス、電車、新幹線 ** 宿泊 : ** ゲストハウスや格安ホテルを選択。食事は、安価な店やコンビニを活用することで予算内に収まるでしょう。 cost_agent : 素晴らしいプランですね! 伏見稲荷大社、清水寺、金閣寺、嵐山と、京都の主要な観光スポットをバランス良く網羅しています。市バスの一日乗車券を利用するなど、交通費を抑える工夫も素晴らしいです。 ** 詳細な費用概算 ** * ** 交通費 : ** * 新幹線(往復):約20 , 000円 ( 出発地によります ) * 市バス1日乗車券: 1 , 100円 × 2日 = 2 , 200円 * 嵐電、JRなど: 3 , 000円 * ** 合計:約25 , 200円 ** * ** 宿泊費 : ** ゲストハウスまたは格安ホテル ( 3泊 ) : 1泊あたり5 , 000円と仮定して 、 15 , 000円 * ** 食費 : ** 1日あたり3 , 000円 × 3日 = 9 , 000円 * ** 観光費 : ** 各施設の入場料、お土産代など:約5 , 000円 * ** 合計:約54 , 200円 ** この概算では、残念ながら5万円の予算を若干オーバーしてしまいます。 そこで、いくつか調整案をご提案します。 ** 調整案 ** 1 . ** 交通費の見直し : ** * 新幹線の代わりに、 夜行バスを利用する ( 往復10 , 000円程度で済む可能性があります ) 。 2 . ** 宿泊費の見直し : ** * カプセルホテルや、より格安なゲストハウスを探す。 * Airbnbなどを利用する。 3 . ** 食費の見直し : ** * 自炊できるゲストハウスを選ぶ。 * ランチは軽食にする。 4 . ** 観光費の見直し : ** * 入場料が無料の場所も活用する(例:伏見稲荷大社)。 * お土産は、少なめにするか、リーズナブルなものを選ぶ。 これらの調整を行うことで、 5万円の予算内に収まる可能性があります 。 review_agent : ありがとうございます。詳細な費用概算と、的確な調整案のご提示、大変助かります。確かに、新幹線を夜行バスに変更することや、宿泊費を抑えることが、予算内に収めるための重要なポイントですね。 ** 修正後のプラン(調整案を反映) ** ** 1日目 : ** * ** 午前 : ** 伏見稲荷大社へ。 * ** 午後 : ** 清水寺周辺を散策。清水の舞台からの眺めを堪能し、二年坂・産寧坂でお土産探し。 * ** 夕食 : ** 予算を考慮し、駅周辺の定食屋で。 * ** 交通 : ** 市バス( 1日乗車券利用 ) ** 2日目 : ** * ** 午前 : ** 金閣寺へ。 * ** 午後 : ** 嵐山へ。竹林の道や渡月橋を散策。人力車は今回は見送り。 * ** 夕食 : ** 嵐山で食べ歩きや、手頃な価格のカフェで夕食を。 * ** 交通 : ** 嵐電、JR ** 3日目 : ** * ** 午前 : ** 錦市場で食べ歩き。 * ** 午後 : ** 京都御所を散策(事前予約推奨)。 * ** 夕食 : ** 京都駅周辺で新幹線に乗る前に、お土産を購入し、駅ビル内のレストランで最後の食事を。 * ** 交通 : ** 市バス、電車、夜行バス ** 費用の再概算(調整案反映後) ** * ** 交通費 : ** * 夜行バス(往復):約10 , 000円 * 市バス1日乗車券: 1 , 100円 × 2日 = 2 , 200円 * 嵐電、JRなど: 3 , 000円 * ** 合計:約15 , 200円 ** * ** 宿泊費 : ** カプセルホテルまたは格安ゲストハウス ( 3泊 ) : 1泊あたり4 , 000円と仮定して 、 12 , 000円 * ** 食費 : ** 1日あたり2 , 500円 × 3日 = 7 , 500円 (ランチは軽食中心) * ** 観光費 : ** 各施設の入場料、お土産代など:約10 , 300円 ( 伏見稲荷大社、清水寺、金閣寺は入場料がかかりますが、それ以外の場所では無料の場所も活用し、お土産代を調整 ) * ** 合計:約45 , 000円 ** これで、 5万円の予算内に収まりそうですね ! このプランは、夜行バスの利用、宿泊費の節約、食費の見直し、観光費の調整によって、予算をクリアできる可能性が高くなりました。素晴らしいです。 itinerary_agent : 完璧ですね!修正案が反映され、 5万円以内の予算で 、京都の主要観光地を巡る3日間の旅程が実現可能になりました。 ** 補足事項 : ** * ** 夜行バスの予約 : ** 確実に予約を確保するため、早めの予約をお勧めします。 * ** 宿泊施設の予約 : ** ゲストハウスやカプセルホテルも、人気のある施設はすぐに満室になることがあります。こちらも早めの予約を。 * ** 京都御所の予約 : ** 事前予約が必要ですので、忘れずに手続きしてください。 * ** 観光施設の開館時間 : ** 各施設の開館時間を事前に確認し、効率よく観光できるように計画を立てましょう。 * ** 移動時間 : ** 市バスや電車での移動時間を考慮し、無理のないスケジュールを立てましょう。 * ** 持ち物 : ** 歩きやすい靴、防寒具、雨具などを忘れずに準備しましょう。 このプランで、京都の魅力を存分に楽しんでください! 良い旅になりますように! cost_agent : ありがとうございます! 完璧なプランニングですね。補足事項も非常に的確で、旅行者が安心して旅を楽しめるよう、細やかな配慮がされています。 このプランを参考に、楽しい京都旅行を満喫してください! 何か他に質問があれば、お気軽にお尋ねください。 review_agent : ありがとうございます! こちらこそ、素晴らしいプランを一緒に作り上げることができ、大変光栄です。何かお役に立てることがあれば、いつでもお声がけください。 良い旅になりますように! 単一エージェント(ChatGPTのような)の使用とマルチエージェントの使用の比較 同じ質問に対するChatGPTの出力 【1日目:清水寺と祇園】 清水寺(400円)を参拝。三年坂・二年坂を散策。昼食はカフェで約1,200円。午後は祇園を歩く。夕食はうどん(約900円)。 合計:約3,000円 【2日目:嵐山エリア】 JRで嵐山へ(240円)。竹林の小径(無料)、天龍寺庭園(500円)。川辺でお弁当(900円)。希望があれば猿山(600円)。夕食は1,000円ほど。 合計:約3,500円 【3日目:伏見稲荷と錦市場】 伏見稲荷(無料)。朝食600円。錦市場へ(210円)。食べ歩き1,200円。河原町を散策。夕食1,200円。 合計:約3,200円 【総費用:約1〜1.5万円(宿泊除く)】 余裕で5万円以内に収まります。 安いバージョン、豪華バージョン、が必要であればお知らせください。 特徴 単一エージェント(ChatGPT) マルチエージェント タスク 一つのモデルがすべてをこなす 専門家ごとに異なる業務を担当する エラーチェック なし レビュー担当者が誤りを発見する 反復 ユーザーは改善を要求しなければならない エージェントは自動的に互いを洗練させる 出力品質 良いが、単調である 構造化された、階層化された、より深い出力 結論 オートジェンはAIの活用方法におけるパラダイムシフトです。協調知能を実現することで、単一エージェントシステムでは達成できない可能性を解き放ちます。開発者、研究者、技術愛好家の方々に、オートジェンは力になります。 オートゲンの真価は、モデルが賢くなることではなく、ワークフローが効率化されることにある。 イベント告知 12月23日にイベントを開催します!申し込みはこちらから▼ https://mynaviit.connpass.com/event/376769
アバター
本記事は【 Advent Calendar 2025 】の12日目の記事です。 結論 フロントエンドでは、OrvalでzodではなくFetch Clientを生成することを検討してみてはいかがでしょうか? Orvalとは Orval とは、OpenAPIからTypeScriptのコードを生成できるツールです。 例えば、以下のようなコードが生成できます。 Fetch Client React Query Zod Hono このように、Orvalは様々な種類のコードを生成できるとても便利なツールです。 しかし、使い方によっては、逆に保守性の低下を引き起こす可能性があります。 特に、「フロントエンドでzodを生成させている」ことが課題となるケースがあります。 そこで、本記事ではフロントエンドでOrvalにzodを生成させることについて、いくつかの観点から考察していきます。 Fetch Clientという選択肢 フロントエンドにおいて、なぜOrvalを使用するのでしょうか? それは、APIのレスポンスに型が欲しいからです。 zodを生成させることで型を得ることができますが、Fetch Clientを生成させることでも同様に実現でき、より簡潔にできる可能性があります。 zodとFetch Clientの比較 では、zodを生成させた場合とFetch Clientを生成させた場合のコードを比較してみましょう。 OpenAPI openapi: 3.0.0servers: - url: 'http://petstore.swagger.io/v2'info: description: >- This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. version: 1.0.0 title: OpenAPI Petstore license: name: Apache-2.0 url: 'https://www.apache.org/licenses/LICENSE-2.0.html'tags: - name: pet description: Everything about your Pets - name: store description: Access to Petstore orders - name: user description: Operations about userpaths: /pet: post: tags: - pet summary: Add a new pet to the store description: '' operationId: addPet responses: '200': description: successful operation content: application/xml: schema: $ref: '#/components/schemas/Pet' application/json: schema: $ref: '#/components/schemas/Pet' '405': description: Invalid input security: - petstore_auth: - 'write:pets' - 'read:pets' requestBody: $ref: '#/components/requestBodies/Pet' put: tags: - pet summary: Update an existing pet description: '' operationId: updatePet externalDocs: url: "http://petstore.swagger.io/v2/doc/updatePet" description: "API documentation for the updatePet operation" responses: '200': description: successful operation content: application/xml: schema: $ref: '#/components/schemas/Pet' application/json: schema: $ref: '#/components/schemas/Pet' '400': description: Invalid ID supplied '404': description: Pet not found '405': description: Validation exception security: - petstore_auth: - 'write:pets' - 'read:pets' requestBody: $ref: '#/components/requestBodies/Pet' /pet/findByStatus: get: tags: - pet summary: Finds Pets by status description: Multiple status values can be provided with comma separated strings operationId: findPetsByStatus parameters: - name: status in: query description: Status values that need to be considered for filter required: true style: form explode: false deprecated: true schema: type: array items: type: string enum: - available - pending - sold default: available responses: '200': description: successful operation content: application/xml: schema: type: array items: $ref: '#/components/schemas/Pet' application/json: schema: type: array items: $ref: '#/components/schemas/Pet' '400': description: Invalid status value security: - petstore_auth: - 'read:pets' /pet/findByTags: get: tags: - pet summary: Finds Pets by tags description: >- Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. operationId: findPetsByTags parameters: - name: tags in: query description: Tags to filter by required: true style: form explode: false schema: type: array items: type: string responses: '200': description: successful operation content: application/xml: schema: type: array items: $ref: '#/components/schemas/Pet' application/json: schema: type: array items: $ref: '#/components/schemas/Pet' '400': description: Invalid tag value security: - petstore_auth: - 'read:pets' deprecated: true '/pet/{petId}': get: tags: - pet summary: Find pet by ID description: Returns a single pet operationId: getPetById parameters: - name: petId in: path description: ID of pet to return required: true schema: type: integer format: int64 responses: '200': description: successful operation content: application/xml: schema: $ref: '#/components/schemas/Pet' application/json: schema: $ref: '#/components/schemas/Pet' '400': description: Invalid ID supplied '404': description: Pet not found security: - api_key: [] post: tags: - pet summary: Updates a pet in the store with form data description: '' operationId: updatePetWithForm parameters: - name: petId in: path description: ID of pet that needs to be updated required: true schema: type: integer format: int64 responses: '405': description: Invalid input security: - petstore_auth: - 'write:pets' - 'read:pets' requestBody: content: application/x-www-form-urlencoded: schema: type: object properties: name: description: Updated name of the pet type: string status: description: Updated status of the pet type: string delete: tags: - pet summary: Deletes a pet description: '' operationId: deletePet parameters: - name: api_key in: header required: false schema: type: string - name: petId in: path description: Pet id to delete required: true schema: type: integer format: int64 responses: '400': description: Invalid pet value security: - petstore_auth: - 'write:pets' - 'read:pets' '/pet/{petId}/uploadImage': post: tags: - pet summary: uploads an image description: '' operationId: uploadFile parameters: - name: petId in: path description: ID of pet to update required: true schema: type: integer format: int64 responses: '200': description: successful operation content: application/json: schema: $ref: '#/components/schemas/ApiResponse' security: - petstore_auth: - 'write:pets' - 'read:pets' requestBody: content: multipart/form-data: schema: type: object properties: additionalMetadata: description: Additional data to pass to server type: string file: description: file to upload type: string format: binary /store/inventory: get: tags: - store summary: Returns pet inventories by status description: Returns a map of status codes to quantities operationId: getInventory responses: '200': description: successful operation content: application/json: schema: type: object additionalProperties: type: integer format: int32 security: - api_key: [] /store/order: post: tags: - store summary: Place an order for a pet description: '' operationId: placeOrder responses: '200': description: successful operation content: application/xml: schema: $ref: '#/components/schemas/Order' application/json: schema: $ref: '#/components/schemas/Order' '400': description: Invalid Order requestBody: content: application/json: schema: $ref: '#/components/schemas/Order' description: order placed for purchasing the pet required: true '/store/order/{orderId}': get: tags: - store summary: Find purchase order by ID description: >- For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions operationId: getOrderById parameters: - name: orderId in: path description: ID of pet that needs to be fetched required: true schema: type: integer format: int64 minimum: 1 maximum: 5 responses: '200': description: successful operation content: application/xml: schema: $ref: '#/components/schemas/Order' application/json: schema: $ref: '#/components/schemas/Order' '400': description: Invalid ID supplied '404': description: Order not found delete: tags: - store summary: Delete purchase order by ID description: >- For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors operationId: deleteOrder parameters: - name: orderId in: path description: ID of the order that needs to be deleted required: true schema: type: string responses: '400': description: Invalid ID supplied '404': description: Order not found /user: post: tags: - user summary: Create user description: This can only be done by the logged in user. operationId: createUser responses: default: description: successful operation security: - api_key: [] requestBody: content: application/json: schema: $ref: '#/components/schemas/User' description: Created user object required: true /user/createWithArray: post: tags: - user summary: Creates list of users with given input array description: '' operationId: createUsersWithArrayInput responses: default: description: successful operation security: - api_key: [] requestBody: $ref: '#/components/requestBodies/UserArray' /user/createWithList: post: tags: - user summary: Creates list of users with given input array description: '' operationId: createUsersWithListInput responses: default: description: successful operation security: - api_key: [] requestBody: $ref: '#/components/requestBodies/UserArray' /user/login: get: tags: - user summary: Logs user into the system description: '' operationId: loginUser parameters: - name: username in: query description: The user name for login required: true schema: type: string pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' - name: password in: query description: The password for login in clear text required: true schema: type: string responses: '200': description: successful operation headers: Set-Cookie: description: >- Cookie authentication key for use with the `api_key` apiKey authentication. schema: type: string example: AUTH_KEY=abcde12345; Path=/; HttpOnly X-Rate-Limit: description: calls per hour allowed by the user schema: type: integer format: int32 X-Expires-After: description: date in UTC when token expires schema: type: string format: date-time content: application/xml: schema: type: string application/json: schema: type: string '400': description: Invalid username/password supplied /user/logout: get: tags: - user summary: Logs out current logged in user session description: '' operationId: logoutUser responses: default: description: successful operation security: - api_key: [] '/user/{username}': get: tags: - user summary: Get user by user name description: '' operationId: getUserByName parameters: - name: username in: path description: The name that needs to be fetched. Use user1 for testing. required: true schema: type: string responses: '200': description: successful operation content: application/xml: schema: $ref: '#/components/schemas/User' application/json: schema: $ref: '#/components/schemas/User' '400': description: Invalid username supplied '404': description: User not found put: tags: - user summary: Updated user description: This can only be done by the logged in user. operationId: updateUser parameters: - name: username in: path description: name that need to be deleted required: true schema: type: string responses: '400': description: Invalid user supplied '404': description: User not found security: - api_key: [] requestBody: content: application/json: schema: $ref: '#/components/schemas/User' description: Updated user object required: true delete: tags: - user summary: Delete user description: This can only be done by the logged in user. operationId: deleteUser parameters: - name: username in: path description: The name that needs to be deleted required: true schema: type: string responses: '400': description: Invalid username supplied '404': description: User not found security: - api_key: []externalDocs: description: Find out more about Swagger url: 'http://swagger.io'components: requestBodies: UserArray: content: application/json: schema: type: array items: $ref: '#/components/schemas/User' description: List of user object required: true Pet: content: application/json: schema: $ref: '#/components/schemas/Pet' application/xml: schema: $ref: '#/components/schemas/Pet' description: Pet object that needs to be added to the store required: true securitySchemes: petstore_auth: type: oauth2 flows: implicit: authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' scopes: 'write:pets': modify pets in your account 'read:pets': read your pets api_key: type: apiKey name: api_key in: header schemas: Order: title: Pet Order description: An order for a pets from the pet store type: object properties: id: type: integer format: int64 petId: type: integer format: int64 quantity: type: integer format: int32 shipDate: type: string format: date-time status: type: string description: Order Status enum: - placed - approved - delivered complete: type: boolean default: false xml: name: Order Category: title: Pet category description: A category for a pet type: object properties: id: type: integer format: int64 name: type: string pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' xml: name: Category User: title: a User description: A User who is purchasing from the pet store type: object properties: id: type: integer format: int64 username: type: string firstName: type: string lastName: type: string email: type: string password: type: string phone: type: string userStatus: type: integer format: int32 description: User Status xml: name: User Tag: title: Pet Tag description: A tag for a pet type: object properties: id: type: integer format: int64 name: type: string xml: name: Tag Pet: title: a Pet description: A pet for sale in the pet store type: object required: - name - photoUrls properties: id: type: integer format: int64 category: $ref: '#/components/schemas/Category' name: type: string example: doggie photoUrls: type: array xml: name: photoUrl wrapped: true items: type: string tags: type: array xml: name: tag wrapped: true items: $ref: '#/components/schemas/Tag' status: type: string description: pet status in the store deprecated: true enum: - available - pending - sold xml: name: Pet ApiResponse: title: An uploaded response description: Describes the result of uploading an image resource type: object properties: code: type: integer format: int32 type: type: string message: type: string openapi : 3.0.0 servers : - url : ' http://petstore.swagger.io/v2 ' info : description : >- This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. version : 1.0.0 title : OpenAPI Petstore license : name : Apache-2.0 url : ' https://www.apache.org/licenses/LICENSE-2.0.html ' tags : - name : pet description : Everything about your Pets - name : store description : Access to Petstore orders - name : user description : Operations about user paths : /pet : post : tags : - pet summary : Add a new pet to the store description : '' operationId : addPet responses : ' 200 ' : description : successful operation content : application/xml : schema : $ref : ' #/components/schemas/Pet ' application/json : schema : $ref : ' #/components/schemas/Pet ' ' 405 ' : description : Invalid input security : - petstore_auth : - ' write:pets ' - ' read:pets ' requestBody : $ref : ' #/components/requestBodies/Pet ' put : tags : - pet summary : Update an existing pet description : '' operationId : updatePet externalDocs : url : " http://petstore.swagger.io/v2/doc/updatePet " description : " API documentation for the updatePet operation " responses : ' 200 ' : description : successful operation content : application/xml : schema : $ref : ' #/components/schemas/Pet ' application/json : schema : $ref : ' #/components/schemas/Pet ' ' 400 ' : description : Invalid ID supplied ' 404 ' : description : Pet not found ' 405 ' : description : Validation exception security : - petstore_auth : - ' write:pets ' - ' read:pets ' requestBody : $ref : ' #/components/requestBodies/Pet ' /pet/findByStatus : get : tags : - pet summary : Finds Pets by status description : Multiple status values can be provided with comma separated strings operationId : findPetsByStatus parameters : - name : status in : query description : Status values that need to be considered for filter required : true style : form explode : false deprecated : true schema : type : array items : type : string enum : - available - pending - sold default : available responses : ' 200 ' : description : successful operation content : application/xml : schema : type : array items : $ref : ' #/components/schemas/Pet ' application/json : schema : type : array items : $ref : ' #/components/schemas/Pet ' ' 400 ' : description : Invalid status value security : - petstore_auth : - ' read:pets ' /pet/findByTags : get : tags : - pet summary : Finds Pets by tags description : >- Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. operationId : findPetsByTags parameters : - name : tags in : query description : Tags to filter by required : true style : form explode : false schema : type : array items : type : string responses : ' 200 ' : description : successful operation content : application/xml : schema : type : array items : $ref : ' #/components/schemas/Pet ' application/json : schema : type : array items : $ref : ' #/components/schemas/Pet ' ' 400 ' : description : Invalid tag value security : - petstore_auth : - ' read:pets ' deprecated : true ' /pet/{petId} ' : get : tags : - pet summary : Find pet by ID description : Returns a single pet operationId : getPetById parameters : - name : petId in : path description : ID of pet to return required : true schema : type : integer format : int64 responses : ' 200 ' : description : successful operation content : application/xml : schema : $ref : ' #/components/schemas/Pet ' application/json : schema : $ref : ' #/components/schemas/Pet ' ' 400 ' : description : Invalid ID supplied ' 404 ' : description : Pet not found security : - api_key : [] post : tags : - pet summary : Updates a pet in the store with form data description : '' operationId : updatePetWithForm parameters : - name : petId in : path description : ID of pet that needs to be updated required : true schema : type : integer format : int64 responses : ' 405 ' : description : Invalid input security : - petstore_auth : - ' write:pets ' - ' read:pets ' requestBody : content : application/x-www-form-urlencoded : schema : type : object properties : name : description : Updated name of the pet type : string status : description : Updated status of the pet type : string delete : tags : - pet summary : Deletes a pet description : '' operationId : deletePet parameters : - name : api_key in : header required : false schema : type : string - name : petId in : path description : Pet id to delete required : true schema : type : integer format : int64 responses : ' 400 ' : description : Invalid pet value security : - petstore_auth : - ' write:pets ' - ' read:pets ' ' /pet/{petId}/uploadImage ' : post : tags : - pet summary : uploads an image description : '' operationId : uploadFile parameters : - name : petId in : path description : ID of pet to update required : true schema : type : integer format : int64 responses : ' 200 ' : description : successful operation content : application/json : schema : $ref : ' #/components/schemas/ApiResponse ' security : - petstore_auth : - ' write:pets ' - ' read:pets ' requestBody : content : multipart/form-data : schema : type : object properties : additionalMetadata : description : Additional data to pass to server type : string file : description : file to upload type : string format : binary /store/inventory : get : tags : - store summary : Returns pet inventories by status description : Returns a map of status codes to quantities operationId : getInventory responses : ' 200 ' : description : successful operation content : application/json : schema : type : object additionalProperties : type : integer format : int32 security : - api_key : [] /store/order : post : tags : - store summary : Place an order for a pet description : '' operationId : placeOrder responses : ' 200 ' : description : successful operation content : application/xml : schema : $ref : ' #/components/schemas/Order ' application/json : schema : $ref : ' #/components/schemas/Order ' ' 400 ' : description : Invalid Order requestBody : content : application/json : schema : $ref : ' #/components/schemas/Order ' description : order placed for purchasing the pet required : true ' /store/order/{orderId} ' : get : tags : - store summary : Find purchase order by ID description : >- For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions operationId : getOrderById parameters : - name : orderId in : path description : ID of pet that needs to be fetched required : true schema : type : integer format : int64 minimum : 1 maximum : 5 responses : ' 200 ' : description : successful operation content : application/xml : schema : $ref : ' #/components/schemas/Order ' application/json : schema : $ref : ' #/components/schemas/Order ' ' 400 ' : description : Invalid ID supplied ' 404 ' : description : Order not found delete : tags : - store summary : Delete purchase order by ID description : >- For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors operationId : deleteOrder parameters : - name : orderId in : path description : ID of the order that needs to be deleted required : true schema : type : string responses : ' 400 ' : description : Invalid ID supplied ' 404 ' : description : Order not found /user : post : tags : - user summary : Create user description : This can only be done by the logged in user. operationId : createUser responses : default : description : successful operation security : - api_key : [] requestBody : content : application/json : schema : $ref : ' #/components/schemas/User ' description : Created user object required : true /user/createWithArray : post : tags : - user summary : Creates list of users with given input array description : '' operationId : createUsersWithArrayInput responses : default : description : successful operation security : - api_key : [] requestBody : $ref : ' #/components/requestBodies/UserArray ' /user/createWithList : post : tags : - user summary : Creates list of users with given input array description : '' operationId : createUsersWithListInput responses : default : description : successful operation security : - api_key : [] requestBody : $ref : ' #/components/requestBodies/UserArray ' /user/login : get : tags : - user summary : Logs user into the system description : '' operationId : loginUser parameters : - name : username in : query description : The user name for login required : true schema : type : string pattern : ' ^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$ ' - name : password in : query description : The password for login in clear text required : true schema : type : string responses : ' 200 ' : description : successful operation headers : Set-Cookie : description : >- Cookie authentication key for use with the `api_key` apiKey authentication. schema : type : string example : AUTH_KEY=abcde12345; Path=/; HttpOnly X-Rate-Limit : description : calls per hour allowed by the user schema : type : integer format : int32 X-Expires-After : description : date in UTC when token expires schema : type : string format : date-time content : application/xml : schema : type : string application/json : schema : type : string ' 400 ' : description : Invalid username/password supplied /user/logout : get : tags : - user summary : Logs out current logged in user session description : '' operationId : logoutUser responses : default : description : successful operation security : - api_key : [] ' /user/{username} ' : get : tags : - user summary : Get user by user name description : '' operationId : getUserByName parameters : - name : username in : path description : The name that needs to be fetched. Use user1 for testing. required : true schema : type : string responses : ' 200 ' : description : successful operation content : application/xml : schema : $ref : ' #/components/schemas/User ' application/json : schema : $ref : ' #/components/schemas/User ' ' 400 ' : description : Invalid username supplied ' 404 ' : description : User not found put : tags : - user summary : Updated user description : This can only be done by the logged in user. operationId : updateUser parameters : - name : username in : path description : name that need to be deleted required : true schema : type : string responses : ' 400 ' : description : Invalid user supplied ' 404 ' : description : User not found security : - api_key : [] requestBody : content : application/json : schema : $ref : ' #/components/schemas/User ' description : Updated user object required : true delete : tags : - user summary : Delete user description : This can only be done by the logged in user. operationId : deleteUser parameters : - name : username in : path description : The name that needs to be deleted required : true schema : type : string responses : ' 400 ' : description : Invalid username supplied ' 404 ' : description : User not found security : - api_key : [] externalDocs : description : Find out more about Swagger url : ' http://swagger.io ' components : requestBodies : UserArray : content : application/json : schema : type : array items : $ref : ' #/components/schemas/User ' description : List of user object required : true Pet : content : application/json : schema : $ref : ' #/components/schemas/Pet ' application/xml : schema : $ref : ' #/components/schemas/Pet ' description : Pet object that needs to be added to the store required : true securitySchemes : petstore_auth : type : oauth2 flows : implicit : authorizationUrl : ' http://petstore.swagger.io/api/oauth/dialog ' scopes : ' write:pets ' : modify pets in your account ' read:pets ' : read your pets api_key : type : apiKey name : api_key in : header schemas : Order : title : Pet Order description : An order for a pets from the pet store type : object properties : id : type : integer format : int64 petId : type : integer format : int64 quantity : type : integer format : int32 shipDate : type : string format : date-time status : type : string description : Order Status enum : - placed - approved - delivered complete : type : boolean default : false xml : name : Order Category : title : Pet category description : A category for a pet type : object properties : id : type : integer format : int64 name : type : string pattern : ' ^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$ ' xml : name : Category User : title : a User description : A User who is purchasing from the pet store type : object properties : id : type : integer format : int64 username : type : string firstName : type : string lastName : type : string email : type : string password : type : string phone : type : string userStatus : type : integer format : int32 description : User Status xml : name : User Tag : title : Pet Tag description : A tag for a pet type : object properties : id : type : integer format : int64 name : type : string xml : name : Tag Pet : title : a Pet description : A pet for sale in the pet store type : object required : - name - photoUrls properties : id : type : integer format : int64 category : $ref : ' #/components/schemas/Category ' name : type : string example : doggie photoUrls : type : array xml : name : photoUrl wrapped : true items : type : string tags : type : array xml : name : tag wrapped : true items : $ref : ' #/components/schemas/Tag ' status : type : string description : pet status in the store deprecated : true enum : - available - pending - sold xml : name : Pet ApiResponse : title : An uploaded response description : Describes the result of uploading an image resource type : object properties : code : type : integer format : int32 type : type : string message : type : string orval.config.ts /** * @summary Add a new pet to the store */export const addPetBodyCategoryNameRegExp = new RegExp( '^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$',);export const addPetBody = zod .object({ id: zod.number().optional(), category: zod .object({ id: zod.number().optional(), name: zod.string().regex(addPetBodyCategoryNameRegExp).optional(), }) .optional() .describe('A category for a pet'), name: zod.string(), photoUrls: zod.array(zod.string()), tags: zod .array( zod .object({ id: zod.number().optional(), name: zod.string().optional(), }) .describe('A tag for a pet'), ) .optional(), status: zod .enum(['available', 'pending', 'sold']) .optional() .describe('pet status in the store'), }) .describe('A pet for sale in the pet store');export const addPetResponseCategoryNameRegExp = new RegExp( '^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$',);export const addPetResponse = zod .object({ id: zod.number().optional(), category: zod .object({ id: zod.number().optional(), name: zod.string().regex(addPetResponseCategoryNameRegExp).optional(), }) .optional() .describe('A category for a pet'), name: zod.string(), photoUrls: zod.array(zod.string()), tags: zod .array( zod .object({ id: zod.number().optional(), name: zod.string().optional(), }) .describe('A tag for a pet'), ) .optional(), status: zod .enum(['available', 'pending', 'sold']) .optional() .describe('pet status in the store'), }) .describe('A pet for sale in the pet store'); /** * @ summary Add a new pet to the store */ export const addPetBodyCategoryNameRegExp = new RegExp ( ' ^[a-zA-Z0-9]+[a-zA-Z0-9 \\ . \\ -_]*[a-zA-Z0-9]+$ ' , ) ; export const addPetBody = zod . object ( { id : zod . number () . optional () , category : zod . object ( { id : zod . number () . optional () , name : zod . string () . regex ( addPetBodyCategoryNameRegExp ) . optional () , } ) . optional () . describe ( ' A category for a pet ' ) , name : zod . string () , photoUrls : zod . array ( zod . string ()) , tags : zod . array ( zod . object ( { id : zod . number () . optional () , name : zod . string () . optional () , } ) . describe ( ' A tag for a pet ' ) , ) . optional () , status : zod . enum ([ ' available ' , ' pending ' , ' sold ' ]) . optional () . describe ( ' pet status in the store ' ) , } ) . describe ( ' A pet for sale in the pet store ' ) ; export const addPetResponseCategoryNameRegExp = new RegExp ( ' ^[a-zA-Z0-9]+[a-zA-Z0-9 \\ . \\ -_]*[a-zA-Z0-9]+$ ' , ) ; export const addPetResponse = zod . object ( { id : zod . number () . optional () , category : zod . object ( { id : zod . number () . optional () , name : zod . string () . regex ( addPetResponseCategoryNameRegExp ) . optional () , } ) . optional () . describe ( ' A category for a pet ' ) , name : zod . string () , photoUrls : zod . array ( zod . string ()) , tags : zod . array ( zod . object ( { id : zod . number () . optional () , name : zod . string () . optional () , } ) . describe ( ' A tag for a pet ' ) , ) . optional () , status : zod . enum ([ ' available ' , ' pending ' , ' sold ' ]) . optional () . describe ( ' pet status in the store ' ) , } ) . describe ( ' A pet for sale in the pet store ' ) ; zodの場合: /** * @summary Add a new pet to the store */export const addPetBodyCategoryNameRegExp = new RegExp( '^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$',);export const addPetBody = zod .object({ id: zod.number().optional(), category: zod .object({ id: zod.number().optional(), name: zod.string().regex(addPetBodyCategoryNameRegExp).optional(), }) .optional() .describe('A category for a pet'), name: zod.string(), photoUrls: zod.array(zod.string()), tags: zod .array( zod .object({ id: zod.number().optional(), name: zod.string().optional(), }) .describe('A tag for a pet'), ) .optional(), status: zod .enum(['available', 'pending', 'sold']) .optional() .describe('pet status in the store'), }) .describe('A pet for sale in the pet store');export const addPetResponseCategoryNameRegExp = new RegExp( '^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$',);export const addPetResponse = zod .object({ id: zod.number().optional(), category: zod .object({ id: zod.number().optional(), name: zod.string().regex(addPetResponseCategoryNameRegExp).optional(), }) .optional() .describe('A category for a pet'), name: zod.string(), photoUrls: zod.array(zod.string()), tags: zod .array( zod .object({ id: zod.number().optional(), name: zod.string().optional(), }) .describe('A tag for a pet'), ) .optional(), status: zod .enum(['available', 'pending', 'sold']) .optional() .describe('pet status in the store'), }) .describe('A pet for sale in the pet store'); /** * @ summary Add a new pet to the store */ export const addPetBodyCategoryNameRegExp = new RegExp ( ' ^[a-zA-Z0-9]+[a-zA-Z0-9 \\ . \\ -_]*[a-zA-Z0-9]+$ ' , ) ; export const addPetBody = zod . object ( { id : zod . number () . optional () , category : zod . object ( { id : zod . number () . optional () , name : zod . string () . regex ( addPetBodyCategoryNameRegExp ) . optional () , } ) . optional () . describe ( ' A category for a pet ' ) , name : zod . string () , photoUrls : zod . array ( zod . string ()) , tags : zod . array ( zod . object ( { id : zod . number () . optional () , name : zod . string () . optional () , } ) . describe ( ' A tag for a pet ' ) , ) . optional () , status : zod . enum ([ ' available ' , ' pending ' , ' sold ' ]) . optional () . describe ( ' pet status in the store ' ) , } ) . describe ( ' A pet for sale in the pet store ' ) ; export const addPetResponseCategoryNameRegExp = new RegExp ( ' ^[a-zA-Z0-9]+[a-zA-Z0-9 \\ . \\ -_]*[a-zA-Z0-9]+$ ' , ) ; export const addPetResponse = zod . object ( { id : zod . number () . optional () , category : zod . object ( { id : zod . number () . optional () , name : zod . string () . regex ( addPetResponseCategoryNameRegExp ) . optional () , } ) . optional () . describe ( ' A category for a pet ' ) , name : zod . string () , photoUrls : zod . array ( zod . string ()) , tags : zod . array ( zod . object ( { id : zod . number () . optional () , name : zod . string () . optional () , } ) . describe ( ' A tag for a pet ' ) , ) . optional () , status : zod . enum ([ ' available ' , ' pending ' , ' sold ' ]) . optional () . describe ( ' pet status in the store ' ) , } ) . describe ( ' A pet for sale in the pet store ' ) ; Fetch Clientの場合: /** * @summary Add a new pet to the store */export type addPetResponse200 = { data: Pet; status: 200;};export type addPetResponse405 = { data: null; status: 405;};export type addPetResponseComposite = addPetResponse200 | addPetResponse405;export type addPetResponse = addPetResponseComposite & { headers: Headers;};export const getAddPetUrl = () => { return `/pet`;};export const addPet = async ( petBody: PetBody, options?: RequestInit,): Promise<addPetResponse> => { const res = await fetch(getAddPetUrl(), { ...options, method: 'POST', headers: { 'Content-Type': 'application/json', ...options?.headers }, body: JSON.stringify(petBody), }); const body = [204, 205, 304].includes(res.status) ? null : await res.text(); const data: addPetResponse['data'] = body ? JSON.parse(body) : {}; return { data, status: res.status, headers: res.headers } as addPetResponse;}; /** * @ summary Add a new pet to the store */ export type addPetResponse200 = { data : Pet ; status : 200 ; } ; export type addPetResponse405 = { data : null ; status : 405 ; } ; export type addPetResponseComposite = addPetResponse200 | addPetResponse405 ; export type addPetResponse = addPetResponseComposite & { headers : Headers ; } ; export const getAddPetUrl = () => { return ` /pet ` ; } ; export const addPet = async ( petBody : PetBody , options ?: RequestInit , ) : Promise < addPetResponse > => { const res = await fetch ( getAddPetUrl () , { ... options , method : ' POST ' , headers : { ' Content-Type ' : ' application/json ' , ... options ?. headers }, body : JSON . stringify ( petBody ) , } ) ; const body = [ 204 , 205 , 304 ] . includes ( res . status ) ? null : await res . text () ; const data : addPetResponse [ ' data ' ] = body ? JSON . parse ( body ) : {} ; return { data , status : res . status , headers : res . headers } as addPetResponse ; } ; zodを生成させた場合、生成されるのはあくまでもzodのスキーマなので、実際にfetchする処理は自分で書く必要があります。 それに対して、Fetch Clientを生成させた場合はfetchする処理まで生成してくれるので、ボイラープレートを減らすことができます。 型安全性についての考察 Fetch Clientで生成させたコードを見てもらうとわかりますが、 as ​を使用して型のアサーションを行っています。 そうです。Fetch Clientで生成したコードは厳密には型安全ではありません。 しかし、ここで考えてみたいことがあります。 型安全ではないことによって、問題が発生するのはどのようなケースでしょうか? それは、OpenAPIのスキーマと実際にバックエンドから返ってくるスキーマが異なるケースです。 そして、それは果たしてフロントエンドの、しかもランタイム上で検知すべきことなのでしょうか? それを踏まえると、OpenAPIとバックエンドの齟齬がフロントエンドのランタイム上で判明するのは理想的なタイミングとは言えないかもしれません。 したがって、この問題はバックエンドの責務と考え、バックエンド側のテストで対処する方が適切だと考えられます。 バックエンドはフロントエンドを信頼してはいけませんが、フロントエンドはバックエンドを信頼するという考え方もできます。 ランタイム検証についての考え方 実際問題、ランタイムエラーが発生したらどうするのか? という懸念もあるかと思います。 その場合、素直にエラーをthrowするという選択肢があります。 OpenAPIとバックエンドに齟齬があるという致命的な問題が発生している場合、フロントエンド側でできることは限られています。 また、Next.jsなら error.tsx ​を配置しておくことで、エラー画面を表示することができます。 catchした後どうするのか? を念頭においてエラーハンドリングを設計しましょう。 クエリパラメータやリクエストボディのバリデーションについて フロントエンドからバックエンドのAPIにリクエストを送る前に、クエリパラメータやリクエストボディのバリデーションを行いたいというユースケースがあると思います。 ここでは、一つの考え方として、その段階でのバリデーションの必要性について検討してみます。 実際は、その直後にバックエンドがバリデーションを行います。 したがって、フロントエンド側での重複したバリデーションは省略できる場合が多いです。 また、フロントエンドで行うバリデーションはUXのためであるという視点を持つことが重要です。 つまり、セキュリティや不正な値を防ぐためのバリデーションはバックエンドで行い、フロントエンドとバックエンドで二度同じバリデーションを行う必要性は低いと考えることができます。 フォームのバリデーションについて UXのためのフォームのバリデーションで、zodのスキーマが欲しくなるケースがあるかもしれません。 その場合は、フォームのスキーマをAPIのスキーマとは別に定義することをおすすめします。 なぜなら、フォームのスキーマはAPIのスキーマと必ず対応しているとは限らないからです。 例えば、郵便番号を入力するとき、API側では半角数字の文字列を期待しますが、フォーム側ではUXのために全角数字の文字列も受け取れるようにしたい場合があります。 また、数値入力でも、API側では数値型を期待しますが、フォーム側では一時的に文字列として扱い、カンマ区切りの表示に対応したい場合があります。 それ以外でも、API側は完成形のオブジェクトを期待しますが、フォームでは段階的に異なる形状のデータを扱うような場合があります。 このような場合、API側のスキーマは流用できません。フロントエンド側で独自に定義する必要があります。 こうなると、APIのスキーマを流用するものと、しないものが混在することになります。 そして、現状流用できているスキーマも、後から変更される可能性があります。 つまり、APIのスキーマとフォームのスキーマは本質的に異なるものであると考えることができます。 したがって、フォームのスキーマは仮にAPIのスキーマと一致していても、別で定義することを検討してみてはいかがでしょうか。 zodを生成した方が適しているケース ここまで、zodを生成しない選択肢について考察してきましたが、zodを生成した方が適しているケースもあります。 外部サービスのAPIを使用する場合 自分たちが管理していない外部APIを使用する場合は、zodによるランタイム検証が有効な選択肢となります。 なぜなら、OpenAPIスキーマとバックエンドの実装に齟齬があっても、バックエンド側で修正することができないためです。 また、外部APIは予告なく仕様が変更されることもあります。 このような場合、フロントエンド側で防御的にランタイム検証し、不正なデータを早期に検出することで、予期しないエラーを防ぐことができます。 バックエンドでTypeScriptを使用している場合 バックエンドでTypeScriptを使用している場合、Orvalによるzod生成を活用することで、バックエンド側でバリデーションを行うことができます。 ただし、これはフロントエンドの話ではなく、バックエンドの話です。 (ここまで「フロントエンドにおける」と強調してきたのはこのためです) バックエンドでは、フロントエンドから送られてくるリクエストボディやクエリパラメータを信頼すべきではありません。 したがって、バックエンド側でランタイムバリデーションを行う必要があります。 このとき、OpenAPIからzodスキーマを生成することで、バリデーションのコードを自動生成でき、保守性を向上させることができます。 補足: APIの型の使い方について zodの話とは少し逸れますが、Orvalを使う際に注意したい点として、APIの型をコンポーネントからAPIクライアントまで使い回すというアンチパターンがあります。 前提として、APIのレスポンスはJSONであり、それはシリアライズされたDTOに過ぎません。 JSONはドメイン知識を持たず、ドメインモデルとして機能しないため、Orvalが生成した型をフロントエンド側であたかもドメインモデルであるかのように直接依存すると、様々な箇所で不整合が発生する可能性があります。 そのため、フロントエンドではフロントエンド用のドメインモデルを定義することをおすすめします。 そして、DTOをドメインモデルに変換するMapperを実装することも、一つの有効なアプローチです。 この辺りの話は以下の記事が参考になるため、よろしければ読んでみてください。 フロントエンドエンジニアが「自分はJSON色付け係」と自虐する理由を考察した フロントエンドにおける「型」の責任分解に対する1つのアプローチ まとめ 本記事では、フロントエンドでOrvalを使用する際に、zodではなくFetch Clientを生成するという選択肢について考察しました。 重要なポイントは以下の4つです。 Fetch Clientを生成することで、zodよりも簡潔に型がついたAPI通信の処理を書ける ランタイム上でのOpenAPIとバックエンドの齟齬検出は、バックエンドのテストで対処する考え方もある フォームのスキーマとAPIのスキーマは本質的に異なる目的を持つ APIのレスポンスはシリアライズされたDTOであり、ドメインモデルとは区別して扱うことが望ましい これらは一つの考え方であり、プロジェクトの状況やチームの方針によって最適な選択は異なります。 それぞれのアプローチのトレードオフを理解した上で、プロジェクトに適した方法を選択することをおすすめします。
アバター
本記事は【 Advent Calendar 2025 】の11日目の記事です。 はじめに こんにちは!マイナビのN.Yです。 2025年12/1(月)から12/5(金)に開催された「re:Invent2025」に参加しました。 自分にとって初海外出張、初アメリカで不安もありましたが、 結果的に多くの学びがあり、あっという間に過ぎた1週間をご紹介したいと思います。 筆者について 新卒入社3年目 社内のクラウド支援部署所属 英語とは大学受験でお別れ、英語圏への渡航経験ゼロ re:Inventとは AWSが主催する世界最大規模のクラウドカンファレンスで、毎年12月に米国ネバダ州ラスベガスで開催されます。 今年は14回目の開催となり、世界中から約60,000人、日本からも1900人近くが参加したらしいです。 基調講演(Keynote)をはじめ、3000以上のセッション、パートナー企業の展示会(EXPO)、たくさんのイベント、ノベルティグッズなど、学びからエンタメまで幅広く展開されます。 会場 「ラスベガス・ストリップ」と呼ばれるカジノ街にある複数のホテルが会場になります。 最も離れているホテルは歩いて1時間かかるほどの距離があります。 開催期間中、参加者は会場間をつなぐシャトルバスやモノレールが無料で利用できるため、それらを駆使して移動します。 ホテル自体も巨大なので、スケジュールを組む際には移動時間も考慮する必要があります。 今回、マイナビメンバーが宿泊した Harrah's は、メイン会場のVenetianへも歩いて行ける距離にあり、アクセス良好でとても便利でした。 一週間の過ごし方 ここからは開催期間中の様子を、写真を交えながらご紹介します。 前段で述べたように、期間中さまざまなイベントやセッションが開催されています。 あくまでも初めて参加した人間が体験した一例として眺めていただければと思います。 ざっくりスケジュール 11/30 羽田空港出発 -- 日付変更線 -- 11/30 サンフランシスコ経由でラスベガス到着 12/1 ~ 12/4 re:Invent参加 12/5 ラスベガス出発、サンフランシスコ空港経由 -- 日付変更線 -- 12/6 羽田空港到着 2025/11/30(0日目) 15:00 羽田空港に集合 指定されていた集合時間は15:00でしたが、心配性のマイナビメンバーは13:00くらいには全員集合していました。 今回はJTBツアーで参加したため、案内に従って搭乗券発行、荷物預け、保安検査へと大きなトラブルなく進むことができました。 18:00(JTC) 羽田空港→サンフランシスコへ出発 約9時間の長距離フライトです。機内食が2回提供されました。 有料で機内Wi-Fiが利用でき、過去のレポート記事を読んだり、セッションスケジュールを組み直したりした後は、寝て過ごしました。 ーー日付変更線ーー 19:00(PST) サンフランシスコ→ラスベガス到着 トランジットのサンフランシスコで約6時間待機の後、 1時間ほど飛行機の出発が遅れ、現地時間の19時頃にラスベガス(ハーリーヤード)空港に到着しました。 re:Invent参加者は、はじめに入場バッジを取得する必要があり、空港や会場で手続きができるのですが、この日は間に合わず翌日に取得することにしました。 ホテルに着いた後は、荷物を置いて早速ラスベガスの街を軽く散策しました。 翌日に備えて早めにホテルに戻って就寝しました。 2025/12/0(1日目) 8:00 ホテルベネチアンへ移動 初めてのラスベガスの朝! 先輩と、すごい、でかい、すごい、とか言いながら本会場の Venetian へ移動しました。 入場バッジを取得し、まずは朝食。 果てしなく広い会場に円卓が広がっており、ビュッフェ形式で朝食が用意されていました。 予約していたセッションまで時間があったため、毎年恒例となっているDatadog社提供の滑り台を滑ったり、巨大黒板に社名を刻んだりしました。 SWAGエリアに行くと、re:Invent参加者特典のパーカーとボトルをゲットできました。 会場の至る所に給水できるところがあるので、もらったボトルは持ち歩いておくと便利です。 パーカーは裏起毛で、ラスベガスの夜や なぜか冷房が効いている会場では非常に助けられました。 10:00 Mandalay Bayへ移動 Venetianから、最南の会場であるMandalay Bayまでシャトルバスに乗って20分ほどで到着しました。 午後は、参加者が4名1チームに分かれて他チームと対戦する形式のセッションである「Game Day」に参加したり、いくつかの実践型セッションを受講しました。 18:00 セッション終了 全てのセッションを終えて、開催前に日本での re:Invent参加者交流会で知り合った方と合流しました。 開催期間中は日本人が集まりがちと噂のお店「noodle asia」に行きました。 私たち以外に日本人はおらず、時間帯によるのかなと思いつつ、 3000円くらいするワンタンメンを、味わいながらいただきました。 2025/12/2(2日目) 7:00 KeyNote この日は re:Invent メインコンテンツの1つである AWS CEO Matt Garman氏の Keynote が午前中にあるため、早めにホテルを出てVenetian会場へ向かいます。 朝食を食べてから会場へ向かったところ、すでに本会場は満員だったため中継会場へ案内されました。 本会場に入るためには1時間前くらいから並んでおく必要がありそうです、、 CEOのKeynoteでは、新しい機能やサービスが次々に発表されました。 サービスアップデート紹介タイムアタックが始まったときは、聴衆をワクワクさせる演出に感動しました。 興味のある方はAWS の公式Youtubeに公開されているので見てみてください。 会場へ向かう途中、バケットくんに遭遇!! 気さくな方で、一緒に写真を撮ってくれました。 当日に Keynote でGAが発表された、S3 Vectors のジャケット着用バージョンでアツいです。 バケットくんについてもっと知りたい方はこちらも参照: 【AWS re:Invent2024】S3バケットくん徹底解剖 | マイナビエンジニアブログ Keynoteが終わった後は Caesars Forum へ向かい、AWSについて楽しみながら学べるカードゲーム「BuilderCards」をゲットしました。 社内でメンバーを募って遊ぼうと思います。 11:00~ セッション 午後のセッションのため、Mandalay Bayへ移動。 「DDos攻撃からAWS WAFを活用してサービスを守り品質を保つ」というシナリオのGameDayにマイナビエンジニア2名で参加しました。 セッション名:AWS GameDay - Winning the DDos Game (SEC403-R) GameDayは、参加者が4人1チームに分かれて競うセッションです。 結果は17チーム中4位でした。(チームメイトの外国人がつよつよだった) 高めの言語の壁はありましたが、WAFコンソール画面での操作や、攻撃的なアクセスを特定して制御する方法は勉強になりました。 終了後、同じチームだったイスラエル人にLinkedInのアカウントを持ってるか聞かれました。 海外の方との交流は名刺交換ではなく、LinkedInのアカウントを作成しておくと良さそうです。 19:00 Japan Night 夜は今回ツアーに参加させていただいた JTBさん主催の日本人向け交流イベントに参加しました。 普段、他社のエンジニアと交流する機会があまりないのですが、交流会ならではの情報交換ができて有意義な時間となりました。 ネットワーキングも re:Invent の醍醐味であることを痛感しました。 2025/12/3(3日目) 1日を通してセッションに参加しました。 参加したセッション Accelerating Incident Resolution: AI-Driven Root Cause Analysis (COP320-R2) AIによる自動インシデント調査機能である CloudWatch Investigation を使用してアラート発生時のトラブルシューティング体験 Building Serverless applications with Terraform workshop (CNS312-R) Terraformを使ったサーバレスアーキテクチャの構築体験ワークショップ Amazon ECS observability patterns and design decisions (CNS351-R1) ECSのオブザーバビリティやベストプラクティスな運用方法についてスピーカーと参加者がディスカッションするChalk talk。翻訳機がないと、ちょっと何言ってるか分かんない状態でしたが、アメリカならではの熱いディスカッションを味わえたのは良い経験でした。 Build Streaming Analytics Dashboards in Minutes with Kiro CLI (IND312-R1) Kiro CLIを使用して自然言語でのCDKアプリケーションの構築を行うハンズオン。デプロイ待ち時間が長く、スピーカーと参加者が生成AIのアンチテーゼについて議論しているのを翻訳機で聞いていました。 この日は他に特筆することがないので、 午後になると会場に現れるおやつコーナーの写真を載せます。 おかげさまで日中帯は食事に困りませんでした。 2025/12/4(4日目) 8:00 朝食、EXPO 我々が参加するのはこの日が最後でした。 いつも通り朝食を済ませた後は、時間に余裕があったのでEXPOブースを巡りました。 自分は参加できなかったのですが、ブースを周りながらスタンプを集めることで豪華ノベルティがもらえる催しなどもあったみたいです。 AWSのデータセンターで管理されている物理サーバーが展示されていました。 業務で物理サーバーを見る機会がないので新鮮でした。 12:00~ セッション 午後はハンズオン形式のセッションであるWorkShopと、AWS Japanが主催する日本人向けのセッション「Japan Wrap-up Session」を聴講しました。 日本語でre:Inventを振り返ることができます。 16:00 認定者ラウンジ Venetian の2階にあるAWS認定資格保持者のみが入場できる認定者ラウンジに寄ってみました。 事前に認定資格アカウントの登録をすることで、最初からバッジに印(チェックマーク)がつきます。 自分は登録を忘れていたので、受付で説明して Credly の認定証画面を見せると快く印シールを貼ってくれました。 中に入ると軽食や、いくつかの椅子とテーブルが用意されていて、多くの人がくつろぎながらKeynoteの中継を観ていました。 20:00 re:Play re:Inventを締めくくる、大規模屋外パーティです。 PM19:00からAM0:00まで開かれていて、東京ドームくらいの広さの会場に飲食ブースや体験型イベントブース、ライブ会場が併設されていました。 2025/12/5(帰国) 現地時間の AM3:30 頃にホテルのロビーに集合し、ラスベガス空港へ。 行きと同じくサンフランシスコ経由で帰ります。 帰りの飛行機は11時間くらいのフライトでした。 1週間の疲労と緊張がほどけ、着席した瞬間からの記憶がありません。 日本時間の12月6日18:00頃、無事に羽田空港に到着しました。 さようならラスベガス、ただいま日本。 最後に 今回参加してみて、会場、イベント、AWSが創造する世界のスケールの大きさに圧倒されつつ、世界中から参加しているエンジニアやAWSエキスパートとの交流を通して、普段の業務では得られない刺激を受けました。 エンジニアはもちろん、非エンジニアの方や勉強中の方など、あらゆる立場の方にとって参加する価値があると思います。 とは言え、費用や渡航準備、体力などを考えると気軽に参加できないのも事実です。 参加する機会を与えてくれた会社、そして一緒に参加したメンバーにはとても感謝しています! 最後までお読みいただき、ありがとうございました! イベント告知 12月23日にイベントを開催します!申し込みはこちらから▼ https://mynaviit.connpass.com/event/376769
アバター
本記事は【 Advent Calendar 2025 】の10日目の記事です。 はじめに ITD2-2-1のH・Tです。マイナビで内製開発しています。 開発現場で作業効率が落ちるタスクがあったので、自分でライブラリを作ったのでその紹介をします。 今後の内製開発でも大いに役立つかなと思ってます。 なにをつくったか Tatsumaki - Rails to TypeSpec Generator npmにすでに公開しています。 https://www.npmjs.com/package/@tyranno269/tatsumaki ライブラリが提供する価値 RailsのDBスキーマからTypeSpecモデルを自動生成するnpmライブラリでスキーマ駆動開発を簡易化できます。 開発モチベーション マイナビの開発現場では、Rails API + Next.js + zodによる型安全な開発が多いです、ただ以下の課題がありました。 手動変換の煩雑さ: Rails schema.rb → TypeSpec → OpenAPI → zodの変換チェーンが手動 型同期の遅延: Rails側の変更をフロントエンドに反映するのに時間がかかる 型不整合のリスク: 手動変換によるランタイムエラーの発生 OpenAPIからZod生成では Orval というライブラリを使用しています。 これによってOpenAPIからシームレスに実装を進められます。一方でバックエンドのRailsからTypeSpecに書き出すのは少々手間をかけていました。ここが自分は煩雑だなと感じてました。これを解決しようと思ったのがライブラリ開発のモチベーションです。 Tatsumakiによる解決 # docsディレクトリで実行 npx @tyranno269/tatsumaki # → rails.tsp自動生成 → TypeSpec → OpenAPI → orval → zod型定義 機能 Rails schema.rbから自動でTypeSpec生成 Rails enum完全対応 - モデルファイルからenum定義を自動抽出・生成 monorepo対応で柔軟なプロジェクト構造をサポート 型安全性をRailsからフロントエンドまで一貫して確保 Rails互換の単数形化 ( company_branches ​ → CompanyBranch ​) 参考: activesupport/lib/active_support/inflections.rb 想定プロジェクト構造 project/ ├── backend/     # Rails API │   ├── app/models/  # Rails enum定義 │   └── db/ │       └── schema.rb ├── docs/        # TypeSpec (実行場所) │   └── rails.tsp (生成される) └── frontend/    # Next.js + zod Rails Enum RailsにはModel層にEnum値を定義します。書き方が多様にあります。Schema.rbではintegerですが、APIレスポンスとして返す値はStringのケースが多くあるので対応が必要でした。ただしEnumもi18nで翻訳ファイルを定義するとバックエンドから日本語化して返せますが、フロントエンドメンバーと相談した結果、翻訳はFE側で対応すると良いとの結論になり、純粋にmodelファイルの定義をTypeSpecに出力します。 対応するenum形式 class Company < ApplicationRecord # ハッシュ形式 enum :company_status, { disabled: 0, enabled: 1, suspended: 9 } # 配列形式 enum :status, [ :active, :archived ] # %i記法 enum :priority, %i(low medium high) # キーワード引数 enum priority: { low: 0, medium: 1, high: 2 } end class Book < ApplicationRecord   enum :status, [ :draft, :published, :archived ] end 生成されるTypeSpec namespace CompanyEnums {   enum CompanyStatus {     disabled,     enabled,     suspended,   }   enum Status {     active,     archived,   } } namespace BookEnums {   enum Status {     draft,     published,     archived,   } } model Company {   id: int64;   company_status: CompanyEnums.CompanyStatus; // default: "enabled"   status: CompanyEnums.Status; // default: "active"   created_at: utcDateTime;   updated_at: utcDateTime; } model Book {   id: int64;   name: string;   status: BookEnums.Status; // No naming conflict with CompanyEnums.Status   created_at: utcDateTime;   updated_at: utcDateTime; } enum機能の特徴 名前空間による衝突回避 - CompanyEnums.Status ​ vs BookEnums.Status ​ schema.rbとの連携 - テーブル定義があるモデルのみ処理 型置換 - int32 ​フィールドを適切な enum​ 型に変換 全Rails enum構文対応 - ハッシュ、配列、%i記法、キーワード引数 その他にも、 a_matsuda さんの Stateful_enum といったようにenum定義からブロックでイベントを記述するといったGemもあります。TypeSpecの出力ではブロック部分は回避する工夫もしました。 開発フロー統合 Tatsumakiの設計により、Rails schema.rb + enum → TypeSpec → OpenAPI → zod の完全自動化チェーンを実現し、型安全なフルスタック開発を支援可能になりました。 インストール・使用方法 # 実行(最新版が自動取得される) npx @tyranno269/tatsumaki # 既存ファイル上書き npx @tyranno269/tatsumaki --force # カスタム出力ファイル npx @tyranno269/tatsumaki --out models.tsp --force # 既存ファイルに追記 npx @tyranno269/tatsumaki --append マイナビでの今後の活用方法 TypeSpecディレクトリ構成次第にはなりますが様々なパターンの開発で応用できうるかとおもいます。 パターン1 project/ ├── backend/     # Rails API │   ├── app/models/  # Rails enum定義 │   └── db/ │       └── schema.rb ├── docs/        # TypeSpec (実行場所) │   └── rails.tsp (生成される) └── frontend/    # Next.js + zod 生成されるrails.tspにroutesになるオペレーション情報を記述していくパターン パターン2 project/ ├── backend/     # Rails API │   ├── app/models/  # Rails enum定義 │   └── db/ │       └── schema.rb ├── docs/        # TypeSpec (実行場所) │   └── rails.tsp (生成される) │   └── routes │       └── admin │           └── admin.tsp (ex:管理サイトの管理者一覧・詳細など) │       └── user │           └── notice.tsp (ex:ユーザーサイトのお知らせ一覧・詳細など) └── frontend/    # Next.js + zod 生成されるrails.tspにroutesで定義したオペレーションをimportして組み込んでいくパターン パターン3 project/ ├── backend/     # Rails API │   ├── app/models/  # Rails enum定義 │   └── db/ │       └── schema.rb ├── docs/        # TypeSpec (実行場所) │   └── rails.tsp (生成される) │   └── main.tsp (出力の親となるファイル) │   └── models │       └── admin.tsp (rail.tspからnamespaceの該当部分をコピペで定義) │       └── notice.tsp (rail.tspからnamespaceの該当部分をコピペで定義) │   └── routes │       └── admin │           └── admin.tsp (ex:管理サイトの管理者一覧・詳細など) │       └── user │           └── notice.tsp (ex:ユーザーサイトのお知らせ一覧・詳細など) └── frontend/    # Next.js + zod 生成されるrails.tspからコピペで抽出し利用していくパターン 最後に 実際のRailsのプロジェクトで使用される主要機能をカバーできたかなと思っています。よかったら利用してみてください。今回は自分の作業時間を減らしたいモチベーションで生み出したライブラリなので実際のユースケースで対応できていないこともあるかなと思います。要望があれば Github Issue に上げて頂けますと嬉しいです!! イベント告知 12月23日にイベントを開催します!申し込みはこちらから▼ https://mynaviit.connpass.com/event/376769
アバター