Diverse developer blog

株式会社Diverse(ダイバース) 開発者ブログです。

NotebookLMでYYCの仕様を質問できるようにした

こんにちは、Diverse developer blogです。

はじめに

プロダクトの仕様について、エンジニアなら誰しも一度は「これってどういう仕様でしたっけ?」と質問された経験があると思います。

プロダクトを運営していると、仕様の全てを把握するのは難しく、仕様確認の依頼はエンジニアのところに集まってきます。その都度コードを読みに行くことになり、こうしたコストは地味に積み重なっていきます。

今回はそのやり取りを、NotebookLMに肩代わりしてもらう仕組みを整えました。

これまでの仕様確認フロー

これまでYYCでは仕様調査の一部を Devin に任せてきました。コードベースを一定理解しているAIに質問ができる、というのは非常に便利です。

エンジニア以外のメンバーが自分で仕様を確認できる手段ができたことで、エンジニアへの問い合わせが減り、運用としても定着していました。

運用見直しのきっかけ

しかしDevinの料金体系が変更され、これまで無料で気軽に使えていた範囲が有料化されました

cognition.ai

そのまま課金して使い続ける選択肢もありましたが、仕様確認の用途であればコストを抑える方法があるはず。

そう考え、代わりになるツールを検討した結果、NotebookLMを採用することにしました。

なぜ NotebookLMを選んだのか

判断の軸になったのは、次の4つの観点です。

1. 料金

Devin で行っていた仕様調査と同じことが、NotebookLM なら追加コストなしでできる。

2. Google Workspace との統合

弊社ではGoogle Workspace を使っているため、社員のアカウントとアクセス管理がそのまま利用できる。

3. Devinの良いところを継承している

具体的には次の2点

  • 引用付きの回答:答えの根拠(どのファイルのどの部分を参照したか)が明示されるので、AI の答えを鵜呑みにせず裏取りできる

  • 参照範囲の限定:NotebookLMにアップロードしたソース以外は参照しないので、ハルシネーションが起こりにくい

4. Google エコシステムの将来的な恩恵

NotebookLM と Workspace は Google が継続的に改善している領域で、便利に改善してくれる流れにも乗れる。例えば、今手動で行なっているNotebookLMのソースの更新も、将来的に自動化できる可能性があり、新しいツールや契約を増やさずに、できることが広がっていくという期待ができる。

これらを踏まえ、引き続きコストがかからず・安全に・コードベースで仕様確認ができるという点が決定打でした。

NotebookLMにソースコードを渡したいが、膨大なコードは「そのまま」では渡せない

NotebookLMにはソース1ファイルあたり約 50 万語という上限があります。

support.google.com

一方、YYCは長年培ってきた大量のコードを抱えており、様々なコードが入り混じっています。とても1ファイルに収まる規模ではありません。

NotebookLMのソースとしてプロダクトのコードをアップロードするには、「単にコードを集めるツール」ではなく「何を含めて何を含めないかを決める、適切な単位に分けて渡せるツール」が必要でした。

Repomixを使ってNotebookLM向けにコードを分割する

この問題を解決するために、Repomix を使って NotebookLM 向けのコードパックを生成しつつ、その分割方法を設計しました。

github.com

Repomix とは、リポジトリのファイル群を1つのテキストファイルに集約してくれるCLI です。includeやignore のglob指定、出力フォーマット選択(plain / xml / markdown)、コメント圧縮といった「LLM向けにコンテキストを渡す」前提の機能が揃っています。

まずは「どのコードを含めて、どの単位でファイルに分けるか」を整理することから始めました。実際の思考フローは、次のような順で進みました。

  1. 容量制限

    → ソース1ファイルあたり約50万語という上限を超えないようにする

  2. 不要コードの除外

    → 仕様確認に使わないコード(テストコードなど)は含めない

  3. 残すコードの判断軸

    →「仕様やSQLの質問に答えられる」を満たす範囲で取捨選択

  4. サーバーとクライアント両方の参照

    → 画面の情報を参照しつつ、裏側のサーバーの仕様も答えられる構成にしたいが、両方を1ファイルにまとめると上限を超えてしまう

  5. 分割方針の決定

    → サーバーは論理レイヤーで複数ファイルに切り分け、クライアントは1ファイルに収める

この結果、サーバー側5ファイル + クライアント側1ファイルとして書き出し、それらをすべて同じノートブックにまとめてアップロードする構成に落ち着きました。


設計はこれで決まったので、あとは実行できる形にまとめるだけです。

弊社ではMakefile にターゲットを作成し、ファイルを書き出す運用にしました。

以下は実際に Makefile に追加した内容です(一部抜粋)。

REPOMIX_OPTS := --style plain --compress
REPOMIX := npm run --silent repomix --
REPOMIX_DATE := $(shell date +%Y%m%d)

repomix: 
    @echo "==> server/Data層 (+本番スキーマ)"
    @$(REPOMIX) $(REPOMIX_OPTS) --output repomix-server-data-$(REPOMIX_DATE).txt \
        --include "Data層ファイル, スキーマ" \
        --ignore "***/**/*"
    @echo "==> server/Model層"
    @$(REPOMIX) $(REPOMIX_OPTS) --output repomix-server-model-$(REPOMIX_DATE).txt \
        --include "Model層ファイル" \
        --ignore "***/**/*"
    @echo "==> server/Handler+Site層"
    @$(REPOMIX) $(REPOMIX_OPTS) --output repomix-server-handler-$(REPOMIX_DATE).txt \
        --include "Handler+Site層ファイル" \
        --ignore "***/**/*"
    # ... 管理画面系、その他、クライアントと続く

分割の単位は以下の6つ

ファイル 含まれるもの
repomix-server-data-{date}.txt Data 層のソースコード + DB スキーマ
repomix-server-model-{date}.txt Model 層のソースコード
repomix-server-handler-{date}.txt Controller 層(リクエスト処理層)
repomix-backend-admin-{date}.txt 管理画面系のコード(複数の管理プロダクトを一括)
repomix-backend-misc-{date}.txt 上記以外のバックエンドコード(共通ユーティリティ等)
repomix-client-{date}.txt クライアント
💡 --compress と --style オプションについて

--compress
コード圧縮を有効にします。本質的な構造要素を抽出・保持しながら、実装の詳細を除外することで、LLM処理のためのトークン数を削減することができます。

--style
出力形式を指定できます。今回はNotebookLMが読みやすいplainで出力するようにしました。

実際の使い方

あとは make repomix で生成された6ファイルを NotebookLM のノートブックにドラッグ&ドロップでアップロードするだけです。

「メッセージを1通送るのに、ポイントはいくつ必要?」「足あとがつくのはどのタイミング?」「特定のユーザーのプロフィールを取得するSQLを教えて!」などの質問にコードを根拠にした回答が返ってきます。

まとめ

今回の取り組みで、Devinに頼っていた『コードベースの仕様確認』を追加コストなしで継続できるようになりました

ポイントは、NotebookLM のソース容量制限という壁を Repomix で突破できたことです。「何を含めて、どう分けるか」さえ一度設計してしまえば、あとは出力してアップロードという作業だけで、いつでも最新の状態に更新できます

また、Google Workspace 上で完結するため、アクセス管理も既存の仕組みをそのまま使えます。新しいツール契約や情シスとの調整なしに移行できたのも、大きなメリットでした。

NotebookLM 側ではソース自動同期に向けたアップデートも進められており、手動で行なっているソースファイルの生成も、いずれ不要になりそうな見込みです。

workspaceupdates.googleblog.com

こうしたエコシステム側の進化に合わせて、開発フローの中で自然に使い続けられる仕組みにしていきたいと思っています。

Diverseのジュニアエンジニア採用の魅力 ~ エンジニアをどう育てているか ~

こんにちは。Diverse developer blogです。今回は、Diverseで実施しているジュニアエンジニア採用について、どのような採用枠なのか、入社後にどう育成しているのかをご紹介します。

herp.careers

ジュニアエンジニア採用とは

ジュニアエンジニア採用は『業務でのエンジニア経験が未経験〜5年以内の方を対象にした採用枠』です。エンジニアとして今後成長できるポテンシャルのある方を、長期目線で育てていくことを前提にしています。

