ããã«ã¡ã¯ãã¹ããŒããã£ã³ã ãšã³ãžãã¢ã®äžç°ã§ãã çããã¯Goã®ORMã«ã¯äœã䜿ãããŠããŸããïŒ æåã©ããã ãšæ©èœã®è±å¯ãª GORM ãååŸããŒã¿ã®ãããã³ã°éšåã ããæ
ãã·ã³ãã«ãª sqlx ã æè¿ã ãšããŒãã«å®çŸ©ããã¢ãã«ã³ãŒãã®èªåçæããŠããã SQLBoiler ãªã©ãGoã«ã¯å€ãã®ORMããããŸãã çè
ã®ORMéæŽã¯ä»¥äžã®ããã«ãªã£ãŠãŸãã Active Record(Ruby on Rails): 2幎ã»ã© GORM(Go): å幎ã»ã© åŒç€Ÿã®ãããã¯ãã®ããã¯ãšã³ã㯠Ruby on Rails ã§äœãããŠãããã®ãã»ãšãã©ã§ãã Ruby on Rails ãå©çšããŠã®éçºçµéšãç§ã®ãã£ãªã¢ã®å€§åãå ããŠããããšããããå人çã« ActiveRecord ã®ãããªæ©èœã®ç¶²çŸ
çã®é«ãORMã«ã¯å®å¿æãèŠããŸãã å幎åããæ°èŠã§éçºãå§ãããããã¯ãã«ãŠãæ°ãã« Go ãå©çšãå§ããŸããã åŒç€Ÿã®Go補ãããã¯ãã®ORMã«ã¯ GORM ãå©çšããŠããŸãã GORMã¯ããã¥ã¡ã³ããå
å®ããŠããæ©èœèªäœãè±å¯ã§ãããããç¹ã«äºãªãå©çšã§ããŠããŸãã ããããvalidatorãçµã¿èŸŒã¿ã§ãªãç¹ããAuto migrationã§ up ã¯å¯èœã§ãã down ã¯ã§ããªãç¹ãªã©è¥å¹²ã®ç©è¶³ããªããæããŠããŸãã ããã§ãæ¬èšäºã§ã¯ GORM ã«åã£ãŠä»£ããæ°ããªORMãæ¢ãã¹ãã Facebook Connectivity ããŒã ããéçºãããã ent ãšããORMã調æ»ããŠã¿ãŸãã ãentããšã¯ ç¹åŸŽ ã°ã©ãæ§é è§Šã£ãŠã¿ã ã¹ããŒãã®å®çŸ© ent/schema/user.go ent/schema/company.go ent/schema/time_mixin.go Mixin Fields Edges ent/schema/user.go ent/schema/company.go ã³ãŒãçæ CRUD APIãäœæããŠã¿ã DBæ¥ç¶ READ UPDATE DELETE CREATE(Transaction) è¯ãã£ãç¹ã»ããã²ãšã€ã ã£ãç¹ è¯ãã£ãç¹ èªåçææ©èœã®åŒ·åã ããã¥ã¡ã³ãã®å
å®åºŠ æ©èœã®è±å¯ã ããã²ãšã€ã ã£ãç¹ Schemaã®ç®¡ç ãŸãšã ãentããšã¯ åè¿°ããããã«ã ent 㯠Facebook Connectivity ããŒã ã«ããéçºãããŠããGoã®ORMã§ãã GitHubãªããžããªããReleaseå±¥æŽã蟿ã£ãŠã¿ããš v0.1.0 ã2020幎1æã«å
¬éãããŠãããGoã®ORMã®äžã§ã¯æ¯èŒçæ°ããæ¹ã«åé¡ãããã®ã§ã¯ãªãã§ããããã ç¹åŸŽ ent ã®ç¹åŸŽã å
¬åŒ ããåŒçšãããšä»¥äžã§ãã ã·ã³ãã«ãªããããã¯ãã«ãªGoã®ãšã³ãã£ãã£ãã¬ãŒã ã¯ãŒã¯ã§ãããå€§èŠæš¡ãªããŒã¿ã¢ãã«ãæã€ã¢ããªã±ãŒã·ã§ã³ã容æã«æ§ç¯ã»ä¿å®ã§ããããã«ããŸãã ã»Schema As Code(ã³ãŒããšããŠã®ã¹ããŒã) - ããããããŒã¿ããŒã¹ã¹ããŒããGoãªããžã§ã¯ããšããŠã¢ãã«åããŸãã ã»ä»»æã®ã°ã©ããç°¡åã«ãã©ããŒã¹ã§ããŸã - ã¯ãšãªãéçŽã®å®è¡ãä»»æã®ã°ã©ãæ§é ã®èµ°æ»ã容æã«å®è¡ã§ããŸãã ã»100%éçã«åä»ããããæç€ºçãªAPI - ã³ãŒãçæã«ããã100%éçã«åä»ããããææ§ãã®ãªãAPIãæäŸããŸãã ã»ãã«ãã¹ãã¬ãŒãžãã©ã€ã - MySQLãPostgreSQLãSQLiteãGremlinããµããŒãããŠããŸãã ã»æ¡åŒµæ§ - Goãã³ãã¬ãŒãã䜿çšããŠç°¡åã«æ¡åŒµãã«ã¹ã¿ãã€ãºã§ããŸãã ã»Schema As Code(ã³ãŒããšããŠã®ã¹ããŒã) - ããããããŒã¿ããŒã¹ã¹ããŒããGoãªããžã§ã¯ããšããŠã¢ãã«åããŸãã ã»ä»»æã®ã°ã©ããç°¡åã«ãã©ããŒã¹ã§ããŸã - ã¯ãšãªãéçŽã®å®è¡ãä»»æã®ã°ã©ãæ§é ã®èµ°æ»ã容æã«å®è¡ã§ããŸãã ent ã§ã¯ã¹ããŒããã¡ã€ã«ã®å®çŸ©ããGoã®Generatorãå©çšããŠã¢ãã«ãDBã¹ããŒããèªåã§çæããŠãããŸããèªåçæããã¢ãã«ã«ã¯å®çŸ©å
容ãå
ã«ã¯ãšãªãã«ãçšã®æ±çšé¢æ°ãäœæãããDBãžã®åŠçå®è¡æã«ã¯ãã®ã¯ãšãªãã«ãçšã®é¢æ°ããã§ãŒã³ããŠå®çŸãããã¯ãšãªãçµã¿ç«ãŠãŠãããŸãã ã»100%éçã«åä»ããããæç€ºçãªAPI - ã³ãŒãçæã«ããã100%éçã«åä»ããããææ§ãã®ãªãAPIãæäŸããŸãã Goã«ã¯ v1.17.X æç¹ã§ãžã§ããªã¯ã¹ãå
¥ã£ãŠããªãããšããããGORMãªã©ä»ã®ORMã§ã¯ interface{} ãå©çšããæœè±¡åã§ãªãŒãã³ã«åŒæ°ãåãåããå
éšã§åãå€å¥ãããããªå®è£
ãå€ããšæããŸãã ent ã§ã¯ã¹ããŒãå®çŸ©ããã¢ãã«ããã£ãŒã«ãããšã«ã³ãŒããèªåçæãããããããããã®åã«åã£ã颿°ãå©çšã§ã100%ã®éçãªåä»ããå®çŸãããŠããŸãã ãŸãã ent ã GORM åæ§ã«ããã¥ã¡ã³ããå
å®ããŠããŸãã æ¥æ¬èªç¿»èš³ããããŠãããæ¬èšäºã®å·çã«ããã ent ãå®éã«å©çšããŠã¿ãéã«çããå°ãããšã¯ã»ãŒã»ãŒ å
¬åŒããã¥ã¡ã³ã ãåç
§ããã°è§£æ±ºã§ããŸããã ã°ã©ãæ§é ent ã§ã¯ã°ã©ãæ§é ã«åºã¥ããŠã¹ããŒããå®çŸ©ããŠãããŸãã ã°ã©ãæ§é ãšã¯ä»¥äžã®å³ã®ããã«ããŒã(ç¯ç¹ã»é ç¹ãç¹)ã®éåãšãšããž(æã»èŸºãç·)ã®éåã§æ§æãããæ§é ã®ããšã§ãããã®ããã«æ§é åããããšã§ããŸããŸãªãªããžã§ã¯ãã®é¢é£ã衚ãããšãã§ããŸãã ãã®å³ã§ã¯åç
§å
ãåç
§å
ã衚çŸããªãç¡åã°ã©ããæžãããŠããŸããã ent ã®å Žåã¯çŽã¥ããç¢å°ã§è¡šãæåã°ã©ãã§æ§é åãããŸãã ã°ã©ã(WikiPediaãã) WikiPedia - ã°ã©ãçè« ent ã«ãããããŒãã¯ã¢ãã«ããšããžã¯ã¢ãã«ã®ãªã¬ãŒã·ã§ã³ãæããŸãã åæåæç¹ã§ã®ã¹ããŒãã«ã¯äžã€ã®ããŒãã«å¯ŸããŠã Fields ã Edges ã¡ãœãããçããç¶æ
ã§ã³ãŒããçæãããŸãã现ããªå®çŸ©æ¹æ³ã¯åŸè¿°ããŸãããããã§ Fields ã«ã¯ã¢ãã«ã®ãã£ãŒã«ããã Edges ã«ã¯ãã¢ãã«ã®ãªã¬ãŒã·ã§ã³ãå®çŸ©ããŸãã è§Šã£ãŠã¿ã ããã§ã¯å®éã« ent ãè§Šã£ãŠã¿ãŸãã å®è£
ç°å¢ã¯ä»¥äžã§ãã OS: macOS BigSur Go: v1.17.1 MySQL: v5.7.35 -- ãµã³ãã«ã¢ããªã§äœ¿çšããŠããã©ã€ãã©ãª -- ORM: (ent)[https://entgo.io/ent] ã«ãŒã¿ãŒ: (chi)[https://github.com/go-chi/chi] Nullå€: (null)[https://github.com/guregu/null] ãµã³ãã«ã¢ããªã®ãœãŒã¹ã³ãŒãã¯(ã³ã³) https://github.com/kiki-ki/lesson-ent ããåç
§å¯èœã§ãã åãã«äœæ¥çšã«ã¯ãŒã¯ã¹ããŒã¹ãåãã ent ã®CLIãã€ã³ã¹ããŒã«ããŸãã go install entgo.io/ent/cmd/ent@latest ã¹ããŒãã®å®çŸ© ä»åã¯ä»¥äžã®ããŒã¿æ§é ã§äœæããŠãããŸãã companies : users = 1 : N companies --- id: bigint auto increment pk name: varchar(255) not null created_at: timestamp updated_at: timestamp --- users --- id bigint auto increment pk company_id: bigint not null name: varchar(255) not null email: varchar(255) not null unique role: enum('admin', 'normal') comment: varchar(255) nullable created_at: timestamp updated_at: timestamp --- 以äžã®ã³ãã³ããå®è¡ããŠã User ã Company ã¹ããŒããã¡ã€ã«ãèªåçæããŸãã ent init User Company ent/schema ãã£ã¬ã¯ããªé
äžã«åã¢ãã«ã®ã¹ããŒããã¡ã€ã«ãçæãããŸããã ãã®ãã¡ã€ã«ãç·šéããŠãããŸãã äžè¿°ã®ããŒã¿æ§é ãåçŸããããã«ã以äžã®ããã«ã¹ããŒããå®çŸ©ããŸããã ent/schema/user.go package schema ...snip // User holds the schema definition for the User entity. type User struct { ent.Schema } // Mixin of the User. func (User) Mixin() []ent.Mixin { return []ent.Mixin{ TimeMixin{}, } } // Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.Int( "company_id" ), field.String( "name" ), field.String( "email" ).Unique(), field.Enum( "role" ).Values( "admin" , "normal" ), field.Text( "comment" ). Optional(). Nillable(). GoType(null.String{}), } } // Edges of the User. func (User) Edges() []ent.Edge { return []ent.Edge{ edge.From( "company" , Company.Type). Ref( "users" ). Unique(). Required(). Field( "company_id" ), } } ent/schema/company.go package schema ...snip // Company holds the schema definition for the Company entity. type Company struct { ent.Schema } // Mixin of the Company. func (Company) Mixin() []ent.Mixin { return []ent.Mixin{ TimeMixin{}, } } // Fields of the Company. func (Company) Fields() []ent.Field { return []ent.Field{ field.String( "name" ), } } // Edges of the Company. func (Company) Edges() []ent.Edge { return []ent.Edge{ edge.To( "users" , User.Type). Annotations(entsql.Annotation{ OnDelete: entsql.Cascade, }), } } ent/schema/time_mixin.go package schema ...snip type TimeMixin struct { mixin.Schema } func (TimeMixin) Fields() []ent.Field { return []ent.Field{ field.Time( "created_at" ).Immutable().Default(time.Now), field.Time( "updated_at" ).Default(time.Now).UpdateDefault(time.Now), } } ã³ãŒãã®èª¬æãããŠãããŸãã Mixin æåã« Mixin ã¡ãœããã«æ³šç®ããŠã¿ãŸãã ent ã§ã¯æ±çšæ§ã®é«ããã£ãŒã«ã矀ã Mixin ãšããŠåãåºããŠå¥ã¹ããŒãã«æ³šå
¥ã§ããŸãããµã³ãã«ã³ãŒãã§ã¯ time_mixin.go ã« created_at ã updated_at ã®2ãã£ãŒã«ããã»ããã§åãåºãã company ã user ã®äž¡ã¹ããŒãã«MixinããŠããŸããåãã¢ã®Mixinã¯ã©ã€ãã©ãªã®ããã©ã«ãã§ã mixin.Time ãšããŠçµã¿èŸŒãŸããŠããŸãããä»åã¯ã«ã¹ã¿ã Mixinã§æ°ãã«å®çŸ©ããŠã¿ãŸããã Fields 次㫠Fields ã¡ãœããã«æ³šç®ããŠã¿ãŸããããã«ã¯ã¡ãœããåã®éãã«ã¢ãã«ã®ãã£ãŒã«ããå®çŸ©ããŸãã func (User) Fields() []ent.Field { return []ent.Field{ field.Int( "company_id" ), field.String( "name" ). Validate(validation.BlackListString([] string { "hoge" , "fuga" })),, field.String( "email" ).Unique(). Match(regexp.MustCompile(validation.EmailRegex)), field.Enum( "role" ).Values( "admin" , "normal" ), field.Text( "comment" ). Optional(). SchemaType( map [ string ] string { dialect.MySQL: "text" , }). GoType(null.String{}), } } åºæ¬çã«ã¯ãã£ãŒã«ãããšã« ent çµã¿èŸŒã¿ã®åããä»»æã®åãæå®ããã®ã¡ãœããã«ããŒãã«ã®ã«ã©ã åãæž¡ãã°ãã¢ãã«ã®ãã£ãŒã«ããšããŒãã«ã®ã«ã©ã å®çŸ©ã¯å®äºã§ãã id ãã£ãŒã«ãã¯ããã©ã«ãã§äœæãããããèšèŒäžèŠã§ã(ååã®ãã£ãŒã«ããå®çŸ©ããã°èšå®ã®äžæžããå¯èœ)ã ããšã¯å®çŸ©ããåãã£ãŒã«ãã«ã¡ãœãããã§ãŒã³ãã圢ã§çްããå®çŸ©ãããŠãããŸãã Unique: ãŠããŒã¯å¶çŽãããã Values: Enumå€ãèšå®ãã Optional: ã¢ãã«ã®Createæãªã©ã«ãã®ãã£ãŒã«ããä»»æã®é
ç®ã«ãã(ããã©ã«ãã¯å¿
é ) SchemaType: ããŒã¿ããŒã¹ã®ã«ã©ã åãç¬èªã«ãããã³ã°ãã(Textã¡ãœããã®ããã©ã«ã㯠longtext ) GoType: ã¢ãã«ã®ãã£ãŒã«ãåãç¬èªã«ãããã³ã°ãã(ããã§ã¯nullå€ãèš±å¯ã§ããåãæå®) Validate: ããªããŒã·ã§ã³ãé©çšãã Validate ã¡ãœããã®åŒæ°ã«ã¯ãã£ãŒã«ãã®åãåŒæ°ã« error ãè¿ã颿°ãã¢ãµã€ã³ããŸãã以äžã«äœ¿çšäŸã瀺ããŸãã ãŸãã ent çµã¿èŸŒã¿ã®ããªããŒã·ã§ã³ãå€ããããäžèšã®ã³ãŒãã§å©çšããŠãã Must ããã®å
ã®äžã€ã§ããå®çŸ©ãããããªããŒã·ã§ã³ã¯ã¢ãã«ã® Save ã¡ãœãããã³ãŒã«ããã¿ã€ãã³ã°ã§ããã¯ãããŸãã package validation ...snip func BlackListString(blackList [] string ) func (s string ) error { return func (s string ) error { isBlackList := false for _, u := range blackList { if s == u { isBlackList = true break } } if isBlackList { return fmt.Errorf( "%sã¯èš±å¯ãããªãæååã§ã" , s) } return nil } } ent ã§ã¯ãµã³ãã«ã¢ããªã§å©çšããŠãããã®ã®ä»ã«ãå€ãã®ãã£ãŒã«ãã®ãªãã·ã§ã³ã¡ãœãããçšæãããŠããŸãã 詳ãã㯠å
¬åŒããã¥ã¡ã³ã ããåç
§ãã ããã Edges æåŸã« Edges ã¡ãœããã§ããåé ã§ãå°ãè§Šããããã« ent ã«ããããšããžã¯ã¢ãã«éã®ãªã¬ãŒã·ã§ã³ãæããŸãããµã³ãã«ã¢ããªã§ã¯Company-Useréã§One to Manyãªãªã¬ãŒã·ã§ã³ãå®çŸ©ããŸãã ent/schema/user.go ...snip // Edges of the User. func (User) Edges() []ent.Edge { return []ent.Edge{ edge.From( "company" , Company.Type). Ref( "users" ). Unique(). Required(). Field( "company_id" ), } } ent/schema/company.go ...snip // Edges of the Company. func (Company) Edges() []ent.Edge { return []ent.Edge{ edge.To( "users" , User.Type). Annotations(entsql.Annotation{ OnDelete: entsql.Cascade, }), } } äžèšã®ããã«ãªã¬ãŒã·ã§ã³ãå®çŸ©ã§ããŸããããã®èŸºãã¯ããªãã©ã€ãã©ãªåºæãªæžãã£ã·ãã«ãªã£ãŠããå°è±¡ã§ãã Fields åæ§ã« Edges ã«ããªãã·ã§ã³ã¡ãœãããçšæãããŠããããããã䜿ã£ãŠçްããªèšå®ãå¯èœã§ããOne to Manyã®ä»ã«ãOne to OneãMany to Manyãèªå·±ã«ãŒããªã©ã®ãªã¬ãŒã·ã§ã³ã«ã察å¿ããŠããŸãã 詳现㯠å
¬åŒããã¥ã¡ã³ã ããåç
§ãã ããã â»1ã€çåã ã£ãã®ããUserã«å®çŸ©ããŠããå€éšã㌠company_id ã Required ã¡ãœããã§ not null ãªãã£ãŒã«ããšããŠå®çŸ©ããã®ã§ããäžæããããŸããã§ãããå
¬åŒããã¥ã¡ã³ããšåæ§ã®èšè¿°ãããã€ããã ã£ãã®ã§ãã...ããã¡ãæèè
ã®æ¹ããã£ãããã°SNS, ã¯ãŠãã³ã¡ã³ããªã©ã§ãææããã ãããšå©ãããŸãã ã³ãŒãçæ å眮ããé·ããªããŸããããå®çŸ©ããã¹ããŒãã®æ
å ±ãå
ã«ä»¥äžã®ã³ãã³ãã§ã³ãŒããçæããŸãã go generate ./ent å®è¡ãããš ent/ 以äžã«å€§éã®ã³ãŒããçæãããŸãã CRUD APIãäœæããŠã¿ã DBæ¥ç¶ ãŸãã¯DBã«æ¥ç¶ããŸãã ent ã«ã¯çµã¿èŸŒã¿ã§Auto migrationæ©èœãããã®ã§ãã¡ããå©çšããŠã¿ãŸãã package main ...snip func main() { entClient := database.NewEntClient() defer entClient.Close() entClient.Migrate() ...snip } ...snip --- package database ...snip type EntClient struct { *ent.Client } func NewEntClient() *EntClient { dsn := fmt.Sprintf( "%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=true" , os.Getenv( "DB_USER" ), os.Getenv( "DB_PASS" ), os.Getenv( "DB_HOST" ), os.Getenv( "DB_PORT" ), os.Getenv( "DB_NAME" ), ) client, err := ent.Open(dialect.MySQL, dsn) if err != nil { panic (fmt.Sprintf( "failed openning connection to mysql: %v" , err)) } env := os.Getenv( "ENV" ) // ãããã°ã¢ãŒããå©çš if env != "staging" && env != "production" { client = client.Debug() } return &EntClient{client} } func (c *EntClient) Migrate() { err := c.Schema.Create( context.Background(), migrate.WithDropIndex( true ), migrate.WithDropColumn( true ), ) if err != nil { log.Fatalf( "failed creating schema resources: %v" , err) } } go run ./main.go ãå®è¡ãããšMigrateåŠçãèµ°ããŸãã以äžã®å
容ã§ããŒãã«ãäœæãããŸããã mysql> desc users; +------------+------------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+------------------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | created_at | timestamp | YES | | NULL | | | updated_at | timestamp | YES | | NULL | | | name | varchar(255) | NO | | NULL | | | email | varchar(255) | NO | UNI | NULL | | | role | enum('admin','normal') | NO | | NULL | | | comment | text | YES | | NULL | | | company_id | bigint(20) | YES | MUL | NULL | | +------------+------------------------+------+-----+---------+----------------+ 8 rows in set (0.00 sec) mysql> desc companies; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | created_at | timestamp | YES | | NULL | | | updated_at | timestamp | YES | | NULL | | | name | varchar(255) | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec) ç¶ããŠCRUDåŠçãäœæããŸããcontrollerã®å®çŸ©ã¯ä»¥äžã®ããã«ãªã£ãŠãŸãã package controller ...snip // *database.EntClientã¯*ent.Clientãã©ããããæ§é äœ func NewCompanyController(dbc *database.EntClient) CompanyController { return &companyController{ dbc: dbc, ctx: context.Background(), } } type CompanyController interface { Show(http.ResponseWriter, *http.Request) Update(http.ResponseWriter, *http.Request) Delete(http.ResponseWriter, *http.Request) IndexUsers(http.ResponseWriter, *http.Request) CreateWithUser(http.ResponseWriter, *http.Request) } type companyController struct { dbc *database.EntClient ctx context.Context } READ func (c *companyController) Show(w http.ResponseWriter, r *http.Request) { cId, err := strconv.Atoi(chi.URLParam(r, "companyId" )) // error handling company, err := c.dbc.Company.Get(c.ctx, cId) // error handling w.WriteHeader(http.StatusOK) render.JSON(w, r, company) } 2021/10/20 09:11:15 driver.Query: query=SELECT DISTINCT `companies`.`id`, `companies`.`created_at`, `companies`.`updated_at`, `companies`.`name` FROM `companies` WHERE `companies`.`id` = ? LIMIT 2 args=[1] Showã¯åãåã£ã id ã§ companies ããŒãã«ã«æ€çŽ¢ãããããããããã¬ã³ãŒããååŸããã¡ãœããã§ãã *ent.Client(dbc) ãã察象ã®ããŒãã«ã決ã( Company )ãäž»ããŒã§ã®æ€çŽ¢çšã® Get ã¡ãœããã§ã¬ã³ãŒããååŸããŸãã func (c *companyController) IndexUsers(w http.ResponseWriter, r *http.Request) { cId, err := strconv.Atoi(chi.URLParam(r, "companyId" )) // error handling company, err := c.dbc.Company.Get(c.ctx, cId) // error handling users, err := company.QueryUsers().All(c.ctx) // error handling w.WriteHeader(http.StatusOK) render.JSON(w, r, users) } 2021/10/20 09:18:40 driver.Query: query=SELECT DISTINCT `companies`.`id`, `companies`.`created_at`, `companies`.`updated_at`, `companies`.`name` FROM `companies` WHERE `companies`.`id` = ? LIMIT 2 args=[1] 2021/10/20 09:18:40 driver.Query: query=SELECT DISTINCT `users`.`id`, `users`.`created_at`, `users`.`updated_at`, `users`.`company_id`, `users`.`name`, `users`.`email`, `users`.`role`, `users`.`comment` FROM `users` WHERE `company_id` = ? args=[1] IndexUsersã¯ããŸãShowåæ§ã«åãåã£ã id ã§ companies ããŒãã«ã«æ€çŽ¢ããããååŸããäŒæ¥ã«å±ãããŠãŒã¶ãŒã®äžèЧãè¿ãã¡ãœããã§ãã ãŸãã *ent.Client(dbc) ãã察象ã®ããŒãã«ã決ã( Company )ãäž»ããŒã§ã®æ€çŽ¢çšã® Get ã¡ãœããã§ã¬ã³ãŒããäŒæ¥ãååŸããŸãã ãã®åŸãååŸããäŒæ¥ã¢ãã«ãã QueryUsers ã§ã¹ããŒãã§èšå®ãã Users ãšããžã«åããŠã¯ãšãªãå®è¡ããŠããŸãã All ã¯å
šä»¶ååŸã§ãã UPDATE func (c *companyController) Update(w http.ResponseWriter, r *http.Request) { cId, err := strconv.Atoi(chi.URLParam(r, "companyId" )) // error handling company, err := c.dbc.Company.Get(c.ctx, cId) // error handling var req request.CompanyUpdateReq err := render.DecodeJSON(r.Body, &req) // error handling company, err = company.Update().SetName(req.Name).Save(c.ctx) // error handling w.WriteHeader(http.StatusOK) render.JSON(w, r, company) } // ---------- package request ...snip type CompanyUpdateReq struct { Name string `json:"name"` } 2021/10/20 09:27:14 driver.Query: query=SELECT DISTINCT `companies`.`id`, `companies`.`created_at`, `companies`.`updated_at`, `companies`.`name` FROM `companies` WHERE `companies`.`id` = ? LIMIT 2 args=[1] 2021/10/20 09:27:14 driver.Tx(5ca9d42e-1823-4956-8427-f9937f5fb5c6): started 2021/10/20 09:27:14 Tx(5ca9d42e-1823-4956-8427-f9937f5fb5c6).Exec: query=UPDATE `companies` SET `updated_at` = ?, `name` = ? WHERE `id` = ? args=[2021-10-20 09:27:14.615562 +0900 JST m=+1007.701267213 chan2 1] 2021/10/20 09:27:14 Tx(5ca9d42e-1823-4956-8427-f9937f5fb5c6).Query: query=SELECT `id`, `created_at`, `updated_at`, `name` FROM `companies` WHERE `id` = ? args=[1] 2021/10/20 09:27:14 Tx(5ca9d42e-1823-4956-8427-f9937f5fb5c6): committed Updateã¯åãåã£ã id ããäŒæ¥ãååŸãããªã¯ãšã¹ããã©ã¡ãŒã¿ãŒãå
ã«äŒæ¥æ
å ±ãæŽæ°ããã¡ãœããã§ãã ãŸããå
ã»ã©ãšåæ§ã«äŒæ¥ãååŸããŸãã ååŸããäŒæ¥ã¢ãã«ãã Update ãåŒã³åºããŠæŽæ°çšã®ã¯ãšãªãã«ããè¡ããŸããåŸè¿°ã® Set~ ã¯ã»ãã¿ãŒã§æåŸã® Save ã§ã¯ãšãªãå®è¡ããŠããŸãã DELETE func (c *companyController) Delete(w http.ResponseWriter, r *http.Request) { cId, err := strconv.Atoi(chi.URLParam(r, "companyId" )) // error handling company, err := c.dbc.Company.Get(c.ctx, cId) // error handling err = c.dbc.Company.DeleteOne(company).Exec(c.ctx) // error handling w.WriteHeader(http.StatusOK) render.JSON(w, r, fmt.Sprintf( "id=%d is deleted" , cId)) } 2021/10/20 09:31:41 driver.Query: query=SELECT DISTINCT `companies`.`id`, `companies`.`created_at`, `companies`.`updated_at`, `companies`.`name` FROM `companies` WHERE `companies`.`id` = ? LIMIT 2 args=[1] 2021/10/20 09:31:41 driver.Tx(55aded72-c284-490e-8096-8226edafc3f7): started 2021/10/20 09:31:41 Tx(55aded72-c284-490e-8096-8226edafc3f7).Exec: query=DELETE FROM `companies` WHERE `companies`.`id` = ? args=[1] 2021/10/20 09:31:41 Tx(55aded72-c284-490e-8096-8226edafc3f7): committed Delteã¯åãåã£ã id ããäŒæ¥ãååŸãã該åœäŒæ¥ãåé€ããã¡ãœããã§ãã ãŸããå
ã»ã©ãšåæ§ã«äŒæ¥ãååŸããŸãã *ent.Client(dbc) ãã DeleteOne ã§åé€ããã¬ã³ãŒããæå®ã Exec ã§åŠçãå®è¡ããŠããŸãã CREATE(Transaction) func (c *companyController) CreateWithUser(w http.ResponseWriter, r *http.Request) { var req request.CompanyCreateWithUserReq err := render.DecodeJSON(r.Body, &req) // error handling tx, err := c.dbc.Tx(c.ctx) // error handling company, err := tx.Company. Create(). SetName(req.CompanyName). Save(c.ctx) if err != nil { err = util.Rollback(tx, err) // error handling } user, err := tx.User.Create(). SetCompany(company). SetName(req.UserName). SetEmail(req.UserEmail). SetRole(user.RoleAdmin). SetComment(req.UserComment). Save(c.ctx) if err != nil { err = util.Rollback(tx, err) // error handling } err = tx.Commit() // error handling w.WriteHeader(http.StatusOK) render.JSON(w, r, map [ string ] interface {}{ "company" : company, "user" : user, }) } // ---------- package request type CompanyCreateWithUserReq struct { CompanyName string `json:"companyName"` UserName string `json:"userName"` UserEmail string `json:"userEmail"` UserComment null.String `json:"userComment"` } // ---------- package util func Rollback(tx *ent.Tx, err error ) error { if rerr := tx.Rollback(); rerr != nil { err = fmt.Errorf( "%w: %v" , err, rerr) } return err } 2021/10/20 09:37:31 driver.Tx(66b33cc7-bf03-48ec-9ec1-029298c7e6c0): started 2021/10/20 09:37:31 Tx(66b33cc7-bf03-48ec-9ec1-029298c7e6c0).Exec: query=INSERT INTO `companies` (`created_at`, `updated_at`, `name`) VALUES (?, ?, ?) args=[2021-10-20 09:37:31.187128 +0900 JST m=+9.538263311 2021-10-20 09:37:31.187128 +0900 JST m=+9.538263623 nullcorp] 2021/10/20 09:37:31 Tx(66b33cc7-bf03-48ec-9ec1-029298c7e6c0).Exec: query=INSERT INTO `users` (`created_at`, `updated_at`, `name`, `email`, `role`, `comment`, `company_id`) VALUES (?, ?, ?, ?, ?, ?, ?) args=[2021-10-20 09:37:31.188519 +0900 JST m=+9.539654394 2021-10-20 09:37:31.188521 +0900 JST m=+9.539656522 a abcde@example.com admin {{ false}} 4] 2021/10/20 09:37:31 Tx(66b33cc7-bf03-48ec-9ec1-029298c7e6c0): committed CreateWithUserã¯åãåã£ããªã¯ãšã¹ããã©ã¡ãŒã¿ãŒããäŒæ¥ããŠãŒã¶ãŒãäœæããã¡ãœããã§ãã ãŸãã *ent.Client(dbc) ãã Tx ã§ãã©ã³ã¶ã¯ã·ã§ã³ãäœæãããã©ã³ã¶ã¯ã·ã§ã³å
ã§è¡ãªãåŠçãåŸè¿°ããŠããŸãã äœæãããã©ã³ã¶ã¯ã·ã§ã³ãã *ent.Client(dbc) ãšåæ§ã«å¯Ÿè±¡ããŒãã«ãæå®ãã Create ã§äœæçšã®ã¯ãšãªãã«ããè¡ããæŽæ°åŠçãšåæ§ã« Save ã§åŠçãå®è¡ããŠããŸãã error ãè¿ã£ãŠããå Žåã«ã¯ util.Rollback ã§ã©ããããŠã tx.Rollback ãå®è¡ãããŒã«ããã¯ããŸãã æåŸã« tx.Commit ã§ãã©ã³ã¶ã¯ã·ã§ã³ãã³ãããããŸãã CRUDéããŠã©ããèªåçæãããã³ãŒãããç°¡åã«ã¯ãšãªãã«ããã§ããŸããã ä»åå©çšãã颿°ä»¥å€ã«ãå€ãã®é¢æ°ãèªåçæã«ããçšæããããããå°ã
è€éãªã¯ãšãªããããã®çµã¿åããã§æ§ç¯ã§ãããã§ããã è¯ãã£ãç¹ã»ããã²ãšã€ã ã£ãç¹ æåŸã« ent ãå©çšããŠã¿ãŠæãããè¯ãã£ãç¹ã»ããã²ãšã€ã ã£ãç¹ãæããŸãã è¯ãã£ãç¹ è¯ãã£ããšæããç¹ã¯ä»¥äžã«ãªããŸãã èªåçææ©èœã®åŒ·åã ããã¥ã¡ã³ãã®å
å®åºŠ æ©èœã®è±å¯ã èªåçææ©èœã®åŒ·åã ãã¯ãã³ã¬ãäžçªã®ã¡ãªããã«æããŸãããRepositoryå±€ããããšèšããŸãããåææ®µéã§ã¢ãã«ã«æ±çšé¢æ°ãäžéãæã£ãŠãããããæ°ãã«èªåã§æµããã³ãŒãã®éã¯æå°éã§æžã¿ãŸãã ã¹ããŒãã®å®çŸ©ãæ¯èŒççŽæçã«ã§ãããã§ãããä»åã¯åããŠè§Šã£ããšããããšãããå®è£
ã«å°ã
æãããç®æããããŸããããæ
£ããŠããŸãã°å¹çè¯ãå®è£
ã§ãããã ãšæããŸããã ãŸããã«ã¹ã¿ãã€ãºæ§ãäœãç¹ãèªåçæã«ãããæžå¿µç¹ããšæããŸããã ent ã«ã¯ã¹ããŒãã®åã¡ãœããå®çŸ©ã«å¯ŸããŠè±å¯ã«ãªãã·ã§ã³ãåãæããããŠãããäžè¬ã«ORMå©çšæã«ããã¯ã«ãªããããã±ãŒã¹ã¯ã©ãããªãã·ã§ã³ã§ã«ããŒãããŠãããã§ããã ã¹ããŒãããããŒãã«å®çŸ©ãã¢ãã«ã®å®çŸ©ã®äž¡æ¹ãè¡ãããããçžäºã®éã«å®çŸ©ã®ãºã¬ãçããããšãç¡ãç¹ã管çã®ç
©éããæžã£ãŠè¯ãã§ãã ããã¥ã¡ã³ãã®å
å®åºŠ 2020幎v0.1.0çºãšæ¯èŒçè¥ãORMã§ãããªãããå
¬åŒããã¥ã¡ã³ããå
å®ããŠãã倧æµã®äžæç¹ã¯ããã§è§£æ±ºã§ãããã§ãããæ¥æ¬èªç¿»èš³ã®ããã¥ã¡ã³ããããã®ã¯ãšã£ãŠãå©ãããŸãã æ©èœã®è±å¯ã æ¬èšäºã§ç޹ä»ããããŸããã§ããããäžè¬çãªORMã§å©çšã§ãã(ãã©ã³ã¶ã¯ã·ã§ã³ãEagerããŒãã£ã³ã°ãããã¯ãããŒãžã³ã°)ãªã©ã®æ©èœã¯ ent ã§ãäžéãã«ããŒãããŠããŸãã ãŸããGORMã«ã¯çµã¿èŸŒãŸããŠããªãããªããŒã·ã§ã³ãªã©ã®æ©èœã ent ã«ã¯çµã¿èŸŒãŸããŠããŸãã åŒç€Ÿã®ãããã¯ãã§ã¯ãGin + GORMæ§æã ãšããããšãããGinã«ããã¯ãããŠãã validator ãã¢ãã«ã®ããªããŒã·ã§ã³ã«ãæµçšããŠããŸãã ent ã§ã¯ããã1ããã¯ã«å©çšã§ãããããã©ã€ãã©ãªéã®æ©æž¡ãçãªå®è£
ãããå¿
èŠããªã䟿å©ã§ããã ä»ã«ãèªåçæãç¬èªãã³ãã¬ãŒãã§è¡ãªããªãã·ã§ã³ãªã©æ¡åŒµæ©èœãçšæãããŠããããã§ãããã®èŸºãã¯ä»å調æ»ã§ããªãã£ãã®ã§ããŸããããããŠè§Šã£ãŠã¿ãããšæããŸãã ããã²ãšã€ã ã£ãç¹ ããã²ãšã€ã«æããç¹ã¯ä»¥äžã«ãªããŸãã Schemaã®ç®¡ç Schemaã®ç®¡ç ãã㯠ent çµã¿èŸŒã¿ã®Auto migrationæ©èœã§ãããã¯ã·ã§ã³ã¹ããŒãã®ç®¡çãå¯èœããšããç¹ã§ã®äžæºç¹ã§ãã åŒç€Ÿã®ãããã¯ãã§ã¯ORMã«ã¯GORMãããã€ã°ã¬ãŒã·ã§ã³ããŒã«ã«ã¯ goose ãå©çšããŠããŸãã ent ã§ã¯GORMãšã¯ç°ãªãAuto migrationã«ãããªãœãŒã¹ã®åé€ãã§ããŸããããããããŒãžã§ã³ç®¡çãªãã«æ¬çªç°å¢ã§Auto migrationãå®è¡ã§ããããšãããšå°ã
å¿èš±ãªãæ°ãããŸãã å¥éãã€ã°ã¬ãŒã·ã§ã³ããŒã«ãå°å
¥ããŠã *migrate.Schema ã«çšæãããŠãã WriteTo ã¡ãœããã§äžåºŠDDLã§å°å
¥ããããŒã«ã®ãã€ã°ã¬ãŒã·ã§ã³ãã¡ã€ã«ã«å®çŸ©ãåãåºããããã§ãæ¬çªã§ã¯ãã¡ããå®è¡ããéçšããããããã§ããããã WriteTo ã®ãããªã¡ãœãããçšæãããŠãã䟿å©ã§ã¯ãããŸãããã¡ãã£ãšäžæéã§ã¯åã£ãããããã²ãšã€ã®ç¹ãšããŠæããŸããã ãŸãšã ãããã§ããã§ããããã æ¬èšäºã§ã¯Goã®ORM ent ã«ã€ããŠã玹ä»ããããŸãããæ®æ®µå©çšããŠãã GORM ãšã¯åæãéãéšåãå€ããæ°æèŠã§æ¥œãããŸããã ãŸã ãŸã æ°ããã©ã€ãã©ãªãªã®ã§ä»åŸã®çºå±ã楜ãã¿ã§ãã æ©ãããã¡ã¯ãã¹ã¿ã³ããŒããæ±ºãŸã£ãŠããã¡ããžåããŠããŸãããããšããæ°æã¡ãããã€ã€ããããããšè²ã
ãªããŒã«ã«è§ŠããŠã¿ãŠã®æ¥œããããã£ãããšã©ã£ã¡ã€ãããªæãã®ç§ã®å€é·ã§ãã æåŸãŸã§ãèªã¿ããã ãããããšãããããŸããïŒ