Phase 1-2: DynamoDBテーブルの設計と実装

目的

Mesh v2システムのデータ永続化層としてDynamoDBテーブルを設計・実装します。Domain概念を導入し、グローバルIPベースのスコープ管理を実現します。

設計要件

Domain概念

  • グループIDの正式名: {group_id}@{domain}
  • Domain: グローバルIP(自動)または任意文字列(手動指定、最大256文字)
  • 検索スコープ: 同一domain内のグループのみリストアップ可能

タスク

テーブル設計

  • Domain対応のPK/SK設計
    • PK: DOMAIN#{domain}, SK: GROUP#{group_id}#METADATA - グループメタデータ
    • PK: DOMAIN#{domain}, SK: GROUP#{group_id}#NODE#{node_id} - Node情報
    • PK: NODE#{node_id}, SK: METADATA - Node所属情報
  • 属性定義
    • Group: id, domain, fullId, name, hostId, createdAt
    • Node: id, name, groupId, domain
    • NodeStatus: nodeId, groupId, domain, data, timestamp
  • GSI設計
    • GSI-PK: GROUP#{group_id}, GSI-SK: DOMAIN#{domain} - グループID逆引き

CDK実装

  • DynamoDB Table リソースの定義
  • 課金モード: PAY_PER_REQUEST (オンデマンド)
  • GSIの定義
  • RemovalPolicy設定 (開発環境: DESTROY)

バリデーション

  • Domain文字列の最大長: 256文字
  • 許可文字: 英数字、記号、マルチバイト文字

テスト

  • テーブル作成の動作確認
  • PK/SK設計の妥当性検証
  • Domain検索の動作確認

成果物

  • DynamoDBテーブル定義コード (lib/mesh_v2_stack.rb)
  • デプロイ可能なStack

テーブル設計詳細

メインテーブル

グループメタデータ

PK: DOMAIN#{domain}
SK: GROUP#{group_id}#METADATA

属性:
- id (String): group_id部分
- domain (String): domain部分(最大256文字)
- fullId (String): {group_id}@{domain}
- name (String): グループ名
- hostId (String): ホストのnodeId
- createdAt (String): ISO8601形式のタイムスタンプ

例:
PK: DOMAIN#203.0.113.1
SK: GROUP#abc123#METADATA
id: abc123
domain: 203.0.113.1
fullId: abc123@203.0.113.1
name: My Group
hostId: node_001
createdAt: 2025-01-15T12:00:00Z

Node情報と状態

PK: DOMAIN#{domain}
SK: GROUP#{group_id}#NODE#{node_id}

属性:
- nodeId (String): nodeId
- groupId (String): groupId
- domain (String): domain
- name (String): Node名
- data (List): SensorData配列
  - M: {key: String, value: String}
- timestamp (String): 最終更新時刻

例:
PK: DOMAIN#203.0.113.1
SK: GROUP#abc123#NODE#node_001
nodeId: node_001
groupId: abc123
domain: 203.0.113.1
name: Node 1
data: [
  {key: "temperature", value: "25"},
  {key: "humidity", value: "60"}
]
timestamp: 2025-01-15T12:05:00Z

Node所属情報

PK: NODE#{node_id}
SK: METADATA

属性:
- nodeId (String): nodeId
- groupId (String): 所属しているgroupId
- domain (String): 所属しているdomain

例:
PK: NODE#node_001
SK: METADATA
nodeId: node_001
groupId: abc123
domain: 203.0.113.1

GSI (Global Secondary Index)

名前: GroupIdIndex
GSI-PK: GROUP#{group_id}
GSI-SK: DOMAIN#{domain}

用途: グループIDからdomain逆引き

例:
GSI-PK: GROUP#abc123
GSI-SK: DOMAIN#203.0.113.1

アクセスパターン

操作 アクセス方法
Domain内のグループ一覧取得 Query: PK = DOMAIN#{domain}, SK begins_with GROUP#
グループメタデータ取得 GetItem: PK = DOMAIN#{domain}, SK = GROUP#{group_id}#METADATA
グループ内のNode一覧取得 Query: PK = DOMAIN#{domain}, SK begins_with GROUP#{group_id}#NODE#
NodeStatus取得 GetItem: PK = DOMAIN#{domain}, SK = GROUP#{group_id}#NODE#{node_id}
Node所属グループ取得 GetItem: PK = NODE#{node_id}, SK = METADATA
グループID逆引き Query GSI: GSI-PK = GROUP#{group_id}

技術仕様

Ruby CDK実装例

# lib/mesh_v2_stack.rb (一部抜粋)

# DynamoDB テーブル定義
iot_table = AwsCdkLib::AwsDynamodb::Table.new(self, 'MeshV2Table',
  partition_key: { name: 'pk', type: AwsCdkLib::AwsDynamodb::AttributeType::STRING },
  sort_key: { name: 'sk', type: AwsCdkLib::AwsDynamodb::AttributeType::STRING },
  billing_mode: AwsCdkLib::AwsDynamodb::BillingMode::PAY_PER_REQUEST,
  removal_policy: AwsCdkLib::Core::RemovalPolicy::DESTROY # 開発環境用
)

# GSI: グループID逆引き用
iot_table.add_global_secondary_index(
  index_name: 'GroupIdIndex',
  partition_key: { name: 'gsi_pk', type: AwsCdkLib::AwsDynamodb::AttributeType::STRING },
  sort_key: { name: 'gsi_sk', type: AwsCdkLib::AwsDynamodb::AttributeType::STRING },
  projection_type: AwsCdkLib::AwsDynamodb::ProjectionType::ALL
)

# DynamoDB データソース
dynamo_ds = api.add_dynamo_db_data_source('DynamoDataSource',
  table: iot_table
)

関連

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com