Frame

A simple frame for quickly setting up api services based on [go-cloud](https://github.com/google/go-cloud) framework

View the Project on GitHub pitabwire/frame

Datastore Component

Overview

The Frame datastore component provides a robust database integration layer built on top of GORM. It supports multiple database connections, read/write separation, and multi-tenancy out of the box.

Features

1. Multiple Database Support

2. Multi-tenancy

3. Migration Management

Configuration

Basic Setup

mainDbOption := frame.Datastore(ctx, "postgres://user:secret@primary_server/service_db", false)
readDbOption := frame.Datastore(ctx, "postgres://user:secret@secondary_server/service_db", true)
service := frame.NewService("Data service", mainDbOption, readDbOption)

Connection URL Format

postgres://[user]:[password]@[host]:[port]/[database]?[options]

Common options:

Usage Examples

1. Basic CRUD Operations

type User struct {
    ID        uint
    Name      string
    Email     string
    TenantID  string `gorm:"index"`
}

// Create
db := frame.GetDB(ctx)
user := User{Name: "John", Email: "john@example.com"}
result := db.Create(&user)

// Read
var user User
db.First(&user, 1) // Find user with id 1

// Update
db.Model(&user).Update("Name", "John Doe")

// Delete
db.Delete(&user)

2. Working with Transactions

err := frame.GetDB(ctx).Transaction(func(tx *gorm.DB) error {
    // Perform multiple operations
    if err := tx.Create(&user).Error; err != nil {
        return err
    }
    
    if err := tx.Create(&userProfile).Error; err != nil {
        return err
    }
    
    return nil
})

3. Multi-tenant Queries

// Tenant context is automatically injected
db := frame.GetDB(ctx)
var users []User
db.Find(&users) // Will automatically filter by tenant

// Override tenant context
db.WithContext(frame.WithTenant(ctx, "tenant-id")).Find(&users)

Best Practices

  1. Always Use Context
    db := frame.GetDB(ctx) // Instead of accessing DB directly
    
  2. Handle Errors
    if err := db.Create(&user).Error; err != nil {
        return fmt.Errorf("failed to create user: %w", err)
    }
    
  3. Use Transactions for Multiple Operations
    tx := db.Begin()
    defer func() {
        if r := recover(); r != nil {
            tx.Rollback()
        }
    }()
    
  4. Implement Proper Indexes
    type Model struct {
        ID        uint      `gorm:"primarykey"`
        CreatedAt time.Time `gorm:"index"`
        TenantID  string    `gorm:"index"`
    }
    

Migration Management

Creating Migrations

type Migration20240207 struct{}

func (m *Migration20240207) Up(db *gorm.DB) error {
    return db.AutoMigrate(&User{})
}

func (m *Migration20240207) Down(db *gorm.DB) error {
    return db.Migrator().DropTable(&User{})
}

Registering Migrations

migrator := frame.NewMigrator()
migrator.AddMigration(&Migration20240207{})

Performance Optimization

  1. Use Indexes Wisely
    • Create indexes for frequently queried fields
    • Consider composite indexes for multi-column queries
    • Monitor index usage
  2. Batch Operations
    db.CreateInBatches(&users, 100)
    
  3. Select Required Fields
    db.Select("name", "email").Find(&users)
    
  4. Preload Related Data
    db.Preload("Profile").Find(&users)
    

Monitoring and Debugging

  1. Enable SQL Logging
    db.Debug().Find(&users)
    
  2. Monitor Connection Pool
    stats := db.DB().Stats()
    log.Printf("Open connections: %d", stats.OpenConnections)
    
  3. Set Statement Timeout
    db.Statement.Settings.Set("gorm:query_timeout", time.Second*5)