# How to Handle Database Migrations with domainx in Gorig: A Complete Guide

> Master database migrations in Gorig using domainx. Learn to register schema changes with domainx.AutoMigrate and execute them automatically with domainx.Start().

- Repository: [Jom/gorig](https://github.com/jom-io/gorig)
- Tags: how-to-guide
- Published: 2026-03-04

---

**Use `domainx.AutoMigrate` in your model package's `init()` function to register schema changes, then call `domainx.Start()` in your main function to automatically execute migrations against your configured database service.**

Handling database migrations with domainx in Gorig provides a declarative, driver-agnostic approach to schema management. The `jom-io/gorig` framework abstracts migration complexity through its `domainx` package, allowing you to define model changes alongside your domain logic while the framework handles execution timing and database-specific implementation details.

## Understanding the domainx Migration Architecture

The migration system centers on a lightweight registry pattern that separates migration definitions from execution logic, defined primarily in [`domainx/migration.go`](https://github.com/jom-io/gorig/blob/main/domainx/migration.go) and orchestrated through [`domainx/service.go`](https://github.com/jom-io/gorig/blob/main/domainx/service.go).

### Core Components

The **`Migration` struct** (lines 19-22 in [`domainx/migration.go`](https://github.com/jom-io/gorig/blob/main/domainx/migration.go)) encapsulates a `DBFunc` returning a `ConTable` and an optional index list. These instances are collected in the global **`MigrationList`** slice (line 17), which acts as the central registry for all pending migrations.

The **`DBService` interface** (lines 76-80 in [`domainx/service.go`](https://github.com/jom-io/gorig/blob/main/domainx/service.go)) defines the contract for concrete database drivers:

```go
type DBService interface {
    Name() string
    Migrate(con *Con, tableName string, value ConTable, indexList []Index) error
    // ... additional methods
}

```

### Migration Execution Flow

When your application starts, the following sequence occurs:

1. **Registration**: During package initialization, `domainx.AutoMigrate` (lines 61-67 in [`domainx/api.go`](https://github.com/jom-io/gorig/blob/main/domainx/api.go)) appends migration entries to `MigrationList`.
2. **Startup**: `domainx.Start` invokes `service.Start`, which initializes all database connections.
3. **Execution**: After a 1-second grace period (lines 29-34 in [`domainx/service.go`](https://github.com/jom-io/gorig/blob/main/domainx/service.go)), the system iterates through `MigrationList` and calls `service.Migrate` for each entry.
4. **Resolution**: `service.Migrate` (lines 47-73) resolves the appropriate `DBService` implementation and delegates schema creation.

## Registering Migrations with AutoMigrate

To handle database migrations with domainx in Gorig, you define models that embed `domainx.Con` and register them using the `AutoMigrate` helper.

### Defining a Model

Create a struct that embeds `domainx.Con` and implements the `ConTable` interface:

```go
package model

import "github.com/jom-io/gorig/domainx"

type User struct {
    domainx.Con                // Embeds base Con with connection metadata
    Name  string `gorm:"size:64"`
    Email string `gorm:"size:128;uniqueIndex"`
}

// TableName satisfies the ConTable interface
func (User) TableName() string {
    return "users"
}

```

### Registering the Migration

In the same package, use an `init` function to register the migration:

```go
func init() {
    domainx.AutoMigrate(
        func() domainx.ConTable { return &User{} },
        // Define a unique index on the Email column using CtIdx
        domainx.CtIdx(domainx.Unique, "email"),
    )
}

```

The **`CtIdx` function** (lines 36-58 in [`domainx/api.go`](https://github.com/jom-io/gorig/blob/main/domainx/api.go)) constructs an `Index` value specifying the index type (`Unique` or standard), field names, and optional custom name. When `AutoMigrate` executes, it creates a `Migration` instance containing your function and index definitions, appending it to the global `MigrationList`.

## Implementing Database-Specific Migration Logic

While `domainx` handles orchestration, concrete schema operations are implemented by services satisfying the `DBService` interface.

### The DBService Interface Contract

As defined in [`domainx/service.go`](https://github.com/jom-io/gorig/blob/main/domainx/service.go) (lines 76-80), your database driver must implement:

```go
type DBService interface {
    Name() string
    Migrate(con *Con, tableName string, value ConTable, indexList []Index) error
    // Additional methods for queries, transactions, etc.
}

```

The `Migrate` method receives the connection configuration, table name, model instance, and any indexes defined via `CtIdx`.

### Example MySQL Implementation

Below is a simplified implementation showing how to handle the `Migrate` call for MySQL using GORM:

```go
type MySQLService struct {
    db *gorm.DB
}

func (s *MySQLService) Migrate(
    con *domainx.Con,
    tableName string,
    value domainx.ConTable,
    indexList []domainx.Index,
) error {
    // Auto-create or update table schema
    if err := s.db.AutoMigrate(value); err != nil {
        return fmt.Errorf("auto migrate failed: %w", err)
    }
    
    // Apply custom indexes defined via CtIdx
    for _, idx := range indexList {
        if idx.IdxType == domainx.Unique {
            // Create unique index on specified fields
            s.db.Model(value).Clauses(
                clause.Unique{Column: clause.Column{Name: idx.Fields[0]}},
            ).Create(value)
        }
    }
    
    return nil
}

```

Register this service during bootstrap:

```go
func init() {
    domainx.RegisterDBService(domainx.Mysql, &MySQLService{db: gormDB})
}

```

## Starting the Application and Executing Migrations

Once models and services are registered, starting the application triggers the migration process automatically.

In your [`main.go`](https://github.com/jom-io/gorig/blob/main/main.go):

```go
func main() {
    // domainx.Start initializes connections and runs pending migrations
    if err := domainx.Start("", "8080"); err != nil {
        log.Fatalf("failed to start domainx: %v", err)
    }
}

```

According to the source in [`domainx/service.go`](https://github.com/jom-io/gorig/blob/main/domainx/service.go) (lines 29-34), `service.Start` waits approximately 1 second after initializing database connections before iterating over `MigrationList`. This grace period ensures all `DBService` implementations are fully registered and connected before schema modifications begin.

Each registered migration is then executed via `service.Migrate` (lines 47-73), which:
1. Invokes the migration's `DBFunc` to obtain the `ConTable` instance
2. Extracts the connection via `GetCon()`
3. Dispatches to the appropriate `DBService.Migrate` implementation based on `con.GetConType()`

## Summary

Handling database migrations with domainx in Gorig follows a declarative, service-oriented pattern:

- **Define models** by embedding `domainx.Con` and implementing the `ConTable` interface with a `TableName()` method
- **Register migrations** using `domainx.AutoMigrate` in package `init()` functions, optionally specifying indexes via `CtIdx`
- **Implement drivers** by satisfying the `DBService` interface, specifically implementing the `Migrate` method to handle schema creation
- **Execute automatically** by calling `domainx.Start`, which applies all pending migrations from `MigrationList` after a brief initialization delay

This architecture keeps migration logic co-located with domain models while remaining agnostic to the underlying database engine.

## Frequently Asked Questions

### How does domainx track which migrations have already been applied?

The `domainx` package does not maintain a separate schema migrations table like traditional tools such as Flyway or Liquibase. Instead, it relies on the underlying database driver's `Migrate` implementation—such as GORM's `AutoMigrate`—to perform idempotent schema changes. Each time `domainx.Start` runs, it re-applies all registered migrations from `MigrationList`, and the driver determines whether tables or indexes need creation or modification.

### Can I use domainx migrations with databases other than MySQL?

Yes, the `domainx` architecture is explicitly database-agnostic. You can handle database migrations with domainx in Gorig for any storage backend by implementing the `DBService` interface defined in [`domainx/service.go`](https://github.com/jom-io/gorig/blob/main/domainx/service.go) (lines 76-80). Whether you are using PostgreSQL, MongoDB, SQLite, or a custom datastore, you simply register your implementation via `domainx.RegisterDBService` before calling `domainx.Start`, and the framework routes migration calls to your driver.

### What happens if a migration fails during application startup?

If a migration fails, `service.Migrate` (lines 47-73 in [`domainx/service.go`](https://github.com/jom-io/gorig/blob/main/domainx/service.go)) returns an error that propagates up through `domainx.Start`, causing the application to exit with a fatal error. This fail-fast behavior ensures that your application does not start with an incomplete or inconsistent schema. You should wrap critical migrations in transactions within your `DBService.Migrate` implementation to allow rollback on error, though this capability depends on the specific database driver's transaction support.

### Is it possible to run migrations manually instead of automatically on startup?

While `domainx` is designed to run migrations automatically via `domainx.Start`, you can trigger them manually by interacting directly with `MigrationList` and `service.Migrate`. However, this is not the intended pattern. To disable automatic execution, avoid calling `domainx.Start` and instead manually iterate over `domainx.MigrationList`, invoking the migration logic yourself. Note that this requires careful management of the database connection lifecycle, as `service.Migrate` depends on a fully initialized `DBService` being registered via `domainx.RegisterDBService`.