Skip to content

Schema Export

Export your Dynorm entity schemas to CloudFormation and Terraform for infrastructure-as-code workflows. Define your entities once in Go, and generate the infrastructure to deploy them.

Overview

The schema export feature allows you to:

  • Generate CloudFormation YAML templates from entity definitions
  • Generate Terraform HCL configurations for DynamoDB tables
  • Export all key schemas, GSIs, LSIs, and TTL configurations
  • Customize billing mode, capacity, and table settings
flowchart LR
    Entity[Go Entity] --> Schema[Schema Parser]
    Schema --> CFN[CloudFormation YAML]
    Schema --> TF[Terraform HCL]
    CFN --> Deploy[AWS Deploy]
    TF --> Deploy

Basic Usage

CloudFormation Export

import (
    "fmt"
    "reflect"

    "github.com/go-gamma/dynorm/pkg/schema"
    "github.com/go-gamma/dynorm/pkg/schema/export"
)

// Your entity definition
type User struct {
    dynorm.Entity
    Email    string `dynorm:"gsi:ByEmail"`
    Username string `dynorm:"gsi:ByUsername"`
    Status   string
}

func main() {
    // Get the schema
    s, _ := schema.Get(reflect.TypeOf((*User)(nil)))

    // Generate CloudFormation
    yaml, err := export.ToCloudFormation("users", s)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(yaml)
}

Output:

AWSTemplateFormatVersion: '2010-09-09'
Description: DynamoDB table generated by dynorm

Resources:
  UsersTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: users
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: email
          AttributeType: S
        - AttributeName: id
          AttributeType: S
        - AttributeName: username
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      GlobalSecondaryIndexes:
        - IndexName: ByEmail
          KeySchema:
            - AttributeName: email
              KeyType: HASH
          Projection:
            ProjectionType: ALL
        - IndexName: ByUsername
          KeySchema:
            - AttributeName: username
              KeyType: HASH
          Projection:
            ProjectionType: ALL

Outputs:
  UsersTableArn:
    Description: ARN of the DynamoDB table
    Value: !GetAtt UsersTable.Arn
  UsersTableName:
    Description: Name of the DynamoDB table
    Value: !Ref UsersTable

Terraform Export

// Generate Terraform HCL
hcl, err := export.ToTerraform("users", s)
if err != nil {
    log.Fatal(err)
}

fmt.Println(hcl)

Output:

resource "aws_dynamodb_table" "users" {
  name         = "users"
  billing_mode = "PAY_PER_REQUEST"

  hash_key = "id"

  attribute {
    name = "email"
    type = "S"
  }

  attribute {
    name = "id"
    type = "S"
  }

  attribute {
    name = "username"
    type = "S"
  }

  global_secondary_index {
    name            = "ByEmail"
    hash_key        = "email"
    projection_type = "ALL"
  }

  global_secondary_index {
    name            = "ByUsername"
    hash_key        = "username"
    projection_type = "ALL"
  }

  tags = {
    ManagedBy = "dynorm"
  }
}

output "users_table_arn" {
  description = "ARN of the DynamoDB table"
  value       = aws_dynamodb_table.users.arn
}

output "users_table_name" {
  description = "Name of the DynamoDB table"
  value       = aws_dynamodb_table.users.name
}

Export Options

Customize the exported schema with options:

Billing Mode

// Pay-per-request (default)
yaml, _ := export.ToCloudFormation("users", s)

// Provisioned capacity
yaml, _ := export.ToCloudFormation("users", s,
    export.WithProvisionedCapacity(10, 5), // 10 RCU, 5 WCU
)

Point-in-Time Recovery

yaml, _ := export.ToCloudFormation("users", s,
    export.WithPointInTimeRecovery(true),
)

Deletion Protection

yaml, _ := export.ToCloudFormation("users", s,
    export.WithDeletionProtection(true),
)

Tags

yaml, _ := export.ToCloudFormation("users", s,
    export.WithTags(map[string]string{
        "Environment": "production",
        "Team":        "platform",
        "CostCenter":  "12345",
    }),
)

TTL

// Entity with TTL field
type Session struct {
    dynorm.Entity
    UserID    string
    ExpiresAt int64 `dynorm:"ttl"`
}

s, _ := schema.Get(reflect.TypeOf((*Session)(nil)))

yaml, _ := export.ToCloudFormation("sessions", s,
    export.WithTTL(true),
)

Index Projection

// Only project keys (reduces storage costs)
yaml, _ := export.ToCloudFormation("users", s,
    export.WithIndexProjection(export.ProjectionKeysOnly),
)

// Project specific attributes
yaml, _ := export.ToCloudFormation("users", s,
    export.WithIndexProjection(export.ProjectionInclude),
)

Combined Options

