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

Case studies of designing developer friendly li...

Case studies of designing developer friendly libraries #gocon

Avatar for Masayuki Izumi

Masayuki Izumi

May 18, 2019
Tweet

More Decks by Masayuki Izumi

Other Decks in Programming

Transcript

  1. ©2019 Wantedly, Inc. Case studies of
 designing developer friendly libraries

    Go Conference 2019 Spring May 18, 2019 - Masayuki Izumi(@izumin5210)
  2. ©2019 Wantedly, Inc. 1. Simple • ΄͔ͷ΋ͷͱབྷΈ߹ͬͯͳ͍ • ઈରతɾ٬؍త 2.

    Easy • ਎ۙͰ͋Δɺ਌͠Έ΍͍͢ • ૬ରతɾओ؍త Simple / Easy Simple Made Easy * https://speakerdeck.com/takeru0757/simple-is-not-easy?slide=49
  3. ©2019 Wantedly, Inc. ڷʹै͏ Go քͷ׳शʹै͏ “ڷʹೖͬͯ͸ڷʹै͑”  ༨ܭͳࢥߟ΍஌ࣝΛཁٻ͠ͳ͍͍ͯ͘Α͏ʹɺ׳शʹै͏ 

    FH  AFSSPSAΛฦ͢ͱ͖͸Ҿ਺ͷ࠷ޙʹ  ADPOUFYU$POUFYUA͸Ҿ਺ͷ࠷ॳʹ  'VODUJPOBMPQUJPO͸A8JUIA͔Β͸͡ΊΔʢॾઆ͋Γʣ  ΤσΟλͷิ׬ͰPQUJPOͷҰཡ͕ݟΕͯศར
  4. ©2019 Wantedly, Inc. `context.Context` ਖ਼͘͠࢖͓͏ context  ωοτϫʔΫ*0ͳͲɺ͔͔࣌ؒΔॲཧ͕͋Δͱ͖͸ඞͣDPOUFYUΛ౉͢ʂ  ωοτϫʔΫෆௐͰΊͬͪΌ͔͔࣌ؒΔ͔΋ˠλΠϜΞ΢τ͍ͨ͠

     $POUFYUΛར༻ͯ͠ϝτϦΫεऩूͳͲΛߦ͏πʔϧ΁ͷରԠʢޙड़ʣ  HPPHMFDMPVEHPͳͲɺίϯετϥΫλͰDPOUFYUΛ
 ཁٻ͢Δύλʔϯ΋͋Δ client, err := pubsub.NewClient(ctx, "project-id") if err != nil { // ... } ڷʹै͏
  5. ©2019 Wantedly, Inc. `context.Context`ͱܭଌ  DPOUFYUʹσʔλऩू༻ͷTUSVDUΛ࢓ࠐΉύοέʔδ͕ଘࡏ͢Δ  FHPQFODFOTVTHP OFXSFMJDHPBHFOU 

    AIUUQ 3PVOE5SJQQFSA΍ATUBUT)BOEMFSAͰDPOUFYU͔Β
 ΦϒδΣΫτΛऔΓग़͠ɺܭଌͨ͠σʔλΛॻ͖ࠐΉ  ͜ͷ࢓૊ΈΛਖ਼͘͠ಈ࡞ͤ͞ΔͨΊʹ΋$POUFYU఻ൖ͸ॏཁ func traceHandleRPC(ctx context.Context, rs stats.RPCSt span := trace.FromContext(ctx) switch rs := rs.(type) { case *stats.Begin: span.AddAttributes( trace.BoolAttribute("Client", rs.Client), trace.BoolAttribute("FailFast", rs.FailFast)) case *stats.InPayload: // snip. case *stats.OutPayload: // snip. case *stats.End: // snip. span.End() } } https://github.com/census-instrumentation/opencensus-go/blob/v0.21.0/plugin/ocgrpc/trace_common.go#L84-L107 ڷʹै͏
  6. ©2019 Wantedly, Inc. Interceptor / Middleware  ॲཧલޙʹϑοΫͰ͖ΔػߏΛ࡞Δύλʔϯ  H31$ʹ͸JOUFSDFQUPSͱ͍͏໊લͰ࣮૷͞Ε͍ͯΔ

     ॲཧͷલޙͰϏδωεϩδοΫʹؔ܎ͳ͍ॲཧΛ࣮ߦͰ͖Δ  FHೝূ ϩΪϯά ΤϥʔϨϙʔτ ʜ * https://docs.pylonsproject.org/projects/pylons-webframework/en/latest/concepts.html#wsgi-middleware ڷʹै͏
  7. ©2019 Wantedly, Inc. ֦ுੑΛอͭ Redigoͷྫ github.com/gomodule/redigo  3FEJTΫϥΠΞϯτ  A$POOAΠϯλϑΣʔεʹίϚϯυൃߦ༻ͷ


    ϝιου͕ੜ͍͑ͯΔ type Conn interface { // Close closes the connection. Close() error // Err returns a non-nil value when the connection is not usable. Err() error // Do sends a command to the server and returns the received reply. Do(commandName string, args ...interface{}) (reply interface{}, err // Send writes the command to the client's output buffer. Send(commandName string, args ...interface{}) error // Flush flushes the output buffer to the Redis server. Flush() error // Receive receives a single reply from the Redis server Receive() (reply interface{}, err error) }
  8. ©2019 Wantedly, Inc. Redigoͷྫ github.com/gomodule/redigo  ࣮ࡍͷར༻Ͱ͸ɺ͍͍ͩͨDPOOFDUJPOQPPMΛ
 ࢖͏͜ͱʹͳΔ pool :=

    &redis.Pool{ Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, } conn, err := pool.GetContext(ctx) ֦ுੑΛอͭ
  9. ©2019 Wantedly, Inc. Redigoͷྫ github.com/gomodule/redigo  ࣮ࡍͷར༻Ͱ͸ɺ͍͍ͩͨ$POOFDUJPO1PPMΛ
 ࢖͏͜ͱʹͳΔ pool :=

    &redis.Pool{ DialContext: func (ctx context.Context) (redis.Conn, error) { return redis.Dial("tcp", addr) }, } conn, err := pool.GetContext(ctx) ֦ுੑΛอͭ
  10. ©2019 Wantedly, Inc. Redigoͷྫ github.com/gomodule/redigo  ࣮ࡍͷར༻Ͱ͸ɺ͍͍ͩͨ$POOFDUJPO1PPMΛ
 ࢖͏͜ͱʹͳΔ  %JBM࣮૷ΛಠࣗఆٛͰ͖ΔΑ͏ʹͳ͍ͬͯΔͷ

    ͰɺASFEJT$POOAΛXSBQ͢Δ͜ͱͰϩΨʔ΍ ϝτϦΫεऩूΛ࢓ࠐΊΔ  ࣅͨྫͱͯ͠͸AOFUIUUQ5SBOTQPSUAͳͲ pool := &redis.Pool{ DialContext: func (ctx context.Context) (redis.Conn, error) { conn, err := redis.Dial("tcp", addr) if err != nil { return nil, err } return redis.NewLoggingConn(conn, logger, "redis") }, } conn, err := pool.GetContext(ctx) ֦ுੑΛอͭ
  11. ©2019 Wantedly, Inc. google-cloud-go ͷྫ cloud.google.com/go  $MPVE%BUBTUPSF΍$MPVE1VC4VCͳͲɺ
 ($1Ͱఏڙ͞ΕΔ͋ΒΏΔαʔϏεͷΫϥΠΞ ϯτ͕ଘࡏ

     ͍͍ͩͨͷΫϥΠΞϯτ͸ίϯετϥΫλͰΦϓ γϣϯΛड͚औΕΔΑ͏ʹͳ͍ͬͯΔ client, err := pubsub.NewClient(ctx, "project-id") if err != nil { // ... } ֦ுੑΛอͭ
  12. ©2019 Wantedly, Inc. google-cloud-go ͷྫ cloud.google.com/go  ίϯετϥΫλʹ౉ͤΔΦϓγϣϯʹ͸ɺ௨৴ʹ ར༻͢ΔA HSQD$MJFOU$POOA΍

    A IUUQ$MJFOUAͳͲ΋ؚ·ΕΔ  JOUFSDFQUPS΍TUBUTIBOEMFSͳͲ͕ඞཁʹͳΔ ϩΨʔ΍ܭଌܥͳͲ͸࢓ࠐΈ์୊ * https://godoc.org/google.golang.org/api/option#ClientOption ֦ுੑΛอͭ
  13. ©2019 Wantedly, Inc. Functional option  ϥΠϒϥϦͳͲͰɺΦϓγϣϯΛࢦఆͤ͞ΔΑ͏ ʹ͢ΔͨΊͷύλʔϯ  DGCVJMEFSQBUUFSO

    type Option func(*Config) func WithHTTPClient(client *http.Client) Option { return func(c *Config) { c.HTTPClient = client } } func NewClient(opts ...Option) Client { cfg := new(Config) for _, f := range opts { f(cfg) } // ... } ֦ுੑΛอͭ
  14. ©2019 Wantedly, Inc. type Option func(*Config) func WithHTTPClient(client *http.Client) Option

    { return func(c *Config) { c.HTTPClient = client } } func NewClient(opts ...Option) Client { cfg := new(Config) for _, f := range opts { f(cfg) } // ... } func NewClientBuilder() *Builder { return &Builder{} } func (b *Builder) SetHTTPClient(client *http.Client) *Builder { b.HTTPClient = client return b } func (b *Builder) Build() Client { // ... } Builder pattern Functional option pattern
  15. ©2019 Wantedly, Inc. type Option func(*Config) func WithHTTPClient(client *http.Client) Option

    { return func(c *Config) { c.HTTPClient = client } } func NewClient(opts ...Option) Client { cfg := new(Config) for _, f := range opts { f(cfg) } // ... } func NewClientBuilder() *Builder { return &Builder{} } func (b *Builder) SetHTTPClient(client *http.Client) *Builder { b.HTTPClient = client return b } func (b *Builder) Build() Client { // ... } Builder pattern Functional option pattern Ϣʔβ͕ಠࣗPQUJPOΛఆٛ͢Δ͜ͱ΋ՄೳͳͷͰɺ ݸਓతʹ͸'VODUJPOBMPQUJPOͷ΄͏͕޷͖
  16. ©2019 Wantedly, Inc. ܕΛ׆͔ͨ͢Ίͷίʔυੜ੒  FH  03.42-#PJMFS YPʢ֎෦ʹ͋ΔεΩʔϚΛ(Pʹམͱ͠ࠐΉʣ 

    %*XJSFʢܕΛݟͯґଘؔ܎ͷ%"(Λͭ͘Δʣ  ͱ͘ʹ(Pͷ֎ʹܕఆ͕ٛ͋Δ৔߹ʢFH%# TXBHHFS QSPUPCVGʣʹ༗ޮ  Ϣʔβ͕ੜ੒ޙͷίʔυΛฤू͢Δඞཁ͕ͳ͍Α͏ʹੜ੒ͯ͋͛͠Δͱྑ͍  ੜ੒πʔϧ͕Ξοϓσʔτ͞Εͨͱ͖ʹ௥ैͰ͖ΔΑ͏ʹ // Code generated by protoc-gen-go. DO NOT EDIT. // source: content.proto package api_pb import ( context "context" fmt "fmt" ADO NOT EDIT` ͬͯॻ͍͓ͯ͘ͱ ͍ΖΜͳπʔϧͰແࢹͯ͠΋Β͑·͢ // Code generated by protoc-gen-go. DO NOT EDIT. // source: content.proto package api_pb import ( context "context" ίʔυੜ੒
  17. ©2019 Wantedly, Inc. Boilerplate ੜ੒  FH  TUSJOHFSΊΜͲ͍͘͞εχϖοτͷੜ੒ 

    HSBQJΞϓϦέʔγϣϯίʔυͷTDBGGPMEJOH  ʮϢʔβ͕ฤू͢ΔίʔυʯʮϢʔβ͕৮Βͳ͍΄͏͕ྑ͍ίʔυʯΛ
 ͦΕͧΕผϑΝΠϧʹు͍͓ͯ͘ͷ͕ྑ͍ʢཧ༝͸͖ͬ͞ͱҰॹʣ  ʮϢʔβ͕ฤू͢Δίʔυʯ͸࠷খݶʹཹΊΔ // NewBookServiceServer creates a new BookServiceServer instance func NewBookServiceServer() BookServiceServer { return &bookServiceServerImpl{} } type bookServiceServerImpl struct { } func (s *bookServiceServerImpl) ListBooks(ctx context.Context, r // TODO: Not yet implemented. return nil, status.Error(codes.Unimplemented, "TODO: You shoul } func (s *bookServiceServerImpl) GetBook(ctx context.Context, req // TODO: Not yet implemented. return nil, status.Error(codes.Unimplemented, "TODO: You shoul } func (s *bookServiceServerImpl) CreateBook(ctx context.Context, // TODO: Not yet implemented. return nil, status.Error(codes.Unimplemented, "TODO: You shoul } func (s *bookServiceServerImpl) UpdateBook(ctx context.Context, // TODO: Not yet implemented. return nil, status.Error(codes.Unimplemented, "TODO: You shoul } func (s *bookServiceServerImpl) DeleteBook(ctx context.Context, // TODO: Not yet implemented. return nil, status.Error(codes.Unimplemented, "TODO: You shoul } ίʔυੜ੒
  18. ©2019 Wantedly, Inc. ͍͍ײ͡ʹ΍Δࣾ಺ϥΠϒϥϦ  8):  αʔόΛຊ൪؀ڥͰಈ͔͢ʹ͸ɺ"1.ͱBDDFTTMPHͱFSSPSSFQPSUͱʜ
 ΍Δ͜ͱ͕͍ͬͺ͍͋Δ 

    Θ͢Εͦ͏  8)"5  ؀ڥม਺ΛೖΕΔ͚ͩͰ͍͍ײ͡ʹશͯಈ͘Α͏ʹ͍ͨ͠ func run() error { defer servicex.Close() ctx := context.Background() s := grapiserver.New( grpcx.WithDefault(), grapiserver.WithServers( NewContentServiceServer(), ), ) return s.Serve() } উखʹ͍Ζ͍Ζ΍Δ
  19. ©2019 Wantedly, Inc. ͍͍ײ͡ʹ΍Δࣾ಺ϥΠϒϥϦ  ؀ڥม਺ಡΈग़͠dॾʑͷॳظԽΛATFSWJDFYAͱ͍͏ύοέʔδͷ
 AJOJU AͰશ෦উखʹ΍Δ  AJOJU

    AͳͷͰJNQPSU͑͞͞Ε͍ͯΕ͹Α͍  ˠAEFGFSTFSWJDFY$MPTF AΛॻ͔ͤΔ͚ͩͰΑ͍  ʢCMBOLJNQPSU͡Όͳ͍ͷ͸खͰॻ͘ͷΊΜͲ͍͔͘͞Βʣ func run() error { defer servicex.Close() ctx := context.Background() s := grapiserver.New( grpcx.WithDefault(), grapiserver.WithServers( NewContentServiceServer(), ), ) return s.Serve() } func init() { loadEnv() initErrorReporter() initLogger() // ... } উखʹ͍Ζ͍Ζ΍Δ
  20. ©2019 Wantedly, Inc. $PNQPVOEGVODPQUJPO  1SPEVDUJPOSFBEZͳαʔόʹඞਢͷػೳ͕શͯ༗ޮʹͳΔΑ͏ͳ
 ΧελϜGVODUJPOBMPQUJPOΛͭ͘Δ  'VODUJPOBMPQUJPO͸͍͍͕ͩͨ
 AGVOD

    $POpH A͔AGVOD $POpH  $POpHAͳͷͰɺ߹੒Ͱ͖Δ func WithDefault() grapiserver.Option { opts := []grapiserver.Option{ grapiserver.WithGrpcServerUnaryInterceptors( grpc_ctxtags.UnaryServerInterceptor(ctxtagsOptio ClientMetadataUnaryServerInterceptor(), AccessLoggingUnaryServerInterceptor(), grpc_zap.UnaryServerInterceptor(zap.L(), ...), ErrorsUnaryServerInterceptor(), RecoveryUnaryServerInterceptor(), AuthUnaryServerInterceptor(), ), // ... } return func(c *grapiserver.Config) { for _, f := range opts { f(c) } } } func run() error { defer servicex.Close() ctx := context.Background() s := grapiserver.New( grpcx.WithDefault(), grapiserver.WithServers( উखʹ͍Ζ͍Ζ΍Δ
  21.  lڷʹೖͬͯ͸ڷʹै͑z  ͪΌΜͱ(Pͷ׳शʹԊͬͨ"1*ʹ͠Α͏  ֦ுੑΛอͭ  ೉͍͜͠ͱΛ΍Ζ͏ͱͨ͠ͱ͖ʹࠔΒͳ͍Α͏ʹ͠Α͏  ࠷ѱίΞʹͳͬͯΔύοέʔδʹ΋৮ΕΒΕΔΑ͏ʹ͠Α͏

     ίʔυੜ੒Λ͏·͘࢖͏͜ͱΛݕ౼͠Α͏  ࢖͍͗͢ɾ࢖͍ॴʹ͸ཁ஫ҙʂ  Ϣʔβ͕ݶΒΕΔɺดͨ͡ίϯςΩετɺϏδωεϩδοΫ͕བྷ·ͳ͍ͳΒɺ
 উखʹ৭ʑ΍Δύοέʔδ΋͋Γ͔΋ʂʁ