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¶
Deletion Protection¶
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¶
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¶
- Optimistic Locking - Prevent concurrent update conflicts
- Query Caching - Reduce DynamoDB costs with caching
- Testing - Test with DynamoDB Local