Diverseの自社マッチングサービス『YYC』は、累計登録ユーザー数が1,900万人を超えるプロダクトで、20年以上の開発・運営の実績があります。SNS、マッチング、ライブ配信の機能を持つこのプロダクトの開発チームに、サーバーサイドを主軸としながら他の領域にも挑戦していく、という育成方針でジュニアエンジニアは配属されます。

育成プログラムのステップ

入社から9ヶ月以降まで、段階的に目標を設定しています。後述するとおりDiverseに入社するすべてのエンジニアに共通するもので、記載している期間は目安です。

入社オンボーディング

働く上で必要なツールのセットアップと、Notionに自己紹介ページの作成をしてもらいます。最初の数日で、社内のコミュニケーションに参加できる状態を作ります。

試用期間中(0〜3ヶ月)

この期間は、社員とプロダクトの理解を最優先にしています。具体的には以下のような内容です。

  • 週次1on1
  • スクラムへの参加
  • プロダクトを実際に使う
  • 開発環境の構築
  • 簡単なスプリントタスクにトライ

いきなり大きなタスクを任せるのではなく、まずは「何を作っているチームなのか」「どんなユーザーが使っているのか」を知ってもらう期間として設計しています。

試用期間後(3〜9ヶ月)

プロダクトの仕様、事業目標とチーム目標の理解を深めてもらいます。社員のサポートを受けながら、スプリントタスクを自分で完了できるようになることを目指します。

9ヶ月以降

事業目標とチーム目標に関わる成果を出すフェーズに入ります。同時に、次のグレードへのスキルアップを目指してもらいます。

育成プログラムは研修か

カジュアル面談でよく聞かれる質問があります。「この育成プログラムは研修ですか?」という質問です。質問の背景には、早く現場に入って実務で自分を伸ばしていきたい、業務から離れた研修は避けたい、という気持ちがあるのだと思います。

先にお答えすると、この育成プログラムはすべて実務です。業務から切り離した研修プログラムではありません。週次1on1もスクラムへの参加もプロダクトを使うことも、Diverseのエンジニアが日常的にやっていることで、それに最初から加わってもらいます。

もう一つ重要な点があります。この育成プログラムはジュニアエンジニアのためだけに作ったものではなく、Diverseに入社するすべてのエンジニアに共通で受けてもらう内容です。

経験の浅い方は9ヶ月ほどかけて各ステップを進みますが、豊富な経験を持っている方であれば同じステップを1週間で終えることもあります。記載している「0〜3ヶ月」「3〜9ヶ月」「9ヶ月以降」はあくまで目安で、できる人は早く終わって次に進めますし、時間がかかる人はそれなりにかかる、というだけの話です。

なぜ経験を問わず、同じ育成プログラム(オンボーディングとも社内では呼んでいます)を受けてもらうのかというと、どれだけ業務経験があってもプロダクトの仕様や事業の特性、チームの雰囲気は入社してから理解する必要があるからです。プロダクトを実際に使う、スクラムに参加する、といった内容は経験豊富な方にとっても価値があり、むしろそういう方ほど早く吸収して次のステップに進んでいきます。

Diverseは1スプリント1週間のスクラムで開発しており、年間約48回のリリースサイクルを回しています(詳細は2023年の振り返り記事を参照)。この短いサイクルの中で、自分のペースで進んでいける設計になっています。

developer.diverse-inc.com

チーム開発の特徴

Diverseでは、Engineer・Designer・Biz・CS・QAメンバーが1つのチームとなり、スクラムで開発を進めています。

チームでは「なぜこの機能を作るのか」「どうすればユーザーに価値が届くのか」を議論します。ジュニアエンジニアとして入社した方も、このチーム議論に最初から参加します。実装だけを切り出して任せるのではなく、上流の議論にも触れてもらうことで、「何を作るべきか」を考える習慣を早い段階でつけてもらうためです。

開発環境

ジュニアエンジニアとして入社した方が触れる開発環境は以下のとおりです。

  • インフラ:AWS(ECS, Aurora MySQL8 など)
  • 構成管理:Terraform
  • 分析基盤:Looker / Re:dash
  • 監視:Mackerel / Sentry
  • CI/CD:GitHub Actions / CircleCI
  • その他:GitHub / Slack / Asana / Notion / Miro
  • 開発手法:スクラム開発
  • 言語やフレームワーク:Perl / Python(Lambda) / TypeScript(Vue) / Dart(Flutter)

サーバーサイドの言語にはPerlを利用しています。20年以上運営しているサービスですが、現在のYYCのバックエンドは最新のPerl5.40とDebian Bookwormで稼働しています。2024年にPerl5.8とCentOSから最新のLTSバージョンまでアップデートするプロジェクトを完了させています(詳細はこちらの記事)。

developer.diverse-inc.com

長年運営されているサービスと聞くとレガシー環境を想像される方も多いかもしれませんが、言語もOSも最新のLTSバージョンで稼働しており、新規で作るものはユースケースに応じてPythonやTypeScriptなども採用しています。

AIツールも、エンジニアはClaudeCodeやCodexを会社の経費で利用可能です。ジュニアエンジニアもAI使って開発することは可能です。

ジュニアエンジニア採用の実績

ジュニアエンジニア採用は2年半ほど前に募集を開始し、これまでに1名を採用しました。入社から2年が経過した現在、サーバーサイドエンジニアとして活躍しています

今後、ジュニアエンジニアとして入社する方は、この先輩社員をメンターにしながら一緒に働くことができます。同じ立場でスタートした社員が身近にいる環境は、「どうやって成長していけばいいのか」の具体的なイメージが湧きやすく、質問もしやすいはずです。育成ステップを実際に経験してきた人だからこそ伝えられる話があります。

メンターになる社員の経験談

ここからは、当社のジュニアエンジニアとしてどのような環境で働き、どのように成長していけるのかを、実体験をもとにお伝えします。


未経験の私がエンジニアとして活躍できるようになるまで

私は未経験からエンジニアとしてDiverseに入社し、現在3年目になります。以前はエンジニアとは全く異なる業界で働いていました。未経験からの転職では、「どんな環境でスタートするのか」が大きな不安になると思います。そこで、先ほどご紹介した「育成プログラム」の各ステップを、私が実際にどのように歩んできたのか。弊社でのリアルな成長のプロセスをお伝えできれば嬉しいです。

【STEP 1】試用期間中(0〜3ヶ月): 「ハテナ」だらけのスタートから、チーム開発の基礎へ

入社直後の私は、独学で少し触れていたもののターミナルのコマンド入力すらおぼつかない状態でした。環境構築や、文字を一行修正するだけのタスクでも時間がかかり、エンジニアとしてやっていけるか不安に感じていました。それでも、定期的な1on1やプロダクト理解のための説明会など、手厚いフォローがあったおかげで、まずは「何を学ぶべきか」を整理することができました。

入社2ヶ月目からは、管理画面のデザイン一新と改修を担当することになりました。実はこの時、エンジニアとしてこの改修をメインで担当したのは未経験の私一人でした。

「未経験の私に務まるかな?」と不安もありましたが、弊社には『Fail Fast, Go Higher — 大胆に試そう、成長しよう』という行動指針が根付いています。この価値観によって、気負いすぎず「まずはやってみよう」と前向きに取り組むことができました。

もちろん丸投げではありません。わからないことがあれば常に質問できる環境ではありますし、私が大胆に試せるようにしっかりサポートしてくださる安心感がありました。

一通りの実装を一人でやり切る責任感を早い段階で味わえたのは、この価値観を大事にする会社ならではの経験だったと思います。

【STEP 2】試用期間後(3〜9ヶ月): 膨大なコードに圧倒されながらも、ペアプロで一歩ずつステップアップ

入社半年が過ぎる頃には、実際のプロダクトに関わるタスクや、クライアントとのAPI連携なども任せてもらえるようになりました。

歴史のあるプロダクトゆえにコード量も膨大で、構造の理解に苦しむことも多かったです。そんな時は迷わず、先輩エンジニアに「ペアプログラミング」を依頼してタスクを進めることもありました。先輩エンジニアの実装の進め方を隣で見ることで、一人で悩んで進めるよりも何倍も速く成長できたと感じています。

また、自分一人では理解が難しい技術的なところでぶつかった時も、相談すると快く勉強会を開催してもらったりしました。個人の技術力ももちろん大事ですが、それ以上に「チームとしてどう成果を出すか」を弊社では大切にしているので、周りの知識をどんどん吸収して成長していくことを、みんながポジティブに応援してくれました。

