Slide 1

Slide 1 text

Ways To Do Things By your friend,
 @peterbourgon

Slide 2

Slide 2 text

Outline Actor stuff Running stuff Orchestrating stuff

Slide 3

Slide 3 text

Outline Actor stuff Running stuff Orchestrating stuff ➫

Slide 4

Slide 4 text

ctrl-C handler Storage HTTP API Cron State machine

Slide 5

Slide 5 text

ctrl-C handler Storage HTTP API Cron State machine

Slide 6

Slide 6 text

Actors (basic) type stateMachine struct {
 state string
 fooc chan fooReq
 barc chan barReq
 quitc chan struct{}
 } func (sm *stateMachine) loop() {
 for {
 select {
 case r := <-sm.fooc:
 sm.handleFoo(r)
 case r := <-sm.barc:
 sm.handleBar(r)
 case <-sm.quitc:
 return
 }
 }
 } func (sm *stateMachine) foo() int {
 c := make(chan int)
 sm.fooc <- fooReq{c}
 return <-c
 }

Slide 7

Slide 7 text

Actors (advanced?) type stateMachine struct {
 state string
 actionc chan func()
 quitc chan struct{}
 } func (sm *stateMachine) loop() {
 for {
 select {
 case f := <-sm.actionc:
 f()
 case <-sm.quitc:
 return
 }
 }
 } func (sm *stateMachine) foo() int {
 c := make(chan int)
 sm.actionc <- func() {
 if sm.state == "A" {
 sm.state = "B"
 }
 c <- 123
 }
 return <-c
 }

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

N chans 1 chan + Clean separation of concerns - Logic separate from methods - More types and fields; verbose
 - Mixing mechanics and logic
 
 · Relatively few API methods
 · Request/response pattern - Maybe not totally obvious 
 + Colocate method and logic + Fewer types and fields
 + Mechanics and logic separate
 
 · More API methods
 · More complex input/output

Slide 10

Slide 10 text

Outline Actor stuff Running stuff Orchestrating stuff ➫

Slide 11

Slide 11 text

Outline Actor stuff Running stuff Orchestrating stuff ➫

Slide 12

Slide 12 text

Hidden goroutine func newStateMachine() *stateMachine {
 sm := &stateMachine{
 state: "initial",
 actionc: make(chan func()),
 quitc: make(chan struct{}),
 }
 go sm.loop()
 return sm
 } func (sm *stateMachine) loop() {
 for {
 select {
 // ...
 case <-sm.quitc:
 return
 }
 }
 } func (sm *stateMachine) stop() {
 close(sm.quitc)
 }

Slide 13

Slide 13 text

Hidden goroutine++ func newStateMachine() *stateMachine {
 sm := &stateMachine{
 state: "initial",
 actionc: make(chan func()),
 quitc: make(chan chan struct{}),
 }
 go sm.loop()
 return sm
 } func (sm *stateMachine) loop() {
 for {
 select {
 // ...
 case q := <-sm.quitc:
 close(q)
 return
 }
 }
 } func (sm *stateMachine) stop() {
 q := make(chan struct{})
 sm.quitc <- q
 <-q
 }

Slide 14

Slide 14 text

Run method func newStateMachine() *stateMachine {
 return &stateMachine{
 state: "initial",
 actionc: make(chan func()),
 }
 } func (sm *stateMachine) Run(cancel <-chan struct{}) {
 for {
 select {
 // ...
 case <-cancel:
 return
 }
 }
 }

Slide 15

Slide 15 text

Run method func newStateMachine() *stateMachine {
 return &stateMachine{
 state: "initial",
 actionc: make(chan func()),
 }
 } func (sm *stateMachine) Run(ctx context.Context) error {
 for {
 select {
 // ...
 case <-ctx.Done():
 return ctx.Err()
 }
 }
 }

Slide 16

Slide 16 text

go loop Run + Obvious (?)
 + Self-contained component + Lifecycle encapsulation - Tricky to get determinism - Tricky to make re-entrant
 
 · Simpler components
 · When easy > correct - More work for caller
 - More state to track
 + Less scaffolding in component
 + Straightforward determinism + Much easier to test! 
 · More complex components
 · When correct > easy

Slide 17

Slide 17 text

Outline Actor stuff Running stuff Orchestrating stuff ➫

Slide 18

Slide 18 text

