Backlog課題を自動連携する仕組みを作った話 🚀

和田 海斗

和田 海斗

  • 2025.08.28
  • ある日、社内から「事業部の活動や日々のタスクが最終的にどのように会社目標につながっていて、どんな成果になっているのか可視化したい」という話がありました。

    これまでも社内ではBacklogを活用したタスク管理はしていましたが、経営目標からつながった事業部活動のタスク管理は行えていませんでした。

    そこで、Backlogのプロジェクトに階層構造を定義し、課題が自動でつながる仕組みを作ることにより、慣れ親しんだツールを使いながら実現してみることにしました。

    いくつか新しい学びもありましたので、今回はその内容を記事にしています。


    アーキテクチャ 🏗

    全体の構成はこんな感じです👇

    • Backlog が課題イベントを Webhook で発火
    • Cloudflare Workers がフロントで認証・整形・リトライ制御を担い
    • Google Apps Script (GAS) がメインロジックで課題生成・同期・整合性チェックを行う
    • Google Sheets がCONFIGとログを保持し
    • Slack がエラー時のみ通知を送信
    • GitHub + Actions + clasp がCI/CDでGASを自動デプロイする

    ※ 今回はPoCのため、運用維持費0円を重視した構成です。


    Cloudflare Workers を使ってみた 🔒⚡

    Webhookを直接GASに投げるのではなく、間にWorkersを置く構成にしました。

    理由としては、

    • Backlogに即200を返してリトライを防ぐ
    • URLトークン認証と追加ヘッダでセキュリティ強化
    • Exponential Backoff + Jitter でリトライ制御
    • ストリーム転送で高速かつ省メモリに中継

    Workersは今回が初導入でしたが、思った以上にシンプルに組めて「フロントゲートウェイ」としてちょうど良い役割を果たしています。


    Workers 実装(抜粋)🧩

    Cloudflare Dashboard の Modules 形式でそのまま動作するシンプルな実装です。

    export default {
      async fetch(request, env, ctx) {
        if (request.method !== 'POST') return new Response('Method Not Allowed', { status: 405 });
    
        const url = new URL(request.url);
        const token = url.searchParams.get('token');
        if (!token || token !== env.WEBHOOK_TOKEN) return new Response('Unauthorized', { status: 401 });
    
        // 非同期転送(Backlogには即200)
        ctx.waitUntil(forwardToGASWithRetry(request, env));
        return new Response('OK', { status: 200 });
      },
    };

    メトリクス (PoC) 📈

    試しにイベントを流してメトリクスを観測しました。
    CPU時間やウォールタイムは小さく収まり、エラー率も0%のまま安定。PoC段階では十分に軽快に動いています。


    GAS 側の設計 🧠

    GASはオーケストレーション層として、Backlog REST APIを操作しながら階層間の整合性を保つ役割を担っています。

    Idempotency Gate(多重実行の抑止)

    Webhookの重送を想定して、イベントIDをキャッシュして二重処理を防いでいます。

    Echo Prevention(自己ループ防止)

    自動更新時に [sync-bot] コメントを付与し、それを検知して自分自身のイベントはスキップします。

    Normalization Layer(正規化)

    Backlog APIは日付やIDの扱いが揺らぎやすいため、送信前に必ずISO日付や文字列IDに正規化しています。

    function normalizeUpdatePayloadForBacklog_(upd){
      const out = {};
      for (const k of Object.keys(upd||{})) {
        const v = upd[k];
        if (k === 'startDate' || k === 'dueDate') out[k] = ymdFromAny_(v);
        else if (['assigneeId','statusId'].includes(k)) out[k] = String(v);
        else if (/^customField_\d+$/.test(k)) out[k] = String(v);
        else out[k] = v;
      }
      return out;
    }
    

    Configuration as Data(設定の外部化)

    ルーティングやプロジェクト定義はGoogle Sheetsに外部化
    非エンジニアでもシートを編集してルートを切り替えられるようにしています。


    付録:GASの仕組みとファイル構成 🧩

    リクエストの流れ(ざっくり)

    1、Backlog → Cloudflare Workers:Webhook を POST
    2、Workers → GAS:認証チェック後にボディをストリーム転送
    3、GAS → Backlog API:課題の生成・更新・リンク付けを実行
    4、GAS → Sheets / Slack:CONFIG読み込み・監査ログ出力・エラー通知を実行

    トリガーとエントリーポイント

    • Webhook入口webhook.jsdoPost(e) が受け取り、冪等性ゲートとECHO除外を適用
    • メニュー/手動実行menu.js でスプレッドシートの拡張メニューを追加
    • 定期実行(必要に応じて)main.js がトリガー登録を担当(rollup系など拡張時に使用)

    ファイル構成(役割つき)

    backlog/
    ├─ .clasp.json            # clasp 設定(GASプロジェクトと紐づけ)
    ├─ appsscript.json        # GAS マニフェスト(スコープやトリガー)
    ├─ api.js                 # Backlog REST API 呼び出しラッパ(GET/POST/PATCH など)
    ├─ config.js              # CONFIGシート読み込み、定数・ルーティング設定の解決
    ├─ debug.js               # デバッグ補助(ダンプ出力や一時的な検証用)
    ├─ dump.js                # エンティティの整形表示、ログ出力ユーティリティ
    ├─ main.js                # 初期化・トリガー登録・セットアップ入口
    ├─ menu.js                # スプレッドシート拡張メニュー追加
    ├─ propagate.js           # 上位→下位の生成・リンク伝播
    ├─ rollup.js              # (拡張時用)集計や親子同期のユーティリティ
    ├─ routing.js             # 配賦先決定ロジック(カテゴリ/CF/デフォルトの優先順位)
    ├─ sync_l2_l3.js          # 下位↔上位の双方向同期(リンク補完、差分反映の粒度制御)
    ├─ types.js               # 型名/ステータス/カテゴリなどの辞書と型っぽい定義
    ├─ utils.js               # 冪等性ゲート、正規化、リトライ、日付変換など共通関数
    └─ webhook.js             # doPost入口、ECHO防止、イベントルーティング
    

    コアとなる実装ポイント

    • Idempotency Gate:イベントIDをCacheServiceに記録して近接重複を抑止
    • Echo Prevention[sync-bot] 付きコメントやリンク系だけの変更をスキップ
    • Normalization Layer:日付を yyyy-MM-dd、IDは文字列に正規化してAPIの型ゆらぎを吸収
    • Configuration as Data:ルーティングや種別はコードに埋めず、CONFIG シートから読み出す

    セキュリティと運用のメモ

    • 秘密情報Script Properties と Workers のシークレットで管理(ソースに直書きしない)
    • 監査ログLOG シートに最低限のメタデータだけを記録
    • デプロイは GitHub Actions + clasp で自動化して、手作業の反映ミスを防止



    開発中の気付き 💡

    • タイトル一致だけで既存課題を判定すると精度が足りませんでした。そのため、契約プランをプレミアムプランに変更して、カスタムフィールドを利用した多段階判定により精度向上させました。
    • APIレート制限(429)に対応するため、指数バックオフ + ジッターを導入することにより、安定性が大幅向上しました。
    • Workersを初めて使いましたが、Webhookのフロントゲートウェイとしてシンプルに実装できると実感しました。

    今後の展望 🔮

    PoC段階として、仕組みは形になりました。

    今後は…

    • 本格運用にのせて、実際に運用してみる
    • ダッシュボード化によって、課題のつながりをより可視化しやすくする
    • AI分析を導入し、タスクの進み具合から「ボトルネック」や「改善ポイント」を自動で提示できるようにする
    • 経営層がダッシュボードを開けば「今どこで止まっているか」「どの施策が効いているか」がAIによってサマリされるようにする

    まとめ ✅

    まだ本番運用前ですが、課題の階層概念イベント駆動の自動連携をPoCとして実現できました。
    Workersをフロントに置く構成も安定して動いていて、本格運用に向けて準備が整ってきています。

    今後はAI分析やダッシュボード連携を組み合わせることにより、Backlogを単なるタスク管理だけではなく、 「経営と現場をつなぐモノ」 に進化させていきたいですね。

    同じような課題に取り組んでいる方や、GASを使って業務効率化にチャレンジしたい方の参考になれば幸いです。

    和田 海斗

    和田 海斗

    ITS事業部 / General Manager

    フルスタックエンジニア兼プロジェクトマネージャー

    受託開発やSaasの企画・設計から実装まで幅広く担当しています。
    このブログでは、技術的な学びやノウハウなどをまとめて発信しています。

    最近は話を聞いてくれるのが愛犬ではなくAIになってきました。
    どちらも無言ですが、反応はだいぶ違います。