yaml, _ := export.ToCloudFormation("users", s,
    export.WithProvisionedCapacity(100, 50),
    export.WithPointInTimeRecovery(true),
    export.WithDeletionProtection(true),
    export.WithTTL(true),
    export.WithTags(map[string]string{
        "Environment": "production",
    }),
)

Entity Examples

Simple Entity

type User struct {
    dynorm.Entity
    Email string `dynorm:"gsi:ByEmail"`
    Name  string
}

Generates a table with: - Primary key: id (HASH) - GSI: ByEmail with email as partition key

Composite Keys

type OrderItem struct {
    OrderID   string `dynorm:"pk"`
    ProductID string `dynorm:"sk"`
    Quantity  int
    Price     float64
}

Generates a table with: - Primary key: order_id (HASH), product_id (RANGE)

Composite GSI

type Event struct {
    dynorm.Entity
    UserID    string    `dynorm:"gsi:ByUserDate"`
    EventDate string    `dynorm:"gsi:ByUserDate:sk"`
    Type      string
    Data      string
}

Generates a table with: - Primary key: id (HASH) - GSI: ByUserDate with user_id (HASH), event_date (RANGE)

Multiple GSIs

type Product struct {
    dynorm.Entity
    SKU       string  `dynorm:"gsi:BySKU"`
    Category  string  `dynorm:"gsi:ByCategory"`
    Brand     string  `dynorm:"gsi:ByBrand"`
    Price     float64
    InStock   bool
}

Generates a table with three GSIs for flexible querying.

Type Mapping

Go types are automatically mapped to DynamoDB types:

Go Type DynamoDB Type
string S (String)
int, int64, float64 N (Number)
bool BOOL
[]byte B (Binary)
[]T L (List)
map[string]T M (Map)
time.Time S (ISO 8601 string)

CLI Integration

Create a simple CLI tool to export schemas:

package main

import (
    "flag"
    "fmt"
    "os"
    "reflect"

    "github.com/go-gamma/dynorm/pkg/schema"
    "github.com/go-gamma/dynorm/pkg/schema/export"
)

var entities = map[string]any{
    "users":    (*User)(nil),
    "orders":   (*Order)(nil),
    "products": (*Product)(nil),
}

func main() {
    format := flag.String("format", "cloudformation", "Output format: cloudformation or terraform")
    table := flag.String("table", "", "Table name to export (or 'all')")
    flag.Parse()

    if *table == "all" {
        for name, entity := range entities {
            exportTable(name, entity, *format)
        }
    } else if entity, ok := entities[*table]; ok {
        exportTable(*table, entity, *format)
    } else {
        fmt.Fprintf(os.Stderr, "Unknown table: %s\n", *table)
        os.Exit(1)
    }
}

func exportTable(name string, entity any, format string) {
    s, _ := schema.Get(reflect.TypeOf(entity))

    var output string
    var err error

    switch format {
    case "cloudformation":
        output, err = export.ToCloudFormation(name, s)
    case "terraform":
        output, err = export.ToTerraform(name, s)
    }

    if err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        return
    }

    fmt.Println(output)
}

Usage:

# Export single table to CloudFormation
go run export.go -table=users -format=cloudformation > users.yaml

# Export all tables to Terraform
go run export.go -table=all -format=terraform > main.tf

CI/CD Integration

GitHub Actions

name: Generate Infrastructure

on:
  push:
    paths:
      - 'models/**'

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: '1.24'

      - name: Generate CloudFormation
        run: |
          go run cmd/export/main.go -table=all -format=cloudformation > infrastructure/dynamodb.yaml

      - name: Commit changes
        uses: stefanzweifel/git-auto-commit-action@v5
        with:
          commit_message: "chore: regenerate DynamoDB infrastructure"
          file_pattern: infrastructure/*.yaml

Best Practices

Do

  • Keep entity definitions as the single source of truth
  • Regenerate infrastructure files when entities change
  • Use version control for generated files
  • Review generated output before applying
  • Use deletion protection in production

Don't

  • Manually edit generated files (changes will be lost)
  • Deploy without reviewing capacity settings
  • Forget to set appropriate billing mode for your workload

Infrastructure as Code

Store generated CloudFormation/Terraform files in your repository alongside your Go code. This ensures your infrastructure stays in sync with your entity definitions.

API Reference

Functions

Function Description
ToCloudFormation(tableName, schema, opts...) Generate CloudFormation YAML
ToTerraform(tableName, schema, opts...) Generate Terraform HCL
ToCloudFormationFromType(tableName, type, opts...) Generate from reflect.Type
ToTerraformFromType(tableName, type, opts...) Generate from reflect.Type

Options

Option Description
WithProvisionedCapacity(read, write) Set provisioned throughput
WithPointInTimeRecovery(enabled) Enable PITR
WithDeletionProtection(enabled) Enable deletion protection
WithTags(tags) Add resource tags
WithTTL(enabled) Enable TTL
WithIndexProjection(type) Set GSI projection type

Next Steps