【STEP 3】9ヶ月以降〜現在: 3年目の今、実感するエンジニアとしての面白さ

現在はマッチング機能だけでなく、ライブ配信機能などプロダクト内の多岐にわたる機能開発に携わっています。

弊社のプロダクトは非常に多機能なので、一つのサービスにいながら様々なアーキテクチャに触れられるのが、エンジニアとして大きな魅力です。

また、リリースした施策の結果を数値で共有してもらえる場もあり、自分の実装がどれくらいユーザーに届き、プロダクトを動かしているのかをダイレクトに知ることができます。

入社したての頃は「一行の修正」に必死でしたが、今では「プロダクトの成長を左右する機能」を任せてもらえるようになりました。自分のできることが増えていくにつれて、ユーザーやプロダクトに与えられる価値が日に日に大きくなっているのを実感できること。 それが今の私にとって、一番のやりがいです。

最後に、これから入社する皆さんへ

最初はわからないことだらけで当たり前です。私もそうでした。 弊社は、未経験でも早い段階から実際のタスクを任せてもらえ、自分の手でリリースまで一貫して経験できる環境です。複雑な仕様に頭を抱えることもありますが、それを乗り越えるたびに確実にレベルアップできます。

次は私が、皆さんが「大胆に試せる」ように全力でバックアップし、同じチームのエンジニアとして一緒に切磋琢磨し、高め合えるような関係を築いていきたいと思っています。皆さんと一緒に働ける日を楽しみにしています!

まとめ:ジュニアエンジニア採用で大事にしていること

育成にはそれなりにコストがかかります。先輩エンジニアが時間を割いて質問に答える、1on1を毎週やる、インプット期間中は大きな成果を求めない、といった投資が必要です。

このコストを払ってもジュニアエンジニアを採用している理由は、考え方の合う人材を中長期で育てていきたいからです。過去にジュニアエンジニアとして入社して成長したメンバーの実績もあり、先輩エンジニアは自分から聞きに行けば、基本的な質問でも答えてくれる文化があります。

また、Diverseには継続的な改善を怠らない姿勢があります。機能開発と並行しながらPerlとDebianのアップデートプロジェクトをやり切った実績からもわかるように、目の前の開発だけでなく、中長期でプロダクトと開発環境を良くしていくことを重視しています。ジュニアエンジニアとして入社した方にも、こうした改善の取り組みへ段階的に関わってもらいたいです。

逆に言うと、ジュニアエンジニアとして入社する方には『自分で開発したり、自分から聞きに行く姿勢』を期待しています。わからないことを放置せず、素直に質問できる方と一緒に働きたいと考えています。

最後に、ジュニアエンジニア採用は『未経験〜5年以内のエンジニアを、9ヶ月以降に成果を出せる状態まで育てていく採用枠』です。累計登録ユーザー数1,900万人・運営20年以上のプロダクトでスクラム開発に最初から関わりながら段階的にスキルアップしていくことができます。

ブログの内容やDiverseに興味のある方は、ぜひ以下の採用ページからカジュアル面談へ。様々なご意見をお待ちしています。

herp.careers

Figma MCP vs Pencilを実務で比較してみた話

こんにちは、Diverse Developer Blog です。

はじめに

YYCのフロントエンド開発では、Figmaデザインを基にしたUI実装が不可欠ですが、この工程には依然として多くの手作業が残っています。

近年、AIを使ってこの工程を効率化するアプローチが増えてきており、「どこまで実装を自動化できるのか?」が気になっていました。

そこで今回は、

  • Figma MCP
  • Pencil

の2つを対象に比較検証しました。

本記事では、「実装工数の削減」と「デザイン再現性」の観点から見えた、比較検証の結果を共有します。

ツールの特徴

まずは、比較する2つのツールの立ち位置を整理します。

Figma MCP

Figma公式が公開しているMCPサーバーを介し、LLM(Claude等)が直接Figma API経由でデザイン情報を取得する仕組みです。

  • 公式リポジトリ: https://github.com/GLips/Figma-Context-MCP
  • AIがFigmaデザインを読み取り、コード化するための参照用として設計
  • get_file(ファイル取得)、get_nodes(要素取得)、get_image(画像取得)など、デザイン情報取得に特化
  • 実行環境:クラウドベース

👉 既存のデザインをAIに参照させるための仕組み

Pencil (Pencil.dev)

Pencilは、AIエージェントとの連携を前提に設計されたデザインツールです。

  • 公式サイト: https://www.pencil.dev/
  • "Built for AI agents": AIによる操作を前提に設計
  • "Canvas as a Database": キャンバスが単なる画像ではなく、AIが編集可能なJSON構造データとして扱う
  • "Bidirectional editing": AIと人間が双方向に編集可能
  • 実行環境:ローカルファースト(.penファイル)、Gitで管理可能

👉 AIがデザインを読み取りつつ、直接編集・再構築できる環境

特徴まとめ(2026年4月現在)

観点 Figma MCP Pencil
役割 デザイン情報の参照 デザインの編集・再構築
AIとの関係 読み取り+書き込み 読み取り+書き込み
データの扱い API経由で取得 JSON構造データ(直接操作可能)
管理方法 クラウド ローカルファイル、Git
コスト Figmaプランに依存 無料

検証方法

実際のプロダクト開発で使用するFigmaデザインを対象に、以下の2パターンで検証しました。

パターンA:Figma MCP

  • Figmaデザインをそのまま使用
  • Claude Code(Opus 4.6)からMCP経由で参照し、コード生成

パターンB:Pencil

  • FigmaデザインをPencilに移行(ほぼコピペで再現可能)
  • Claude Code(Opus 4.6)からMCP経由でPencilデータを参照し、コード生成

対象

  • Webブラウザ(Vue3、SCSS)
  • モバイルアプリ(Flutter)

※ 同一UI・同一要件で比較

検証結果

Webブラウザ(Vue3、SCSS)

  • 両者ともに、UI構造はほぼ再現
  • 一覧画面のヘッダー・フッター固定なども、プロンプト指定なしで実装
  • 全体的にPencilの方がデザイン再現度は高い傾向
    • グラデーション
    • ボーダー有無
    • 余白やサイズなど細かいスタイル

一方で、以下の点は両者共通の課題として見られました。

  • アイコン画像の選定が不安定(生成ごとにブレる)
  • カラー変数・スタイル定数の参照が不安定

モバイルアプリ(Flutter)

  • 両者ともに、UI構造はほぼ再現
  • ベースのFigmaデザインのコンポーネント階層も再現した実装
  • Figma MCPの方がデザイン忠実度が高い結果に

また、Webと同様に以下の傾向が見られました。

  • アイコン画像の選定が生成ごとにブレる
  • カラー変数・スタイル定数の参照が安定しない

初回生成後の修正フロー

スタイルやアセット参照の課題については、プロンプトの工夫やskillsの整備により、おおよそ8割程度の完成度までは安定して出力可能になりました。

その後、主に以下の修正を行っています。

  • コンポーネント構成の最適化 (Figmaデザインと実装の設計差分や、エンジニアごとの粒度差による調整)
  • スタイルの微調整
  • ビジネスロジック(JS/Dart)など動的処理の実装

まとめ

再現性について

再現性は以下の3軸で評価しました。

  • UI構造(レイアウト・階層)
  • スタイル(色・余白・装飾)
  • アセット参照(画像・アイコン・変数)

その結果、

  • UI構造:両者とも高精度
  • スタイル:
    • Web → Pencilが優位
    • モバイル → Figma MCPが安定
    • リスト型のようなシンプルなUIでは差は小さい
  • アセット参照:両者とも不安定

現時点では「構造の自動生成」には極めて強いものの、「詳細なスタイリング」と「プロジェクト固有のアセット参照」には、人間によるコンテキストの補完(プロンプトでの補足や、skillsの整備、デザインシステム側での命名規則の徹底)が不可欠です。

実装工数削減について

今回の検証では、実際にプロダクトへ組み込む前提で評価しました。

  • どちらも生成コードをそのまま使うのは難しい
  • 特に以下の修正が必要になるケースが多い
    • アイコン差し替え
    • カラー変数の置き換え
    • コンポーネント分割・整理

一方で、UIの骨組みはほぼ完成しているため、ゼロから実装するよりは確実に工数削減になりました。

体感としては、

  • 50~80%程度の工数削減効果は見込める
  • “完全自動化”にはまだ届かない

