# Generating Code in Go Different approach to some challenges (no AI)  Gopher: [https://github.com/egonelbre/gophers](https://github.com/egonelbre/gophers) Note: The core idea of this talk is to look at generating code as something akin to design pattern. It's a way to solve some problems differently, hopefully with fewer drawbacks. --- ## Who am I? ### Daniel Antos I *used to* hate generated code. Staff Backend Engineer at Pento   --- ### What are we talking about today? - Why? - Basics of generating code in Go. - Generate your APIs: gRPC, GraphQL. - Generate your ORM. - Generating decorators. - Everyday life with generated code. --- ### Why? - Compile time safety - To reduce abstraction and complexity - Computer is better at repetitive task ```plaintext Error while building: # github.com/antosdaniel/go-presentation-generate-code/internal/grpc internal/grpc/payrollServiceWithAuth.go:16:9: ... (missing method GetPayslip) ``` --- ### Basics of generating code in Go Generators are just CLI tools:   go generate \<path\> go generate ./... Note: Generators are programs that create code. --- ### Generate your APIs: gRPC, GraphQL ```protobuf syntax = "proto3"; package payroll.v1; service PayrollService { rpc AddPayroll(AddPayrollRequest) returns (AddPayrollResponse); rpc AddPayslip(AddPayslipRequest) returns (AddPayslipResponse); rpc GetPayroll(GetPayrollRequest) returns (GetPayrollResponse); } message AddPayrollRequest { string payroll_id = 1; string tenant_id = 2; Date payday = 3; } // ... ``` Note: Schema first approach. If you discuss schema with your colleagues, why would you try to represent it in the code? My team used to write GraphQL implementation by hand: schema mismatches, runtime errors. -- ### Generate your APIs: gRPC, GraphQL, REST ```go // PayrollServiceHandler is an implementation of the payroll.v1.PayrollService service. type PayrollServiceHandler interface { AddPayroll(context.Context, *connect_go.Request[payrollv1.AddPayrollRequest]) (*connect_go.Response[payrollv1.AddPayrollResponse], error) AddPayslip(context.Context, *connect_go.Request[payrollv1.AddPayslipRequest]) (*connect_go.Response[payrollv1.AddPayslipResponse], error) GetPayroll(context.Context, *connect_go.Request[payrollv1.GetPayrollRequest]) (*connect_go.Response[payrollv1.GetPayrollResponse], error) } // Now we only have to implement it: func (s *payrollServiceServer) AddPayroll( ctx context.Context, request *connect_go.Request[payrollv1.AddPayrollRequest], ) (*connect_go.Response[payrollv1.AddPayrollResponse], error) { // TODO: business logic return &connect_go.Response[payrollv1.AddPayrollResponse]{ Msg: &payrollv1.AddPayrollResponse{ PayrollId: id, }, }, nil } ``` Note: No boilerplate: you can focus on API and business logic. --- ### Generate your ORM ```sql create table payrolls ( id uuid not null primary key, tenant_id uuid not null, payday date not null ); ``` ```go type PayrollModel struct { ID string `orm:"id"` TenantID string `orm:"tenant_id"` Payday string `orm:"payday"` } ``` Note: You already have your DLL. You know types already, you know if they are nullable. Any changes to the database will be reflected in the code (after regenerating). It is not possible to forget to rename a column. -- ### The ugly side of abstraction ```go func FindPayroll(ctx context.Context, exec boil.ContextExecutor, iD string, selectCols ...string) (*Payroll, error) { payrollObj := &Payroll{} sel := "*" if len(selectCols) > 0 { sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") } query := fmt.Sprintf( "select %s from \"payrolls\" where \"id\"=$1", sel, ) q := queries.Raw(query, iD) err := q.Bind(ctx, exec, payrollObj) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, sql.ErrNoRows } return nil, errors.Wrap(err, "models: unable to select from payrolls") } return payrollObj, nil } ``` Note: Have you ever tried to debug your ORM? The more complex queries, the closer to the SQL you want to be. -- ### Abstraction and reflection are costly   Source: [https://github.com/volatiletech/sqlboiler](https://github.com/volatiletech/sqlboiler) --- ### Generating decorators  Note: You can keep your business logic free(er) of non-functional requirements. Logs, traces, metrics, auth, validation, rate limit, cache and so on... -- ### What can we generate? ```go func (ps PayrollServiceWithTrace) AddPayroll(...) error { ps.tracer.Start(...) defer ps.tracer.End(...) err := ps.base.AddPayroll(...) if err != nil { ps.tracer.RaiseError(err) } return err } func (ps PayrollServiceWithTrace) AddPayslip(...) error { ps.tracer.Start(...) defer ps.tracer.End(...) err := ps.base.AddPayslip(...) if err != nil { ps.tracer.RaiseError(err) } return err } ``` Note: Can you see repeating, boring part? There are many ready-to-go templates in gowrap. If you don't find one that fits you, it is easy to write your own. --- ### Everyday life with generated code *because you will hit some issues* -- ### Commit generated code, and check it in CI  - No need to generate code before build - Easy to spot unintended changes during code review - Easily reproducible builds -- ### Makefile is where it begins ```makefile .PHONY: install install: @printf "\nInstalling sqlboiler...\n" @go install -mod=readonly github.com/volatiletech/sqlboiler/v4@v4.15.0 @go install -mod=readonly github.com/volatiletech/sqlboiler/v4/drivers/sqlboiler-psql@v4.15.0 @printf "\nInstalling gowrap...\n" @go install -mod=readonly github.com/hexdigest/gowrap/cmd/gowrap@v1.3.2 # ... .PHONY: generate generate: @printf "Generating protos...\n" @buf generate --template gen/grpc/buf.gen.yaml @printf "Generating db models...\n" @sqlboiler --config db/sqlboiler.toml psql @printf "go generate...\n" @go generate ./... @$(MAKE) format ``` --- # Live coding!  Note: - Add an API endpoint - Generate API handlers - Show failing auth - Generate decorators Voila! --  --   --   --   --   --- ### What did we learn? - When you have schema, there is no reason to repeat yourself (DRY) - Compiler can find mistakes for you - You can drive improvement through automation --- ### Recommended tools - gRPC: [Buf](http://buf.build) - GraphQL: [gqlgen](https://gqlgen.com/) - Database: [sqlboiler](https://github.com/volatiletech/sqlboiler) - Decorators: [gowrap](https://github.com/hexdigest/gowrap) --- # Thanks! **Any questions for me?**   [https://github.com/antosdaniel/go-presentation-generate-code](https://github.com/antosdaniel/go-presentation-generate-code)