Slide 1

Slide 1 text

©2019 Wantedly, Inc. Case studies of
 designing developer friendly libraries Go Conference 2019 Spring May 18, 2019 - Masayuki Izumi(@izumin5210)

Slide 2

Slide 2 text

@izumin5210 Application Engineer, Wantedly People Wantedly, Inc.

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

©2019 Wantedly, Inc. `context.Context` ਖ਼͘͠࢖͓͏ context  ωοτϫʔΫ*0ͳͲɺ͔͔࣌ؒΔॲཧ͕͋Δͱ͖͸ඞͣDPOUFYUΛ౉͢ʂ  ωοτϫʔΫෆௐͰΊͬͪΌ͔͔࣌ؒΔ͔΋ˠλΠϜΞ΢τ͍ͨ͠  $POUFYUΛར༻ͯ͠ϝτϦΫεऩूͳͲΛߦ͏πʔϧ΁ͷରԠʢޙड़ʣ  HPPHMFDMPVEHPͳͲɺίϯετϥΫλͰDPOUFYUΛ
 ཁٻ͢Δύλʔϯ΋͋Δ client, err := pubsub.NewClient(ctx, "project-id") if err != nil { // ... } ڷʹै͏

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

©2019 Wantedly, Inc. ͋ΒΏΔ΋ͷΛࠩ͠ସ͑Մೳʹ͓ͯ͘͠  ͨͱ͑͹"1*ΫϥΠΞϯτͰ͋Ε͹ɺ
 HSQD$MJFOU$POO΍IUUQ$MJFOUʹΞΫηεՄೳͳύεΛ͓ͭͬͯ͘͘ͱศར  ϝτϦΫεऩू΍෼ࢄτϨʔγϯάͷεύϯੜ੒ͷϑοΫͳͲʹར༻Ͱ͖Δ  ίϯετϥΫλͳͲʹGVODUJPOBMPQUJPOͳͲͷܗࣜͰ౉ͤΔΑ͏ʹ͓ͯ͘͠ ֦ுੑΛอͭ

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

©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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

©2019 Wantedly, Inc. ͜Μͳʹউखͳ͜ͱͯ͠ྑ͍ͷ͔ʁ  ϏδωεϩδοΫʹؔΘΔΑ͏ͳίʔυͩͱμϝ  ڞ༗ίʔυ͸ଟ͚Ε͹ଟ͍΄ͲΑ͘ͳ͍ͬͯʮϚΠΫϩαʔϏεΞʔΩςΫνϟʯʹॻ͍ͯͨ
  ͱ͸͍͑ɺ͜ͷࣾ಺ϥΠϒϥϦ͕ղܾ͢Δͷ͸ϏδωεϩδοΫͱ͸શવผͷϨΠϠͰ͋Γɺ
 ΧελϚΠζཉٻ΋ߴ͘ͳ͍ՕॴͳͷͰΪϦΪϦηʔϑʢʁʣ  ͱ͸͍͑։ൃऀͷखݩͷ؀ڥͰมͳಈ͖Λ͠ͳ͍Α͏ɺࡉ৺ͷ஫ҙΛ෷͍࣮ͭͭ૷ উखʹ͍Ζ͍Ζ΍Δ

Slide 39

Slide 39 text

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