という位置付けでした。

評価項目 Figma MCP Pencil 備考
UI構造の再現 両者ともに精度が高い
スタイル再現(Webブラウザ) ⚪︎ PencilのJSON構造がCSSと相性良
スタイル再現(モバイルアプリ) ⚪︎ Figmaの階層構造がFlutterと親和性高
アセット参照 アイコン画像やスタイル変数の紐付けに課題
導入の容易さ ⚪︎ Pencilは移行コストが発生
工数削減(体感値) 50~
80%
50~
80%
下書き生成ツールとして極めて優秀

最後に

アウトプットの品質自体に大きな差はありませんでしたが、ワークフローの観点では差が見られました。

  • Pencilはエディタ上でデザインを開きながら、AIと対話し修正できる
  • デザインをコードと同様にバージョン管理できる

これらを踏まえると、Pencilが無料である現時点では、品質・スピード・ワークフローに置いてメリットが大きいと感じました。

一方で、デザインデータの扱いについては運用設計が重要になります。 当社としても、将来的にPencilをマスターデータとして扱うことは現実的な選択肢の一つだと考えていますが、現時点では段階的な移行が前提になると認識しています。

特に、デザインレビューの進め方やデザイナー・エンジニア間の役割分担といった既存のプロセスを変えずにツールのみを移行すると、データの二重管理や認識の不一致が発生しやすくなります。

そのため当社では、まずはFigmaをデザインの正としつつ、Pencilを実装領域で活用する二層構造の運用を採用し、今後は新規開発領域から段階的にPencil中心のワークフローへ移行していく方針が現実的だと考えています。

出会いの「温度感」をマッチング検索に乗せるために Two-Tower Model と Solr KNN でやったこと

こんにちは、Diverse Developer Blog です。

今回は、YYC のマッチング検索に、SageMaker 上で学習した Two-Tower Model のユーザー Embedding を使ったベクトル検索 (Solr KNN) を追加した話をします。

チャンスタイムやデイリーミッションといった既存のルールベースの行動導線は引き続き動かしつつ、「自分と温度感が近い相手」を ML ベースの検索で並行して返せるようにする、という構成です。

TL;DR

  • これまで YYC のマッチング機会は、チャンスタイム(新規登録直後)やデイリーミッション(継続期)といったルールベースの行動導線で設計していました
  • ただ、ユーザーの目的が多様化するにつれて「全員一律の導線」では受け止めきれない部分が出てきたため、温度感 という軸を決定論的に定義し、それを教師情報として 64 次元のユーザー Embedding を学習する仕組みを追加しました
  • 学習は Amazon SageMaker 上の Two-Tower Model で行い、推論結果は Solr の Dense Vector フィールドに投入、KNN 検索で「温度感の近い相手」を返せるようにしています
  • 既存のルールベース導線とは置き換えではなく併用で、検索結果の一部を ML 側から差し込む形で運用しています

1. これまでの設計

YYC には、ユーザーのライフサイクルに応じた行動導線がいくつかあります。

  • チャンスタイム(新規登録直後): チュートリアル完了時に時間制限で、一定時間のあいだプロフィール閲覧やメッセージ送信が無料になる施策です。初動のアクションを後押しして、最初の 1 通までの距離を縮める役割を担っています。
  • デイリーミッション(継続期): 一定の条件で抽出された相手に対してあいさつ(初回メッセージ)が無料で送れる仕組みで、日々の行動を後押しして継続利用につなげる役割を担っています。
  • 検索ロジックのルールベースの並び替え :「新規ユーザー優先」「最終ログインが近い順」など、経験則ベースの並び替えです。

いずれも「誰にどの動線を踏ませるか」をルール側で先に決め打つ設計で、登録直後はチャンスタイム、その後はデイリーミッションという役割分担になっていました。

ルールベースの強み

先に書いておくと、ルールベースの設計自体に問題があるわけではありません。数年単位で YYC を支えてきた土台で、以下のような強みがあります。

  • 説明可能性が高い: 「なぜこの動線が出たか」をコードと config で完全に説明できます。運営や CS からの問い合わせにもすぐ答えられます。
  • 即効性がある: 新しい施策を思いついてから数日で本番に出せます。
  • 数値設計が容易: 送信上限や有効時間などのパラメータを A/B で直接調整できます。
  • 障害耐性が高い: 壊れても原因が特定しやすく、切り戻しも簡単です。
  • 運用知見が蓄積しやすい: 長年の施策で「どの数字を動かすと何が起きるか」が体感値として溜まっています。

新規プロダクトや、シグナルが十分に溜まっていないフェーズでは、ルールベースは今でも最短ルートだと思います。

見えてきた課題

一方で、ユーザー層と目的の多様性が広がってくると、次のような課題が見えてきました。

  • ユーザー多様性への追従が遅い: 新しい利用目的や層が出てくるたびに、ルールを増やすか既存ルールを改修するしかありません。
  • 個別最適ができない: 全員一律のルールなので、同じ温度感を持つ人同士をピンポイントで繋ぐことができません。
  • トラブルの事後学習ができない: ブロックや通報があっても、ルール側に自動で反映されるわけではなく、改善のたびに人間が仮説を立てて手を動かす必要があります。
  • 初動と継続で導線が分断する: チャンスタイム(新規)とデイリーミッション(継続)の接続が、ロジックとしても体験としても連続しません。
  • ルールの複雑化: これが一番重くなっていく課題です。

ルールの複雑化についてもう少し書くと、

  • 例外が例外を呼ぶ: 「このユーザー群だけ除外」「この時間帯だけ特別扱い」「このアプリバージョン以降はこう」といった分岐が、一度入ると消せずに積もっていきます。
  • ルール同士の相互作用が読めなくなる: 単体では正しいルールが、他のルールと組み合わさることで予期しない動作をします。
  • 変更のリスクが非対称: 新しいルールの追加は安全に見える一方、既存ルールの削除や改修は副作用が読めず怖くなり、結果として削除されないコードが溜まり続けます。

ルールベースは初速こそ速いのですが、メンテコストは時間とともに増えていきます。ある時点からは、新しい施策を入れるより「既存ルールと衝突しないか確認する」ほうが時間を食うようになります。

ユーザーの目的が多様化するほど、全員一律の導線では受け止めきれない領域が広がってきたので、ここを ML 検索で補完することにしました。


2. 温度感を 1 次元スコアとして定義する

まず取り組んだのは、モデルに学習させる前に「温度感とは何か」を自分たちで定義することです。

ユーザーの「出会いに対する姿勢」は、実は 1 本の軸にかなり綺麗に乗ります。

  じっくり  ←─────────────────────────────→  カジュアル
   0.0                                        1.0
  • 左寄り: じっくり話したい、ゆっくり関係を築きたい層
  • 中央: 一般的な出会い目的の層
  • 右寄り: 気軽につながりたい、カジュアル志向の層

さらに、それぞれのユーザーには「自分のスコア位置」だけでなく「どの範囲の相手なら噛み合うか」という希望レンジもあります。ざっくり書くとこういうイメージです。

   じっくり                                            カジュアル
    0.0                                                   1.0
     ├──────────────────────────────────────────────────────┤

       ◆                                                       ← ユーザー A
       ├───────┤  A の希望レンジ

               ◆                                               ← ユーザー B
          ├───────────────┤  B の希望レンジ

                             ◆                                 ← ユーザー C
                        ├──────────────────────────┤  C の希望レンジ

ポイントは、希望レンジがユーザー (カテゴリ) ごとに形が違うという点です。左寄りの層は同じ左寄りの相手を狭めに希望することが多く、中央・右寄りの層は逆に広めのレンジを持ちやすい、といった非対称な分布になります。実際にはこれをもっと細かい複数カテゴリに分けていますが、構造としては同じです。

この軸を決定論的なロジックとして実装しています。

スコア算出のしくみ

  1. フラグ優先判定: 悪質行為フラグや属性フラグなど、確実にカテゴリが決まる条件を最優先で判定します。
  2. 目的ベーススコア: フラグに該当しないユーザーは、プロフィールの「出会いの目的」から平均スコアを算出します。
  3. 希望レンジ: 自分のスコアに対して「どの範囲の相手なら噛み合うか」を希望の中心値として定義します。

この段階ではまだ機械学習は出てきません。プロフィール・属性・目的コードから、決定論的に scorematch_center の 2 値を出しているだけです。

