LoginSignup
2
0

【Terraform】NoSQL workbenchで作成したDynamoDB の定義をもとにリソースを作成する

Last updated at Posted at 2024-03-31

TL;DR

  • NoSQL workbenchは作成したdata modelをexportできる
  • exportした定義をもとにTerraformでリソースを作成したい
  • 既存の仕組みではできなそうなので、自分で作りました

NoSQL workbenchとは

NoSQL Workbench は、DynamoDB テーブルの設計、作成、クエリ、管理に役立つデータモデリング、データ可視化、クエリ開発といった特徴を提供する視覚的開発ツールです。

要するに、DynamoDB用のDBツール見たいな感じです。RDBに比べてなじみが薄いDynamoDBの設計・実装を楽にしてくれます

NoSQL workbenchというぐらいなので、Amazon Keyspacesにも対応していたりします

AWSのCredentialを設定すれば、デプロイまでできてしまいます

Terraform でデプロイしたい

上述の通り、NoSQL Workbench自体でデプロイもできるのですが、かかわったプロジェクトではNoSQL workbenchで設計したdata modelをリポジトリにコミットして、開発者間で変更共有していました。そして、クラウドにはTerraformでIaCをしており、そこにまとめて管理しておきたいという要件がありました。

単純にTerraformでやる場合だと、data modelの更新時にTerraformの更新も必要になり、以下の問題点が発生します

  • 開発側にTerraformの知見がなく、Terraformの更新が負担になる。開発側の実装速度低下
     * プロジェクトの体制上、開発側のキャッチアップを十分にとることは難しかった
  • workbenchとTerraformファイルのダブルメンテナンスになる

そこで、exportしたworkbenchの定義をもとにTerraformでリソースを作成することにしました

実装方針

  • AWS公式のTerraform DynamoDBで実装できることは可能にする
  • itemまで実装
    • data modelにはデータも含めることが可能で、それも併せてデプロイできるようにします
  • あくまでTerraform上は、1つのテーブルに1つのリソースを定義する
    • workbenchの定義ファイルには複数のテーブル情報が含まれるので不要にもできるが、テーブルを増減する場合は、Terraform側でも明示的に変更を入れる形にして、意図せずテーブルが増減するケースを防ぐ
    • ここは今後方針を変えてもよいかも

実装内容

以下抜粋。全コードはこちら

基本的にテーブルによって、使わない要素があるので、tryを使用

複数回定義される可能性がある要素は、Dynamic + for_eachを使用して、0からn個までの定義を可能にしました。

locals {
  range_key                = try(var.data_model.KeyAttributes.SortKey.AttributeName, null)
  read_capacity            = try(var.data_model.ProvisionedCapacitySettings.ProvisionedThroughput.ReadCapacityUnits, null)
  write_capacity           = try(var.data_model.ProvisionedCapacitySettings.ProvisionedThroughput.WriteCapacityUnits, null)
  auto_scaling_read        = try(var.data_model.ProvisionedCapacitySettings.AutoScalingRead, null)
  auto_scaling_write       = try(var.data_model.ProvisionedCapacitySettings.AutoScalingWrite, null)
  global_secondary_indexes = try(var.data_model.GlobalSecondaryIndexes, [])
  splat_table_data         = try(var.data_model.TableData, [])
  splat_table_facet_data   = try(var.data_model.TableFacets[*].TableData[0], [])
  table_data               = concat(local.splat_table_data, local.splat_table_facet_data)
}

resource "aws_dynamodb_table" "DynamoDBTable" {
  dynamic "attribute" {
    for_each = var.data_model.KeyAttributes
    content {
      name = attribute.value.AttributeName
      type = attribute.value.AttributeType
    }
  }
  dynamic "attribute" {
    for_each = { for i in local.global_secondary_indexes : i.IndexName => i.KeyAttributes.PartitionKey }
    iterator = gsi_partition_key
    content {
      name = gsi_partition_key.value.AttributeName
      type = gsi_partition_key.value.AttributeType
    }
  }
  dynamic "attribute" {
    for_each = { for i in local.global_secondary_indexes : i.IndexName => i.KeyAttributes.SortKey if can(i.KeyAttributes.SortKey) }
    iterator = gsi_sort_key
    content {
      name = gsi_sort_key.value.AttributeName
      type = gsi_sort_key.value.AttributeType
    }
  }
  name           = var.name
  hash_key       = var.data_model.KeyAttributes.PartitionKey.AttributeName
  range_key      = local.range_key
  billing_mode   = var.data_model.BillingMode
  read_capacity  = local.read_capacity
  write_capacity = local.write_capacity
  dynamic "global_secondary_index" {
    for_each = { for i in local.global_secondary_indexes : i.IndexName => i }
    content {
      name            = global_secondary_index.value.IndexName
      hash_key        = global_secondary_index.value.KeyAttributes.PartitionKey.AttributeName
      range_key       = try(global_secondary_index.value.KeyAttributes.SortKey.AttributeName, null)
      projection_type = global_secondary_index.value.Projection.ProjectionType
      read_capacity   = try(global_secondary_index.value.ProvisionedCapacitySettings.ProvisionedThroughput.ReadCapacityUnits, null)
      write_capacity  = try(global_secondary_index.value.ProvisionedCapacitySettings.ProvisionedThroughput.WriteCapacityUnits, null)
    }
  }
}

上記をモジュールとして定義し、それを以下のように使用します

locals {
  data_models_json     = file(var.datamodel_filepath)
  dynamodb_data_models = jsondecode(local.data_models_json).DataModel
}

data "aws_caller_identity" "current" {}


module "dynamodb_Forum" {
  ### Module Path
  source = "../../modules/DynamoDB/from_data_model"

  for_each = { for i in local.dynamodb_data_models : i.TableName => i if endswith(i.TableName, "Forum") }

  name       = "${var.resource_prefix}-Forum"
  data_model = each.value
}

module "dynamodb_Thread" {
  ### Module Path
  source = "../../modules/DynamoDB/from_data_model"

  for_each = { for i in local.dynamodb_data_models : i.TableName => i if endswith(i.TableName, "Thread") }

  name       = "${var.resource_prefix}-Thread"
  data_model = each.value
}

module "dynamodb_Reply" {
  ### Module Path
  source = "../../modules/DynamoDB/from_data_model"

  for_each = { for i in local.dynamodb_data_models : i.TableName => i if endswith(i.TableName, "Reply") }

  name       = "${var.resource_prefix}-Reply"
  data_model = each.value
}

振り返りと今後

NoSQL workbench自体は便利ですが、Terraformと絡めたときに管理上の煩雑さが出るので、そこを改善する1つのアプローチをしてみました。

(最も、Cfnを使えばそこも楽ではあるが。。それはIaCツールのポリシーによるかなと。このPJTではマルチクラウドだったのでTerraformを選択しています)

せっかく作成したのでモジュール化して公開をしたいと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0