Skip to content

IaC

TIP

One of the key features is the use of a single JSON schema not only for code generation, but also for defining the table structure in DynamoDB using Infrastructure as Code (IaC) tools.

WARNING

Below are some example approaches for working with a JSON schema and IaC tools.

These are just a few options, and the actual implementation may vary significantly depending on your specific needs and conventions within your projects.

GoDyno does not enforce any particular use of IaC tools.

🌍 Terraform

Example of a module for describing a DynamoDB table

bash
project/
  ├── main.tf  
  └── modules/
       └── dynamodb/
            ├── dynamo.tf 
            ├── variables.tf
            └── outputs.tf
hcl
terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

# --- >

module "schema_table" {
  source = "./modules/dynamodb"
  schema = jsondecode(file("${path.module}/schema.json"))
}

# --- >

output "table_name" {
  value = module.schema_table.table_name
}

output "table_arn" {
  value = module.schema_table.table_arn
}
hcl
locals {
  key_attributes = var.schema.attributes
      
  gsi_indexes = [
    for idx in var.schema.secondary_indexes : idx
    if try(idx.type, "GSI") == "GSI"
  ]

  lsi_indexes = [
    for idx in var.schema.secondary_indexes : idx
    if try(idx.type, "LSI") == "LSI"
  ]
}

# --- >

resource "aws_dynamodb_table" "this" {
  name           = var.schema.table_name
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = var.schema.hash_key
  range_key      = var.schema.range_key

  dynamic "attribute" {
    for_each = local.key_attributes
    content {
      name = attribute.value.name
      type = attribute.value.type
    }
  }

  dynamic "global_secondary_index" {
    for_each = local.gsi_indexes
    content {
      name               = global_secondary_index.value.name
      hash_key           = global_secondary_index.value.hash_key
      range_key          = try(global_secondary_index.value.range_key, null)
      projection_type    = global_secondary_index.value.projection_type
      non_key_attributes = global_secondary_index.value.projection_type == "INCLUDE" ? global_secondary_index.value.non_key_attributes : null
    }
  }

  dynamic "local_secondary_index" {
    for_each = local.lsi_indexes
    content {
      name               = local_secondary_index.value.name
      range_key          = local_secondary_index.value.range_key
      projection_type    = local_secondary_index.value.projection_type
      non_key_attributes = local_secondary_index.value.projection_type == "INCLUDE" ? local_secondary_index.value.non_key_attributes : null
    }
  }

  tags = {
    Name      = var.schema.table_name
    ManagedBy = "go-dyno"
  }
}
hcl
variable "schema" {
  type = object({
    table_name           = string
    hash_key             = string
    range_key            = optional(string)
    attributes           = list(object({
      name = string
      type = string
    }))
    common_attributes = optional(list(object({
      name = string
      type = string
    })), [])
    secondary_indexes = optional(list(object({
      name               = string
      type               = optional(string, "GSI")
      hash_key           = optional(string)
      range_key          = optional(string)
      projection_type    = string
      non_key_attributes = optional(list(string), [])
      read_capacity      = optional(number)
      write_capacity     = optional(number)
    })), [])
  })
  description = "DynamoDB table schema from go-dyno JSON"
}
hcl
output "table_name" {
  description = "Name of the DynamoDB table"
  value       = aws_dynamodb_table.this.name
}

output "table_arn" {
  description = "ARN of the DynamoDB table"
  value       = aws_dynamodb_table.this.arn
}

output "table_id" {
  description = "ID of the DynamoDB table"
  value       = aws_dynamodb_table.this.id
}

output "hash_key" {
  description = "Hash key of the table"
  value       = aws_dynamodb_table.this.hash_key
}

output "range_key" {
  description = "Range key of the table"
  value       = aws_dynamodb_table.this.range_key
}

Usage

bash
terraform init && terraform apply

Single file example

variable "schema" {
  type = object({
    table_name           = string
    hash_key             = string
    range_key            = optional(string)
    attributes           = list(object({
      name = string
      type = string
    }))
    secondary_indexes = optional(list(object({
      name               = string
      type               = optional(string, "GSI")
      hash_key           = optional(string)
      range_key          = optional(string)
      projection_type    = string
      non_key_attributes = optional(list(string))
      read_capacity      = optional(number)
      write_capacity     = optional(number)
    })))
  })
}

locals {
  gsi_indexes = [
    for idx in coalesce(var.schema.secondary_indexes, []) : idx
    if lookup(idx, "type", "GSI") == "GSI"
  ]

  lsi_indexes = [
    for idx in coalesce(var.schema.secondary_indexes, []) : idx
    if lookup(idx, "type", "LSI") == "LSI"
  ]
}

resource "aws_dynamodb_table" "this" {
  name         = var.schema.table_name
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = var.schema.hash_key
  range_key    = var.schema.range_key

  dynamic "attribute" {
    for_each = var.schema.attributes
    content {
      name = attribute.value.name
      type = attribute.value.type
    }
  }

  dynamic "global_secondary_index" {
    for_each = local.gsi_indexes
    content {
      name               = global_secondary_index.value.name
      hash_key           = global_secondary_index.value.hash_key
      range_key          = lookup(global_secondary_index.value, "range_key", null)
      projection_type    = global_secondary_index.value.projection_type
      read_capacity      = global_secondary_index.value.read_capacity
      write_capacity     = global_secondary_index.value.write_capacity
      non_key_attributes = global_secondary_index.value.projection_type == "INCLUDE" ? global_secondary_index.value.non_key_attributes : null
    }
  }

  dynamic "local_secondary_index" {
    for_each = local.lsi_indexes
    content {
      name               = local_secondary_index.value.name
      range_key          = local_secondary_index.value.range_key
      projection_type    = local_secondary_index.value.projection_type
      non_key_attributes = local_secondary_index.value.projection_type == "INCLUDE" ? local_secondary_index.value.non_key_attributes : null
    }
  }
}

Usage

bash
# for example, the schema is passed dynamically via an environment variable
export TF_VAR_schema=$(cat schema.json)

terraform init && terraform apply

Released under the MIT License.