Slide 1

Slide 1 text

Adding context to existing code Alexey Palazhchenko

Slide 2

Slide 2 text

Existing code QueryRow(string, ...interface{}) *Row Query(string, ...interface{}) (*Rows, error) Exec(string, ...interface{}) (Result, error)

Slide 3

Slide 3 text

Solution 1: «just» add ctx QueryRow(string, ...interface{}) *Row QueryRow(context.Context, string, ...interface{}) *Row Query(string, ...interface{}) (*Rows, error) Query(context.Context, string, ...interface{}) (*Rows, error) Exec(string, ...interface{}) (Result, error) Exec(context.Context, string, ...interface{}) (Result, error)

Slide 4

Slide 4 text

Solution 1 problems • Internal code – large-scale refactoring.

Slide 5

Slide 5 text

Solution 1 problems • Internal code – large-scale refactoring. • Use context.TODO().

Slide 6

Slide 6 text

Solution 1 problems • Internal code – large-scale refactoring. • Use context.TODO(). • «TODO is recognized by static analysis tools that determine whether Contexts are propagated correctly in a program.»

Slide 7

Slide 7 text

Solution 1 problems • Internal code – large-scale refactoring. • Use context.TODO(). • «TODO is recognized by static analysis tools that determine whether Contexts are propagated correctly in a program.» • This tool does not exist.

Slide 8

Slide 8 text

Solution 1 problems • External code – large-scale refactoring for users.

Slide 9

Slide 9 text

Solution 1 problems • External code – large-scale refactoring for users. • v0.m -> v0.m+1

Slide 10

Slide 10 text

Solution 1 problems • External code – large-scale refactoring for users. • v0.m -> v0.m+1 • vM -> vM+1

Slide 11

Slide 11 text

Solution 2x QueryRow(string, ...interface{}) *Row QueryRowContext(context.Context, string, ...interface{}) *Row Query(string, ...interface{}) (*Rows, error) QueryContext(context.Context, string, ...interface{}) (*Rows, error) Exec(string, ...interface{}) (Result, error) ExecContext(context.Context, string, ...interface{}) (Result, error)

Slide 12

Slide 12 text

Solution 2x Delete(record Record) error DeleteFrom(view View, tail string, args ...interface{}) (uint, error) Exec(query string, args ...interface{}) (sql.Result, error) FindAllFrom(view View, column string, args ...interface{}) ([]Struct, error) FindByPrimaryKeyFrom(table Table, pk interface{}) (Record, error) FindByPrimaryKeyTo(record Record, pk interface{}) error FindOneFrom(view View, column string, arg interface{}) (Struct, error) FindOneTo(str Struct, column string, arg interface{}) error FindRows(view View, column string, arg interface{}) (*sql.Rows, error) Insert(str Struct) error InsertColumns(str Struct, columns ...string) error InsertMulti(structs ...Struct) error NextRow(str Struct, rows *sql.Rows) error QualifiedColumns(view View) []string QualifiedView(view View) string Query(query string, args ...interface{}) (*sql.Rows, error) QueryRow(query string, args ...interface{}) *sql.Row Reload(record Record) error Save(record Record) error SelectAllFrom(view View, tail string, args ...interface{}) (structs []Struct, err error) SelectOneFrom(view View, tail string, args ...interface{}) (Struct, error) SelectOneTo(str Struct, tail string, args ...interface{}) error SelectRows(view View, tail string, args ...interface{}) (*sql.Rows, error) Update(record Record) error UpdateColumns(record Record, columns ...string) error UpdateView(str Struct, columns []string, tail string, args ...interface{}) (uint, error)

Slide 13

Slide 13 text

Solution 3 type Querier struct { Context context.Context } func (q *Querier) QueryRow(query string, args ...interface{}) *sql.Row

Slide 14

Slide 14 text

https://golang.org/pkg/context/ «Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx.» Solution 3 «problem»

Slide 15

Slide 15 text

https://github.com/golang/go/issues/14660 «While we've told people not to add contexts to structs, I think that guidance is over-aggressive. The real advice is not to store contexts. They should be passed along like parameters. But if the struct is essentially just a parameter, it's okay.» Solution 3

Slide 16

Slide 16 text

https://github.com/golang/go/issues/22602 context: relax recommendation against putting Contexts in structs Solution 3

Slide 17

Slide 17 text

Solution 3b type Querier struct { ctx context.Context } func (q *Querier) WithContext(ctx context.Context) *Querier func (q *Querier) QueryRow(query string, args ...interface{}) *sql.Row

Slide 18

Slide 18 text

Conclusion • "Just" add ctx to internal code.

Slide 19

Slide 19 text

Conclusion • "Just" add ctx to internal code. • Consider your options for the external code.

Slide 20

Slide 20 text

Conclusion • "Just" add ctx to internal code. • Consider your options for the external code. • Know best practices beyond their short versions.