in entgo orm, is it necessary to keep the schema limited to one entity per file? whats the best practice recommendations
No, it’s not necessary to limit Ent schema files to one entity per file — but it is strongly recommended as a best practice. Here’s why:
entc expects things.Stick with one entity per file unless you have a compelling, narrow-scoped reason to deviate. Ent’s tooling, ecosystem, and community all lean in this direction — swimming upstream isn’t worth it here.
the following is the entgo schema for an entity called abtest
package schema
import ( “time”
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"github.com/google/uuid" )
// ABTest holds holds the schema definition for the ABTest entity. type ABTest struct { ent.Schema }
// Fields of the Template. func (ABTest) Fields() []ent.Field { return []ent.Field{ field.UUID(“id”, uuid.UUID{}). Default(uuid.New). Immutable(), field.String(“name”), field.String(“description”).Optional(), field.String(“status”).Default(“draft”), //”draft”,”active”,”paused”,”completed” field.UUID(“stageId”, uuid.UUID{}).Unique(), field.UUID(“campaign_id”, uuid.UUID{}), field.String(“variants”), field.Time(“start_at”).Optional(), field.Time(“end_at”).Optional(), field.String(“success_metric”), // sign, share, donate etc field.Time(“created_at”).Default(time.Now), field.Time(“updated_at”).Default(time.Now).UpdateDefault(time.Now), } }
// Edges of the Template. func (ABTest) Edges() []ent.Edge { return []ent.Edge{ edge.From(“campaigns”, Campaign.Type).Ref(“abtests”), } }
an abtest can have multiple variants, called abtestvariant with the following schema:
package schema
import ( “time”
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"github.com/google/uuid" )
// ABTestVariant holds holds the schema definition for the ABTestVariant entity. type ABTestVariant struct { ent.Schema }
// Fields of the Template. func (ABTestVariant) Fields() []ent.Field { return []ent.Field{ field.UUID(“id”, uuid.UUID{}). Default(uuid.New). Immutable(), field.UUID(“test_id”, uuid.UUID{}). Immutable(), field.String(“name”), field.String(“description”).Optional(), field.Int(“traffic_percentage”), field.JSON(“variant_config”, map[string]any{}), field.Time(“created_at”).Default(time.Now), field.Time(“updated_at”).Default(time.Now).UpdateDefault(time.Now), } }
give me edges and indexes for both entity relations in goent
To model the relationship correctly between ABTest and ABTestVariant in Ent, and to optimize for performance and relational integrity, here’s what you should add:
Edge (one ABTest has many variants):
// Edges of the ABTest.
func (ABTest) Edges() []ent.Edge {
return []ent.Edge{
edge.From("campaigns", Campaign.Type).
Ref("abtests"),
edge.To("variants", ABTestVariant.Type).
StorageKey(edge.Column("test_id")),
}
}
Index (optional for query performance, depending on access patterns — e.g. querying ABTests by stageId or campaign_id):
func (ABTest) Indexes() []ent.Index {
return []ent.Index{
index.Fields("stageId").Unique(),
index.Fields("campaign_id"),
}
}
Edge (each variant belongs to one test):
// Edges of the ABTestVariant.
func (ABTestVariant) Edges() []ent.Edge {
return []ent.Edge{
edge.From("ab_test", ABTest.Type).
Ref("variants").
Field("test_id").
Unique().
Required(),
}
}
Index (to speed up joins and group-by on test_id):
func (ABTestVariant) Indexes() []ent.Index {
return []ent.Index{
index.Fields("test_id"),
}
}
StorageKey matches the field used in ABTestVariant (test_id), otherwise Ent will create a separate join table or get confused about foreign keys..Field().Required().Unique() on the From edge in ABTestVariant ensures referential integrity without ambiguity.name, status, etc., based on query needs.Let me know if you’re also planning to soft-delete or version these entities — that would change indexing and defaults.
there is one more schema which is related to both test and variant, what kind of edges and indexes might this have
// Fields of the ABTestAssignment. func (ABTestAssignment) Fields() []ent.Field { return []ent.Field{ field.UUID(“id”, uuid.UUID{}). Default(uuid.New). Immutable(), field.UUID(“test_id”, uuid.UUID{}). Immutable(), field.UUID(“variant_id”, uuid.UUID{}). Immutable(), field.String(“session_id”).Optional(), field.String(“user_id”).Optional(), field.Time(“assigned_at”).Default(time.Now), } }
This ABTestAssignment entity clearly acts as a join entity (or fact table) connecting:
ABTest (via test_id)ABTestVariant (via variant_id)You want referential integrity, query efficiency, and clear cardinality. Here’s what to add:
// Edges of the ABTestAssignment.
func (ABTestAssignment) Edges() []ent.Edge {
return []ent.Edge{
edge.From("test", ABTest.Type).
Ref("assignments").
Field("test_id").
Required().
Unique(),
edge.From("variant", ABTestVariant.Type).
Ref("assignments").
Field("variant_id").
Required().
Unique(),
}
}
If a single assignment must belong to one test and one variant, this setup is correct. If you’re planning to allow multiple assignments per test or variant, drop the .Unique().
In ABTest:
edge.To("assignments", ABTestAssignment.Type),
In ABTestVariant:
edge.To("assignments", ABTestAssignment.Type),
func (ABTestAssignment) Indexes() []ent.Index {
return []ent.Index{
index.Fields("test_id"),
index.Fields("variant_id"),
index.Fields("session_id"),
index.Fields("user_id"),
index.Fields("test_id", "variant_id"), // for joins / dedupes
}
}
You might also consider a composite uniqueness constraint if you want to enforce “one assignment per user/session per test”:
index.Fields("test_id", "user_id").Unique(),
Or if session_id is primary:
index.Fields("test_id", "session_id").Unique(),
Don’t assume UUIDs are sufficient indexes on their own — join performance can degrade without explicit indexes, especially under write-heavy load.
Let me know how you plan to assign (randomly? deterministic by hash?) — it could influence schema decisions too.
set “ABTest” foreign-keys: column “test_id” definition on edge “variants” should be replaced with Field(“test_id”) on its reference “ab_test”
Yes — Ent is telling you that the foreign key column test_id must be defined on the referencing side (ABTestVariant) via .Field("test_id"). The ABTest schema’s edge definition should not redundantly define the column.
In ABTestVariant:
edge.From("ab_test", ABTest.Type).
Ref("variants").
Field("test_id").
Required().
Unique(),
In ABTest:
edge.To("variants", ABTestVariant.Type),
❗Remove the .StorageKey(edge.Column("test_id")) from the ABTest schema — it’s unnecessary and causes the conflict.
Do this:
.Field("test_id") only on the From edge (inside ABTestVariant).To("variants", ABTestVariant.Type) on the ABTest sideField() declarationThat satisfies Ent’s codegen rules and avoids duplicated foreign key definitions.
resolve “ABTestAssignment” relations: mismatch type for back-ref “variants” of ABTestAssignment.test <-> ABTest.variants
This error means Ent is confused because you’re linking ABTestAssignment.test to ABTest.variants, but variants points to ABTestVariant, not ABTestAssignment.
You likely defined this in ABTestAssignment:
edge.From("test", ABTest.Type).
Ref("variants"). // ❌ wrong ref
Field("test_id").
Required().
Unique(),
But variants in ABTest refers to ABTestVariant, not ABTestAssignment.
In ABTestAssignment, fix the .Ref() target:
edge.From("test", ABTest.Type).
Ref("assignments"). // ✅ must match the edge in ABTest
Field("test_id").
Required(),
In ABTest, define the back edge:
edge.To("assignments", ABTestAssignment.Type),
Similarly, for the variant link in ABTestAssignment:
edge.From("variant", ABTestVariant.Type).
Ref("assignments").
Field("variant_id").
Required(),
And in ABTestVariant:
edge.To("assignments", ABTestAssignment.Type),
.Ref("X") must point to a real edge.To("X", ...) on the referenced type.Ref("variants") instead of Ref("assignments")) breaks codegen.