ã¯ããã« ã¯ãããŸããŠïŒ23åã®ãšã³ãžãã¢ã®R.AãšãããŸãã çŸåšã¯äž»ã«ç€Ÿå
ã·ã¹ãã ã®ããã€ã¹ç®¡çããŒã«ã®éçºãè¡ãªã£ãŠããŸãã çŸåšã¯ããã¯ãšã³ãåŽã¯GoãçšããŠã¬ã€ã€ãŒãã¢ãŒããã¯ãã£ãšDDD(ãã¡ã€ã³é§åèšèš)ã«æ²¿ã£ãŠéçºãé²ããŠããŸãã éçºããŠããäžã§ã¬ã€ã€ãŒãã¢ãŒããã¯ãã£ãã¯ãªãŒã³ã¢ãŒããã¯ãã£ãªã©ã¯ç¶ºéºã«æžãããã®ã®ã³ãŒãã®éãå€ããæéãåãããŠããŸããšããåé¡ç¹ããããŸããã ãã®åé¡ã解決ããããã«å®åçãªã³ãŒãã¯èªåçæããã»ããæ¥åå¹çåã«ã€ãªãããšæãã³ãŒããçæã§ããããã«ããŸããã 䜿ã£ãã©ã€ãã©ãªãšããŠã¯ä»¥äžã®ãã®ã«ãªããŸãã jennifer æŠèŠ èªåçæã®æŠèŠãšããŠã¯ä»¥äžã®ããã«ãªããŸãã ãããžã§ã¯ãã®domainé
äžã«å¯Ÿè±¡ã®entityæ§é äœãäœæããŠãã generatoré
äžã®modelãã£ã¬ã¯ããªã«å¯Ÿè±¡ã®modelã®æ§é äœãé
眮ãã(sqlboilerã ãšã¹ããŒãããmodelãèªåçæãããã®æ¥œã§ã) make generate-backend-base-code Entity=”ä»»æã®ãšã³ãã£ãã£å” Model=”ä»»æã®ã¢ãã«å” ãšããã³ãã³ããå®è¡ interfase, di, repository, usecaseã®ã³ãŒããèªåçã«çæããã ã¬ã³ãŒãã®æ°èŠäœæé¢æ°ãæŽæ°é¢æ°ãèªåçæããã 以äžãåç»ã«ãªããŸãã 0:04ãããã«ã³ãã³ããå®è¡ããŠããŸãã èªåçæã®äžèº«ã®èª¬æ åãã©ã«ãã«ãœãŒã¹ã³ãŒããçæããŠãããçŽ ãšãªããã®ãå
¥ã£ãŠããŸãã generator ⣠di â â gen_di.go // diçæ â£ handler â â gen_handler.go // handlerçæ â£ models â ⣠admin.go // çæãããã¢ã㫠⣠repository â ⣠gen_repo_impl.go // ã¬ããžããªå®äœçæ â â gen_repo_interface.go // ã¬ããžããªã€ã³ã¿ãŒãã§ãŒã¹çæ ⣠usecase â â gen_usease.go // ãŠãŒã¹ã±ãŒã¹çæ â generator.goã// main颿° ã³ãŒãçæã®éšåãå
šãŠæžããšé·ããªã£ãŠããŸãã®ã§çæãããšãã«å®è¡ãããmain颿°ãšããŠãŒã¹ã±ãŒã¹ã®çæéšåã«ã€ããŠæžããŸãã generator.go(main颿°) 以äžã®main颿°ãå®è¡ããããšã§åãã©ã«ãã®é¢æ°ãå®è¡ããçæãããšãã圢ããšã£ãŠããŸãã func main() { // ã³ãã³ãã©ã€ã³åŒæ°ãå®çŸ© entity := flag.String("e", "", "Entity name") model := flag.String("m", "", "Model name") // ã³ãã³ãã©ã€ã³åŒæ°ãè§£æ flag.Parse() // ãšã³ãã£ãã£åãååŸ e := *entity m := *model fileName := toSnakeCase(e) // handleräœæ genhandler.GenHandler(e, fileName) // diäœæ gendi.GenDi(e, fileName) // usecaseäœæ genusecase.GenUsecase(e, fileName) // repositoryinterfaceãäœæ genrepository.GenRepoInterface(e, fileName) // repositoryimpläœæ genrepository.GenRepoImpl(e, m, fileName) } gen_usease.go (ãŠãŒã¹ã±ãŒã¹çæ) ããã§ã¢ãã«åãšãšã³ãã£ãã£åã䜿ãããŠãŒã¹ã±ãŒã¹ã®ã³ãŒããçæããŠããŸãã é£ãããã«èŠããŸããã³ãŒãã®æåãçŸ
åããŠããæèŠã«è¿ãã®ã§ãã©ã€ãã©ãªã®ä»æ§ãçè§£ããŠããŸãã°ãããŸã§é£ããã¯ãªãã§ãã package genusecase import ( "strings" "github.com/dave/jennifer/jen" ) func GenUsecase(e string, fileName string) { // Usecase interface E := strings.Title(e) // ãšã³ãã£ãã£åã®å
é ã倧æåã« abbrUsecase := strings.ToLower(string(e[0])) + "u" abbrRepository := strings.ToLower(string(e[0])) + "r" f := jen.NewFile("usecase") f.ImportName("main/domain/entity", "entity") f.ImportName("main/domain/repository", "repository") f.Type().Id(E+"UseCase").Interface( jen.Id("Add"+E).Params(jen.Id(e).Op("*").Qual("main/domain/entity", E)).Error(), jen.Id("Update"+E).Params(jen.Id(e).Op("*").Qual("main/domain/entity", E)).Error(), ) f.Type().Id(E + "UseCaseImpl").Struct( jen.Id(E+"Repository").Qual("main/domain/repository", E+"Repository"), ) f.Func().Id("New"+E+"UseCaseImpl").Params(jen.Id(abbrRepository).Qual("main/domain/repository", E+"Repository")).Id(E + "UseCase").Block( jen.Return(jen.Op("&").Id(E+"UseCaseImpl").Values(jen.Dict{ jen.Id(E + "Repository"): jen.Id(abbrRepository), })), ) f.Line() f.Func().Params(jen.Id(abbrUsecase).Id(E + "UseCaseImpl")).Id("Add"+E).Params(jen.Id(e).Op("*").Qual("main/domain/entity", E)).Error().Block( jen.List(jen.Id("err")).Op(":=").Id(abbrUsecase + "." + E + "Repository").Dot("Add" + E).Call(jen.Id(e)), jen.If(jen.Id("err").Op("!=").Nil()).Block( jen.Return(jen.Id("err")), ), jen.Return(jen.Nil()), ) f.Line() f.Func().Params(jen.Id(abbrUsecase).Id(E + "UseCaseImpl")).Id("Update"+E).Params(jen.Id(e).Op("*").Qual("main/domain/entity", E)).Error().Block( jen.List(jen.Id("err")).Op(":=").Id(abbrUsecase + "." + E + "Repository").Dot("Update" + E).Call(jen.Id(e)), jen.If(jen.Id("err").Op("!=").Nil()).Block( jen.Return(jen.Id("err")), ), jen.Return(jen.Nil()), ) // Save to file f.Save("../application/usecase/" + fileName + ".go") } ã³ãã³ãå®è¡åŸã«çæãããã³ãŒãã¯ä»¥äžã®ãã®ã«ãªããŸãã package usecase import ( "main/domain/entity" "main/domain/repository" ) type MemberUseCase interface { AddMember(Member *entity.Member) error UpdateMember(Member *entity.Member) error } type MemberUseCaseImpl struct { MemberRepository repository.MemberRepository } func NewMemberUseCaseImpl(mr repository.MemberRepository) MemberUseCase { return &MemberUseCaseImpl{MemberRepository: mr} } func (mu MemberUseCaseImpl) AddMember(Member *entity.Member) error { err := mu.MemberRepository.AddMember(Member) if err != nil { return err } return nil } func (mu MemberUseCaseImpl) UpdateMember(Member *entity.Member) error { err := mu.MemberRepository.UpdateMember(Member) if err != nil { return err } return nil } 工倫ããç¹ ã€ã³ãã©å±€ã§ãšã³ãã£ãã£ããDBã¢ãã«ã«å€æããããã®é¢æ°ãå¿
èŠã§ãã(sqlboilerã®modelç¹æã®åããããã) ãã®é¢æ°ã®äžã«ã¯ãã£ãŒã«ã30åã»ã©ååšãããã®ãããæžãã®ã倧å€ã§ãã£ãããããã®é¢æ°ãçæã§ããããã«ããŸããã 該åœã®é¢æ°ã¯ä»¥äžã®ãããªå€æé¢æ°ã§ãã func ToDbModelMember(mri *entity.Member) *models.Member { return &models.Member{ Email: mri.Email, EntryTime: mri.EntryTime, Kana: mri.Kana, MemberID: mri.MemberID, Name: mri.Name, Post: mri.Post, UpdateTime: mri.UpdateTime, } } äžèšã®ã³ãŒããçæããããã«ä»¥äžã®æé ãç«ãŠãŸããã ASTã䜿ãmodelã®æ§é äœã®åæ
å ±ãã«ãŒãããentityãšæ¯èŒãã æ¯èŒããäžã§entityãã©ã®åã«å€æããããããåå²ããã äžèšã®æ
å ±ãããšã«é¢æ°ãçæãã ãã®æé ãããšã«ä»¥äžã®ã³ãŒããäœæããŸããã models, _ := FindModelStruct(fileName, modelName) // ããã§modelãååŸ dict := make(map[string]jen.Code) // ããã§ãã£ãŒã«ãåãkey 倿ããentityãvalueãšããŠäœæ pk := "none" for _, field := range models.Fields.List { // modelã®æ§é äœãã«ãŒããããŸã name := field.Names[0] if strings.Contains(name.Name, "ID") && strings.Contains(name.Name, E) { pk = name.Name // äž»ããŒååŸ } // ããã§typeããšã«å€æé¢æ°ãèšè¿°ãããããã«ããŠããŸã // sqlboilerã§äœ¿ãããŠããnull.stringãªã©ã倿ã§ããããã«ããŠããŸã switch typeName := fmt.Sprintf("%s", field.Type); typeName { case "&{null String}": dict[name.Name] = jen.Qual("github.com/volatiletech/null", "StringFrom").Call(jen.Id(abbrRepository + "." + name.Name)) case "&{null Int}": dict[name.Name] = jen.Qual("github.com/volatiletech/null", "IntFrom").Call(jen.Id(abbrRepository + "." + name.Name)) case "&{null Time}": dict[name.Name] = jen.Qual("github.com/volatiletech/null", "TimeFrom").Call(jen.Id(abbrRepository + "." + name.Name)) default: // ãã®ä»ã®åã®å Žåã®åŠç dict[name.Name] = jen.Id(abbrRepository + "." + name.Name) } } stmt := jen.Dict{} for k, v := range dict { stmt[jen.Id(k)] = v } // model颿°çæ f.Func().Id("ToDbModel" + E).Params(jen.Id(abbrRepository).Op("*").Id("entity." + E)).Op("*").Qual("main/infrastructure/postgres/sqlboiler", modelName).Block( jen.Return( jen.Op("&").Qual("main/infrastructure/postgres/sqlboiler", modelName).Values(stmt), ), ) modelæ§é äœååŸé¢æ° äžèšã®FindModelStruct(modelã®æ§é äœãååŸé¢æ°)ã«ã€ããŠè§£èª¬ããŠãããŸãã ãã¡ã€ã«ãããŒã¹ã㊠Goã®æœè±¡æ§ææšïŒASTïŒ ãšããæšæºã©ã€ãã©ãªã䜿çšããååŸããŠããŸãã func FindModelStruct(fileName, modelName string) (*ast.StructType, error) { fset := token.NewFileSet() node, err := parser.ParseFile(fset, "./models/"+fileName+".go", nil, parser.ParseComments) if err != nil { log.Println(err) return nil, err } var model *ast.StructType ast.Inspect(node, func(n ast.Node) bool { switch t := n.(type) { case *ast.TypeSpec: if t.Name.Name == modelName { if x, ok := t.Type.(*ast.StructType); ok { // modelã®æ§é äœãååŸ model = x return false // æ§é äœãèŠã€ãã£ããæ¢çŽ¢ãçµäºããŸã } } } return true }) if model == nil { return nil, fmt.Errorf("%s struct not found", modelName) } return model, nil } ä»åŸãšèª²é¡ æ°èŠè¿œå ãšæŽæ°åŠçãããªãã®ã§CRUDåŠçå
šãŠãçæã§ããããã«ãããããŸããäžèŠãªãã®ãçæãããŠããŸãå Žåãããã®ã§ã«ã¹ã¿ã ããŠå¿
èŠãªãã®ã ãçæã§ããæ¹ããã ãããžã§ã¯ãã«å¿ããŠé¢æ°åãªã©åŸ®åŠã«å€ããªããšãããªãã®ã§ããããžã§ã¯ãã«å¿ãã察å¿ãå¿
èŠãçµ±äžåãããªã©ãããæ¹ããã entityããmodelã«å€æãã颿°éšåãsqlboilerã«äŸåããŠããŸã£ãŠããããŸãä»åã®ããã«DBã¢ãã«ãšãšã³ãã£ãã£ã䌌ãŠãããã®ã«ãã䜿çšã§ããªã DBé§åèšèšãšãªã£ãŠããã®ã§æ¹è¯ãå¿
èŠ æåŸã« èªåçæãèªãææ¡ããŠãããžã§ã¯ãã®æ¥åå¹çåã«è²¢ç®ã§ããããšã¯å€§ããªçµéšãšãªããŸããã èªäž»çã«è¡åããŠãéçºã¹ããŒããäžããŠãããããªå·¥å€«ããããããç¶ããŠããããã§ãã åèã«ããèšäº https://qiita.com/nghrsss/items/e6c9c95db19640f0f654 https://qiita.com/po3rin/items/a19d96d29284108ad442