ただ、この 2 値が後段の学習モデルの教師情報になります。


3. Two-Tower Model で 64 次元に埋め込む

次に、各ユーザーを 64 次元のベクトル (Embedding) に変換する学習パイプラインを、Amazon SageMaker 上に構築しました。採用したのは、推薦系で定番の Two-Tower Model です。

   [User Tower]                [Target Tower]
        │                            │
        ▼                            ▼
   64 次元ベクトル ──コサイン類似度──▶ マッチングスコア
  • User Tower と Target Tower は同じ構造の別重みです
  • 入力は user_id / sex / score / match_center の 4 カラムのみ
  • 出力は 64 次元の密ベクトルです

なぜ学習させるのか

ここで「scorematch_center が決定論的に出るなら、その 2 値でソートすればいいのでは?」と思われるかもしれません。

実は、Embedding の先頭 2 次元はスコアをそのままピン留めしているので、そこだけ見ればロジック単体でも再現できます。ML を使う価値は、むしろ残りの 62 次元のほうにあります。

この 62 次元は実際のユーザー行動 (特にブロック) から学習されるので、

  • 温度感スコアが同じでも、「実際にはブロックされがちな特徴」を持つ人は遠ざけられます
  • プロフィールに明示されていない共通点で近い人同士は、自然と近くなります
  • 過去の行動パターンから、「この相手は希望に合いそうか」という期待度合いを織り込めます

静的な属性ベースのスコアリングに、行動データから学習した「振る舞いの近さ」を重ねる、ということです。先頭 2 次元で説明可能性を担保しつつ、残り 62 次元で汎化を稼ぐ、という役割分担になっています。

損失関数

学習の損失関数は 2 つの項を組み合わせています。

1 つ目は、推薦系で標準的に使われる InfoNCE です。バッチ内のペアを正例・負例としてコントラストさせ、良い組み合わせは近く、悪い組み合わせは遠くなるように学習させます。これに加えて、後述するブロックを明示的な負例として投入しています。

2 つ目は、Embedding の先頭 2 次元を scorematch_center に誘導する MSE 項です。解釈可能なスコアがあるので、それを Embedding の先頭次元にそのまま載せてしまおう、という発想です。これによって、

  • 「なぜ近いのか説明できない」マッチングを返しにくくなります
  • 温度感レンジでのフィルタリングを、ベクトル検索側で低コストに再現できます

ブロックを負例として使う

ユーザー間の明示的なブロックは、マッチングでは強い負例シグナルです。学習パイプラインでは、直近 90 日のブロックを weight = -1.0 の負例インタラクションとして投入しています。

ただし、この負例が担っているのは「ブロック当人同士を離す」ことではなく、「ブロック相手に似た別人を遠ざける」ことです。

ブロック関係にある当人同士は、Solr の検索フィルタ側で確定的に除外しているので、設計としては二層になっています。

担当 効果
Solr の検索フィルタ 確定フィルタ ブロック当人同士は 100% 出ない
負例 -1.0 ML 学習 ブロック相手に似た別人を Embedding 空間で遠ざける

フィルタで当人は既に落ちているので、ML 側の負例は「似た傾向の他人がまた現れる」事故を確率的に減らす役割に専念できます。片方が外れても最低限の安全性は担保されます。


4. 推論結果の配信(Solr KNN)で配信する

学習した Embedding は Solr の Dense Vector フィールドに投入し、KNN 検索で「自分のベクトルに近いユーザーを k 件返す」形で配信しています。

既存の属性フィルタ (性別・年齢・エリア・ブロック除外など) は Graph Pre-Filter として併用できるので、ベクトル検索と条件検索のハイブリッドになります。


5. 何が変わるのか

今回追加した温度感ベクトル KNN 検索と、これまでのルールベース検索との違いは次のとおりです。

観点 これまで (ルールベース) 追加した ML 検索 (温度感ベクトル KNN)
推薦の基準 新規ユーザー優先・最終ログイン順などの並び替え ユーザー同士の温度感の近さ
新規目的への対応 ルール追加や改修 プロフィール更新 → 次回学習で反映
トラブル抑止 人手でルールを足す ブロック負例でモデルが学習
説明可能性 ルールを辿れば説明可 先頭 2 次元にスコアを固定して担保
検索性能 属性 INDEX 頼り Solr KNN で高速ハイブリッド検索

6. 直近のネクスト: 数字を見ながら ML 側に寄せていく

ここまでで「決定論的スコア + Embedding ベースの類似検索」という土台はできましたが、現時点では既存のルールベース導線と ML 検索を併用しながら効果を計測している段階です。マッチング検索全体を ML 側に寄せるかどうかは、数字を見ながら判断していくことになります。

直近のネクストは、

  1. マッチ成立率、メッセージ継続率といったプロダクト側の KPI で ML 検索の効果を計測する
  2. 結果を見ながら、学習データ・特徴量・損失設計を改善していく
  3. 数字が安定して良くなったところで、ML 検索の比率を上げていき、最終的にはマッチング検索をルールベース側から ML 側に寄せていく

という流れを想定しています。ML モデルは学習データや特徴量を入れ替えれば継続的に改善できるので、しばらくはルールベースと併用しながら、数字を見て少しずつ振り方を ML に寄せていく構成で考えています。

その先の改善余地としては、以下のような項目も見えています。

  • シグナル設計の余地: 現状はブロックを負例として使っているのみで、ポジティブな行動 (メッセージ継続、マッチング成立など) をどう反映させるか?
  • コールドスタート: 行動ログがない新規ユーザーに向けてどう最適化するか?
  • 再学習サイクル: 現状は日次バッチですが、更新頻度と計算コストのバランスをどう取るか?

まとめ

今回は、出会いの「温度感」を マッチング検索に乗せるために、Two-Tower Model と Solr KNN でやったことを書きました。

  • 温度感を決定論的なスコアとして定義した上で、それを教師情報として Two-Tower Model を学習させ、Solr KNN で配信する構成にしました
  • Embedding の先頭 2 次元にスコアをピン留めすることで、説明可能性を保ちつつ、残り 62 次元で行動データからの学習を効かせています
  • ブロックは確定フィルタ (Solr) と ML 負例の二層で効かせる設計にしました
  • ML 検索の効果はまだ計測中で、既存のルールベース導線と併用しながらチューニングを続けていく予定です

FlutterKaigi 2024に参加してきました

はじめに

昨年に引き続き、今年もFlutterのカンファレンス、FlutterKaigi2024に参加してきました。

今年は11月21日と22日の2日間にわたり開催され、例年以上に多くのセッションに参加することができました。

会場は有明セントラルタワーホール&カンファレンスで、セッションは2つのホールで行われました。スポンサーブースも2箇所に設置され、会場は非常に賑やかでした。

今年もFlutter製でFlutterkaigiのアプリが作成されており、リポジトリが公開されているのでこちらからも多くの学びが得られそうです。

github.com

どのセッションも非常に学びが多く素晴らしいものでしたが、中でも特に印象深かったセッションについての感想を綴ろうと思います。

印象に残ったセッション

出前館アプリにおけるFlutterアプリ設計とそれを支えるCICD環境の進化

元々React Nativeで作成されていた出前館のクライアントアプリをflutter化するにあたり、どういったフォルダ構成やアセット管理また状態管理をどのように行うかの発表がなされていました。

まずフォルダ構成として

  • Type/Domain
  • Feature
  • Package

この三つが候補としてあげられており、プロジェクトの規模等を考えてFeature型を採用されたようでした。弊社も元々はType/Domain型で運用しておりましたが、プロジェクトの規模が大きくなるにつれファイルを検索しずらい等の問題が発生したためFeature型に移行した経緯があります。

アセット管理にはflutter_genを使っているとのことでした。BuildContextをextensionしてcontext経由でアセットへアクセスしているという工夫もお話しされていました。弊社でもflutter_genを採用しており、flutter_genが自動生成するSvgGenImage, AssetGenImageをextensionしてなるべく不要な引数やデフォルト引数を設定することでアセットへのアクセスを簡易にする工夫をしています。アセット管理に関するベストプラクティスについて、本発表を機会に今後もっと追求していければと思いました。

状態管理にはBLoCを状態を示すStateにはFreezedを採用されているとのことでした。BLoCについては未経験だったためどういった使い方をするのかを知れてよかったです。Freezedは弊社でも採用しており利便性の高さは共感するところでありました。

