Simple Golang database seeding abstraction for Gorm
One of the most feature-full ORMs for Go that I have worked with is Gorm. If you would like to learn more about it, I recommend checking out its official website and its documentation.
Recenlty I wanted to write a small database seeding abstraction. Database seeding is a process in which an initial set of data is provided to a database when it is being set up or installed.
Yet, in true Go fashion my goal was to keep the abstraction tiny, yet provide some structure to folks that would write seeds in this application. This is absolutely not the only way you can achieve this, the options are plenty. Still, this is one approach that I have found to be working well.
We will have three different components:
- Create a
Seed
abstraction. - Identify path for the seeds and how to collect them in a single
seeds
package. - Create a
seeder
command line tool that can run the seeds against a database.
First, to define a small Seed
type that would be coupled to the gorm.DB
connection instance:
// pkg/seed/seed.go
package seed
import (
"github.com/jinzhu/gorm"
)
type Seed struct {
Name string
Run func(*gorm.DB) error
}
Next, we need to define a path where we’ll store the seeds and how to collect them:
// pkg/seeds/seeds.go
package seeds
import (
"github.com/fteem/seeding/pkg/seed"
)
func All() []seed.Seed {
return []seed.Seed{}
}
The seeds
package will contain all instances of seed.Seed
and collect them
in the All
function. This will allow the third component, a CLI tool, to
iterate over them and execute them against a database. Additionally, in the same
seeds
package we can have separate files for seeding various types of data.
For example:
// pkg/seeds/users.go
package seeds
func CreateUser(db *gorm.DB, name string, age int) error {
return db.Create(&users.User{Name: name, Age: age}).Error
}
The usage of the function in All
:
// pkg/seeds/seeds.go
import (
"github.com/fteem/seeding/pkg/seed"
)
func All() []seed.Seed {
return []seed.Seed{
seed.Seed{
Name: "CreateJane",
Run: func(db *gorm.DB) error {
CreateUser(db, "Jane", 30)
},
},
seed.Seed{
Name: "CreateJohn",
Run: func(db *gorm.DB) error {
CreateUser(db, "John", 30)
},
},
}
}
The example above is contrived, but it can work really well if you have to compose seeds using multiple such functions.
Last, a very simple command line tool that can running the seeds we defined:
// pkg/seeder/seeder.go
package main
import (
"log"
"github.com/fteem/seeding/pkg/seeds"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
func main() {
dbConn := openConnection()
defer dbConn.Close()
for _, seed := range seeds.All() {
if err := seed.Run(dbConn); err != nil {
log.Fatalf("Running seed '%s', failed with error: %s", seed.Name, err)
}
}
}
func openConnection() *gorm.DB {
db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
if err != nil {
log.Fatalf("Couldn't establish database connection: %s", err)
}
return db
}
The binary produced by this is quite a simple one: loads all of the seeds using
the seeds.All
function, iterates over the collection and executes their Run
function. If an error occurs, the whole program bombs.
That’s it. Very thin and simple way to abstract away your database seeds using Gorm. Before you go, let me know in the comments how you approach database seeding.