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

Case studies of designing developer friendly libraries #gocon

Case studies of designing developer friendly libraries #gocon

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)

    View full-size slide

  2. @izumin5210
    Application Engineer, Wantedly People
    Wantedly, Inc.

    View full-size slide

  3. ©2019 Wantedly, Inc.
    developer friendly ͳύοέʔδʁ

    View full-size slide

  4. ©2019 Wantedly, Inc.
    developer friendly ͳύοέʔδʁ
    ͔͍ͭ΍͍͢ʁ
    Θ͔Γ΍͍͢ʁ
    গͳ͍ίʔυྔͰ͔͚Δʁ
    ࢖ͬͯͯͨͷ͍͠ʁ

    View full-size slide

  5. ©2019 Wantedly, Inc.
    developer friendly ͳύοέʔδʁ
    ͔͍ͭ΍͍͢ʁ
    Θ͔Γ΍͍͢ʁ
    গͳ͍ίʔυྔͰ͔͚Δʁ
    ࢖ͬͯͯͨͷ͍͠ʁ
    Simple?
    Easy?

    View full-size slide

  6. ©2019 Wantedly, Inc.
    1. Simple
    • ΄͔ͷ΋ͷͱབྷΈ߹ͬͯͳ͍
    • ઈରతɾ٬؍త
    2. Easy
    • ਎ۙͰ͋Δɺ਌͠Έ΍͍͢
    • ૬ରతɾओ؍త
    Simple / Easy Simple Made Easy
    * https://speakerdeck.com/takeru0757/simple-is-not-easy?slide=49

    View full-size slide

  7. ©2019 Wantedly, Inc.
    SimpleͱEasy͸ಉ͡Ͱ͸ͳ͍

    View full-size slide

  8. ©2019 Wantedly, Inc.
    Simpleͷରٛޠ͕EasyͳΘ͚Ͱ͸ͳ͍
    Simpleͷ൓ର͸Complexʁ

    View full-size slide

  9. ©2019 Wantedly, Inc.
    Easy͕ѱͳΘ͚Ͱ͸ͳ͍
    ։ൃͷॳ଎ΛߴΊΔ

    View full-size slide

  10. ©2019 Wantedly, Inc.
    ͳʹ͕Easy͔͸ϢʔβͷίϯςΩετʹΑΔ

    View full-size slide

  11. ©2019 Wantedly, Inc.
    Simple͞ΛଛͳΘͳ͍ɺ
    ͔ͭEasy ͳϥΠϒϥϦσβΠϯͷྫΛݟ͍ͯ͘
    Today’s goal

    View full-size slide

  12. ©2019 Wantedly, Inc.
    ڷʹै͏
    ઓུͦͷᶃ

    View full-size slide

  13. ©2019 Wantedly, Inc.
    ڷʹै͏
    Go քͷ׳शʹै͏
    “ڷʹೖͬͯ͸ڷʹै͑”
    ༨ܭͳࢥߟ΍஌ࣝΛཁٻ͠ͳ͍͍ͯ͘Α͏ʹɺ׳शʹै͏
    FH
    AFSSPSAΛฦ͢ͱ͖͸Ҿ਺ͷ࠷ޙʹ
    ADPOUFYU$POUFYUA͸Ҿ਺ͷ࠷ॳʹ
    'VODUJPOBMPQUJPO͸A8JUIA͔Β͸͡ΊΔʢॾઆ͋Γʣ
    ΤσΟλͷิ׬ͰPQUJPOͷҰཡ͕ݟΕͯศར

    View full-size slide

  14. ©2019 Wantedly, Inc.
    `context.Context`
    ਖ਼͘͠࢖͓͏ context
    ωοτϫʔΫ*0ͳͲɺ͔͔࣌ؒΔॲཧ͕͋Δͱ͖͸ඞͣDPOUFYUΛ౉͢ʂ
    ωοτϫʔΫෆௐͰΊͬͪΌ͔͔࣌ؒΔ͔΋ˠλΠϜΞ΢τ͍ͨ͠
    $POUFYUΛར༻ͯ͠ϝτϦΫεऩूͳͲΛߦ͏πʔϧ΁ͷରԠʢޙड़ʣ
    HPPHMFDMPVEHPͳͲɺίϯετϥΫλͰDPOUFYUΛ

    ཁٻ͢Δύλʔϯ΋͋Δ
    client, err := pubsub.NewClient(ctx, "project-id")
    if err != nil {
    // ...
    }
    ڷʹै͏

    View full-size slide

  15. ©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
    ڷʹै͏

    View full-size slide

  16. ©2019 Wantedly, Inc.
    Interceptor / Middleware
    ॲཧલޙʹϑοΫͰ͖ΔػߏΛ࡞Δύλʔϯ
    H31$ʹ͸JOUFSDFQUPSͱ͍͏໊લͰ࣮૷͞Ε͍ͯΔ
    ॲཧͷલޙͰϏδωεϩδοΫʹؔ܎ͳ͍ॲཧΛ࣮ߦͰ͖Δ
    FHೝূ ϩΪϯά ΤϥʔϨϙʔτ ʜ
    * https://docs.pylonsproject.org/projects/pylons-webframework/en/latest/concepts.html#wsgi-middleware
    ڷʹै͏

    View full-size slide

  17. ©2019 Wantedly, Inc.
    ֦ுੑΛอͭ
    ઓུͦͷᶄ

    View full-size slide

  18. ©2019 Wantedly, Inc.
    ͪΐͬͱෳࡶͳ͜ͱ͢ΔͱҟৗʹେมʹͳΔ
    &BTZͳ΋ͷΛ࢖͏ͱ͖ʹΑ͘ى͜Δ͜ͱ

    View full-size slide

  19. ©2019 Wantedly, Inc.
    ͪΐͬͱෳࡶͳ͜ͱ͢ΔͱҟৗʹେมʹͳΔ
    &BTZͳ΋ͷΛ࢖͏ͱ͖ʹΑ͘ى͜Δ͜ͱ
    ͳΔ΂֦͘ுੑΛอ͍ͪͨ

    View full-size slide

  20. ©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)
    }

    View full-size slide

  21. ©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)
    ֦ுੑΛอͭ

    View full-size slide

  22. ©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)
    ֦ுੑΛอͭ

    View full-size slide

  23. ©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)
    ֦ுੑΛอͭ

    View full-size slide

  24. ©2019 Wantedly, Inc.
    google-cloud-go ͷྫ
    cloud.google.com/go
    $MPVE%BUBTUPSF΍$MPVE1VC4VCͳͲɺ

    ($1Ͱఏڙ͞ΕΔ͋ΒΏΔαʔϏεͷΫϥΠΞ
    ϯτ͕ଘࡏ
    ͍͍ͩͨͷΫϥΠΞϯτ͸ίϯετϥΫλͰΦϓ
    γϣϯΛड͚औΕΔΑ͏ʹͳ͍ͬͯΔ
    client, err := pubsub.NewClient(ctx, "project-id")
    if err != nil {
    // ...
    }
    ֦ுੑΛอͭ

    View full-size slide

  25. ©2019 Wantedly, Inc.
    google-cloud-go ͷྫ
    cloud.google.com/go
    ίϯετϥΫλʹ౉ͤΔΦϓγϣϯʹ͸ɺ௨৴ʹ
    ར༻͢ΔAHSQD$MJFOU$POOA΍
    AIUUQ$MJFOUAͳͲ΋ؚ·ΕΔ
    JOUFSDFQUPS΍TUBUTIBOEMFSͳͲ͕ඞཁʹͳΔ
    ϩΨʔ΍ܭଌܥͳͲ͸࢓ࠐΈ์୊
    * https://godoc.org/google.golang.org/api/option#ClientOption
    ֦ுੑΛอͭ

    View full-size slide

  26. ©2019 Wantedly, Inc.
    ͋ΒΏΔ΋ͷΛࠩ͠ସ͑Մೳʹ͓ͯ͘͠
    ͨͱ͑͹"1*ΫϥΠΞϯτͰ͋Ε͹ɺ

    HSQD$MJFOU$POO΍IUUQ$MJFOUʹΞΫηεՄೳͳύεΛ͓ͭͬͯ͘͘ͱศར
    ϝτϦΫεऩू΍෼ࢄτϨʔγϯάͷεύϯੜ੒ͷϑοΫͳͲʹར༻Ͱ͖Δ
    ίϯετϥΫλͳͲʹGVODUJPOBMPQUJPOͳͲͷܗࣜͰ౉ͤΔΑ͏ʹ͓ͯ͘͠
    ֦ுੑΛอͭ

    View full-size slide

  27. ©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)
    }
    // ...
    }
    ֦ுੑΛอͭ

    View full-size slide

  28. ©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

    View full-size slide

  29. ©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ͷ΄͏͕޷͖

    View full-size slide

  30. ©2019 Wantedly, Inc.
    ίʔυੜ੒
    ઓུͦͷᶅ

    View full-size slide

  31. ©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"
    ίʔυੜ੒

    View full-size slide

  32. ©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
    }
    ίʔυੜ੒

    View full-size slide

  33. ©2019 Wantedly, Inc.
    উखʹ͍Ζ͍Ζ΍Δ
    ઓུͦͷᶆ

    View full-size slide

  34. ©2019 Wantedly, Inc.
    ͔͜͜Β͸༻๏༻ྔΛकͬͯ
    ਖ਼͘͠࢖͍·͠ΐ͏ ⚠

    View full-size slide

  35. ©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()
    }
    উखʹ͍Ζ͍Ζ΍Δ

    View full-size slide

  36. ©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()
    // ...
    }
    উखʹ͍Ζ͍Ζ΍Δ

    View full-size slide

  37. ©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(
    উखʹ͍Ζ͍Ζ΍Δ

    View full-size slide

  38. ©2019 Wantedly, Inc.
    ͜Μͳʹউखͳ͜ͱͯ͠ྑ͍ͷ͔ʁ
    ϏδωεϩδοΫʹؔΘΔΑ͏ͳίʔυͩͱμϝ
    ڞ༗ίʔυ͸ଟ͚Ε͹ଟ͍΄ͲΑ͘ͳ͍ͬͯʮϚΠΫϩαʔϏεΞʔΩςΫνϟʯʹॻ͍ͯͨ

    ͱ͸͍͑ɺ͜ͷࣾ಺ϥΠϒϥϦ͕ղܾ͢Δͷ͸ϏδωεϩδοΫͱ͸શવผͷϨΠϠͰ͋Γɺ

    ΧελϚΠζཉٻ΋ߴ͘ͳ͍ՕॴͳͷͰΪϦΪϦηʔϑʢʁʣ
    ͱ͸͍͑։ൃऀͷखݩͷ؀ڥͰมͳಈ͖Λ͠ͳ͍Α͏ɺࡉ৺ͷ஫ҙΛ෷͍࣮ͭͭ૷
    উखʹ͍Ζ͍Ζ΍Δ

    View full-size slide

  39. lڷʹೖͬͯ͸ڷʹै͑z
    ͪΌΜͱ(Pͷ׳शʹԊͬͨ"1*ʹ͠Α͏
    ֦ுੑΛอͭ
    ೉͍͜͠ͱΛ΍Ζ͏ͱͨ͠ͱ͖ʹࠔΒͳ͍Α͏ʹ͠Α͏
    ࠷ѱίΞʹͳͬͯΔύοέʔδʹ΋৮ΕΒΕΔΑ͏ʹ͠Α͏
    ίʔυੜ੒Λ͏·͘࢖͏͜ͱΛݕ౼͠Α͏
    ࢖͍͗͢ɾ࢖͍ॴʹ͸ཁ஫ҙʂ
    Ϣʔβ͕ݶΒΕΔɺดͨ͡ίϯςΩετɺϏδωεϩδοΫ͕བྷ·ͳ͍ͳΒɺ

    উखʹ৭ʑ΍Δύοέʔδ΋͋Γ͔΋ʂʁ

    View full-size slide