実装詳細とアーキテクチャ要件
Role Bが担当する「バックエンド&Webexモジュール」は、エッジから送られてくる大量のデータを安全かつ低コストで処理・保存し、外部サービス(Webex)との連携基盤を構築する役割を担います。「スロットリングによるデータ欠損」や「不適切なクエリによるクラウド破産(青天井課金)」、さらには「プライバシー情報の漏洩」を防ぐための堅牢な設計が求められます。
また、他ロールの完成を待つ「ウォーターフォール的な待ち状態」を排除するため、モックAPIを用いた先行構築を積極的に行い、フロントエンドとの結合テストを最速で実施することが重要です。
目的: エッジから送信されるMQTTメッセージを永続化する。
expiration_time を計算して付与する(例: SELECT *, timestamp() / 1000 + 2592000 AS expiration_time FROM 'topic/#' ※30日間保持の場合)。
【重要】技術的課題と根本解決:
1. ペイロードのネスト問題: レガシーな「DynamoDBアクション」を使用すると、データが payload 属性内にネストされてしまいます。Lambdaを挟んで整形するとコストと障害点が増加するため、必ずJSONを直接マッピングできる「DynamoDBv2アクション」を使用してください。
2. データ欠損対策: プロビジョニングモード(1WCU固定)で運用するため、バーストトラフィック時に書き込みスロットリングが発生しデータが欠損するリスクがあります。必ずIoT Coreルールの「エラーアクション」として、CloudWatch Logsへエラー内容を出力する設定、またはSQS(Dead Letter Queue)へメッセージを退避する設定を実装してください。
目的: 時系列データを効率的に保存し、かつ空間単位で一括検索できるようにする。
HazardEventsdevice_id (String)、ソートキー(SK)を timestamp (Number) に設定。RoomEventsIndex とし、PKを room_id (String)、SKを timestamp (Number) に設定。射影される属性は ALL とする。expiration_time (Number - Unixタイムスタンプ秒) をTTL属性として有効化する。
【重要】技術的課題と根本解決:
複数カメラがある部屋全体のハザードデータを取得する際、device_id がPKだと複数回のクエリや全件スキャンが必要になります。必ず room_id をPKとするGSIを設計し、1回のQuery操作で特定期間・特定空間のデータを取得できるようにしてRCU消費を抑えてください。また、プロビジョニングモード(1WCU/1RCU固定)で運用するため、不要なデータによるストレージ肥大化とスキャンコスト増大を防ぐべく、TTLによるライフサイクル管理は必須です。
目的: プライバシー情報を保護し、認証済みユーザーのみがシステムにアクセスできるようにする。
iot:Connect, iot:Subscribe, iot:Receive 権限を付与する。
【重要】技術的課題と根本解決:
未認証アクセスを許可すると、URLを知る第三者が子供の監視データを覗き見できる重大なインシデントに繋がります。必ずユーザープールによるログインを必須とし、IDプールに紐づくIAMポリシーのResource句は、Role CがSubscribeする特定のMQTTトピックのみに制限して最小権限の原則を徹底してください。
目的: Role Cが過去のハザードデータを安全に取得できるようにする。Role AのJSONスキーマ確定を待たずに、インフラと認可の結合テストを先行させる。
export const handler = async (event) => {
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods': 'OPTIONS,GET'
},
body: JSON.stringify([
{ device_id: 'mv_camera_01', room_id: 'living_room', timestamp: Date.now() - 3600000, event_type: 'ai_hazard', details: { hazard_type: 'fall', x: 640, y: 480, confidence: 0.85 } },
{ device_id: 'mt20_door_01', room_id: 'living_room', timestamp: Date.now() - 1800000, event_type: 'sensor_alert', details: { sensor_type: 'door', status: 'open', battery_level: 95 } }
])
};
};
Query を実行する本番ロジックへLambdaを書き換える。
【重要】技術的課題と根本解決:
1. CORSエラーの確実な防止: API Gateway側でのCORS設定だけでなく、Lambda関数からの戻り値(レスポンスヘッダー)にも必ず Access-Control-Allow-Origin などのCORSヘッダーを含めてください。
2. 不正クエリによるDB負荷の防止: フロントエンドから渡されるクエリパラメータをLambda内で厳格に型検証し、不正な値の場合は 400 Bad Request を返すフェイルセーフを実装してください。
目的: Role Cが開発した専用サイトをインターネット上に公開する。
【重要】技術的課題と根本解決:
1. セキュリティ: S3バケットを直接パブリック公開せず、必ずCloudFrontを前段に配置し、S3バケットポリシーでCloudFrontからのアクセスのみを許可するセキュアな構成にしてください。
2. SPAルーティング設定(必須): React等のSPAでは、画面遷移後にリロードするとS3のKey NotFoundエラーになります。必ずCloudFrontの「エラーページ」設定で、HTTPステータスコード 404 および 403 に対して、レスポンスページパスを /index.html にし、HTTPレスポンスコードを 200 に設定してください。
目的: Role Aが画像送信ロジックを実装するために必要なWebexの認証情報と送信先を準備する。
【重要】技術的課題と根本解決:
Role AがAI推論と画像エンコードの実装に集中できるよう、インフラ・API連携担当であるRole Bが事前にAPIの疎通確認を済ませておくことが不可欠です。未検証のTokenを渡すと、Role A側でエラーが出た際に「コードのバグ」か「Token/権限の不備」かの切り分けに無駄な時間を費やすことになります。
【重要】AWS IoT CoreおよびDynamoDBで扱うJSONスキーマが以下に確定しました。 これを前提に、DynamoDBのテーブル設計(PK: device_id, SK: timestamp, GSI-PK: room_id, GSI-SK: timestamp, TTL: expiration_time)と、Lambda関数の本番ロジック実装を進めてください。
// パターン1: AI推論アラート
{
"device_id": "mv_camera_01", "room_id": "living_room", "timestamp": 1723307400000, "event_type": "ai_hazard",
"details": { "hazard_type": "fall", "x": 640, "y": 480, "confidence": 0.85 }
}
// パターン2: Merakiセンサーアラート
{
"device_id": "mt20_door_01", "room_id": "living_room", "timestamp": 1723307405000, "event_type": "sensor_alert",
"details": { "sensor_type": "door", "status": "open", "battery_level": 95 }
}
// パターン3: 複合アラート
{
"device_id": "system_logic_01", "room_id": "living_room", "timestamp": 1723307410000, "event_type": "complex_alert",
"details": { "alert_type": "night_wandering", "trigger_device": "mt20_door_01", "lux": 5 }
}
// パターン4: 行動パターン解析結果
{
"device_id": "system_logic_01", "room_id": "living_room", "timestamp": 1723307420000, "event_type": "risk_suggestion",
"details": { "risk_level": "high", "suggested_area": { "x": 100, "y": 200, "radius": 50 }, "reason": "unusual_access_time" }
}