Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[GoCon2022 Spring] Go言語でコードジェネレーターを作ろう / let's make a code generator by golang

riita10069
April 24, 2022
25

[GoCon2022 Spring] Go言語でコードジェネレーターを作ろう / let's make a code generator by golang

Go Conference 2022 Spring

Go言語のコードジェネレーターを作ってみよう
https://gocon.jp/2022spring/ja/speakers/riita10069/

動画:
後に追加

riita10069

April 24, 2022
Tweet

More Decks by riita10069

Transcript

  1. Ryot
    a
    Y
    a
    m
    a
    d
    a
    (Pݴޠͷ
    ίʔυδΣωϨʔλʔΛ
    ࡞ͬͯΈΑ͏
    The Go gopher was designed by Renée French. Illustrations by tottie

    View Slide

  2. GoݴޠͰɺίʔυδΣωϨʔλʔΛ࡞͍ͬͯ·͔͢ʁ

    View Slide

  3. GoݴޠͰɺίʔυδΣωϨʔλʔΛ࢖͍ͬͯ·͔͢ʁ

    View Slide

  4. Goݴޠʹ͓͚ΔίʔυδΣωϨʔλʔ
    •gomock


    •gqlgen


    •go-swagger


    •protoc-gen-go


    •kubebuilder

    View Slide

  5. ίʔυੜ੒ͷϝϦοτ
    •ࣅͨΑ͏ͳίʔυΛԿճ΋ॻ͘ඞཁ͕ͳ͍

    →ΤϯδχΞͷϑϥετϨʔγϣϯͷܰݮ


    •ਓҝతͳϛε(e.g. Typo)ͷ࡟ݮ


    •։ൃ଎౓͕ૣ͘ͳΔ


    •୭͕ॻ͍ͯ΋ಉ͡Α͏ͳߏ੒ʹͳΔ

    →ϓϩδΣΫτશମͱͯ͠ͷอकੑɺՄಡੑͷ޲্

    View Slide

  6. ಠࣗͷίʔυδΣωϨʔλʔΛ࡞Γ͍ͨ

    View Slide

  7. e := &entity.User
    {

    Name: name
    ,

    NameKana: name_kana
    ,

    Email: email
    ,

    Age: age
    ,

    Address: address
    ,

    Birthday: birthday
    ,

    PhoneNumber: phone_number
    ,

    LastLogin: last_login
    ,

    is_premium: is_premium
    ,



    }
    ͜ΕΛ࡞Δͷ͕


    ΊΜͲ͍͘͞ʂ

    View Slide

  8. !"" cm
    d

    !"" domai
    n

    # !"" entit
    y

    # %"" repositor
    y

    !"" infr
    a

    # !"" mode
    l

    # %"" repositor
    y

    %"" usecase
    ͜Ε΋࡞Δͷ͕


    ΊΜͲ͍͘͞ʂ

    View Slide

  9. https://github.com/riita10069/roche

    View Slide

  10. roche skaffold init
    •APIαʔόʔͷ਽ܗΛੜ੒ͯ͘͠ΕΔ


    •಺෦࣮૷͸ɺgrapiΛgo getͯ͠ݺͼग़͍ͯ͠Δ͚ͩ
    https://github.com/izumin5210/grapi

    View Slide

  11. roche skaffold all NAME
    .proto
    handler
    repository
    di_container
    usecase
    entity
    migration


    f
    ile

    View Slide

  12. ϢʔβʔͷCRUD͢ΔΤϯυϙΠϯτ΄͍ͥ͠
    message User
    {

    string name = 1
    ;

    string name_kana = 2
    ;

    int64 age = 3
    ;

    string email = 4
    ;



    }
    ͜Ε͚ͩ࡞Ε͹


    ׬੒ͳͷͩ
    ϊʔίʔυͷੈք

    View Slide

  13. ίʔυੜ੒ͷσϝϦοτ
    • ίʔυδΣωϨʔλʔ͕ؒҧ͍ͬͯΔͱࢮ͵


    • ख࡞ۀͰશͯͷ΋ͷΛ௚͢ͷ͸େม


    • ઃܭͷมߋ͸Ͱ͖ͳ͍


    • DDDΛ࠾༻͍ͨ͠ʁNoSQLΛ࢖͍͍ͨʁrocheͰ͸ແཧ


    • ࿮૊Έ͔Β֎ΕΔ࣌͸ࣗಈੜ੒͞ΕͨίʔυΛফ͢


    • Web APIͰɺentity, Req, DBͷεΩʔϚ͸ࣅͨΑ͏ͳ΋ͷʹͳΔͱ
    ͍͏ԾઆΛஔ͍͍ͯΔ

    View Slide

  14. ίʔυੜ੒ͷϝϦοτ(ମײ)
    • ࣮ࡍͷͱ͜Ζָʹͳͬͨ


    • ੜ੒͞Εͨίʔυͷେ෦෼͸ͦͷ··ར༻Ͱ͖Δ


    • ੜ੒͞ΕͨίʔυΛಡΊ͹ͳΜͱ͔ͳΔ


    • ෳ਺ਓͰ։ൃͯ͠΋ɺߏ੒͕อͨΕΔ


    • ϚΠΫϩαʔϏεͳͲͰνʔϜ͕ҧ͏৔߹ʹ΋༗༻͔ʁ(ະݕূ)


    • ։ൃεϐʔυ΋࣮֬ʹ্͕ͬͨ

    View Slide

  15. ΋ͬͱޮ཰తͳઃܭ͸ͳ͍ͷ͔

    View Slide

  16. ΋ͬͱྑ͍ઃܭΛߟ͑Δ
    • ίʔυδΣωϨʔλʔ͸ϓϩδΣΫτ͝ͱʹࣗ࡞͢Δ΂͖


    • ઃܭɺٕज़બఆ͕ίʔυδΣωϨʔλʔʹΑͬͯݻఆ͞Εͨ͘ͳ͍


    • ࠷খݶͷ੹຿Λ࣋ͬͨδΣωϨʔλʔ͕υϛϊ౗͠తʹػೳ͢΂͖


    • All in OneͷίʔυδΣωϨʔλʔ͸ແବ͕ଟ͍


    • ੜ੒͞ΕΔίʔυྔ͕૿͑Ε͹ϛεϚον΍όά΋૿͑Δ

    View Slide

  17. ࠷খͷ੹຿Λ࣋ͬͨผʑͷπʔϧͰ࣮ݱ͍ͨ͠
    handler
    repository
    di_container
    usecase
    entity
    migration


    f
    ile
    .proto
    .pb.go
    repository
    _interface

    View Slide

  18. গ͠ͷָΛ͢ΔͨΊʹίʔυੜ੒Λ͠Α͏(෍ڭ)

    View Slide

  19. https://github.com/dave/jennifer

    View Slide

  20. https://github.com/dave/jennifer#readme
    ੜ੒͍ͨ͠ίʔυΛ؆୯ʹੜ੒͢Δ
    package mai
    n

    import
    (

    "fmt
    "

    . "github.com/dave/jennifer/jen
    "

    )

    func main()
    {

    f := NewFile("main"
    )

    f.Func().Id("main").Params().Block
    (

    Qual("fmt", "Println").Call(Lit("Hello, world"))
    ,

    )

    fmt.Printf("%#v", f
    )

    }
    package mai
    n

    import "fmt
    "

    func main()
    {

    fmt.Println("Hello, world"
    )

    }

    View Slide

  21. https://github.com/riita10069/roche/blob/master/pkg/rochectl/gen-sca
    ff
    old/gen-usecase.go
    ϝιουͷ࡞੒
    usecaseFile.Func().Params(Id("u").Id(name + "Usecase")).Id("GetByID").Params(Id("id").Int64()).Params(Id("*entity." + name), Error()).Block
    (

    List(Id("e"), Err()).Op(":=")
    .

    Id("u").Dot(name+ "Repo").Dot("GetByID").Call(Id("id"))
    ,

    If
    (

    Err().Op("!=").Nil()
    ,

    ).Block
    (

    Return(Id("nil"), Err())
    ,

    )
    ,

    Return(Id("e"), Err())
    ,

    )
    func (u EnterpriseUsecase) GetByID(id int64) (*entity.Enterprise, error)
    {

    e, err := u.EnterpriseRepo.GetByID(id
    )

    if err != nil
    {

    return nil, er
    r

    }

    return e, er
    r

    }

    View Slide

  22. https://github.com/riita10069/roche/blob/master/pkg/rochectl/gen-sca
    ff
    old/gen-usecase.go
    ϝιουͷ࡞੒
    usecaseFile.Func().Params(Id("u").Id(name + "Usecase")).Id("GetByID").Params(Id("id").Int64()).Params(Id("*entity." + name), Error()).Block
    (

    List(Id("e"), Err()).Op(":=")
    .

    Id("u").Dot(name+ "Repo").Dot("GetByID").Call(Id("id"))
    ,

    If
    (

    Err().Op("!=").Nil()
    ,

    ).Block
    (

    Return(Id("nil"), Err())
    ,

    )
    ,

    Return(Id("e"), Err())
    ,

    )
    func (u EnterpriseUsecase) GetByID(id int64) (*entity.Enterprise, error)
    {

    e, err := u.EnterpriseRepo.GetByID(id
    )

    if err != nil
    {

    return nil, er
    r

    }

    return e, er
    r

    }

    View Slide

  23. https://github.com/riita10069/roche/blob/master/pkg/rochectl/gen-sca
    ff
    old/gen-usecase.go
    ϝιουͷ࡞੒
    usecaseFile.Func().Params(Id("u").Id(name + "Usecase")).Id("GetByID").Params(Id("id").Int64()).Params(Id("*entity." + name), Error()).Block
    (

    List(Id("e"), Err()).Op(":=")
    .

    Id("u").Dot(name+ "Repo").Dot("GetByID").Call(Id("id"))
    ,

    If
    (

    Err().Op("!=").Nil()
    ,

    ).Block
    (

    Return(Id("nil"), Err())
    ,

    )
    ,

    Return(Id("e"), Err())
    ,

    )
    func (u EnterpriseUsecase) GetByID(id int64) (*entity.Enterprise, error)
    {

    e, err := u.EnterpriseRepo.GetByID(id
    )

    if err != nil
    {

    return nil, er
    r

    }

    return e, er
    r

    }
    Id() & ม਺໊ɺؔ਺໊ͳͲ .Ͱܨ͛ΔͱεϖʔεͰܨ͕Δ
    Params()&Ҿ਺ɺؙׅހɺෳ਺ͷҾ਺͕౉ͤΔ
    Block()&ϒϩοΫɺ೾ׅހ
    Dot()&. Call() & Ͱؙׅހ
    ͦͷଞɺ༧໿ޠ͸ؔ਺͕ଘࡏ͢Δɻ

    View Slide

  24. https://github.com/riita10069/roche/blob/master/pkg/rochectl/gen-sca
    ff
    old/gen-model.go
    Structͷ࡞੒
    var codes []jen.Cod
    e

    var code *jen.Statemen
    t

    for _, field := range structAst.Fields.List
    {

    for _, nameIdent := range field.Names
    {

    code := jen.Id(nameIdent.Name
    )

    // ཁૉͷܕ໊
    switch field.Type.(type)
    {

    // ผύοέʔδͷܕΛར༻͍ͯ͠Δ৔߹
    case *ast.SelectorExpr
    :

    selectorExpr, _ := field.Type.(*ast.SelectorExpr
    )

    xIdent, _ := selectorExpr.X.(*ast.Ident
    )

    if xIdent.Name == "protoimpl"
    {

    continu
    e

    }

    code.Id(xIdent.Name + "." + selectorExpr.Sel.Name
    )

    // ૊ΈࠐΈ·ͨ͸ύοέʔδ಺ͷܕΛར༻͍ͯ͠Δ৔߹
    case *ast.Ident
    :

    ident, _ := field.Type.(*ast.Ident
    )

    code.Id(ident.Name
    )

    case *ast.StarExpr
    :

    code.Id(nameIdent.Name
    )

    }

    codes = append(codes, code
    )

    }

    }

    f.Type().Id(name).Struct(codes...)
    type User struct
    {

    Id int6
    4

    Name strin
    g

    Age int6
    4

    }

    View Slide

  25. File & string
    func JenFileToString(f *jen.File)
    {

    buf := &bytes.Buffer{
    }

    err := f.Render(buf
    )

    if err != nil
    {

    fmt.Println(err.Error()
    )

    os.Exit(1
    )

    }

    return buf.Strin
    g

    }
    https://github.com/riita10069/roche/blob/master/pkg/rochectl/
    fi
    le/wrirte-
    fi
    le.go
    ҙਤͨ͠ίʔυ͕ੜ੒Ͱ͖Δ͔ͷϢχοτςετ
    ࣮ࡍʹϑΝΠϧʹจࣈྻΛॻ͖ࠐΉ

    View Slide

  26. String & ϑΝΠϧॻ͖ࠐΈ
    func makeDirectory (filename string)
    {

    paths := strings.Split(filename, "/"
    )

    paths = paths[:len(paths)-1
    ]

    filepath := strings.Join(paths, "/"
    )

    if _, err := os.Stat(filepath); os.IsNotExist(err)
    {

    defaultUmask := syscall.Umask(0
    )

    os.MkdirAll(filepath, 0755
    )

    os.Chmod(filepath, 0755
    )

    syscall.Umask(defaultUmask
    )

    }

    fmt.Println("Execute Make Directory"
    )

    (" ✔ ", filepath
    )

    }

    func CreateAndWrite(content string, filename string) error
    {

    if strings.Contains(filename, "/")
    {

    makeDirectory(filename
    )

    }

    err := ioutil.WriteFile(filename, []byte(content), 0664
    )

    if err != nil
    {

    return errors.New("cannot write" + filename
    )

    }

    fmt.Println("Execute code generate to following file"
    )

    fmt.Println(" ✔ ", filename
    )

    return ni
    l

    } https://github.com/riita10069/roche/blob/master/pkg/rochectl/
    fi
    le/wrirte-
    fi
    le.go

    View Slide

  27. AST(ந৅ߏจ໦)ͷ࡞੒
    func getAst(filePath string) f *ast.File
    {

    fset := token.NewFileSet(
    )

    f, err := parser.ParseFile(fset, filePath, nil, parser.Mode(0)
    )

    if err != nil
    {

    fmt.Println("cannot parse file, filepath is ", filePath
    )

    fmt.Println(err.Error()
    )

    return ni
    l

    }

    return
    f

    }
    https://github.com/riita10069/roche/blob/master/pkg/rochectl/ast/
    fi
    nd-struct.go

    View Slide

  28. ͋ΔϑΝΠϧ͔ΒstructͷཁૉΛऔಘ
    func getAstHash(f *ast.File, structASTMap map[string]*ast.StructType)
    {

    for _, decl := range f.Decls
    {

    d, ok := decl.(*ast.GenDecl
    )

    if !ok || d.Tok != token.TYPE
    {

    continu
    e

    }

    for _, spec := range d.Specs
    {

    s, ok := spec.(*ast.TypeSpec
    )

    if !ok
    {

    continu
    e

    }

    t, ok := s.Type.(*ast.StructType
    )

    if !ok
    {

    continu
    e

    }

    structASTMap[s.Name.Name] =
    t

    }

    }

    }
    https://github.com/riita10069/roche/blob/master/pkg/rochectl/ast/
    fi
    nd-struct.go
    func GetPropertyByStructAst(structAst *ast.StructType) ([]string, []string)
    {

    var properties []strin
    g

    var propertiesType []strin
    g

    for _, field := range structAst.Fields.List
    {

    for _, nameIdent := range field.Names
    {

    switch field.Type.(type)
    {

    case *ast.SelectorExpr
    :

    selectorExpr, _ := field.Type.(*ast.SelectorExpr
    )

    xIdent, _ := selectorExpr.X.(*ast.Ident
    )

    if xIdent.Name == "protoimpl"
    {

    continu
    e

    }

    properties = append(properties, util.SnakeToLowerCamel(nameIdent.Name)
    )

    propertiesType = append(propertiesType, xIdent.Name
    )

    case *ast.Ident
    :

    ident, _ := field.Type.(*ast.Ident
    )

    properties = append(properties, nameIdent.Name
    )

    propertiesType = append(propertiesType, ident.Name
    )

    }

    }

    }

    return properties, propertiesTyp
    e

    }

    View Slide

  29. JenifferΛ࢖ͬͯɺίʔυδΣωϨʔλΛ࡞ͬͯΈΑ͏

    View Slide