Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

GoらしいAPIを求める旅路 (Go Conference 2018 Spring)

12,330 views

Published on

GoっぽいAPIを求めてAPIデザインを考えていきます

Published in: Engineering
  • Very Nice: See Latest Blogs @ https://www.thesisscientist.com/Blog
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

GoらしいAPIを求める旅路 (Go Conference 2018 Spring)

  1. 1. Goらしい APIを求める旅路 GoCon 2018 Spring Apr 15 2018 Daisuke Maki @lestrrat (HDE inc)
  2. 2. • @lestrrat • Perl/Go hacker, author, father • Author of github.com/peco/peco • Organizer for builderscon
  3. 3. <宣伝> http://blog.builderscon.io/entry/call-for-sponsors-2018 スポンサー募集、今月末まで!
  4. 4. お題
  5. 5. 「Goっぽい」コード
  6. 6. Goを使うなら なるたけGoっぽいAPIがいい
  7. 7. Goを使うなら なるたけGoっぽいAPIがいい
  8. 8. github.com/cenkalti/backoff backoff.Retry(f, strategy)
  9. 9. github.com/cenkalti/backoff backoff.Retry(f, strategy) [func() error]
  10. 10. Callback/Closure • 関数を受け取るAPIでは、シグネチャがポイ ントになる • 関数を許すからにはadhocな関数呼び出しが できるべき
  11. 11. net/http type HandleFunc func(ResponseWriter, *Request) • net/httpでこの関数の副作用は気にしない でよい • AdhocなHandlerをポンポン登録できる
  12. 12. backoff.Retry(f, stretegy)
  13. 13. func Foo() (Result, Result, Result, error) What About…
  14. 14. backoff.Retry(Foo, …) This no worky…
  15. 15. backoff.Retry(Foo, …) This no worky… signatureが合わない
  16. 16. backoff.Retry(func() error { a, b, c, err := Foo() if err != nil { return err } }, …) Capture Return Values
  17. 17. backoff.Retry(func() error { a, b, c, err := Foo() if err != nil { return err } }, …) Capture Return Values この戻り値をRetryの外側で使うには?
  18. 18. var a, b, c Result backoff.Retry(func() error { a, b, c, err := Foo() if err != nil { return err } }, …) Upper Scope
  19. 19. var a, b, c Result backoff.Retry(func() error { a, b, c, err := Foo() if err != nil { return err } }, …) Upper Scope あ!
  20. 20. var a, b, c Result backoff.Retry(func() error { a, b, c, err = Foo() if err != nil { return err } }, …) Final Result
  21. 21. ちょっとまった! • スコープを気にしながらコードを書かな いといけない • := と = を間違ってもコンパイラが怒っ てくれない • 毎回ムダ?にクロージャを作ってて
  22. 22. どんなAPIだと ? + 拡張性を持たせられる?
  23. 23. Use Interface? backoff.Retry(f, stretegy)
  24. 24. Use Interface? backoff.Retry(f, stretegy) type Operation interface { Do() error }
  25. 25. type FooWrapper struct { A Result B Result C Result } func (f *FooWrapper) Do() error { f.A, f.B, f.C, err = Foo() … } Store The Results
  26. 26. var f FooWrapper backoff.Retry(f, …) // Use f.A, f.B, f.C Final Result
  27. 27. var f FooWrapper backoff.Retry(f, …) // Use f.A, f.B, f.C Final Result 「これじゃない感」… 毎回型を作る必要がある…
  28. 28. たとえばadhocな関数は?
  29. 29. type OperationFunc func() error func (f OperationFunc) Do() error { return f() } Adhoc Functions?
  30. 30. type OperationFunc func() error func (f OperationFunc) Do() error { return f() } Adhoc Functions? http.HandlerFuncと同じ 形にすればキレイになる?
  31. 31. var a, b, c Result backoff.Retry(OperationFunc(func() error { a, b, c, err = Foo() if err != nil { return err } }), …) あ、これ前と同じだ
  32. 32. 小細工では同じような形に なってしまう
  33. 33. 一歩引いて考え直す…
  34. 34. backoffは何をするのか • 関数を実行する • 成功したら終わる • 失敗したら、待つ • optional: context.Contextで途中停止
  35. 35. for { if err := f(); err == nil { return } time.Sleep(delay) } First pass
  36. 36. for { if err := f(); err == nil { return } time.Sleep(delay) } First pass 関数を実行する 成功したら終わる 失敗したら待つ
  37. 37. backoffは何をするのか • 関数を実行する • 成功したら終わる • 失敗したら、待ち時間を増やしながら待つ • optional: context.Contextで途中停止
  38. 38. for { if err := f(); err == nil { return } select { case <-ctx.Done(): return case <-time.After(nextDelay()): } } Second pass
  39. 39. for { if err := f(); err == nil { return } select { case <-ctx.Done(): return case <-time.After(nextDelay()): } } Second pass 関数を実行する 成功したら終わる
  40. 40. for { if err := f(); err == nil { return } select { case <-ctx.Done(): return case <-time.After(nextDelay()): } } Second pass 関数を実行する 成功したら終わる 失敗したら待ち時間を 増やしながら待つ context.Contextで 途中停止
  41. 41. この形を簡単に書けるように すればいいはず…
  42. 42. github.com/lestrrat-go/backoff
  43. 43. policy := backoff.NewExponential(…) Backoff Policy Object
  44. 44. b, cancel := policy.Start(ctx) Backoff object
  45. 45. b, cancel := policy.Start(ctx) defer cancel() for { if err := f(); err == nil { return } select { case <-b.Done(): return case <-b.Next(): } }
  46. 46. b, cancel := policy.Start(ctx) defer cancel() for { if err := f(); err == nil { return } select { case <-b.Done(): return case <-b.Next(): } } <-chan time.Time <-chan struct{}
  47. 47. ただのコードブロックなので 戻り値も自由自在
  48. 48. b, cancel := policy.Start(ctx) defer cancel() for { a, b, c, err := f() if err == nil { // use or return a, b, c here } select { case <-b.Done(): return case <-b.Next(): } }
  49. 49. b, cancel := policy.Start(ctx) defer cancel() for { a, b, c, err := f() if err == nil { // use or return a, b, c here } select { case <-b.Done(): return case <-b.Next(): } }
  50. 50. Bonus: separate Policy/Backoff
  51. 51. b := cenkalti.NewExponential() for … { go func(b cenkalti.Backoff) { // b is shared… be careful! }(b) } bを共有せざるを得ない
  52. 52. for … { go func(b backoff.Backoff, cancel func()) { // b is safe to be used in a separate goroutine }(policy.Start(ctx)) } 共有されるpolicyとbは別
  53. 53. Problem: Boilerplate Code is Long
  54. 54. b, cancel := policy.Start(ctx) defer cancel() for { a, b, c, err := f() if err == nil { return } select { case <-b.Done(): return case <-b.Next(): } }
  55. 55. b, cancel := policy.Start(ctx) defer cancel() for { a, b, c, err := f() if err == nil { return } select { case <-b.Done(): return case <-b.Next(): } }
  56. 56. for { select { case <-b.Done(): return case <-b.Next(): } }
  57. 57. for { select { case <-b.Done(): return case <-b.Next(): } }
  58. 58. for { select { case <-b.Done(): return case <-b.Next(): } } bail out of loop
  59. 59. for { select { case <-b.Done(): return case <-b.Next(): } } bail out of loop continue loop
  60. 60. これ、Loop Condition だ
  61. 61. func Continue(b *Backoff) bool { select { case <-b.Done(): return false case <-b.Next(): return true } return false // never reached } func to determine loop condition
  62. 62. b, cancel := policy.Start(ctx) defer cancel() for backoff.Continue(b) { a, b, c, err := f() if err == nil { return } } キュッとした!
  63. 63. b, cancel := policy.Start(ctx) defer cancel() for backoff.Continue(b) { a, b, c, err := f() if err == nil { return } } キュッとした! use as condition
  64. 64. 確認
  65. 65. 確認 contextで途中停止
  66. 66. 確認 channelでタイミング制御 contextで途中停止
  67. 67. 確認 channelでタイミング制御 for backoff.Continue() でループ制御 contextで途中停止
  68. 68. 確認 channelでタイミング制御 for backoff.Continue() でループ制御 contextで途中停止 goroutine safe by design
  69. 69. 確認 クロージャに頼る必要がない channelでタイミング制御 for backoff.Continue() でループ制御 contextで途中停止 goroutine safe by design
  70. 70. APIデザインをする時はその言 語の自然なフローを許す形を 考えるのがお勧め
  71. 71. (なお、コードをよく読むと、 cenkalti/backoffも実装はほぼ同じ仕 組みになっているが、公開しているイ ンターフェースがまったく違う)
  72. 72. おまけ
  73. 73. 「Google APIから学ぶ自動生成」 ※ Goの連載記事です ※ GoのためのAPIデザインの話です
  74. 74. End

×