最後にCI/CD環境についてお話しされていました。当初はBitriseを採用するところから始まったというお話しからはじまり、BtoBアプリのflutter化に追従する形でTeamCityへ移行されていったというお話しをされていました。複数チームが存在する中で、オンプレ環境でバージョンの異なるビルド環境でCI/CDをしていくことで発生する苦労があることを知れました。

発表概要

https://2024.flutterkaigi.jp/session/b57f3efd-b31d-4d57-aaa3-f45f1b17f7272024.flutterkaigi.jp

発表資料

speakerdeck.com


気をつけたい!Desktop対応で陥りやすい罠とその対策

本セッションではDesktop対応をするにあたり事前に知っておかなければ陥ってしまう特有の罠とその対策についてお話しされていました。Desktop対応とありますが、Webも含む広義の意味で使用されています。なのでPadやブラウザ等のマルチデバイスでのレスポンシブ対応における内容も含まれていました。

まず文字入力時の文字数制限がアプリでは問題なかったものがWebだと入力言語を切り替えた際に制限できなくなる現象についてお話しされていました。こちらはTextFieldのmaxLengthEnforcementにenforcedを指定することで解決できるようでした。知らなければ絶対に陥ってしまう罠なので本発表でTipsを知れてよかったです。

次にスクロール可能なWidgetがWeb対応することによってできなくなる現象についてお話しされていました。こちらはマウスによるドラッグを有効にするプロパティを明示的に指定することで解決されたとのことです。アプリ版のみであればタッチによるドラッグしか考えなくてよいですが、デスクトップ等の対応をする場合はマウスによるドラッグも念頭においておく必要があることが知れました。

次に画面レイアウトに関わるレスポンシブ対応についてお話しされていました。 BottomNavigationBarをWebで利用するとタブ間のすき間が大きくなり使いづらくなってしまうため、左側にメニュー一覧を表示することができるNavigationRailを採用されたとのことでした。Material3のWindow size classesを基準にBottomNavigationBarとNavigationRailを切り分けていました。

またNavigationRailの最下部にメニューを表示したいというデザイナーさんからの要望を実現するためにtrailingプロパティを利用して実現しようとしたが、想定していたものと全く違う表示になってしまい、それでも実現するために試行錯誤した結果コードがとても複雑化してしまったようです。 そこでデザイナーさんに相談した上でデザインを調整してもらい解決できたようです。この事例のようにflutter側では解決できないこと・解決しようとするととても複雑になってしまいメンテナンス性が損なわれてしまう場合には相談してデザインを変えることで解決するということは実際の現場では大切なことであると感じました。

最後にWeb特有の課題についてお話しされていました。アプリほどには成熟していないDesktop領域では実装してみると様々な課題にぶちあたることになり、その際にどういった行動をとればいいのかが明確になりました。

  • flutterのissueを検索する
  • Widgetのプロパティを詳しくみてみる
  • 公式のドキュメント、MigrationGuideをちゃんと読む

とても基本的だとされていることですが、ちゃんとできてないことも多いので、本発表を期に改めて意識したいことだと思いました。

発表概要

https://2024.flutterkaigi.jp/session/6cda6895-57f8-47dc-ab29-ebcbd9c7f95a2024.flutterkaigi.jp

発表資料

speakerdeck.com


マッチングアプリ『Omiai 』のFlutterへのリプレイスの挑戦

Omiaiからは長年iOS, Androidそれぞれのプラットフォーム言語で開発されてきたアプリを技術的負債や仕様差異を解消するためにflutter化により解消していくお話しでした。リプレイス中にも既存アプリの機能追加は止めたくないということでAdd-to-Appによるアプローチをとっていたのは、弊社でも同様の経験があり当時のことを思い出させてくれるようでした。

リプレイスするにあたりマンパワーに依存することにより発生するコード規約違反や技術的負債の発生に対する対策として

  • 依存関係を明確にするためマルチパッケージ構成をとる
  • ユニットテストを十分に継続的に書く
  • 実装内容をパターン化し新規参入者に対する学習難易度を下げる

といったことを徹底されているようでした。

依存関係が明確にされていることで、依存に関する誤った実装がそもそもできないというように仕組みで解決されていたので素晴らしいと思いました。また各レイヤーでの責務が明確化されておりユニットテストを書くのも容易になったということも学ぶことができました。

コードへのドキュメーションコメントを書くのも必須にしており、そのチェックにpublic_member_api_docsというlintルールを活用していました。コメントを必須にすることによってソースコードが「実行可能な仕様書」にしているというのが興味深かったです。

またVS Codeのスニペット機能をかなり活用していたのも興味深かったです。 これを機に弊社でもスニペット機能は活用していこうと考えております。

発表概要

https://2024.flutterkaigi.jp/session/369e19d9-1f27-40e6-b294-64c6ca9bfcae2024.flutterkaigi.jp

発表資料

www.slideshare.net


アニメーションを最深まで理解してパフォーマンスを向上させる

弊社のプロダクトでも各所にアニメーションを使っていますが、flutterでのアニメーションは若干特殊なところもあり、正しい実装なのか、他の代替実装やライブラリはあるのか、改善できる要素はあるのかなど、知見を増やすことを目的に本セッションに参加しました。

本セッションでは、flutterにおけるアニメーションの最新動向や入門解説、アニメーションの詳細な説明まで幅広く解説した、本セッションでflutterのアニメーションに関する知識を網羅的に学べる内容でした。

特にパフォーマンスの計測と分析のセクションでは、1000個のロゴを回転させたアプリを例に、パフォーマンス低下の原因とdevtoolsを使った改善方法の解説はとてもわかりやすく学びが多かったセッションでした。

発表概要

https://2024.flutterkaigi.jp/session/4156b633-8043-4d94-8da8-39c367088c592024.flutterkaigi.jp

発表資料

speakerdeck.com


実践的パッケージ戦略

弊社でも、既存プロダクトのマルチパッケージ化を検討しており、ぜひ知見を得たいと思い、本セッションに参加しました。

本セッションではクリーンアーキテクチャを例に、riverpodを使った簡単なコードで説明するセッションでした。

まず3つのレイヤーを使ったシンプルなプロジェクトを例に解説していました。

次に上記の実装を依存性逆転の原則(DIP)に従い、以下のように依存関係を変更した実装の手順を説明をいただきました。

依存関係を逆転すると、当然直接モジュールを参照していたところは壊れるため、

下位になったUseCaseは依存してたモジュールを使用する際に、インターフェースを追加してそれを介して呼ぶように修正します。

上記になったGatewayは、UseCaseのUserRepositoryの具象クラスを実装して、それを返すproviderを更新します。

最後に、AppのProviderScopeのoverridesに更新したproviderを設定して完成です。

しかし、依存してるモジュールの数が増えていくと、都度overridesに追加設定していく必要がでてくる問題が発生します。

そこで、Kyoheiさん自身が作成した override_pod というツールを使うことで、build_runnerで自動生成して、前述の問題を解決できるようです。

今回のようにレイヤーごとにパッケージに分けて、マルチパッケージな構成するにあたり、melosというツールの紹介がありました。melos をつかったマルチパッケージプロジェクトでそのままtestを実行すると、期待した正しいカバレッジが得られないことがあるため、Kyoheiさん自身が作成したlcov_excluderというツールを使って除外設定することで、melosと併せて使うと便利とのことでした。

普段業務で、依存関係をひっくり返すようなことはする機会がなかったですが、実際今回の発表を例にriverpodでの解決方法は非常に興味深く学びの多かったセッションでした。

発表概要

https://2024.flutterkaigi.jp/session/5fa4f6aa-47ef-40d9-b6bb-216493ffc1b12024.flutterkaigi.jp

発表資料

speakerdeck.com


Shorebirdを活用したFlutterアプリの即時アップデート:Code Pushの実践と可能性

Shorebird のCode Pushとは、Flutterをforkしたプロダクトで、Google Play StoreやAppStoreのアプリ審査フェーズをスキップして、Flutter アプリの変更差分をユーザに反映することができる夢のようなツール(サービス)です。

アプリリリースするまでに、必ず審査を通過しなくてはいけませんが、その審査結果はまちまちで、早くても半日〜1日かかってしまって、早くユーザに届けたいプロダクト側はいつももどかしい思いをした経験はあるのではないでしょうか?

その問題をこのShorebirdのCode Pushを使えば解消できる!?というものでした。