Outline Actor stuff Running stuff Orchestrating stuff ➫

Slide 19

Slide 19 text

ctrl-C handler Storage HTTP API Cron State machine

Slide 20

Slide 20 text

// Setup
 ctx, cancel := context.WithCancel(context.Background()) 
 
 go sm.Run(ctx) 
 go http.ListenAndServe(":8080", api)
 go cronJobs(sm)
 go signalCatcher(???)
 
 // wait for shutdown somehow

Slide 21

Slide 21 text

// Setup
 ctx, cancel := context.WithCancel(context.Background())
 
 go sm.Run(ctx) // cancel() 
 go http.ListenAndServe(":8080", api) // ?
 go cronJobs(sm) // ?
 go signalCatcher(???) // ?
 
 // wait for shutdown somehow

Slide 22

Slide 22 text

// Setup
 ctx, cancel := context.WithCancel(context.Background()) 
 ln, _ := net.Listen("tcp", ":8080")
 
 go sm.Run(ctx) // cancel()
 go http.Serve(ln, api) // ln.Close()
 go cronJobs(sm) // ?
 go signalCatcher(???) // ?
 
 // wait for shutdown somehow

Slide 23

Slide 23 text

// Setup
 ctx, cancel := context.WithCancel(context.Background()) 
 ln, _ := net.Listen("tcp", ":8080")
 cancelCron := make(chan struct{})
 
 go sm.Run(ctx) // cancel()
 go http.Serve(ln, api) // ln.Close()
 go cronJobs(cancelCron, sm) // close(cancelCron)
 go signalCatcher(???) // ?
 
 // wait for shutdown somehow

Slide 24

Slide 24 text

// Setup
 ctx, cancel := context.WithCancel(context.Background()) 
 ln, _ := net.Listen("tcp", ":8080")
 cancelCron := make(chan struct{})
 cancelSig := make(chan struct{})
 
 go sm.Run(ctx) // cancel()
 go http.Serve(ln, api) // ln.Close()
 go cronJobs(cancelCron, sm) // close(cancelCron)
 go signalCatcher(cancelSig) // close(cancelSig)
 
 // wait for shutdown somehow

Slide 25

Slide 25 text

gopkg.in/tomb.v2

Slide 26

Slide 26 text

golang.org/x/sync/errgroup

Slide 27

Slide 27 text

// Setup
 ctx, cancel := context.WithCancel(context.Background()) 
 ln, _ := net.Listen("tcp", ":8080")
 cancelCron := make(chan struct{})
 cancelSig := make(chan struct{})
 
 go sm.Run(ctx) // cancel()
 go http.Serve(ln, api) // ln.Close()
 go cronJobs(cancelCron, sm) // close(cancelCron)
 go signalCatcher(cancelSig) // close(cancelSig)
 
 // wait for shutdown somehow

Slide 28

Slide 28 text

github.com/oklog/oklog/pkg/group

Slide 29

Slide 29 text

github.com/oklog/oklog/pkg/group

Slide 30

Slide 30 text

github.com/oklog/oklog/pkg/group

Slide 31

Slide 31 text

Use a group var g group.Group
 {
 ctx, cancel := context.WithCancel(context.Background())
 g.Add(func() error {
 return sm.Run(ctx)
 }, func(error) {
 cancel()
 })
 }


Slide 32

Slide 32 text

Use a group 
 {
 ln, _ := net.Listen("tcp", ":8080")
 g.Add(func() error {
 return http.Serve(ln, api)
 }, func(error) {
 ln.Close()
 })
 }


Slide 33

Slide 33 text

Use a group 
 {
 cancel := make(chan struct{})
 g.Add(func() error {
 return cronJobs(cancel, sm)
 }, func(error) {
 close(cancel)
 })
 }


Slide 34

Slide 34 text

Use a group 
 {
 cancel := make(chan struct{})
 g.Add(func() error {
 return signalCatcher(cancel)
 }, func(error) {
 close(cancel)
 })
 }
 g.Run()

Slide 35

Slide 35 text

tomb errgroup + Complex lifecycles
 - Complex API - net.Listener? group + Small API
 - Context-only - net.Listener? + Super flexible
 - Esoteric API?
 + net.Listener!

Slide 36

Slide 36 text

Maybe the moral here is
 Give more control to the caller
 ~idk~

Slide 37

Slide 37 text

Ways To Do Things Thanks y'all