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

Consider pluggable CLI tool implementation #gocon

Consider pluggable CLI tool implementation #gocon

Masayuki Izumi

November 25, 2018
Tweet

More Decks by Masayuki Izumi

Other Decks in Programming

Transcript

  1. 1
    Consider pluggable CLI tool implementation
    Go Conference 2018 Autumn - @izumin5210

    View Slide

  2. 2

    View Slide

  3. 3
    grapi1SFTFOUFEPO(P$PO4QpOH
    https://speakerdeck.com/izumin5210/grapi-bulding-json-api-server-with-grpc-gateway-for-microservices

    View Slide

  4. grapi(Pͷ"1*TFSWFS։ൃମݧΛߴΊΔ$-*QBDLBHF
    4
    * https://github.com/izumin5210/grapi
    ** https://cloud.google.com/apis/design/

    View Slide

  5. grapi(Pͷ"1*TFSWFS։ൃମݧΛߴΊΔ$-*QBDLBHF
    4
    * https://github.com/izumin5210/grapi
    ** https://cloud.google.com/apis/design/

    View Slide

  6. ‣ ϘΠϥϓϨʔτͷTDBGGPMEJOH
    ن໿ʹଇͬͨΠϯλϑΣʔεʢQSPUPʣͱ࣮૷ʢHPʣͷςϯϓϨʔτੜ੒
    (PPHMF"1*%FTJHO(VJEFʹԊͬͨ΋ͷ͕ੜ੒͞ΕΔ
    grapi(Pͷ"1*TFSWFS։ൃମݧΛߴΊΔ$-*QBDLBHF
    4
    * https://github.com/izumin5210/grapi
    ** https://cloud.google.com/apis/design/

    View Slide

  7. ‣ ϘΠϥϓϨʔτͷTDBGGPMEJOH
    ن໿ʹଇͬͨΠϯλϑΣʔεʢQSPUPʣͱ࣮૷ʢHPʣͷςϯϓϨʔτੜ੒
    (PPHMF"1*%FTJHO(VJEFʹԊͬͨ΋ͷ͕ੜ੒͞ΕΔ
    ‣ H31$*%-DPEFHFOFSBUJPO
    SPVUJOH VO
    NBSTIBMJOHͳͲؾʹ͠ͳͯ͘ྑ͍ɼ

    ຊ࣭ʹϑΥʔΧε͠΍͍͢։ൃମݧΛఏڙ
    HSQDHBUFXBZΛ಺แ͠ɼ;ͭ͏ͷ+40/"1*TFSWFSͱͯ͠ར༻Մ
    ΋ͪΖΜɼHBUFXBZͳ͠ͷH31$TFSWFSʹ΋Ͱ͖Δ
    grapi(Pͷ"1*TFSWFS։ൃମݧΛߴΊΔ$-*QBDLBHF
    4
    * https://github.com/izumin5210/grapi
    ** https://cloud.google.com/apis/design/

    View Slide

  8. ‣ ϘΠϥϓϨʔτͷTDBGGPMEJOH
    ن໿ʹଇͬͨΠϯλϑΣʔεʢQSPUPʣͱ࣮૷ʢHPʣͷςϯϓϨʔτੜ੒
    (PPHMF"1*%FTJHO(VJEFʹԊͬͨ΋ͷ͕ੜ੒͞ΕΔ
    ‣ H31$*%-DPEFHFOFSBUJPO
    SPVUJOH VO
    NBSTIBMJOHͳͲؾʹ͠ͳͯ͘ྑ͍ɼ

    ຊ࣭ʹϑΥʔΧε͠΍͍͢։ൃମݧΛఏڙ
    HSQDHBUFXBZΛ಺แ͠ɼ;ͭ͏ͷ+40/"1*TFSWFSͱͯ͠ར༻Մ
    ΋ͪΖΜɼHBUFXBZͳ͠ͷH31$TFSWFSʹ΋Ͱ͖Δ
    grapi(Pͷ"1*TFSWFS։ൃମݧΛߴΊΔ$-*QBDLBHF
    5
    HSBQJʹ৽ͨͳDPEFHFOFSBUPSΛೖΕͨ͘ͳͬͨ
    ͔ͦ͠͠Ε͸QSPUPCVG͸͔ͭ͏͚ͲH31$͸ͪΐͬͱؔ܎ͳ͍
    ୯७ͳ8FCαʔό։ൃʹؔΘͬͯ͘Δ࿩Ͱ΋ͳ͍
    HSBQJ͸PQFOͳ෺ͳͷͰɼ͋·Γϔϯͳ΋ͷ͸ೖΕͨ͘ͳ͍ʜ

    View Slide

  9. ‣ ϘΠϥϓϨʔτͷTDBGGPMEJOH
    ن໿ʹଇͬͨΠϯλϑΣʔεʢQSPUPʣͱ࣮૷ʢHPʣͷςϯϓϨʔτੜ੒
    (PPHMF"1*%FTJHO(VJEFʹԊͬͨ΋ͷ͕ੜ੒͞ΕΔ
    ‣ H31$*%-DPEFHFOFSBUJPO
    SPVUJOH VO
    NBSTIBMJOHͳͲؾʹ͠ͳͯ͘ྑ͍ɼ

    ຊ࣭ʹϑΥʔΧε͠΍͍͢։ൃମݧΛఏڙ
    HSQDHBUFXBZΛ಺แ͠ɼ;ͭ͏ͷ+40/"1*TFSWFSͱͯ͠ར༻Մ
    ΋ͪΖΜɼHBUFXBZͳ͠ͷH31$TFSWFSʹ΋Ͱ͖Δ
    grapi(Pͷ"1*TFSWFS։ൃମݧΛߴΊΔ$-*QBDLBHF
    5
    HSBQJʹ৽ͨͳDPEFHFOFSBUPSΛೖΕͨ͘ͳͬͨ
    ͔ͦ͠͠Ε͸QSPUPCVG͸͔ͭ͏͚ͲH31$͸ͪΐͬͱؔ܎ͳ͍
    ୯७ͳ8FCαʔό։ൃʹؔΘͬͯ͘Δ࿩Ͱ΋ͳ͍
    HSBQJ͸PQFOͳ෺ͳͷͰɼ͋·Γϔϯͳ΋ͷ͸ೖΕͨ͘ͳ͍ʜ

    View Slide

  10. 6
    Consider pluggable CLI tool implementation
    Go Conference 2018 Autumn - @izumin5210

    View Slide

  11. 7
    @izumin5210
    Application Engineer, Wantedly People
    Wantedly, Inc.

    View Slide

  12. 2ͳͥQMVHJOػߏ͕ඞཁͳͷ͔

    View Slide

  13. 2ͳͥQMVHJOػߏ͕ඞཁͳͷ͔
    "։ൃऀɾར༻ऀͲͪΒ΋)BQQZʹ͢ΔͨΊ

    View Slide

  14. 9
    Users
    ͓٬༷ͷ੠ʢྫʣ
    ʢ1PTUHSF42-༻πʔϧʹରͯ͠ʣ.Z42-ʹ΋ରԠͯ͠΄͍͠ʂ
    ʢίʔυδΣωϨʔλʹରͯ͠ʣUFTUJGZͰςετ΋ੜ੒ͯ͠ʂ
    Author

    View Slide

  15. 9
    Users
    ͓٬༷ͷ੠ʢྫʣ
    ʢ1PTUHSF42-༻πʔϧʹରͯ͠ʣ.Z42-ʹ΋ରԠͯ͠΄͍͠ʂ
    ʢίʔυδΣωϨʔλʹରͯ͠ʣUFTUJGZͰςετ΋ੜ੒ͯ͠ʂ
    Author
    Զͷ੠ʢྫʣ
    ʢ1PTUHSF42-͔͠࢖Θ΁Μ͠ʜʣ
    ʢAUFTUJOHA೿ͳͷͰϞνϕʔγϣϯ͕ͳ͍ʜʣ

    View Slide

  16. Users
    ͓٬༷ͷ੠ʢྫʣ
    ʢ1PTUHSF42-༻πʔϧʹରͯ͠ʣ.Z42-ʹ΋ରԠͯ͠΄͍͠ʂ
    ʢίʔυδΣωϨʔλʹରͯ͠ʣUFTUJGZͰςετ΋ੜ੒ͯ͠ʂ
    10
    Author
    ͔ͤͬ͘ݟ͚ͭͯ΋Β͑ͨͷʹʜ
    .Z42-࢖ͬͨ͜ͱͳ͍͔Β೉͍͠ʜ
    ͜͜ͰରԠͪ͠Ό͏ͱɼ΄͔ͷ%#.4΋

    αϙʔτཁٻ͕དྷͪΌ͏͔΋ʜ
    044CVSOPVUͦ͠͏
    6OIBQQZ
    Զͷ੠ʢྫʣ
    ʢ1PTUHSF42-͔͠࢖Θ΁Μ͠ʜʣ
    ʢAUFTUJOHA೿ͳͷͰϞνϕʔγϣϯ͕ͳ͍ʜʣ

    View Slide

  17. Author
    Զͷ੠ʢྫʣ
    ʢ1PTUHSF42-͔͠࢖Θ΁Μ͠ʜʣ
    ʢAUFTUJOHA೿ͳͷͰϞνϕʔγϣϯ͕ͳ͍ʜʣ
    11
    Users
    ͔ͤͬ͘ྑ͍πʔϧʹग़ձͬͨͷʹʜ
    ࣗ෼͕ରԠ࡞ۀͯ͠΋͍͍͚Ͳɼ

    ࡞ऀ͕.Z42-࢖Θͳ͍ͳΒෆ҆ఆ͔΋ʜ
    ͔ͩΒͱ͍ͬͯࣗ෼͕ϝϯς͠ଓ͚Δͷ͔ʜʁ
    6OIBQQZ
    ͓٬༷ͷ੠ʢྫʣ
    ʢ1PTUHSF42-༻πʔϧʹରͯ͠ʣ.Z42-ʹ΋ରԠͯ͠΄͍͠ʂ
    ʢίʔυδΣωϨʔλʹରͯ͠ʣUFTUJGZͰςετ΋ੜ੒ͯ͠ʂ

    View Slide

  18. Author
    Զͷ੠ʢྫʣ
    ʢ1PTUHSF42-͔͠࢖Θ΁Μ͠ʜʣ
    ʢAUFTUJOHA೿ͳͷͰϞνϕʔγϣϯ͕ͳ͍ʜʣ
    12
    Users
    ͓٬༷ͷ੠ʢྫʣ
    ʢ1PTUHSF42-༻πʔϧʹରͯ͠ʣ.Z42-ʹ΋ରԠͯ͠΄͍͠ʂ
    ʢίʔυδΣωϨʔλʹରͯ͠ʣUFTUJGZͰςετ΋ੜ੒ͯ͠ʂ
    Ϣʔβ͕֎͔Β֦ுͰ͖ΔΑ͏ʹ͢Δ
    ֎ʹ੾Γग़͞Εͨ%#.4ͷESJWFSΛݺͿ
    ίʔυੜ੒ͷલޙͰ֎෦ϓϩάϥϜΛݺͿ

    View Slide

  19. 13
    ຊମΞϓϦ͸ͳΔ΂͘ബ͘อͪͭͭ
    ֎͔ΒॲཧΛࠩ͠ࠐΊΔιέοτ͕͋Ε͹ྑ͍
    Extensibility֦ுੑͷߴ͍πʔϧ

    View Slide

  20. 13
    ຊମΞϓϦ͸ͳΔ΂͘ബ͘อͪͭͭ
    ֎͔ΒॲཧΛࠩ͠ࠐΊΔιέοτ͕͋Ε͹ྑ͍
    Extensibility֦ுੑͷߴ͍πʔϧ
    ‣ ϢʔβͷΞϓϦέʔγϣϯίʔυ͔Βར༻
    FH42-%SJWFS Adatabase/sql/driver.DriverA
    .JEEMFXBSF Ahttp.HandlerA
    *OUFSDFQUPS Agrpc.*InterceptorA

    View Slide

  21. 13
    ຊମΞϓϦ͸ͳΔ΂͘ബ͘อͪͭͭ
    ֎͔ΒॲཧΛࠩ͠ࠐΊΔιέοτ͕͋Ε͹ྑ͍
    Extensibility֦ுੑͷߴ͍πʔϧ
    ‣ ϢʔβͷΞϓϦέʔγϣϯίʔυ͔Βར༻
    FH42-%SJWFS Adatabase/sql/driver.DriverA
    .JEEMFXBSF Ahttp.HandlerA
    *OUFSDFQUPS Agrpc.*InterceptorA

    ‣ طଘΞϓϦέʔγϣϯʹػೳΛ֎෇͚ͯ͠ར༻
    FH1SPUPDQMVHJOT 5FSSBGPSNQSPWJEFST 3BJMTHFOFSBUPST
    ࠓ೔͸ͬͪ͜ͷ࿩

    View Slide

  22. 14
    Plugin ExperienceϓϥάΠϯͱ%FWFMPQFS&YQFSJFODF
    Users Plugin Developers

    View Slide

  23. 15
    Plugin ExperienceϓϥάΠϯͱ%FWFMPQFS&YQFSJFODF
    ‣ ϢʔβͷυϝΠϯͱπʔϧͷSFRVJSFNFOU͕ζϨͳ͍͔
    FHʮΠϯϑϥπʔϧͷϓϥάΠϯػߏʹ/PEFKT͕ඞཁʯ͸ଟ෼͠ΜͲ͍
    Users Plugin Developers

    View Slide

  24. 15
    Plugin ExperienceϓϥάΠϯͱ%FWFMPQFS&YQFSJFODF
    ‣ ϢʔβͷυϝΠϯͱπʔϧͷSFRVJSFNFOU͕ζϨͳ͍͔
    FHʮΠϯϑϥπʔϧͷϓϥάΠϯػߏʹ/PEFKT͕ඞཁʯ͸ଟ෼͠ΜͲ͍
    ‣ ෳ਺ਓϓϩδΣΫτͰ΋ར༻Ͱ͖Δ͔
    ʮਓʹΑͬͯ࢖ͬͯΔόʔδϣϯ͕ҟͳΔʯ͸஍ࠈ
    Ϛγϯάϩʔόϧ͡Όͳͯ͘ϓϩδΣΫτϩʔΧϧʹΠϯετʔϧग़དྷͳ͍ͱμϝ
    Users Plugin Developers

    View Slide

  25. 16
    Plugin ExperienceϓϥάΠϯͱ%FWFMPQFS&YQFSJFODF
    Users Plugin Developers
    ‣ ࣮૷ɾσόοά͸؆୯͔
    ݩπʔϧͷίϯςΩετΛࢀরɾར༻Ͱ͖Δ͔
    ϩʔΧϧͰ࡞੒தͷ΋ͷΛ࣮πʔϧ͔Βར༻Ͱ͖Δ͔
    ࣮૷ͷͨΊͷϨʔϧ͸͋Δ͔

    View Slide

  26. 16
    Plugin ExperienceϓϥάΠϯͱ%FWFMPQFS&YQFSJFODF
    Users Plugin Developers
    ‣ ࣮૷ɾσόοά͸؆୯͔
    ݩπʔϧͷίϯςΩετΛࢀরɾར༻Ͱ͖Δ͔
    ϩʔΧϧͰ࡞੒தͷ΋ͷΛ࣮πʔϧ͔Βར༻Ͱ͖Δ͔
    ࣮૷ͷͨΊͷϨʔϧ͸͋Δ͔
    ‣ ഑෍͸؆୯͔
    FH0SHBOJ[BUJPOJOUFSOBMͳπʔϧΛOQNʹެ։͢Δͷ͸͠ΜͲ͍

    View Slide

  27. Pluggable tools: Real-World Examples

    View Slide

  28. ‣ ମݧͷྑ͍πʔϧ
    3BJMT(FOFSBUPST
    DSFBUFSFBDUBQQ
    ‣ (PͰ࣮૷͠΍ͦ͢͏ͳπʔϧ
    QSPUPD
    5FSSBGPSN
    LVCFDUM
    42-CPJMEFS
    Pluggable tools3FBM8PSME&YBNQMFT
    18

    View Slide

  29. ‣ ࣮૷͕࠷ߴʹΧϯλϯ
    A3BJMT(FOFSBUPST#BTFAͷαϒΫϥεΛ࡞Δ
    ͜ͷαϒΫϥε໊͕·Μ·αϒίϚϯυʹʢԼͷίʔυͳΒASBJMTHJOJUJBMJ[FSAʹͳΔʣ
    ࣮ߦ͢ΔͱQVCMJDNFUIPET͕ఆٛ͞Εͨॱʹݺͼग़͞Ε͍ͯ͘
    Ralis Generators*%4-Ͱهड़(FNpMFʹॻ͍ͯಋೖ
    19
    * https://guides.rubyonrails.org/generators.html#creating-your-first-generator
    class InitializerGenerator < Rails::Generators::Base
    def create_initializer_file
    create_file "config/initializers/initializer.rb",
    "# Add initialization content here"
    end
    end

    View Slide

  30. ‣ ࣮૷͕࠷ߴʹΧϯλϯ
    A3BJMT(FOFSBUPST#BTFAͷαϒΫϥεΛ࡞Δ
    ͜ͷαϒΫϥε໊͕·Μ·αϒίϚϯυʹʢԼͷίʔυͳΒASBJMTHJOJUJBMJ[FSAʹͳΔʣ
    ࣮ߦ͢ΔͱQVCMJDNFUIPET͕ఆٛ͞Εͨॱʹݺͼग़͞Ε͍ͯ͘
    ‣ ಋೖ΋ָ
    3BJMTBQQMJDBUJPOىಈ࣌ʹಡ·ΕΕ͹0,
    HFNԽͯ͠(JU)VCʹஔ͚͹ɼ(FNpMFʹॻ͚ͩ͘
    Ralis Generators*%4-Ͱهड़(FNpMFʹॻ͍ͯಋೖ
    19
    * https://guides.rubyonrails.org/generators.html#creating-your-first-generator
    class InitializerGenerator < Rails::Generators::Base
    def create_initializer_file
    create_file "config/initializers/initializer.rb",
    "# Add initialization content here"
    end
    end

    View Slide

  31. ‣ ར༻͢ΔUFNQMBUFͷࠩ͠ସ͕͑؆୯
    FH`npx create-react-app my-app --scripts-version=react-scripts-ts` Ͱ5ZQF4DSJQUʹͳΔ
    A--script-versionA͸࠷ऴతʹAyarn addAʹ౤͛ࠐ·ΕΔͷͰɼ೚ҙͷOQNNPEVMFͷ໊લΛೖΕ͍͍ͯ
    create-react-app*͍·Ͳ͖ͷ+4FS͸webpack.config.jsॻ͔ͳ͍ΜͰ͢Αʁ<ཁग़య>
    20
    * https://github.com/facebook/create-react-app

    View Slide

  32. ‣ ར༻͢ΔUFNQMBUFͷࠩ͠ସ͕͑؆୯
    FH`npx create-react-app my-app --scripts-version=react-scripts-ts` Ͱ5ZQF4DSJQUʹͳΔ
    A--script-versionA͸࠷ऴతʹAyarn addAʹ౤͛ࠐ·ΕΔͷͰɼ೚ҙͷOQNNPEVMFͷ໊લΛೖΕ͍͍ͯ
    ‣ ಠࣗͷUFNQMBUF΋؆୯ʹ࡞ΕΔ
    ಺෦తʹ͸ASFBDUTDSJQUTAͷAscripts/init.jsAΛ࣮ߦͯ͠Δ͚ͩ
    ͳͷͰɼʮΦϦδφϧͷASFBDUTDSJQUTAͷJOJUΛ࣮ߦͨ͋͠ͱʹಠࣗͷॲཧΛՃ͑ΔʯΑ͏ͳ

    Areact-scripts-AΛ࡞Ε͹ྑ͍
    AZBSOBEEA͕ղऍͰ͖Ε͹͍͍ͷͰɼͱΓ͋͑ͣ(JU)VCʹஔ͍ͱ͚͹ΈΜͳ࢖͑ΔʢͨͿΜʣ
    create-react-app*͍·Ͳ͖ͷ+4FS͸webpack.config.jsॻ͔ͳ͍ΜͰ͢Αʁ<ཁग़య>
    20
    * https://github.com/facebook/create-react-app

    View Slide

  33. create-react-app*͍·Ͳ͖ͷ+4FS͸webpack.config.jsॻ͔ͳ͍ΜͰ͢Αʁ<ཁग़య>
    21
    https://github.com/facebook/create-react-app/blob/v2.1.1/packages/create-react-app/createReactApp.js#L305-L313
    if (useYarn) {
    command = 'yarnpkg';
    args = ['add', '--exact'];
    // snip.
    [].push.apply(args, dependencies);
    args.push('--cwd');
    args.push(root);
    // snip.
    } else {
    // snip.
    }
    if (verbose) {
    args.push('--verbose');
    }
    const child = spawn(command, args, { stdio: 'inherit' });
    child.on('close', code => {
    // snip.
    });

    View Slide

  34. ‣ ࣮૷͸TJNQMF FBTZ

    Aprotoc --foo_out=DIR input.protoAͰAprotoc-gen-fooAͱ͍͏࣮ߦϑΝΠϧΛݺͼग़͢
    Ҿ਺ͷ౉͠ํ͸ಠಛ
    ର৅ʹͳͬͯΔQSPUPͳͲ͸TUEJO͔Β
    ͦͷଞͷҾ਺͸A--foo_out=:DIRAͱ͢Ε͹ʢͨͩͷจࣈྻͱͯ͠ʣ౉ͬͯ͘Δ
    protoc*࣮ߦ࣌ͷҾ਺೉֮ͯ͑͘͠ΒΕ΁Μ
    22
    * https://developers.google.com/protocol-buffers/docs/reference/go-generated

    View Slide

  35. ‣ ࣮૷͸TJNQMF FBTZ

    Aprotoc --foo_out=DIR input.protoAͰAprotoc-gen-fooAͱ͍͏࣮ߦϑΝΠϧΛݺͼग़͢
    Ҿ਺ͷ౉͠ํ͸ಠಛ
    ର৅ʹͳͬͯΔQSPUPͳͲ͸TUEJO͔Β
    ͦͷଞͷҾ਺͸A--foo_out=:DIRAͱ͢Ε͹ʢͨͩͷจࣈྻͱͯ͠ʣ౉ͬͯ͘Δ
    ‣ ಋೖ͸ʜ
    FYFDVUBCMF͔ͭA1"5)Aʹଘࡏ͢ΔPSA--plugin=path/to/protoc-gen-fooAΛ౉ͤ͹ྑ͍
    ͲͷݴޠͷίϯςΩετԽʹΑͬͯಋೖ೉қ౓͕มΘΓͦ͏
    protoc*࣮ߦ࣌ͷҾ਺೉֮ͯ͑͘͠ΒΕ΁Μ
    22
    * https://developers.google.com/protocol-buffers/docs/reference/go-generated

    View Slide

  36. ‣ ࣮૷͸&BTZ
    ϨʔϧΛෑ͍ͯ͘Ε͍ͯΔʢAgithub.com/hashicorp/terraform/pluginAʣ
    ಺෦͸Agithub.com/hashicorp/go-pluginA
    ϓϥάΠϯ͕αʔόɾຊମ͕ΫϥΠΞϯτʹͳͬͯnet/rpcͰ௨৴
    Terraform*͔͜͜ΒΑ͏΍͘(Pͷ࿩Ͱ͢
    23
    * https://www.terraform.io/docs/plugins/basics.html

    View Slide

  37. ‣ ࣮૷͸&BTZ
    ϨʔϧΛෑ͍ͯ͘Ε͍ͯΔʢAgithub.com/hashicorp/terraform/pluginAʣ
    ಺෦͸Agithub.com/hashicorp/go-pluginA
    ϓϥάΠϯ͕αʔόɾຊମ͕ΫϥΠΞϯτʹͳͬͯnet/rpcͰ௨৴
    ‣ ಋೖ͸·͋·͋
    ಛఆͷσΟϨΫτϦʹFYFDVUBCMFͳ΋ͷΛஔ͚ͩ͘
    ஔ͖৔͸ϚγϯશମͰͭͳͷͰɼϓϩδΣΫτ͝ͱʹόʔδϣϯม͑Δͱ͔͸Ͱ͖ͳ͍
    ʢ5FSSBGPSNͷ৔߹͸ͦΜͳधཁ΋ͳ͍ͷ͔΋ʣ
    Terraform*͔͜͜ΒΑ͏΍͘(Pͷ࿩Ͱ͢
    23
    * https://www.terraform.io/docs/plugins/basics.html

    View Slide

  38. ‣ ࣮૷͸TJNQMF FBTZ

    Akubectl foo barAͰAkubectl-foo-barAΛ୳࣮ͯ͠ߦ͢Δ
    ίϯςΩετͱ͔͸؀ڥม਺ʹೖ͍ͬͯΔ
    (PͰ͋Ε͹ศརύοέʔδ͕ଘࡏ͢ΔʢAk8s.io/kubectl/pkg/pluginutilsA Ak8s.io/cli-runtimeAʣ
    kubectl*ਅͬ౰Ͱࣗવͳ΍Γํ
    24
    * https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/
    https://github.com/kubernetes/kubernetes/blob/v1.12.2/pkg/kubectl/cmd/cmd.go

    View Slide

  39. ‣ ࣮૷͸TJNQMF FBTZ

    Akubectl foo barAͰAkubectl-foo-barAΛ୳࣮ͯ͠ߦ͢Δ
    ίϯςΩετͱ͔͸؀ڥม਺ʹೖ͍ͬͯΔ
    (PͰ͋Ε͹ศརύοέʔδ͕ଘࡏ͢ΔʢAk8s.io/kubectl/pkg/pluginutilsA Ak8s.io/cli-runtimeAʣ
    ‣ ಋೖ͸·͋·ָ͋ͦ͏
    &YFDVUBCMFͳϑΝΠϧஔ͚ͩ͘
    ͭ͘Δͱ͖͸มͳϥϯλΠϜʹґଘͤ͞ͳ͍΄͏͕਌੾
    kubectl*ਅͬ౰Ͱࣗવͳ΍Γํ
    24
    * https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/
    https://github.com/kubernetes/kubernetes/blob/v1.12.2/pkg/kubectl/cmd/cmd.go

    View Slide

  40. 25
    type defaultPluginHandler struct{}
    func (h *defaultPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error {
    return syscall.Exec(executablePath, cmdArgs, environment)
    }
    func handleEndpointExtensions(pluginHandler PluginHandler, cmdArgs []string) error {
    remainingArgs := []string{} // all "non-flag" arguments
    for idx := range cmdArgs {
    if strings.HasPrefix(cmdArgs[idx], "-") {
    break
    }
    remainingArgs = append(remainingArgs, strings.Replace(cmdArgs[idx], "-", "_", -1))
    }
    foundBinaryPath := ""
    for len(remainingArgs) > 0 {
    path, err := pluginHandler.Lookup(fmt.Sprintf("kubectl-%s", strings.Join(remainingArgs, "-")))
    // snip.
    foundBinaryPath = path
    break
    }
    // snip.
    if err := pluginHandler.Execute(foundBinaryPath, append([]string{foundBinaryPath}, cmdArgs[len(remainingArgs):]...), os.Environ()); err != nil {
    return err
    }
    return nil
    }
    ࣮૷͸ૉ๿
    https://github.com/kubernetes/kubernetes/blob/v1.12.2/pkg/kubectl/cmd/cmd.go

    View Slide

  41. SQLBoiler*๻͸͜Ε͕Ұ൪޷͖Ͱ͢
    26
    * https://github.com/volatiletech/sqlboiler
    users, err := models.Users(
    qm.Where("age > ?", 30),
    qm.Limit(5),
    qm.Offset(6),
    ).All(db, ctx)

    View Slide

  42. ‣ 8IBUJT42-#PJMFS
    ίʔυੜ੒ϕʔεͷ03.
    %#ͷεΩʔϚΛಡΜͰɼͦΕͬΆ͍TUSVDUͱ࣮૷Λੜ੒ͯ͘͠ΕΔ
    SQLBoiler*๻͸͜Ε͕Ұ൪޷͖Ͱ͢
    26
    * https://github.com/volatiletech/sqlboiler
    users, err := models.Users(
    qm.Where("age > ?", 30),
    qm.Limit(5),
    qm.Offset(6),
    ).All(db, ctx)

    View Slide

  43. ‣ 8IBUJT42-#PJMFS
    ίʔυੜ੒ϕʔεͷ03.
    %#ͷεΩʔϚΛಡΜͰɼͦΕͬΆ͍TUSVDUͱ࣮૷Λੜ੒ͯ͘͠ΕΔ
    ‣ Ͳ͕͜QMVHHBCMFʹͳͬͯΔʁ
    ཪͷ%#.4ʹͭͳ͍ͰɼεΩʔϚಡΜͰɼϑΝΠϧΛੜ੒͢Δ΍ͭ
    SQLBoiler*๻͸͜Ε͕Ұ൪޷͖Ͱ͢
    26
    * https://github.com/volatiletech/sqlboiler
    users, err := models.Users(
    qm.Where("age > ?", 30),
    qm.Limit(5),
    qm.Offset(6),
    ).All(db, ctx)

    View Slide

  44. ‣ ࣮૷͕&BTZ
    ATRMCPJMFSAͳFYFDVUBCMFͳϑΝΠϧΛಡΉ
    ATRMCPJMEFSESJWFST*OUFSGBDFAͷ࣮૷Λ

    AESJWFST%SJWFS.BJOAʹ౉͚ͩ͢
    SQLBoiler*๻͸͜Ε͕Ұ൪޷͖Ͱ͢
    27
    * https://github.com/volatiletech/sqlboiler
    func main() {
    drivers.DriverMain(&driver.PostgresDriver{})
    }
    // Interface abstracts either a side-effect imp
    // that is called in order to produce the data
    type Interface interface {
    // Assemble the database information into a n
    Assemble(config Config) (*DBInfo, error)
    // Templates to add/replace for generation
    Templates() (map[string]string, error)
    // Imports to merge for generation
    Imports() (importers.Collection, error)
    }

    View Slide

  45. ‣ ࣮૷͕&BTZ
    ATRMCPJMFSAͳFYFDVUBCMFͳϑΝΠϧΛಡΉ
    ATRMCPJMEFSESJWFST*OUFSGBDFAͷ࣮૷Λ

    AESJWFST%SJWFS.BJOAʹ౉͚ͩ͢
    ‣ ಋೖ͸ʁ
    A1"5)AʹFYFDVUBCMFͳϑΝΠϧΛ഑ஔ͢ΔγϦʔζ
    %SJWFS΋(PQBDLBHFͳͷͰɼͱΓ͋͑ͣAHPHFUAͰ͍͍
    όʔδϣϯݻఆ͸ʜ
    (Pʹ΋UPPMΛϓϩδΣΫτ͝ͱʹ؅ཧ͢Δखஈ͕΄͍͠Ͱ͢Ͷ
    SQLBoiler*๻͸͜Ε͕Ұ൪޷͖Ͱ͢
    27
    * https://github.com/volatiletech/sqlboiler
    func main() {
    drivers.DriverMain(&driver.PostgresDriver{})
    }
    // Interface abstracts either a side-effect imp
    // that is called in order to produce the data
    type Interface interface {
    // Assemble the database information into a n
    Assemble(config Config) (*DBInfo, error)
    // Templates to add/replace for generation
    Templates() (map[string]string, error)
    // Imports to merge for generation
    Imports() (importers.Collection, error)
    }

    View Slide

  46. ‣ ମݧͷྑ͍πʔϧ
    3BJMT(FOFSBUPST3VCZ΍͹͍
    DSFBUFSFBDUBQQͺͬͱݟ࣏҆ΘΔ͍͚Ͳɼ69ͱ֦ுੑͷόϥϯε͸ૉ੖Β͍͠
    ‣ (PͰ࣮૷͠΍ͦ͢͏ͳπʔϧ
    QSPUPD LVCFDUMΘ͔Γ΍͍͚͢ͲϓϥάΠϯͮ͘Γ͕໘౗
    5FSSBGPSNΞϓϩʔν͸͓΋͠Ζ͍͚ͲɼόʔδϣϯϩοΫ͕ඞཁͳ৔໘Ͱ೰·͍͠
    42-#PJMFS(PͰUPPM؅ཧ͢Δखஈ͑͋͞Ε͹ྑͦ͞͏ʁ
    Pluggable tools3FBM8PSME&YBNQMFT
    28

    View Slide

  47. Implements plugin-mechanism into grapi

    View Slide

  48. ‣ HSBQJͰٻΊ͍ͨཁ݅
    1MVHJO͕(PͰ͋Δඞཁੑ͸ͳ͍
    ͕ɼͳΜΒ͔ͷQBDLBHFNBOBHFSʹ৐͔ͬͬͨ΄͏͕࣮૷͸ָ
    HSBQJ͕࣋ͬͯΔQSPUPD࣮ߦػೳ͸ར༻͍ͨ͠
    ಋೖʹ಄Λ࢖Θͳ͍ͱ͍͚ͳ͍ͷ͸ආ͚͍ͨ
    όʔδϣϯϩοΫ͸ඞਢ
    Approach)PXUPJNQMFNFOUQMVHJONFDIBOJTJOUPHSBQJ
    30

    View Slide

  49. ‣ HSBQJͰٻΊ͍ͨཁ݅
    1MVHJO͕(PͰ͋Δඞཁੑ͸ͳ͍
    ͕ɼͳΜΒ͔ͷQBDLBHFNBOBHFSʹ৐͔ͬͬͨ΄͏͕࣮૷͸ָ
    HSBQJ͕࣋ͬͯΔQSPUPD࣮ߦػೳ͸ར༻͍ͨ͠
    ಋೖʹ಄Λ࢖Θͳ͍ͱ͍͚ͳ͍ͷ͸ආ͚͍ͨ
    όʔδϣϯϩοΫ͸ඞਢ
    Approach)PXUPJNQMFNFOUQMVHJONFDIBOJTJOUPHSBQJ
    31
    ݁ہ(PͰ΍Δલఏͩͱָͦ͏
    42-#PJMFSͱಉ͡Α͏ͳ

    )FMQFSΛ༻ҙͯ͋͛͠Δ

    View Slide

  50. ‣ HSBQJͰٻΊ͍ͨཁ݅
    1MVHJO͕(PͰ͋Δඞཁੑ͸ͳ͍
    ͕ɼͳΜΒ͔ͷQBDLBHFNBOBHFSʹ৐͔ͬͬͨ΄͏͕࣮૷͸ָ
    HSBQJ͕࣋ͬͯΔQSPUPD࣮ߦػೳ͸ར༻͍ͨ͠
    ಋೖʹ಄Λ࢖Θͳ͍ͱ͍͚ͳ͍ͷ͸ආ͚͍ͨ
    όʔδϣϯϩοΫ͸ඞਢ
    Approach)PXUPJNQMFNFOUQMVHJONFDIBOJTJOUPHSBQJ
    32
    πʔϧͷύοέʔδΛWFOEPSJOHͯ͠ɼ

    ͦΕΛ؅ཧɾϏϧυͰ͖Δ࢓૊Έ͕͋Δͱྑͦ͞͏

    View Slide

  51. 5PPMEFQFOEFODJFTΛ؅ཧɾ࣮ߦ͢Δπʔϧ
    github.com/izumin5210/gex5IFJNQMFNFOUBUJPOPGDMBSJGZCFTUQSBDUJDFGPSUPPMEFQFOEFODJFT
    33
    // Code generated by github.com/izumin5210/gex. DO NOT E
    // +build tools
    package tools
    // tool dependencies
    import (
    _ "github.com/haya14busa/reviewdog/cmd/reviewdog"
    _ "github.com/srvc/wraperr/cmd/wraperr"
    _ "golang.org/x/lint/golint"
    )

    View Slide

  52. 5PPMEFQFOEFODJFTΛ؅ཧɾ࣮ߦ͢Δπʔϧ
    ಺෦తʹ͸AEFQAAHPNPEA͍ͣΕ͔Λར༻͍ͯ͠Δ
    ͷͰɼͨͩ͘͠όʔδϣϯϩοΫ͞ΕΔ
    github.com/izumin5210/gex5IFJNQMFNFOUBUJPOPGDMBSJGZCFTUQSBDUJDFGPSUPPMEFQFOEFODJFT
    33
    // Code generated by github.com/izumin5210/gex. DO NOT E
    // +build tools
    package tools
    // tool dependencies
    import (
    _ "github.com/haya14busa/reviewdog/cmd/reviewdog"
    _ "github.com/srvc/wraperr/cmd/wraperr"
    _ "golang.org/x/lint/golint"
    )

    View Slide

  53. 5PPMEFQFOEFODJFTΛ؅ཧɾ࣮ߦ͢Δπʔϧ
    ಺෦తʹ͸AEFQAAHPNPEA͍ͣΕ͔Λར༻͍ͯ͠Δ
    ͷͰɼͨͩ͘͠όʔδϣϯϩοΫ͞ΕΔ
    AUPPMTHPAͱ͍͏ϑΝΠϧΛੜ੒ͯ͠JNQPSU͍ͯ͠Δ
    *NQPSUͯ͠ͳ͍ϑΝΠϧ͸؅ཧ͞Εͳ͍ͷͰ
    github.com/izumin5210/gex5IFJNQMFNFOUBUJPOPGDMBSJGZCFTUQSBDUJDFGPSUPPMEFQFOEFODJFT
    33
    // Code generated by github.com/izumin5210/gex. DO NOT E
    // +build tools
    package tools
    // tool dependencies
    import (
    _ "github.com/haya14busa/reviewdog/cmd/reviewdog"
    _ "github.com/srvc/wraperr/cmd/wraperr"
    _ "golang.org/x/lint/golint"
    )

    View Slide

  54. 5PPMEFQFOEFODJFTΛ؅ཧɾ࣮ߦ͢Δπʔϧ
    ಺෦తʹ͸AEFQAAHPNPEA͍ͣΕ͔Λར༻͍ͯ͠Δ
    ͷͰɼͨͩ͘͠όʔδϣϯϩοΫ͞ΕΔ
    AUPPMTHPAͱ͍͏ϑΝΠϧΛੜ੒ͯ͠JNQPSU͍ͯ͠Δ
    *NQPSUͯ͠ͳ͍ϑΝΠϧ͸؅ཧ͞Εͳ͍ͷͰ
    ΋ͱ΋ͱ͸AmockgenAͷόʔδϣϯΛݻఆͨͯ͘͠࡞ͬͨ
    github.com/izumin5210/gex5IFJNQMFNFOUBUJPOPGDMBSJGZCFTUQSBDUJDFGPSUPPMEFQFOEFODJFT
    33
    // Code generated by github.com/izumin5210/gex. DO NOT E
    // +build tools
    package tools
    // tool dependencies
    import (
    _ "github.com/haya14busa/reviewdog/cmd/reviewdog"
    _ "github.com/srvc/wraperr/cmd/wraperr"
    _ "golang.org/x/lint/golint"
    )

    View Slide

  55. 5PPMEFQFOEFODJFTΛ؅ཧɾ࣮ߦ͢Δπʔϧ
    ಺෦తʹ͸AEFQAAHPNPEA͍ͣΕ͔Λར༻͍ͯ͠Δ
    ͷͰɼͨͩ͘͠όʔδϣϯϩοΫ͞ΕΔ
    AUPPMTHPAͱ͍͏ϑΝΠϧΛੜ੒ͯ͠JNQPSU͍ͯ͠Δ
    *NQPSUͯ͠ͳ͍ϑΝΠϧ͸؅ཧ͞Εͳ͍ͷͰ
    ΋ͱ΋ͱ͸AmockgenAͷόʔδϣϯΛݻఆͨͯ͘͠࡞ͬͨ
    ʢ͙͢ެࣜʹ΋ೖΔͰ͠ΐʜͱ͍͏ࢥ͍͕͋ΔͷͰബ͘Ͱ͖͍ͯΔʣ
    github.com/izumin5210/gex5IFJNQMFNFOUBUJPOPGDMBSJGZCFTUQSBDUJDFGPSUPPMEFQFOEFODJFT
    33
    // Code generated by github.com/izumin5210/gex. DO NOT E
    // +build tools
    package tools
    // tool dependencies
    import (
    _ "github.com/haya14busa/reviewdog/cmd/reviewdog"
    _ "github.com/srvc/wraperr/cmd/wraperr"
    _ "golang.org/x/lint/golint"
    )

    View Slide

  56. ‣ HSBQJͰٻΊ͍ͨཁ݅
    1MVHJO͕(PͰ͋Δඞཁੑ͸ͳ͍
    ͕ɼͳΜΒ͔ͷQBDLBHFNBOBHFSʹ৐͔ͬͬͨ΄͏͕࣮૷͸ָ
    HSBQJ͕࣋ͬͯΔQSPUPD࣮ߦػೳ͸ར༻͍ͨ͠
    ಋೖʹ಄Λ࢖Θͳ͍ͱ͍͚ͳ͍ͷ͸ආ͚͍ͨ
    όʔδϣϯϩοΫ͸ඞਢ
    Approach)PXUPJNQMFNFOUQMVHJONFDIBOJTJOUPHSBQJ
    34
    πʔϧͷύοέʔδΛWFOEPSJOHͯ͠ɼ

    ͦΕΛ؅ཧɾϏϧυͰ͖Δ࢓૊Έ͕͋Δͱྑͦ͞͏
    ಺෦తʹHFYΛ͔ͭ͑͹Αͦ͞͏ʂ

    View Slide

  57. Plugin exampleHSQBJHFODPNNBOE
    35

    View Slide

  58. Plugin exampleHSQBJHFODPNNBOE
    35
    γϯϓϧͳྫ

    View Slide

  59. Plugin exampleHSQBJHFODPNNBOE
    35
    γϯϓϧͳྫ
    ੩తϑΝΠϧʢUFNQBMUFʣͷFNCFEEJOHʹ͸WGTHFOΛ࢖ͬͨ
    ಺෦తʹ͸AIUUQ'JMF4ZTUFNAʹͳΕ͹ԿͰ΋͍͍

    View Slide

  60. Plugin exampleHSQBJHFODPNNBOE
    35
    γϯϓϧͳྫ
    ੩తϑΝΠϧʢUFNQBMUFʣͷFNCFEEJOHʹ͸WGTHFOΛ࢖ͬͨ
    ಺෦తʹ͸AIUUQ'JMF4ZTUFNAʹͳΕ͹ԿͰ΋͍͍
    ੜ੒ίʔυΛTOBQTIPUUFTU͍ͯ͠Δ
    - github.com/bradleyjkemp/cupaloy

    View Slide

  61. Plugin exampleHSQBJHFOUZQF
    36

    View Slide

  62. Plugin exampleHSQBJHFOUZQF
    36
    ͪΐͬͱ͚ͩ΍΍͍͜͠ྫ
    %*ͱ͔͋Δͷ͸ɼHSBQJຊମͷQSPUPD8SBQQFSΛར༻͍ͨͨ͠Ί
    ຊମ͕಺෦ͰXJSFΛ࢖͍ͬͯΔͷͰɼͦΕʹ৐͔ͬͬͨ
    ͳͷͰผʹඞਢ͡Όͳ͍

    View Slide

  63. Plugin exampleHSQBJHFOUZQF
    37
    func main() {
    buildCommand(di.NewApp).MustExecute()
    }
    func buildCommand(createApp di.CreateAppFunc, opts ...gencmd.Option) gencmd.Exec
    {
    return gencmd.New(
    "type",
    newGenerateCommand(createApp),
    newDestroyCommand(),
    opts...,
    )
    }
    func newGenerateCommand(createApp di.CreateAppFunc) *gencmd.Command {
    var (
    app *di.App

    View Slide

  64. Plugin exampleHSQBJHFOUZQF
    38
    func newGenerateCommand(createApp di.CreateAppFunc) *gencmd.Command {
    var (
    app *di.App
    )
    return &gencmd.Command{
    Use: "generate NAME",
    Short: "Generate a new type",
    Args: cobra.ExactArgs(1),
    TemplateFS: template.FS,
    ShouldInsideApp: true,
    PreRun: func(c *gencmd.Command, args []string) error {
    var err error
    app, err = createApp(c)
    return errors.WithStack(err)
    },
    BuildParams: func(c *gencmd.Command, args []string) (interface{}, error) {
    return buildParams(args[0], c.Ctx().Ctx)
    },
    PostRun: func(c *gencmd.Command, args []string) error {
    return errors.WithStack(app.Protoc.Exec(context.TODO()))
    },
    }
    }

    View Slide

  65. Plugin exampleHSQBJHFOUZQF
    39
    func newGenerateCommand(createApp di.CreateAppFunc) *gencmd.Command {
    var (
    app *di.App
    )
    return &gencmd.Command{
    Use: "generate NAME",
    Short: "Generate a new type",
    Args: cobra.ExactArgs(1),
    TemplateFS: template.FS,
    ShouldInsideApp: true,
    PreRun: func(c *gencmd.Command, args []string) error {
    var err error
    app, err = createApp(c)
    return errors.WithStack(err)
    },
    BuildParams: func(c *gencmd.Command, args []string) (interface{}, error) {
    return buildParams(args[0], c.Ctx().Ctx)
    },
    PostRun: func(c *gencmd.Command, args []string) error {
    return errors.WithStack(app.Protoc.Exec(context.TODO()))
    },
    }
    }
    ґଘϞδϡʔϧͷॳظԽ
    ͜͜Ͱ͸ࣗલͷ%*DPOUBJOFSΛ

    ࢖͍ͬͯΔ͕ɼ;ͭ͏͸Կ΋͠ͳ͍

    View Slide

  66. Plugin exampleHSQBJHFOUZQF
    40
    func newGenerateCommand(createApp di.CreateAppFunc) *gencmd.Command {
    var (
    app *di.App
    )
    return &gencmd.Command{
    Use: "generate NAME",
    Short: "Generate a new type",
    Args: cobra.ExactArgs(1),
    TemplateFS: template.FS,
    ShouldInsideApp: true,
    PreRun: func(c *gencmd.Command, args []string) error {
    var err error
    app, err = createApp(c)
    return errors.WithStack(err)
    },
    BuildParams: func(c *gencmd.Command, args []string) (interface{}, error) {
    return buildParams(args[0], c.Ctx().Ctx)
    },
    PostRun: func(c *gencmd.Command, args []string) error {
    return errors.WithStack(app.Protoc.Exec(context.TODO()))
    },
    }
    }
    UFYUUFNQMBUFʹ౉Δύϥϝλ૊Έཱͯ
    ϑΝΠϧͷੜ੒ɾॏෳ൑ఆͳͲ͸

    উखʹ΍ͬͯ͘ΕΔ

    View Slide

  67. Plugin exampleHSQBJHFOUZQF
    41
    func newGenerateCommand(createApp di.CreateAppFunc) *gencmd.Command {
    var (
    app *di.App
    )
    return &gencmd.Command{
    Use: "generate NAME",
    Short: "Generate a new type",
    Args: cobra.ExactArgs(1),
    TemplateFS: template.FS,
    ShouldInsideApp: true,
    PreRun: func(c *gencmd.Command, args []string) error {
    var err error
    app, err = createApp(c)
    return errors.WithStack(err)
    },
    BuildParams: func(c *gencmd.Command, args []string) (interface{}, error) {
    return buildParams(args[0], c.Ctx().Ctx)
    },
    PostRun: func(c *gencmd.Command, args []string) error {
    return errors.WithStack(app.Protoc.Exec(context.TODO()))
    },
    }
    }
    ੜ੒ͷલޙʹ೚ҙͷॲཧΛڬΊΔ
    ͜͜Ͱ͸QSPUPDΛ͍͍ײ͡ʹ

    ࣮ߦ͍ͯ͠Δ

    View Slide

  68. ‣ ςετ༻ͷNPDLTUVC͸༻ҙͯ͋͛͠Δ
    ͓͠ΌΕඪ४ग़ྗͱ͔ɼࠓճͷ৔߹ͩͱQSPUPDͱ͔
    ‣ &YBNQMFΛ༻ҙͯ͋͛͠Δ
    ͜Ε͕Ұ൪ମݧྑ͘ͳΔؾ͕͢Δ
    HSBQJͷ৔߹͸HFOFSBUPS͕͢΂ͯQMVHJOʹͳ͍ͬͯΔͷͰɼ͜Ε͕ͦͷ··FYBNQMFʹͳΔ
    ͨͩυΩϡϝϯτ͕ͳ͍ͷͰʜ
    ‣ 1MVHJOHFOFSBUPSΛఏڙ͢Δ
    50%0ͭ͗ͷϦϦʔεʹؚΊΔ
    TipsGPSJNQSPWJOH%FWFMPQFS&YQFSJFODF
    42

    View Slide

  69. Go Conference 2018 Autumn - @izumin5210
    ConclusionΈΜͳ͕޾ͤʹͳΔπʔϧΛ࡞ΔͨΊʹ
    ‣ ϝΠϯ͡Όͳ͍ػೳ͸ɼQMVHJOͱͯ͠ఏڙͰ͖ΔΑ͏ʹ͢Δ
    044CVSOPVU๷ࢭ
    ܧଓతʹϝϯς͍ͯͨ͘͠Ίʹ΋ɼʮͻͱͭͷ͜ͱΛ͏·͘΍ΔʯΞʔΩςΫνϟ
    ‣ ࢖͍΍͍͢ɾ࡞Γ΍͍͢QMVHJOػߏ
    Ϣʔβͷ஌ࣝɾπʔϧͷίϯςΩετʹ͋ͬͨखஈͰఏڙͰ͖ΔΑ͏ʹ
    AHJUDMPOFA௚ޙͰ΋໰୊ͳ͘࢖͍࢝ΊΒΕΔΑ͏ʹ
    ϓϥάΠϯΛ࡞Δਓ΋CVSOPVU͠ͳ͍Α͏ʹ

    View Slide