ただし、当サービスを利用するにあたって若干制約があるようでした

  • flutter v3.24.0以上の環境が必要(意外と高い)
  • 本家flutterでビルドしたアプリは不可
  • dartコードの差分のみ適用可能(assetの追加削除やネイティブコードの差分が含まれる場合は通常通りの各ストアのリリースフロー)
  • 各ストアのガイドラインには準拠(当然)
  • ユーザ側はアプリを2回起動が必要

料金プランは、従量課金制で5,000パッチ(1端末1パッチ計算)までは無料プランで使用可能なので、まず気軽に使い始めてもよさそうですね!

発表概要

https://2024.flutterkaigi.jp/session/dd12ee26-4d87-4081-a01a-621411231a942024.flutterkaigi.jp


Flutterアプリで可用性を向上させたFeatureFlagの運用戦略とその方法

トランクベース開発を実現するための仕組みであるFeature Flag に関するセッションで、実際にプロダクトで直面した課題と、それを踏まえた実装方法や運用について解説されていました。

弊社でもFeatureFlagを導入しており、発表前からとても注目していたセッションの1つでした。

FeatureFlagは新機能の公開/非公開をリリース後でも簡単に切り替えができる仕組みで、これにより、以下のようなメリットが得られます。

  • 開発中の機能をマージすることができる
  • PRの粒度を小さくできる (コンフリクトを抑える、レビューコストの軽減)
  • リリース後に大きな問題が起きてもすぐにロールバックできる (Firebase RemoteConfigなどとの連携)
  • ABテストの実施が容易になる

発表の中で特に印象的だったのは、UI層でのFeatureFlagの扱い方で、FeatureFlagをラップしたFlagBuilderというWidgetを用いているという点です。

FlagBuilder は、onBuilder と offBuilder という2つのコールバックを持ち、引数に渡した FeatureFlag の値に応じて、それぞれのコールバックが呼ばれる仕組みになっているようです。

if 文や三項演算子を使って条件分岐を行うと、分岐が散らばったり、可読性が低下することがありますが、FlagBuilder 内に分岐を集約することで、この問題を解消できている点が非常に良いと感じました。

また、既存の実装に関しては、コピペで offBuilder に残す運用を採用することで、デグレの発生を抑える取り組みが行われているとのことでした。

ロジック面においても、Feature Flag による制御を基本的に domain レイヤー(UseCase) のみに制限することで、条件分岐が分散しないようになっているようです。

Danger を使ってレビュー時に機械的にFeature Flag の制御を忘れに気づけるようにしている点も非常に参考になりました。

発表概要

https://2024.flutterkaigi.jp/session/454605d6-b1b5-40f4-85c6-1636a6c32b1a2024.flutterkaigi.jp

発表資料

speakerdeck.com


DevTools Extensions で独自の DevTool を開発する

DevTools Extensions を使って独自のデバッグツールを作成する方法について、ライブコーディングを交えてとても詳しく解説されているセッションでした。

(NeoVimを使いこなしていてめちゃくちゃかっこよかったです 笑)

まず、 DevToolsに独自の機能を追加できることを知らなかったので、その点に驚きました。

セッションでは、DevTools Extensions を活用した具体的なツールとして、以下の3つが紹介されていました。

  • GraphQL Cache Inspector
    • GraphQLClientのキャッシュを可視化
    • 正規化されたTree構造で出力
  • Loading State Toggle
    • Loading状態のUIへの切り替え
    • Skeleton などのデザイン確認に使用
  • Dio Logger Toggle
    • Http通信のログ出力を ON/OFF
    • ログの追跡を容易にする

dev toolの作り方は下記の3ステップで比較的簡単に作成できるようです。

  • config.yamlの作成
  • Devtools extensionsのパッケージを作成
  • Flutter webでbuild

GraphQL Cache InspectorはReactのツールを参考に作成したとのことで、

どんなツールを作成するかのアイディアはWeb界隈のツールを参考にするのもオススメとのことでした。

DevTools Extensions を活用することで、アプリ内にデバッグ機能を埋め込むことなく、開発中のデバッグや日々の開発がより快適に行えるのは非常に嬉しいポイントですね!

発表概要

https://2024.flutterkaigi.jp/session/83119f09-e90c-468b-9a5e-69d2df646dfb2024.flutterkaigi.jp

発表資料

speakerdeck.com


Effective Form ~ Flutterによる複雑なフォーム開発の実践 ~

ユーザーからの入力を受け取るためのインターフェースであるフォームの実装方法や注意点について詳しく解説されたセッションでした。

まず、例として挙げられたのはログインフォームです。

このフォームでは、EmailとPasswordを入力値として受け取り、Loginボタンが表示される比較的シンプルな画面が想定されています。

フォームのフローは以下の通りです。

  1. UIの表示
  2. 入力状態の管理
  3. バリデーション
  4. Dirty管理
  5. Submit

特に注目したいのが、4番目のDirty管理です。

EmailのTextFieldにシンプルなバリデーションを適用した場合、初期状態でいきなりInvalid状態になってしまうという問題が紹介されました。

Dirty管理は、こうしたケースでバリデーション結果をユーザーにフィードバックするタイミングを適切に制御するための考え方とのことです。

Dirty管理のポイント

  • 入力フィールドが変更されたタイミングを追跡
  • ユーザーが変更を確定したタイミングで「dirty状態」と見なす
  • エラー表示や変更確認ダイアログの表示タイミングを管理できる

このDirty管理を実現する方法として、formzというパッケージが紹介されました。

formzパッケージには、FormzInputという抽象クラスが用意されており、入力値を「pure」と「dirty」の2つの状態で管理できるようです。

TextFieldのonChangedでpure状態に、onSubmittedでdirty状態に切り替えることで、Dirty管理を実現できます。

また、validatorメソッドをオーバーライドすることで、バリデーションロジックを実装できます。

displayErrorの値を使うことで、dirty状態の場合のみエラーを表示することができ、エラー表示のタイミングを柔軟に制御できる点が非常に便利ですね!

さらに、より複雑なフォームにおける実装課題とその戦略についても解説がありました。

  • 管理すべき状態が多い → Single Source of Truthの担保 (状態の一貫性が担保できる)
  • 状態更新による多彩な動的制御が必要 (状態が決まれば挙動が決まる)
  • 大量の機能と複雑な依存 (状態の管理範囲が最小化される)

弊社のアプリでも複数の場所でフォームを使用しているため、Dirty管理や設計戦略を踏まえて改善に活かしていきたいと思います。

発表概要

https://2024.flutterkaigi.jp/session/c56e7002-02e3-44d5-825c-b64934d122882024.flutterkaigi.jp

発表資料

speakerdeck.com

最後に

冒頭でも触れましたが、今年は2日間にわたる開催で、内容が非常に充実していました。 どのセッションもとても多くの学びがあり、参加者の皆様の熱意にも多くの刺激をいただきました。 登壇者の皆様、そして運営スタッフの皆様、本当にありがとうございました。 来年のFlutterKaigiも楽しみにしています。

YYCのバックエンドをPerl5.40とDebian Bookwormへ更新したプロジェクトを振り返る

こんにちは Diverse developer blogです。今回はプロジェクトの構想から完了まで、1年半ほどかけて行った「PerlとDebianの更新プロジェクト」を振り返ります。

なぜやったのか?

弊社のYYCは20年以上稼働しているサービス(SNS, マッチング, ライブ配信)です。ユーザーの要望に応えるため、機能開発を優先してきたことで開発環境の改善が遅れていました。

特にバックエンドの開発言語(Perl 5.8)と、コンテナOS(CentOS)のアップデートが遅れており、今後の機能開発やセキュリティ対応に課題が生じていました。

そのため、プロジェクトの目標は「開発言語とコンテナOSをLTS(long-term support)バージョンまで更新する」ことにしました。そして、課題を一つ一つ段階的に解決して、ついに目標を達成しました。現在のYYCのバックエンドは、最新のPerl5.40とDebian Bookwormで稼働しています。

どうやったのか?

ここらはプロジェクトの進行を時系列順に紹介します。

1. 段階的リプレイスで失敗した

プロジェクト当初は、当時のYYCとは別にYYC v2を開発して画面や機能ベースで段階的にリプレイスする方式をとりました。しかし、新旧のYYCから共通のDBを安全に操作するのが難しくなり、途中でリプレイスを中止しました。

