Skip to main content

Several databases

For read replicas of a single database, see read replicas. This page is about databases that hold different data.

One context per database​

Declare a context for each. Each has its own models, migrations, and cmd/migrate.

type Invoice struct {
ID int64
}

type MainDB struct {
ant.Context
Users ant.Table[User]
}

type BillingDB struct {
ant.Context
Invoices ant.Table[Invoice]
}

A relation can't cross databases. Keep the key, and load the other side in a second query. Mark such a relation .NoForeignKey().

Routing by tenant​

To give each large tenant a database of its own, keep a map of open contexts and pick one per request. Open each one once: an ant context owns a connection pool.

type Router struct {
mu sync.RWMutex
dbs map[string]*AppDB
}

func (r *Router) For(ctx context.Context, tenant string) (*AppDB, error) {
r.mu.RLock()
db := r.dbs[tenant]
r.mu.RUnlock()

if db != nil {
return db, nil
}

r.mu.Lock()
defer r.mu.Unlock()

if db := r.dbs[tenant]; db != nil {
return db, nil
}

db, err := ant.Open[AppDB](ctx, postgres.Dialect, dsnFor(tenant))
if err != nil {
return nil, err
}

r.dbs[tenant] = db

return db, nil
}

func dsnFor(tenant string) string {
return "postgres://localhost/" + tenant
}

Apply migrations to each with migrate.Up, which is safe to call from several processes at once.

No cross-database transactions

A transaction can't span databases, and ant has no two-phase commit. Use an outbox to carry a change from one to the other.