フロー定義 JSON を再帰探索してリソースを逆引きする
本連載は3部作です
| 回 | テーマ | 内容 |
|---|---|---|
| 第1回 | フロントエンドのみで AWS 接続 | Cognito で一時認証情報を取得し、バックエンドなしで AWS SDK を叩く |
| 第2回(本記事) | リソース関連性の逆引き解析 | フロー定義の JSON を再帰探索し、リソース同士の関連を逆引きする |
| 第3回 | 性能・UX の最適化 | 大量の API 呼び出しを捌くキャッシュ・並列制御・段階ロードの工夫 |
この記事について
Amazon Connect のリソース(キュー、エージェント、モジュールなど)が「どのフローから使われているか」を自動で探し出す、逆引き解析の仕組みを解説します。
Connect の管理コンソールは、フローを開けば「そのフローが何を使っているか」は分かります。しかし逆に「このキューを使っているフローはどれか」を一覧するのは苦手です。この記事では、フロー定義の JSON を読み解いてその逆引きを実現する考え方を扱います。
この記事で分かること
- なぜ Connect では「逆引き」が難しいのか
- フロー定義(
Content)の正体と、そこから情報を取り出す方法 - ネスト構造を取りこぼさず探索する「再帰探索」の考え方
- ID と ARN が混在する現実への対処(正規化・重複排除)
- 逆引き機能を他のリソースへ横展開するパターン
⚠️ 記事中のコードは要点を抜き出した抜粋です。実際のコードにあるデバッグ用ログなどは省略し、ロジックの骨子だけを示しています。そのまま動く完成形ではありません。
前提(事前に把握しておくこと)
- 第1回の構成(Cognito 経由で
ConnectClientを利用できる状態)ができていること - Amazon Connect の基本概念(フロー、モジュール、キュー、エージェント、ルーティングプロファイル)を一通り知っていること
- 利用する IAM ロールに
connect:ListContactFlows/connect:DescribeContactFlowなどの参照権限があること
1. 課題:Connect は「順引き」はできても「逆引き」ができない
Amazon Connect の設定は、おおまかに次のような関係でつながっています。
- ユーザー → ルーティングプロファイル → キュー
- フロー / モジュール → (内部で)キュー・エージェント・別モジュール・クイック接続を参照
コンソールでは上から下(順引き)はたどれます。しかし運用では逆方向の質問が頻発します。
- 「このキューを廃止したいが、どのフローが参照している?」
- 「このモジュールを修正したら、どのフローに影響する?」
- 「このルーティングプロファイルは、誰に割り当てられている?」
これらに答えるには、全リソースを横断的に調べて関連を割り出す必要があります。とくにフロー・モジュールは設定が 定義 JSON の中に埋もれている ため、単純な一覧 API では分かりません。ここが逆引きの肝になります。
2. フロー定義(Content)の正体
DescribeContactFlow でフローの詳細を取得すると、Content というフィールドに フロー定義が JSON 文字列として 入っています。文字列なので、まず JSON.parse してオブジェクトにします。
const flow = await describeContactFlow(flowId);
if (flow?.Content) {
const parsed = JSON.parse(flow.Content); // ← ここで構造化データになる
// parsed.Actions に、フロー内の各ブロックが配列で入っている
}
パース後のオブジェクトは、おおまかに次のような形をしています。
{
"Actions": [
{
"Identifier": "abc-123",
"Type": "TransferToQueue",
"Parameters": { "QueueId": "arn:aws:connect:...:queue/xxxx" }
},
{
"Type": "InvokeFlowModule",
"Parameters": { "FlowModuleId": "yyyy" }
}
// ...(多数のアクションが続く)
],
"Metadata": { /* レイアウト情報など */ }
}
つまり「フローが何を使っているか」は、この Actions[].Parameters の中に QueueId や FlowModuleId といったキーで書かれています。ここを拾えばよい、というのが出発点です。
3. 素直に拾うと取りこぼす ― だから「再帰探索」
最初は「Actions を回して Parameters.QueueId を見ればいい」と考えます。しかし実際のフロー定義は、
- アクションの種類によって
Parametersの構造が違う - 参照が
Parametersの さらに深いネスト に入っていることがある - 同じ「キュー参照」でも
QueueId(ID形式)の場合とQueueArn(ARN形式)の場合がある
といった事情で、決め打ちのパスでは漏れます。
そこで、オブジェクトを再帰的に全走査し、目的のキー名に当たったら値を回収する 方針にします。配列なら各要素へ、オブジェクトなら各値へ潜っていくだけのシンプルな関数です。
// queues / modules / agents をそれぞれ Set に集める(重複を自然に排除)
const searchInObject = (obj: any) => {
if (!obj || typeof obj !== 'object') return;
if (Array.isArray(obj)) {
obj.forEach(searchInObject); // 配列 → 各要素へ
return;
}
for (const key of Object.keys(obj)) {
const value = obj[key];
// キュー参照(ID形式)
if (key === 'QueueId' && typeof value === 'string' && value.trim()) {
queues.add(value);
}
// エージェント参照(複数のキー名に対応)
if ((key === 'AgentId' || key === 'UserId' || key === 'UserArn') && typeof value === 'string') {
// ARN なら user/ 以降の ID を抜き出す
const m = value.match(/user\/(.+)$/);
agents.add(m ? m[1] : value);
}
// モジュール参照(呼び出し方によってキー名が違う)
if ((key === 'FlowModuleId' || key === 'ContactFlowModuleId' || key === 'ModuleId') && typeof value === 'string') {
modules.add(value);
}
// キュー参照(ARN形式)
if (key === 'QueueArn' && typeof value === 'string' && value.includes('queue/')) {
queues.add(value);
}
// それ以外でも、オブジェクト/配列ならさらに潜る
if (value && typeof value === 'object') searchInObject(value);
}
};
ポイントは2つです。
-
複数のキー名を許容する:エージェントは
AgentId/UserId/UserArn、モジュールはFlowModuleId/ContactFlowModuleId/ModuleIdのように、文脈によって名前が揺れます。OR で受けておきます -
Setに集める:同じ参照が複数箇所に出ても、Setなら自然に重複排除されます
実際には Actions だけでなく Metadata やその他のトップレベルプロパティも走査対象にして、取りこぼしを減らしています。
4. ID と ARN の混在をどう正規化するか
現実のフロー定義では、同じキューが ある箇所では ID、別の箇所では ARN で書かれていることがあります。
- ID 形式:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - ARN 形式:
arn:aws:connect:<region>:<account>:instance/.../queue/xxxxxxxx-...
これを別物として数えると、同じキューが二重に出てしまいます。そこで、回収した後に ARN から ID 部分を抜き出して ID に寄せる 正規化を行います。
const finalQueueIds = new Set<string>();
// ID 形式はそのまま
collected.filter(q => !q.startsWith('arn:')).forEach(id => finalQueueIds.add(id));
// ARN 形式は queue/ 以降の ID を抽出して寄せる(既にあれば追加しない)
collected.filter(q => q.startsWith('arn:')).forEach(arn => {
const m = arn.match(/queue\/(.+)$/);
if (m) finalQueueIds.add(m[1]);
else finalQueueIds.add(arn); // ID が取れなければ ARN のまま保持
});
こうして「フロー1つが参照しているキュー / モジュール / エージェントの ID 一覧」を、形式の揺れを吸収した上で取り出せます。ここまでが 抽出器(あるフローの中身を読む処理)です。
5. 逆引きの実装パターン
抽出器ができれば、逆引きは「全フローを取得し、1つずつ抽出して、目的のリソースを含むか判定する」だけです。例として「あるキューを使っているフロー」を探す処理を示します。
async function findFlowsUsingQueue(queueId: string) {
// 1. 対象キューの ARN も取得しておく(ID / ARN 両方で照合するため)
const queueArn = (await describeQueue(queueId))?.QueueArn;
// 2. 全フローを取得
const allFlows = await listContactFlows();
// 3. 各フローを解析して判定(※並列実行の工夫は第3回で扱う)
const results = await Promise.all(allFlows.map(async (flow) => {
const detail = await describeContactFlowWithContent(flow.Id);
if (!detail?.parsedContent) return null;
const { queues } = extractResourcesFromFlowContent(detail.parsedContent);
// 4. ID / ARN / ARN内ID の3パターンで照合
const used = queues.some(q =>
q === queueId ||
(queueArn && q === queueArn) ||
q.includes(queueId) ||
(q.startsWith('arn:') && q.match(/queue\/(.+)$/)?.[1] === queueId)
);
return used ? { id: flow.Id, name: flow.Name } : null;
}));
return results.filter(Boolean); // ヒットしたフローだけ返す
}
照合を「ID 一致」だけにせず、ARN 一致・ARN から抽出した ID の一致 まで見ているのがポイントです。抽出側で正規化していても、フロー定義の書かれ方は一様ではないため、判定側でも複数パターンを許容して取りこぼしを防いでいます。
モジュールの中身も同じ JSON 構造なので、
DescribeContactFlowModuleのContentをパースして 同じ抽出器を再利用 できます。「キューを使っているモジュール」も同じ要領で探せます。
6. 同じ仕組みを横展開する
抽出器+「全件取得して判定」という型ができると、逆引き機能を次々と量産できます。本ツールでは以下を実装しています。
| 逆引き | やりたいこと | 調べ方 |
|---|---|---|
| キュー → フロー | このキューを使うフロー一覧 | 全フローの Content を解析 |
| キュー → モジュール | このキューを使うモジュール一覧 | 全モジュールの Content を解析 |
| キュー → ルーティングプロファイル | このキューを持つRP一覧 | 各RPの ListRoutingProfileQueues を確認 |
| モジュール → フロー | このモジュールを呼ぶフロー一覧 | 全フローの Content を解析 |
| エージェント → フロー | このエージェントへ転送するフロー一覧 | 全フローの Content を解析 |
| ルーティングプロファイル → ユーザー | このRPを割り当てられたユーザー | 各ユーザーの RoutingProfileId を確認 |
| クイック接続 → キュー | このQCを設定したキュー一覧 | 各キューの ListQueueQuickConnects を確認 |
注目したいのは、逆引きには2タイプある ことです。
- JSON 解析型:フロー・モジュールのように設定が定義 JSON に埋もれているもの(再帰探索が必要)
- 専用 API 型:ルーティングプロファイルとキューの関係や、キューとクイック接続の関係のように、関係を返す API が用意されているもの(解析不要で素直に照合)
後者は AWS の API(ListRoutingProfileQueues や ListQueueQuickConnects など)が関係を直接返してくれるので、JSON を読む必要はありません。「JSON を解析するしかないのはどこか」を見極めるのが設計のコツです。
7. 全体像
ここまでで扱ったリソースの関連を図にすると、次のようになります。実線は順引き(コンソールでも見える方向)、点線が本ツールで実現した逆引きの主な方向です。
まとめ
- Connect の逆引きが難しいのは、設定が フロー定義 JSON の中に埋もれている から
-
Contentは JSON 文字列。JSON.parseして 再帰探索 すれば、ネストや表記揺れに強く拾える - ID と ARN の混在は、ARN から ID を抜き出して寄せる 正規化で吸収する
- 逆引きは「全件取得 → 抽出 → 照合」の型で量産でき、関係を返す専用 API があるものはそれを使う
ただし「全フローを取得して1つずつ解析する」という素朴な実装は、リソースが増えると 大量の API 呼び出しで遅くなり、スロットリングの危険 もあります。次回(最適化編)では、この重い処理を実用的な速度で動かすためのキャッシュ・並列制御・段階的ロードの工夫を解説します。