dbx

Seederek

Idempotens alapadat-betöltés migráció után, first-or-create-cel, opcionális feltétellel és multi-tenant támogatással.

A migrációk a sémát hozzák létre; a seederek az adatot: egy admin usert, szerepköröket, lookup táblákat. A gpsystem ehhez a seed csomagot és egy központi seeds/ package-et generál minden projektbe, a cmd/migrate binárison keresztül futtatva.

go run ./cmd/migrate up --seed   # migrál, majd seedel
go run ./cmd/migrate seed        # csak seedel, migráció nélkül

A Registry és a Seeder

Egy seeder egy seed.Seeder érték: név, opcionális Guard (eldönti, lefusson-e egyáltalán) és a Run törzs. Az add seeder parancs generálja és regisztrálja a seeds/ package-ben:

seeds/admin_user.go
func adminUser(deps Deps) seed.Seeder {
    return seed.Seeder{
        Name: "admin_user",
        Run: func(ctx context.Context) error {
            return nil
        },
    }
}
seeds/register.go
func Register(reg *seed.Registry, deps Deps) {
    reg.Add(adminUser(deps))
    // gpsystem:seeds
}

A Deps a modulok Dependencies-ét tükrözi: a query-felület (*pg.DB vagy *bun.DB, a projekt --db választása szerint) és a dbx.Transactor. A Registry.Run regisztrálási sorrendben futtatja a seedereket: számít, ha az egyik egy másik által létrehozott sorra hivatkozik (pl. a szerepkörök az admin user előtt).

A lelke: first-or-create

A seederek idempotensek: kétszeri futtatás nem duplikál. A bun-alapú projektekhez ehhez van egy generikus primitív, a seed/bunx.FirstOrCreate:

user := &User{Email: "admin@example.com", Name: "Admin"}
created, err := bunx.FirstOrCreate(ctx, deps.DB, user, "email")

Megkeresi az email oszlop alapján a meglévő sort; ha van, betölti user-be (a mezők a valódi sor értékeire állnak), ha nincs, beszúrja user-t úgy, ahogy van. A bunx.From-on át fut, tehát csatlakozik egy WithinTransaction-nal nyitott tranzakcióhoz, ha van ilyen. Összetettebb egyezésvizsgálathoz (nem sima oszlop-egyenlőség) ott a FirstOrCreateWhere, ami egy tetszőleges *bun.SelectQuery lezárást vár.

Natív (pgx) projektekhez nincs kit-oldali helper: a seeder saját INSERT ... ON CONFLICT (oszlop) DO NOTHING SQL-t ír a pg.DBTX-en, ugyanúgy, ahogy bármelyik más natív repository.

Feltételes futás: Guard és ErrSkip

Egy seedernek el kell tudnia dönteni, hogy egyáltalán lefusson-e: erre való a Guard. Kiértékelése megelőzi a Run-t; false esetén a runner skipnek logolja, nem hibának:

seed.Seeder{
    Name:  "demo_data",
    Guard: seed.OnlyEnv(currentEnv, "dev", "staging"),
    Run:   loadDemoData,
}

Amikor a feltétel csak futásidőben derül ki (pl. a tenant tartalmától függ), a Run maga is kiszállhat: a seed.ErrSkip visszaadása ugyanúgy skipnek számít, nem hibának.

Run: func(ctx context.Context) error {
    t, _ := seed.TenantFromContext(ctx)
    if t.Data.(TenantConfig).Plan != "enterprise" {
        return seed.ErrSkip
    }
    _, err := bunx.FirstOrCreate(ctx, deps.DB, &EnterpriseDefaults{TenantID: t.ID}, "tenant_id")
    return err
},

Multi-tenant: eltérő tartalom tenantonként

Egy seeder ugyanaz a kód lehet minden tenantra, miközben más-más adatot hoz létre: a Registry.Run a seed.WithTenants opcióval tenantonként fut, a tenantot a contextbe téve.

tenants := []seed.Tenant{
    {ID: "acme", Data: TenantConfig{Plan: "enterprise"}},
    {ID: "globex", Data: TenantConfig{Plan: "starter"}},
}
if err := reg.Run(ctx, seed.WithTenants(tenants)); err != nil {
    log.Fatal(err)
}

A Tenant.Data tetszőleges, projekt-specifikus payload; a seeder típuskonverzióval (type assertion) éri el. A seed.OnlyTenants(ids...) egy kész Guard arra az esetre, ha egy seeder csak bizonyos tenantokra fusson.

A kit nem ad kész tenant-forrást (nincs tenant tábla, nincs search_path-váltás, nincs RLS): a Tenant-lista honnan jön (env, egy tábla, egy admin API) a projektre van bízva. A WithTenants egy kampó, nem egy kész multi-tenant infrastruktúra.

Miért nem a migrációk része?

A migráció és a seedelés más-más életciklust futhat: egy migráció mindig lefut, egy seeder gyakran csak fejlesztésben vagy első telepítéskor kell. Ezért a --seed flag és a seed subcommand explicit: go run ./cmd/migrate up önmagában sosem ír adatot, csak sémát. Éles környezetben ez azt jelenti, hogy a seedelés bekapcsolása (vagy kihagyása) a deploy-pipeline döntése, nem a migrációs lépés hallgatólagos mellékhatása.

Copyright © 2026