当時の判断や取り組みは、YAPC::Kyoto 2023で登壇した資料 にまとまっています。この取り組みは失敗になり、段階的なリプレイスに半年ほど費やしました。しかし、サンクコストを気にせずに方針を転換して、今では正しかったと思います。

2. リファクタリングに方針転換した

段階的なリプレイスで開発したコードやリソースは完全に削除して、既存のYYCをリファクタリングする方針に切り替えました。まずは、開発環境を理想の状態に近づけることを目指しました。そして、この作業で出た差分を本番環境へ徐々に適応しました。実施した順に具体的な作業内容を箇条書きで紹介します。

  1. CentOSをDebian Busterへ移行する
    • 開発環境のコンテナOSをまずはDebianベースへ移行した
  2. mod_perl v1をStarmanとPSGI(Perl Web Server Gateway Interface)へ移行する
    • PSGIでも動作するか開発環境で検証した
    • Perlのバージョンを固定している原因のひとつがmod_perl v1だったため、mod_perlは置き換え可能か調査した
  3. Perlは5.16までアップデートする
    • 5.18にアップデートするとコードの修正箇所が多いため
      • 例えば foreach my $hoge qw(a b c) {} が使えない
      • 多数のモジュールがインストールに失敗する
    • 5.16でモジュールの整理整頓と非推奨の警告修正を優先した
  4. ファイルベースで管理していたライブラリをcpanfile経由へ移行する
    • プロジェクトで使用中のライブラリを洗い出し、必要なライブラリのみを移行した
    • すべてのパッケージを最新バージョンにアップグレードした
    • 移行できないライブラリ(社内専用、CPANに存在しない)は、専用ディレクトリを用意してコードをそのままコミットして利用する方式をとった

3. 段階的にLTSバージョンまでアップデートする

PerlもDebianも段階的にアップデートしました。まずは、Debian Busterで本番環境でも正常に稼働する確認がとれてから、Perlのアップデートへ移りました。段階的な手順は以下のとおりです。

  1. Perl5.8 + mod_perl + busterとPerl5.16 + PSGI + Starman + busterのどちらでもバックエンドが起動して単体テストとE2Eテストが通ることを確認した
  2. パフォーマンステスト(taurus)で性能劣化がないことを確認した
  3. Perl5.16 + PSGI + Starman + busterをリリースして正常に稼働した

最後にPerl5.38.2をアップデート対象にしてコードの修正をしつつ、2024年6月30日にBusterはEOLをむかえるため、Bookwormの検証も同時に開始しました。ところが、この作業中にPerl5.40がリリースされました。Perl5.38.2で動作確認がとれていたのでPerl5.40へアップデートしてリリースしました。Perl5.40 + PSGI + Starman + bookwormでリリース後、use v5.40; をすべてのPerlのコードに追記して、プロジェクトは完了しました。

4. アップデート対応で工夫したこと

このプロジェクトと同時並行で、現行環境での機能開発も継続して行っていました。そのため、Perl 5.16 + PSGIへの移行時は、新しい環境の変更をできるだけ現行環境にも取り込んでいく必要がありました。特に以下の2点を重視していました。

  • 新しい環境の変更点を現行環境の開発中でも意識できるようにして移行をスムーズに進めたい
  • コード自体は事前にリリースしておき、環境の切り替えはコンテナイメージの変更のみで行い、切り戻しをしやすい状態にしたい

これらを実現するために以下の工夫をしました。

  • Perl5.8 + mod_perl と、Perl5.16 + PSGI どちらでもプロジェクトのコードが動くように、ベースクラスで環境の違いを吸収した
  • Apache::ConstantsApache::Singletonなど環境に依存するパッケージを直接使っている場合は、プロジェクト内に同様の役割のクラスを作成して事前に移行した
  • パッケージの最新化で Perl5.8 環境から挙動やインタフェースが変わる場合には、一時的にプロジェクト内で該当のパッケージを継承したり MixIn をすることで、Perl 5.16 環境と同じように動くようにした
  • CI では Perl 5.8 + mod_perl と Perl 5.16 + PSGI の2つの環境を用意し、どちらの環境でも動作確認できるようにした

2つの環境をまたがって作業すると、混乱することはありました。しかし、切替はイメージの変更だけで行えたので、不具合の特定やロールバックはしやすい状態で移行できました。正常に移行できた後、新旧の両方で動くように工夫したコードはすべて削除しました。

やってよかったことは?

1. 課題と解決策がわかりやすくなった

アップデートを安全かつ効率的に進めるために不要なコードや機能をたくさん削除しました。考慮するコンテナの数やライブラリの数も減りました。開発言語とOSのアップデートと同時に、利用中のミドルウェアやライブラリも最新化できました。この結果、問題の特定が楽になり、新たな課題の発見とその解決策がわかりやすくなりました。

2. 取れる選択肢が増えた

開発言語もOSもLTSバージョンなので、ライブラリやパッケージが利用できないケースが減りました。以前まではOSが古いため、PerlだけでなくNode.jsなどの他のライブラリのバージョンも更新できない状況でしたが、その心配はなくなりました。

まとめ

プロジェクトを完了して、古く閉ざされた開発環境から解放されました。開発するモチベーションも上がっています。このようなプロジェクトを経験できたのは、多くのユーザーがYYCを長年利用してくれた結果だと思います。今後は、もっと早く計画的に対応できるよう、Diverseでは継続的な改善を怠らない体制を維持していきます。

ブログの内容や弊社に興味のある方は、ぜひ以下の採用ページからカジュアル面談へ!様々な意見をお待ちしています。

diverse-inc.co.jp

メール配信基盤をSendGridへ移行している話

こんにちは、Diverse developer blogです。最近弊社では、メール配信基盤の移行作業を行いました。今回はその経緯についてご紹介いたします。

背景

弊社では、複数のメール配信サービスを利用しており、AWS EC2上に構築したメールサーバーで運用していました。しかし、複数のサービスを利用することでメールの管理が煩雑になっていました。

SendGridの選定理由

SendGridに移行した主な理由はコスト削減で、具体的には月額約10万円のコストが削減されます。一方、新しいメールサービスへの移行によるコストは増加しますが、これはメンテナンスと管理の効率化によって相殺されると見込んでいます。 加えて、SendGridはサーバーレスであり、メール送信プロセスをAPIで統一できるため、AWS EC2のメールサーバー管理コストも削減されます。

sendgrid.kke.co.jp

移行手順

移行した手順と、進める際に留意したポイントについて以下でご紹介します。

1. 送信量とプランの適正確認

SendGridは、プランによって送信可能なメールの量が異なります。移行の検討段階で、現在の送信量とSendGridのプランを比較し、適切なプランを選択できるよう概算を立てました。

2. 独自ドメインの設定と認証

SendGrid上のドメイン設定(Domain Authentication)で、SPFやDKIM、DMARCに必要なDNSレコードをSendGridが自動的に生成します。それを弊社が管理するAmazon Route53へ登録します。 手順詳細については、マニュアルが用意されているので、それに従ってスムーズに進めることができました。

独自ドメインを利用する - ドキュメント | SendGrid

3. APIの実装とテスト

既存のメールサービスの使用範囲を調査し、影響範囲が最小のサービスから開発環境でSendGridへの移行作業を開始しました。SendGridの公式ライブラリではサポート外のPerlで開発しているため、REST APIへ直接リクエストする形で実装しました。事前の調査で既存システムへ置き換えられるSendGridのAPIを検討していたため、スムーズに実装自体は進めることができました。

移行作業中に苦戦したこと

移行作業中、メール送信処理後にSendGrid側で受付完了したメールが宛先に届かない問題が発生しました。調査を行いましたが解決に至らず、問い合わせて判明した原因は、SendGridのアカウントが無効化されていたことでした。SendGridの管理画面上で警告があったのを見落としていたようで、しばらく使用していないアカウントだったのが原因というオチでした。 問い合わせ後、回答が24時間以内で迅速に届いたので、今後トラブルが発生した際も迅速に対応してもらえそうです。

開発環境での実装に問題がないことを確認できた後は、SendGridを無料プランから有料プランへ切り替え、本番環境へリリースしました。

最後に

まだ移行できたサービスは一部ではありますが、この変更によりメール配信システムの効率化を図ると共に、コスト削減も達成できました。残っているサービスも順次移行作業を進めていく予定です。今後も、システムの改善に向けた取り組みを続けていきます。