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

Web API に秩序を与える Protocol Buffers / Protocol Buf...

Web API に秩序を与える Protocol Buffers / Protocol Buffers for Web API #builderscon

builderscon tokyo 2019 で「Web API に秩序を与える Protocol Buffers」というタイトルで発表した資料です。

Protocol Buffers を利用して Web API の Schema 管理をするという観点で、豊富な実例とともにその手法やメリット・デメリットについて話しました。

cf. https://builderscon.io

追記: 61ページ目で Protocol Buffers を利用する際の注意点として後方互換性が壊れるケースの話をしましたが、自分たちが経験したのは gRPC + grpc-gateway 構成特有のケースだったので記述を修正しました。

Avatar for Nao Minami

Nao Minami

August 30, 2019
Tweet

More Decks by Nao Minami

Other Decks in Technology

Transcript

  1. ©2019 Wantedly, Inc. /BP.JOBNJ *OGSBTUSVDUVSF&OHJOFFS 8BOUFEMZ (JU)VCTPVUI 5XJUUFSTPVUI  43&ͱͯ͠ͷ໾ׂ

     ։ൃج൫ͱͯ͠ͷ໾ׂ 1SPUPDPM#V⒎FSTΛར༻ͨ͠ ϚΠΫϩαʔϏε։ൃʹऔΓ૊Ή
  2. ©2019 Wantedly, Inc. (PPHMF੡ͷ4FSJBMJ[BUJPO-JCSBSZ  *OUFSGBDF%FpOJUJPO-BOHVBHF *%-  w *%-Ͱ"1*4DIFNBΛهड़͠ɺTFSJBMJ[BUJPOEFTFSJBMJ[BUJPOίʔυ

    Λࣗಈੜ੒ w *%-Ͱهड़ͨ͠಺༰ʢQSPUPpMFʣ͸ɺ"1*ʹର͢Δ੩తܕ෇͚ͱͳΔ w 1SPUPDPM#V⒎FSTίϯύΠϥʢQSPUPDʣ͸QMVHJOͰ༷ʑͳػೳ֦ு ͕Մೳ w TXBHHFSKTPOͷੜ੒ H31$ར༻ FUD w ༷ʑͳݴޠͰར༻Մೳ w $$ (P +BWB 1ZUIPO 3VCZ /PEF FUD 1SPUPDPM#VGGFSTͱ͸ʁ
  3. ©2019 Wantedly, Inc. service BookService { rpc getBook (getBookRequest) returns

    (Book) { option (google.api.http) = { get: "/api/books/{id}" }; } } message getBookRequest { int64 id = 1; } message Book { int64 id = 1; string title = 2; } #PPL"1*ͷ4DIFNBΛQSPUPGJMFʹهड़
  4. ©2019 Wantedly, Inc. QSPUPDͰίʔυࣗಈੜ੒ w ͦΕͧΕͷNFTTBHFΛTFSJBMJ[FEFTFSJBMJ[F͢ΔSVOUJNFMJCSBSZ ͷίʔυΛࣗಈੜ੒ ྫQSPUPGJMF͔Βίʔυࣗಈੜ੒ Google::Protobuf::DescriptorPool.xxx do

    add_message "books.GetBookRequest" do optional :id, :int64, 1 end add_message "books.Book" do optional :id, :int64, 1 optional :title, :string, 2 end end module Books GetBookRequest = XXX(“books.GetBookRequest”).msgclass Book = XXX(“books.Book").msgclass end message getBookRequest { int64 id = 1; } message Book { int64 id = 1; string title = 2; } QSPUPpMF 3VCZ
  5. ©2019 Wantedly, Inc. [1] book = Book.new(id: 2, title: "Hello,

    Book") => <Book: id: 2, title: "Hello, Book"> [2] book.to_proto => "\b\x02\x12\vHello, Book” [3] book.to_json => "{\"id\":2,\"title\":\"Hello, Book\”}" [4] Book.decode("\b\x02\x12\vHello, Book") => <Book: id: 2, title: "Hello, Book"> 3VCZ message Book { int64 id = 1; string title = 2; } QSPUPpMF
  6. ©2019 Wantedly, Inc. service BookService { rpc getBook (getBookRequest) returns

    (Book) { option (google.api.http) = { get: "/api/books/{id}" }; } } TXBHHFSQMVHJOͰ+40/Λࣗಈੜ੒ w HSQDHBUFXBZQSPUPDHFOTXBHHFSQMVHJOΛར༻ w ҎԼͷQSPUPpMFͷ಺༰Λ൓өͨ͠TXBHHFSKTPOΛੜ੒ ྫQSPUPGJMF͔ΒTXBHHFSKTPOΛࣗಈੜ੒
  7. ©2019 Wantedly, Inc. { "swagger": “2.0", …, "paths": { "/api/books/{id}":

    { "get": { "operationId": "GetBook", "responses": { "200": { "description": … "schema": { "$ref": "#/definitions/booksBook" } } }, “parameters": [ { "name": "id", "in": "path", "required": true, "type": "string", "format": "int64" } ], "tags": … } } }, "definitions": { "booksBook": { "type": "object", "properties": { "id": { "type": "string", "format": "int64" }, "title": { "type": "string" } } } } }
  8. ©2019 Wantedly, Inc. ͳͥ1SPUPDPM#VGGFSTΛར༻͢Δͷ͔ʁ  ࣗಈੜ੒ίʔυར༻ʹΑΔ"1* 4DIFNBͷอূ w QSPUPpMFΛਖ਼֬ʹ൓өͨ͠4FSJBMJ[FS%FTFSJBMJ[FSΛར༻͢ΔͷͰ ʮ࣮૷ͱ4DIFNBͷڍಈ͕ͣΕΔʯͱ͍͏ࣄ͕ݪཧతʹൃੜ͠ͳ͍

    w 4XBHHFSͷ༷ʹUFTU΍WBMJEBUJPOͰ୲อ͢ΔͷͰ͸ͳ͘ɺͨͩ QSPUPD͕ੜ੒͢ΔίʔυΛར༻͢Ε͹ྑ͍ ʮੜ͖ͯΔυΩϡϝϯτʯΛ؆୯͔࣮ͭ֬ʹ࡞Δ͜ͱ͕Ͱ͖Δ
  9. ©2019 Wantedly, Inc. ͳͥ1SPUPDPM#VGGFSTΛར༻͢Δͷ͔ʁ QSPUPDͷ๛෋ͳQMVHJO w QMVHJOʹΑ༷ͬͯʑͳػೳ֦ு͕Մೳ w FHTXBHHFSKTPOͷੜ੒ w

    FHQSPUPpMFͷ಺༰Λ+40/ܗࣜ΁ม׵ w FHH31$ HSQDHBUFXBZͷར༻ w ಛʹɺTXBHHFSKTPO͸ݩʑ8FC'SPOUFOE .PCJMF"QQνʔϜͱͷ ίϛϡχέʔγϣϯʹར༻͍ͯͨ͠ͷͰɺԸܙ͕େ͖͔ͬͨ w QSPUPDQMVHJOΤίγεςϜ͕࡞ΒΕ͖͍ͯͯΔ
  10. ©2019 Wantedly, Inc. HSBQJΛར༻ͯ͠ɺ1SPUPDPM#VGGFST ͷϝϦοτΛڗड w QSPUPpMFΛॻ͘ͱɺH31$ HSQDHBUFXBZͷίʔυΛࣗಈੜ੒ 4DIFNBΛݫີʹຬͨ͢"1*TFSWFSΛ؆୯ʹ࡞੒Մೳ w

    QSPUPDQMVHJOͰTXBHHFSKTPOΛੜ੒ 4XBHHFS6*ͳͲɺ4XBHHFSΤίγεςϜΛ׆͔ͤΔ w ৄࡉ͸ҎԼͷϦϯΫΛࢀর (PΛར༻ͨ͠ϚΠΫϩαʔϏε։ൃ IUUQTTQFBLFSEFDLDPNJ[VNJOHSBQJCVMEJOHKTPOBQJ TFSWFSXJUIHSQDHBUFXBZGPSNJDSPTFSWJDFT
  11. ©2019 Wantedly, Inc. > PATCH /api/books/2 HTTP/1.1 > Accept: application/protobuf

    > Content-Type: application/protobuf > > \x08\x02 < HTTP/1.1 200 OK < Content-Type: application/protobuf < < \x08\x02\x12\x0BHello, Book [1] book = Book.new(id: 2, title: "Hello, Book") => <Book: id: 2, title: "Hello, Book"> [2] book.to_proto => "\b\x02\x12\vHello, Book” 3VCZ )551SFRVFTUSFTQPOTF
  12. ©2019 Wantedly, Inc. Mime::Type.register "application/protobuf", :protobuf ActionController::Renderers.add :protobuf do |obj,

    _| send_data obj.to_proto, type: Mime[:protobuf] end 3VCZPO3BJMTJOJUJBMJ[FS def show req = GetBookRequest.decode(request.body.read) … book = Book.new(id: xxx, title: yyy) render protobuf: book end 3VCZPO3BJMTDPOUSPMMFS
  13. ©2019 Wantedly, Inc. (PPHMFͷ"1*%FTJHO(VJEFͷଘࡏ w (PPHMF$MPVE"1*TͳͲ(PPHMFͷެ։"1*͸QSPUPpMFͰهड़ ͞Ε͓ͯΓɺͦΕ͕४ڌ͢Δ"1*%FTJHO(VJEF΋ެ։͞Ε͍ͯΔɹ w DGIUUQTHJUIVCDPNHPPHMFBQJTHPPHMFBQJT w

    DGIUUQTDMPVEHPPHMFDPNBQJTEFTJHO w (PPHMF"1*T͸༏Ε࣮ͨྫͱͳ͍ͬͯΔ w "1*%FTJHO(VJEF͸"1*ઃܭͷࢀߟʹͰ͖Δ w ඪ४తͳܕͳͲ΋(PPHMFʹΑͬͯଟ਺ఆٛ͞Ε͍ͯͯར༻Ͱ͖Δ 1SPUPDPM#VGGFSTΛར༻ͯ͠ײͨ͡ϝϦοτ
  14. ©2019 Wantedly, Inc. 1SPUPDPM#VGGFSTΛར༻͢ΔதͰݟ͖͑ͯͨ஫ҙ఺ QSPUPpMFͷڞ༗ํ๏͸੔උ͕ඞཁ w 1SPUPDPM#V⒎FST͸$MJFOU 4FSWFS྆ํ͕ಉ͡QSPUPpMFΛࢀর͢Δ ඞཁ͕͋Δɻ 3FQPTJUPSZ͕෼͔Ε͍ͯΔ৔߹ɺQSPUPpMFͷڞ༗͕ඞཁ

    w ڞ༗ํ๏͝ͱͷϝϦοτɾσϝϦοτΛߟ͑Δඞཁ͕͋Δ w ୯७ͳίϐʔʜ࣮ݱ͕؆୯͕ͩɺQSPUPpMFͷมߋ௥ै͕೉͍͠ w QSPUPEFQ౳ͷπʔϧʜ$MJFOU͔ΒWFSTJPOࢦఆ͕Մೳ w QSPUPpMFू໿SFQPTJUPSZʜSFQPTJUPSZʹQSPUPpMFΛू໿ɻ ίʔυੜ੒ͯ͠MJCSBSZͱͯ͠഑৴ɻEFQFOEBCPUͰมߋΛ௨஌ɻ w 8BOUFEMZͰ͸ϓϩδΣΫτ͝ͱʹόϥόϥ͕ͩɺQSPUPpMFू໿ SFQPTJUPSZͷར༻͕૿Ճத
  15. ©2019 Wantedly, Inc. 1SPUPDPM#VGGFSTΛར༻͢ΔதͰݟ͖͑ͯͨ஫ҙ఺ 1SPUPDPM#VGGFSTίϯύΠϥ QSPUPD  ͷWFSTJPOʹ஫ҙ͢Δ w QSPUPDWFSTJPOͷҧ͍ʹΑͬͯɺࣗಈੜ੒ͨ͠ίʔυ಺༰͕มΘΔ

    w ޓ׵ੑ͸΄ͱΜͲյΕͳ͍͸͕ͣͩɺಛʹ(PͰݟͨ໨ͷEJ⒎͕ େ͖͘WFSTJPOΛ্͛ͮΒ͔ͬͨ w ։ൃऀͦΕͧΕ͕QSPUPDͰίʔυੜ੒͢Δ৔߹ɺQSPUPDWFSTJPO Λἧ͑Δඞཁ͋Γ VCFSQSPUPUPPMͳͲΛར༻ͯ͠WFSTJPOΛݻఆ͢Δͱྑ͍
  16. ©2019 Wantedly, Inc. 1SPUPDPM#VGGFSTΛར༻͢ΔதͰݟ͖͑ͯͨ஫ҙ఺ ݴޠʹΑͬͯ͸4FSJBMJ[BUJPO-JCSBSZ ͷػೳ͕ශऑ w +BWB4DSJQUpFMEͷTFU͸TFUUFSNFUIPEΛར༻͢Δඞཁ͕͋Δ w FHTFU*E

    TFU/BNF FUD w 3VCZSFQFBUFEpFME͸"SSBZMJLF͕ͩඍົʹ࢖͍উख͕ҧ͏ w ୯७ͳTFU͸ग़དྷͣɺSFQMBDF͕ඞཁ w "DUJWF3FDPSEͷXIFSFʹͦͷ··౉ͤͳ͍
  17. ©2019 Wantedly, Inc. [1] book = Book.new(id: 1) => <Book:

    id: 1, authors: []> [2] book.authors => [] [3] book.authors.class => Google::Protobuf::RepeatedField [4] book.authors = [Author.new(id: 2)] Google::Protobuf::TypeError: Expected repeated field array from (pry):4:in `method_missing’ [5] book.authors.replace([Author.new(id: 2)]) => [<Author: id: 2, name: "">] [6] book.authors => [<Author: id: 2, name: "">]
  18. ©2019 Wantedly, Inc. [1] req = ListBookRequest.new(ids: [1, 2, 3])

    => <ListBookRequest: ids: [1, 2, 3]> [2] req.ids.class => Google::Protobuf::RepeatedField [4] Book.where(id: req.ids).to_sql => "SELECT \"books\".* FROM \"books\" WHERE \"books\".\"id\" = NULL" [3] Book.where(id: req.ids.to_a).to_sql => "SELECT \"books\".* FROM \"books\" WHERE \"books\".\"id\" IN (1, 2, 3)"
  19. ©2019 Wantedly, Inc. ·ͱΊ 1SPUPDPM#VGGFSTΛ"1*ͷ4DIFNB ؅ཧʹར༻͢Δͱଟ͘ͷར఺͕͋Δ w QSPUPpMFͱͯ͠"1*ͷৼΔ෣͍Λએݴతʹهड़ w ࣗಈੜ੒ͨ͠ίʔυʹΑͬͯ4DIFNBͱ࣮૷ͷҰகΛอূ

    w ੩తಈతܕ෇͚ݴޠ྆ํ͔Βѻ͍΍͍͢ w TXBHHFSKTPOͷੜ੒ͳͲɺQSPUPDQMVHJOͷΤίγεςϜΛ׆༻Մೳ ϝϦοτΛڗड͠ੜ࢈ੑߴ͘։ൃ͠Α͏ʂ
  20. ©2019 Wantedly, Inc. "QQEFOEJYH31$ʹ͍ͭͯ H31$΋ݱࡏݕূத w LTDMVTUFS಺ͷϚΠΫϩαʔϏεʹରͯ͠FOWPZ͕TJEFDBSQSPYZ ͱͯ͠EFQMPZ͞ΕΔ༷ʹͳͬͨ )551MPBECBMBODJOH͕Մೳʹͳͬͨ w

    HSQDHBUFXBZΛ௨ͣ͞ʹ(PͷH31$αʔόʔͱ௨৴ͨ͠Γɺ৽͍͠ 3VCZαʔόʔΛH31$αʔόʔͱͯ͠։ൃ͢ΔࢼΈΛ͍ͯ͠Δ 1SPUPDPM#V⒎FSTͷԸܙΛड͚ͭͭɺUSBOTQPSU΋ޮ཰Խ