株式会社メルカリのブログ - TECH PLAY

TECH PLAY

株式会社メルカリ

株式会社メルカリ の技術ブログ

292

Design System チームの engineering manager をしている vwxyutarooo です。 私達はメルカリのアプリ・ウェブ開発に利用している Design System をフルリニューアルしました。 この記事で Design System に抱えていた問題とそれをどのように解決しようとしているのか、そのコンセプトを紹介していきます。 既存の Design System に抱えていた課題 既存の Design System は社内で 3.0 と呼ばれており、 GroundUp と呼ばれるメルカリのアプリとウェブを刷新するプロジェクトの一部として2020年頃からデザイン・開発が始まりました。 3.0 と聞くと随分進んでいるように見えますが、様々な開発背景により特定プラットフォームを対象にしたものや、日の目を浴びることのなかった過去のバージョンなどが含まれており、実質 3.0 が全社的に取り組んで開発された最初の Design System v1 となっている背景があります。 おおよそ 5 年の運用期間を経て、3.0 で作られたコンポーネントは当初の利用想定ケースを大きく超える状況に対処する場面が多く見られるようになりました。その結果、多数の新規機能開発で Design System のコンポーネントでは表現できず、シンボルをディタッチして変更を加えたコンポーネントや社内でカスタムコンポーネントと呼んでいる非公式のコンポーネントが多数作成される事態に陥っていました。 なぜこのようなことが起こったかを、ItemObject と呼ばれているコンポーネントの例を用いながら簡単に解説します。 これは複数のスクリーンで頻繁に使用されるコンポーネントです。3.0開発時は共通と思われるパーツだったため単一のコンポーネントとして切り出され、いくつかのユニークな要素をプロパティによって表示・非表示を切り替えることで対応していました。社内ではこれを polymorphic API と呼んでいます。 しかし 3.0 リリース後の継続した機能開発により必要な要素は増え続け、必要とされる表示パターンは増え続けました。 この方式の難しいところは個別の UI 最適が進むほど考慮すべき組み合わせパターンが倍に増えていく点です。さらに、特定の要素 A が表示されているときに出現する要素 B or C のように構造が深くなっていき、複雑さが増していきます。私達はこの構造をコンポーネントの Polymorphic API と定義し避けるべきコンポーネントデザインと考えています。 この状況を打開するため、コンポーネントの定義を刷新し異なるコンセプトで Design System を4.0として再構築することにしました。 Atomic Design Methodology 新しいコンポーネントの設計指針として Atomic Design を採用することにしました。古くから存在した概念で、2013年に Brad Frost によってそのアイディアが初めて提唱されたものです。 Atomic Design – Brad Frost Atomic Design は旬をすぎたものとして扱われるようになって久しいですが、これは多くの場面で誤解のもとに運用されたり、拡大解釈されたりすることで、本来意図していない利用をされていることが大きいと考えています。 Brad Frost: Is Atomic Design Dead? – Hatch Conference Berlin 2023 よくある誤解として Atomic Design を実装リードで適用しようとしてしまう、或いは実装でのみ実現しようとしてしまう例がよく見られます。 私達の解釈では、Atomic Design は Design System を開発・運用するデザイナーとエンジニアのためのコンポーネント設計フレームワークであり共通言語です。実装が Atomic Design を強く意識する必要はなく、利用者に強調すべき情報でもありません。 Atomic Designでは、UIの部品を最小単位の「atoms(原子)」に分解し、それらを組み合わせて「molecule(分子)」のようなより大きな部品を構成します。以前は一つの部品として扱っていたものを、複数の小さな部品に分割して組み立て直す考え方です。 Atomic design によるコンポーネントの分解・設計手法に関しては Brad Frost 本人を含む多くの解説記事や動画が存在するため詳細は省略しつつ、先程紹介した Item Object を 4.0 の考え方で構築した例で簡単に紹介します。 まずセオリー通り各コンポーネントをその役割の最小単位にまで分割していきます。 以下の画像の例も、3.0 では1つのコンポーネントとして扱われていましたが、4.0 は 2 つの atoms と呼ばれる最小単位のコンポーネントになります。そしてこれらのパーツを組み合わせてさらにパーツを構成します。この atoms から構成されるコンポーネントを molecule と呼びます。 これを繰り返し、最終的にバラバラのパーツから ItemObject などのよりハイレベルなパーツを構築可能にします。前提として UI をパーツで組み立て可能にするという点を念頭に置きつつ、組み立て後のパーツが汎用的なコンポーネントであるものを molecules や organisms として提供します。 ItemObject のようにユースケースが細かく別れているコンポーネントに関しては使用頻度の高い汎用的なものを優先的に Design System のコンポーネントとして管理しつつ、利用シーンが多くないものや僅かな要素の違いを持つユースケースにはあえて organisms として完成形を提供せず、利用シーンで組み立てるようにしています。 コンポーネントを利用時に組み立てる、というのも場合によっては利用者の負担になります。そのため、組み立て方法の例をレシピ/設計図として配布し補助的に活用しています。 レシピ/設計図を提供するかどうかは、コンポーネントの利用頻度やコンテンツ/コンテキスト依存度から判断しますが、レシピや設計図 (Blueprint) に関しては Atomic Design とは異なる概念となるため次の節でもう少し詳しく紹介します。 Component Design Strategy Atomic design は Design System のコンポーネントの分解・構築のためのフレームワークを提供しますが、なにがコンポーネントであるべきか、どんなコンポーネントが Design System として管理されるべきかその境界を示してはいません。 私のチームでは Design System から内向きのレイヤーを Atomic Design で、外向きのレイヤーを自分たちで独自に設計しました。次の図はそのレイヤーを簡易的に表現したものです。内側に行くほど Design System で、外側に行くほど Design System ではなくなります。厳密に Design System チームの持ち物として責任を追うのは青の領域ですが、現実的にはっきりとした境界線を引けることは稀で、その境界はグラデーションになっていることが多いため、そのグラデーションを意図してこのような図で表現しました。 1つ1つのレイヤーを順番に解説していきます。 Snowflakes ワンオフコンポーネント。コンテンツやコンテキストに依存しているなどの理由から Design System としては考慮されない 控えめな使用を推奨 Custom Component Design System のコンポーネントスペックでは表現できない UI を構成するため、シンボルからディタッチされたり、stroke など Figma 上で制約を設けることができないプロパティをコンポーネントのスペックを超えて改造されたものを指す Design System としては非常に不本意なコンポーネントであるため将来的にそのスペックが Design System でサポートされるか、或いは UI の仕様を調整することで薄くなっていくべきレイヤー Blueprint 直訳すると青写真という意味になりますが、設計図や完成予想図の意味で使用される Blueprint は、Figma のデザインデータから iOS, Android, Web のソースコードまで包括的にその設計図が提供される 主に Design System Component とするにはコンテンツ/コンテキスト依存が強いが頻繁に活用されるもの、或いは snowflakes のようなワンオフに近い用途を持つが、その組み立て方法が複雑なときに活用する Design Recipes Figma のデザインファイルでのみ設計図が提供されるコンポーネント。ソースコード上では提供されない フレームワークの恩恵を受けるなど実装上コンポーネントとして定義する必要性が低いものに対し、デザイン効率化のため Figma のデザインファイルでのみコンポーネントとして利用 (レイアウト系のコンポーネントに多い) Blueprint がデザイン (Figma) とソースコード両方のレシピを提供するのに対し、Design Component はデザイン (Figma) のレシピのみが提供される Design System コンテンツ/コンテキスト非依存で再利用可能な独立したコンポーネント 実はこれらのレイヤーは Brad Frost により提唱されている vocabulary に深く影響を受けているため、彼に詳しい人にとっては既視感のあるものになっています。 ただこれらには Atomic Design のような明示的な名前はついていないため、単に記事中の表現から component vocabulary と呼ぶことにします。 Design system components, recipes, and snowflakes すべての UI コンポーネントが Design System で完結するデザイン組織が最も strict なデザイン組織と言えるかもしれません。実現は難しいですが、そのような組織も少なからず存在しているようです。 このモデルは、もう少し合理的な妥協ラインを求めた場合にとてもフィットします。プロダクト開発でどうしても発生するコンテンツ依存なコンポーネントをワンオフとして一定数許容しつつ、そこにボキャブラリーとレイヤーを与えることで管理対象とし、薄く維持するためのマインドセットを生み出すことができます。そして、Design System と Snowflakes の間を埋める再利用可能だが Design System として管理するには十分な動機が (まだ) ないものをレシピとすることで、全体のコンポーネントレイヤーにグラデーションを与え、メンテナンスコストとリターンの最適化を図る意図があります。 コンポーネント設計・分割指針 次に Design System コンポーネントの設計・分割指針を見ていきます。冒頭で紹介した通り、以前のシステムでは最終的に1つのコンポーネントに振る舞いや variant を持たせ過ぎたことで利便性やメンテナンス性の低下を招きました。 これらの教訓を踏まえ、新しいシステムではセマンティックでシンプルな分解を重視し、以下の4つをコンポーネント設計の指針としました。 Semantic “ビジュアル的に近いものをコンポーネントとするのではなく、挙動や意味的な分類によってコンポーネントを定義/分割するし常に一貫した振る舞いを提供します。” 例としてメルカリにはチップと呼ばれているラウンド状のクリッカブルなコンポーネントがあります。 3.0 では全て1つのコンポーネントとして定義されていましたが、以下のようによく似た見た目を持つコンポーネントに対して大きく異なる振る舞いをすることが分かります。 トグル: タップする事にステートの変化 リムーバブル: タップすると消える 文字入力: タップが別のアクションのトリガーとなる 一見、共通コンポーネントの異なる状態を利用しているだけに見えますが、タップ可能領域やタップ時、およびホバー時 (Web) のスタイルなども違ってきます。1つのコンポーネントで表現するには不要な依存関係を考慮する必要が出てくるため、コンポーネントの分割対象とすることで依存関係がシンプルになりメンテナンス性が向上します。 Properties “異なる色、角の丸みや角ばっているなどに基づいてわずかな視覚的バリエーションを持つことができます。但しコンポーネントの形や振る舞いを変えることはできません。” 先に紹介したチップコンポーネントでは、ストロークのスタイルを solid/dotted のようなプロパティを持たせています。これは視覚的なバリエーションであり形や振る舞いを変えることはないため、1つ目の Semantic 指針を侵害しません。 Optional Elements “コンポーネントはオプショナルな要素を持つことができる (オプションのアイコンやテキストなど)” ボタンの prefix/suffix アイコンのような子要素を持たせることができます。 次の4つ目の指針で紹介する No polymorpihc API と相反することがないよう注意する必要があります。 No polymorphic API “一貫したAPIを持つべきである (必須となるプロパティが別のプロパティの存在の有無に基づいて変更されるべきではない)” 画像とコードの例を用いて解説します。次の画像は、3.0の古い Design System で定義されていた ItemThumbnail というコンポーネントで、3.0 では Large size のみに割引や値段の要素が許可されていましたが、これは polymorphic API とみなし、新しい指針では避ける設計としています。 “特定の条件の時に発生するネストされた条件”には、最終的に冒頭で紹介したような管理の複雑性を生じます。 Polymorphic API を含むコード例: ItemThumbnail( size = Medium ) ItemThumbnail( size = Large( discountPrice = 900¥, price = 1,000¥ ) ) 4.0 ではコンポーネントの分解と再構築により、これらの問題を回避しています。ItemTile という Organism コンポーネントを用意し、構成要素として ItemThumbnail を含む Atoms, Molecules を持たせています。 Polymorphic API を含まないコード例: ItemThumbnail( leftBottomContentSlot = <other atoms/molecules/organism> ) 結果 Atomic Design を採用した私達の Design System は、最終的に150弱の数のコンポーネントに再分解され、以下のようなコンポーネント分布の構成になりました。これが適切なのか過不足あるのかは現時点で判断することはできませんが、今後の運用で明らかになっていくはずです。 Atoms: 50 Molecules: 60 Organisms: 40 また、冒頭でから例として上げている ItemObject はそのレイアウトだけを提供する ObjectLayout と、パーツを組み上げる blueprint に分かれて提供する方法に着地しました。 ObjectLayout: ItemObject (blueprint): 条件分岐などで膨れ上がったコードも、iOS (Swift) で700行あったものが30行弱にまで削減されました。実際組立時に発生するコードもあるため純粋な削減とはなりませんが、コンポーネントの抽象化や汎用化に失敗していた部分が単純化できたと考えられます。 まとめ 今回の Design System 4.0 刷新プロジェクトを通じて、私達は過去の課題と向き合い、より柔軟かつ持続可能なシステムへと進化させるための重要な学びを得ました。 コンポーネントの過度な汎用化が複雑性を生み、メンテナンス性を著しく低下させる教訓から、Atomic Design の原則に立ち返り、コンポーネントを最小単位に分割し、再利用性を高める設計へと移行しました。これにより、各コンポーネントが単一の責任を持つようになり、変更やテストが容易になりました。 同時にコンポーネントがどうあるべきかを考え直しゼロから組み直すことで 3.0 で得た知識と経験を新しいシステムに反映することができました。 今後 Figma AI や Figma MCP をはじめとするデザイン及びコーディングの自動化において、ブランディングコンセプトを反映し、かつセマンティックな意味を持つ Design System コンポーネントはハブとしての役割や、AI に対してのコンテキスト提供者としてその重要性を増していくと考えています。 また続報があればお伝えしていきます。 最後まで読んでいただきありがとうございました。
こんにちは。メルコイン フロントエンドエンジニアの@y-arimaです。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の18日目の記事です。 本記事では、Web版メルカリからメルコインAPIへの疎通確認を行ったPoC(Proof of Concept)について、技術的な課題と解決策、そして得られた知見を紹介します。 背景と目的 現在、メルコインの機能を利用できるWebサービスは存在しません。そこで、技術的な検証として、Web版メルカリからメルコインAPIにアクセスできるかどうかを試してみることにしました。 今回のPoCでは、 Web版メルカリからメルコインAPIへの疎通確認 を主な目標とし、技術的な実現可能性を検証しました。 技術的な課題と解決策 1. 認証設計の複雑さ 既存のWeb版メルカリの認証システムは独自のユーザーID体系を使用していましたが、メルコイン側のマイクロサービスは異なるクラスタに存在しており、セキュリティ上の理由からそのIDをそのまま受け付けない仕様となっていました。 この問題を解決するため、プライバシーを考慮した識別子(PPID: Pairwise Pseudonymous Identifier)を利用する新たなOIDC(OpenID Connect)クライアントを作成する必要がありました。PPIDは、異なるサービス間でお客さまを安全に識別するための仕組みです。PPIDの詳細については、以下の記事をご参照ください。 メルコインにおけるシステム間のデータ分離を実現するための通信アーキテクチャ Applying OAuth 2.0 and OIDC to first-party services 2. インフラ周りの設定 メルコインAPIへのアクセスを可能にするためには、Gatewayやネットワーク周りなどのインフラ設定が必要でした。普段フロントエンドエンジニアとしての業務がメインの私にとって、この辺りは馴染みの薄い領域でした。 しかし、アーキテクトチームやSREチームなどさまざまな方にサポートいただき、必要な設定を進めることができました。この経験を通じて、マイクロサービス間の連携には多くのチームの協力が必要であり、またフロントエンドエンジニアとしても、インフラストラクチャーの知識の重要性を実感しました。 3. 未知のコードベースでの開発 今回のPoCでは、普段触れることのない複数のリポジトリでの作業が必要でした。大規模なコードベースを短期間で理解し、必要な修正を加えていく必要があり、これは大きなチャレンジでした。 この課題に対しては、最新のAIツール(特にCursorなどのAI搭載エディタ)を積極的に活用することで対応しました。特に新たなOIDCクライアントの設定では、普段触れることのないTerraformのコードを修正する必要がありました。しかしCursorの機能を活用して既存のOIDCクライアントの設定を分析し、その構造や仕組みを理解した上で、新しいクライアントの設定を進めることで、開発効率を向上させることができました。 実装の成果 今回のPoCでは、以下の2つの機能を簡易的に実装することで、Web版メルカリからメルコインAPIへの疎通確認を行いました: ビットコインの価格をチャートで表示する機能 取引報告書をダウンロードする機能 これらの機能実装を通じて、認証やAPI通信、ファイルのダウンロードなど、さまざまなパターンでの疎通確認を行うことができ、 Web版メルカリからメルコインAPIへのアクセスは技術的に実現可能である ことを確認できました。 PoCを通して学んだこと 一人での限界と効率的な進め方 PoCを進める中で、フロントエンドエンジニア一人でこのような大規模な検証を完遂することの難しさに直面しました。このような状況を受け、多くのチームの方々に協力いただきましたが、PoCという性質上、本番開発に比べて優先度が低く、各チームへの依頼が完了するまでに時間を要することも少なくありませんでした。そこで、未経験の領域にも果敢に挑戦し、「まずは自分でできることを探る」という姿勢で取り組むことで、チーム間のコミュニケーションコストを抑えつつ、効率的な開発を進めることができました。 AIツールの活用 前述した未知のコードベースの理解促進はもちろん、チャート機能や取引報告書のダウンロード機能の実装においても、AIツールは大きな効果を発揮しました。 開発の流れとしては、以下のプロセスを繰り返しました。 Cursorに要件を伝えてコードを生成 細部を自分で調整 または Cursorを活用して修正 動作確認 問題があれば2に戻る この方法により、極めて短期間で機能を完成させることができました。 また、これらの機能の実装では、フロントエンドエンジニアとして普段から慣れ親しんでいる領域だったことが、AIツール活用の大きなアドバンテージになりました。 正しいコードの形が頭の中にあるため、Cursorが生成したコードの良し悪しを即座に判断でき、適切な修正指示を出すことができたのです。この既存知識とAIツールの組み合わせにより、開発スピードは格段に向上しました。 複数のチームとの効率的なコミュニケーション メルカリグループのエンジニアリング組織は大規模であり、複数のチームから構成されています。 PoCを進める上で「誰に質問すれば良いか」が最初は不明でした。この問題に対しては、メルカリ全体のアーキテクチャを横断的に把握していアーキテクトチームに最初に相談し、必要なタスクと担当チームを特定しました。その後は、複数のチームに並行して質問や相談を行うことで、開発のブロッカーを最小限に抑えながら効率的に進めることができました。 まとめ 今回のPoCでは、Web版メルカリからメルコインAPIへのアクセスが技術的に実現可能であることを確認できました。この検証を通じて、フロントエンドエンジニアとしても認証やインフラなど、システム全体への理解を深めることの重要性を改めて実感しました。 また、AIツールの活用や効率的なチーム連携の方法など、今後のPoC開発にも活かせる知見を得ることができました。 この記事が、同様の技術的挑戦に取り組む方々の参考になれば幸いです。 明日の記事は @keitasuzukiさんです。引き続きお楽しみください。
Merpay & Mercoin Tech Openness Month 2025 の第17回目のブログ投稿です。 ntk1000 です。MerpayでKYCチームとPartner Platformチームのエンジニアリングマネージャーを務めています。本日は特定のチームについて話すのではなく、開発者体験(Developer Experience)を向上させるための会社全体のエンジニアリングOKRイニシアチブについて共有したいと思います。 1. なぜDevExなのか? Developer Experience(以下、DevEx)は、開発者が仕事においてどれだけスムーズに、ストレスなく、価値ある仕事に集中できるかを示す概念です。 Nicole Forsgrenらが提唱した研究では、"良いDevExは、開発者の満足度と効率性を高め、生産性と定着率を向上させることで、ビジネス成果にもつながる" とされています(参考: The SPACE of Developer Productivity )。 また、Googleも、"開発者が実際にどれだけの時間を本質的な価値創出に費やせているか" を重視しており、DevExの改善をプロダクトの品質とスピード向上の重要な要素として扱っています(参考: How Google Measures Developer Productivity )。 このように、DevExは単なる開発効率の指標ではなく、チームの健全性とプロダクトの競争力に直結する、戦略的なテーマです。 AIの台頭、事業の多角化、グローバル展開など、エンジニアリング組織の複雑性が増す中で、エンジニアの日々の業務には、集中時間の確保や自律的な判断の難しさといった新たな課題が生まれています。複雑性が高まるにつれて、個人の努力や善意だけでは対応しきれない構造的な摩擦が目立つようになってきているのです。 たとえば、私が担当しているKYCおよびPartner Platformチームは、社内の他チームやプロダクトに必要な共通機能を提供するプラットフォームとしての役割を担っています。そのため、私たちは多様化・グローバル化するサービスの要求に応える開発と、自チームのプロダクト自体の改善を並行して行う必要があります。しかし現実には、前者への対応に時間とリソースの大半を割かれてしまい、後者の改善が後回しになり、結果として前者の対応にも時間がかかってしまうというジレンマが存在しています。これは構造的な負債であり、個人やチーム単体の努力だけで解決できる問題ではありません。 だからこそ、私たちはDevExを単なる業務効率やスコア改善の話ではなく、開発チームの持続可能性とプロダクトの競争力を両立させるための戦略的な取り組みと位置づけました。複雑な環境の中で、自律的に動けるチームを育てるには、構造的な課題に対して全社的に向き合う必要があります。そのため、私たちはEMや開発チームだけにその責任を任せるのではなく、組織全体でDevEx改善に取り組む体系的なアプローチを選択しました。 2. 測るのは、行動と対話の出発点 私たちは DX という、サーベイベースの定性データとデリバリースループットのような定量データを組み合わせたDevEx可視化ツールを採用しました。目的はスコアを生成することではなく、チームが自分たちの働き方を客観的に見つめ直し、課題を言語化し、改善に向けた行動を起こすきっかけを生み出すことです。定量と定性を合わせて可視化することで、エンジニアやEMが感覚的に持っていた課題認識をチーム全体で共有できるようになり、そこから建設的な会話が始まります。 「測って終わり」にしないために、私たちは四半期ごとの改善サイクルを設計しました。サーベイは単なる数字の羅列ではなく、チーム内の声を可視化し、EMやチームメンバーがその背景にある課題を言語化するための出発点です。そこで得られた定量・定性のデータは、対話のきっかけとなり、チームが納得感を持って改善に向けたアクションを検討するプロセスを支えています。こうした仕組みによって、計測→判断→行動→振り返りというサイクルが継続的に回るようになっています。 3. 組織全体で機能する改善サイクルの設計 改善サイクルの詳細は以下の通りです: 計測 :四半期毎に15分前後の匿名サーベイの実施 判断 :EMがサーベイ結果を確認、チームとも議論して、改善に注力するエリアを判断 行動 :EMは判断結果を元に、具体的なアクションプランを作成し、チームとして実行 振り返り :チームのレトロスペクティブや次のサーベイ結果を元にアクションの効果を確認 このプロセスの主体はチームのエンジニアおよびEMです。Manager of ManagersやDirector、VPは各チームの実施状況や、チームからエスカレートされた課題の確認と解決に責任を持ちます(これによって、EMの改善努力が組織全体に反映される構造が保たれます)。 このプロセス設計には、セクション2で触れた「データをきっかけとした対話と行動」の考え方が反映されています。単にスコアを確認するだけでなく、数値とコメントから文脈を読み取り、現場で実行可能な改善策へと落とし込むことが重要です。そのために、各チームが自律的に進められるよう、プロセス自体はシンプルかつ反復可能な形で整備されています。 また、このサイクルは四半期単位で繰り返すものであり、定常業務と並行しながらも継続的に改善が進むよう設計されています。過剰な負荷を避け、着実な実行と振り返りを促すために、各チームが取り組む改善アクションは一つか二つに絞ることが推奨されています。具体的には、サーベイ結果を元に、Vote数、コメント数、業界や会社平均とのスコアの乖離などの要素から複合的に判断し、チームで対話を行いながら優先度の高い課題を特定します。その中から、現実的に取り組めるものを選定します。アクションの量よりも、実行可能性とチームの納得感を重視しています。 今回の取り組みでは、DevEx改善を個人やチーム単体の工夫ではなく、組織の仕組みとして整え、継続可能な文化として根付かせることを目指しています。実際に、今回のサーベイでは対象となるエンジニア100%からの回答を得ることができ、同じく100%全てのEMが改善アクションの提出・実行に参加しています。 高い参加率を確保できたポイントとしては以下の通りです: このプロセス構築をエンジニア組織全体のOKRとして横断的に取り組んだこと なぜDevEx改善に取り組むのか、背景とその狙いをエンジニアだけでなく組織全体にも継続的に発信したこと サーベイ実施やEMによる改善の検討期間中はLunch&Learn(ランチをとりながら学び、質疑応答ができる会)を積極的に開催し、接点を増やしたこと DXやサーベイに関する質問を受け付けるオープンドアセッションを複数回開催し、疑問や不安の解消につなげたこと プロセス開始前にAll Handsで改善サイクル全体を紹介し、意義や進め方への納得感を醸成したこと 4. チームを越えて見えた構造的課題 このように、改善サイクルはチーム単体の実行だけでなく、組織全体での振返りや支援を通じて持続的に機能する設計になっています。その結果、私たちはチーム単体では捉えきれない構造的な課題にも気づくことができました。 内部スコアは公開できませんが、全社共通で明らかになった課題は次のようなものです: Deep Work(集中できる時間)の不足 :エンジニアが集中を要する複雑な作業に没頭する時間が不足しているという課題です。会議・割り込み・不明瞭な優先順位により、多くのチームで集中が妨げられており、投票数が最も多かった項目でした。複雑な問題解決のためには集中した時間が必要ですが、絶え間ないコンテキストスイッチによってその時間は奪われてしまいます。これは単なる時間管理の問題ではなく、組織の設計や業務の優先順位づけが関係する構造的な課題です。 チーム横断連携における摩擦 :プロダクト開発はエンジニアリング部門だけで完結はせず、プロダクト・法務・CSなどさまざまな関連部署との連携が必要不可欠です。そして事業の多角化、組織の拡大によってチーム数・組織構造は複雑化していきます。この課題は業界平均との差が最も大きかった項目でした。これは私が担当しているKYCおよびPartner Platformチームでも自覚があり、本来は他チームが必要とする共通機能をスムーズに提供したいのですが、整備が間に合っておらず、他チームからの問い合わせ対応に多くの時間を要してしまっているのが現状です。 このような課題はいずれも、個々のチームやEMだけでは解決できない、より上位の構造や仕組みの見直しが必要な領域です。したがって、全社的な文化と仕組みの転換、たとえば集中時間を保護する働き方のルール整備や、チーム間連携をスムーズにするセルフサービス化の推進といった取り組みが求められます。 5. 現時点で見えてきたこと 実例:2つのドメインを持つチームからの学び 私が担当しているKYCおよびPartner Platformチームのサーベイ結果と改善アクションについて共有します。両チームをあわせて分析すると「ドキュメント」に関する課題が共通して浮かび上がりました。一方で、KYCチーム単体では「本番環境でのデバッグの難しさ」や「開発環境の整備不足」が強く指摘されるなど、ドメイン固有の課題も明確になりました。 特にドキュメントに関しては、最新情報の所在が不明確であることや、過去の経緯に関するナレッジが分散していることが要因で、問合せ対応や仕様確認に多くの時間を要しているという声を普段からも聞いていました。これは、プラットフォームチームとしての提供価値を最大化するうえで重要な改善領域です。 従来のドキュメント整備だけでは限界があると判断し、以下のようなアクションを早速進めています: AI/LLMを活用した過去の問合せやナレッジの検索・再利用ができる仕組みの構築 過去の設計ドキュメントやコードベースをもとに、自然言語で仕様を検索・確認できる内部ポータルの構築 更新頻度が高く、非構造的な情報も多い中で、LLMの柔軟性は有効だと考えています。まだ実験段階ではありますが、情報アクセスのしやすさはDevExに直結するため、引き続き取り組んでいきたいテーマです。 6. 最後に:DevExはプロダクト体験そのもの 良いプロダクトを作りたいなら、それを作る人たちにとって良い環境が必要です。DevExは単なるスピードや効率の話ではなく、明確さ・集中・流れの話です。 今回は初回の改善サイクルでしたが、高い関心と参加率をもって全社的に取り組むことができました。対象エンジニアの100%からの回答と、すべてのEMによるアクション提出という結果は、今後に向けた大きな一歩です。一方で、この取り組みを一過性のプロジェクトで終わらせず、疲弊することなく習慣として定着させていくことが次の課題です。 そして、DevEx改善はエンジニアリング組織の効率化にとどまるものではなく、提供するプロダクトそのものの体験価値の向上につながるものです。エンジニアが安心して集中できる環境を整えることが、結果的にユーザーにとっても価値ある機能や品質につながるという視点を忘れずに、今後も取り組んでいきたいと考えています。 私たちもまだ試行錯誤中です。同じような取り組みを進めている方がいれば、ぜひ一緒に学び合いましょう。 より良い開発体験を、一緒に育てていきましょう。 明日の記事は @y-arimaさんの「Web版メルカリにメルコインの機能を組み込む検証をした話」です。引き続きお楽しみください。
こんにちは。メルペイ iOSエンジニアの @shunta です。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の16日目の記事です。 今回は、WWDC25に現地参加してきたので現地の雰囲気やイベントなどについてご紹介します。 私は、今回が初めての参加なのでとてもワクワクしました! WWDCとは WWDCは、Appleが毎年開催している開発者向けのカンファレンスです。最新のiOSやmacOSなどの新機能が発表される他、さまざまなセッションやラボで直接Appleのエンジニアや各国のiOSエンジニアと話せる貴重な機会でもあります。今回初めて現地に行くことができました。 準備 WWDCはコロナ後からチケットが無料・抽選制になり、事前の応募が必要です。 チケットを確保したら、次はホテル・航空券の手配です。チケットを入手した時点で2ヶ月ほどしか猶予がないので、早めに取りました。 また、忘れずにESTAを申請しておきましょう。 Day -1(6月7日): アメリカ到着 時差ボケを考慮して前日の6月7日にアメリカに到着しました。 Apple Visitor Centerに行きました。WWDC期間中は混むとのことなので、当日着ていくAppleのTシャツやグッズを調達しました。 夜にはtry! Swiftコミュニティの飲み会に参加してきました。なんと日本から20~30人くらいの方々が集まっていました。 今までにWWDCに参加したことがある人が多かったので、やっておいたほうがいいことなどを教えていただき、とても役立ちました! Day 0 (6月8日): 前夜祭 WWDCの前夜祭イベントは夕方からだったので、それまでの時間を使ってGoogle Plexに寄ったり現地のFarmer’s Marketに行ってみたりと観光を楽しみます。 夕方からはAppleの旧本社であるInfinity LoopでWelcome Receptionに参加しました。 空港みたいな厳しい手荷物検査(WWDC期間中は毎日検査があります)を通過したら本人確認をして、WWDCのグッズなどをもらえます。 今年はタンブラーと、ピンズ、トートバッグ、キーアクセサリーです。 その後は参加者同士の交流会があり、 ・世界地図にピンを刺して出身地を示すコーナー ・DJブース ・大きいジェンガなどのボードゲームコーナーなど ・美味しいドリンクとフード などが用意されていて、世界のエンジニアと交流が盛んになるような仕組みが素晴らしかったです。 Day 1 (6月9日): Keynote当日 時差ボケで朝4時に目が覚めてしまい、早く行ってみようかなと思い、朝5時には会場に到着しました。Apple Visitor Center付近に集合です。着いたら誰もいなくて、一番先頭になりました。 待機列では6:30頃にドーナッツやコーヒーなど軽食が配られました。 8:00頃から入場を開始しました。先頭で待機していたのでTim Cook氏のXにも載っています。 https://x.com/tim_cook/status/1932275973606834627 席を確保した後は、Apple Park内のCaffè Macsで朝ごはんが用意されていました。ホテルのような高クオリティのご飯が提供されていてとても美味しかったです。 Keynoteの開始時刻になるとTim Cook氏やCraig Federighi氏が登場。その後大きなスクリーンでKeynoteが始まりました。 最初のF1カーの場面では笑いが起こったり、Liquid Glassや会場が沸いたりと現地でしかできない体験をしました。 Keynote後はお昼ご飯が用意されています。行列ができてしまったので全部の写真を撮れなかったのですが、いろいろな国の料理が提供されていました。これもとても美味しかったです。 Apple Park内に設置されたDownload Stationでは高速のネットが使えて、ベータ版をダウンロードできます。iOS 26を試したり、近くにいた人とVisionOS 26を入れて空間体験を共有できる機能などを試しました。 昼食後はDeveloper向けのKeynoteとも言えるState of the Unionを見た後、In-Person LabsというAppleのエンジニアに直接質問できるアクティビティがあります。 さまざまなジャンルのコーナーが用意されていますが、Design Labなどの人気なLabは事前予約制でWWDCに参加する前に予約が必要です。 私は、iOSのシミュレーターについて気になる点があったのでシミュレーターやXcodeなどデベロッパーツールに関して質問できるラボに行きました。 他にも、Keynoteに登場したF1カーと写真を撮ったり、Apple Park内を散策したりと盛りだくさんの一日でした。ディナーまで用意されていて一日中Apple Parkで過ごしました。 Day 2 (6月10日): セッション参加とスペシャルイベント 2日目はApplePark近くのDeveloper Center Cupertinoで行われた、夕方のDeveloper Activityに参加しました。公式サイトのセッションビデオでは見ることができないオリジナルセッションのようで、AppleのエンジニアにLiquid Glassをデザイン・実装する方法を実際にデモやスライドを用いて説明していただける貴重な機会でした。 夜にはSteve Jobs Theaterで「F1: The Movie」の試写会に参加しました。 WWDCのKeynoteで一番初めに紹介された映画です。 シアター内の撮影はできなかったのですが、Steve Jobs Theaterは普通の映画館に比べてディスプレイの発色や明るさが綺麗だったり、音響がとても良かったりシアターの設備にも感動しました。 もちろん映画の方もとても良かったので、公開されたら是非観てください! おわりに 初めてのWWDC現地参加でしたが、本当に濃い数日間でした。オンラインでは得られない現地ならではの体験や、世界中の開発者との交流、Appleのコミュニティへの力の入れ方などを肌で感じることができました。 他にも追加で3日ほど滞在し、現地のAI企業に行ったり、Waymoに乗ったりと最新のテクノロジーに触れてきたので別の機会に紹介できればと思います。 やはり現地でしか得られない経験は多いと思うのでiOSエンジニアの方でもそうでない方でも、一度訪れてみてはいかがでしょうか! 明日の記事は ntkさんです。引き続きお楽しみください。
こんにちは。メルカリモバイル iOSエンジニアでTech Leadをしています @takeshi です。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の16日目の記事です。 今回は私が業務中に利用したAIエージェントの経験を紹介します。 Cursorを使って未経験のKotlinコードをレビューして、iOS/Androidの実装差分をなくした話です。 メルカリモバイルチームについて まず私のチームであるメルカリモバイルチームの説明をさせてください。 メルカリモバイル は2025年3月4日にローンチした新しいサービスです。 iOSとAndroid両方提供しています。 現在は2つのチームに分かれていて、両チームともに少数精鋭です。 OS間の実装差分 メルカリモバイルの開発を進めていく中で、課題になっているのがOS間での実装差分です。リリース前のDogfoodingや各プロジェクトのQAフェーズでiOSとAndroidで挙動が違うことがしばしば見つかりました。 ギガの残り残量が切り捨てなのか切り上げか バリデーションチェックの方法 画面ロジックのエラーハンドリング これらの差分は、Specに記載があるがその記述が曖昧で実装者に委ねられていたり、そもそも情報が不足していることに起因しています。 これらの差分は見つけ次第修正していますが、そもそも起こらないようにしたいとも思っていました。 Androidのコードレビューに挑戦 今回、自分がTech Leadとして担当するプロジェクトがあり、技術的なSpecの作成やDesign Docを作りました。実装自体はチーム内の他メンバーが担当です。 Tech Leadとして、コードの品質を守る目的としても「コードレビューをしよう」と思ったわけです。 しかし、私は10数年のエンジニアのキャリアでiOS/Swiftの開発がメインです。 Androidのコードレビューができません。KotlinのコードがSwiftとあまりにも隔たりがあり、コードを読んでもどんな動作になるかの理解が難しいのです。 ただ、メルカリ社内ではAIエージェントの導入が積極的に推奨されていて、私もCursorを使える状態でした。CursorはAnysphere社が提供するAIコードエディタです。 これを使えばなんとかなるんじゃないかと思い試してみました。 Cursor でKotlinレビュー まずはCursor Rulesで次のようなルールを作りました。 あなたはシニアAndroidエンジニアです。 私はSwift開発者でKotlinの文法に慣れていません。 KotlinやAndroidの質問にはSwiftやiOSで同様の概念を用いて説明してください そして、AndroidのPull Requestをチェックアウトし、差分に書かれているコードの文法でわからないことがあったらひたすらCursorに聞くというのを繰り返しました。 例えばこんなプロンプトを打ちました。 packageとimportってkotlinだとどんな意味? するとこんな回答が返って来ました。 Kotlinのpackageとimportについてですね。Swiftと比較しながら説明します。 **package** Kotlinのpackageは、クラスや関数などのコードをグループ化し、名前の衝突を避けるための仕組みです。Swiftで言うところのモジュール(module)や、より具体的にはXcodeプロジェクト内のターゲット(target)に近い概念と捉えることができます。 **import** Kotlinのimportは、他のpackageで定義されたクラスや関数などを、現在のファイルで手軽に利用できるようにするためのものです。これはSwiftのimportと全く同じ機能と考えていただいて大丈夫です。 まとめると、 - package: コードの所属グループを宣言する(Swiftのモジュールやターゲットのようなもの) - import: 他のグループのコードを自分のファイルで使いやすくする(Swiftのimportと同じ) Kotlinを知っている方からすれば何でもないことですが、私はこのレベルで何も知らない状態でした。その他スコープ関数(letやapply)、data classなど、Swiftにはない文法などを聞いてました。 コードを指定して文法含めたコードの処理をCursorに質問すると、それに合わせた解説をしてくれるのでコードの理解が深まりました。 昔だったら文法のキーワードをググって解説のサイトを読み込んでからコードに戻るのを繰り返さないといけないところです。これではいくら時間があっても最終的にしたい「差分コードの理解を深める」に到達できません。 Cursorに聞くことで、時間をかけず、既存のSwiftでの知識を活用してKotlinの概念を理解しレビューを進めることができました。 発見した実装差分の具体例 レビューの過程で、APIのリクエストパラメーターがiOSと異なることに気がつきました。 他の類似した処理とパラメーターをまとめていたのですが、今回のプロジェクトにおけるBE要件としては不要なパラメーターが含まれていました。iOSではすでに、パラメーターを分けて実装していたので、Androidもそのように指摘をし、無事に分けてリクエストを送るように修正されました。 この指摘で、各画面で必要最小限のパラメーターのみを送信するようになり、iOS側の実装と整合性が取れるようになりました。 AIエージェントでレビューをする上でのポイント レビューもAIエージェントでやればいいんじゃないかという意見があるかもしれませんが、私は反対です。コードレビューはコードの品質を保つ重要な活動で、まだチームのナレッジを100%AIエージェントに伝える手段が確立してないからです。 単純なコードの書き方ならリンターを使えばよくて、それ以上を求めるなら、チームのナレッジを知っている人間がやったほうが早いのが現状です。 また自分のレビューのスタンスとして、Specをコードがちゃんと表現しているかは重視しています。QAで見つかるよりは、レビューで指摘するほうが手戻りがなくて早いでしょう。 チーム内での反応 今回私がAndroidのレビューをAIエージェントを使って行ったことに対して、チームからは好意的なフィードバックをもらいました。Androidメンバーからは「自分もiOSに挑戦したい」という声が上がりました。ゆくゆくは実装も含めて、自分の領域を超えて担当できればいいなと思っています。 まとめ 今回の経験は私の中で、AIエージェントが自分のできる領域を増やせるツールであることを知るきっかけになりました。これまでは全く手が出なかったAndroidのコードレビューに対して、時間をかけず、やりたい成果をあげられたのは大きな進歩です。 「未経験の技術領域は手が出しにくい」と感じているエンジニアの方は多いと思います。しかし、AIエージェントという強力なツールを活用することで、これまで諦めていた領域にも挑戦できるようになります。小さいところから徐々に始めると良いと思います。 私も、次はレビューだけでなく実装にも挑戦したいと思います。 明日の記事は ntkさんです。引き続きお楽しみください。 この記事の画像に利用されたAndroid ロボットは、Google が作成および提供している作品から複製または変更したものであり、 クリエイティブ・コモンズ 表示 3.0 ライセンスに記載された条件に従って使用しています。
こんにちは。メルペイ Credit & Payment Service / Engineering Headの @fivestar です。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の20日目の記事です。 今回はメルペイで主にBFF向けに採用しているgRPC Federationという仕組みを使って、3rd party向けのAPIを実装する取り組みの事例紹介になります。 BFF(Backends For Frontends)開発に導入されているgRPC Federation gRPC Federation は、Protocol Buffers上にDSL(Domain-Specific Language)を記述することでコードを書かずにBFF(Backends for Frontends)を作成できるフレームワークです。現在 OSS として公開しています。 gRPC FederationはBFFに限らずあらゆるマイクロサービス開発において、サービス間の依存関係をProtocol Buffers上で表現することを目指して社内で開発が進められていました。 この仕組みはメルペイのBFFのリアーキテクチャに先行導入されていましたが、IDP(ID Platform)チームが用意しているメルカリID連携の仕組みやAPI Gatewayと組み合わせることで3rd party向けのAPI開発もスムーズに実現することができました。 gRPC Federation: gRPC サービスのための Protocol Buffers を進化させるDSL 【書き起こし】gRPC Federation を利用した巨大なBFFに対するリアーキテクチャの試み – goccy【Merpay & Mercoin Tech Fest 2023】 【書き起こし】メルカリグループの認証基盤における理想と現状、今後の取り組み – kokukuma 【Merpay Tech Fest 2022】 メルペイのアーキテクチャ メルペイではマイクロサービスアーキテクチャを前提とした4レイヤーアーキテクチャを採用しています。API Gatewayを経由してAPI = BFFレイヤーが、Backendのマイクロサービスを束ねるという構成です(図1)。 API GatewayはAPIサーバーをexposeするための処理を行います。メルペイではAPIサーバーはgRPCのエンドポイントを提供しており、API GatewayがgRPCのAPIをJSONのHTTP APIに変換します。他にもアクセストークンの検証等も行います。 APIレイヤーにはMerpay APIという集約的なBFFが提供されていました。しかしサービス拡大に伴って保守コストの増加とオーナーシップの問題が出てきたことを受け、BFFをドメインごとに分割するMerpay APIリアーキテクチャプロジェクトが立ち上がります。このプロジェクトでgRPC Federationが採用されました。 図1 メルペイの4レイヤーアーキテクチャ gRPC Federationの導入 gRPC Federationの利用者として感じる利点は、Protobuf上のDSLの記述のみで完結するため運用時の認知負荷が低いこととパフォーマンス最適化、品質安定性です。 BFFの主な役割は大雑把に言えば「Frontendのために必要なデータをBackendのサービス群から取得して返す」ことです。gRPC Federationは DSLの記述順序に関わらずAPIコールの順序・並列化を最適化 してくれるため、特に複雑なデータ取得が要求される画面で力を発揮します。 複雑な実装が求められる場合は個別にGoのコードを直接記述することもできるのですが、これまでgRPC Federationを用いた開発上はDSLのみで完結しています。そのため自動生成されたGoのコードのみで運用することになり、バグが埋め込まれにくく品質が安定します。 メルペイにおける3rd party向けAPI 現在メルペイでは主に次のような3rd party向けのAPIを提供しています。 ネット決済加盟店向けAPI PFMサービス連携向けAPI メルカリポイント交換API このうちPFM(Personal Financial Management)サービス連携向けAPIとメルカリポイント交換APIについてはgRPC Federationで実装されており、メルカリID連携を用いた認証・認可を採用しています。社内のアセットを最大限に活用して短期間・低コストでAPIを提供する手段が確立でき、外部システムとの連携において非常に前向きな意思決定ができるようになりました。 メルカリID連携 APIを外部に公開するうえで最も重要な要素が認証・認可です。メルカリにはIDPチームがあり、既にメルカリIDを用いたOAuth / Open ID Connect(OIDC)を実現する基本的な仕組みが整っていたので、この点は既存の資産を活用することで実現できました。 これまでAuthentication Code FlowだけでなくClient Credentials Flowを採用したケースもあり、このあたりOAuth 2.0の基本的な機能はおおよそカバーされているため、ユースケースに応じた柔軟な対応が可能となっています。 またメルカリ内部ではお客さまのIDはPII(個人識別用情報)として扱うため慎重に取り扱う必要があるのですが、OIDCを用いたときにPPID(Pairwise Pseudonymous Identifier)として変換されるため、安全かつシームレスに扱える仕組みが整っています。 実際にこれまで3rd party向けのAPIを用意するときは都度IDPチームに相談して適切な選択肢を一緒に考えてもらっているのですが、プラットフォームとして確かな機能が提供されており、またそれらが正しく使えるように毎回丁寧に相談にのってくれるため、大変心強いです。 Applying OAuth 2.0 and OIDC to first-party services gRPC Federationを用いた3rd party向けAPI提供 最初に3rd party向けAPIに導入したのがPFMサービスである マネーフォワードとのシステム連携プロジェクト でした。gRPC Federationが導入されてまもなくの頃で、まだ実際にプロダクション環境での実績がない状況でしたが、前述のような利点があることからSolutionsチーム・Architectチームと相談の上で採用を決定しました。 実はメルカリ全体としてオープンなPublic APIを提供するアイディアもありましたが、意思決定のコストや要求されるスピード感を考えたときに今それを目指すのはToo muchでした。 gRPC Federationを使うことでBFFの立ち上げコストが劇的に下がった ことからも、一旦はドメインごとにAPIを用意していく方が合理性があると判断しています。 このあとに実装したポイント交換APIはgRPC Federationへの習熟度が上がってきたこともあり、おおよそ1-2週間程度で基本的なAPIの用意ができています。gRPC FederationのDSLを記述するのに多少の学習コストが必要でしたが、 Language Server の他、執筆時点では MCPサーバーの実装 も進められており、効率的な開発が行える様々な支援が提供されています。 新規開発の流れ 3rd party向けのAPIをgRPC Federationで実装する場合、次のような手順で進めています。 要件定義 Design Doc作成 API仕様書作成 API仕様に合わせてProtocol Buffers上でスキーマ定義 Protocol Buffers上でDSLを書いてAPI実装 メルペイの場合、要件定義はプロダクトマネージャーが中心となって進めるケースが多いですが、3rd party向けAPIの場合は画面仕様が明確でないケースもあるため、エンジニアがオーナーシップを発揮する必要があります。マネーフォワード連携の場合、私自身がマネーフォワードの利用者で連携を待ちわびていたこともあったので喜々としてやった覚えがあります。 Design Doc作成 Design Docはステークホルダとの合意形成のためにさまざまな観点から情報を整理するために記述します。特にメルカリ・メルペイにおいてはドメインに関係があるチームはもちろん、Architect、SRE、IDP、SecurityといったEnabling方面との合意を早期に得ることが最終的な成果物を早く提供することにつながるため、迅速にまとめることを意識しています。 API提供の背景、目的、スコープ、ゴール APIの名称、ドメイン名 アーキテクチャ図、依存関係 外部システムを含めたシーケンス図 どのチームにどのような作業を要求するか エンドポイント メルカリID連携のクライアントの単位 認可スコープ 提供環境、特に外部向けの開発環境をどうするか こういった情報は要件定義以前から関係チームを巻き込みながら進めておくことで、そこまでにおおよそ決まった方針をDesign Docとして清書し、不確実な要素をつぶしていく作業になります。自分は1度やって勘所を掴んだこともあり要件次第ですがおおよそ1日程度で最低限はまとめられるため、とにかく1度やってしまえば意外と難しいことはなかったりします。 API仕様書作成 3rd partyにAPI仕様を伝える上で当然API仕様書を用意する必要があります。リクエストやレスポンスのヘッダーやパラメーター情報はもちろん、エラーの種別や実際のレスポンスのパターンも用意する必要があります。ただし、特にAPIを新規で開発するタイミングでは、実際にAPIクライアント側が想定するAPI構成になっているかを早めに揃えることが手戻りを抑えるために重要なため、基本的なAPIの単位と主要なパラメーターを整理して早期にすり合わせるように心がけています。 またこの時メルカリID連携のフローも含めたシーケンス図を用意しておくことで、想定されるAPIの呼び出し方法や、お客さまがどこで操作を行うのかといったUX面でもよりイメージを揃えることができたため、シーケンス図もあわせて用意するようにしています。 gRPC Federationを用いたAPI定義の例 gRPC Federationを用いてProtocol Buffers上でAPIを作成するサンプルコードを用意しました。リスト1で3つのエンドポイントを内包する APIService サービスを、リスト2はそのうちの CreateCharge メソッドをそれぞれ定義したものです。(なおコードは実際のものを模したダミーです) gRPCサービス定義 リスト1にはAPIのアウトラインとなる定義が記載されています。 APIService にはまず mercari.api.gateway.spec オプションで、API Gateway向けの設定を行っています。exposeするドメインや、内部的なAPI区分などを指定します。 APIService には CreateCharge GetCharge CaptureCharge という3つのgRPCメソッドが定義されています。各エンドポイントにはAPI GatewayでHTTPのエンドポイントで変換するために google.api.http オプションを設定しています。今回の例ではREST形式のパスを採用しているため、クライアントからするとREST APIとして操作しているように見えるでしょう。 gRPC Federationでは option キーワードを使って .proto ファイル上にannotateします。gRPCサービスに対しては .grpc.federation.service をマッピングするだけで基本的には十分です。もしgRPC FederationのDSL内で環境変数にアクセスしたい場合 env キーで設定することができます。 mercari.api.jp.authority.scopes は必要なスコープを定義するメルカリ独自のオプションです。これはAPI Gatewayによって必要な権限のないリクエストを弾く処理が行われているため、どのようなスコープ単位の定義と、メソッドごとの必要スコープの設定にのみフォーカスできます。 なお、 mercari. で始まっているオプションは基本的にメルカリ社内用のアノテーションで、あくまでメルカリ・メルペイ内部において、API GatewayやIDPとの連携についてアノテーションベースで設定できる仕組みがある、程度の理解で大丈夫です。 リスト1: gRPC サービス定義 service APIService { option (mercari.api.gateway.spec) = { domain : "example-api.merpay.com" endpoint_prefix : "" api_type : API_TYPE_OPEN }; option (.grpc.federation.service) = {}; rpc CreateCharge(CreateChargeRequest) returns (CreateChargeResponse) { option (google.api.http) = { post : "/v1/charges" body : "*" }; option (mercari.api.jp.authority.scopes) = SCOPE_MERPAY_EXAMPLE_API_CHARGE_READWRITE; } rpc GetCharge(GetChargeRequest) returns (GetChargeResponse) { option (google.api.http) = { get : "/v1/charges/{charge_id}" }; option (mercari.api.jp.authority.scopes) = SCOPE_MERPAY_EXAMPLE_API_CHARGE_READWRITE; } rpc CaptureCharge(CaptureChargeRequest) returns (CaptureChargeResponse) { option (google.api.http) = { post : "/v1/charges/{charge_id}:capture" body : "*" }; option (mercari.api.jp.authority.scopes) = SCOPE_MERPAY_EXAMPLE_API_CHARGE_READWRITE; } } gRPCメソッド定義 実際にgRPC FederationでDSLを記載するのは主に各メソッドの戻り値に設定したメッセージになります。リスト2では CreateCharge メソッドの戻り値である CreateChargeResponse に、Backendサービスの merpay.payment.v1.PaymentService/CreateCharge を呼び出して、そのレスポンスから res.charge を取り出して CreateChargeResponse.charge に詰めて返す、という記述をしています。 gRPC Federationでは def キーワードを用いて変数を定義しながら、その中で call キーワードを用いることでBackendサービスのAPIコールを行い、レスポンスに必要なデータを形成していくというフローです。 $.amount のように $ を用いてリクエストパラメータに直接アクセスできます。 今回は省略しましたが validation キーワードを用いてバリデーションしたり、 error キーワードを用いてエラーを返したりといった操作も可能です。 (.grpc.federation.message).alias を用いて、パッケージが異なるがスキーマが同じ場合に自動的にプロパティを詰め替えてくれる機能もあります。レイヤードアーキテクチャを採用しているとこういったレイヤーをまたいだ際のペイロードの詰め替えが発生しがちですが、名前を見て適切にマッピングしてくれるためとても簡潔に扱えます。 customer_id 変数に mercari.grpc.federation.authority.pat().customerId() という値を設定していますが、これはgRPC FederationのDSL(CEL API)を プラグインによって拡張 したもので、IDPが発行した内部用のアクセストークンからカスタマーIDを取得するメルカリ固有のデータアクセスをDSLに組み込んでいます。 なお、このサンプルコードではAPI設計でよく使われる冪等性の担保についても実装例を示しています。昨今ではHTTPリクエストに Idempotency-Key ヘッダーで「冪等キー」を指定する手法が一般的で、サンプルコードでもこの指定に則っています。 grpc.federation.metadata.incoming()['idempotency-key'][0] のようにHTTPヘッダーの値が取得できます。従来メルペイでも 決済や残高のデータ整合性担保 のために冪等キーを導入していましたが、gRPCのリクエストパラメータに指定する手法を採用しているため、BFFレイヤーで詰め替えを行っています。 リスト2: CreateCharge RPCの定義 option (grpc.federation.file) = { import : [ "proto/merpay/payment/v1/payment.proto" ] }; message CreateChargeRequest { uint64 amount = 1; } message CreateChargeResponse { option (.grpc.federation.message) = { def[ { name : "customer_id" by : "mercari.grpc.federation.authority.pat().customerId()" }, { name : "idempotency_key" by : "grpc.federation.metadata.incoming()['idempotency-key'][0]" }, { name : "res" call { method : "merpay.payment.v1.PaymentService/CreateCharge" request : [ {field : "customer_id", by : "customer_id"}, {field : "amount", by : "$.amount"}, {field : "idempotency_key", by : "idempotency_key"} ] } } ] }; Charge charge = 1 [(grpc.federation.field).by = "res.charge"]; } message Charge { option (.grpc.federation.message).alias = "merpay.payment.v1.Charge"; string id = 1; uint64 amount = 2; } gRPC FederationでAPIサーバーが動くまで 実際にはこのProtobufの変更をマージした上で、gRPC Federationを用いてビルドされたGoのコードにテストコードを書いていきます。またAPI Gatewayに対してこのAPIをexposeするための設定も必要です。とはいえおおよそはgRPC Federationによってレールが敷かれるため、1からサービスを作成することに比べると非常にシンプルな工数で実現が可能となっています。 複雑なエンドポイントの実装 先ほど示したサンプルコードはスキーマ自体もかなり簡略化していますが、実際にマネーフォワードとの連携においてはお客さまの残高やメルカードの利用状況など複雑なスキーマやパターンを持つエンドポイントを複数実装しています。 gRPC FederationでDSLを記述する際、次のような操作が基本機能として提供されています。 ネストしたメッセージの中でもAPI call含めた定義が可能 四則演算や型変換が可能 if キーワード、あるいは by キーワード内で三項演算子を用いた条件分岐が可能 call キーワードの中で timeout を用いたタイムアウト時間の設定、及び retry を用いたリトライの指定が可能 複雑なスキーマにおいてはDSLもそれなりの記述量となりますが、基本的には処理結果を変数に詰めるということを繰り返していくため、処理がネストするような書き方にはならず複雑さは比較的抑えられるかと思います。 またスキーマ自体が適切に正規化されていることでメッセージのまとまり単位でDSLの記述も整理できるため、特に新規にスキーマ定義する際は 正規化を意識する ことで全体の見通しがよくなると思います。 まとめ 現時点におけるベストプラクティスの1つとして、gRPC Federationを採用して3rd party向けAPIを提供する流れをざっくりと紹介してきました。 gRPC FederationはBFFのような仕組みを簡単に作成でき、運用負荷も少ないため、低コストでAPIを立ち上げることができます。もちろん複雑なスキーマにも対応できますし、サーバーを分割したい場合にも適しており、プロダクトの規模に応じて様々な状況に対応できる非常に実用的なソリューションです。 もちろんこれはgRPC Federationを導入しただけで作れるものではなく、API GatewayやBackendサービスといったアーキテクチャのレイヤー化や、IDPのようなプラットフォームがあるからこそ、BFFレイヤーの拡張だけで新しいAPIの導入が実現できています。ですのでアーキテクチャの全体像を踏まえて参考にしていただければ幸いです。 今回の記事用に書いたサンプルコードの作成にあたってClaude Codeを用いてDSLを生成しましたが、多少のやり取りでおおよそ期待通りのアウトプットが出来上がりました。今回の Merpay & Mercoin Tech Openness Month 2025 でもAI関連の記事が非常に多く出ていますが、本当に目まぐるしい速度で環境が変わっていっていますよね。ぜひ他の記事も目を通してみてください! 明日の記事は @takeshiさんと@Shuntaさんです。引き続きお楽しみください。
はじめに こんにちは。メルペイ Solutionsチーム所属のデータエンジニア @orfeon です。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の15日目の記事です。 2020年にデータパイプラインをJSONで定義して実行することができるツールとしてmercari/DataflowTemplateを開発して OSSとして公開 しました。 最近このツールに大幅な機能追加を行い、 mercari/pipeline と名前を変更してv1.0.0(β版)をリリースしました。 この記事では今回開発を行った以下の機能について紹介していきます。 運用の容易化 YAML対応 パイプライン構成管理の強化 dead-letter設定の追加 パイプライン定義の容易化 checkerツールの提供 運用の容易化 多くのデータパイプラインの開発・デプロイを進めていくと、極力少ない工数で多くのパイプラインを運用していく必要性が高まります。 ここではデータパイプラインのプロダクション環境での運用負荷を軽減するために追加された以下の機能について紹介します。 YAML対応 パイプライン構成管理強化 dead-letter設定の追加 YAML対応 パイプラインの定義を行うconfigファイルのフォーマットは、これまでJSON形式のみサポートしていたのですが、YAML形式でも定義できるようになりました。 JSONでは、改行やダブルクオートを含むようなパラメータがあった場合にサニタイズする手間が発生したり、コメントを書けなかったり、可読性が落ちたりするなどconfigファイルの保守に問題もありました。YAMLで定義することでこうしたパラメータでも直接指定できるようになり、configをよりシンプルに定義して保守できるようになりました。 YAML定義によるbigquery sourceの定義例 sources: - name: bigquery_source module: bigquery parameters: query: |- WITH subquery AS ( -- some comment SELECT user_id, MIN(timestamp) AS first_timestamp FROM `mytataset.mytable` GROUP BY user_id ) SELECT format('%d#g', user_id) as row_key, first_timestamp FROM subquery パイプライン構成管理強化 さまざまなデータパイプラインを運用していると、別々のパイプラインで共通する処理や設定を使いまわしたいケースがあります。 起動時に変数を指定してパイプラインのパラメータを変更する パイプラインの中で指定したパスのみ実行する 複数のパイプライン定義を一つのパイプラインにマージして動かす 本来共通する部分を別々で定義してしまうと、変更時にそれぞれ修正する必要があり、データパイプラインの保守性が落ちてしまいます。 今回configのsystemの項目で新たに以下のパラメータが追加されました。これらを指定することで、上記のようなケースに対応するためのパイプラインの構成制御ができるようになりました。 system args context imports 以降の節でこれらのパラメータによる構成の制御方法について説明します。 argsによる起動時のパイプラインのパラメータの変更 system.argsパラメータを使うことで、パイプラインの起動時のオプションに指定した変数を使ってモジュールのパラメータを書き換えることができるようになります。 実はこれまでのmercari/DataflowTemplateでもパイプラインの起動時の変数指定はできたのですが、複数手段があったり、パイプライン実行時の動的な変数指定(データの値に応じて宛先のtopicをスイッチするなど)と競合したりするなど、いろいろと問題があったため今回argsパラメータとして整理をしました。 args機能を利用するユースケースとしては、通常起動時はcronの起動時の条件でデータを読み込み、問題発生時のデータのバックフィルで読み込み元のテーブルやフィルタ条件の日付を起動時に指定する例などが挙げられます。 以下はargsでパイプラインの起動時に変数を指定して、パラメータの値を書き換えるconfigの例です。 argsでは起動時の指定が無い場合の変数のデフォルト値を設定しています。 デフォルト値では固定値だけでなくTemplate Engineを使って動的に生成することもできます。 target_tableではクエリで参照するテーブル名、current_dateではクエリのフィルタ条件として使うための日付として起動時の日付を生成しています。 bigqueryモジュールのqueryパラメータでこれらの変数の値を埋め込むことでクエリの条件を起動時に制御できます。 system: args: target_table: "myproject.mydataset.mytable" current_date: "${utils.datetime.current_date('Asia/Tokyo')}" sources: - name: bigquery_source module: bigquery parameters: query: |- SELECT * FROM `${target_table}` WHERE created_date >= DATE("${current_date}") パイプライン起動時に以下のようにparameters=args.{変数名}を指定すると、argsで定義した変数のデフォルト値を指定した値で置き換えることができます。 gcloud dataflow flex-template run sample-job \ --project=myproject \ --region=asia-northeast1 \ --template-file-gcs-location=gs://xxx/yyy/zzz \ --parameters=config="$(cat path/to/config.yaml)" \ --parameters=args.target_table=myproject2.mydataset2.mytable2 contextによるパイプラインのパスの指定 単一の目的のためのデータパイプラインではあるものの、状況により処理を派生させたいケースがあります。 こうした派生する処理ごとに別々のconfigファイルを定義すると管理が煩雑になってしまいます。 contextとtagsパラメータを使うことで、派生する処理も含めて単一のconfigファイルで定義しておき、状況に応じて一部の処理を切り替えることができます。 具体的にはconfigファイルで各モジュールにtagを設定して、起動時にcontextで指定したtagのモジュールだけでパイプラインを構成して実行することができます。 contextとtagsを使う例として、機械学習の学習用パイプラインと予測用パイプラインを単一のconfigファイルで定義してcontextで切り替える構成を紹介します。 機械学習では予測モデルを構築する際に、学習用と予測用で別々のパイプラインを作ることがあります。データのソースは学習時と予測時で別々だが特徴量を生成する処理は共通というケースを想定します。 以下のconfigファイルでは、特徴量生成は共通ですが、学習用にはBigQueryのデータソース/結果シンクを用い、予測用にはPub/Subのデータソース/結果シンクを用いています。 system: context: train sources: - name: ml_source module: bigquery tags: - train parameters: table: xxx timestampAttribute: timestamp_field - name: ml_source tags: - prediction schema: avro: file: xxx parameters: format: avro subscription: xxx transforms: - name: feature inputs: - ml_source tags: - train - prediction parameters: groupFields: - user_id select: - name: moving_avg field: amount_field func: avg range: count: 10 sinks: - name: feature_sink module: bigquery tags: - feature inputs: - feature parameters: table: xxx - name: feature_sink module: pubsub tags: - prediction inputs: - feature parameters: topic: xxx sourcesとsinksにそれぞれml_sourceとfeature_sinkという同じnameを持つモジュールがあります。ただしtagsではtrain、 predictionと異なるtagを持っています。 transformsでは特徴量を生成するselectモジュールとして直近の指定した個数の移動平均を計算する設定をしています。バッチでもストリーミングでも同じ特徴量を生成します。 tagsではtrainとpredictionの両方を指定しています。 system.contextでtrainを指定した場合、sourcesとsinksではtagsでtrainが指定されたモジュール(この場合はbigquery)のみでパイプラインが構成されます(contextでpredictionを指定した場合はsourcesとsinksでpubsubのみ)。 transformのselectモジュールはtagsでtrainとprediction両方指定されているのでどちらのコンテキストでも利用されます。 (なおcontextで何も指定しない場合は全てのモジュールが使われ、同じnameでコンフリクトが発生してエラーになります) contextにより、複数のコンテキストに応じたモジュールの設定を単一のconfigファイルに定義しておき、起動時にcontextを指定することでパイプラインの処理を簡単に切り替えられるようになります。 importsによる複数configファイルのマージ パイプラインのためのインフラや運用のコストを減らすために複数の処理を単一のパイプラインにまとめたい場合があります。 一方で、一つのパイプラインに複数の処理をまとめるとconfigファイルが肥大化してパイプライン定義の見通しが悪くなります。 そこでconfigファイルは用途に応じて別々に定義しておいて、importsパラメータでそれらのconfigファイルを指定することでパイプラインを一つにまとめることができるようになりました。 以下ではimportsパラメータを利用したconfigファイルの例を説明します。 この例のconfigファイルではsystem.importsパラメータのみ指定されています。実際の処理はimportsのfilesで指定されたconfigファイルで定義されており、起動時にこれらのファイルを読み込んで一つのパイプラインとして構成・実行します。 (baseパラメータでconfigファイルのパスのprefixを指定しています) system: imports: - base: gs://example-bucket/configs/ files: - pipeline_1.yaml - pipeline_2.yaml - subdir/pipeline_3.yaml このimports機能は、単純に複数のconfigファイルで定義されたモジュールをマージしているだけなので、各configファイルではnameが重複しないように注意が必要です。 (imports時の重複チェックなどの機能は今後改善予定です) dead-letter設定の追加 運用のためには処理の途中でエラーが発生した場合は原因を特定したりリカバリを行うために、問題のあったデータを切り分けて保持する必要があります。またデータパイプラインの要件によっては問題が発生した場合でも処理を正常に続ける必要があります(streaming処理や処理全体をやり直すコストが非常に大きい場合など)。 今回のバージョンアップではほぼ全てのモジュールで修正を行い、処理に問題が発生した場合も極力処理を正常に続行できるようにしました。また問題のあったデータを切り分けて指定したdead-letterに簡単に出力できるようになりました。 以下、dead-letterのconfigファイルの設定例になります。 このconfigではfailuresの項目が新たに追加されています。 failuresのモジュールでは通常のsinkとは異なりinputsを指定する必要はありません、パイプラインの全てのモジュールで処理に失敗したデータはこのfailuresで指定されたモジュールに送られます。 system: failure: failFast: false sources: - name: pubsub_source module: pubsub parameters: format: avro subscription: xxx sinks: - name: pubsub_sink inputs: - pubsub_source parameters: format: avro topic: xxx failures: - name: pubsub_failure_sink parameters: format: avro topic: xxx 処理に失敗したエラーデータは共通のスキーマでfailuresで定義したモジュールに送られます。 あらかじめBigQueryでこのスキーマに準じたテーブルを作っておき、Pub/SubのBigQuery subscriptionを通じてBQに連携・保持することもできます。 パイプライン定義の容易化 パイプラインの定義を作って動作確認する際に、これまでは実際にJobを実行してうまくいくか確認する必要がありました。 しかしこれは手間がかかる作業であり、パイプラインの定義自体が非常に時間の掛かるプロセスでした。 ここではパイプラインの定義をもっと手軽に試行錯誤できるようにするために追加した以下の機能を紹介します。 checkerツールの提供 checkerツールの提供 ブラウザ上で簡単にconfigファイルの内容をチェックするためのツールを同梱しました。 これまでのmercari/DataflowTemplateのビルド成果物は基本的にDataflow Flex Templateのためのコンテナイメージのみでした。 mercari/pipelineではビルド時のプロファイルを切り替えることで複数のビルド成果物を生成することができるようになりました。 現在では以下のプロファイルがサポートされています。 dataflow Cloud Dataflow Flex Template用のコンテナイメージを生成 direct パイプラインのローカル実行用のコンテナイメージを生成 server パイプラインのローカル実行機能をAPIとして提供するサーバ用のコンテナイメージを生成 プロファイルでserverを指定して生成されたコンテナイメージは、ローカルにpullして起動、利用することもできますし、Cloud Runなどにデプロイして使うこともできます。 APIだけでなくチェック用のUIも備えているのでブラウザ上で操作できます。 以下はこのserverのコンテナイメージを起動してブラウザで開いた画面の例になります。 画面の左側がconfigの内容を記述するテキストエリアになります。 右側はconfigの実行結果を表示するエリアになります。 右上のヘッダーには定義したconfigを実行するために以下の2つのボタンが並んでいます。 Dry Run 定義したパイプライン処理の実行グラフを生成する 各モジュールのパラメータのチェック 各モジュール間の関係整合性のチェック 各モジュールの出力スキーマの確認 Run 定義したパイプライン処理をローカルで実行する Dry Runボタンでは、定義したconfigの内容に問題がないか確認できます。 問題があった場合は右側にエラー内容が表示されるので、それを確認して修正することができます。 問題がなかった場合は右側に各モジュールの出力のスキーマが表示されるので、処理内容が想定した通りか確認することができます。 Runボタンでは、定義したconfigの内容で実際にパイプラインをローカルで実行します。 パイプラインでクラウドリソースにアクセスする場合(BigQueryのクエリ結果を取得するなど)はローカル実行しているサービスアカウントに必要な権限が付与されているか注意してください。 以下のコマンドは、利用者のローカルマシン(MacOS)で自分の権限でserverコンテナを起動する例です。 docker run \ -p "8080:8080" \ -v ~/.config/gcloud:/mnt/gcloud:ro \ --rm asia-northeast1-docker.pkg.dev/{deploy_project}/{template_repo_name}/server:latest Cloud Runにデプロイして使うこともできます。以下Cloud Runにデプロイするためのコマンド例です。 (データ処理でBQ等の外部リソースにアクセスする場合はCloud Runのサービスアカウントに権限が必要です) gcloud run deploy {service_name} \ --project={project} \ --image=asia-northeast1-docker.pkg.dev/{deploy_project}/{template_repo_name}/server:latest \ --platform=managed \ --region=asia-northeast1 \ --execution-environment=gen2 \ --port=8080 \ --no-allow-unauthenticated ローカルであっても規模の小さいデータ処理であれば特に問題なく実行できるはずです。バッチでちょっとしたデータの加工や移動をするための便利ツールとしても利用することができます。 なお現在このcheckerツールではstreamingモードでのRun実行はサポートしていないので、streamingモードでローカル実行する場合はdirectのコンテナイメージをコマンドラインで起動して使うことを推奨しています。 directコンテナを動かすのは基本的にserverイメージをdirectに差し替えるだけです。 ただしconfigファイルを起動時のパラメータに指定する必要があります。 以下のコマンドは、利用者のローカルマシン(MacOS)で自分の権限でdirectコンテナをstreamingモードで起動する例です。 docker run \ -v ~/.config/gcloud:/mnt/gcloud:ro \ --rm asia-northeast1-docker.pkg.dev/{deploy_project}/{template_repo_name}/direct:latest \ --streaming=true \ --config="$(cat path/to/config.yaml)" ちなみにこのUIの部分の開発はClaude Codeを使いながら作りました。 自分はフロントエンド開発の経験はほとんどないのですが、ちょっとしたUIを持ったサービスをサクッと作れてとても便利でした。 今後の開発 今後のmercari/pipelineの開発としては大まかに以下のような項目について開発を進めていきたいと考えています。 checkerツールの拡張 streaming処理機能の強化 Apache Flink, SparkなどCloud Dataflow以外のRunnerへの対応 checkerツールの拡張 checkerツールは現在はまだシンプルなconfigファイルの簡易チェックや簡易な動作確認しかできませんが、非エンジニアでもデータパイプラインを手軽に利用できるように機能を拡張していきたいと考えています。 将来は自然言語で処理内容を指示するとエージェントがドキュメントや過去のconfigファイルの履歴などを参照して利用者とインタラクティブにパイプラインを構築できるようにしていきたいと考えています。 今回のリリースには間に合いませんでしたが、エンジニアがインタラクティブにconfigファイルの定義をできるようにcheckerツールをMCPサーバとして利用できるように準備を進めています。 エージェントの連携を強化するためにも、リポジトリのドキュメントの整備やconfigファイルのexamplesの拡張も進めていきたいと思います。 streaming処理機能の強化 Google Cloudにおいてバッチ処理についてはBigQueryでかなりのことができるようになってきました。例えば Federated Query や Reverse ETL を使うことで、Cloud Spanner や Cloud Bigtable などの外部のデータソースから取得したデータをBigQueryのクエリエンジンで処理して結果を書き戻すことも手軽にできるようになりました。 BigQuery ML で機械学習モデルやLLMの推論結果を手軽にクエリの中で付与することもできます。 またちょっとしたリアルタイム処理も、BigQueryの Continuous queries や、Cloud Pub/Sub の Single Message Transforms などを使うことで手軽に実現できるようになってきました。 Google CloudにおいてCloud Dataflowは、大規模データに対する複雑なstreamingデータ処理を担うことを役割として期待されているように思います。 streaming処理の中でも、Apache Beamの特徴であるbatchとstreamingで同じ処理をするユースケースに対して特に集中して機能開発をしていきたいと考えています。 Cloud Dataflow以外のRunnerのサポート予定 名前を mercari/DataflowTemplate から mercari/pipeline に変更した理由でもあるのですが、mercari/pipeline を Cloud Dataflow 以外のデータ処理基盤でも動かせるようにしていきたいと考えています。 Google CloudでもApache Spark, Flink, Kafkaなどの人気でオープンなビッグデータのフレームワークやそのエコシステムとの連携にも力を入れていこうとしているように思います。 こうしたフレームワークとの連携も進めていき、Cloud Dataprocなどでもパイプラインを動かせるように機能を拡張をしていきたいと考えています。 mercari/pipeline は大幅に変更があり、まだβ版でのリリースのため、機能が不足していたり一部バグがあったりするかもしれません。もし問題に気付かれた方がおられましたら、お知らせいただけますと助かります。 またフィードバックやコントリビュータも随時募集しているので、こんな機能があったら嬉しいといった要望などありましたら、気軽にIssueで相談いただいたり、PRを送っていただけたりすると嬉しいです。 明日の記事はtakeshiさんによる「「自分ができる領域が増えた」-Cursorを使って未経験のKotlinコードレビューに挑戦」とShuntaさんによる「初めてのWWDC25に現地参加!Apple Parkで体験した特別な数日感」の2本です。引き続きお楽しみください。
こんにちは。メルペイSREの @foostan です。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の14日目の記事です。 皆さんはインシデント対応は好きですか。多くの方はこの答えにNoと答えるかもしれません。ただこの業界にいるとYesと答える方もいてなかなか楽しい気分になることがあります。ちなみに私はインシデントの非日常感に少し高揚するタイプではありますが、同僚がたくさんいる昼間に限ります。夜はできる限り携帯電話をスリープ状態にしたいものです。 さて、今回ご紹介するのはメルペイがローンチしてからの約6年間で培ってきたインシデント対応や管理のノウハウです。また実際に直面した課題を例としていくつか取り上げ、その改善をどのようにしてきたか共有します。 なお内容は以前登壇させて頂いた Incident Response Meetup vol.2 のものを少しアップデートしたものになります。 サービスについて 最初にインシデントに関わる話をするにあたり我々がどのような事業を展開し、どのようなデータや規模感でサービスを運用しているのか簡単に紹介させてください。 メルペイはメルカリアプリで使用できるスマホ決済サービスであり、iDやコード決済、メルカードを利用してお店やネットサイトで利用できます。サービスのローンチは2019年2月なので6年と少し経ちました。なおFintechの領域のサービスであり、金融情報や与信などを扱っているためサービスには高い信頼性が求められます。もし不具合が発生しサービスが中断してしまった場合は、速やかに関係各所への連絡と事後対応が求められます。 またメルペイの規模感は 150以上のマイクロサービス 40以上のチーム 1900万人以上の利用者※ と、国内ではそれなりの規模のサービスとなります。 ※ メルペイ「電子マネー」の登録、「バーチャルカード」の設定、「メルカード」の発行、暗号資産取引口座開設を行ったユーザと「メルペイコード決済」「ネット決済」「メルペイスマート払い(翌月払い・定額払い)」等の利用者の合計(自主退会・重複を除く)2025年3月末時点 システム / 組織構成 我々のサービスはマイクロサービスアーキテクチャを採用しており、小さな独立したサービスの集合体になっています。チームごとにサービスの開発や運用を分離できるため、他のサービスに依存することなく変更や拡張が可能です。 ロゴ出典: https://www.cloudflare.com/ja-jp/press-kit/ Google Cloud Official Icons and Solution Architectures チームと責任範囲の例を以下に示します。API GatewayサービスやAuthorityサービス、またCDNなどの共通コンポーネントやネットワーク関連はPlatformチームが担い、ビジネスロジックを持つサービスをProductチームが担います。またPlatformチームは各Productでサービスが運用できるようにサービスのインフラの提供や運用に必要なエコシステムの提供を行っています。開発や運用は基本的にこの責任範囲のもと行っているため、なにか不具合が起きたときはそれぞれの責任範囲で復旧を行います。 ロゴ出典: https://www.cloudflare.com/ja-jp/press-kit/ Google Cloud Official Icons and Solution Architectures エンジニアリング組織と内部統制の概略図を以下に示します。3線モデルに従い、プロダクト提供を行う1線、リスクやコンプライアンス管理する2線、内部監査の3線の大きく3つに分類されます。また1線についてProductチームが効果的に動けるようにSREチームやPlatformチームが存在します。その他にも複数のチームが存在しますが今回は省略しています。 私が所属するSREチームは大きく2つの役割があり、一つはプロダクトに近い立場で各領域の信頼性の向上や課題解決などを通してビジネスの成長に貢献するProduct SRE、もう一つはグループ全体にプラットフォームを提供してビジネスの成長を支えるPlatform SREです。インシデントに関してはProduct SREが実際の対応やサポート、ポストモーテムのレビューなどを行っており、Platform SREがインシデント管理のためのツールの選定や導入等を行っています。またインシデント管理のポリシーや対応フローの作成などインシデントに関わる統制はIT Riskとともに行っています。 インシデント対応・管理 続いて我々が行っているインシデント対応や管理方法について紹介します。 そもそも「インシデント」と言ってもいくつか意味を持ちますが、我々は以下のように分類しています。 システムインシデント : システムトラブル等による予期せぬサービス中断や品質の低下など セキュリティインシデント : サイバー攻撃、システムの脆弱性による情報漏洩など 事務事故 : 事務作業によるミスや不正による個人情報漏洩など 不正や犯罪 : アカウントの不正利用など なお本記事は基本的にシステムインシデントについての話です。特に言及がなく「インシデント」と記載する場合はシステムインシデントを指しています。 インシデント管理は準備、対応、学びのサイクルを繰り返します。各フェーズについて我々が実際に取り組んでいることの例をいくつかご紹介します。 インシデントへの備え SLO / アラートの整備 システムの異常を検知して対応に移れるようにモニターを整備します。どのようなモニターを用意するかはサービスや組織によってさまざまかと思いますが、我々はSLOをベースとしたものを用意しています。また異常を検知した後に原因を追求できるようにオブザーバビリティを確保したり、Dashboardを整備してシステムが正常に動いているかどうか確認できるような体制を取っています。 オンコールの整備 インシデントはいつ発生するかわかりません。仕事をしている日中かもしれないし、休日かもしれない。または夜中に発生する可能性もあります。なのでどのようなときでもアラートを受け取って対応できるようにローテーションを組んで待機します。なお弊社では社内規定を決めて手当が出るようにしています。異常にそなえて休日や深夜にも直ぐに行動ができるように待機するので精神的にも肉体的にも負荷がかかります。またこれは業務なので公平性を保つためにもこのような社内規定は重要です。 マニュアルの整備 緊急の対応は誰が実施するかわからないのでマニュアルを作成して誰でも対応できるように備えておくのが理想的です。前回の対応ログ等を残しておき、参照しやすいようにしておくのも効果的でしょう。最近だとAIの技術が急激に発展してきているので過去の実績をRAGなどによって与えられればAI Opsも現実味を帯びてきます。 インシデントへの対応 異常検知 サービスの異常を即座に捉えるために、アラートでオンコールのメンバーに連絡を送ります。監視と通知の概略は以下のとおりです。我々はGoogle CloudやCloudflareを利用しており、それをDatadogでモニタリングしています。異常を検知するとPagerDuty経由で電話を鳴らしたりSlack上に通知を行います。なお社内にはインシデント情報を共有するMercariグループ共通のSlackチャンネルがあり、一次情報はそこに集約されます。お客さまからのお問い合わせなどの情報もCS経由でここに集まります。 ロゴ出典: https://www.cloudflare.com/ja-jp/press-kit/ https://www.datadoghq.com/about/resources/ https://brandguides.brandfolder.com/pagerduty/logo https://slack.com/intl/ja-jp/media-kit Google Cloud Official Icons and Solution Architectures インシデントの識別 発生した内容や影響度からSEV(重大度)を見積もり対応を行います。SEVのレベルごとにポリシーを設けており、その後の対応方針が決まります(詳細は後述)。ただしいずれのレベルにおいても最優先でインシデントの緩和や解決に動き始めます。 エスカレーション / 通知 影響が社外に及ぶ場合は、並行してステークホルダーへの周知を行います。インシデントコマンダーもしくは連絡役を専任し、社内外の連絡窓口を一本化。お客さま、VPs、パートナー企業へ影響範囲・暫定対応・次回更新予定を通知します。 インシデントの緩和 / 解決 インシデントの対応は関連するチームが主体となって進めます。影響度が単一のプロダクトチームに閉じる場合はそのチームのPdMやEM、テックリードなどがインシデントコマンダーとなりインシデントの緩和や解決、その後の処理を行います。また規模が大きく複数のプロダクトチームをまたぐ場合はSREやPlatformチームなど組織横断で動きやすいチームが中心となって対応します。 インシデントからの学び ポストモーテム インシデントが解決したらなるべく早くポストモーテムを実施します。社内ではポストモーテムのガイドやテンプレートを用意して効果的に振り返りが行えるような体制を整えています。最近ではSlackの会話を要約したり時系列の情報を収集したりするためにAIの活用も進めています。 恒久対応および再発防止 ポストモーテムで特定された根本原因に対しては、一時しのぎではなく恒久的な対策を計画します。コードの修正だけでなく、運用プロセスや組織構造に起因するケースも多いため、変更管理やレビュー体制まで含めて見直します。また対応策は実現可能なものを策定し完了日を設定することを重要視しています。 レポート作成 / 共有 社内で整備しているテンプレートを利用してインシデントレポートを作成します。インシデントが発生してから解決するまでの時系列情報、被害情報、発生原因、対応内容、根本原因、事後改善策などの項目が含まれます。また後に分析できるように内容や原因はいくつかのカテゴリに分類して記録しています。レポート作成においても最近はAI活用の試みを始めており、できる限り早く情報共有ができるように改善を行っています。 インシデント分析 過去インシデントのデータを集約し、ダッシュボードで傾向を可視化しています。たとえばチーム、原因、SEV、MTTRなどをグラフ化し特定の領域の増加傾向の識別やプロセス等を改善した際の効果測定などに利用しています。また未解決インシデントや未実施のポストモーテムの数を可視化することでインシデントの管理プロセスが正常に動いているかどうかを定期的に監視し、悪化傾向にあればプロセス全体の見直しをするなどの判断も行っています。 インシデント識別と対応ポリシー 重大度を示すSEVの定義と対応ポリシーは以下のとおりです。なお公開用に抽象度が高い表現をしていますが、社内ではもっと詳細な定義があります。 SEV 概要 対応ポリシー SEV1 極めて重大なインシデント 全社で最優先に対応 プロジェクト化して継続的に改善 SEV2 多くのお客さまに影響を与える重大なインシデント 関係チームで最優先に対応 恒久対応・再発防止策の完了をIT Riskチームでトラッキング SEV3 お客さまに影響を与えるインシデント 関係チームで優先的に対応 恒久対応・再発防止策の完了をチームでトラッキング SEV4 お客さまに影響はないが対応が必要なインシデント 関係チームで対応 恒久対応・再発防止策の完了をチームでトラッキング SEV5 お客さまに影響はなく対応が不要と判断したインシデント 対応不要 SEVの定義は日々運用していく中で見直しを行っています。SEVの運用にはいくつかの課題がありますが、その一つは正しく選べないというものです。選択するための基準はありますが、機械的に完璧に定義することは難しく最終的にはどうしても人の判断が入ります。そこで最近ではSLOの毀損度に応じて自動的に判断するなど、わかりやすくかつ即座に判断できる新しい基準を設けるための議論も行っています。 課題と改善 数年運用する中で発生した課題やその解決方法、また現在抱えている課題とそれに対して今取り組んでいること、これから取り組もうとしていることなどをご紹介します。一部既に上述したものも含まれています。 大量のアラート インシデントまたはその予兆を知らせるアラートも大量に発生すると対応しきれません。また不要なアラートに埋もれてしまい、重要なアラートを見逃してしまうことで、インシデント対応の初動が遅れるリスクもあります。更にこのようなアラートを放置すると割れ窓理論によって状況が悪化する恐れがあります。 SLOアラート この問題を解決するために、CPUやメモリの使用率といったシステム内部のメトリクスではなく、お客さまへの影響度を指標化した SLO(Service Level Objective)ベースのアラートを採用しました。SLI(Service Level Indicator)はお客さまへ影響が出たときに変化するメトリクスを選ぶ必要があり、現在はエラー率とレイテンシを広く利用しています。「ページャーから呼び出しがある = お客さまへの影響が実際に発生している」という状態が理想です。なおこのSLOアラートは何度かアップデートを繰り返しており我々も試行錯誤を続けている最中です。最近では E2E Testを用いたマイクロサービスアーキテクチャでのUser Journey SLOの継続的最新化 で取り上げたCritical User Journeyに基づいたSLOアラートの仕組みが軌道に乗り始めており、運用のさまざまな場面での活用を進めています。 ロゴ出典: https://www.datadoghq.com/about/resources/ https://brandguides.brandfolder.com/pagerduty/logo https://slack.com/intl/ja-jp/media-kit https://brand.hashicorp.com/product_logos 第一報の遅延 インシデントを検知したあとの情報共有は、社内だけでなく社外に対しても迅速に行う必要があります。第一報が遅れると対応そのものが遅延するだけでなく、現場や関係者を混乱させ、さらなる被害拡大を招くおそれがあります。 自動報告システム SLO を基準とし、一定以上毀損した場合にあらかじめ登録された連絡先へ自動で第一報を送信する仕組みを導入しています。誤報を恐れずとにかく素早く第一報を送ることをコンセプトとしていますが、SLO をベースにしているため一定の精度も担保できます。 ロゴ出典: https://www.datadoghq.com/about/resources/ https://slack.com/intl/ja-jp/media-kit https://aws.amazon.com/jp/architecture/icons/ 情報過多 インシデント対応中は現場が混乱し、情報が過剰に流れてきます。また、途中から参加したメンバーが状況を瞬時に把握するのは困難です。熟練したインシデントコマンダーであればこうしたケアも行えますが、実際にはうまく機能しないことのほうが多いです。 インシデントサマライザー Slack の会話内容を自動で要約し、影響を受けたサービス、その被害状況、原因などをまとめて表示できるようにしています。LLM を利用しているため、プロンプトを変更するだけでさまざまなフォーマットへ容易に拡張できます。外部向け報告資料やポストモーテム資料の作成にも活用可能です。 インシデント管理 ガイドが浸透しない、ポリシーが守られない、ポストモーテムがいつまでも終わらない、恒久対応・再発防止策の実施が進まないなど決められたインシデント管理プロセスの統制を取るのが難しいという問題があります。 コミュニティの形成 インシデント対応・管理を向上させるためには、関係者全員の理解が欠かせません。まずは「自分ごと」として捉え、主体的に対応することが第一歩です。そのために各チームから代表者を選出してコミュニティを組織し、インシデント管理状況の共有、分析結果の報告、ナレッジ共有、課題の整理や解決策の立案・実施などを自主的に進められる体制を構築しました。ただし長年運用をしていると活動が少なくなってしまう時もあったため、SREやIT Riskの責務として継続することが重要だと感じています。 インシデント分析 インシデントに関するデータは順調に蓄積されていますが、現状を手軽に可視化・分析したいというニーズが高まっています。 インシデントダッシュボード インシデントの発生状況や要素別の集計結果、未対応タスクなどを可視化し、状況を迅速に把握できるダッシュボードを整備しました。しかし、現時点では詳細な分析まで踏み込めておらず、類似インシデントの検出や共通原因の特定といった高度な分析およびデータ活用は今後の課題です。 AI活用 最近の AI 技術の発展により、インシデント管理で有効に活用できる場面が急速に増えていると感じます。たとえば、不要なアラートが増えすぎている問題に対しては AI に分析させて削減案を自動で提案させたり、インシデント対応中に過去の類似インシデントを検索して対応の補助に利用できます。また、インシデント管理ツールの MCP サーバーを用意し、AI 経由でレポートの作成やデータ入力を依頼するなど、さまざまなアイデアが提案され実装が進んでいます。以前公開した LLM x SRE: メルカリの次世代インシデント対応 で紹介したIBISもその一例です。 AI 関連領域は目覚ましいスピードで進化しており、IBIS もアーキテクチャのアップデートや他の社内ツールとの連携を継続的に進めています。さらに SaaS でも Bits AI SRE のような AIを 活用したインシデント対応機能が登場しつつあります。 最後に 本記事では、6年間にわたる試行錯誤を通じて得られたインシデント対応・管理に関する知見をご紹介しました。SLO を利用した異常検知、コミュニティ形成によるポリシー適用と運用促進、ポストモーテムの徹底、AI 活用による効率化など、継続的に改善を進めています。インシデントを完全にゼロにすることはできませんが、仕組みに落とし込み、改善サイクルを回し続ければ、サービスの安定化とチームの強化につながると考えています。 本記事が皆さまの運用を一歩前に進めるヒントになれば幸いです。AIがもたらす大きな変化とカオスを楽しみながら運用を楽にし、安眠を勝ち取りましょう。 明日の記事はorfeonさんの「 Mercari Pipeline (旧Mercari Dataflow Template) v1を公開しました 」とfivestarさんの「 gRPC Federationを使った3rd party API開発事例:マネーフォワード連携から学ぶ実装ノウハウ 」の2本です。引き続きお楽しみください。
こんにちは。メルカリモバイル Backend チームでエンジニアリングマネージャーをしている @k_kinukawa です。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の12日目の記事です。 2025年4月21日にメルカリモバイル開発チームでオフサイトミーティングを実施し、その中でAI Hackathonを開催しました。チーム内でのAI活用を促進することを目的とし、約20名のソフトウェアエンジニア・PM・QAエンジニアが参加しました。 なぜAI Hackathonを実施したのか メルカリモバイル開発チームでは、2025年3月に社内の生成AI開発ツールのPoCに参加する形でCursor、Devin、Gemini Code Assistのアカウントをソフトウェアエンジニアに付与し、積極的な活用を推奨していました。しかし、4月中旬の時点で実際の活用は十分に進んでいませんでした。 メルカリモバイルは3月4日にローンチしたばかりの新サービス で、やりたいこともやるべきことも山積している状況でした。一方で、生成AI開発ツールを取り巻く状況は日々ものすごいスピードで変化しており、忙しい中で最新の情報をキャッチアップして業務で活用するのはなかなかハードルが高い状況でした。 そんな中、エンジニアリングマネージャーの週次定例ミーティング内でAI Labs TeamのマネージャーからCursor bootcampを実施したという共有がありました。Cursor bootcampでは以下のような取り組みが行われたとのことです。 Cursorのリファレンスを読み合わせる(1時間) 各々、取り組むことを決めて個別に作業する(2時間) 結果として参加者間での知識レベルとCursorに対する期待値が揃い、Cursor活用や議論が活発になったとのことでした。 また、 PCP LLM Week のような大規模な取り組みの話も耳にしていました。1週間という期間をかけて組織全体でAI活用に取り組むという非常に興味深い試みでしたが、メルカリモバイルの現状では同規模の時間を確保することは難しい状況でした。 これらの事例を参考に、現実的に実行可能な形でチームのAI活用を推進するために 1日という限られた時間で集中的にAI活用を体験する オフサイトイベントを企画しました。 普段の忙しい業務の中では断片的にしか触れないAIツールについて、まとまった時間を確保して一気にキャッチアップし、Hackathon形式で実際に楽しく手を動かして理解を深めることを目的としました。また、全員が同じタイミングで同じ体験をすることで、その後のチーム内での情報共有や議論の土台を作ることも狙いでした。 オフサイトの実施内容 オフサイトミーティングは社外の貸し会議室を利用しました。 参加対象者はソフトウェアエンジニアだけでなく、PM、QAエンジニアも含めました。 当日までに参加者全員に対してCursorのアカウント発行とダウンロードを実施しました。 時間割は以下の通りです。 午前 : アイスブレイク、相互理解セッション(偏愛マップ、ドラッカー風エクササイズ) 午後前半 : AIキャッチアップセッション(0.5時間) 午後後半 : AI Hackathon(2.5時間 + 発表1時間) 余談ですが、このオフサイトではAI Hackathonだけでなく、相互理解のためのセッションとして偏愛マップ、ドラッカー風エクササイズも実施しました。 このセッションを通じて、チームメンバーのパーソナルな一面を知ることができたり、お互いの期待値のズレを認識することができたりと非常に有意義でした。 AIキャッチアップセッション Hackathonに入る前に、全員の認識を揃えるためのキャッチアップセッションを実施しました。 事前に私が社内外の情報を取りまとめて、スライドで発表しました。 内容は以下の通りです。 組織目標の共有 : エンジニアリング組織として設定された重要な指標「全てのエンジニア100%が何らかのAIコーディングアシスタントツールを活用し生産性を高める」を改めて確認 AIツール利用ガイドライン : 社内におけるAIツールの基本的な利用方法が記載されたガイドラインの紹介、AIツールにどんな情報を入力してよいかの確認 社内のCursor利用ガイドライン : コードのindexingについての理解、.cursorindexingignoreファイルの理解 MCP Serverとセキュリティ : MCP Serverの説明、使用可能なMCP Serverの紹介、MCP Server利用時のセキュリティに関する注意点 社内のAIに関する取り組み、活用事例紹介 : 社内で利用可能なAIツールとその活用事例の紹介、AI活用をしているプロジェクトの紹介 社外の事例紹介 : 社外のAI開発ツール活用事例紹介 このセッションを通じて、メルカリ社員として社内業務でAI開発ツールを利用するために必要な基礎知識のキャッチアップと、AI開発ツールを使ってできることの認識を合わせることができました。 AI Hackathon 2.5時間の時間を使って、各々が事前に準備したアイデアの実現に取り組みました。 Hackathonを開催した4月末は、世の中的にMCP Serverが非常に注目されていたタイミングだったため、多くのメンバーがMCP Serverのセットアップとそれを活用したアイデアの検証にチャレンジしていました。 私は以下のようなことに取り組みました。 MCP Serverセットアップ支援 PMとQAエンジニアに対して、CursorとMCP Server(Confluence、Jira、Figma)のセットアップをサポート Terraform編集タスクをAIに任せる実験 あるメンバーへの権限付与タスクをCursorのAgentに依頼 Terraformコードの変更からプルリクエスト作成まで、一切手動でコードを書かずに実施 自作MCP Server開発チャレンジ Cursor Agentを使って、自作のMCP Server作成に挑戦(時間切れで未完成) チームメンバーがチャレンジしたAIを使ったアイデアも一部紹介します。 MCP Server開発・活用 Cursor と Jira、Figma、Confluence、Spanner、BQとのMCP Server 連携そその活用方法の模索 Proto MCP Server 開発 GitHub mermaid sequence diagram 生成 AI開発支援ツール活用 AI Reviewer 作成 Cursorでのリファクタリングとテスト 生成 GitHub MCP Server 連携 デザイン・プランニング領域 FigmaAI調査 新機能のプランニング(1時間で設計からリソース計画まで作成) テキストからワイヤーフレーム作成 業務改善 便利ツールの開発 Test Case自動生成 アンケート分析 私が今回のHackathonで最も印象的だったのは、PMがCursorとMCP Serverを活用して、メルカリモバイルの新機能プランニングに取り組んでいたことでした。 Hackathonの最初、Cursorにメルカリモバイルの最新のソースコードを読ませMCP ServerでConfluenceに接続できる状態までサポートしたのですが、そこからたった1時間程度で新機能開発に関する仕様作成、主要開発項目の洗い出し、既存機能を踏襲した設計、開発・QA工数とスケジュール算出を行いました。もちろんこれを使っていきなり開発に入れるわけではないのですが、叩き台としては十分使えるものが作れてしまったことに本人含めて皆驚いていました。 AI Hackathonを終えて 事後アンケートでは「CursorなどのAIツールをしっかり触る時間を取れて新しい技術をキャッチアップすることができてよかった」「業務から離れる時間を取ってAI活用にフォーカスする時間を取ることができて良かった」といった回答を頂きました。 ↑は DX というツールを使ってメルカリモバイルBackendチーム内のCursorによるline changesを集計したグラフです。4月21日以降、Cursorが日常的に活用されるようになったことが数字からも読み取れます。 また、チームのエンジニアによる メルカリ内製MCP Server リポジトリ へのコントリビュート(いくつかのサービスの追加)も行われました。 興味ある方は是非こちらも御覧ください。 Sourcegraph × 自作MCP Serverによる社内コード検索連携の取り組み Cursorを"導入"だけじゃなく"活用"まで メルカリ2000人展開のリアル 現在ではCursorだけでなく、DevinやClaude Code Agentの利用も徐々に増えています。 まとめ 今回のHackathonを通じて、以下のような学びがありました。 1. まとまった時間確保の重要性 環境は整っていても、忙しい日常では新しいツールを試す時間が取れない 目的のために強制的に時間を確保することで、全員が「最初のハードル」を超え同じスタートラインに立てる 2. 全員参加による学習効果 皆で一緒に作業することで、その場で疑問を解消できる 情報共有により全員の理解を一致させることができる ソフトウェアエンジニア以外のメンバーも効果的にAI開発ツールを活用できる可能性を確認することができた 個人の自発的な学習だけでなく、組織として意図的に学習機会を創出することの有用性を実感することができました。これからも組織としてAI-Nativeになるための機会や仕組みを継続的に作っていきたいと思います。 一方で、日々のミーティングや業務の量を見直していくことで余裕が生まれ、日常的に新しいことにチャレンジできる状態も作り出せるのではないかと考えています。これは今後の大きな課題だと考えています。 明日の記事は@David, @anzaiで「 Building a Flexible Checkout Solution: Frontend Architecture for Multi-Service Integration 」です。引き続きお楽しみください。
こんにちは。Fintech SREの佐藤隆広(@T)です。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の11日目の記事です。 Google社が提唱し、 Site Reliability Engineering Book によって広く知られるようになったSREの信頼性マネジメントは、開発と運用の関係性を再定義し、SLI/SLOとエラーバジェットに始まり、Availability・Latency・エラーレート・トラフィック・リソース飽和度・耐久性といったような指標で補強されてきました。 ところが近年、大規模言語モデル(LLM)の進歩が著しく、サービスにLLMを利用する機会が増えることによって、 プロンプトを数行変えただけで回答品質が変動する Latencyやエラーレートが良好でも幻覚(ハルシネーション)が急増する モデルの軽微なアップデートで回答スタイルが激変する といった、従来指標では見落としがちな事象に遭遇することが多くなりました。 つまり 「LLMサービスの信頼性」 を守るには、クラシックなインフラ指標の他に LLMサービス固有の品質指標 を重ね合わせてモニタリングする必要性が迫られています。 本記事では、LLMサービスの信頼性評価に不可欠な指標の選定から、具体的な測定・評価方法までを、DeepEvalライブラリを用いたデモを交えて紹介します。 1. LLMサービスの一般的評価指標 LLMサービスの信頼性を測る上で、どのような指標に着目すべきでしょうか? LLM Evaluation Metrics: The Ultimate LLM Evaluation Guide では、下記の評価観点の代表例が挙げられていました。 指標名 説明 回答の関連性 (Answer Relevancy) 質問に対して、どれだけ適切に答えているかを測る指標 タスク完遂度 (Task Completion) 与えられたタスクをどれだけ正確にやり遂げられたかを測る指標 正確さ (Correctness) 事前に用意された正解とどれだけ一致しているかを測る指標 幻覚の有無 (Hallucination) 事実に基づかない内容や、デタラメな情報が含まれていないかを測る指標 ツール使用の正確さ (Tool Correctness) タスクを達成するために正しいツールを選び、実行できたかを測る指標 文脈適合性 (Contextual Relevancy) 検索された情報が質問に対してどれだけ適切かを測る指標 責任あるAI指標 (Responsible Metrics) 差別的な表現や攻撃的な内容を含んでいないか、特定の属性に対して偏見を持っていないかなどを測る指標 タスク固有指標 (Task-Specific Metrics) 要約や翻訳など、「特定のタスク」においてLLMの性能を測るための指標 従来のサービスの代表的な指標として、AvailabilityやLatencyなどといったインフラ系SLIを監視すれば、ユーザージャーニーと関連付けてお客さま満足度を把握することができました。 しかしLLMサービスでは、「応答が意図に沿い、事実に基づいているか」「タスクを正しく完遂できたか」といった生成品質そのものがお客さま満足度に直結します。 そのため、従来のAvailabilityやLatencyに加え、LLMサービス特有の生成品質を捉えるSLIを設計し、お客さまが「意図どおりの正しい回答を迅速に得られるか」を定量的に示せる指標体系を整える必要があります。 では、具体的にLLMサービスの指標を設計する上で、どの指標を選定するべきでしょうか。 1.1. 一般的評価指標の落とし穴 上記の表にある、回答の関連性、正確さ、幻覚の有無といった一般的な評価観点は骨格ですが、すべてのLLMサービスのユースケース固有の成功条件をキャッチアップできるとは限りません。 たとえば要約サービスなら「網羅性」や「矛盾の有無」、RAGなら「検索文脈の適合度」といった独自指標がなければ、お客さまが得る価値を測り切れないことが多いです。 The Accuracy Trap: Why Your Model’s 90 % Might Mean Nothing という記事では、顧客離反(churn)予測モデルがテスト精度92%を達成したにもかかわらず、実運用では解約防止どころか誤警告と取りこぼしが発生し、結果として離反率が増えたことを解説しています。 教訓としては、お客さま視点のエンドツーエンド評価を最優先にする、ということだと思われます。 LLMサービスはRAGやエージェント機構など複雑な内部構造を持ちますが、中間コンポーネントをいくら改善しても、お客さまが受け取る回答が向上しなければROIは上がりません。 ブラックボックスとしての最終出力を計測し、エンドツーエンドで測った結果が、サポート工数削減や売上向上と相関することが、そのLLMサービスの選定すべき評価指標でしょう。 1.2. 優れた評価指標とは? The Complete LLM Evaluation Playbook: How To Run LLM Evals That Matter では、優れた評価指標の条件として、下記3点が挙げられていました。 定量的であること(Quantitative) 評価結果として数値スコアを算出できること。数値で評価できれば「合格ラインとなるしきい値」を設定したり、スコアの時系列変化を追ってモデル改善の効果を測定したりできることが望ましいです 信頼性が高いこと(Reliable) 常に安定した評価結果が得られること。LLMの出力に予測不能な揺らぎがある以上、評価指標まで不安定では困ります。例えばLLMを用いた評価手法(後述のLLM-as-a-judgeなど)は従来手法より高精度な反面、評価結果にばらつきが出やすい傾向があるため注意が必要です 正確であること(Accurate) LLMモデルの性能を実際の人間の評価と近い基準で的確に反映できること。評価スコアが高い出力=人間にとって良好と感じられる出力、となるのが理想であり、そのためには人間の期待と整合した基準で評価する必要があります また、評価指標値がいくら高いスコアを叩き出しても、売上やお客さま満足度などのビジネス成果につながらなければ意味がありません。 同記事では、これを Metric Outcome Fit(指標と成果のつながり) と呼んでおり、「現場で行われるLLMの指標評価の95%は、このつながりがなく価値を生まない」とまで言及されていました。ビジネス上「良い結果」とみなされるケースを指標が確実に“良い”と判定できるか、上記を確認・調整し続けることが、指標を外さない唯一の方法、と紹介されています。 2. 指標の評価方法の全体像 次に、指標を実際に評価する手法の種類について紹介します。大別すると、下記の4つが存在し、それぞれに長所・短所があります。 統計的手法 (string-based / n-gram based / surface base) LLM以外のモデルを用いる手法 (classifier / learned metrics / small-LM metrics) 統計的手法、LLM以外のモデルを同時に用いるハイブリッドな手法 (embedding-based metric) LLMそのものを用いる手法 (LLM based / generative evaluator) 2.1 統計的手法 人手で作成した正解データと出力テキストを文字列レベルで比較し、類似度を測って評価する手法です。 BLEU モデル出力と期待される正解文との1〜4-gram 精度を平均し、brevity penalty を乗法して精度ベースで算出し、長さの過不足に対するペナルティも加味したスコアを与えます ROUGE 要約評価によく用いられ、ROUGE-Lは LCS(最長共通部分列)ベースで再現率と精度の F1を取り、ROUGE-1/2 が n-gram再現率に基づき要約が元文書をどれだけカバーしているかを測ります METEOR 精度と再現率の両面から評価し、語順の違いや同義語のマッチングも考慮する指標です。(最終スコアは精度・再現率の調和平均に語順ペナルティを乗法して算出) 編集距離( レーベンシュタイン距離 ) 出力と正解の文字列差分そのものを測定する指標。実務では複数文長の比較にそのまま使うことは稀で、キャッチアップコストの割には使用されていないケースが多いようです ref: LLM evaluation metrics — BLEU, ROGUE and METEOR explained これら統計的指標は計算が単純で再現性(一貫性)は高いですが、テキストの意味や文脈を考慮しないためLLMが生成するような長文回答や高度な推論を要する出力の評価には不向きです。事実、純粋な統計手法では出力の論理的整合性や文意の正しさまでは評価できず、複雑な出力に対しては精度が不十分だとされています。 2.2. LLM以外のモデルを用いる手法 評価専用の機械学習モデルを用いて、分類モデルや埋め込みモデルなど、比較的軽量な自然言語処理モデルを使って評価する手法です。 NLI(自然言語推論)モデル LLMの出力が与えられた参照テキスト(事実情報など)に対して、整合しているか(Entailment)/ 矛盾しているか(Contradiction)/ 無関係か(Neutral)を分類できます。この場合、モデルの出力スコアは「論理的にどれだけ一貫しているか」を表す0.0~1.0の確率値になります Transformer型の言語モデル(NLI, BLEURTなど)をベースに学習した専用モデル LLMの出力と期待される正解との類似度をスコアリングして計測する手法で、モデルベース手法では、テキストの意味をある程度考慮した評価が可能になりますが、評価モデル自体に不確実性があるため、スコアの一貫性(安定性)に欠ける場合があります。例えば、NLIモデルは入力文が長大になるとうまく判断できなかったり、BLEURTは学習データの偏りに影響を受け評価が偏る可能性が指摘されています 2.3. 統計的手法、LLM以外のモデルを同時に用いるハイブリッドな手法 上記の中間に位置する手法で、事前学習済み言語モデルの埋め込んでベクトル化した値と、統計的な距離計算を組み合わせて評価する手法です。 BERTScore BERT などで求めた各単語の文脈ベクトル同士の コサイン類似度 を計算し、出力文と参照文の意味的な重なり度合いを測定します MoverScore 出力文と参照文それぞれについて単語埋め込みを用いた分布を作成し、そこから Earth Mover’s Distance(最適輸送距離) を計算して両者の差異を測定します これらの手法は単語レベル・表面レベルを超えて意味的な近さを捉えられる点で統計的手法で挙げたBLEUなどより優れていますが、結局は元となる埋め込みモデル(BERT等)の性能やバイアスに影響されるという弱点があります。例えば専門領域の文脈や最新の知識について、事前学習モデルが適切なベクトル表現を持っていなければ正確な評価はできません。また評価モデルが内包する社会的バイアスがスコアに現れるリスクもあります。 2.4. LLMを用いた手法(LLM-as-a-judge) 評価手法の中でも近年注目されているのが、LLM自体に計測させて出力品質を評価させる手法、LLM-as-a-judgeです。 高度なLLMに「与えられた回答が基準を満たすか評価してください」と指示を与え、モデルから評価スコアや判定を引き出すアプローチになります。 LLMは文章の意味理解や複雑な判断ができるため、人間の主観に近い評価を自動化できる点が大きな長所です。 実際、GPT-4を評価者に用いる G-Eval という手法では、評価スコアと人間評価との相関が従来の自動評価よりも大幅に向上することが、 G-Eval Simply Explained: LLM-as-a-Judge for LLM Evaluation という記事でも紹介されています。 一方で、LLMベースの評価はそのモデルの応答次第で結果が変動しうるため、スコアの安定性(信頼性)に課題があります。 LLMに同じ回答を再評価させても毎回まったく同じスコアが得られる保証はなく、モデルのランダム要素や出力の揺らぎが評価結果にも影響を及ぼすためです。 下記に、代表的なLLM-as-a-judgeの手法をピックアップしてみます。 G-Eval 評価基準を1~5段階スケールで採点し、LLMが評価スコアと評価結果の理由(Chain of Thoughtの結果)を返す仕組み QAG Score 出力からQA(Yes/No/Unknown)を自動生成し、原文で同じQAを解き、両者の一致率をスコアにする SelfCheckGPT 同じプロンプトでN回サンプリングし、生成文同士の一貫性(例:N-gram・QA・BERTScoreなど複数の比較モード)を測って事実性を推定する。ばらつきが大きいほど幻覚の可能性が高くなる DAG(deep acyclic graph) DeepEval が提供する決定木型メトリック。各ノードはLLM判定(Yes/No)で、経路によって固定スコアを返すため LLM-as-a-judgeなのにブール判定ノードを決定木で束ね、部分点を決定論化する Prometheus2 Model GPT-4を含む高品質ジャッジのフィードバックと多数の評価トレースで蒸留した7B/8×7Bの評価モデル。人間/GPT-4との一致率0.6〜0.7(直接採点), 72–85%(ペアワイズ比較)で立証済み 最後に、ここまで挙げた指標の計測評価方法をまとめてみたのが下記の表になります。 種類 具体的な手法 長所 短所 統計的手法 BLEU / ROUGE / METEOR / 編集距離(レーベンシュタイン距離) ・計算が単純で高速・再現性が高い ・追加学習が不要で実装が容易 ・意味・文脈を考慮せず表層一致のみを評価 ・論理整合性や高度な推論が必要な出力には不向き LLM 以外のモデルを用いる手法 NLI(自然言語推論)モデル / BLEURT / Transformer ベースの専用評価モデル ・意味理解や論理的一貫性をある程度評価できる ・LLM より計算コストが低く、自前で fine-tune 可能 ・評価モデル自体の不確実性 ・バイアスに依存 ・長文・専門領域で精度が低下しやすい ハイブリッド手法 BERTScore / MoverScore ・埋め込みで語義的近さを捉え、統計指標より高精度 ・決定論的で再現性を保ちやすい ・埋め込み元モデルの学習範囲・バイアスに左右される ・最新知識や狭い専門領域では適合しにくい LLM を用いる手法(LLM-as-a-judge) G-Eval / QAG Score / SelfCheckGPT / DAG (Deep Acyclic Graph) / Prometheus2 Model ・人間評価に近い複雑な判断を自動化できる ・回答の多面的品質を一括で評価可能 ・出力が確率的でスコアに揺らぎが出やすい ・モデル利用コストが高く、プロンプトに敏感 これら評価手法を実際に計測するには、効率的に測定するためのツールが必要です。 そこで、今回はLLM評価ライブラリの中から参考記事で垣間見ていたDeepEvalについて紹介したいと思います。 3. DeepEval DeepEval は、LLMサービスを評価するためのPythonライブラリです。 テストケースの作成、評価指標の定義、評価の実行を行うためのフレームワークを提供します。 DeepEvalは、応答の関連性、忠実性、文脈の精度など、さまざまな側面を評価する指標をサポートしており、カスタム指標や評価データセットの自動生成、Pytestのようなテストフレームワークとの統合もサポートしています。 公式ドキュメント には、詳細なインストール手順、基本的な使用方法、各種評価指標の設定方法、カスタム指標の作成方法などが詳しく解説されています。 それでは、簡単な要約サービスを元に、評価手順を実践してみようと思います。 3.1 実践例: 要約サービスでの指標決定と測定方法 ここで想定する要約サービスは、記事やドキュメントなどの長文を入力として受け取り、その内容を短くまとめた要約文を生成するサービスです。 LLMの仕組み的に得意分野として真っ先に思いつくサービスだと思います。 今回は、グリム童話を要約して、子供でもわかるような文章で要約してくれるサービスを考えてみたいと思います。 3.2 指標の選定 要約という観点から、一般的な評価指標として思いつく指標は、 回答の関連性 (Answer Relevancy) , 正確さ (Correctness) , 幻覚の有無 (Hallucination) です。 Deepevalの G-Eval を利用して、上記3つの指標に対応することができますが、今回のケースでの 1.2. 優れた評価指標とは? に該当するか調査する必要があります。 定量的であること(Quantitative) G-Evalは0〜1の連続スコアを返すので、評価結果として数値スコアを算出できると言えます 信頼性が高いこと(Reliable) G-Evalは本来確率的ですが、LLMモデルに渡す temperatureのオプションを0で呼び出し 、 evaluation_stepsを固定しCoT生成処理をスキップ 、 Rubricを指定して評価スコアを一定にする という3点を実行すれば、同じ入力で同じスコアがほぼ再現させることができるので、常に安定した評価結果が得られそうです(厳密には、OpenAI側の sampling noise、 system randomness が残っており完全再現には至りません。top_p=0, seed 固定可能な API/backend を使うか,最終的には majority vote/ensemble 評価が推奨されます) 正確であること(Accurate) G-Evalは参照(expected_output、今回のケースの場合、グリム童話の原文や正解データです)付きの評価であり、事実照合を中心とするタスクではG-Evalは人間判定との相関が高いことが論文・実運用の両方で示されています。 よって、今回のケースでは、 回答の関連性 (Answer Relevancy) , 正確さ (Correctness) , 幻覚の有無 (Hallucination) の指標について、DeepEvalのG-Evalでの指標評価を使用することは妥当だと言えそうです。 3.3 評価観点の分解 次に、ピックアップした指標をどのような手順で評価させるのか、評価するために必要な観点やステップを列挙していきます。 幸いなことに、評価観点を分解する上で、参考になりそうな文献が、Google Cloudの Vertex AIのドキュメント – モデルベース評価の指標プロンプト テンプレート にありましたので、今回はそちらを参考に評価観点を分解していきたいと思います。 回答の関連性 (Answer Relevancy) STEP1. Identify user intent – List the explicit and implicit requirements in the prompt. STEP2. Extract answer points – Summarize the key claims or pieces of information in the response. STEP3. Check coverage – Map answer points to each requirement; note any gaps. STEP4. Detect off-topic content – Flag irrelevant or distracting segments. STEP5. Assign score – Choose 1-5 from the rubric and briefly justify the choice. 正確さ (Correctness) STEP1. Review reference answer (ground truth). STEP2. Isolate factual claims in the model response. STEP3. Cross-check each claim against the reference or authoritative sources. STEP4. Record discrepancies – classify as omissions, factual errors, or contradictions. STEP5. Assign score using the rubric, citing the most significant discrepancies. 幻覚の有無 (Hallucination) STEP1. Highlight factual statements – names, dates, statistics, citations, etc. STEP2. Compare with provided context and known reliable data. STEP3. Label claims as verified, unverifiable, or false. STEP4. Estimate hallucination impact – proportion and importance of unsupported content. STEP5. Assign score following the rubric and list specific hallucinated elements. 3.4 評価スコアの算出 では、実際に評価測定をして評価スコアを算出してみます。 まず、要約させる題材とプロンプトを用意します。 今回、グリム童話の原文は 赤ずきん を使用し、プロンプトは下記を用意してみました。 以下のグリム童話の内容の要約を作成してください。 要件: 1. 主要な登場人物や重要な要素を特定して含める 2. 内容の流れを論理的に整理する 3. 重要な出来事や転換点を含める 4. 原文の内容に忠実であること 5. 要約の長さは500文字以内に収める グリム童話の内容: {赤ずきんの原文} 要約:""" 使用した評価スクリプトは下記になります。 import asyncio import openai from deepeval.metrics.g_eval.g_eval import GEval from deepeval.metrics.g_eval.utils import Rubric from deepeval.test_case.llm_test_case import LLMTestCase, LLMTestCaseParams async def evaluate_comprehensive_metrics(client: openai.AsyncOpenAI, test_case: LLMTestCase, prompt_name: str, original_text: str) -> dict: """G-Evalメトリクス評価を実行""" # 回答の関連性評価 (Answer Relevancy) geval_answer_relevancy = GEval( name="Answer Relevancy", evaluation_steps=[ "STEP1. **Identify user intent** – List the explicit and implicit requirements in the prompt.", "STEP2. **Extract answer points** – Summarize the key claims or pieces of information in the response.", "STEP3. **Check coverage** – Map answer points to each requirement; note any gaps.", "STEP4. **Detect off-topic content** – Flag irrelevant or distracting segments.", "STEP5. **Assign score** – Choose 1-5 from the rubric and briefly justify the choice.", ], rubric=[ Rubric(score_range=(0, 2), expected_outcome="Largely unrelated or fails to answer the question at all."), Rubric(score_range=(3, 4), expected_outcome="Misunderstands the main intent or covers it only marginally; most content is off-topic."), Rubric(score_range=(5, 6), expected_outcome="Answers the question only partially or dilutes focus with surrounding details; relevance is acceptable but not strong."), Rubric(score_range=(7, 8), expected_outcome="Covers all major points; minor omissions or slight digressions that don’t harm overall relevance."), Rubric(score_range=(9, 10), expected_outcome="Fully addresses every aspect of the user question; no missing or extraneous information and a clear, logical focus."), ], evaluation_params=[LLMTestCaseParams.INPUT, LLMTestCaseParams.ACTUAL_OUTPUT, LLMTestCaseParams.RETRIEVAL_CONTEXT], model="gpt-4o" ) # 正確さ評価 (Correctness) geval_correctness = GEval( name="Correctness", evaluation_steps=[ "STEP1. **Review reference answer** (ground truth).", "STEP2. **Isolate factual claims** in the model response.", "STEP3. **Cross-check** each claim against the reference or authoritative sources.", "STEP4. **Record discrepancies** – classify as omissions, factual errors, or contradictions.", "STEP5. **Assign score** using the rubric, citing the most significant discrepancies.", ], rubric=[ Rubric(score_range=(0, 2), expected_outcome="Nearly everything is incorrect or contradictory to the reference."), Rubric(score_range=(3, 4), expected_outcome="Substantial divergence from the reference; multiple errors but some truths remain."), Rubric(score_range=(5, 6), expected_outcome="Partially correct; at least one important element is wrong or missing."), Rubric(score_range=(7, 8), expected_outcome="Main facts are correct; only minor inaccuracies or ambiguities."), Rubric(score_range=(9, 10), expected_outcome="All statements align perfectly with the provided ground-truth reference or verifiable facts; zero errors.") ], evaluation_params=[LLMTestCaseParams.ACTUAL_OUTPUT, LLMTestCaseParams.RETRIEVAL_CONTEXT], model="gpt-4o" ) # 幻覚の有無評価 (Hallucination) geval_hallucination = GEval( name="Hallucination", evaluation_steps=[ "STEP1. **Highlight factual statements** – names, dates, statistics, citations, etc.", "STEP2. **Compare with provided context** and known reliable data.", "STEP3. **Label claims** as verified, unverifiable, or false.", "STEP4. **Estimate hallucination impact** – proportion and importance of unsupported content.", "STEP5. **Assign score** following the rubric and list specific hallucinated elements.", ], rubric=[ Rubric(score_range=(0, 2), expected_outcome="Response is dominated by fabricated or clearly false content."), Rubric(score_range=(3, 4), expected_outcome="Key parts rely on invented or unverifiable information."), Rubric(score_range=(5, 6), expected_outcome="Some unverified or source-less details appear, but core content is factual."), Rubric(score_range=(7, 8), expected_outcome="Contains minor speculative language that remains verifiable or harmless."), Rubric(score_range=(9, 10), expected_outcome="All content is grounded in the given context or universally accepted facts; no unsupported claims.") ], evaluation_params=[LLMTestCaseParams.ACTUAL_OUTPUT, LLMTestCaseParams.RETRIEVAL_CONTEXT], model="gpt-4o" ) await asyncio.to_thread(geval_answer_relevancy.measure, test_case) await asyncio.to_thread(geval_correctness.measure, test_case) await asyncio.to_thread(geval_hallucination.measure, test_case) # Rubricスコアを推定する関数(表示用) def extract_rubric_score_from_normalized(normalized_score, rubric_list): """正規化されたスコア(0.0-1.0)からRubricの範囲を特定""" scaled_score = normalized_score * 10 for rubric_item in rubric_list: score_range = rubric_item.score_range if score_range[0] <= scaled_score <= score_range[1]: return { 'scaled_score': scaled_score, 'rubric_range': score_range, 'expected_outcome': rubric_item.expected_outcome } return None answer_relevancy_rubric_info = extract_rubric_score_from_normalized( geval_answer_relevancy.score, geval_answer_relevancy.rubric ) correctness_rubric_info = extract_rubric_score_from_normalized( geval_correctness.score, geval_correctness.rubric ) hallucination_rubric_info = extract_rubric_score_from_normalized( geval_hallucination.score, geval_hallucination.rubric ) return { "answer_relevancy_score": geval_answer_relevancy.score, "answer_relevancy_rubric_info": answer_relevancy_rubric_info, "answer_relevancy_reason": geval_answer_relevancy.reason, "correctness_score": geval_correctness.score, "correctness_rubric_info": correctness_rubric_info, "correctness_reason": geval_correctness.reason, "hallucination_score": geval_hallucination.score, "hallucination_rubric_info": hallucination_rubric_info, "hallucination_reason": geval_hallucination.reason, } async def generate_summary(client: openai.AsyncOpenAI, prompt_template: str, full_story: str, model: str = "gpt-4o") -> str: """LLMを使って要約を生成""" prompt = prompt_template.format(context=full_story) try: response = await client.chat.completions.create( model=model, messages=[{"role": "user", "content": prompt}], max_tokens=300, temperature=0.0, top_p=0, logit_bias={} ) content = response.choices[0].message.content return content.strip() if content else "" except Exception as e: return f"Error: {str(e)}" async def process_prompt(client: openai.AsyncOpenAI, prompt_info: dict, full_story: str, context: list) -> dict: model = prompt_info.get("model", "gpt-4o") # 要約生成 summary = await generate_summary(client, prompt_info["template"], full_story, model) # テストケース作成 test_case = LLMTestCase( input=prompt_info["template"], # プロンプト actual_output=summary, # 要約結果 retrieval_context=context # 要約対象(童話)の原文 ) # 評価実行 metrics_result = await evaluate_comprehensive_metrics(client, test_case, prompt_info['name'], full_story) return { "prompt_name": prompt_info['name'], "model": model, "summary": summary, **metrics_result } async def main(): # 童話の原文を読み込み with open('little_red_riding_hood.txt', 'r', encoding='utf-8') as f: full_story = f.read().strip() context = [full_story] prompts = [ { "name": "prompt-01", "template": """以下のテキストを読んで、内容の要約を作成してください。 要件: 1. 主要な登場人物や重要な要素を特定して含める 2. 内容の流れを論理的に整理する 3. 重要な出来事や転換点を含める 4. 原文の内容に忠実であること 5. 要約の長さは250文字以内に収める テキスト: {context} 要約:""", "model": "gpt-4o" }, ] async with openai.AsyncOpenAI() as client: tasks = [ process_prompt(client, prompt_info, full_story, context) for prompt_info in prompts ] all_results = await asyncio.gather(*tasks) # 結果表示処理 ... if __name__ == "__main__": asyncio.run(main()) 実行した要約結果は下記になりました。 昔、赤ずきんちゃんという愛らしい女の子がいました。彼女はおばあさんから赤いずきんをもらい、それをいつもかぶっていました。 ある日、病気のおばあさんにお菓子とぶどう酒を届けるため、森を通っておばあさんの家に向かいます。 途中で狼に出会い、行き先を教えてしまいます。狼は先回りしておばあさんを飲み込み、赤ずきんちゃんも騙して飲み込みます。 しかし、通りかかった狩人が狼のお腹を切り開き、赤ずきんちゃんとおばあさんを救出します。赤ずきんちゃんは教訓を得て、二度と森で道を外れないと心に誓いました。 G-Evalが評価した結果は下記になります。(1回目を抜粋) - 回答の関連性 (Answer Relevancy): 0.912 - Expected Outcome: Fully addresses every aspect of the user question; no missing or extraneous information and a clear, logical focus. - Reason: The summary includes key characters like Little Red Riding Hood, her grandmother, the wolf, and the hunter. It logically organizes the flow of events, such as the journey through the forest, the encounter with the wolf, and the rescue. Important events like the wolf's deception and the rescue by the hunter are covered. The summary is faithful to the original text and concise, with no extraneous information. - 正確さ (Correctness): 0.901 - Expected Outcome: All statements align perfectly with the provided ground-truth reference or verifiable facts; zero errors. - Reason: The main facts in the Actual Output align well with the Retrieval Context, including the characters, events, and moral of the story. Minor details like the specific dialogue and actions are slightly condensed but do not affect the overall accuracy. - 幻覚の有無 (Hallucination): 0.903 - Expected Outcome: All content is grounded in the given context or universally accepted facts; no unsupported claims. - Reason: The output closely follows the context with accurate details about Little Red Riding Hood, her grandmother, the wolf, and the hunter. The sequence of events and character actions are consistent with the context, with no unsupported claims. スコアを決定した評価理由を見ますと、各指標に対して的確に評価しているようにみえます。 3.2. 指標の選定 で、G-Evalは評価に揺らぎがあることを紹介しました。よって、上記のスクリプトを50回実行して、計測した評価数値の散布図は下記になります。 結果的には、すべての指標でスコア値が概ね 0.9以上 になりましたが、これで各指標のSLI値を概ね0.9としてSLOを0.9以上として目標値に掲げることはできるでしょうか? 3.5. 評価指標のレビュー 上記で紹介したとおり、このサービスは、 グリム童話を要約して、子供でもわかるような文章で要約してくれるサービス です。 上記の要約結果を 子供でもわかるように するには、下記の指標も考慮しないといけないでしょう。 可読性 (Readability): 子供が読めない難しい漢字、表現が使われていないか? 騙して?、教訓?、ぶどう酒? 安全性・有害性 (Toxicity / Safety): 現代のコンプライアンスと照らし合わせて、子供には過激な表現が使われていないか? お腹を切り開き? 評価指標はお客さま価値とビジネスKPIと密接に関連付けることを意識して評価指標を選定する必要があります。 今回の要約サービスの場合、一般的評価指標より、対象者を考慮して上記の指標をタスク固有指標(Task-Specific Metrics)として最優先に考える指標にするべきです。 また、それに伴い、プロンプトも修正しなければならないでしょう。 とはいえ、初回から完璧な指標セットを作るのは困難です。 The Complete LLM Evaluation Playbook: How To Run LLM Evals That Matter では、 評価指標はまず1つから始め、最終的には5つに絞る指標設計が望ましい とありました。 評価指標のスコアが、 Metric Outcome Fit – 指標と成果のつながり (子どもたちに頻繁に利用されること)と、どれだけ一致しているか、意識しながら指標を選定、計測、評価する必要があります。 (実サービスだった場合、ビジネスKPIとしては、文章より画像で提供した方が、良い成果が得られるかもしれません) 3.6. 自動化の可能性を探る 今回の例では、人間が指標の選定、評価スコアの算出、評価スコアの算出、指標評価のレビューを実施しました。 G-EvalはGPT-4クラスのモデルに「評価手順を自分で分解して考えさせ、最終スコアだけを返させる」仕組みをとるため、人間の代わりに 評価基準の適用・スコアリング・集計までをワンショットで自動化できます。 以下はその手順例です。 評価タスクの提示: 評価に使うLLMに対し、「これから提示する生成文をある評価基準に従って1〜5点で採点して下さい」といったタスク説明を与える。その際に、その評価基準の定義も明示してLLMに文脈を教える(例えば、LLMサービスの一般的評価指標にあった指標一覧を提示する) 評価観点の分解: 1.でLLMが選定した指標に対して、必要な観点やステップを自ら列挙させる スコア算出: 続いてモデルに、先ほど生成した評価ステップに従い、実際の入力・出力を評価させる 注意点として、LLMが評価者だと“LLMらしい”出力を過大評価し、数語仕込むだけでスコアを操作される脆弱性があります。そのため、別系列のLLMモデルで評価してみることや、2つの回答を並べてどちらが良いか比べるペアワイズ比較、異常検知などで緩和を試みても、完全な中立性は保証できません。 また、 3.2. 指標の選定 でも紹介しましたが、G-Evalは確率的評価手法が故に、同じ回答でも評価が揺らぐという再現性に問題があり、評価プロンプトやシードを固定するなどの工夫が必要です。 これらの理由から、最終判断は必ず人間のレビューを併用して補正・検証する二段構えを取ることが不可欠です。 4. まとめ LLMサービスの信頼性評価に不可欠な指標の選定から、具体的な測定・評価方法までを、DeepEvalライブラリを用いたデモを交えてご紹介しました。 従来のAvailabilityやLatencyといった指標だけでは測りきれない『LLMサービスの信頼性評価』の指標をSLIとしてどう定義するかは、SREにとっても新しい分野だと思います。 本記事で試したDeepEvalなどの評価ツールのアプローチも、数ある選択肢の一つに過ぎません。LLMの評価指標は現在も絶賛研究中の分野であり、LLMサービスの信頼性をどう測るか、という問いに、まだ唯一の正解はなさそうです。ただ、この先、新しい評価指標や新しい測定手法が発見されたとしても、 『この指標は本当にお客さま満足度を表しているのか?』 という問いは、変わることのない本質的な問いかけだと思います。 技術の進歩とともに、この問いかけを忘れず、日々のSRE業務に取り組んでいければ幸いです。 明日の記事は @k_kinukawaさんの「メルカリモバイル Dev OffsiteでAI Hackathonをした話」です。引き続きお楽しみください。 References Site Reliability Engineering Book: https://sre.google/books/ LLM Evaluation Metrics: The Ultimate LLM Evaluation Guide: https://www.confident-ai.com/blog/llm-evaluation-metrics-everything-you-need-for-llm-evaluation The Accuracy Trap: Why Your Model’s 90 % Might Mean Nothing: https://medium.com/%40edgar_muyale/the-accuracy-trap-why-your-models-90-might-mean-nothing-f3243fce6fe8 The Complete LLM Evaluation Playbook: How To Run LLM Evals That Matter: https://www.confident-ai.com/blog/the-ultimate-llm-evaluation-playbook レーベンシュタイン距離: https://note.com/noa813/n/nb7ffd5a8f5e9 LLM evaluation metrics — BLEU, ROUGE and METEOR explained: https://avinashselvam.medium.com/llm-evaluation-metrics-bleu-rogue-and-meteor-explained-a5d2b129e87f BERTScore: https://openreview.net/pdf?id=SkeHuCVFDr BERT: https://en.wikipedia.org/wiki/BERT_(language_model) コサイン類似度: https://atmarkit.itmedia.co.jp/ait/articles/2112/08/news020.html MoverScore: https://arxiv.org/abs/1909.02622 Earth Mover’s Distance(最適輸送距離): https://zenn.dev/derwind/articles/dwd-optimal-transport01#%E6%9C%80%E9%81%A9%E8%BC%B8%E9%80%81%E8%B7%9D%E9%9B%A2 G-Eval (Paper): https://arxiv.org/abs/2303.16634 G-Eval Simply Explained: LLM-as-a-Judge for LLM Evaluation: https://www.confident-ai.com/blog/g-eval-the-definitive-guide QAG Score: https://arxiv.org/abs/2210.04320 SelfCheckGPT: https://arxiv.org/abs/2303.08896 DAG(deep acyclic graph): https://deepeval.com/docs/metrics-dag Prometheus2 Model: https://arxiv.org/abs/2405.01535 DeepEval: https://deepeval.com/docs/getting-started Vertex AI – モデルベース評価の指標プロンプト テンプレート: https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates 赤ずきん: https://ja.wikipedia.org/wiki/%E8%B5%A4%E3%81%9A%E3%81%8D%E3%82%93
はじめに こんにちは!メルペイのBalanceチームの中にあるSettlementチームでインターンをしていました、somaです。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の10日目の記事です。 本記事では、インターンでやったことやその感想などを書いていこうと思います。 チームについて Settlementチームは、主にメルペイの加盟店さまの売り上げを集計し、振り込みの指示を行うといった役割を担当しています。 Settlementのシステムについて詳しく知りたい方は、 こちらの記事 が参考になると思います。 取り組んだこと 私が担当したタスクは大きく分けて4つあります。 既存のAPIの分割 決済トランザクションのハンドラの改修 新規機能の設計 お問い合わせ対応の補助をするSlack Botの開発 今回は、太字の3つについて話していこうと思います。 既存のAPIの分割 背景 Settlementサービスには加盟店さまの 月に何回売り上げの入金を行うか 売り上げの入金を次の月に繰り越すかどうか、繰り越すのであればいくら以下の場合か などの設定を行うためのAPIが存在します。 しかし、これらの設定がすべて1つのAPIで行われているため、リクエストが重なると片方のリクエストの結果だけが反映されてしまうリスクがあります。そこで、このAPIを各フィールドを変更する複数のAPIに分割することにしました。 やったこと 各マイクロサービス間の通信にはgRPCが使われているため、まずはProtocol BuffersでAPIのインターフェースを定義しました。社内でProtocol Buffersは共通のリポジトリで管理されており、そこにマージすると、Protocol Buffersの内容に基づいて自動生成されるクライアントライブラリにGoのinterfaceなどのコードが自動生成されます。 その後、その中身をマイクロサービスのリポジトリで実装しました。 また、モックも自動生成されてE2Eテスト上で動作確認できるため、わざわざ自分でAPIを叩いて挙動を確かめなくてもテストができるのは開発がしやすいなと思いました。 新規機能の設計 やったこと 新規機能を追加するにあたって、 どの部分にどのような変更が必要か その変更の実現方法の選択肢と、それぞれのメリットデメリットを考慮した上でどの方法を選択するのが望ましいか タスクへの切り分けと、それぞれのタスクの見積もり QAで確認して欲しいポイント などを考え、設計書を作成しました。 最初に丁寧に説明をしてもらえるわけではなく、仕様書を読みながら自分でプロジェクトについて理解しながら調査をして、設計に取り掛かりました。その中で、どうしてもわからないことは質問して理解しました。それにより、わからないことを整理し、仮説を立て、調査をして検証するような働き方を身につけることができました。 お問い合わせ対応の補助をするSlack Botの開発 背景 最近のメルカリエンジニアリングブログを見てもわかるように、現在メルカリ社内ではAIをどんどん活用していこうという動きがあります。そこで、Settlementチームでも何かできることはないかと話し合った結果、他部署からのお問い合わせに対する調査や回答をしてくれるBotを作りたいという話になりました。 Settlementチームには、加盟店さまから主に振込に関するお問い合わせが届きます。それに対してエンジニアが調査を行い、回答をしたり追加で質問をしたりするなど、対応を決定します。しかし、毎回データベースやコードから手作業で調査を行って対応をするのはコストが高いです。そこで、お問い合わせに対して、データベースやコードを参照して調査を行い、回答をしてくれるようなBotを作って対応コストを削減しようという試みです。 ただ、システム内部を理解していない人でも簡単にお問い合わせに対する正しい回答を得られるようなBotを作るのは難しいです。また、回答に対してそれが本当に正しいのかを判断できる人が使わないと、誤った回答をしてしまう恐れがあります。そのため、まずはシステムをよく理解しているエンジニアが、調査をSlack上で簡単に行えるようにするBotを開発することになりました。 挑戦前の不安 このプロジェクトを計画した段階で、すでに残りのインターン期間は3週間ほどで、開発に使える日数は10日前後しかありませんでした。さらに、自分はAI Agentの構築, GKE(Google Kubernetes Engine)へのデプロイ, Slack Botの開発などの経験がありませんでした。それによって、開発の全体像が見えず、10日前後で本当に終わらせることができるのか見積もりが困難でした。また、思わぬ困難なポイントが出現する可能性も考えられました。そこで、このプロジェクトに取り組むのではなく、先ほど紹介した新規機能のプロジェクトに取り組んだ方が確実なのではないか?とも思いました。 しかし、2日ほど調査した段階で実現できるだろうと判断したため、不安はありましたがGo Boldにこのプロジェクトに取り組むことに決めました。 やったこと AI Agentの構築 簡単にAI Agentが作れる Googleが開発しており、今後BigQueryが組み込みtoolとして導入される動きがあるなど、Google Cloudとの相性が良い という利点があり、 MCPサーバと連携できる Claudeなどさまざまなモデルを使うことができる という要件も満たしていたため、GoogleのADK(Agent Development Kit)を使うことにしました。 また、AI Agentがプロダクションデータが同期されているBigQueryとGitHubリポジトリを参照できるようにするために、それらのMCPサーバを使うことにしました。GitHubのMCPサーバは公式のものを使用しましたが、BigQueryには公式のものがなかったので社内で実装されているものを利用しました。 コンテキストは、Markdown形式で BigQueryのクエリの例 用語と、その情報がSettlementのリポジトリのどのファイルにあるのか リポジトリのディレクトリ構造と、それぞれのディレクトリにどのようなファイルがあるのか 回答に使用したクエリやファイルを添付するようにするなど、回答のフォーマットの指定 のような情報を与えました。 また、Claudeなどのモデルを使えるようにするために、社内に用意されているLiteLLMのプロキシサーバを使用しました。 LiteLLM とは、AnthropicのClaudeやGoogleのGeminiなど、さまざまなモデルをOpenAIのAPIのフォーマットで呼び出すことができるライブラリです。ADKでもLiteLLMがサポートされているため、これを使ってClaudeなどのモデルを使えるようにしました。 GKE上にデプロイ 開発したアプリからコンテナを作成するためのDockerfile, GKEにデプロイするためのKubernetesのマニフェストを作成しました。 ここでは、AI AgentのサーバとGitHubとBigQueryのMCPサーバ、MCPサーバを動かすのに必要なコマンドをコンテナに取り込み、AI Agentが標準入出力を使ってMCPサーバを呼び出せるようにしました。 Slack Botとの連携 次に、Slack BotからのリクエストをGKE上のサーバで受け取り、LLMからの返答をSlack Botにメッセージとして投稿させるようにします。 そのために、APIサーバに以下のようなミドルウェアを実装しました。 Slackからのリクエストであることを確かめるために、署名による検証を行う Slackからのリクエストを受け取り、メッセージ部分を取り出す ADKが本来受け取るはずだったbodyの形式にリクエストを変換し、処理を行う レスポンスからLLMからのメッセージ部分を取り出し、Slackのメッセージとして送信する また、インフラ部分も、IngressでHTTPS通信を受け付け、署名検証をすることでセキュアにSlackからのリクエストを受け付けるようにしました。 結果 Slack Botの導入に関する社内の審査を通さなければならないため、インターン期間中に導入まで持っていくことはできませんでしたが、代替としてWeb UIで動作確認をしたところ、お問い合わせに対して以下のように期待通りの調査を行ってもらうことができました。DBの中では数値で管理されているステータスを、きちんとソースコードを読んだ上でその意味まで説明してくれていたり、内部のプロンプトでソースを提示するように指示すると、きちんと回答に使用したSQLクエリやファイルを教えてくれています。 今後の課題 コンテキストとしてより多くのドメイン知識を与えることで、さらに多くのケースに対応できるようにしたいです。また、現在は内部のプロンプトをgitで管理しているため、より簡単に修正を行えるような仕組みにしたいです。 学んだこと 技術面 Kubernetes gRPC マイクロサービスアーキテクチャ Pub/Sub Spanner のような、今まで深く触れてこなかった技術を使えたのでとても勉強になりました。 特に、Kubernetesは、ずっと勉強したいと思いつつできていなかったので、これを機に本格的に勉強を開始できてよかったです。 また、Slack Botの開発を通して、今まで自分の中でブラックボックスになってしまっていた企業のインフラにしっかり触れられたのはすごく良い経験になりました。 経験面 技術選定や設計、実装の方針などを決める際に、調査〜意思決定まで自分に任せていただけたのは、すごく良い経験になりました。メルペイのインターンではタスクを任された後、基本的には自走して、わからないところがあれば質問をするような方針だったため、自分がタスクやプロジェクトを握っているんだという実感がありました。その中で、技術選定や設計におけるトレードオフなどを考慮して提案し、レビューをもらうようなプロセスを踏むことは1人前に近づくための成長につながりました。また、QAの調整やリリースの周知、他チームへの質問なども自分が行うよう求められ、本当にチームの一員として働くことができました。 タスク以外で取り組んだこと 英語 SettlementチームではGitHub上の会話は基本的に英語で、Slackのやり取りでも英語で話すことがあります。 そのように、インターンを通して英語を使う場面があったため、自分もSlackで英語で話しかけてみたり、オンライン英会話を始めてみたりなど、日常的に英語を使う機会を作る工夫をしてみました。インターン終了後も継続していきたいです。 1on1 キャリアについて考えたり視野や知見を広げる上で、人とたくさん話すのはとても重要だと考えています。そこで、一緒にランチに行った方や、インターンの一次面接をしてくださった方、そうやって話した方のお知り合いなど、いろいろな方に1on1を申し込んで、どのように技術のキャッチアップをしているのかや、なぜ今のキャリアを選んだのかなど、ざっくばらんにお話しました。自分にはなかった考え方や知らなかった知識を身につけることができ、今後の成長のヒントになりました。 突然DMで1on1を申し込んだにもかかわらず、みなさん快く受けて下さりとても感謝しています。 AIの活用 会社がAIの活用を推進しているということで、自分もCursorを使ってみました。特に既存のAPIを分割するタスクでは、基本的に元の実装に倣うため、ほぼ全ての実装をプロンプトを入力するだけで行うことができました。一方で、決済トランザクションのPub/Subハンドラの処理を改修するタスクでは、改修やテストをAgentが正しく実装することはできませんでした。ただ、テストの骨組みさえ作ってしまえば、適切なテストケースの追加はAgentに任せることができました。 また、自分もプライベートでもっとAIを活用できないか?と考えるようになりました。 技術のキャッチアップでNotebookLMを使うようになり、 今まで「名前は結構聞くけど、本腰入れて勉強するほどじゃないんだよな〜」という技術 ドキュメントがあまり好みではない技術 技術的な論文 などを効率的に学習できるようになりました。革命ですね。 また、活用だけでなく、AI関連の技術についても積極的にキャッチアップするようになりました。 社内のコードを漁る コードを読み進めることで、開発基盤やソフトウェアアーキテクチャなどについて理解を深めることができました。マイクロサービスなので、各サービスの全体像の把握がしやすく、学びやすかったです。インターンではせっかく実際に会社で働けるので、タスクに取り組むだけでなく、自分がこのインターンで学べることは何か?を積極的に考えていくと良いと思いました。 さいごに 以上が、私がインターンで行ったことやその感想でした。 メルペイでのインターンを通して、自分の中でできることや今後のキャリアに向けた視野がグッと広がったと感じています。 2ヶ月間ありがとうございました!!
こんにちは。メルペイ Payment & Customer Platform / Client EM の@anzaiです。 この記事は、Merpay & Mercoin Tech Openness Month 2025 10日目の記事です。 E2E テスト実装におけるDevin活用の現状について紹介します。 はじめに モバイルアプリケーション開発において、テスト自動化の重要性は言うまでもありません。特に、メルカリアプリのような大規模かつ複雑なプロダクトでは、品質保証の観点からリグレッションテストは欠かせないプロセスであり、その自動化は非常に重要な課題となっています。 本記事では、AIソフトウェアエンジニアである「Devin」を活用してE2E(End-to-End)テスト実装の課題に取り組んだ事例を紹介します。テスト自動化の効率化やAI技術の導入に関心のある開発者の方々の参考になれば幸いです。 メルカリのE2Eテスト実装における課題 メルカリのリグレッションテストとして実施しているE2Eテストは、お客さま体験を保証しつつ毎週行っているリリースを担保する上で欠かせない要素 ^1 ですが、その実装には以下のような課題があります。 コンテキストスイッチの負荷 : 通常の開発業務からE2Eテストの実装に切り替える際に、大きな負担が発生します。 実装・実行時間の長さ : E2Eテストを正確に実装するには時間がかかり、テストケースが増えるにつれて実行時間も長くなる傾向があります。 コードリーディングの困難性 : メルカリアプリのリグレッションテストとしてのE2Eテスト実装では、アプリの機能を横断的に理解する必要があります。メルペイには非常に多くのコードが存在するため、エンジニアは自分の担当ドメイン外のコードも読み解く必要がありました。これには多くの時間と、コードの実装意図や仕様の理解が困難な時に担当のチームに確認するコミュニケーションコストがかかります。 テストケース判断の複雑性 : テストケースで書かれている手順や期待値はどのように実装すれば満たしたといえるのか、といった判断には詳細なコード解析や事例の確認が必要になり、多くの時間がかかります。 これらの課題により、開発者の時間が圧迫され、本来注力すべき機能開発に遅れが生じる可能性があります。 AIソフトウェアエンジニア「Devin」によるアプローチ このような背景から、私たちはDevinに注目しました。 Devinとは何か Devinは、完全自律型AIソフトウェアエンジニアです。従来のコード補完ツール(GitHub Copilotなど)とは大きく異なり、自然言語による指示だけで、ソフトウェアの設計からコーディング、デバッグ、デプロイまで、開発プロセス全体を自律的に実行できます。 なぜDevinがE2Eテスト実装に適していると考えたのか 今回のE2Eテスト実装において、Devinが特に有効だと考えた理由は以下の通りです: 1. パターン化された実装環境 メルカリアプリのE2Eテストは「ページオブジェクトモデル ^2 」で実装されており、ある程度決まった形で書けるようになっています。また、すでに十分な量の参考テストケースが存在していたため、Devinが学習しやすい環境が整っていました。 2. コード理解の負担軽減 前述のコードリーディングの困難性でお話しした通り、リグレッションテストとしてのE2Eテストの場合、様々なドメインを横断的に理解する必要があります。 しかし、DevinならACU(計算リソース)が許す限り、どこまでも深くコードを読み続けることも可能です。人間のように疲れることがないのは大きなメリットでした。 eKYC画面におけるE2Eテスト実装事例 具体的な検証として、メルペイのeKYC(オンライン本人確認)機能のリグレッションテストのE2Eテスト化のタスクをDevinに任せてみました。 実施方法 メルカリのテストケースはTestRailで管理されています。そのため、以下のフローでDevinにタスクを渡すフローを構築しました。 ① Cursor上からREST APIでTestRailのテストケースを取得 ② 取得したテストケースから実装可能なようにタスクを詳細化 TestRail上の情報は人間が直接操作して実行する前提で記載されていますが、Devinが処理可能なフォーマットに変換する必要があり、それにはアカウントのコード上における作成方法や操作手順が重要になります。 ③ 公式のAtlassian MCP Server経由でJIRAチケットを作成 ④ そのJIRAチケットをDevin が読み、実装作業を開始 TestRailからJIRA、Devinへのデータフロー構成 ロゴ出典: https://lobehub.com/ja/icons/cursor https://www.testrail.com/ https://atlassian.design/foundations/logos https://deepwiki.com/ このフローにした理由は以下の通りです: セッション管理の最適化 : 一つのセッションが大きくなりすぎるとDevinの能力が低下してしまうため、テストケース単位で作業を分割することで品質を保つ 実装タイミングの制御 : 開発チームのスケジュールに合わせて、実装開始のタイミングをコントロールできる 継続的な改善 : 試験的な取り組みだったため、Cursor上で作成したJIRAチケットのフォーマットや内容をみながら逐次JIRAチケット生成プロンプトを改善したい Devinにテスト実装を任せる際、特に意識した情報は以下の2つです: 前提条件の詳細化 メルペイではさまざまなアカウント状態(年齢、本人確認ステータス、銀行接続状況など)が存在します。そのため、各テストに必要なテストアカウントの作成方法やセットアップ手順について詳しく記載しました。 例) eKYC未実施の状態 -> XXX() という関数を呼び出してアカウントを生成してください eKYC実施済みかつ銀行口座接続済みの状態 -> YYY() という関数を呼び出してアカウントを生成してください 完了条件(assertion)のパターン化 テストの操作内容は多様でしたが、完了条件については一定のパターンがありました。そこで以下のような内容を事前に詳しく記載しました: 「画面遷移が成功したかどうかの判定方法」 「エラーメッセージが正しく表示されているかの確認方法」 「各UI要素が期待通りに表示されているかの検証方法」 これらの情報はTestRailsに記載されているテストケースには記載がないため、Cursor上でJIRAチケットに情報を追加するプロンプトを設定しています。 JIRA チケット作成プロンプト一部抜粋 結果 DevinはJIRAチケットの内容に対応するテストコードを含むPull Requestを自動生成しました。生成されたコードを人間がレビューしたところ、以下のような品質でした: コード品質 : 参考にできる水準で、実装内容も妥当 テストロジック : 期待していた通りの流れで実装されていた ページオブジェクトパターンの適用 : 既存のコードスタイルに合致した実装 特に印象的だったのは、複雑な前提条件についてもしっかりと理解し、適切なテストアカウントのセットアップコードを生成できていた点です。現状はシンプルなテストケースの依頼のみですが、試したほとんどのテストケースについて人間の修正なく実装ができました。 さらに、GitHub上で行われたレビューコメントに対して、Devinが自動でコードを修正するといった挙動も確認できました。これは、開発プロセスにおけるコミュニケーションコスト削減への寄与も期待させるものです。 Devinがcommentを確認し、リアクションをつけている Devin活用の評価:メリットと今後の課題 今回の検証を通じて、Devinを活用するメリットをまとめました。 メリット コンテキストスイッチの軽減 : E2Eテスト実装にかかる細切れの作業時間を大幅に削減できました。例えば、「業務開始時にDevinに実装タスクを指示し、夕方にレビュー、翌日に結果確認と再レビュー」といった効率的なワークフローを作ることができます。これにより、開発者は他の重要なタスクに集中しやすくなります。 反復的な作業の委譲 : Devinは、人間なら多大な集中力が必要なコードの追跡や全体像の把握といったタスクを、粘り強く実行してくれます(ACUというリソース制限は存在します)。このような作業をDevinに任せることで、開発者はより創造的な問題解決に時間を使うことができます。 今後の展望 今回は開発者が手元のCursorでタスク生成を行いながらフローを構築しましたが、今後はより完全な自動化を目指しています。 具体的には、以下のような仕組みを検討しています: TestRailのMCPサーバー構築 : TestRailの変更を監視して自動的にJIRAチケットを生成 JIRAチケット監視機能 : チケットの更新を検知して、自動的にDevinのセッションを開始 完全な無人運用 : 人間の介入なしに、TestRailの更新からテストコード実装まで自動実行 これにより、テストケースの追加や変更があった際に、開発者が意識することなく自動的にE2Eテストが実装される環境を実現したいと考えています。 まとめ Devinを使ったE2Eテスト実装の取り組みは、開発効率向上の可能性を示してくれました。特に、コンテキストスイッチの削減や、定型的で負荷の高い作業の自動化は、開発者の生産性向上に貢献する重要なポイントです。 AI技術はまだ発展途上で、Devinも例外ではありません。しかし、その可能性は非常に大きく、今後の技術進歩に期待が持てます。 本記事が、開発現場におけるE2Eテストの課題解決や、AI技術活用の検討に役立てば幸いです。Devinのような新しい技術を効果的に取り入れ、より良い開発プロセスの実現を目指していきたいと思います。 次の記事は @Tさんの「SRE2.0: LLMサービスの信頼性を測る新しい評価指標の紹介」です。引き続きお楽しみください。 参考資料
こんにちは。メルカリモバイル フロントエンドエンジニアのtoshickです。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の9日目の記事です。 この記事は以下の二部構成となっています。 (その1)GAS(Google Apps Script)に助けられたMVNO開発の話 (その2)Jiraのissue作成のためのGAS (その1)GAS(Google Apps Script)に助けられたMVNO開発の話 わたくしtoshickは2025年 今年リリースされたメルカリモバイル というプロジェクトのフロントエンド担当として開発をしています。 メルカリモバイルとはメルカリがMVNO事業者としてモバイル通信事業に参画したものです。 当然MNP(Mobile Number Portability)に対応しているため、お客さまが他のモバイル事業者から転入してきたり、逆にメルカリモバイルから他事業者へ転出されたりするようなケースにも対応しておく必要があります。 これらをポートイン(転入)、ポートアウト(転出)と呼びます。 他事業者との連携なのであらかじめやりとりのためAPIを決めておき、エンドユーザーが任意のタイミングでポートインやポートアウトを実行できるようにしておきます。 MNPワンストップ方式とMNPツーストップ方式 MNPの手続きの大まかな流れは以下です。 お客さまは今自分が契約している通信事業者に対してMNP予約番号の発行を要請しこれを取得する。 お客さまはその予約番号の有効期限内に乗り換え先となる通信事業者にて予約番号を入力して新規契約を行う。 このようにお客さまが自ら予約番号を取得して乗り換え先にもっていくMNPの方法をMNPツーストップ方式と呼びます。 一方で最近はその予約番号発行手続きをAPIから行うようにした、手順が簡略化されたフローが主流となっています。 これがMNPワンストップ方式と呼ばれる方法です。 お客さまからすると特にデメリットはないので、もし利用できる場合には多くのお客さまはこちらのMNPワンストップ方式で事業者変更の手続きを行っているはずです。 MNPワンストップ方式での動作テスト メルカリモバイルもこのMNPワンストップ方式に対応している事業者となります。 ここで問題になるのがワンストップ方式時のテストになります。 ワンストップ方式はメルカリ内部だけで完結するものではないので、ワンストップ時のAPIの受け口となる本番と同様のふるまいをする別MVNO事業者のようなものが必要になります。 これを動作検証仮事業者と呼ぶことにします。(以降 仮事業者) メルカリと仮事業者のAPIでのやりとりを模したテストのためのダミーの事業者としてふるまいます。 開発もかなりすすんできていた段階で、仮事業者も静的なページを用意すればなんとかなると考えていたのですがよく考えると問題がありました。 静的なWebページだとpostのリクエストを処理できないのです。 その事実に気づいた時にやっと「仮事業者はWebサーバ上で起動されたWebアプリケーションである必要がある」ということに気づきました。 動作検証仮事業者の開発 社内でカジュアルにWebアプリケーションを立ち上げて任意のjsを実行できるような環境をスピーディに用意する必要にかられていろいろ相談しましたが、今から新しくマイクロサービスを作成するのは時間的に不可能だということがわかりました。 素早く、新規マイクロサービス作成とかいう高コストでもなく、社内QAからアクセス可能で、postリクエストを処理可能な、社内向けのWebアプリケーションを作成する方法を見つける必要があります。 この問題をチームに相談したところ 「GASでできるかもしれない」 というアドバイスをもらいました。 自分はいままでほとんどGASに触ってこなかったのでそのアイデアは目からウロコです。 早速サンプルのGASを作成してpostのリクエストを送信したところちゃんとリクエストパラメータを取得して画面に出力することができました。 さらにjs実行によりform post(同期post)の実行もできることがわかったのです。 これなら仮事業者としてメルカリモバイルのAPIにむけて特定のパラメータ付きでpostリクエストをコールしたり、メルカリモバイル側からpostリクエストをうけとってから任意のタイミングでコールバックをコールしたりするような本番の外部MVNO事業者と同じふるまいをするアプリケーションが用意できます。 GASによる仮事業者の作成 本格的にGASでアプリケーションを作成してみたところいろいろ課題や混乱が発生しました。 まず最初はコード管理の問題でした。 通常の開発はGitを利用した差分管理およびGitHubへのpush、review、mergeといった手順をふむのが普通ですが、GASの場合はなんとブラウザ上でコードを修正し、ブラウザ上でデプロイを行うものでした。 これだとGit開発でうけている恩恵がうけられません。コードの差分のreveiewも不可能です。 claspの導入 みなさんがどうやってGASを開発しているのか調べたところ、claspというcliツールがgoogleから提供されていることがわかりました。 https://github.com/google/clasp?tab=readme-ov-file claspはGAS開発のための便利なコマンドを提供しており、ローカルから自分のGASアプリへコードをpushすることが可能になります。 これでコードをGit上で管理しつつ、GASへの反映もブラウザ上のファイル編集ではなくローカルのファイルのpushにより可能になるのでより安全な開発になります。 GASの癖 GASのWebアプリケーション作成は少し違和感を覚えるかもしれません。 まず、ランタイム時の依存ファイルのインポートが不可能です。 通常のJavaScript(js)を利用したWebアプリケーションはimport文(もしくはrequire文)を利用して依存するファイルをロードするのが普通ですが、GASだとそれができません。 アプリのアクセス時のSSR処理時にコードをinjectするしか方法がないということです。 PHPのようなやり方ですね。 // これでjsの書かれたhtmlをinjectできる <script> <?!= includeFile('jsUtil') ?> </script> もし関数を定義してそれをコールしたい場合、それはグローバルに定義してあらかじめhtmlのヘッダで読み込んでおく必要があります。 次に、GAS上で取り扱うファイルは拡張子がhtmlである必要があります。 上記のコード includeFile(‘jsUtil’) は jsUtil.html をここに読み込んでくださいという命令になっています。 htmlとなっていはいますが、中身はjsです。 GASが「htmlしか動的に読み込ませない」というルールにしているためにこのようなヘンテコなことになっているようです。 開発側は定義した関数をjsとしてファイルに書き込み、デプロイ時はその拡張子をhtmlとしてpushする。 さらに実行html内には includeFile関数をつかってヘッダからその関数をグローバルに読み込む。 といった工夫が必要になります。 別に直接実行htmlに関数を書いてもよいですが、複数htmlに同じ関数は書きたくないでしょう。 GASアプリからURLのクエリに含まれた値やpostでリクエストされた値を取得する場合built-inで用意された変数があるので、以下のような関数をhtmlに定義すればjs側でランタイム時に利用できるようになります。 <script> <!-- postパラメータをinject --> function getHtmlPostParams() { return { <? for (let key in postParams) { ?> <?= key ?>: <?= postParams[key] ?>, <? } ?> }; } </script> <!-- クエリパラメータをinject --> <script> function getHtmlGetParams() { return { <? for (let key in getParams) { ?> <?= key ?>: <?= getParams[key] ?>, <? } ?> }; } </script> これ以外にも新規デプロイするたびにURLが変わるけど、URLが変わらないデプロイ手順があったりと癖の多いツールですが理解さえしてしまえばなんとかなりました。 動作検証仮事業者の画面サンプル 仮事業者ポートアウト 仮事業者ポートイン 仮事業者ポートイン完了 ポートインとポートアウトという言葉はわかりやすいようでわかりづらいです。 なぜなら一方でのポートインは、同時に他方からするとポートアウトだからです。 ここは混乱しないように仮事業者のタイトルにはわかりやすく主語を明記しておきました。 結論その1 無事メルカリモバイルの動作チェックのための仮事業者のアプリケーションを立ち上げることができました。 仮とはいってもQAのためのアプリなので、メルカリモバイルの堅牢性確認のため様々なパターンのリクエストを発生させる機能も入っています。 このように、claspとGASを利用して手軽にpostリクエストを処理できるWebアプリケーションを社内に用意することができました。 post処理可能なWebアプリケーションを簡単に作成できるツールがこんなに身近にあるとは発想としてありませんでした。 みなさんもテストなどの際に社内におけるpostやgetを処理するための受け皿となるWebサービスが必要になるときが来るかもしれません。 そのとき、これほど簡単に自前サーバーも必要なく立ち上がってくれるこのツールを思い出すと役に立つかもしれません。 (その2)Jiraのissue作成のためのGAS もうひとつGASで効率をあげた話をします。 メルカリは開発のサポートとしてAIを積極的に導入しています。 その一環でMCPサーバ経由からのJira issueの作成を試してみました。 MCPサーバとはAI経由でサードパーティのツール(Jiraやfigma等いろいろ)を操作するための仕組みです。 今まではAIに操作のやり方をおしえてもらって自分でツールを操作していたと思いますが、これを使うとAIが直接ツールを操作することができるようになります。つまり、Jiraのissue作成もやってもらえます。 CursorというAIツール上のプロンプトから日本語で、issue作成を依頼してみました。 XXXプロジェクトに以下のJira issueを作成してください タイトル: MCP経由からのチケットさくせい (AIからの回答) Jiraチケットが正常に作成されました! チケット番号: XXX-0000 タイプ: タスク ステータス: To Do URL: https://****** なんと簡単にissueが作成されてしまいました。 しかしこれだけではわたくしは満足せず、どうせならGoogleスプレッドシートに属性をすべて定義しておいてそれからissueを作成してほしいなと考えました。 // シートのイメージ サマリー | issueのリンク | Epic | 担当 | ラベル | スプリント | ストーリーポイント rowごとにissueの属性を定義し、isseの生成が完了するとリンクがシートに追加されるというものです。 前からJiraのissue作成後の手動属性追加は面倒だと考えていたため、属性をシートでまとめて管理できるのはよいアイデアだと考えました。 Jiraのissue管理は便利な反面、指定すべき項目が多いです。 手動で付与していると設定忘れが発生したりします。 タイトル 説明 担当 複数ラベル(Frontendラベルおよびチームのラベル等) スプリントへの追加 ストーリーポイントの追加 親Epicの追加 リリースバージョンの追加 ローカルnodeアプリケーションからのJira issue作成 最初は対象のスプレッドシートを読み込んでJiraのAPIをコールするようなローカルのnodeアプリケーションを考えたので、Cursorに聞きながらnodeのexpressアプリケーションを作成しました。 expressである必要もなかったかもしれないですが、即座にコードが出力されて割と簡単にアプリを作成してJira issueが追加できるようになりました。 ほぼ想定どおりの動きをするアプリケーションができあがったあと、いちいちローカルのWebアプリを起動するのも面倒だなと考えてしまいました。 GASによるスプレッドシートからのJira issue作成 ということで、結局はこのローカルコードをクラウド上で実行すれば一番楽だということに気づいたのでCursorに依頼をしました。 このnodeアプリケーションをGASに持っていって、GAS上のボタンを押すとJiraのissueが追加されるようにしたいです (AIからの回答) GAS (Google Apps Script) への移行についてアドバイスさせていただきます。 現在のNode.jsアプリケーションをGASに移行するために、主な変更点と実装方法を説明します。 まず、コードベースを確認させていただきます。 ... 現在のNode.jsアプリケーションをGAS(Google Apps Script)に移行するために、以下のような変更を提案します: 1. GASプロジェクトの作成 2. スプレッドシートとの連携 3. Jira APIとの連携 4. UIの実装(ボタンの追加) ... 素晴らしいです。ものすごくわかりやすく段階的な手順を明示してから具体的なコードを提示してもらえました。 これにより無事スプレッドシート上で動くGASアプリを使って簡単にJira issueを作成することができるようになりました。 いままでどちらかというとJiraのissue作成は面倒に感じていたのですが、せっかく自分でツールを作成したとなるとそうは言っていられません。 シートのタブごとに実行できるので文脈をかえてどんどんissueを作成していけます。 リリースバージョンごと、アサイニーごと、エピックごとというようにさまざまな文脈でシートを作成してissueを作成していけます。 もちろん属性のつけ忘れは存在しません。 ボタンを押した後にissue作成が成功するとIssueのカラムに自動的にリンクが付与されます。 すでにリンクが存在している行は無視されて作成からは除外されるようになっています。 ラベルも任意の数紐づけが可能です。 自分で作成したJira issueのヒストリーも見られるのがよいなと感じています。 結論その2 このように、GASを利用して効率をあげることができた事例を紹介しました。 他にも気づいていないだけでさまざまな業務を要所要所で自動化できるかもしれません。 ひらめいたアイデアをツールに落とし込むためのAIの存在も見逃せないところです。 いままでアイデアはあったけれどもどうやってツールにすればよいかわからなくてあきらめていたという人は、ぜひともAIに問いかけてアイデアを実現してみることをおすすめします。 以上でわたくしからの話は終わりです。 読んでいただきありがとうございました。 明日の記事は @anzaiさんの「Davin にE2Eテストの実装を任せる」と @Soma Nakaoさんの「インターンレポート」です。引き続きお楽しみください。
目次 はじめに 背景 広告審査における課題 AI審査の概要とシステム構成概要 プロンプトの検証 まとめ はじめに こんにちは。メルカリAdsのバックエンドエンジニア、chapaと申します。 今回は、メルカリAdsがOpenAIを活用して実現した広告審査プロセスについて解説します。 本記事を通じて、AI活用による課題解決について具体的にお伝えできればと思います。 背景 メルカリAdsでは多くの広告主様にご利用いただいており、さまざまな広告素材が日々入稿されています。その中には、残念ながら不適切な表現を含むものもあり、それらがメルカリのお客さまの目に触れることは、お客さま体験を損ねるリスクとなります。 これまではサンプリングした広告素材を人の目で確認していましたが、広告主様の増加に伴い、手動での対応に限界が見えてきました。そこで、審査の自動化に取り組む必要性が生じました。 広告審査における課題 広告事業をおこなうにあたって、入稿されている広告素材全てに対して審査を行う必要があります。 ただ入稿される広告素材は非常に多く、手動審査での対応では難しい状況です。 そのため、自動判定を取り入れることで基準を満たすかを効率的にチェックして、手動審査するべき数を減らし、運用の効率化を進めています。その手法の一つがAI審査です。 AI審査の概要とシステム構成概要 AI審査では、以下の機能を実現しています。 広告画像やタイトルテキストなどに、不適切な表現が含まれていないかをチェック 禁止表現が検出された場合は、広告配信を即座に停止 システム構成については以下のようになっています。 広告主様が更新するすべての広告をリアルタイムでOpenAIへ送信するとコストが非常に大きいため、以下の工夫を採用しました。 リアルタイムチェックは行わず定期的にBatchAPIを用いてOpenAIへ送信 ライトなモデル(gpt-4o-mini)を利用することでOpenAIへのコストを削減 OpenAIのオプション設定も以下を採用 結果のブレをなくすため、 Temperature は 0.01 に設定 結果はJSON形式にするため ResponseFormat は json_object に設定 プロンプトの検証 審査を実施するためのプロンプトを作成し、さまざまなパターンを用意して検証しました。 Playgroundでプロンプトと質問を送信した際には、期待通りに結果が返ってきましたが、BatchAPIを使用した際にいくつかの課題がありました。 システム的な課題と要件的な課題をいくつかサンプルとともにご紹介します。 ① 期待しているJSON形式で返却されない 返却値はOpenAIのオプション設定にてJSON形式と定義し、プロンプトに例を記載しました。しかし、以下のようなケースが見られました。 JSON形式として正しいものの、期待している統一フォーマットではない場合がある JSON形式そのものが正しくない場合がある 期待される返却値の一例: // 期待される返却値の一例: { "id": "test-id-00001", "result": <審査結果値>, "reasons": [{"type": 1, "value": "審査理由詳細・・・"}] // 審査理由があれば配列で設定される } // 実際の返却値の例: { "id": "test-id-00001", "result": <審査結果値>, "reasons": [""] // 不正な形式(配列が空欄の際にダブルクォートのみが入っている) } このケースの場合はプロンプトに出力形式を厳守するようプロンプトに記載し解決しました。 これによりほぼ不正な形式で返却されることは解消されました。 // 省略 ## 出力形式 **必ず以下の出力フォーマットを厳守** { "id": <id>, "result": <int>, "reasons": [{type: <int>, value: <string>}] } ② 期待しているID値が返却されない 質問で送信したIDをキーとして返却されることを期待していましたが、以下のようなケースが見られました。 // 送信値の例: { "id": "test-id-00001", "text": "test-text" } // 期待される返却値の一例: { "id": "test-id-00001" "result": "<審査結果値>", "reason": [] } // 実際の返却値の例: { "id": "test-id-00", "result": "<審査結果値>", "reason": [] } このケースはBatchAPIへリクエストを送信する際にユニークなID( custom_id )を設定することができるので、返却後もそのIDを正として利用することで解決しています。 参照: https://platform.openai.com/docs/api-reference/batch/request-input ③ 性的な表現と下着広告の判別 広告素材の中には性的表現を含む画像と下着広告が混在しています。これにより、下着広告が誤って性的表現と判定される問題がありました。 入稿されたデータから無作為にデータを取得して、期待値を設定し正答率を取りながらプロンプトの改善を行いました。 初期プロンプト 正答率: 70.76% 「性的表現」と「下着広告」を区別する定義が曖昧 改善プロンプト 改善後の正答率は 77.33% に向上しました。これは下着広告に関するプロンプトの条件追加が要因と考えられます。 しかし、「性的表現」と「下着広告」の詳細定義が曖昧なままでした。 最終プロンプト 「性的表現」と「下着広告」の詳細に定義することで不明瞭な基準に基づく誤判定が大幅に減少し、正答率が 13.37ポイント 向上し 90.67% となりました。 例としては、以下のような記載を追加しました。 性的表現: 過度に性的な印象を与える画像の構図・演出等を明確定義 下着広告: 通常の下着製品画像は適切なファッション広告として区別 こうしたプロンプトの改善にもAIを活用しており、OpenAIにプロンプト、質問内容、返却された結果を伝えて、改善提案してもらい結果の正答率を上げることができました。 プロンプトの書き方の改善が最も大事だと改めて認識しました。 とうとう有識者とではなくとも、AIと壁打ちできるようになってしまったと感じました。 まとめ この記事では、メルカリAdsにおけるOpenAIを活用した取り組みを紹介しました。 特別な工夫を施さなくても、基本的なAI活用によって大量のデータを効率的に処理できる可能性をお伝えすることができたと思います。 今後も引き続き改善を行い、より安心で安全なお客さま体験を提供できるよう努めていきたいと考えています。
はじめに こんにちは。メルペイVPoEの@jorakuです。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 8日目の記事です。 AI Agent / AI Code AssistなどのAI Toolsが日々リリースされており、めまぐるしい時代を楽しく過ごしています。ここで記載されているものも1ヶ月後には古くなる可能性もありますが、現時点での情報を残しておきます。 目次 AI 2027 シナリオ AI Code Assist / AI Agentの登場 One Person, One Release 役割の再定義 AI Nativeの夜明けに求められるもの AI 2027 シナリオ 一時期話題になった非営利団体 AI Futures Projectが作成した レポート です。元Open AIの方などが著者です。 レポート内容は衝撃的でシンギュラリティについても触れられています。賛否両方のコメントもあり、正確性については敢えてここでは言及しません。というか私には分かりません。 AI Code Assist / AI Agentの登場 ただ、現実的に今、ソフトウェア開発に大きな変化が生まれています。 ソフトウェア開発の風景は、AIコードアシスタントやAIエージェントの登場により、根本的な変革の岐路に立っています。これらの技術は、単に「何を作れるか」だけでなく、「どのように作るか」を根本から変えつつあります 。マイクロソフト社のKevin Scott氏が「我々の生涯で起こった最も重要な技術プラットフォームのシフトかもしれない」と 述べる ように、この変化の大きさは計り知れません 。AIは単なる技術ではなくプラットフォーム、ソフトウェア開発ライフサイクル(SDLC)における協調的なパートナー、場合によっては自律的なエージェントへと進化しています 。 20年以上前の話になりますが、Public Cloudという概念を作ったAWSの登場も衝撃的でした。 AWS/Azure/Google Cloudの登場により、私たちはより安全に、より安定した、拡張性の高いプロダクトを顧客にいち早く提供できるようになりました。ただ、今回のAI Code Assist / AI Agentは、コード生成だけでなく、テスト、デプロイ、保守に至るまでソフトウェア開発ライフサイクル(SDLC)全体に影響を与えており、その進化のスピードは目を見張るものがあります。 One Person, One Release これまでの技術的な専門性や、業界の知識などの専門性を活かしながら価値貢献してきている時代でした。ただ、これからはそれが両極端になっていくと考えます。より専門性の深い分野に特化していくのか、それとも、幅広い領域で素早く価値提供するのか。 これまでのAI Toolsの登場でプロンプトだけでアプリケーションが作れる時代になりました。プロンプトベースでアプリケーションを素早くプロトタイピングし、PoCレベルまで到達できる時代になっています。非エンジニア職種の方でも、一定のアウトプットが可能な環境が整ってきました。逆にエンジニアだとしても良い体験設計できるデザインをできるようになりました。 誰もがプロダクトを作れる時代に入り、“プロダクトをつくる会社”と“プロダクトを利用する会社”の境界が曖昧になりつつあります。もはや「プロダクトカンパニー」というのは古い時代の言葉になるのかもしれません。 そこで期待されるのは、起業したばかりのスタートアップの様に、一人の人がより多くの役割をスピード感もって価値提供していくことになると考えます。 次年度の Engineering Roadmap を思案している所ですが、Visionとして「One Person, One Release」を入れていきたいと考えています。 PMもEngineerも壁を越えていきます。一人の人が企画から開発、QA、リリースまで一気通貫で出来ることを目指します。 技術の壁を越え、ドメイン知識を越え、役割を越えて行くためのAIの活用とし、それらを使い熟すのです。 役割の再定義 それでは今後それぞれの役割はどのように変化するのでしょうか。より多くの方がこれまでの役割を越えていけると考えます。越えて行かねばならない。とも言えるかもしれません。 専門性がより深化していく流れは今の専門性と変わらない点があるかもしれませんが、より影響範囲を広げてお客さまへの価値提供が出来る役割です。仮にここではAI Agent Orchestration Engineerと名付けます。( 参考記事 ) もちろん、AIを駆使することで各職種がコードに触れることも可能になりますが、求められる責任や精度はそれぞれの職能に応じて変わります。 役割 責任 AI Expert AIの専門家としてAI/LLM製品やツールの開発 System / Domain Architect AI/LLMでは補えない技術的難易度の高いものやPlatformなどの基盤を整備する役割。また、法令要件や求めるべき倫理など業界の専門性が高い要件を構築・監査する役割 Agent Developer 自社の業務や運用に合わせてデータの整備やMCPの構築、業務に合わせた自動化/省人化するAgentの開発。また、Agent to Agentのような基盤の開発も担う AI Agent Orchestration Engineer AIエージェント同士を組み合わせ、お客さまの課題に対して機能や体験を統合的に設計・実装・提供する役割。One Person, One Release を実現する役割 AI Nativeの夜明けに求められるもの 新しい時代の夜明けです。地球が回る限り後戻りすることはありません。 最後に新しい時代に応じて何が求められてくるのか、あくまで個人的な意見ですが、夜が明けた今、個人・組織とって大切にしていきたいマインド・スキルセットを記載します。 組織 セキュリティと利便性の両立 AIは利便性高く、生産性にも大きく寄与しますが、自社の大事な情報の流出や新たなハッキングのリスク、また倫理的な観点のリスクも発生します。守るべき点ももちろん大事ですが、攻めと守り両方を求めていく姿勢が大事だと考えます。 情報管理/情報戦略 これまでの情報資産や、情報化されていない経験知見をどのように蓄積していくのか、組織においてはこれまで以上に重要な要素になります。 AI活用の定量評価 自社でどのようにAIが使われているのか、それを定量的に観測し、評価にも組み込んでいく事が必要になってきます。もちろんお客さまにどのような価値を提供できたのかとても大事な部分ですが、エンジニア含めたプロダクト開発する組織の一つの指標としては追うべき数値と捉えています。 個人 言語化能力 AIに理解させるためには、正しく言葉で伝えていく必要がありますし、それらを学習してもらう必要があります。何がしたいのか、どうしたいのか、誰にでも分かるように論理的に記載して良く必要があります。 メルカリ入社後に驚いた事の一つとしてドキュメント文化があります。今後の時代にはこのドキュメントが功を奏するように持って行きたいと考えています。 好奇心 個人のマインドセットとしてとても大切だと考えるのが好奇心です。wakuwakuする心ですね。好奇心を持つのではなく、それを自ら作り出せる事がとても大事です。時代の変化は激しいですし、自分の領域を一部奪われてしまうのではないかという猜疑心も生まれます。止まっていては何も始まらないので進むしかありません。前に進むための動力としての好奇心を持つことが大切です。 もし、まだAIを活用しきれていないという課題感を感じている方、大丈夫です。今はAI Agent / AI Code Assistの勃興時代です。日々変わりますし、1ヶ月前の情報や経験がもはや古くなる時代です。 ですから、今までのアドバンデージは無く、今から始めても直ぐに先頭を走ることが出来ます。みなさま「好奇心」をもってこの変化を楽しんでいきましょう! 明日の記事は @toshickさんによる「GASで効率化!MVNOの動作検証仮事業者&Jira issue作成 with AI」です。引き続きお楽しみください。
こんにちは。メルカリモバイル Tech Leadの @_seitau です。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の7日目の記事です。 今回は、CursorをはじめとするAIコーディング支援ツールに、社内コードの検索能力を持たせるための取り組みをご紹介します。 はじめに メルカリでは、社内向けのマイクロサービス開発フレームワークや、ScenarigoというE2Eシナリオテストツールなど、独自の技術基盤が整備されており、これらが開発速度を大きく向上させています。 一方で、社内に類似した実装が豊富にあるにもかかわらず、Cursorがそれを認識できず、実装内部を自律的に把握できないという新たな課題も生まれていました。 社内基盤の充実がCursorの制約になっていた背景 メルカリでは、メルカリモバイルチームを含む複数のチームで共通のマイクロサービス開発フレームワークが使われており、構成や実装パターンが似ているため、他チームの実装を参考にすることは日常的です。ScenarigoによるE2Eテストのシナリオ記述も同様に、既存のパターンを参照する場面が多くあります。 しかし、これらの実装は複数のプライベートリポジトリに分散しており、通常のGitHub検索では、求めるコードを効率的に見つけることが難しい状況でした。単一リポジトリに閉じた実装や、広く知られた著名なライブラリを使用した実装であれば、Cursorは十分にその能力を発揮できていましたが、社内独自のライブラリやツールを参照している箇所では、Cursorが期待通りに機能していない状態でした。 解決策:Sourcegraph MCP Server 既存のGitHubのMCP Serverも存在しますが、GitHubのAPIは社内のコードを横断的に検索するには限界がありました。特にメルカリのように複数のマイクロサービスが多層的に構成され、リポジトリが分割管理されている場合、単一リポジトリ検索に依存するGitHub APIでは、Cursorにとって十分な参照性を持たせることが困難です。 そこで、Sourcegraphのクロスリポジトリ検索機能と、構造化された検索性を活かす形で、SourcegraphのMCP Serverを社内向けに実装しました。 このMCP Serverにより、Cursorにプロンプトの中でSourcegraphを使用するように指示することで、必要な社内コードを自発的に検索し、その結果を回答やコード生成に反映することが可能になります。 自作Sourcegraph MCP Serverのインターフェース 今回私が開発したSourcegraph MCP Serverは、Cursorが社内コードにアクセスするための非常にシンプルなAPIインターフェースを提供しています。主に以下の2つのメソッドで構成されています。 1. コード検索ツール ( mcp_sourcegraph_search_code ) このツールはSourcegraphの強力な検索機能を利用し、メルカリの全社内リポジトリを横断してコードを検索できます。 パラメータ q (required): Sourcegraphのクエリ構文に従った検索クエリテキスト リクエストサンプル 基本的な検索: { "q": "PubSubLogWithFieldsInterceptor" } 言語を指定した検索: { "q": "import React language:typescript" } 関数名での検索: { "q": "func main language:go" } ファイル名を含む検索: { "q": "handleUserLogin file:*.go" } レスポンスサンプル { "results": [ { "repository": "github.com/mercari/service-a-repo", "file": "internal/interceptor/logging.go", "lineNumber": 45, "content": "func PubSubLogWithFieldsInterceptor() {...}", "url": "https://sourcegraph.com/github.com/mercari/service-a-repo/-/blob/internal/interceptor/logging.go#L45" }, { "repository": "github.com/mercari/shared-lib", "file": "pkg/logging/interceptor.go", "lineNumber": 23, "content": "type PubSubLogWithFieldsInterceptor struct {...}", "url": "https://sourcegraph.com/github.com/mercari/shared-lib/-/blob/pkg/logging/interceptor.go#L23" } ], "totalCount": 12, "hasNextPage": true } 2. ファイル内容取得ツール ( mcp_sourcegraph_get_file_content ) このツールは、指定したリポジトリの特定のファイル内容を直接取得します。 パラメータ repository (required): github.com/を含む完全なリポジトリパス filePath (required): リポジトリ内のファイルパス commitID (optional): 特定のコミットIDまたはブランチ名 リクエストサンプル 基本的なファイル取得: { "repository": "github.com/mercari/service-a-repo", "filePath": "src/main.go" } 特定ブランチのファイル取得: { "repository": "github.com/mercari/service-a-repo", "filePath": "config/app.yaml", "commitID": "develop" } 特定コミットのファイル取得: { "repository": "github.com/mercari/service-a-repo", "filePath": "README.md", "commitID": "a1b2c3d4e5f6789" } 深いパスのファイル取得: { "repository": "github.com/mercari/service-a-repo", "filePath": "internal/services/user/handler.go" } レスポンスサンプル { "content": "package main\n\nimport (\n \"fmt\"\n \"log\"\n \"os\"\n)\n\nfunc main() {\n if len(os.Args) < 2 {\n log.Fatal(\"Usage: program <arg>\")\n }\n \n fmt.Printf(\"Hello, %s!\\n\", os.Args[1])\n}", "repository": "github.com/mercari/service-a-repo", "filePath": "src/main.go", "commitID": "main", } 主要メリット これらのツールが提供する主なメリットは以下の通りです。 1. リポジトリの横断検索 メルカリ組織の全リポジトリを検索対象にできるため、広範囲なコード探索が可能です。 共有ライブラリの実装詳細を効率的に見つけるのに役立ちます。 2. 高度なクエリ構文 Sourcegraphの強力なクエリ構文をサポートしており、柔軟な検索が可能です。 言語、ファイル名、パスなどで検索を絞り込むことができます。 3. 直接的なファイルアクセス リポジトリをクローンすることなく、ファイル内容を直接取得できます。 特定のコミットやブランチのファイルにアクセスできるため、過去の履歴や開発中のブランチのコードも確認できます。 Cursor Ruleによる自発的な検索を実現 Sourcegraph MCP Serverを利用すると、Cursorに明示的に「Sourcegraphを利用して」と指示するだけで必要な時にコードベースを検索してくれます。 さらに、以下のようにCursor用のルールを設定することによって、明示的に指示を与えずとも、Cursorが自発的にMCPを利用して検索を行うようになります。 # 社内コード検索のためのルール メルカリ組織内のリポジトリでコードを検索する際は、search_code MCPツールを使用してください。これにより、すべての社内リポジトリを横断的に検索できます。 使用例 mcp_sourcegraph_search_code(q="PubSubLogWithFieldsInterceptor") 利点 - 社内すべてのリポジトリを横断的に検索可能 - 共有ライブラリの実装詳細を特定できる - 通常のリポジトリ閲覧では見つけにくいコードにもアクセス可能 - 社内フレームワークの実装やパターンをより深く理解できる 一例として、以下のようにCursorに「〜のリポジトリの実装を参考にして」と伝えるだけで、Sourcegraphを通じて社内のコードベースを検索し、実装を進められるようになっていることがわかります。 まとめ Cursorに対して必要な情報を与えるだけでなく、自ら必要な実装を探しにいける環境を整えることが重要でした。GitHub APIよりも社内実装の検索に適したSourcegraph APIを採用し、それをMCP Server経由でCursorから能動的に利用できるようにしたことで、より実用的なコード生成パートナーとして活用できるようになりました。 今回の取り組みが、Cursorを活用した開発体験の向上や、社内における生産性のさらなる向上につながれば幸いです。 明日の記事は メルペイ VPoE @Jorakuさんです。引き続きお楽しみください。
はじめに こんにちは。メルペイ Payment Coreチームの @susho です。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の6日目の記事です。 我々のチームでは、メルペイにおける各決済手段に応じた決済処理を提供しています。今回、新しくチェックアウトソリューションという、決済処理の実装と画面を一括で提供するソリューションを提供することにしました。 ( Stripe Checkout の内製版のようなものをイメージしてもらえると良いと思います。) 詳細は 決済基盤の新たな挑戦: 決済チェックアウトソリューションの開発 をご参照ください。 この記事では、そのソリューションのバックエンドに着目し、アーキテクチャを紹介したいと思います。 これまでの課題 これまでメルカリグループでは、決済処理が必要な新規サービスを立ち上げる際、提供する決済手段に応じて各サービス提供者で決済処理を実装する必要がありました。単純な同期処理だけの場合であればそこまで難易度は高くないですが、3DSを利用したクレジットカード決済など、リダイレクトが必要になる非同期処理が含まれる場合、実装コストは格段に高くなります。 また、決済処理における画面も実装する必要がありますが、基本的にはどのサービスも必要な画面の部品は共通のものが多くなります。 車輪の再発明のように、新規でサービスを立ち上げる際これらの実装をしなければならず、爆速にサービスを立ち上げることが難しくなっていました。 そこで、これらの機能を備えてソリューションを提供することで、これらの課題を解決できるのではないかと考えました。 アーキテクチャ概要 まずはアーキテクチャの概要を紹介します。 Checkout Solution Service 決済処理の一連のフローを管理するリソースを管理し、決済処理を実行するマイクロサービスです。 技術スタックとして、主にSpannerやPub/Subを利用しています。また、安全に分散トランザクションを管理するために、内製のWorkflow Engineを利用しています。Workflow Engineに関しては こちら を参照してください。 Checkout Frontend 決済画面を提供し、BFFを経由してCheckout Solutionの決済処理を呼び出します。 Checkout BFF Frontendで決済画面を提供するために必要なAPIを提供します。 grpc-federation を利用してBFFを構築しています。 Processing Tracer データの整合性が担保されているかどうかをチェックをするためのリコンサイル処理をキックし、その成否を管理するマイクロサービスです。詳細は こちら を参照してください。 Payment Service さまざまな決済手段を提供するための各種APIを提供しているマイクロサービスです。Checkout SolutionはこれらのAPIを組み合わせて決済処理を提供します。 利用者であるClient Servicesはお客さまへ決済画面を提供するために、Checkout SolutionへAPIを呼び出し、そのレスポンスに含まれるURLへ遷移させることで決済機能を提供することができます。 アーキテクチャ詳細 Checkout Solution Serviceの詳細を説明します。 ベースとなる部分は Stripe Checkout を参考にしています。 API まず、Checkout Solution Serviceで提供するAPIの機能について説明します。 CreateCheckoutSession API 決済処理のフローを開始するためにCheckoutSessionを作成するAPIです。決済画面の一意なURLが払い出され、Client ServicesはそのURLへ遷移することで、お客さまへ決済機能を提供することができます。 IdempotencyKeyを受け取り、冪等性を担保します。 ConfirmCheckoutSession API 決済処理を実行するためのAPIです。お客さまが決済ボタンを押下することでFrontendから呼び出されます。ある決済手段で決済が失敗した場合でも別の決済手段で決済できるように、このAPIは決済が成功するまで有効期限が切れるまで呼び出すことができます。 IdempotencyKeyを受け取り、冪等性を担保します。 リソース 次に、Checkout Solution Serviceで管理する主なリソースについて説明します。 CheckoutSession 決済処理の一連のフローを管理するリソースです。 状態遷移 open 初期状態。CreateCheckoutSession APIが呼び出されることでこの状態になります。 complete 後述するPaymentIntentがrequires_captureになるとこの状態になります。 expired 有効期限が切れるとexpiredになります。 PaymentIntent 実際の決済処理を管理するためのリソースです。 状態遷移 requires_payment_method 初期状態。決済処理を実行するまで待っている状態です。 processing 決済処理の実行中に必ず遷移する状態です。この状態にある場合、他のリクエストによってこのリソースを操作することはできず、ロックされます。 requires_capture 決済処理のオーソリが完了したら遷移する状態です。 requires_action 決済処理のオーソリを実行するために追加のアクションが必要になる場合に遷移する状態です。例えば、3DSの認証などが必要な場合はこの状態に遷移します。 succeeded オーソリが確定したら遷移する状態です。 canceled オーソリがキャンセルされたり、追加のアクションを待っている場合に有効期限が切れてキャンセルされた場合に遷移する状態です。 Charge 単一の決済処理を管理するリソースです。PaymentIntentから作成されます。各決済手段の機能を提供しているPaymentのリソースと対応する形で状態を管理します。 CheckoutConfig Clientでカスタマイズしたい設定を管理するリソースです。例として、レイアウトの設定などを管理しています。 シーケンス 次に、実際にこれらのリソースがどのように関連して処理を実行するかのシーケンスを紹介します。 CreateCheckoutSession API リソースを作成するためにDBへINSERTし、Clientへリソースを返します。ここに決済画面へのURLが含まれます。 ConfirmCheckoutSession API PaymentIntentを作成し、決済処理を実行します。冪等かどうかをチェックし、冪等であれば処理を進め、そうでない場合にすでにロックされていたらリクエストをエラーで終了させます。また、決済処理が失敗した場合、その失敗理由をDBに保存します。 オーソリが失敗した場合、状態をprocessingからrequires_payment_methodへ戻すようにすることで、再度APIが呼ばれてもまた別のオーソリを実行できるようにしています。 APIの処理全体をWorkflow Engine経由で実行するため、途中でタイムアウトエラーになった場合、非同期でリトライされます。 冪等性の担保 ここで、APIの共通機能である、冪等性の担保についてどのように実装しているかについて紹介します。 APIでIdempotencyKeyを受け取ります。 同じIdempotencyKeyでDBに保存されているレコードがあるかどうかをチェックします。 存在していない場合は、DBに保存し処理を進めます。 存在している場合は、リクエストフィールドのハッシュ値から計算されたFingerprintと、保存されていたFingerprintが一致しているかどうかをチェックします。 一致している場合は、レスポンスを返すか、処理を進めます。 一致していない場合は、エラーを返します。 このようにすることで、同じIdempotencyKey、リクエストフィールドであれば処理を続行、または即座にレスポンスを返せるようにでき、そうでない場合はエラーにすることが可能になります。 おわりに この記事では、チェックアウトソリューションのアーキテクチャを紹介させていただきました。 3DS認証が必要な場合など、リダイレクト処理が必要になるためまた複雑になるのですが、今回は1番シンプルなケースを書きました。内部の状態遷移や冪等性の担保など、少しでも参考になれば幸いです。 明日の記事は seitauさんの「Sourcegraph × 自作MCP Serverによる社内コード検索連携の取り組み」です。引き続きお楽しみください。
この記事は、 Merpay & Mercoin Tech Openness Month 2025 の5日目の記事です。 この記事では、Payment & Customer Platform (PCP) Vision 2.0の一環として進行中の、決済チェックアウトソリューション開発に関する背景、プロダクトビジョン、全体設計、そして現在の状況について紹介します。 はじめに こんにちは。メルペイPayment & Customer Platform(PCP)チームのEngineering Headの @foghost です。 PCPの各ドメインチームが現在メルペイの事業だけでなく、メルカリグループが展開するすべての事業を支えるための決済、KYC、加盟店管理の社内共通ビジネス基盤(Foundation)の開発を行っています。 (現状をVision 1.0と定義します) しかし、現状複数の事業に利用可能な共通機能の提供はできてるとはいえ、機能の拡張性が不足してたり、導入時のコストがかかったりする課題があり、各事業の立ち上げやグロースを爆速させる武器にはまだなりきれていない状況です。 昨年(2024年)から、今後の10年を見据えたTech Roadmapとして、新たな PCP Vision 2.0 を策定し、事業拡大のスピードを大幅に加速させる共通ビジネス基盤への進化を目指して取り組んできました。 PCP Vision 2.0では、「 Functionalities Evolution(機能の進化) 」と「 Domain Architecture Evolution(アーキテクチャの進化) 」と2つの側面から、現在の課題を整理しながら、各ドメイン領域において考えられる将来の姿を定義し、取り組みを進めています。 今回ご紹介する 決済チェックアウトソリューション は「Functionalities Evolution(機能の進化)」を実現するため、決済基盤の領域でチャレンジしている取り組みの一つになります。 課題定義 現在、メルペイは決済事業だけでなく、メルカリやメルカリShopsなどのEC事業にも同じ決済基盤を提供する決済APIを使用しています。 共通の決済APIソリューションの提供のみでも外部決済手段の接続、複合決済含めた決済トランザクションの管理、不正検知、会計連携など含めて共通化ができて各EC事業における決済機能の導入の開発負担を大きく削減できています。 しかし、決済APIのみでは、新しい決済手段(例:コンビニ支払い、ビットコイン支払い)に対応するには、各プロダクトごとに個別の実装が必要になります。このため、画面の改修やバックエンド処理のフローを含めた実装作業が必要です。プロダクトの企画、要件定義、機能設計、開発などをすべて含めると、数ヶ月かかる場合もあります。 また、プロダクトにおけるチェックアウトのコンバージョン率を向上させるためのチューニングや、チェックアウトのUX改善においても、複数のプロダクトで重複した開発労力が発生しています。 このような課題を解決するために、Stripeなどの海外の決済事業者が提供してるLow Code Checkout Solutionのような、社内向けの Low Code Solution を開発できないかについて、2024年6月頃から検討を始めていました。 プロダクトビジョン ソリューションの企画段階では、解決したい課題に向けて、US事業を含むEC事業におけるチェックアウトのユースケースを調査し、PMを含めて以下のようにソリューションのプロダクトビジョンを明確にしました。 各プロダクトにチェックアウトソリューションのClient/Frontendの画面実装を シームレスに組み込められる Low Codeで簡単にインテグレーションできる 社内統一したDesign Systemに基づいた統一感のあるUXの提供 Configurable 利用可能な決済手段や、クーポンなどの共通要素はConfigでカスタマイズできる。設定すれば、すぐにその決済手段を利用できるようになる チェックアウト画面についてもConfigでカスタマイズが可能であり、プロダクト側の独自な画面要素も簡単に拡張することができる プロダクトを横断して、利用者の 決済設定を共通化 することができる 利用者が一度決済設定(クレジットカード情報など)をすれば、複数のプロダクトを横断して利用することができる 国内事業とGlobal事業 、両方サポートできる  国内事業に向けて自社決済手段のサポート 台湾や香港の越境取引を行っているGlobal事業に向けて現地決済手段のサポートや、General Data Protection Regulation(GDPR)などの現地の法的規則に準拠するシステム設計 ソリューション設計 既存のAPIソリューションに加えて、Low Codeのソリューションを検討する際に考えられる実現方法はいくつかあります。以下の観点からそれぞれ5段階評価して選定しました。 インテグレーションコストの削減効果 これは最も解決したい課題であり、プロダクトに最小限のインテグレーションコストでチェックアウト機能を組み込むことを目指したい ガバナンスの容易さ 複数のプロダクトを横断してUX体験のガバナンスが可能 共通のチェックアウト機能・体験を横断的に最適化しやすい状態を目指したい フレキシビリティの高さ プロダクトごとに独自の画面設計や要素を拡張したいニーズが必ず存在するため、トレードオフが発生することもあるが、拡張性の高い仕組みを提供することを目指したい。 パータン 手法 インテグレーションコスト削減 ガバナンスの容易さ フレキシビリティーの高さ チェックアウト画面のオーナーシップ A 決済APIのみ提供(比較のため) 1 1 5 プロダクトチーム B 共通のチェックアウト画面を提供し、画面要素ごとに一定のカスタマイズ性を提供する 5 5 2 決済基盤チーム C チェックアウト画面を自由に組み立てる共通の仕組みを提供する。画面要素について共通の画面要素(Core Element)とプロダクト特有画面要素(Flex Element)どちらも組み込み可能にする 4~5 4 4 決済基盤チーム D 画面の組み立てはプロダクトに任せる、共通の画面要素(決済手段など)をSDKなどで提供する <4 3 4.5 プロダクトチーム 最終的にソリューションの立ち上げ時の実装方法を「 C 」に決めました。また、タイミングもよく社内で一緒に共同開発できる新規事業のプロジェクトがあり、このプロジェクトはWebサービスであるため、最初は 決済基盤側でself-hostedしたWebのチェックアウトソリューション の開発からスタートしました。将来必要があれば、蓄積された共通の画面要素をパターンDのようにSDKとしてプロダクトへ提供することも可能だと考えています。 プロダクト視点からチェックアウトソリューションを導入するときの処理フローが以下のようになります。初期のセットアップ、インテグレーションの実装が必要になるが、チェックアウトにおける各種共通機能(例: 決済手段、クーポン)を利用するにはチェックアウトの設定だけで対応できるようになります。 プロダクトのチェックアウト要件に応じてチェックアウトの設定情報を作成する 購入処理をトリガーにバックエンド経由でチェックアウトのセッションを発行する チェックアウトセッションに含まれるチェックアウトのトップページの遷移URLへ遷移すれば、決済基盤が提供するチェックアウト画面が表示される それ以降プロダクトの利用者がチェックアウトが提供する各種決済手段を利用して決済処理できる 決済の結果についてCallback経由、もしくは非同期イベントの通知から受け取ることができる 受け取った決済結果に基づいて、プロダクトのバックエンド側で最終的なバリデーションを行い、決済を確定したり、キャンセルしたりすることができる プロダクト特有画面要素のサポート ソリューションとして一見簡単に見えますが、各プロダクトで共通化がまだ難しい画面要素をどのようにサポートすれば良いか、非常に悩ましい課題でした。 この課題を解決するために以下のように共通の画面要素とプロダクト特有の画面要素を分けて、それぞれ決済基盤とプロダクトサイドで開発できるためのフレームワークを開発してます。 複数のプロダクトで利用可能な共通の画面要素(例:決済手段、クーポン)については、「 Core Element 」として基盤チームが担当して開発を行う。 プロダクト固有の画面要素については、「 Flex Element 」としてプロダクト側のエンジニアが独自で開発できる。 プロダクトは、ゼロから自前で実装するのではなく、提供される共通の画面要素(Core Elements)と、独自で開発した画面要素(Flex Elements)を組み合わせて、製品固有のレイアウトを作成する。そうすることで、独自のカスタマイズされたチェックアウト体験を実現できるようになる。 開発の現状と今後の展望 現在、チェックアウトソリューションはメルカリの新規サービス「 メルカリNFT 」のリリースと共にすでに本番で機能提供し始めています。また他の新規サービスのリリース向けにも共同開発を進めており、既存プロダクトのチェックアウト機能のリプレースも現在検討しています。 今後は冒頭でもお伝えしたように、各事業の立ち上げや成長を加速させるための強力な武器となるよう、以下の観点からチェックアウトソリューションをさらに成熟させていきたいと考えています。 決済手段を含む共通画面要素の拡充 即時決済だけでなく、継続払いなど多様なユースケースへの対応 プロダクトを横断した一貫性のあるチェックアウト体験の継続的な改善 チェックアウトソリューションに関連する記事が公開予定なので、あわせてご確認ください。 6/9公開予定「 チェックアウトソリューションのバックエンドアーキテクチャ 」 6/18公開予定「 Checkout frontend design 」 明日の記事は sushoさんの「チェックアウトソリューションのバックエンドアーキテクチャ」です。引き続きお楽しみください。
この記事は Merpay & Mercoin Tech Openness Month 2025 の 4 日目の記事です。 こんにちは、Merpay の Payment Core チームでエンジニアリングマネージャーをしている komatsu です。 普段は決済基盤を開発するチームのマネージャーをしており、最近では社内で AI/LLM 関連の導入や登壇などもしています。 この記事では、私たちの組織で実施した「PCP LLM Week」という取り組みについてのレポートと、イベントを通して得られた知見についてご紹介します。 PCP LLM Week は、50 人程度のエンジニア組織で一週間にわたって一切の手動コーディングを禁止し、AI/LLM のみを使用した開発を強制的に行うという、かなりチャレンジングな実験でした。 PCP LLM Week とは 2025/05/08 から 2025/05/14 までの 1 週間にわたって、Merpay の Payment & Customer Platform (PCP; Payment Core チームを含む、決済基盤や KYC、パートナー向けの基盤機能の開発をするチームが所属する領域) 内で実施しました。 対象となったのは約 50 名のメンバーで、バックエンドエンジニア、クライアントエンジニア、QA エンジニア、そして各チームのエンジニアリングマネージャーを対象に開催しました。 このイベントの最大の特徴は、期間中は手動でのコーディングを基本的に禁止し、LLM のみを使用して開発を行うという厳格なルールを設けたことです。 なぜ始めたのか: 組織の課題と解決策 この取り組みを始めた背景には、メンバーとの 1on1 や普段のコミュニケーションの中で上がっていた以下の課題がありました。 学習機会の不足 : 多くのエンジニアが AI/LLM に興味を持っていたものの、日々の業務が忙しくまとまった学習時間を確保できない 組織的な情報格差 : 全社的に進めているライセンスやセキュリティの整備状況が全員に伝わっておらず、何を使ってよいのか分からない 高度な活用方法の未習得 : MCP (Model Context Protocol) を活用したドキュメント連携や Slack 連携などの応用的な使い方を試せている人が少ない 情報交換の機会不足 : AI/LLM についてエンジニア間で気軽に情報交換する機会が限られている 参考事例: 強制的な学習環境の効果 今回の PCP LLM Week は、 ある企業の CTO が「エンジニアのコーディングを禁止する」という指令を出した事例 と その結果 を参考にして企画しました。 この事例では、短期的には生産性が約半分に低下したものの、AI の得意・不得意分野が明確になり、組織全体の AI 活用能力向上という長期的価値を確認できたと報告されていました。 私たちも同様のアプローチを取ることで、個人の自発的な学習に頼るのではなく、組織として確実に AI/LLM と向き合う機会を作ることができると考えました。 ツール選択: なぜ Cursor なのか 今回のイベントでは以下の理由から主に Cursor エディタを推奨しました。 学習環境の共有 : 全員が同じツールを使うことで、知見の共有やサポートがしやすい 高度な機能 : MCP ツールとの連携など、より発展的な使い方を学べる 実用性 : (企画時点において) 実際の業務で継続的に使用できるレベルの機能を持っている Kotlin や Swift を主に扱うクライアントエンジニアにとっては制約が大きくなりますが、全員が同じツールを使うことで統一された学習体験を提供し、知見の共有やサポートをより効果的に行うことができると判断しました。 また、当時社内では GitHub Copilot も利用可能でしたが、社内での Cursor への注目や性能を加味して原則 Cursor に寄せました。 設計思想: 目標とルール イベントを有意義なものにして、同じ方向に向かっていくためには適切な目的と目標設定が必要です。 そのため、イベントを企画したタイミングで考え、モチベーションやゴールを記載したドキュメントを作成し、事前に参加メンバーに共有しました。 目的と目標 Motivation AI/LLM がエンジニアの開発スタイルを大きく変革している昨今に、「実際の開発現場でどの程度活用できるのか」を組織全体で実践的に検証する 日常の開発業務を LLM のみで実行することにより、AI との効果的な協働方法や、人間が担うべき領域との適切な境界線を、チーム全体で発見・共有する Goals 以下の 5 つをイベントの目標として設定しました。 LLM の能力と限界を直接体験する : LLM によって何ができて何ができないのかを肌で感じる 開発ワークフローの最適化戦略を構築する : LLM を現在の開発プロセスに効果的に統合し、持続可能な生産性向上を実現するための実践的な活用戦略を学ぶ AI コラボレーションスキルを習得する : プロンプトエンジニアリングや LLM による問題分解のスキルを身につける 開発パラダイムの再考 : 従来のコーディングアプローチをゼロベースで考え直し、新しい問題解決アプローチを探求する AI 拡張時代への適応 : 開発者の役割がどのように変化していくかの洞察を得る LLM は開発プロセスにおける大きなパラダイムシフトですが、愚直に適用すると既存のプロセスに LLM を上乗せするだけになってしまいます。 LLM を体験して既存の業務プロセスに適用するだけでなく、ゼロベースで開発プロセスやエンジニアのあり方を見つめ直すことが近年の AI/LLM 時代に必要だと考え、このような構成にしました。 Non-Goals 一方で、以下は今回のイベントの目標ではないことを明確にしました。 イベント期間内の生産性向上 : あくまで中長期的な生産性向上の一環であり、短期的な生産性低下は気にしない この取り組みを実現するために、Director、VPoE レベルでの組織的な意思決定を行い、短期的な生産性低下を許容して学習投資として位置づけることで、エンジニアが安心して実験できる環境を整備しました。 ルール設計 厳格なルールを設けることで、全員が LLM と向き合う環境を作りました。 基本原則 手動コーディングの完全禁止 : 一行のコード修正であっても、必ず LLM を通して行う 小さな編集も例外なし : 変数名の変更、コメントの追加など、些細な変更も LLM で実施する 普段の業務での実践 : 特別なタスクを用意するのではなく、日常業務を LLM で行う 許可される例外 緊急対応 : インシデント対応やシステム障害への対処 締め切り間近のタスク : リリース直前など、時間的制約が厳しい場合 環境設定 : LLM ツール自体のセットアップや設定変更 ドキュメント作成 LLM による作成を推奨 : 技術仕様書、設計書、README 等は LLM で作成することを推奨 手動修正は許可 : LLM が生成した内容の事実確認や微調整は人間が行ってもよい なぜ日常業務で実践するのか 今回のイベントでは、特別なタスクやサンプルプロジェクトを用意するのではなく、これらの理由から、あえて普段の業務に LLM を適用することにしました。 現実的な活用可能性の検証 実際の業務環境で LLM がどの程度役立つのかを正確に把握するため 理想的な条件ではなく、制約のある現実の中での効果を測定するため 既存のコードベースや技術スタックとの相性を確認するため 真の課題と限界の発見 サンプルプロジェクトでは見えない、実業務特有の困難さを体験するため レガシーコードや複雑な依存関係がある環境での制約を理解するため ドメイン知識が必要な場面での LLM の限界を実感するため 継続可能性の評価 イベント終了後も継続して使えるかどうかの判断材料を得るため 日常的なワークフローに LLM を組み込む際の現実的な課題を把握するため チーム開発や既存プロセスとの統合における問題点を発見するため 組織全体での実用性確認 個人の実験レベルではなく、チーム・組織レベルでの実用性を検証するため 異なる役割 (バックエンド、フロントエンド、QA、マネージャー) での効果の違いを確認するため 実際のプロダクト開発における生産性への影響を測定するため これらの方針によって、より実用的で価値のある知見を得ることを狙いとしました。 実施スケジュール イベントは参考にした事例と同じく 1 週間の構成で実施しました。 初日 (5/8) にイベント紹介、他部署の AI 活用事例紹介、Cursor ハンズオン、設定・開発時間を行い、その後 1 週間 (5/8-5/14) 各自で普段の開発業務に AI/LLM を活用し、最終日 (5/14) に成果発表会を開催しました。 成功例と課題: 実践から見えた現実 イベント後にアンケート調査を行ったりメンバーとの 1on1 を通してさまざまなフィードバックを得たりしました。 全体的な満足度と効果 まず、メンバーのイベントに対する満足度について、多くの (92%) メンバーがイベントを効果的に活用し、その機会に満足していました。 特に初日に細かい設定について話し、まとまった準備時間を取ることで社内で開発されている MCP ツールの導入など、発展的な設定までできたことが良い体験だったという声もありました。 また、アンケート結果から、参加者のスキルレベルによって異なる効果が見られました。 このイベントによって、多数の「初心者」だったメンバーが「中級者」へとレベルアップしました。 LLM ツールを初めて使う人にとって、強制的に使用する環境が効果的な学習機会となったようです。 基本的な使い方から応用的な活用方法まで、短期間で幅広く体験できたことが、さまざまな知見の習得につながったと思われます。 一方で、既に「上級者」レベルの参加者については、さらなるレベルアップは限定的でした。 これらのメンバーには、より高度な学習機会や異なる学習スタイルが必要であることが判明しました。 ただし、上級者には他のメンバーへの指導やベストプラクティス共有という重要な役割があり、組織全体の底上げに大きく貢献していました。 アンケート結果では、ほぼすべて (96%) のメンバー が「今後も使い続けたい」と回答しており、スキルレベルに関わらず継続的な活用への意欲が高いことが確認できました。 この記事の執筆時点の統計を見ると、使用頻度の差はありますが、PCP における Cursor の使用率は非常に高い水準になっていました。 このイベントが多くのメンバーがツールを習得し、スキルレベルの底上げに寄与したことが一つの要因だと思うので、主催者としてはとても満足しています。 期間については、ほとんどの参加者が「1 週間が適切」と回答しており、学習効果と業務への影響のバランスが良好であることが確認できました。 短すぎず長すぎない、集中して取り組める期間として評価されています。 実際、普段のアサインもある中でこれ以上長いと多少支障が出てきますし、短すぎても消化不良になる可能性もあるので、適切な期間設定だったと感じます。 実際の参加者の声もいくつか紹介します。 全く触ったことがない状態だったが、Copilot のときと同様に無くてはならないものになった。簡単な仕事なら AI で完結できる感覚がある。 どのように指示するかでアウトプットのクオリティは変わるものの、もう開発に使用できるレベルまで LLM の信頼性があったことに驚いた。 社内のドキュメントや repository をワークスペースに追加することで、社内の技術基盤や事情に沿ったコーディングを LLM がしてくれた体験が良かった。 元々かなり有用だという噂を聞いていた程度だったが、実際に使ってみてその効果を実感できたため、どのように活用できるかをタスクごとに考えるようになってきた。 It seems to allow us to work more efficiently by being able to review many lines of codes and files to find and summarize information. It also allows us to peek at where it found such information to confirm the accuracy of its results as well. It’s also able to help refactor and find unused code as well. 曖昧な指示だとずれた変更が行われるのでコンテキストを明確にする必要があり、自分がやろうとしていることをコンテキストなしの状態から言語化するところに慣れが必要だと感じた。また、一見自然言語でも通じるように見えるので素朴に質問してしまいがちだが、特に MCP server などでは内部でどのような問い合わせが行われるのかを理解した上での利用が必要といった難しさがあると感じた。 コード生成にはまだ一定の限界がある。一発ではできない。k8s や tf のレポジトリはファイルが多すぎるため LLM にとってはノイズになることもある。 多くのメンバーの LLM に対する信頼度が向上し、その活用方法についても考える良い機会になったことが伺えます。 また、同時に現状の LLM の性能の限界を理解する良い機会にもなり、どのようなタスクに適用していくかの洞察を得ることができました。 成功例: LLM の可能性を実感した瞬間 開発効率の大幅向上 テスト作成、API 修正などの定型的なタスクで劇的な時間短縮 既存コードの解析や大規模リファクタリングでの威力 ボイラープレートコード生成による生産性向上 高度なコーディング支援 Cursor や GitHub Copilot による的確な修正提案 複雑なコード生成による協働的な開発体験 一発で修正箇所を発見できる精度の高さ ワークスペース統合とドキュメント活用 社内ドキュメントやリポジトリとの連携による組織固有の技術基盤に沿ったコード生成 既存データソースを基にした技術仕様書の自動生成 MCP を活用した Confluence の仕様から Jira チケットの自動生成 プロンプトエンジニアリングの重要性の理解 明確で詳細な指示の重要性 (人間への指示と同様) プロンプトの品質が出力品質に直結することの実感 当初の狙い通り、多くのメンバーが直近の LLM の性能について理解し、活用できる開発プロセスを発見していました。 また、MCP や v0.50 でちょうど追加されたワークスペース機能なども駆使し、複数マイクロサービスに跨った開発なども可能となり、高度な活用をしているメンバーも多々いました。 課題: 直面した限界と困難 また、イベントを通して現状の AI/LLM の課題も見えてきました。 精度と品質の問題 不正確な出力や意味不明な結果の生成 複雑で非標準的な概念への対応困難 コード品質の一貫性の欠如 効率性とワークフローの課題 AI との反復的なやり取りによる時間コスト 特定タスクでは人間の方が依然として高速 既存ワークフローとの統合やツール切り替えの煩雑さ AI 生成コンテンツの検証時間が手動作業と同等かそれ以上 プロンプトとコンテキストの難しさ 曖昧な指示に対する AI の対応困難 大規模で複雑なコンテキストの処理限界 自分の要求を詳細に言語化することの困難さ 知識ギャップと限界 ドメイン固有知識やインターネット上にない情報への対応困難 暗黙知やコードで明示されていない側面の理解不足 技術領域による制約 iOS/Swift 開発など、特定の技術スタックでの効果的な活用の困難さ 当然ではありますが、コードや仕様には載っていない各メンバーが持っているドメイン知識を適切に伝えるには一定の負荷がかかり、そのことが生成されるコードの限界になることがわかりました。 これを機に私のチームではドキュメンテーションを自動化したり不足しているコードコメントを追加したりするといった次のアクションにつながっており、良い学びができたと感じています。 また、強制的な機会だったからこそ、AI でできることの限界を知ることができました。 開発に対する考え方の変化 イベントを通じて、メンバーの開発に対する考え方に以下のような変化が見られました。 実用性への確信の高まり 「思っていたより実用レベルに達している」という認識の変化 従来の開発手法を置き換える可能性への確信 コード編集などの特定領域での代替可能性の実感 戦略的活用の理解 全てのタスクの代替ではなく、適材適所での活用の重要性 設計ドキュメントやコードレビューなど特定用途での高い効果 人間の専門知識を最終段階に残すプロセスの有効性 継続学習の必要性 効果的な使用方法を見つけるための実験の重要性 他者の経験から学ぶことの価値 ベストプラクティスの継続的な学習の必要性 組織的なシナジー効果 全員が LLM を使うことで生まれる相乗効果への気づき チーム間での活用度合いの差とベストプラクティス共有の重要性 実際に全員で使うことで、どのように LLM を活用していくのか、どのようにエンジニアの役割が変わっていくのかといった当初の目標を考える有意義な機会になりました。 また、継続的に知見の共有や学習を続けていく必要性も再度実感できたと思います。 マネージャー視点: 組織運営への影響 EM の観点でも多くの学びがありました。 組織運営に関する学び まず、 強制力の重要性 を痛感しました。 普段から AI/LLM という声は多く聞いていましたが、実際には日々の業務に追われて学習時間を確保できないメンバーがほとんどでした。 今回のように組織全体で取り組む期間を明確に設けることで、全員で LLM に真剣に向き合う機会を作ることができました。 PCP のメンバーは自走力の高いエンジニアが多いですが、そのような環境だとしても自発的な学習だけに頼らず、組織全体としてのスキルアップの機会を提供することの重要性を再確認しました。 次に、 情報共有の活性化 が想定以上の効果を生みました。 イベント用に作成した専用 Slack チャンネルは、当初は質問や困りごとを共有する場として考えていましたが、実際には知見の共有やちょっとした発見の報告など、非常に活発なコミュニケーションの場となりました。 イベント終了後も継続的な学習コミュニティとして機能しており、組織の学習文化醸成に大きく貢献しています。 そして、 スキルレベルの標準化 という予想外の効果も得られました。 これまでは AI/LLM の活用レベルに個人差が大きく、チーム間での知見共有も限定的でした。 しかし、全員が同じ体験をすることで、組織全体の AI リテラシーが底上げされ、共通言語で議論できるようになったのは大きな収穫でした。 生産性への現実的な影響 短期的には実際に生産性が多少低下しましたが、これは組織として予想し、受け入れていた結果でした。 また、個人的には予想していたほどの生産性の低下は見られず、直近の LLM の性能向上による恩恵が大きいと感じています。 同時に生産性観点でも長期的な価値を確認できました。 適材適所の理解 : どのタスクに AI が向いているかの判断力向上 開発スタイルの変化 : プロンプトエンジニアリングを通じた問題分解能力の向上 AI ファーストな思考習慣 : 課題に直面した際に AI による解決を当然の選択肢として考える習慣の定着 個別最適化されたワークフロー : 各メンバーが自身の開発スタイルに最適化された AI ツールの組み合わせと活用方法を確立 特に AI による解決策を常に考える習慣ができたことは、ゼロベースでアプローチを考え直す上で非常に価値ある体験だったと感じています。 また、この結果は、組織のリーダーシップが「短期的なコストを払ってでも、長期的な AI 活用能力を獲得する」という明確な意思決定を行ったからこそ実現できたものであり、組織として成長していくことへの重要性を再度確認しました。 マネージャーアンケートから見えた現実 マネージャー向けのアンケートでは、AI 導入による生産性向上とリソース計画への影響について以下のような現実的な見解が得られました。 生産性向上への見通し 生産性向上については、短期的な劇的な変化ではなく、中長期にわたって徐々に向上していく見込みであることが分かりました。 現在はツールの乱立や性能差の変化により、短期では一長一短の状況が続いており、これから模索や選定を継続的に行っていくことが重要です。 また、AI の効果は作業の種類と開発者の LLM 経験に大きく依存することも明らかになりました。 適切なタスク選択と生産性維持のためのトレーニングが重要であり、特に問い合わせ対応や運用系など、直接的な生産性に結びつかないタスクでの効率化に期待が寄せられています。 リソース計画への影響 現時点では従来のリソース計画手法を大幅に変更するレベルの変化は見られませんでした。 また、短期で AI によってリソースに大幅な余裕が出るには依然として壁がありますが、組織横断の開発スタイルは導入しやすくなったという手応えを感じています。 段階的な環境整備が現実的なアプローチであることが確認できました。 マネージャーとしての学び 組織的な取り組みの価値として、IC からの結果を聞いて想定よりも課題が多いことを実感しました。 一方で、全員が使うことで生まれるシナジー効果を確認でき、組織として LLM にどう寄り添っていくかを考える機会の重要性を認識しました。 現実的な期待値設定については、短期的な劇的な生産性向上への過度な期待は禁物であることが分かりました。 中長期的な投資として捉え、継続的な学習と改善が必要であり、ツールの組み合わせや切り替えの最適化が今後の課題となります。 その後の展開: 継続的な取り組み イベント最後には以下の項目でメンバーを表彰し、効果的な活用方法を共有しました。 LLM Code Generation Champion : 最も多くのコードを生成した人 LLM Refactoring Champion : 最も多くのコードを削除 (リファクタリング) した人 Precision Prompter : 最も高い Accept Rate を達成した人 同僚がどのように活用できているかを間近でシェアすることで、全員がより自分ごととして AI スキルセットに対する理解を深めたり、期ごとの個人目標に追加したりと、全体的な AI に対する視座向上ができたと思います。 継続的な取り組み 情報共有チャンネルの継続 : イベント用 Slack チャンネルを汎用的な名前に変更し、継続的な情報交換の場として活用 ベストプラクティスの共有 : 効果的な活用方法を組織内で継続的に共有 新機能のキャッチアップ : AI/LLM ツールの新機能を組織全体で迅速に取り入れる体制構築 まとめ PCP LLM Week は、短期的な生産性低下というコストを払いながらも、全員で体験することで組織全体の AI 活用能力を大幅に向上させる貴重な機会となりました。 特に重要だったのは、「全員で同じ体験をする」ことで生まれた学習効果と、その後の継続的な情報共有文化の醸成です。 AI/LLM の活用は個人のスキルに依存する部分が大きいですが、組織として取り組むことで、より大きな価値を生み出せることを実感しました。 特に昨今のモデルやエコシステムの進化は個人でキャッチアップしていくにはあまりに膨大なため、組織として方向性を示し、スキルアップの機会を提供することでモチベーションを獲得し、各メンバーの自走力に繋げる良いサイクルが生まれると感じました。 今後も組織として AI-Native になるための機会や仕組みを継続的に作っていきたいです。 明日は同じく PCP の foghost さんの記事です。 引き続き Merpay & Mercoin Tech Openness Month 2025 をお楽しみください。
こんにちは。メルコイン バックエンドソフトウェアエンジニアの @toshinao です。 この記事は、 Merpay & Mercoin Tech Openness Month 2025 の3日目の記事です。 これまでの採用フロー メルコインはメルカリグループですが、メルペイやメルカリとは別に採用を行っています。これまでメルコインのソフトウェアエンジニア採用(バックエンド)は、「技術課題 → 1次面接 → 2次面接 → 最終面接」という流れでした。技術課題はGoまたはJavaで出題され、応募者は1週間以内に提出します。1次面接はエンジニア、2次面接はマネージャー、最終面接は役員が担当します。 技術課題の問題点 技術課題は、応募者の経験している技術言語をもとにご自身で選択していただいています。従来の技術課題は、GoとJavaで内容が異なっていました。Goでは課題プログラムの修正や機能追加を行う形式で、応募者のプログラミングスキルやコードの理解力、バグ修正・実装力などを評価していました。一方、Javaではアプリケーションを1から開発する課題が出され、設計・実装・テストまで一連の開発プロセスを通じて、総合的な開発力や設計力を評価していました。 メルコインのBackend開発はGoで書かれており、Java経験者はポテンシャルによる採用となるため、Javaの課題はアプリケーション全体の設計能力が強く要求され、応募者にとってハードルが高いという課題がありました。 また、技術課題をGoだけにすると応募者が減ってしまう懸念がありました。 また、応募者に大きな時間的負担がかかっていました。応募者は想定回答時間が5時間\~10時間の課題を1週間以内に提出する必要がありました。実際に課題を受けて入社した社員に聞いたところ10時間以上かかった社員が多くいました。課題に加えて、1次面接も1時間程度必要で、全体として多くの時間をいただいていました。 導入の経緯 こうした課題を解決するため、技術課題と1次面接をSystem Design Interview(以下、SDI)を導入しました。すべての採用フローがSDIになったわけではなく、主にGo未経験者を対象に導入しています。SDIの導入にあたっては、他社の事例を参照したり、社内メンバーに何回も試し、ブラッシュアップを繰り返しました。 開発経験、とくにシステム設計ができる方であれば、Go未経験でもメルコインで活躍できると考えています。実際、Javaの課題で合格した方もメルコインで活躍しています。Goのスキルも必要ですが、金融システムとしてスケールや耐障害性を考慮した設計力も重要です。設計力があれば、既存コードを参考にしつつ、メンバーのサポートを受けてGoでの開発も可能だと考えています。 System Design Interviewとは System Design Interviewは、ソフトウェアエンジニアの採用面接でシステム設計能力を評価する手法です。GoogleやAmazon、Microsoftなどの大手テック企業で広く採用されており、バックエンドエンジニアやシステムアーキテクトの重要な評価基準となっています。 SDIの進め方 SDIでは、面接官がシステムの要件を提示し、応募者は要件を確認しながら対話形式でアーキテクチャを設計していきます。例えば「決済サービスの設計」や「大規模なログ収集基盤の設計」など、実際の業務に近い課題が出されます。応募者は、要件のヒアリングから始め、システムの全体像をホワイトボードやオンラインツールで図示しながら説明します。 一般的なSDIでは、YouTubeやX(旧Twitter)のような大規模サービスや、検索機能・レコメンデーションシステムなどの設計課題が出されます。応募者は、要件定義・スコープの明確化、データモデル設計、コンポーネント分割、インターフェース設計、スケーラビリティ・パフォーマンス・可用性・耐障害性・セキュリティ・コスト・運用性など、さまざまな観点から設計を進めます。 メルコインのSDIでは、より実務に近いバックエンドシステム設計の課題を出題しています。Product Managerから作りたいシステムの要件を教えてもらい、システム設計をしていく形になっています。 評価基準 SDIで重視しているのは、与えられた要件の理解、適切な技術選定、スケーラビリティ・セキュリティ・パフォーマンスなどを考慮した設計力です。この過程で、技術的知識だけでなく、問題解決力やコミュニケーション能力など、実務で必要なスキルも評価できます。特に、設計の根拠を論理的に説明できるか、トレードオフを意識した提案ができるか、チームでの議論を想定したコミュニケーションができるかを重視しています。 SDIの面接は90分で、最初の70分がSDI、次の10分が過去の開発経験の質問、最後の10分が応募者からの質問時間です。面接官は、応募者が本質的な課題に集中できるよう、適宜ヒントを出したり、議論の方向性を調整したりしています。 SDIの効果 SDI導入により、当初想定していた以上の効果が得られました。 まず、技術課題でいただいていた応募者の時間が削減されたことで、選考にかかる期間を短くすることができました。さらに、評価の質も向上し、システム設計能力だけでなく、コミュニケーション力や問題解決アプローチも評価できるようになりました。これにより、実際の業務での活躍イメージをより正確に把握できるようになっています。 また、特定のプログラミング言語経験に依存しない評価方法となったことで、多様なバックグラウンドを持つ人材の発掘にもつながっています。これにより、技術チームに多様な視点や経験を持つエンジニアを迎え入れることができています。 さらに、SDIを通じて応募者の「考え方」や「価値観」も把握しやすくなりました。例えば、障害発生時の対応方針や、セキュリティリスクへの意識、コストとパフォーマンスのバランス感覚など、実際の業務で重要となる観点を深掘りできるようになりました。 SDIの課題 一方で、SDIにも課題があります。 元々の問題が70分で最後まで回答するのが難しいため、設計の本題と関係ない部分に時間を取られると、ほとんど進まないまま終わってしまうことがあります。そのため、面接官が軌道修正する必要があり、話を遮る場面も増えます。応募者に不快な思いをさせないよう配慮が必要です。 また、応募者からの質問にどこまで答えてよいかの判断も難しいです。正解をそのまま伝えてしまうことを避けるため、曖昧な回答になりがちです。特に、設計の根幹に関わる部分はどこまで答えるか非常に難しいです。 これらの課題を解消するため、面接ごとに曖昧さを減らし、「この試験で見ないこと」や「SDIの進め方の補足」などを追加し、本題から逸れないよう問題をブラッシュアップし続けています。面接官同士での振り返りや、応募者からのフィードバックも積極的に取り入れています。 今後の展望 今後は、SDIの課題バリエーションを増やすなど、SDIの質を上げていくことや、面接官のトレーニングや評価基準のさらなる明確化にも力を入れ、より公平で納得感のある選考プロセスを目指します。 また、SDIの内容や運用ノウハウを社内外に発信し、他社やコミュニティとの情報交換も積極的に行っていきたいと考えています。 まとめ SDIの導入により、メルコインの採用プロセスは大きく改善されました。時間的な効率化だけでなく、より実践的な評価が可能となり、多様な人材の発見にもつながっています。一方で、面接の進め方や質問対応など課題もありますが、継続的な改善を通じて解消を図っています。 今後も、SDIを通じて実践的なシステム設計能力を持つエンジニアを発見・採用し、より強固なシステム開発チームの構築を目指していきます。 メルコインにご興味ある方は、下記よりご応募ください。 Product Engineer,Backend – Mercoin 明日の記事はkomatsuさんです。引き続きお楽しみください。