Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

MIXI TECH NOTE #12

MIXI TECH NOTE #12

#技術書典 17 に出典された、MIXI GROUP エンジニア有志による技術書です。

<< 目次 >>
1章:TIPSTAR における DWH 活用と開発チームとの連携
2章:Appium によるモバイルアプリ動作検証の自動化入門
3章:Cloud Spanner のしくみを理解する
4章:入門 Bzlmod
5章:バナー生成ツールでデザイナーを幸せにしたかった話
6章:運用入稿データを AI でチェックする仕組みを作った
7章:Obsidian プラグイン開発入門。簡易なベクトル検索を導入してみる
 
<< MIXI TECH NOTE バックナンバー >>
mixi tech note #01
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-01

mixi tech note #02
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-02

mixi tech note #03
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-03

mixi tech note #04
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-04

mixi tech note #05
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-05

mixi tech note #06
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-06

mixi tech note #07
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-07

MIXI TECH NOTE #08
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-08

MIXI TECH NOTE #09
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-09

MIXI TECH NOTE #10
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-10

MIXI TECH NOTE #11
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-11

XFLAG Tech Note Vol.01
https://speakerdeck.com/mixi_engineers/xflag-tech-note-vol-dot-01

XFLAG Tech Note vol.02
https://speakerdeck.com/mixi_engineers/xflag-tech-note-vol-dot-02

MIXI ENGINEERS

November 02, 2024
Tweet

More Decks by MIXI ENGINEERS

Other Decks in Technology

Transcript

  1. ·͕͖͑ ຊॻʮMIXI TECH NOTE #12ʯ͸ɺMIXI GROUP ʹॴଐ͢Δ༗ࢤୡʹΑͬͯࣥචɾ੍࡞͞Εͨ ٕज़ॻͰ͢ɻMIXI Ͱ͸༷ʑͳٕज़Λ࢖֤ͬͯαʔϏεΛ։ൃ͍ͯ͠·͢ɻຊॻͰ͸ɺMIXI ͷͦΕ

    ͧΕͷ։ൃݱ৔Ͱ׆ಈ͢ΔΤϯδχΞୡ͕ۀ຿Ͱಘͨ஌ݟ΍ϊ΢ϋ΢ɺ·ͨ͸ɺݸਓతʹௐ΂ͨ͜ͱ ͳͲΛࢥ͍ࢥ͍ʹ঺հ͍ͯ͠·͢ɻ֤ষͦΕͧΕͰٕज़ςʔϚ͕ҟͳΓɺ̍ষͰ׬͍݁ͯ͠Δ಺༰ʹ ͳ͍ͬͯ·͢ͷͰɺ޷͖ͳষ͔Β޷͖ͳॱ൪Ͱָ͓͠Έ͍ͩ͘͞ɻ ·ͨɺຊॻ͸ɺMIXI GROUP ʹ͋Δٕज़త஌ݟ΍ΞΠσΞΛੵۃతʹڞ༗ɾެ։͍ͯ͘͜͠ͱ ͰɺੈͷதʹΑΓྑ͍αʔϏε͕ҲΕग़͢͜ͱΛئͬͯץߦ͞Ε͍ͯ·͢ɻܝࡌ͞Ε͍ͯΔ৘ใ͸ɺ ࣥචऀࣗ਎ͷ؀ڥͰݕূࣥ͠ච͞Εͨ΋ͷͰ͢ͷͰɺ͝ࢀߟʹ͞ΕΔࡍ͸ɺࣗ͝਎ͷ੹೚Ͱ൑அ͠ ͝׆༻͍ͩ͘͞ɻͳ͓ɺจষදݱʹ͖ͭ·ͯ͠΋ɺࣥචऀࣗ਎ͷݴ༿Ͱ఻͑ͨ͘ɺϑϥϯΫͳදݱͱ ͳ͓ͬͯΓ·͢͜ͱ͝ཧղ͍͚ͨͩΕ͹ͱࢥ͍·͢ɻ MIXI DEVELOPERS ༗ࢤҰಉ ˗ຊॻʹؔ͢Δ͓໰͍߹Θͤઌ ɹ https://x.com/mixi_engineers ˗ MIXI GROUP ʹ͍ͭͯ ɹ https://mixi.co.jp/ ˞ MIXI ͷ໊শɺ͜Εʹؔ࿈͢Δ঎ඪٴͼϩΰ͸ɺגࣜձࣾ MIXI ͷ঎ඪٴͼొ࿥঎ඪͰ͢ɻ·ͨɺ ֤ࣾͷձ໊ࣾɺαʔϏεٴͼ੡඼ͷ໊শ͸ɺͦΕͧΕͷॴ༗͢Δ঎ඪ·ͨ͸ొ࿥঎ඪͰ͢ɻ iii
  2. ໨࣍ ·͕͖͑ iii ୈ 1 ষ TIPSTAR ʹ͓͚Δ DWH ׆༻ͱ։ൃνʔϜͱͷ࿈ܞ

    1 1.1 DWH Λ࡞ͬͨཧ༝ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 DWH ͷߏ੒ͷৄࡉ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.3 DHW ΛͲ͏׆༻͍ͯ͠Δ͔ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.4 DWH ΛͲ͏ҡ࣋͢Δ͔ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1.5 ͦͯ͠ࠓ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 11 2.1 Appium ͷΞʔΩςΫνϟ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.2 Android ΞϓϦͷͨΊͷ؀ڥߏங . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.4 iOS ΞϓϦͷͨΊͷ؀ڥߏங . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 2.5 iOS Ͱಈ࡞ݕূΛࣗಈԽ͢Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 2.6 ͍͞͝ʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 ୈ 3 ষ Cloud Spanner ͷ͘͠ΈΛཧղ͢Δ 51 3.1 ·͕͖͑ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 3.2 ຊষʹ͍ͭͯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 3.3 Cloud SpannerɾNewSQL ͱ͸ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 3.4 ਫฏεέʔϥϏϦςΟΛఏڙ͢ΔΞʔΩςΫνϟ . . . . . . . . . . . . . . . . . . . 52 3.5 ֎෦੔߹ੑΛࢧ͑Δ͘͠Έ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 3.6 ऴΘΓʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 3.7 ࢀߟ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 ୈ 4 ষ ೖ໳ Bzlmod 59 4.1 αϯϓϧϓϩάϥϜ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 4.2 Bazel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 4.3 Bzlmod Λ࢖ͬͨΞϓϦέʔγϣϯ։ൃ . . . . . . . . . . . . . . . . . . . . . . . . 61 v
  3. ໨࣍ 4.4 ͓͠·͍ . . . . . . .

    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 ୈ 5 ষ όφʔੜ੒πʔϧͰσβΠφʔΛ޾ͤʹ͔ͨͬͨ͠࿩ 69 5.1 ·͕͖͑ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 5.2 എܠ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 5.3 πʔϧͷ֓ཁ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 5.4 πʔϧͷٕज़બఆ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 5.5 πʔϧͷ։ൃ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 5.6 ఏڙͨ݁͠Ռ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 5.7 ࠓޙͷల๬ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 5.8 ͓·͚ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 5.9 ࢖༻ͨ͠ AI ػೳʹ͍ͭͯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 5.10 ࣮ࡍʹ AI ੜ੒ͯ͠Έͨ݁Ռ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 5.11 ࠷ޙʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 ୈ 6 ষ ӡ༻ೖߘσʔλΛ AI ͰνΣοΫ͢Δ࢓૊ΈΛ࡞ͬͨ 79 6.1 ·͕͖͑ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 6.2 ՝୊ʹ͍ͭͯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 6.3 ։ൃʹ͍ͭͯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 6.4 ӡ༻࣌ʹݟ͔ͭͬͨ՝୊ͱରԠ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 6.5 ͋ͱ͕͖ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳϕΫτϧݕࡧΛಋೖͯ͠ΈΔ 89 7.1 Obsidian ͱ͸Կ͔ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 7.2 ࢲ͕ Obsidian ͕޷͖ͳཧ༝ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 7.3 ϓϥάΠϯ։ൃೖ໳ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 7.4 Obsidian ʹϕΫτϧݕࡧΛಋೖͯ͠ΈΔ . . . . . . . . . . . . . . . . . . . . . . . 93 7.5 ऴΘΓʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 ஶऀ঺հ 101 vi
  4. ୈ 1 ষ TIPSTAR ʹ͓͚Δ DWH ׆༻ͱ։ൃ νʔϜͱͷ࿈ܞ ͜Μʹͪ͸ɺιʔγϟϧϕοςΟϯάࣄۀຊ෦ ։ൃࣨ

    ৽ن։ൃάϧʔϓͷߐാͰ͢ɻ ຊষͰ͸ɺ2023 ೥౓͝Ζʹ TIPSTAR *1 ʹ਺஋ج൫ͱͯ͠࡞੒ͨ͠ DHW (σʔλ΢ΣΞϋ΢ ε) ͱɺDWH ͱϓϩμΫτͷ਺஋ܭଌʹ·ͭΘΔ։ൃνʔϜͱͷ࿈ܞϓϩηεʹ͍ͭͯ঺հ͠·͢ɻ ͳ͓ຊষ͸ϓϩμΫτͰσʔλ෼ੳΛߦͬͯΑΓྑ͍αʔϏεͮ͘ΓΛ͍ͨ͠σʔλΞφϦετɾ ϓϥϯφʔɾ։ൃऀ΁޲͚ͯॻ͔Ε͍ͯ·͢ɻ 1.1 DWH Λ࡞ͬͨཧ༝ ·ͣ͸લஈͱͯ͠ɺͳͥ TIPSTAR ʹ DWH Λ࡞Δ͜ͱʹͳͬͨͷ͔Λઆ໌͠·͢ɻ DWH ಋೖҎલͷσʔλநग़ DWH ಋೖલͷ TIPSTAR Ͱ͸ɺܦཧ΍ػೳɾࢪࡦͷޮՌܭଌͷͨΊʹɺσʔλϕʔεͷόοΫ ΞοϓͳͲͷ͞·͟·ͳσʔλιʔε *2 *3 ͔ΒɺσʔλΞφϦετ΍։ൃνʔϜ͕௚઀ SQL ΫΤ ϦΛൃߦ͢Δ͜ͱͰσʔλநग़Λߦ͍ͬͯ·ͨ͠ɻ·ͨɺϏδωε৬Λ͸͡Ίͱ͢Δσʔλར༻ऀ ͸ɺͦΕΒͷநग़σʔλΛ Google Spreadsheet *4 ΍ Looker *5 Λ׆༻ͯ͠ɺࢹ֮Խɾ෼ੳʹར༻ ͍ͯ͠·ͨ͠ɻ ͜ͷϑϩʔ͸ɺ४උͳ͠ͰͨͩͪʹσʔλΛநग़Ͱ͖ɺಛʹ KPI ͳͲͷ਺஋ఆ͕͍ٛ͋·͍ͳαʔ Ϗεॳظஈ֊Ͱ͸ YAGNI *6 ͷ؍఺͔Β΋༗ޮͰͨ͠ɻ *1 ڝྠɾPIST6ɾΦʔτϨʔεͷωοτ౤ථ͕Ͱ͖Δڞ༡ܕεϙʔπϕοςΟϯάαʔϏε https://about.tipstar.com/ *2 https://cloud.google.com/bigquery *3 https://developers.google.com/analytics *4 https://developers.google.com/sheets *5 https://cloud.google.com/looker *6 You ain’t gonna need it. ࢖͏͔Θ͔Βͳ͍ػೳ͸௥Ճ͠ͳ͍ํ͕ྑ͍ͱ͍͏ΤϯδχΞϦϯάͳͲͷ෼໺Ͱ༻͍Β ΕΔݪଇͷҰͭ 1
  5. ୈ 1 ষ TIPSTAR ʹ͓͚Δ DWH ׆༻ͱ։ൃνʔϜͱͷ࿈ܞ 1.1 DWH Λ࡞ͬͨཧ༝

    ՝୊ͱλεΫϑΥʔεൃ଍ ͔͠͠ϓϩμΫτΛӡ༻͍ͯ͘͠தʹ͸ɺܦཧ্ͷਖ਼֬ͳ਺஋Λࢉग़͢Δඞཁ͕͋Δ৔߹͕͋Γ· ͢ɻ·ͨɺαʔϏεશମͷػೳར༻τϨϯυͳͲͷத௕ظతͳܭଌ΋ٻΊΒΕ·͢ɻ͞Βʹɺظ຤ɾ ೥຤ͳͲͷఆظతʹඞཁͱͳΔෳࡶͳσʔλநग़ʹؔͯ͠΋ಉ༷Ͱ͢ɻ ͜ΕΒͷέʔεͰσʔλநग़ΫΤϦΛ౎౓৽ن࡞੒͢Δମ੍Ͱ͸ɺ਺஋ͷͣΕ͕ੜ͡ΔՄೳੑ͕͋ Γ·͢ɻ͞ΒʹɺαʔϏε͕௕ظӡ༻͞Ε͍ͯ͘ʹͭΕΫΤϦ࡞੒ͷྦྷܭίετ͕ߴಅ͢Δ͜ͱ΋໌ Β͔ʹͳΓ·ͨ͠ɻ ਤ 1.1: DWH ੔උҎલʹى͍ͬͯͨ͜σʔλநग़࣌ͷࠔΓ͝ͱ ͦ͜Ͱσʔλج൫੔උͱͯ͠ɺDWH Λ࡞੒͢ΔλεΫϑΥʔε͕݁੒͞Εɺ2023 ೥౓Λத৺ʹ ҎԼͷΑ͏ͳ DWH ʹؔ͢Δ։ൃɾӡ༻Λ࣮ࢪ͠·ͨ͠ɻ 1. KPI Λ͸͡Ίͱ͢Δ֤छ਺஋ͷఆٛͷݟ௚͠ 2. σʔλநग़ͷج൫ͱͳΔ DWH ͷઃܭ͓Αͼߏங 3. ϏδωεଆΛத৺ͱͨ͠ DWH ͷՁ஋ͱར༻ํ๏ʹ͍ͭͯͷΨΠμϯεͷ࣮ࢪ 2
  6. ୈ 1 ষ TIPSTAR ʹ͓͚Δ DWH ׆༻ͱ։ൃνʔϜͱͷ࿈ܞ 1.1 DWH Λ࡞ͬͨཧ༝

    KPI Λ͸͡Ίͱ͢Δ֤छ਺஋ͷఆٛͷݟ௚͠ KPI πϦʔΛ੔ཧ͠ɺചΓ্͛΍ΞΫςΟϒϢʔβ਺ͳͲͷ਺஋ͷఆٛΛݟ௚͢͜ͱͰɺDWH ͱ KPI ͷରԠΛ࡞੒͠·ͨ͠ɻ ྫ͑͹ɺചΓ্͛ʹฦֹۚΛؚΉ΂͖͔ɺΞΫςΟϒϢʔβ਺ͱͯ͠ϩάΠϯϘʔφεͷड͚औΓ Λϕʔεͱͯ͠ܭ্͢Δ͔ͳͲͷৄࡉͳఆٛΛܾΊ·ͨ͠ɻ͜ΕʹΑΓɺσʔλநग़͝ͱʹಉ໊͡લ ͷ਺஋͕ҟͳΔࣄ৅Λ๷͙͜ͱ͕ՄೳͱͳΓ·ͨ͠ɻ ֤छσʔλநग़ʹ͓͚ΔϕʔεͱͳΔ DWH ͷ࡞੒ DWH ଆͰ͸ KPI Λ௚઀తʹఆٛ͢ΔͷͰ͸ͳ͘ɺσΟϝϯγϣφϧϞσϦϯάΛϕʔεʹɺυ ϝΠϯతʹಠཱͨ͠ϑΝΫτɾσΟϝϯγϣϯςʔϒϧΛఆٛ͠·ͨ͠ɻ͜ΕʹΑΓɺσʔλΞφϦ ετ͕͞·͟·ͳཁ݅ͷσʔλநग़ʹ׆͔͢͜ͱ͕Ͱ͖ΔΑ͏ʹ͠·ͨ͠ɻ σΟϝϯγϣφϧϞσϦϯάͱ͸ɺं݊ͷߪೖֹۚͳͲͷ਺஋ΛϑΝΫτςʔϒϧɺं݊ʹඥͮ͘ Ϩʔε৘ใ΍ߪೖϢʔβʔͷ৘ใΛσΟϝϯγϣϯςʔϒϧͱͯ͠ఆٛ͢Δ͜ͱͰɺϑΝΫτͰ͋Δ ਺஋Λ͞·͟·ͳࢹ఺ʢσΟϝϯγϣϯʣͰ෼ੳͰ͖ΔσʔλϞσϦϯάख๏Ͱ͢ɻ ͳ͓ɺTIPSTAR ʹ͓͚ΔചΓ্͛ KPI ͸ं݊ߪೖֹۚΛϕʔεʹܭࢉ͞Ε·͕͢ɺചΓ্͛ͷ ઐ༻ςʔϒϧͱͯ͠ fc_sales Λఆٛ͢Δ୅ΘΓʹɺं݊ߪೖςʔϒϧͱͯ͠ fc_purchase_tick ets Λఆٛ͠·ͨ͠ɻKPI ͷܭࢉ͸͜ͷςʔϒϧΛ΋ͱʹͨ͠ΫΤϦͰߦ͍·͢ɻ͜ͷΞϓϩʔν ΛऔΔཧ༝͸ɺKPI ͷݟ௚͕͠සൟʹߦΘΕΔҰํͰɺं݊ߪೖͱ͍͏ߦಈࣗମ͸ҰఆͰ͋ΔͨΊ Ͱ͢ɻ·ͨɺKPI ͷ਺஋͸ Looker ͷ LookML Ͱ؅ཧɾӡ༻͞Ε͍ͯ·͢ɻ σʔλΞφϦετ͸͜Ε·ͰΞϓϦέʔγϣϯίʔυͱ֤छϩάΛಡΈͳ͕Β LookML Ͱ KPI Λఆ͍ٛͯ͠·͕ͨ͠ɺDWH Λ੔උ͢Δ͜ͱͰɺ͜ΕΒͷςʔϒϧΛ༻͍ͯ؆୯ʹ KPI Λఆٛɾ ௐ੔Ͱ͖ΔΑ͏ʹͳΓ·ͨ͠ɻ ਤ 1.2: DWH Ͱ͸ fc_purchase_tickets Λఆٛ͠ɺ۩ମతͳ KPI ͳͲͷ਺஋͸ LookML ͳͲͰՃ ޻ͯ͠ࢹ֮Խ͢Δ 3
  7. ୈ 1 ষ TIPSTAR ʹ͓͚Δ DWH ׆༻ͱ։ൃνʔϜͱͷ࿈ܞ 1.1 DWH Λ࡞ͬͨཧ༝

    ϏδωεଆΛத৺ͱͨ͠ DWH ͷՁ஋ͱར༻ํ๏ʹ͍ͭͯͷΨΠμϯεͷ࣮ࢪ DWH Λ׆༻ͯ͠΋Β͏ͨΊɺ·ͨࠓޙͷ DWH ͷӡ༻ʹ͍ͭͯͷཧղΛಘΔͨΊʹɺϏδωε ଆΛத৺ʹ DWH ͱ͸ͲͷΑ͏ͳ΋ͷͰɺαʔϏεͷҙࢥܾఆʹͲͷΑ͏ͳྑ͍ӨڹΛ༩͑Δ͜ͱ ͕Ͱ͖Δͷ͔ʹ͍ͭͯΨΠμϯεΛߦ͍·ͨ͠ɻ ·ͨɺϏδωεଆ͔Β௥ՃͰ DWH ͱͯ͠੔උͯ͠΄͍͠਺஋ΛώΞϦϯά͢Δ͜ͱͰɺϓϩμ Ϋτ಺ͷ DWH ͷՁ஋ΛߴΊ·ͨ͠ɻ ͨͱ͑͹ɺڝྠͷ֤։࠵৘ใͷσΟϝϯγϣϯςʔϒϧʹ։࠵࣌ؒଳ *7 ͷ৘ใΛ௥Ճ͢ΔͳͲͷ վળΛߦ͏͜ͱ͕Ͱ͖·ͨ͠ɻ *7 ڝྠͷϨʔε͕Ͳͷ࣌ؒଳʹ։࠵͞ΕΔ͔ͷ۠෼ɻͨͱ͑͹ϛουφΠτͰ͋Ε͹ਂ໷ʹ։࠵͞ΕΔ΋ͷΛࢦ͢ɻ 4
  8. ୈ 1 ষ TIPSTAR ʹ͓͚Δ DWH ׆༻ͱ։ൃνʔϜͱͷ࿈ܞ 1.2 DWH ͷߏ੒ͷৄࡉ

    1.2 DWH ͷߏ੒ͷৄࡉ TIPSTAR ʹ͸ɺσʔλϕʔεͷόοΫΞοϓ΍ΞϓϦέʔγϣϯͷ࣮ߦϩάɺGoogle Analytics ͳͲͷ͞·͟·ͳσʔλιʔεΛ BigQuery ܦ༝ͰΫΤϦͰ͖ΔΑ͏ʹͳ͍ͬͯ·͢ɻͦΕͧΕͷ σʔλιʔε͸਺஋ܭଌ্ॏཁͳ໾ׂΛ୲͍ͬͯ·͢ɻͨͱ͑͹ɺDAU Λܭଌ͢Δ৔߹ʹ͸ΞϓϦ έʔγϣϯͷ࣮ߦϩάɺं݊ͷߪೖྔΛௐ΂Δʹ͸σʔλϕʔεͷόοΫΞοϓɺಛఆͷը໘ͷϢʔ βʔΞΫηε਺ͷܭଌʹ͸ Google Analytics ͱɺॏཁͳ਺஋͕఺ࡏ͍ͯ͠·͢ɻ ·ͨσʔλϕʔεͷόοΫΞοϓ͸͋͘·ͰΞϓϦέʔγϣϯ༻ʹνϡʔϯφοϓ͞ΕͨεΩʔϚ ͷίϐʔͰ͋ΓɺͲͷςʔϒϧΧϥϜʹͲͷΑ͏ͳσʔλ͕ೖ͍ͬͯΔ͔͸ΞϓϦέʔγϣϯͷιʔ είʔυΛಡ·ͳ͚Ε͹ղಡ͢Δ͜ͱ͸Ͱ͖·ͤΜɻ ͜͏͍ͬͨࣄ৘͔ΒɺσʔλΞφϦετ͕ͨͩͪʹॊೈ͔ͭਖ਼֬ͳσʔλநग़Λ͢Δ͜ͱ͸ࠔ೉Ͱ ͋ΓɺDWH ͷ੔උΛߦ͏͜ͱ͸Ұఆͷཧ͕͋Γ·ͨ͠ɻͦ͜Ͱɺ͋Δఔ౓ਝ଎͔ͭॊೈʹ DWH Λ ߏஙͰ͖Δٕज़Λબఆ͠ɺTIPSTAR Ͱ͸ dbt *8 Λར༻ͯ͠σΟϝϯγϣφϧϞσϦϯάΛߦ͏͜ ͱͱ͠·ͨ͠ɻ σΟϝϯγϣφϧϞσϦϯάΛߦ͏͜ͱͰɺͨͱ͑͹ं݊ͷߪೖσʔλ΍Ϣʔβʔͷߦಈϩάͱ ͍ͬͨ਺஋Λ͋Δఔ౓ͷਖ਼نԽͷ΋ͱͰఆٛͰ͖Δ΄͔ɺΞφϦετ͕σʔλఆٛΛΑΓ௚ײతʹཧ ղͰ͖ΔΑ͏ʹͳΓ·ͨ͠ɻ ͞Βʹ dbt docs *9 Λ׆༻֤ͯ͠ςʔϒϧɾΧϥϜʹ͍ͭͯͷઆ໌Λߦ͏͜ͱͰɺΞϓϦέʔγϣ ϯίʔυΛಡ·ͣͱ΋σʔλநग़͕ߦ͑Δ؀ڥΛ੔උ͠·ͨ͠ɻ ਤ 1.3: DWH ੔උҎ߱ɺσʔλΞφϦετ͸ΞϓϦέʔγϣϯίʔυ΍ͦͷपล஌͕ࣝͳ͘ͱ΋ σʔλநग़͕ߦ͑ΔΑ͏ʹͳͬͨ *8 https://www.getdbt.com/ *9 https://docs.getdbt.com/docs/build/documentation 5
  9. ୈ 1 ষ TIPSTAR ʹ͓͚Δ DWH ׆༻ͱ։ൃνʔϜͱͷ࿈ܞ 1.2 DWH ͷߏ੒ͷৄࡉ

    ۩ମతͳΠϝʔδΛ͔ͭΉͨΊɺͨͱ͑͹ҏ౦Թઘڝྠ৔ *10 Ͱ 2024 ೥ 10 ݄ 1 ೔ʹߪೖ͞Εͨ 3 ࿈୯ं݊ͷ߹ܭߪೖֹۚΛܭࢉ͢Δͱͨ͠ࡍͷΫΤϦʹ͍ͭͯٞ࿦͠·͢ *11ɻ DWH ԽҎલͰ͸ɺ৔ͷ৘ใ΍ౌ͚ࣜ͸ ID Ͱ؅ཧ͞Ε͍ͯΔ΄͔ɺ೔෇͕ UTC Ͱ؅ཧ͞Ε͍ͯ Δ͜ͱ͔ΒɺΫΤϦ࡞੒࣌ʹ͸ࣄલ஌͕ࣝඞཁͰ͋ΓɺΫΤϦͷߏஙϛε͕ൃੜ͠΍͍͢؀ڥͰͨ͠ɻ Ϧετ 1.1: DWH ԽҎલͷΫΤϦ SELECT SUM(money) FROM user_keirin_tickets WHERE DATE(purchased_at, "Asia/Tokyo") = DATE("2024-10-01") AND ticket_state.return_type != "payback" AND velodrome_id = 37 AND bet_type = 1 ͜ͷΫΤϦ͸ɺDWH Խ͞Εͨ͜ͱͰҎԼͷΑ͏ʹॻ͖Լ͢͜ͱ͕Ͱ͖ΔΑ͏ʹͳΓ·ͨ͠ɻ Ϧετ 1.2: DWH ԽҎ߱ͷΫΤϦ SELECT SUM(fc_purchase_tickets.bet_money) FROM fc_purchase_tickets JOIN dm_races ON fc_purchase_tickets.race_id = dm_races.race_id JOIN dm_bet_types ON fc_purchase_tickets.bet_type = dm_bet_types.bet_type WHERE fc_purchase_tickets.purchase_date = DATE("2024-10-01") AND fc_purchase_tickets.race_type = "ڝྠ" AND fc_purchase_tickets.return_type != "ฦؐ" AND dm_races.velodrome_name = "ҏ౦" AND fc_purchase_tickets.bet_type_name = "ڝྠ:3࿈୯" *10 https://www.itokeirin.com/ *11 ࣮ࡍͷΫΤϦʹ͍ۙ΋ͷΛ༻ҙ͍ͯ͠·͕͢ɺ·ͬͨ͘ಉҰͷΫΤϦͰ͸͋Γ·ͤΜɻ 6
  10. ୈ 1 ষ TIPSTAR ʹ͓͚Δ DWH ׆༻ͱ։ൃνʔϜͱͷ࿈ܞ 1.3 DHW ΛͲ͏׆༻͍ͯ͠Δ͔

    1.3 DHW ΛͲ͏׆༻͍ͯ͠Δ͔ લઅͰ঺հͨ͠Α͏ʹɺσʔλநग़ͷ඼࣭ͱ଎౓ͷ޲্Λ໨ࢦͯ͠σΟϝϯγϣφϧϞσϦϯά ͷݩɺDWH Λ੔උ͠·ͨ͠ɻ·ͨσʔλΞφϦετ΍ϏδωεαΠυͷڠྗΛ͍͖ͨͩͳ͕Βɺ DWH ͷ׆༻ମ੍͸ॱ࣍੔උ͞Ε͍ͯ·͢ɻ 2024 ೥౓࣌఺ͰɺDWH ͸ҎԼͷ৔໘Ͱ׆༻͞Ε͍ͯ·͢ɻ • Looker ͱ઀ଓͯ͠ඇΤϯδχΞ޲͚ͷ GUI σʔλநग़ج൫ͱͯ͠ • σʔλΞφϦετͷΞυϗοΫͳෳࡶͳσʔλநग़ͷϕʔεͱͯ͠ • CSʢΧελϚʔαϙʔτʣͷෆఆظͳσʔλநग़ج൫ͱͯ͠ Looker ͱ઀ଓͯ͠ඇΤϯδχΞ޲͚ͷ GUI σʔλநग़ج൫ͱͯ͠ Looker Ͱ͸ɺKPI πϦʔΛμογϡϘʔυͱͯ͠ࢹ֮Խ͠ɺఆظతʹ Slack νϟϯωϧʹ౤ߘ͢ Δ͜ͱͰɺ୭Ͱ΋ਖ਼͍͠ϓϩμΫτͷݱࡏͷঢ়گΛ֬ೝͰ͖ΔΑ͏ʹ͠·ͨ͠ɻ·ͨɺDWH ͷσʔ λʹิ଍ࣄ߲ΛՃ͑ͨ Looker Explore Λ੔උ͢Δ͜ͱͰɺKPI ͷৄࡉ෼ੳ͕༰қʹͰ͖Δ؀ڥΛఏ ڙ͠·ͨ͠ɻ ͨͱ͑͹ɺTIPSTAR Ͱ͸ಛఆͷڝྠ৔Ͱͷं݊ߪೖʹରͯ͠ΩϟϯϖʔϯΛ࣮ࢪ͢Δ͜ͱ͕͋Γ ·͢ɻ͜ͷΩϟϯϖʔϯͷ৚݅ઃఆ΍࣮ࢪޙͷৼΓฦΓʹ DWH ͕׆༻͞Ε͍ͯ·͢ɻ Ϧετ 1.3: ं݊ߪೖͷ LookML (view) view: fc_purchase_tickets { view_label: "˒ं݊ߪೖ৘ใ" sql_table_name: ‘pjt.dwh.fc_purchase_tickets‘ ;; # ... measure: sales { group_label: "KPI" type: sum label: "Sales" description: "ं݊ߪೖ߹ܭֹۚ (ฦؐ͸আ͔ΕΔ)" filters: [state: "-ฦؐ"] sql: "${bet_money}" ;; } } 7
  11. ୈ 1 ষ TIPSTAR ʹ͓͚Δ DWH ׆༻ͱ։ൃνʔϜͱͷ࿈ܞ 1.3 DHW ΛͲ͏׆༻͍ͯ͠Δ͔

    Ϧετ 1.4: ं݊ߪೖͷ LookML (explore) explore: fc_purchase_tickets { label: "˒ं݊ߪೖ৘ใ" join: dm_users { sql_on: ${fc_purchase_tickets.user_id}=${dm_users.user_id} ;; relationship: many_to_one type: inner } join: dm_races { sql_on: ${fc_purchase_tickets.race_id} = ${dm_races.race_id} ;; relationship: many_to_one type: left_outer } join: dm_bet_types { sql_on: ${fc_purchase_tickets.bet_type_id} = ${dm_bet_types.bet_type_id};; relationship: many_to_one type: left_outer } } σʔλΞφϦετͷΞυϗοΫͳෳࡶͳσʔλநग़ͷϕʔεͱͯ͠ σʔλ෼ੳͰ͸ɺ෼ੳΛਐΊΔ͏ͪʹෳࡶͳΫΤϦ͕ඞཁͱͳΔ͜ͱ͕͋Γ·͢ɻͨͱ͑͹ɺಛ ఆͷߦಈΛͨ͠Ϣʔβʔͷं݊ߪೖ܏޲ΛϢʔβʔͷαʔϏε։࢝೔͝ͱʹൺֱ͢Δͱ͍ͬͨཁ݅ Ͱ͢ɻ ͨͱ͑͹ɺ͋ΔಛఆͷߦಈΛͨ͠Ϣʔβʔͷं݊ߪೖ܏޲ΛɺϢʔβʔͷαʔϏε։࢝೔ͷηάϝ ϯτ͝ͱʹൺֱ͍ͨ͠ɺͱ͍ͬͨधཁ͸ɺͨͩͪʹ Looker Ͱ͸දݱ͢Δ͜ͱ͕ࠔ೉Ͱ͢ɻ DWH Λ੔උ͢Δલ͸σʔλϕʔε͔Β௚઀αϒΫΤϦΛநग़͢ΔͳͲͯ͠ɺ਺ඦߦͷΫΤϦΛߏ ੒͢Δඞཁ͕͋Γ·ͨ͠ɻ͔͠͠ɺDWH ͷ੔උޙ͸ɺ౷Ұ͞ΕͨϕʔεͷݩͰΫΤϦΛॻ͘͜ͱ͕ Ͱ͖ɺΫΤϦߦ਺ͷ࡟ݮ΍σʔλΞφϦετؒͷϨϏϡʔ࣌ؒͷ୹ॖ͕࣮ݱ͠·ͨ͠ɻ CS ͷෆఆظͳσʔλநग़ج൫ͱͯ͠ CS ۀ຿΍ܦཧۀ຿ʹ͸ಥൃతͳσʔλ֬ೝ͕ඞཁͱͳΔ͜ͱ͕͋Γ·͢ɻͨͱ͑͹ɺϢʔβʔͷ Ωϟϯϖʔϯͷ௅ઓঢ়گͷௐࠪ΍ɺ֎෦ͱͷܾࡁঢ়گͷ੔߹ੑͷνΣοΫͳͲͰ͢ɻ DWH Λར༻͢Δ͜ͱͰɺΫΤϦϛεΛݮΒ͠ɺΤϯδχΞͷ޻਺Λ࡟ݮͭͭ͠ɺநग़σʔλͷ඼ ࣭Λ୲อͰ͖ΔΑ͏ʹͳΓ·ͨ͠ɻ 8
  12. ୈ 1 ষ TIPSTAR ʹ͓͚Δ DWH ׆༻ͱ։ൃνʔϜͱͷ࿈ܞ 1.4 DWH ΛͲ͏ҡ࣋͢Δ͔

    1.4 DWH ΛͲ͏ҡ࣋͢Δ͔ ͜͜·Ͱ DWH Λ࡞੒ͨ͠ཧ༝ͱͦͷϝϦοτΛ঺հ͖ͯ͠·͕ͨ͠ɺDWH ͸࡞੒ͯ͠ऴΘΓͰ ͸͋Γ·ͤΜɻϓϩμΫτͷػೳ͕૿͑Δͨͼʹ DWH ࣗମͷߋ৽͕ඞཁͰ͋Γɺ։ൃνʔϜͷυ ϝΠϯ஌͕ࣝෆՄܽͰ͢ɻ ͔͠͠ɺDWH ͷଟ͘ͷϝϦοτ͸ϏδωεαΠυʹ͋Γɺ։ൃνʔϜʹ͸௚઀తͳϝϦοτ͸গ ͳ͍͔΋͠Ε·ͤΜɻ։ൃνʔϜ͕ DWH ͷ੔උ΍਺஋ͷࢹ֮ԽʹϝϦοτΛݟग़͘͢͠Έ࡞Γ͕ɺ DWH ͷҡ࣋ʹ͸͔ܽͤ·ͤΜɻ ։ൃνʔϜʹͱͬͯ DWH ͷϝϦοτ ໿Ұ೥ؒ DWH Λ։ൃɾӡ༻͖ͯͨ͠தͰɺҎԼͷϝϦοτ͕ݟΒΕ·ͨ͠ɻ·ͨ͜ΕΒͷྫΛ௒ ͑ΒΕΔΑ͏ͳϝϦοτΛ࡞Δ͜ͱ͕ݱࡏͷࣗ෼ͷ՝୊ͱͳ͍ͬͯ·͢ɻ ӡ༻ۀ຿ͷෛ୲ܰݮ CS ͷσʔλநग़ۀ຿ͳͲͷӡ༻ۀ຿ͷෛ୲ΛݮΒ͢͜ͱ͕Ͱ͖·͢ɻ ࣗ෼ͨͪͷࢪࡦͷݟ͑ΔԽ Ϣʔβʔͷ࢖༻ঢ়گ΍ධՁΛࢹ֮Խ͠ɺࢪࡦͷޮՌΛ֬ೝͰ͖·͢ɻ AI ׆༻ AI Λར༻ͨ͠ػೳ։ൃ΍෼ੳɺ৽͍ٕ͠ज़ྖҬ΁ͷ௅ઓ͕ՄೳͰ͢ɻ੔ܗ͞ΕͨσʔλΛ࢖ ͏͜ͱͰɺAI ͷੑೳ޲্ʹد༩͠·͢ɻ 1.5 ͦͯ͠ࠓ 2024 ೥ 10 ݄ݱࡏɺDWH ͸ϓϩμΫτͷσʔλج൫ͱͯ͠ॱௐʹՔಇ͍ͯ͠·͢ɻ࠷ۙͰ͸͞Β ͳΔ௅ઓͱͯ͠ɺҎԼͷࢪࡦΛ࣮ࢪ͍ͯ͠·͢ɻ • dbt ΍ Looker ͷ։ൃΠϕϯτΛ։࠵͠ɺ։ൃνʔϜͷ DWH ҡ࣋ίετͷ࡟ݮɺෑډͷ௿Լ ʹ௅ઓ͍ͯ͠·͢ • CS ରԠͳͲͷఆৗӡ༻ۀ຿ʹ DWH ϕʔεͷ෼ੳج൫Λ૊ΈࠐΜͰ͍͘͜ͱͰɺ։ൃνʔϜ ͷ޻਺࡟ݮʹߩݙ͍ͯ͠·͢ • ։ൃνʔϜҎ֎ͷϝϯόʔͰ΋ DWH ͷӡ༻͕Ͱ͖ΔΑ͏ʹɺϓϩμΫτίʔυʹؔ͢Δ஌ ࣝΛਂΊɺΑΓྑ࣭ͳσʔλΛऔಘͰ͖ΔΑ͏ʹௐ੔͍ͯ͠·͢ ࠓޙ͸ɺGemini in BigQuery *12 ͳͲͷ৽ٕज़Λ׆༻͠ɺϓϩμΫτͷ؍ଌਫ਼౓Λ޲্ͤ͞ɺϢʔ βʔʹͱͬͯΑΓՁ஋ͷ͋ΔαʔϏεΛఏڙ͍͖͍ͯͨ͠ͱߟ͍͑ͯ·͢ɻ *12 https://cloud.google.com/gemini/docs/bigquery/overview 9
  13. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕ ূͷࣗಈԽೖ໳ ͜Μʹͪ͸ɻΈͯͶϓϩμΫτ։ൃ෦ͷ sou ͱਃ͠·͢ɻ ʮՈ଒ΞϧόϜ

    ΈͯͶʢҎԼɺΈͯͶʣ ʯ Ͱ iOSɺAndroid ΞϓϦͱαʔόͷ։ൃΛϝΠϯͷۀ຿ͱ͍ͯ͠·͢ɻ ΈͯͶ͸ଟݴޠʹରԠͨ͠ΞϓϦͱͳ͓ͬͯΓ೔ʑ৽ͨͳ຋༁จݴ͕௥Ճ͞Ε·͢ɻ຋༁จݴ͸຋ ༁୲౰΁ґཔͨ͠΋ͷ͕ద༻͞ΕΔͨΊ඼࣭ͷߴ͍΋ͷͱͳ͍ͬͯ·͕͢ɺػೳ௥Ճʹࡍͯ͠౎౓ґ པΛߦ͏ܗͷͨΊ௥Ճ͞Εͨจݴ͕σβΠϯશମͷτϯϚφͱ߹͍ͬͯΔ͔͸ผͱͳΓ·͢ɻͦ͜Ͱ ը໘શମͰݟͨͱ͖ͷ඼࣭΋୲อ͢ΔͨΊ຋༁Λద༻ͨ͠ը໘શମͷνΣοΫ࡞ۀ΋຋༁ͱผʹߦͬ ͍ͯ·͢ʢ͜ΕΛࣾ಺Ͱ͸ LQA ͱݺΜͰ͍·͢ʣ ɻ LQA ࡞ۀ͸຋༁ΛߦͬͨશݴޠͰ֬ೝ͢Δඞཁ͕͋ΓɺΈͯͶͰ͸ࣥච࣌఺Ͱ೔ຊޠΛআ͖ 7 ͭ ͷݴޠʹରԠ͍ͯ͠ΔͨΊը໘ҰͭͰ΋ 7 ͭͷεΫϦʔϯγϣοτΛࡱΒͳͯ͘͸ͳΓ·ͤΜɻͦΕ ΋ͨͩݴޠΛ੾Γସ͑Ε͹Α͍ͱ͍͏΋ͷͰ΋ͳ͘ɺ৽ͨʹ௥Ճͨ͠ػೳ͕ඞཁͱͳΔΑ͏ͳঢ়ଶΛ ࡞ΔͨΊʹҰ࣌తʹσʔλ΍ίʔυΛमਖ਼͢Δඞཁ΋͋Γ·͢ɻ ·ͨɺLQA Ҏ֎ʹ΋ίʔυϨϏϡʔ࣌ʹ͓͍ͯૣ͍ஈ֊Ͱෆ۩߹Λݟ͚ͭΔͨΊඞཁʹԠͯ͡Ϩ ϏϡΞʔ͸ಈ࡞ݕূΛߦ͓ͬͯΓɺಛʹαʔόαΠυͷมߋʹΑͬͯΞϓϦ্ͷৼΔ෣͍΍ݟͨ໨ʹ ӨڹΛ༩͑ΔΑ͏ͳमਖ਼͸ iOS ͱ Android ͷ྆ํͰ໰୊͕ى͖͍ͯͳ͍͔Λ֬ೝ͢ΔͨΊʹίετ ͷֻ͔Δ࡞ۀͱͳ͍ͬͯ·͢ʢ΋͠໰୊͕͋ͬͨ৔߹ʹ͸ίʔυͷमਖ਼ޙʹ΋ಈ࡞ݕূ͕ඞཁͰ͢ʣ ɻ ͜͏ͨ͠՝୊ʹର͠ͳΔ΂͘ίετΛֻ͚ͣʹղܾ͢Δखஈͱͯ͠චऀ͸ Appium Ͱಈ࡞ݕূΛࣗ ಈԽ͢ΔπʔϧΛ࡞ͬͯΈ·ͨ͠ɻຊষͰ͸ͦ͜ͰಘͨܦݧΛݩʹϋϯζΦϯܗࣜͰ iOS/Android Ͱಈ࡞ݕূͷࣗಈԽΛ͢ΔͨΊͷखॱΛ঺հ͠·͢ɻ঺հ͢Δ಺༰͸ΈͯͶΞϓϦͷը໘ߏ੒ʹ߹Θ ͤͨ΋ͷͱͳ͍ͬͯ·͕͢ Appium ʹΑΔϞόΠϧΞϓϦͷࣗಈૢ࡞Λߦ͏͏͑ͰԿ͕ඞཁͰͲͷ Α͏ͳ͜ͱ͕Ͱ͖Δ͔ʹ͍ͭͯ͸ຊষͷ಺༰͔ΒټΈऔ͍͚ͬͯͨͩΔͷͰ͸ͳ͍͔ͱࢥ͍·͢ɻ 11
  14. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ ຊষશମͷྲྀΕ͸࣍ͷͱ͓ΓͰ͢ɻ 1. Appium ͷ঺հ 2.

    Android ΞϓϦͷͨΊͷ؀ڥߏங 3. Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ 4. iOS ΞϓϦͷͨΊͷ؀ڥߏங 5. iOS ΞϓϦͰಈ࡞ݕূΛࣗಈԽ͢Δ ·ͨɺຊষͰߦ͏ಈ࡞ݕূ͸࣍ͷΑ͏ͳૢ࡞Λߦ͏΋ͷͱ͠·͢ɻ 1. ΞϓϦΛ্ཱͪ͛Δ 2. Ո଒ઃఆը໘Λ։͘ 3. ݴޠઃఆ·ͰεΫϩʔϧ͠ݴޠΛ੾Γସ͑Δ 4. ετΞը໘Λ։͘ 5. εΫϦʔϯγϣοτΛࡱΔ ஫ҙࣄ߲ ຊষͰ঺հ͢Δ಺༰͸ҎԼͷಈ࡞؀ڥͰߦ͍ͬͯ·͢ɻ ҟͳΔ؀ڥͰͷಈ࡞֬ೝ͸͍ͯ͠ͳ͍ͨΊ͝ཹҙ͍ͩ͘͞ɻ • ୺຤: MacBook Pro • νοϓ: Apple M2 Max • ϝϞϦ: 64 GB • OS: macOS Sonoma 14.6.1 • Xcode: 15.4 (15F31d) • Android Studio: Jellyfish | 2023.3.1 Patch 2 ·ͨɺຊষͰ঺հ͢Δ੒Ռ෺͸࣍ͷπʔϧΛ࢖༻͠·͢ɻ • Visual Studio Code • Bun v1.1.30 (ύοέʔδϚωʔδϟʔ) • Node v22.4.0 • Appium (Appium αʔό) • WebdriverIO (Appium ΫϥΠΞϯτ) • Appium Inspector (GUI Ξγελϯτπʔϧ) • iOS γϛϡϨʔλɺAndroid ΤϛϡϨʔλ • VCS: Git 12
  15. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.1 Appium ͷΞʔΩςΫνϟ ຊষͰ঺հ͢Δίʔυ͸ GitHub

    ্ʹΦʔϓϯιʔεͱͯ͠ެ։͍ͯ͠·͢*1ɻιʔείʔυ͸ຊ ষͷྲྀΕʹԊͬͯίϛοτΛੵΜͰ͍ΔͷͰ΋్͠தͰ໰୊͕ੜͨ͡৔߹͸ͦͪΒ͔Β೚ҙͷ࣌఺ͷ ίʔυΛ෮ݩͰ͖·͢ʢ࠷৽ͷίϛοτ*2͕ຊষͷ׬੒ܗͱͳ͍ͬͯ·͢ʣ ɻ ͦΕͰ͸ͬͦ͘͞ Appium ʹ͍ͭͯ঺հ͍͖ͯ͠·͢ɻ 2.1 Appium ͷΞʔΩςΫνϟ Appium ͸ϚϧνϓϥοτϑΥʔϜʹରԠͨ͠ E2E ςεςΟϯάϑϨʔϜϫʔΫͰ͢ɻ͞·͟· ͳϓϥοτϑΥʔϜʹରԠ͓ͯ͠ΓಛʹϞόΠϧΞϓϦͷྖҬʹ͓͍ͯ͸ࣥච࣌఺ͰσϑΝΫτελ ϯμʔυͱݴͬͯ΋ࠩ͠ࢧ͑ͳ͍Ͱ͠ΐ͏ɻৄ͍͠ಛ௃͸υΩϡϝϯτʹৄ͘͠ࡌ͍ͬͯΔͷͰׂѪ ͍͖ͤͯͨͩ͞·͕͢ɺAppium Λཧղ͢Δ͏͑ͰΞʔΩςΫνϟͷશମ૾͕೺ѲͰ͖ΔͱϋϯζΦ ϯܗࣜͰԿΛ΍͍ͬͯΔͷ͔໎ࢠʹͳΓͮΒ͍͔ͱࢥ͍·͢ͷͰ͜͜Ͱ؆୯ʹ঺հ͍͖ͤͯͨͩ͞ ·͢ɻ Appium ͸ओʹʮAppium ΫϥΠΞϯτʯ ʮAppium αʔόʯ ʮAppium υϥΠόʯͷ 3 ͭͱʮσό ΠεʯͰߏ੒͞Ε·͢ɻͦΕͧΕͷ໾ׂ͸ҎԼͷͱ͓ΓͰ͢ɻ Appium ΫϥΠΞϯτ Appium ΫϥΠΞϯτ͸ Appium αʔόͱ௨৴͠σόΠεΛૢ࡞͢ΔͨΊͷίʔυΛఏڙ͢Δ πʔϧͰ͢ɻAppium αʔό΍ͦͷઌʹ͋ΔσόΠε͓Αͼૢ࡞ର৅ͱͳΔΞϓϦͷ઀ଓ৘ใ΍ઃఆ ΋ Appium ΫϥΠΞϯτ্Ͱఆٛ͠·͢ɻAppium ΫϥΠΞϯτʹ͸ Appium νʔϜ͕։ൃ͍ͯ͠ Δ΋ͷͱ༗ࢤ͕։ൃ͍ͯ͠Δ΋ͷɺ͞·͟·ͳݴޠͷ࣮૷͕͋ΓϢʔβʔ͸ࣗ਎͕ීஈ࢖͍׳Ε͍ͯ ΔݴޠͰࣗಈԽ͢ΔͨΊͷεΫϦϓτΛॻ͚·͢ɻ·ͨɺ۩ମతͳૢ࡞͸ Appium ΫϥΠΞϯτʹ ΑΓந৅Խ͞Ε͍ͯΔͨΊ͋Δఔ౓ϓϥοτϑΥʔϜʹґଘ͠ͳ͍ܗͰॲཧΛهड़Ͱ͖ΔΑ͏ʹͳͬ ͍ͯ·͢ɻ Appium αʔό Appium αʔό͸ Node.js ੡ͷ HTTP αʔόͰ Appium ΫϥΠΞϯτͱσόΠεͷதܧ໾Λ୲ͬ ͓ͯΓɺAppium ΫϥΠΞϯτ͔Βड͚ͨϦΫΤετΛࢦఆ͞ΕͨσόΠε্Ͱ࣮ߦ͠ड͚औͬ ࣮ͨߦ݁ՌΛ Appium ΫϥΠΞϯτ΁ฦ٫͠·͢ɻσόΠε্ͰॲཧΛ͢ΔͨΊʹ࣍ʹ঺հ͢Δ Appium υϥΠό͕ඞཁͱͳΓ·͢ɻ Appium υϥΠό Appium υϥΠό͸ Appium ͕ఏڙ͢Δ֤ϞόΠϧϓϥοτϑΥʔϜʹಛԽͨ͠πʔϧͱͳͬͯ ͍·͢ɻAppium αʔό͔Βͷ௨৴Λड͚औΓந৅Խ͞Ε͍ͯͨ UI ૢ࡞ॲཧΛ֤ϓϥοτϑΥʔϜ *1 https://github.com/soudai-s/appium-example *2 https://github.com/soudai-s/appium-example/tree/119a0561c56e49ea1446ec45d230bde7a07b1d6b 13
  16. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.2 Android ΞϓϦͷͨΊͷ؀ڥߏங Ͱߦ͏ͨΊͷॲཧʹม׵͠σόΠεΛૢ࡞͠·͢ɻ σόΠε σόΠε͸࣮ػɺ΋͘͠͸ΤϛϡϨʔλ΍γϛϡϨʔλͷ͜ͱΛࢦ͠·͢ɻຊষͰ͸

    Android Τ ϛϡϨʔλ͓Αͼ iOS γϛϡϨʔλΛ࢖༻͠·͢ɻ͜ΕΒͷσόΠε͕ Appium υϥΠόܦ༝Ͱ Appium αʔόͱ઀ଓ͠σόΠε্Ͱ Appium ΫϥΠΞϯτͰॻ͍ͨॲཧ͕࣮ߦ͞Ε·͢ɻ ߏ੒ʹ͍ͭͯͷิ଍ Appium Λಈ࡞ͤ͞ΔͨΊʹ͸͜ΕΒͷ໾ׂͦΕͧΕΛ୲͏πʔϧ͕ඞཁͱͳ͓ͬͯΓɺ·ͨɺ໾ ׂʹΑͬͯ͸πʔϧͷબ୒ࢶ͕ଟذʹ౉ΔͨΊ΍΍ށ࿭͏͔΋͠Ε·ͤΜɻຊষͰ͸࣍Ҏ߱ͰॱΛ ௥ͬͯԿΛηοτΞοϓ͍ͯ͠Δͷ͔Λৄ͘͠આ໌͠·͢ɻ 2.2 Android ΞϓϦͷͨΊͷ؀ڥߏங ·ͣ͸ηοτΞοϓ͕ൺֱత༰қͳ Android ͔Β؀ڥߏஙΛ͍͖ͯ͠·͢ɻ ϦϙδτϦͷηοτΞοϓ ͦΕͰ͸ Android ΞϓϦͷͨΊͷ؀ڥߏஙΛߦ͍·͠ΐ͏ɻຊষͰ͸චऀͷ޷ΈʹΑΓ Appium ΫϥΠΞϯτͷݴޠͱͯ͠ TypeScript Λ࢖༻͠ɺ·ͨɺύοέʔδϚωʔδϟʔʹ͸ Bun Λ࢖͍· ͢ɻຊষͰ͸جຊతͳػೳ͔͠࢖Θͳ͍ͨΊ͓޷ΈͷύοέʔδϚωʔδϟʔ͕͋Ε͹ͦͪΒΛ͝ར ༻͍͚ͨͩ·͢ɻ ·ͣϦϙδτϦΛ༻ҙ͠·͢ɻ bun init ࣍ʹύοέʔδΛ੔ཧ͠·͢ɻTypeScript ͸ peerDependencies ʹೖ͍ͬͯΔͨΊ devDepen- dencies ͱͯ͠ೖΕ௚͍ͯ͠·͢ɻ 14
  17. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.2 Android ΞϓϦͷͨΊͷ؀ڥߏங bun remove @types/bun

    typescript bun add -d typescript ts-node ·ͨɺࠓޙͷ։ൃΛݟਾ͑ϦϯλɺϑΥʔϚολΛ४උ͠·͢ɻຊষͰ͸͜ΕΒʹ Biome Λ࢖͏ ͜ͱʹ͠·͢*3ɻ·ͨɺBiome ͸Πϯετʔϧ࣌ʹ--exact Φϓγϣϯɺͭ·ΓΠϯετʔϧ࣌ͷ όʔδϣϯͰͱͲΊ͓ͯ͘͜ͱΛਪ঑͍ͯ͠·͢ɻͦͷͨΊຊষͰ΋࡞ۀ࣌ͷόʔδϣϯͷ··ͱͲ Ί͓ͯͨ͘ΊʹόʔδϣϯΛ௚઀ࢦఆ͠·͢ɻ bun add -d @biomejs/[email protected] Πϯετʔϧޙ͸ Biome ͷઃఆΛ௥Ճ͠·͢ɻ biome.json { "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json", "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }, "files": { "ignoreUnknown": false, "ignore": [] }, "formatter": { "attributePosition": "auto", "enabled": true, "formatWithErrors": false, "ignore": [], "indentStyle": "space", "indentWidth": 2, "lineEnding": "lf", "lineWidth": 80 }, "organizeImports": { "enabled": true }, "linter": { "enabled": true, "rules": { "recommended": true } *3 https://biomejs.dev/ja/ 15
  18. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.2 Android ΞϓϦͷͨΊͷ؀ڥߏங }, "javascript": {

    "formatter": { "arrowParentheses": "always", "bracketSameLine": false, "bracketSpacing": true, "jsxQuoteStyle": "double", "quoteProperties": "asNeeded", "semicolons": "always", "trailingCommas": "all" } }, "json": { "formatter": { "trailingCommas": "none" } } } npm εΫϦϓτ΋௥Ճ͠·͢ɻ package.json "devDependencies": { "@biomejs/biome": "1.9.3", "ts-node": "^10.9.2", "typescript": "^5.6.3" + }, + "scripts": { + "lint": "biome lint", + "format": "biome format", + "check": "biome check", + "lint:fix": "biome lint --write", + "format:fix": "biome format --write", + "check:fix": "biome check --write" } ϦϯτɺϑΥʔϚοτ͸ IDE ͰϓϥάΠϯΛೖΕϑΝΠϧอଘ࣌ʹΦʔτϑΥʔϚοτ͢Δઃఆ Λ௥Ճ͓ͯ͘͠ͱศརͰ͢ɻVisual Studio Code Ͱ͸ϦϙδτϦݻ༗ͷઃఆ΍ਪ঑ͷ֦ுػೳΛఆ ٛͰ͖·͢ɻͬͦ͘͞௥Ճ͓͖ͯ͠·͠ΐ͏ɻ .vscode/settings.json { "editor.codeActionsOnSave": { "quickfix.biome": "explicit", "source.organizeImports.biome": "explicit" 16
  19. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.2 Android ΞϓϦͷͨΊͷ؀ڥߏங }, "editor.defaultFormatter": "biomejs.biome",

    "editor.formatOnSave": true } .vscode/extensions.json { "recommendations": ["biomejs.biome"] } ηοτΞοϓ͕ऴΘͬͨΒ Visual Studio Code ΛϦϩʔυ͠ index.ts ͷηϛίϩϯΛ࡟আͯ͠อ ଘͯ͠Έ·͠ΐ͏ɻ͏·͍͚͘͹ηϛίϩϯ͕௥Ճ͞ΕϑΝΠϧ຤ඌʹվߦ͕ೖΔ͸ͣͰ͢ɻ͜ΕͰ Visual Studio Code Λ࢖͏ࡍϑΝΠϧฤूޙͷอଘ࣌ʹࣗಈͰमਖ਼͞ΕΔΑ͏ʹͳΓ·ͨ͠ɻ ͞Βʹ Visual Studio Code Ҏ֎Λ࢖͍ͬͯͯ΋मਖ਼͕ద༻͞ΕΔΑ͏ pre-commit ϑοΫͰࣗಈ मਖ਼͞ΕΔΑ͏ʹ͠·͠ΐ͏ɻGit ͷ Hook Λ؅ཧ͢ΔͨΊͷπʔϧͱͯ͠ Lefthook ΛΠϯετʔ ϧ͠·͢ɻ bun add -d lefthook@^1.7.18 Lefthook ͸ lefthook.yml ͱ͍͏ઃఆϑΝΠϧΛ࢖ͬͯ Git ͷ Hook Λ؅ཧͰ͖·͢ɻࠓճ͸ pre-commit ϑοΫΛ࢖ͬͯ biome Λ࣮ߦ͢ΔઃఆΛ௥Ճ͢ΔͨΊʹࣗಈ࡞੒͞Εͨ lefthook.yml Λ࣍ͷΑ͏ʹमਖ਼͠·͠ΐ͏ɻ lefthook.yml pre-commit: commands: biome: glob: "*.{js,ts}" run: bun check:fix --staged && git update-index --again Lefthook Ͱ͸{staged_files}ͱ͍͏ߏจͰεςʔδϯάʹ͋ΔϑΝΠϧΛϑΟϧλϦϯάͰ͖ ·͕͢ Biome ͷ--staged ΦϓγϣϯͰ΋ಉ͜͡ͱ͕ՄೳͰ͢ɻ͜ͷઃఆͰॏཁͳ఺͸࣮ߦεΫϦ ϓτͷ AND ԋࢉࢠͰ݁߹͍ͯ͠Δޙ൒෦෼Ͱ͢ɻ͜Ε͕ͳ͍ͱ Biome ʹΑΔमਖ਼͕ऴΘΔલʹί ϛοτ͞Εͯ͠·͏৔߹͕͋Γ·͢ɻ 17
  20. ୈ2ষ AppiumʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.2 AndroidΞϓϦͷͨΊͷ؀ڥߏங Lefthook ͷઃఆ͕ऴΘͬͨΒ࣍ͷΑ͏ʹ IDE Λ࢖Θͣ index.ts Λमਖ਼ͦ͠ͷ··ίϛοτͯ͠

    Έ·͠ΐ͏ɻ echo "console.log(’Hello via Bun!’)" > index.ts git add index.ts git commit -m "Try Lefthook" ‷ᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷ‸ ᴹ lefthook v1.7.18 hook: pre-commit ᴹ ‹ᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷ› ᴺ biome > $ biome check --write --staged Checked 1 file in 1397Ÿ s. Fixed 1 file. ᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷᴷ summary: (done in 0.11 seconds) v biome [main c1c4cab] Try Lefthook 1 file changed, 1 insertion(+), 1 deletion(-) ແࣄ Lefthook ͕ಈ͖·ͨ͠ɻมߋ಺༰ΛݟΔͱ Biome ʹΑΔमਖ਼ֻ͕͔͍ͬͯΔ͸ͣͰ͢ɻ Appium ͷηοτΞοϓ ͜͜·Ͱͷ४උ͕Ͱ͖ͨͱ͜ΖͰ Appium αʔόΛΠϯετʔϧ͠·͢ɻ bun add -d appium ࣍ʹυϥΠόΛΠϯετʔϧ͠·͢ɻ Appium ͸͞·͟·ͳϓϥοτϑΥʔϜ্Ͱಈ࡞͠·͢ɻυϥΠόͷϦετ͸࣍ͷίϚϯυͰ֬ೝ Ͱ͖·͢ɻ npx appium driver list v Listing available drivers - uiautomator2 [not installed] - xcuitest [not installed] - espresso [not installed] - mac2 [not installed] - windows [not installed] - safari [not installed] - gecko [not installed] 18
  21. ୈ2ষ AppiumʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.2 AndroidΞϓϦͷͨΊͷ؀ڥߏங - chromium [not installed] ͜͜Ͱ͸ Android

    ͷ؀ڥߏஙΛ͍ͨ͠ͷͰࣥච࣌఺Ͱ Android ༻ͷυϥΠόͱͯ͠ఏڙ͞Ε͍ͯ Δ UIAutomator2 ΛΠϯετʔϧ͠·͠ΐ͏ɻ npx appium driver install uiautomator2 υϥΠόͷΠϯετʔϧޙ͸ͦͷυϥΠόΛ࢖༻ͨ͠ࡍʹ͓͚Δ࣮ߦ؀ڥ͕੔͍ͬͯΔ͔Λ਍அͰ ͖·͢ʢͨͱ͑͹ UIAutomator2 Ͱ͋Ε͹ Android ޲͚ͷ࣮ߦ؀ڥ͕੔͍ͬͯΔ͔਍அͯ͘͠Ε· ͢ʣ ɻWARN ͸ͳͯ͘΋ྑ͍͕͋ΔͱΑΓྑ͍΋ͷɺError ͸ඞཁͳ΋ͷͱͯ͠਍அ݁Ռ͕දࣔ͞Ε ·͢ɻError ͕ग़͍ͯΔ৔߹͸ਖ਼ৗʹಈ͔ͳ͍ͨΊ਍அ͢Δ͜ͱΛ͓קΊ͠·͢ɻUIAutomator2 ͷ ৔߹͸ appium driver doctor uiautomator2 Ͱ਍அͰ͖·͢ɻ Ҏલ·Ͱ͸ಈ࡞؀ڥͷ਍அπʔϧͱͯ͠ఏڙ͞Ε͍ͯΔ Appium Doctor ͱ͍͏΋ͷ͕͋Γ· ͨ͠ɻnpm ্Ͱ͸ appium-doctor ͱ@appium/doctor ͕͋Γ·͕͢લऀ͸͢Ͱʹඇਪ঑Ͱޙऀ΋ Appium αʔό 2.4. θϩ͔Βඇਪ঑Ͱ͢ɻ୅ΘΓʹ࢖༻͢ΔυϥΠόʢ·ͨ͸ϓϥάΠϯʣ͝ͱʹί Ϛϯυ͕ఏڙ͞Ε͍ͯ·͢ͷͰͦͪΒΛ࢖͏ͱྑ͍Α͏Ͱ͢ɻ υϥΠόͷΠϯετʔϧ͕׬ྃͨ͠Β Appium ΫϥΠΞϯτΛΠϯετʔϧ͠·͠ΐ͏ɻຊষͰ ͸ Appium ΫϥΠΞϯτͱͯ͠ WebdriverIO Λ࢖༻͠·͢ɻWebdriverIO ͷυΩϡϝϯτ*4Ͱ͸ ςϯϓϨʔτ࡞੒༻ͷεΫϦϓτ͕঺հ͞Ε͍ͯ·͕ࣥ͢ච࣌఺Ͱ͸εΫϦϓτʹΑΓ࡞੒͞ΕΔς ϯϓϨʔτͰ͸ϒϥ΢β޲͚ͷςετͱͳͬͯ͠·͍ Appium ͷͨΊʹଟ͘ͷ࡞ۀ͕ඞཁͱͳͬͯ ͠·͍·͢ɻWebdriverIO ʹ͸ςϯϓϨʔτͷ΄͔ʹ΋໨తʹ߹Θͤͯର࿩ܗࣜͰηοτΞοϓΛ ਐΊΔ͜ͱ͕Ͱ͖Δ࣮ߦπʔϧ͕༻ҙ͞Ε͍ͯΔͷͰຊষͰ͸ͦͪΒΛ࢖͍·͢ɻCLI ΛΠϯετʔ ϧ͠ର࿩ܗࣜͷ࣮ߦπʔϧΛݺͼग़͠·͢ɻ bun i -d @wdio/cli@~9.2.1 bunx wdio config v A project named "appium-example" was detected at "/path/to/repos/appium-example", correct? yes v A project named "appium-example" was detected at "/path/to/repos/appium-example", correct? yes v What type of testing would you like to do? E2E Testing - of Web or Mobile Applications v Where is your automation backend located? On my local machine v Which environment you would like to automate? Mobile - native, hybrid and mobile web apps, on And v Which mobile environment you’ld like to automate? Android - native, hybrid and mobile web apps, t > using UiAutomator2 (https://www.npmjs.com/package/appium-uiautomator2-driver) v Which framework do you want to use? Mocha (https://mochajs.org/) v Do you want to use Typescript to write tests? yes *4 https://webdriver.io/docs/gettingstarted 19
  22. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.2 Android ΞϓϦͷͨΊͷ؀ڥߏங v Do you

    want WebdriverIO to autogenerate some test files? no v Which reporter do you want to use? spec v Do you want to add a plugin to your test setup? v Would you like to include Visual Testing to your setup? For more information see https://webdrive v Do you want to add a service to your test setup? appium v Do you want me to run ‘bun install‘ yes v Continue with Appium setup using appium-installer (https://github.com/AppiumTestDistribution/appi ͜ΕʹΑΓ࣍ͷΑ͏ͳมߋ͕ࣗಈͰద༻͞Ε·͢ɻ • WebdriverIO Λ࣮ߦ͢ΔͨΊͷґଘͱεΫϦϓτίϚϯυͷ௥Ճ • WebdriverIO ͷૢ࡞ର৅Λࢦఆ͢ΔͨΊͷઃఆϑΝΠϧ (wdio.conf.ts) ͷ௥Ճ ࣍ʹ WebdriverIO ্ͰઃఆϑΝΠϧ΍Φʔτϝʔγϣϯ༻ͷεΫϦϓτΛॻͨ͘Ίʹ TypeScript ͷઃఆϑΝΠϧ (tsconfig.json) Λ࣍ͷΑ͏ʹमਖ਼͠·͢ɻ΄΅͢΂ͯॻ͖׵͑ΔͷͰ diff Ͱ͸ͳ͘ ॻ͖׵͑ͨޙͷঢ়ଶΛࡌͤ·͢ɻ tsconfig.json { "compilerOptions": { "moduleResolution": "node", "module": "ESNext", "target": "es2022", "lib": [ "es2022" ], "types": [ "node", "@wdio/globals/types", "expect-webdriverio", "@wdio/mocha-framework", "@wdio/appium-service" ], "skipLibCheck": true, "noEmit": true, "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, "include": [ "test", "wdio.conf.ts" ] } 20
  23. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.2 Android ΞϓϦͷͨΊͷ؀ڥߏங ॾʑͷηοτΞοϓ͕ࡁΜͩΒ@wdio/cli Λ 8

    ܥʹμ΢ϯάϨʔυ͠·͢ɻ͜Ε͸ࣥච࣌఺ʹ͓͍ ͯ 9 ܥͩͱςετϥϯφʔ࣮ߦ࣌ʹΤϥʔ͕ൃੜ͢ΔͨΊͰ͢ɻ bun i -d @wdio/cli@^8.40.6 ͜͜·Ͱͷ४උ͕Ͱ͖Ε͹ηοτΞοϓ׬ྃ·Ͱ΋͏গ͠Ͱ͢ʂ ࠷ޙʹ WebdriverIO ্ͷεΫ ϦϓτͰ Appium αʔόΛհ͠σόΠε্Ͱ UIAutomator2 ʹΑΔૢ࡞ΛͰ͖ΔΑ͏ʹ͢ΔͨΊ ʹσόΠε΍ΞϓϦɺαʔόͷઃఆΛ capabilities ΁هड़͠·͢ɻͱ͍ͬͯ΋͍ͣΕ΋؀ڥม਺͔ ΒಡΈࠐ·ͤΔΑ͏ʹ͢Δ͚ͩͰ͢ɻ·ͨɺҰॹʹςετϥϯφʔ͕ಉ࣌ʹ্ཱ͕ͪΔ࠷େ਺Λ maxInstances Ͱ੍ݶ͓͖ͯ͠·͠ΐ͏ɻ wdio.conf.ts - maxInstances: 10, + maxInstances: 1, ... capabilities: [{ // capabilities for local Appium web tests on an Android Emulator platformName: ’Android’, - browserName: ’Chrome’, - ’appium:deviceName’: ’Android GoogleAPI Emulator’, - ’appium:platformVersion’: ’12.0’, + ’appium:deviceName’: process.env.ANDROID_DEVICE_NAME, + ’appium:appPackage’: process.env.ANDROID_APP_PACKAGE, + ’appium:appActivity’: process.env.ANDROID_APP_ACTIVITY, ’appium:automationName’: ’UiAutomator2’ }], ͦͯ͠ WebdriverIO ͕ಡΈࠐΉςετϥϯφʔΛࢦఆ͠·͢ɻ wdio.conf.ts specs: [ + "./test/specs/**/*.ts", - // ToDo: define location for spec files here ], ·ͨɺࠓճͷ໨త͸ςετͰ͸ͳ͘ಈ࡞ݕূͰ͢ɻͰ͖Δָ͚ͩʹಈ࡞ݕূΛ͢ΔͨΊʹҎԼͷઃ ఆ΋௥Ճ͠·͢ɻ 21
  24. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ wdio.conf.ts capabilities: [

    { // capabilities for local Appium web tests on an Android Emulator platformName: "Android", "appium:deviceName": process.env.ANDROID_DEVICE_NAME, "appium:appPackage": process.env.ANDROID_APP_PACKAGE, "appium:appActivity": process.env.ANDROID_APP_ACTIVITY, "appium:automationName": "UiAutomator2", + "appium:noReset": true, + "appium:autoGrantPermissions": true, }, ], appium:noReset ͸ςετ࣮ߦ࣌ʹΞϓϦΛΫϦʔϯΠϯετʔϧ͠ͳ͍ͨΊͷઃఆͰ͢ɻtrue Λࢦఆ͢Δ͜ͱͰ͢ͰʹΞϓϦ͕Πϯετʔϧ͞Ε͍ͯͨ৔߹ʹ͸ΠϯετʔϧࡁΈͷΞϓϦΛ࢖͏ Α͏ʹͳΓ·͢ɻappium:autoGrantPermissions ͸ςετ࣮ߦ࣌ʹٻΊΒΕΔݖݶΛࣗಈͰঝೝ ͠·͢ɻ ͜ΕͰ४උ͸׬ྃͰ͢ʂ 2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ͔͜͜Β͸͍Α͍Αಈ࡞ݕূͷͨΊͷॲཧΛॻ͍͍͖ͯ·͕͢ɺͦͷલʹ؀ڥม਺ʹಀͨ͠ύϥ ϝʔλΛࢦఆ͢Δඞཁ͕͋Γ·͢ɻadb Ͱௐ΂Δ͜ͱ͕Ͱ͖·͢ɻadb ͸ Android ։ൃͷͨΊͷ ηοτΞοϓͷաఔͰΠϯετʔϧ͞Ε͍ͯΔ͔ͱࢥ͍·͢ͷͰΠϯετʔϧखॱ͸͜͜Ͱ͸ׂѪ͠ ·͢ɻ ·ͣσόΠε ID Λௐ΂·͠ΐ͏ɻAndroid ΤϛϡϨʔλ͕͋Β͔͡ΊΠϯετʔϧ͞Ε͍ͯΔঢ় ଶͰҎԼͷίϚϯυΛ࣮ߦ͢Δͱ࣍ͷΑ͏ʹ֬ೝ͕Ͱ͖·͢ɻ adb devices List of devices attached emulator-5554 device ͜ͷྫͰ͸ emulator-5554 ͕σόΠε ID ͱͳΓ·͢ɻ ࣍ʹΤϛϡϨʔλ্ͰΠϯετʔϧ͞Ε͍ͯΔΞϓϦͷύοέʔδ໊Λௐ΂·͢ɻ΋ͪΖΜ͜ͷΑ ͏ͳ͜ͱΛ͠ͳͯ͘΋෼͔ΔͷͰ͋Ε͹௚઀ࢦఆ͢Ε͹Α͍ͷͰ͜ͷ࡞ۀ͸ෆཁͰ͢ɻDEVICE_ID ෦෼͸ઌ΄Ͳͷૢ࡞Ͱऔಘ࣮ͨ͠ࡍͷσόΠε ID Λࢦఆ͠·͢ɻ 22
  25. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ adb -s DEVICE_ID

    shell pm list packages package: ͔ΒޙΖͷจࣈྻ͕ύοέʔδ໊ͱͳΓ·͢ɻ͍Ζ͍ΖͳΞϓϦ͕දࣔ͞Ε·͕͢͜ͷ த͔Βࣗ਎͕ૢ࡞͍ͨ͠ΞϓϦΛ୳͠·͠ΐ͏ɻσόΠε ID ͱύοέʔδ໊͕෼͔ͬͨΒΞϓϦͷ ΤϯτϦϙΠϯτΛௐ΂·͢ɻͪ͜Β΋͢Ͱʹ஌͍ͬͯΕ͹ෆཁͰ͢ɻPACKAGE_NAME ͸࣮ࡍ ͷύοέʔδ໊Λࢦఆ͍ͯͩ͘͠͞ɻ adb -s DEVICE_ID shell dumpsys package PACKAGE_NAME ͍Ζ͍Ζͱදࣔ͞Ε·͕͢ android.intent.action.MAIN ʹ͋Δͷ͕ΤϯτϦϙΠϯτͰ͢ɻ ۩ମతʹ͸ PACKAGE_NAME/ACTIVITY_NAME ͱ͍͏ϑΥʔϚοτͷ ACTIVITY_NAME ෦෼͕Τ ϯτϦϙΠϯτͱͳ͍ͬͯ·͢ɻ ͜͜·Ͱʹಘͨ৘ใΛͦΕͧΕ؀ڥม਺ʹࢦఆ͠·͢ɻ .env ANDROID_DEVICE_NAME=σόΠεID ANDROID_APP_PACKAGE=ύοέʔδ໊ ANDROID_APP_ACTIVITY=ΤϯτϦʔϙΠϯτ ͯ͞ɺ͜͜·Ͱ४උ͕Ͱ͖Ε͹εΫϦϓτ͑͋͞Ε͹ςετϥϯφʔ͕ಈ͖·͢ɻ࣮ࡍʹ͸Կ΋͠ ͳ͍ςετΛॻ͍ͯࢼͯ͠Έ·͠ΐ͏ɻຊষͰ͸ςεςΟϯάϑϨʔϜϫʔΫʹ Mocha*5Λ࢖༻͠ ͍ͯ·͕͢ Mocha ͷৄ͍͠ղઆ͸͜͜Ͱ͸ׂѪ͠·͢ɻ͜͜Ͱಛච͢΂͖఺͕͋Δͱ͢Ε͹ɺͦΕ ͸ࣗ਎Ͱબ୒ͨ͠ςετϥϯφʔͰීஈ࢖͍׳Εͨݴޠ΍ςεςΟϯάϑϨʔϜϫʔΫͰॲཧ͕ॻ͚ Δͱ͍͏఺Ͱ͢ɻ mkdir -p test/specs/ touch test/specs/e2e.ts *5 https://mochajs.org/ 23
  26. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ test/specs/e2e.ts describe("Automation", ()

    => { it("takes screenshots in all languages", async () => {}); }); bun run wdio ͏·͍͚͘͹࣮ࡍʹΞϓϦ͸ಈ͔ͳ͍Ͱ͕͢ςετ͕ύεͨ͠ͱදࣔ͞ΕΔ͸ͣͰ͢ɻ ͯ͞ɺ͜͜·Ͱ͸ΞϓϦΛಈ͔ͨ͢ΊͷηοτΞοϓͰ͕ͨ͠ɺ͔͜͜Β͸࣮ࡍͷΞϓϦΛૢ࡞͢ ΔͨΊͷεΫϦϓτΛॻ͘ඞཁ͕͋Γ·͢ɻ ΞϓϦ͸͞·͟·ͳύʔπͰߏ੒͞Ε͍ͯ·͕͢ɺAppium ܦ༝ͰΞϓϦΛૢ࡞͢ΔͨΊʹ͸ UI ্ͰಛఆͷཁૉΛࣝผ͢ΔͨΊʹͲͷΑ͏ͳϩέʔλ͕࢖͑Δ͔ௐ΂ͳ͚Ε͹ͳΓ·ͤΜɻͦ͜Ͱ Appium Inspector ͱ͍͏΋ͷΛ࢖͍·͢ɻ Appium Inspector Appium Inspector ͸ΞϓϦͰཁૉ͝ͱʹ࢖༻ՄೳͳϩέʔλͱͦͷϩέʔλͰཁૉΛࣝผ͢Δͨ Ίͷ஋Λௐ΂Δ͜ͱͷͰ͖Δ GUI πʔϧͰɺ୺తʹݴ͑͹ Appium ΫϥΠΞϯτʹศརͳ GUI ͕ ෇͍ͨ΋ͷͰ͢ɻGUI Λհͯ͠ΞϓϦΛૢ࡞ͨ͠Γϩέʔλ΍ࣝผ͢ΔͨΊͷ஋Λௐ΂Δ͜ͱ͕Ͱ ͖ΔͨΊ Appium Λ࢖͏্Ͱ΄΅͔ܽͤͳ͍πʔϧͱݴ͑·͢ɻWeb ্Ͱ࣮ߦͰ͖ΔαΠτ΋ެ։ ͞Ε͍ͯ·͕͢ຊষͰ͸σεΫτοϓΞϓϦΛΠϯετʔϧͯ͠࢖͍·͢*6ɻ Πϯετʔϧ͕׬ྃͨ͠Β্ཱͬͦͪ͛͘͞·͢ɻ ্ཱͪ͛Δͱ Capability Builder ͷλϒ͕දࣔ͞Εͨঢ়ଶͱͳ͍ͬͯ·͢ɻ͖͞΄Ͳ؀ڥม਺΁ ઃఆͨ͠஋΋ؚΊͨ Capability ͷઃఆΛͦͷ··ࢦఆ͢Ε͹ OK Ͱ͢ɻ *6 https://appium.github.io/appium-inspector/latest/quickstart/installation/ 24
  27. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ਤ 2.1: Appium

    Inspector ઃఆ׬ྃޙɺStart Session ϘλϯΛԡͤ͹ಈ͖·͢ɻ͕ɺͦͷલʹ Appium αʔό্ཱ͚ͩͪ͛ ͓͖ͯ·͠ΐ͏ɻAppium αʔό͸࣍ͷίϚϯυͰ্ཱͪ͛Δ͜ͱ͕ՄೳͰ͢ɻ bunx appium server αʔό্ཱ͕͕ͪͬͨΒ Appium Inspector ͷ Start Session ϘλϯΛԡͯ͠Έ͍ͯͩ͘͞ɻ͏· ͍͚͘͹ΤϛϡϨʔλͰࠓදࣔ͞Ε͍ͯΔը໘͕ Appium Inspector ্Ͱ΋දࣔ͞Ε͍ͯΔ͜ͱ͕෼ ͔Δ͔ͱࢥ͍·͢ɻ 25
  28. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ਤ 2.2: Android

    ΤϛϡϨʔλ ͜ͷঢ়ଶͰը໘ࠨʹදࣔ͞Ε͍ͯΔΤϛϡϨʔλ্Λλοϓ͢Δͱλοϓͨ͠ཁૉΛಛఆ͢ΔͨΊ ʹ࢖༻Ͱ͖Δϩέʔλͱࣝผ͢ΔͨΊͷ஋ͷηοτΛௐ΂Δ͜ͱ͕Ͱ͖·͢ɻ·ͨɺΤϛϡϨʔλ্ ෦ʹ͋Δ੺࿮Ͱࣔͨ͠ϘλϯΛΦϯʹͨ͠ঢ়ଶͰ͸λοϓ΍εϫΠϓͳͲΤϛϡϨʔλ΁ Appium Inspector Λ઀ଓͨ͠··ը໘ૢ࡞͕Ͱ͖ΔͨΊҰ౓ʹෳ਺ͷը໘ʹ͋ΔཁૉΛௐ΂Δ͜ͱ͕Մೳ Ͱ͢ɻ 26
  29. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ Ͱ͸࣍͸࣮ࡍʹ໨తͷΞϓϦΛ্ཱͪ͛ͨঢ়ଶͰݟͯΈ·͠ΐ͏*7ɻͦͯ͠໨తͷૢ࡞Λߦ͏ͨΊ ʹը໘Լ෦ʹදࣔ͞ΕΔλϒͷதͰҰ൪ӈʹ͋ΔՈ଒ઃఆλϒΛ Appium

    Inspector ্Ͱλοϓ͠ϩ έʔλΛ֬ೝ͠·͢ɻ ਤ 2.3: ΈͯͶ Android ΞϓϦͷΞϧόϜը໘ λοϓ͢Δͱը૾ͷ௨Γ Appium Inspector ը໘ӈଆʹબ୒ͨ͠ཁૉʹؔ͢Δ৘ใ͕දࣔ͞Ε· ͢ɻ͜ͷ͏ͪ੺࿮Ͱғͬͨ෦෼͕ϩέʔλʹؔ͢Δ৘ใͱͳ͓ͬͯΓɺ͜ΕΒ͕ςετϥϯφʔͰཁ ૉΛಛఆ͢ΔͨΊͷબ୒ࢶͱͳ͍ͬͯ·͢ɻ͍ͣΕͰ΋໰୊ͳ͘ཁૉΛಛఆͰ͖·͕͢ Accessibility ID ͸ଟݴޠରԠ͍ͯ͠ΔΞϓϦͩͱݴޠ͝ͱʹ஋͕ҟͳΓ΍΍࢖͍ͮΒ͍͔΋͠Ε·ͤΜɻ· ͨɺXPath ͸ཁૉ͕૬ରతʹද͞Ε͍ͯΔ͜ͱ΋͋ΔͨΊ஫ҙ͕ඞཁͰ͢ɻ࡞ΓʹΑͬͯΞϓϦ ͷσʔλ΍ঢ়ଶʹΑͬͯҟͳΔ஋ͱͳΔ͜ͱ͕͋Γ·͢ɻAndroid UIAutomator ͸ͦͷ໊ͷ௨Γ UIAutomator ͷ UiSelector Λ࢖༻ͯ͠ཁૉΛಛఆ͢ΔͨΊͷίϚϯυΛ࣮ߦ͠·͢ɻID ͸࠷΋ແ ೉ͳબ୒ࢶͱͳΓ·͢ɻAndroid ͷ৔߹ɺPACKAGE_NAME:id/IDENTIFIER_STRING ͱ͍͏ϑΥʔ Ϛοτͱͳ͍ͬͯΔͨΊ IDENTIFIER_STRING ෦෼ͷઌ಄ʹ#Λ෇͚ͯࢦఆ͠·͢ɻ *7 ˞ ࢽ໘ͷ౎߹্ɺ։ൃ༻ͷΈͯͶΞϓϦΛΠϯετʔϧ͠ΞΧ΢ϯτͷηοτΞοϓ͕ࡁΜͰ͍ΔલఏͰਐΊ·͢ɻಉ ͡Α͏ʹਐΊΔʹ͸ΈͯͶΞϓϦΛΠϯετʔϧ͠ηοτΞοϓΛࡁ·ͤΔඞཁ͕͋Γ·͢ɻ 27
  30. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ͨͱ͑͹ઃఆΞϓϦͷτοϓը໘ʹදࣔ͞ΕΔΞόλʔͷ ID ͸

    com.Android.settings.id/ac count_avatar ͱͳ͓ͬͯΓɺ͜ͷ৔߹͸#account_avatar ͕ ID ͱͳΓ·͢ɻ ਤ 2.4: Android ͷઃఆΞϓϦ ͳ͓ɺ࡞ΓʹΑͬͯ͸࢖༻Ͱ͖Δϩέʔλ͕গͳ͘ɺ๬·͍͠બ୒ࢶ͕ͳ͍ͨΊʹଥڠΛͤ͟ΔΛ ಘͳ͍৔߹΋͋ΔͰ͠ΐ͏ɻͦͷ৔߹͸ಉ͡ૢ࡞͕ҟͳΔঢ়ଶͰ΋࠶ݱͰ͖Δ͔܁Γฦ֬͠ೝ͓ͯ͠ ͘ͱ҆৺Ͱ͢ʢ҆ఆ͠ͳ͍৔߹͸Կ͔͠Βͷ޻෉͕ඞཁʹͳΔ͔΋͠Ε·ͤΜʣ ɻ ಈ࡞ݕূͷࣗಈԽ ͦΕͰ͸ௐ΂ͨ݁ՌΛͬͦ͘͞ίʔυʹ൓ө͠·͢ɻ࣍ͷΑ͏ʹॻ͖׵͑·͢ɻ test/specs/e2e.ts describe("Automation", () => { it("takes screenshots in all languages", async () => { await $(process.env.ANDROID_SETTING_TAB_ID).click(); }); }); 28
  31. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ $͸ WebdriverIO ͕ఏڙ͍ͯ͠ΔίϚϯυ*8ͱͳ͓ͬͯΓҾ਺ʹ౉͞ΕͨจࣈྻͰࣝผՄೳͳཁ

    ૉΛฦ͠·͢ɻͦͯ͠ฦ͞Εͨཁૉʹରͯ͠ݺͼग़͍ͯ͠Δ click ίϚϯυ*9͸ϞόΠϧΞϓϦͰ ͸λοϓʹ૬౰͠·͢ɻ ্هͷྫͰ͸$ίϚϯυͷҾ਺ʹΘ͟Θ͟؀ڥม਺Λ࢖༻͍ͯ͠·͕͢ಛผͳཧ༝͕ͳ͚Ε͹௚઀ จࣈྻΛهड़ͨ͠ํ͕෼͔Γ΍͍͔͢ͱࢥ͍·͢ɻ Ͱ͸͜ͷίʔυΛಈ͔ͯ͠Έ·͢ɻAppium Inspector ͷͨΊʹ্ཱ͓͍ͪ͛ͯͨ Appium αʔ ό͸ͦͷ··ىಈ͍ͯͯ͠΋མͱ͍ͯͯ͠΋໰୊ͳ͘ಈ͖·͢ɻ bun run wdio ͯ͞ɺ͜ΕͰՈ଒ઃఆը໘Λ։͚·ͨ͠ɻ࣍͸Ո଒ઃఆը໘্ʹ͋ΔݴޠઃఆΛλοϓ͠ݴޠΛ੾ Γସ͑Δඞཁ͕͋Γ·͢ɻ͔͠͠ݴޠઃఆ͸Ո଒ઃఆը໘ͷϑΝʔετϏϡʔͰ͸දࣔ͞Ε͍ͯ·ͤ Μɻ͜ͷঢ়ଶͰ͸ϩέʔλΛ࢖༻ͯ͠ཁૉΛಛఆͰ͖ͳ͍ͨΊεΫϩʔϧΛ͢Δඞཁ͕͋Γ·͢ɻͦ ͜ͰεΫϩʔϧ͢ΔͨΊͷίʔυΛ௥Ճ͠·͢ɻ ͜ͷ··ίʔυΛॻ͖ਐΊͯ΋Α͍Ͱ͕͢ຊষͰ͸ WebdriverIO ͕ਪ঑͍ͯ͠ΔϖʔδΦϒδΣ ΫτύλʔϯΛ౿ऻͯ͠Έ·͢ɻৄ͘͠͸υΩϡϝϯτ*10Λࢀর͍ͩ͘͞ɻ ·ͣ test σΟϨΫτϦͷԼʹ pageobjects σΟϨΫτϦΛ࡞Γ·͢ɻͦͯ͠શϖʔδڞ௨ͷॲཧ Λهड़͢Δ page ΫϥεΛఆٛ͢ΔͨΊʹ page.ts Λ࡞੒͠·͢ɻ mkdir test/pageobjects touch test/pageobjects/page.ts ͦͯ͠ઌ΄Ͳॻ͍ͨॲཧ΋ page.ts ΁Ҡಈͤ͞·͠ΐ͏ɻ test/pageobjects/page.ts export abstract class Page { async goToSetting() { await this.settingTab.click(); } private get settingTab() { return $(process.env.ANDROID_SETTING_TAB_ID); } *8 https://webdriver.io/docs/api/browser/$ *9 https://webdriver.io/docs/api/element/click/ *10 https://webdriver.io/docs/pageobjects/ 29
  32. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ } த਎͸ۭͰ͕͢ΞϧόϜը໘ͷϖʔδΦϒδΣΫτ΋༻ҙ͠·͢ɻ test/pageobjects/album.page.ts

    import { Page } from "./page.ts"; class AlbumPage extends Page {} export const albumPage = new AlbumPage(); ͦͯ͠ςετΛॻ͖׵͑·͢ɻ test/specs/e2e.ts import { albumPage } from "../pageobjects/album.page.ts"; describe("Automation", () => { it("takes screenshots in all languages", async () => { await albumPage.goToSetting(); }); }); Ͱ͸εΫϩʔϧ͢ΔॲཧΛॻ͍͍͖ͯ·͢ɻεΫϩʔϧ͸શը໘Ͱ࢖༻͢ΔͷͰ Page Ϋϥεʹ࣮ ૷͠·͢ɻ test/pageobjects/page.ts protected async scrollDownTo(text: string, maxAttempts = 10) { // 1 const scrollableElementId = await $( "android=new UiScrollable(new UiSelector().scrollable(true))", ).elementId; // 2 let attempt = 1; while (true) { if (maxAttempts < attempt) { throw Error( ‘ࢦఆͨ͠จࣈྻͰಛఆՄೳͳཁૉ͕ݟ͔ͭΓ·ͤΜͰͨ͠ɻจࣈྻ: ${text}‘, ); } if (attempt <= 3) { await driver.execute("mobile: scrollGesture", { 30
  33. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ elementId: scrollableElementId, direction:

    "down", percent: 10, }); } else { // 3 const direction = attempt % 2 === 0 ? "up" : "down"; await driver.execute("mobile: scrollGesture", { elementId: scrollableElementId, direction, percent: 10, }); } // 4 await new Promise((resolve) => setTimeout(resolve, 1000)); // 5 if (await this.elementBy(text).isDisplayed()) { break; } attempt++; } } protected elementBy(text: string) { return $(‘android=new UiSelector().textMatches("${text}")‘); } ͜Ε͸ͬ͘͟Γղઆ͢Δͱཁૉ͕ݟ͔ͭΔ·ͰεΫϩʔϧμ΢ϯ͢Δॲཧͱͳ͍ͬͯ·͢ɻ1 ͱί ϝϯτʹهड़͍ͯ͠Δ෦෼͸ Android Ͱ࣮֬ʹεΫϩʔϧ͢ΔͨΊʹ UiScrollable ΦϒδΣΫτΛ ੜ੒͍ͯ͠Δॲཧͱͳ͍ͬͯ·͢ɻ͜ΕΛεΫϩʔϧδΣενϟʹεΫϩʔϧՄೳͳཁૉͱͯ͠౉͢ ͜ͱͰ Android ͰεΫϩʔϧ͕࣮֬ʹॲཧ͞Ε·͢ɻ2 ͸ϧʔϓͱͳ͓ͬͯΓཁૉ͕ݟ͔ͭΔ·Ͱ εΫϩʔϧΛ܁Γฦ͢ॲཧͰ͢ɻ3 ͸ཁૉΛ௨Γա͗ͯ͠·͍ͬͯΔՄೳੑΛՃຯ͠εΫϩʔϧΛߦ ͖དྷ͍ͤͯ͞·͢ɻ4 ͸ Appium ্Ͱཁૉ͕ݕग़ՄೳʹͳΔલʹཁૉݕग़ॲཧΛͯ͠͠·Θͳ͍Α ͏ʹҰఆ࣌ؒͷ஗ԆΛೖΕ͍ͯ·͢ɻ5 ͸৽ͨʹ௥Ճͨ͠ཁૉݕग़ॲཧΛ࢖ͬͯεΫϩʔϧΛதஅ͞ ͤΔॲཧͰ͢ɻAndroid Ͱ͸ UiSelector ͷ textMatches*11Λ࢖͏͜ͱͰࢦఆͨ͠จࣈྻʹϚον͢ ΔจࣈྻΛ࣋ͭཁૉΛ୳ͤ·͢ɻ εΫϩʔϧ͢ΔॲཧΛॻ͍ͨΒͲ͜·ͰεΫϩʔϧ͢Ε͹Α͍͔Λಛఆ͠·͢ɻAppium Inspec- tor Ͱௐ΂·͠ΐ͏ɻ *11 https://developer.android.com/reference/androidx/test/uiautomator/UiSelector#textMatches(java.lang.String) 31
  34. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ਤ 2.5: ݴޠઃఆ

    ͜ͷը૾Ͱ͸ݴޠઃఆͷηϧΛද͢ཁૉͰཁૉΛಛఆ͢Δ͜ͱ͕ࠔ೉ͩͬͨͨΊηϧͷதʹ͋Δจ ࣈྻΛௐ΂͍ͯ·͢ɻ͜ͷΑ͏ʹ͋ΔཁૉΛಛఆ͢Δͷ͕೉͍͠৔߹Ͱ΋ Appium Inspector ্Ͱࢦ ఆͰ͖ΔཁૉͰ͋Ε͹ͦͪΒͰ୅ସՄೳͰ͢ɻ ͯ͞ɺ͜ͷཁૉΛλοϓ͢Ε͹ݴޠ͸੾ΓସΘΔͷͰ͠ΐ͏͔ɻAppium Inspector ্Ͱૢ࡞Λ੾ Γସ࣮͑ͯࡍʹಈ͔ͯ͠Έ·͢ɻ͢Δͱ࣍͸ݴޠબ୒༻ͷϞʔμϧ͕දࣔ͞ΕΔ͜ͱ͕෼͔Γ·͢ɻ ࠶౓ཁૉݕग़ʹૢ࡞Λ੾Γସ͑ɺ͜͜Ͱ͸ϑϥϯεޠʹ͢ΔͨΊͷϩέʔλͱ஋Λ֬ೝ͠·͢ɻ 32
  35. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ਤ 2.6: ݴޠબ୒Ϟʔμϧ

    ͦͯ͠·ͨૢ࡞Λ੾Γସ͑ͯϑϥϯεޠΛλοϓͯ͠Έ·͠ΐ͏ɻ͢Δͱࠓ౓͸֬ೝμΠΞϩά͕ දࣔ͞Ε·͢ɻ ਤ 2.7: ݴޠ੾Γସ͑֬ೝμΠΞϩά μΠΞϩά্ͷ OK Ϙλϯͷϩέʔλͱ஋Λ֬ೝ͠ OK ϘλϯΛԡ͠·͢ɻ͜ΕͰݴޠ͕੾Γସ ΘΔ͜ͱ͕֬ೝͰ͖·ͨ͠ɻ 33
  36. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ͦΕͰ͸͜͜·Ͱͷૢ࡞ΛࣗಈԽ͠·͢ɻ·ͣՈ଒ઃఆը໘ͷϖʔδΦϒδΣΫτΛ௥Ճ͠·͢ɻ test/pageobjects/setting.page.ts import

    { Page } from "./page.ts"; class SettingPage extends Page { async switchToFrench() { await this.scrollDownTo(process.env.ANDROID_LANGUAGE_SETTING_TEXT); await this.languageSetting.click(); await this.frenchOnLanguageModal.click(); await this.okButtonOnDialog.click(); } private get languageSetting() { return this.elementBy(process.env.ANDROID_LANGUAGE_SETTING_TEXT); } private get frenchOnLanguageModal() { return this.elementBy(process.env.FRENCH_TEXT); } } export const settingPage = new SettingPage(); ͯ͞ɺ͜͜Ͱઌ΄Ͳ Page Ϋϥεʹ࣮૷ͨ͠ scrollDownTo ΍ elementBy Λݺͼग़͍ͯ͠·͕͢ Ҿ਺ͷܕ͕߹Θͳ͍ͨΊ IDE Λ࢖͍ͬͯΔ৔߹Τϥʔ͕දࣔ͞Ε͍ͯΔ͔ͱࢥ͍·͢ɻͦ͜Ͱ؀ڥ ม਺ʹಠࣗͷܕΛఆٛ͠·͢ɻ env.d.ts declare global { namespace NodeJS { interface ProcessEnv { readonly ANDROID_DEVICE_NAME: string; readonly ANDROID_APP_PACKAGE: string; readonly ANDROID_APP_ACTIVITY: string; readonly ANDROID_SETTING_TAB_ID: string; readonly ANDROID_LANGUAGE_SETTING_TEXT: string; readonly ANDROID_OK_BUTTON_XPATH: string; readonly FRENCH_TEXT: string; } } } export {}; 34
  37. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ͦͯ͜͠ͷܕ͕ಡΈࠐ·ΕΔΑ͏ʹ tsconfig.json ͷ

    include ΦϓγϣϯΛ࣍ͷΑ͏ʹमਖ਼͠·͢ɻ tsconfig.json "include": ["env.d.ts", "test", "wdio.conf.ts"] ͜ͷঢ়ଶͰ IDE ΛϦϩʔυ͢Ε͹ಠࣗʹ௥Ճͨ͠؀ڥม਺͕ఆٛͨ͠ܕͰղऍ͞Ε·͢ɻ SettingPage Ϋϥεͷ switchToFrench ಺ͰݺΜͰ͍Δ okButtonOnDialog ͸ Page Ϋϥεʹఆٛ ͠·͢ɻ test/pageobjects/page.ts protected get okButtonOnDialog() { return $(process.env.ANDROID_OK_BUTTON_XPATH); } ͦͯ͜͜͠·ͰͷॲཧΛςετʹ൓ө͠·͢ɻ test/specs/e2e.ts import { albumPage } from "../pageobjects/album.page.ts"; import { settingPage } from "../pageobjects/setting.page.ts"; describe("Automation", () => { it("takes screenshots in all languages", async () => { await albumPage.goToSetting(); await settingPage.switchToFrench(); }); }); .env Λ։͍ͯ๨Εͣʹ؀ڥม਺Λઃఆͨ͋͠ͱɺΞϓϦΛΞϧόϜը໘ʹ໭ͨ͠ঢ়ଶͰςετΛ ࣮ߦ͠·͢ɻ͢ΔͱՈ଒ઃఆλϒΛλοϓ͠ઃఆը໘Ͱը໘Λݴޠઃఆ·ͰεΫϩʔϧ͠ݴޠઃఆΛ λοϓͯ͠ϑϥϯεޠ΁੾Γସ͑Δૢ࡞͕֬ೝͰ͖ΔΑ͏ʹͳΓ·ͨ͠ɻ ଓ͚ͯετΞը໘Λ։͘ॲཧΛ௥Ճ͠·͢ɻ͜Ε·Ͱಉ༷ʹ Appium inspector Λ࢖ͬͯཁૉ͕ࣝ ผՄೳͳϩέʔλͱ஋Λ֬ೝͦ͠ΕΛίʔυʹ൓өͤ͞·͢ɻ env.d.ts + readonly ANDROID_STORE_TAB_ID: string; 35
  38. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ test/pageobjects/page.ts export abstract

    class Page { + async goToStore() { + await this.storeTab.click(); + } ... + private get storeTab() { + return $(process.env.ANDROID_STORE_TAB_ID); + } } test/specs/e2e.ts import { albumPage } from "../pageobjects/album.page.ts"; import { settingPage } from "../pageobjects/setting.page.ts"; describe("Automation", () => { it("takes screenshots in all languages", async () => { await albumPage.goToSetting(); await settingPage.switchToFrench(); + await albumPage.goToStore(); }); }); ετΞը໘Λ։͍ͨΒεΫϦʔϯγϣοτΛอଘ͠·͢ɻWebdriverIO ʹ͸εΫϦʔϯγϣοτ Λอଘ͢ΔͨΊͷίϚϯυ΋༻ҙ͞Ε͍ͯ·͢ͷͰͦͪΒΛ࢖͍·͢ɻը໘ભҠॲཧͷ௚ޙͩͱඳը ͕ؒʹ߹Θͳ͍ͨΊεϦʔϓॲཧΛϝιουԽ͠ݺͼग़͠·͢ɻ·ͨɺ͍ͭͰʹεΫϩʔϧதͷε Ϧʔϓॲཧ΋ॻ͖׵͑·͢ɻ test/pageobjects/page.ts + async saveScreenshot(language: string) { + await this.sleep(); + await driver.saveScreenshot( + ‘./tmp/${this.platform.toLowerCase()}-${language}.png‘, + ); + } + + protected get platform() { + return driver.isAndroid ? "Android" : "iOS"; + } + protected async sleep(ms = 1000) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } ... 36
  39. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳2.3 Android Ͱಈ࡞ݕূΛࣗಈԽ͢Δ - await new

    Promise((resolve) => setTimeout(resolve, 1000)); + await this.sleep(); εΫϦʔϯγϣοτͰอଘ͢ΔϑΝΠϧͷ໊લʹ͸ݴޠͷ΄͔ɺϓϥοτϑΥʔϜ΋ࢦఆ͢Δ͜ͱ Ͱݴޠ x ϓϥοτϑΥʔϜͰεΫϦʔϯγϣοτΛอଘͰ͖ΔΑ͏ʹ͍ͯ͠·͢ɻϓϥοτϑΥʔ Ϝͷ஋͸ driver.capabilities ܦ༝Ͱ wdio.conf.ts Ͱఆٛͨ͠஋ΛऔಘͰ͖·͕͢ܕΞαʔγϣ ϯ͠ͳ͚Ε͹ͳΓ·ͤΜɻࠓճͷΑ͏ʹ 2 ͭͷϓϥοτϑΥʔϜͷΈͰ͋Ε͹ isAndroid ِ͕ͱͳ Δ৔߹͸ iOS ͱͯ͠͠·ͬͯ΋໰୊ͳ͍ͨΊ৽ͨʹ௥Ճͨ͠ platform Ͱ͸ͦͷΑ͏ͳॲཧͱ͍ͯ͠ ·͢ɻ Ͱ͸࠷ޙʹςετʹεΫϦʔϯγϣοτΛอଘ͢ΔॲཧΛ௥Ճͯ͠ಈ࡞Λ֬ೝ͠·͢ɻετΞը໘ Ͱૢ࡞Λߦ͏ͨΊ StorePage Ϋϥε΋༻ҙ͓͖ͯ͠·͢ɻ test/pageobjects/store.page.ts import { Page } from "./page.ts"; class StorePage extends Page {} export const storePage = new StorePage(); test/specs/e2e.ts import { albumPage } from "../pageobjects/album.page.ts"; import { settingPage } from "../pageobjects/setting.page.ts"; +import { storePage } from "../pageobjects/store.page.ts"; describe("Automation", () => { it("takes screenshots in all languages", async () => { await albumPage.goToSetting(); await settingPage.switchToFrench(); await albumPage.goToStore(); + await storePage.saveScreenshot("french"); }); }); ࣮ߦલʹ.gitignore Ͱ tmp σΟϨΫτϦΛ࡞੒͠ Git ͷ௥੻ର৅͔Βআ֎͓͖ͯ͠·͠ΐ͏ɻ mkdir tmp echo tmp >> .gitignore bun run wdio 37
  40. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.4 iOS ΞϓϦͷͨΊͷ؀ڥߏங ͏·͘ಈ͍͍ͯΕ͹ tmp/android-french.png

    ʹϑϥϯεޠͰ։͍ͨετΞը໘ͷεΫϦʔϯ γϣοτ͕อଘ͞Ε·͢ɻ ͜ΕͰ Android ͸׬੒ͱ͠·͢ɻ 2.4 iOS ΞϓϦͷͨΊͷ؀ڥߏங iOS Ͱ͸ϦϙδτϦͷ४උ࡞ۀ͸΋ͪΖΜͷ͜ͱ Appium αʔό΋͢ͰʹΠϯετʔϧࡁΈͳͷ Ͱ Appium υϥΠόΛΠϯετʔϧ͠·͢ɻ bunx appium driver install xcuitest ͜ͷͱ͖ύοέʔδϚωʔδϟʔ͸Ϣʔβʔͷ؀ڥʹ͔͔ΘΒͣ npm Ͱ࣮ߦ͞Ε·͢ɻ΍΍͜͠ ͍ͷͰ bun Ͱ࠶Πϯετʔϧ͠ package-lock.json ʹ͍ͭͯ͸࡟আ͓͖ͯ͠·͠ΐ͏ɻ bun i rm package-lock.json υϥΠόͷηοτΞοϓ iOS ͰͷΦʔτϝʔγϣϯʹ͸ Android ʹ͸ͳ͍खॱ͕ඞཁʹͳΓ·͢ɻυΩϡϝϯτ*12ʹ͋Δ ͱ͓ΓͰ͕͢ຊষͰ΋ղઆ͍ͨ͠ͱࢥ͍·͢ɻ ·ͣ࣍ͷίϚϯυΛ࣮ߦ͠·͢ɻ *12 https://appium.github.io/appium-xcuitest-driver/7.27/guides/run-preinstalled-wda/#using-xcode 38
  41. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.4 iOS ΞϓϦͷͨΊͷ؀ڥߏங bunx appium

    driver run xcuitest open-wda ͢Δͱ Xcode ͕։͖·͢ɻXcode Ͱ WebDriverAgentRunner ͷεΩʔϜΛબ୒͠·͢ɻ ਤ 2.8: WebDriverAgentRunner Λબ୒ ͦͯ͠ςετΛ࣮ߦ͠·͢ɻ ਤ 2.9: Xcode ্ͰςετΛ࣮ߦ ͢Δͱ Xcode ্ʹωοτϫʔΫड৴઀ଓͷڐ୚μΠΞϩά͕දࣔ͞Ε·͢ɻ͜ΕΛڐՄ͢Δ͜ͱ Ͱ iOS γϛϡϨʔλͱ Appium αʔό͕௨৴Ͱ͖·͢ɻ 39
  42. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.5 iOS Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ਤ 2.10:

    ωοτϫʔΫड৴઀ଓͷڐ୚μΠΞϩά ͜ΕͰ४උ͕੔͍·ͨ͠ɻ 2.5 iOS Ͱಈ࡞ݕূΛࣗಈԽ͢Δ iOS Ͱ΋ Android ಉ༷ʹ·ͣσόΠε΍ΞϓϦͷ৘ใ͕ඞཁʹͳΓ·͢ɻAppium Λಈ͔͍ͨ͠ γϛϡϨʔλΛىಈ͍ͯ͠Ε͹࣍ͷίϚϯυͰ UDID ͷ֬ೝ͕Ͱ͖·͢ɻUDID Λ֬ೝͨ͠Β 2 ߦ ໨ͷίϚϯυͰϞσϧͱ OS όʔδϣϯ΋ௐ΂·͠ΐ͏ɻϞσϧ໊͸γϛϡϨʔλͷ৔߹ Simulator ͱ͍͏จࣈྻͷखલ෦෼ɺOS όʔδϣϯ͸࠷ॳͷׅހ಺ͷ஋Ͱ͢ɻ໨తͷΞϓϦͷόϯυϧ ID ͕ ෼͔Βͳ͍৔߹ɺ͋Β͔͡ΊγϛϡϨʔλʹΠϯετʔϧ͓ͯ͘͜͠ͱͰ 3 ߦ໨ͷίϚϯυͰ֬ೝͰ ͖·͢ɻ xcrun simctl list devices | grep ’Booted’ xcrun xctrace list devices | grep UDID xcrun simctl listapps UDID ͜͜·Ͱʹ֬ೝͨ͠஋ΛͦΕͧΕ؀ڥม਺ʹࢦఆ͠·͢ɻ .env +IOS_DEVICE_NAME=Ϟσϧ໊ +IOS_PLATFORM_VERSION=OSόʔδϣϯ +IOS_UNIQUE_DEVICE_IDENTIFIER=UDID +IOS_BUNDLE_IDENTIFIER=όϯυϧID ͦͯ͠ wdio.conf.ts ͷ capabilities ʹ৽ͨʹ iOS ༻ͷઃఆΛ௥Ճ͠·͢ɻAndroid ͷઃఆͷલ ʹ௥Ճ͢ΔΑ͏ʹ͠·͠ΐ͏ɻcapabilities ͸࣍ͷΑ͏ʹͳΓ·͢ɻ 40
  43. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.5 iOS Ͱಈ࡞ݕূΛࣗಈԽ͢Δ wdio.conf.ts capabilities:

    [ { platformName: "iOS", "appium:automationName": "XCUITest", "appium:deviceName": process.env.IOS_DEVICE_NAME, "appium:platformVersion": process.env.IOS_PLATFORM_VERSION, "appium:udid": process.env.IOS_UNIQUE_DEVICE_IDENTIFIER, "appium:bundleId": process.env.IOS_BUNDLE_IDENTIFIER, "appium:noReset": true, "appium:autoGrantPermissions": true, "appium:showXcodeLog": true, }, { // capabilities for local Appium web tests on an Android Emulator platformName: "Android", "appium:deviceName": process.env.ANDROID_DEVICE_NAME, "appium:appPackage": process.env.ANDROID_APP_PACKAGE, "appium:appActivity": process.env.ANDROID_APP_ACTIVITY, "appium:automationName": "UiAutomator2", "appium:noReset": true, "appium:autoGrantPermissions": true, }, ], ܕఆٛϑΝΠϧ΋๨Εͣߋ৽͓͖ͯ͠·͠ΐ͏ɻ env.d.ts declare global { namespace NodeJS { interface ProcessEnv { readonly ANDROID_DEVICE_NAME: string; readonly ANDROID_APP_PACKAGE: string; readonly ANDROID_APP_ACTIVITY: string; readonly IOS_DEVICE_NAME: string; readonly IOS_PLATFORM_VERSION: string; readonly IOS_UNIQUE_DEVICE_IDENTIFIER: string; readonly IOS_BUNDLE_IDENTIFIER: string; readonly ANDROID_STORE_TAB_ID: string; readonly ANDROID_SETTING_TAB_ID: string; readonly ANDROID_LANGUAGE_SETTING_TEXT: string; readonly ANDROID_OK_BUTTON_XPATH: string; readonly FRENCH_TEXT: string; } } } 41
  44. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.5 iOS Ͱಈ࡞ݕূΛࣗಈԽ͢Δ export {};

    ͜ͷঢ়ଶͰγϛϡϨʔλ্Ͱ໨తͷΞϓϦΛ্ཱͪ͛ͨ··εΫϦϓτΛ૸ΒͤͯΈ·͢ɻઃఆʹ ໰୊͕ͳ͚Ε͹ඪ४ग़ྗ্Ͱॲཧ͕ਖ਼ৗʹಈ͍͍ͯΔ͜ͱ͕֬ೝͰ͖Δ͸ͣͰ͢ʢωοτϫʔΫड৴ ઀ଓͷڐ୚ʹ͍ͭͯ࠶౓ٻΊΒΕΔ৔߹͕͋Γ·͕ͦ͢ͷ৔߹͸࠶౓ڐՄΛ͍ͯͩ͘͠͞ʣ ɻ ͯ͞ɺ͜͜·Ͱ iOS ͷ઀ଓ৘ใ͕෼͔Γ·ͨ͠ɻͦͯ͠ iOS ͸ Android ͱϩέʔλͷछྨ΍஋͕ ҟͳΔͨΊ͜ͷ··Ͱ͸ಈ͖·ͤΜɻ࠶౓ Appium Inspector ͷग़൪Ͱ͢ɻAppium Inspector Λཱ ্ͪ͛ͨΒࠓ౓͸ Capability Builder Λ্ॻ͖͍ͯͩ͘͠͞ɻ্ॻ͖͢Δલʹը໘ӈԼͷ Save As... ϘλϯΛԡ͢ͱࠓͷઃఆʹ໊લΛ෇͚ͯอଘͰ͖ Saved Capability Sets ͔Βอଘͨ͠ઃఆΛ෮ݩ͢ Δ͜ͱ΋ՄೳͰ͢ɻඞཁʹԠͯ͡อଘ͓ͯ͘͠ͱศརͰ͢ɻ ઀ଓ৘ใΛ্ॻ͖ͨ͠Β Appium αʔό্ཱ͕͕͍ͪͬͯΔ͔֬ೝ͠ຬΛ࣋ͯ͠ Start Session Λ ԡ͠·͠ΐ͏ɻ্ཱ͕͍ͪͬͯͳ͚Ε͹࠶౓ҎԼͷίϚϯυͰ্ཱ͓͍͍ͪ͛ͯͯͩ͘͞ɻ bunx appium server ແࣄ্ཱ͕ͪΓ·ͨ͠ɻ ਤ 2.11: iOS γϛϡϨʔλ 42
  45. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.5 iOS Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ಈ࡞ݕূͷࣗಈԽ ͦΕͰ͸

    Android ಉ༷ʹ·ͣ࣍ͷ஋ΛͦΕͧΕௐ΂·͢ɻ • Ո଒ઃఆλϒ • ݴޠબ୒ iOS ͸ Android ͱϩέʔλͷछྨ͕Ұ෦ҟͳΓ·͢ɻݻ༗ͷϩέʔλʹ͸ Android ಉ༷ʹυΩϡ ϝϯτ΁ͷϦϯΫ͕ซه͞Ε͍ͯΔͷͰ࢖͍෼͚ʹ͍ͭͯ͸ͦͪΒΛ͝ࢀর͍ͩ͘͞ɻ ͬͦ͘͞ௐ΂ͨ݁ՌΛϖʔδΦϒδΣΫτ΁൓ө͠·͢ɻ͜ͷͱ͖ɺϩέʔλ͕ҟͳΔ͜ͱʹΑΔ ޻෉͕ඞཁͱͳΓ·͢ɻ·ͨɺ͜ͷ··Ͱ͸εΫϩʔϧʹ͍ͭͯ΋ಈ͔ͳ͍ͨΊมߋ͕ඞཁͱͳΓ· ͢ɻiOS ͸ Android ͱҧ͍εΫϩʔϧͷڍಈ͕҆ఆ͍ͯ͠ΔͨΊཁૉΛ௨Γա͗Δ͜ͱ͸ߟྀ͠· ͤΜɻ·ͱΊΔͱ࣍ͷΑ͏ʹͳΓ·͢ɻ ˞ ࢽ໘ͷ౎߹্ɺมߋͨ͠෦෼ͷΈൈਮ͍ͯ͠·͢ɻ·ͨɺҎ߱͸؀ڥม਺΁ͷ஋ͷઃఆ͸ܕఆ ٛϑΝΠϧ΁ͷهड़ͱಉ࣌ʹߦΘΕͨ΋ͷͱͯ͠ѻ͍·͢ɻࢽ໘্Ͱ͸লུ͞Ε͍ͯ·͕͢ຊষΛࢀ ߟʹ࣮ࡍʹखΛಈ͔͢ࡍʹ͸๨Εͣʹઃఆ͠·͠ΐ͏ʢΦʔϓϯιʔεʹ͢Δඞཁ͕ͳ͚Ε͹؀ڥม ਺Ͱ͸ͳ͘௚઀هड़ͯ͠͠·͏ํ͕ྑ͍͔΋͠Ε·ͤΜʣ ɻ env.d.ts readonly IOS_SETTING_TAB_ID: string; readonly IOS_LANGUAGE_SETTING_TEXT: string; test/pageobjects/page.ts protected async scrollDownTo(text: string, maxAttempts = 10) { if (driver.isAndroid) { const scrollableElementId = await $( "android=new UiScrollable(new UiSelector().scrollable(true))", ).elementId; let attempt = 1; while (true) { if (maxAttempts < attempt) { throw Error( ‘ࢦఆͨ͠จࣈྻͰಛఆՄೳͳཁૉ͕ݟ͔ͭΓ·ͤΜͰͨ͠ɻจࣈྻ: ${text}‘, ); } if (attempt <= 3) { await driver.execute("mobile: scrollGesture", { elementId: scrollableElementId, direction: "down", percent: 1, }); 43
  46. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.5 iOS Ͱಈ࡞ݕূΛࣗಈԽ͢Δ } else

    { // 4ճ໨Ҏ߱͸໨తͷElementΛ௨Γա͗ͯ࠷Լ෦΁౸ୡ͍ͯ͠ΔՄೳੑ͕ߴ͍ͨΊަޓʹ্Լ͞ ͤΔ const direction = attempt % 2 === 0 ? "up" : "down"; await driver.execute("mobile: scrollGesture", { elementId: scrollableElementId, direction, percent: 1, }); } // εΫϩʔϧ௚ޙͩͱඳը͕ؒʹ߹Θͣfalse͕ฦΔ৔߹͕͋ΔͨΊεϦʔϓΛೖΕΔ await this.sleep(); if (await this.elementBy(text).isDisplayed()) { break; } attempt++; } } else { const predicateString = this.predicateStringWith(text); driver.executeScript("mobile: scroll", [ { direction: "down", predicateString }, ]); } } protected elementBy(text: string) { return $( driver.isAndroid ? ‘android=new UiSelector().textMatches("${text}")‘ : ‘-ios predicate string: ${this.predicateStringWith(text)}‘, ); } protected predicateStringWith(text: string) { return ‘name = "${text}" OR label = "${text}" OR value = "${text}"‘; } private get settingTab() { return $( driver.isAndroid ? process.env.ANDROID_SETTING_TAB_ID : process.env.IOS_SETTING_TAB_ID, ); } ·ͣ settingTab ʹ͍ͭͯ͸ͲͪΒ΋ ID ϩέʔλͩͬͨͨΊγϯϓϧͳมߋͱͳΓ·ͨ͠ɻҰํ Ͱ elementBy ʹ͍ͭͯ͸ϩέʔλͷ৔߹෼͚Λߦ͍ͬͯ·͢ɻεΫϩʔϧ͸͜Ε·ͰͷॲཧΛ͢΂ ͯ Android ༻ͱͯ͠৽ͨʹ௥Ճ͠·͕ͨͪ͜͠Β΋ൺֱతγϯϓϧͳ࣮૷ͱͳ͍ͬͯ·͢ɻ SettingPage ͸࣍ͷͱ͓Γݴޠબ୒෦෼ͷ஋Λ৔߹෼͚͍ͯ͠·͢ɻelementBy Ͱ͸ϩέʔλͷ 44
  47. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.5 iOS Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ৔߹෼͚΋ߦΘΕ͍ͯΔͨΊ࣮ࡍʹ͸ҟͳΔϩέʔλʹҟͳΔ஋͕ઃఆ͞Ε͍ͯ·͢ɻ test/pageobjects/setting.page.ts

    private get languageSetting() { return this.elementBy( driver.isAndroid ? process.env.ANDROID_LANGUAGE_SETTING_TEXT : process.env.IOS_LANGUAGE_SETTING_TEXT, ); } ͜ͷঢ়ଶͰಈ͔ͯ͠Έ·͠ΐ͏ɻ͢ΔͱࣗಈςετͷεΫϦϓτ͸ม͑ͳͯ͘΋ݴޠબ୒·Ͱ͸͏ ·͘ಈ͖·͕͢ݴޠબ୒ޙͷಈ͖͕ iOS ΞϓϦͱ Android ΞϓϦʹΑͬͯҟͳΔͨΊʹ iOS ͸్த Ͱࢭ·ͬͯ͠·͍·͢ɻ͜Ε͸ iOS Ͱ͸ݴޠબ୒Λԡ͢ͱઃఆΞϓϦ಺ʹ͋ΔΞϓϦͷݸผઃఆͰ ݴޠΛ੾Γସ͑ΔΑ͏ͳ࣮૷ʹͳ͍ͬͯΔͨΊͰ͢ɻ ਤ 2.12: iOS Ͱͷݴޠઃఆը໘ ݱࡏͷઃఆͷ৔߹ɺAppium Ͱ͸ϑΥΞάϥ΢ϯυʹͳ͍ͬͯΔΞϓϦʹରͯ͠ૢ࡞͕ߦΘΕ· ͢ɻ޾͍ʹ΋ݴޠઃఆΛԡͨ͠ޙ͸ઃఆΞϓϦ͕ϑΥΞάϥ΢ϯυͱͳΔͨΊଓ͚ͯૢ࡞͢Δ͜ͱ͸ ՄೳͰ͢ɻͦ͜Ͱ͜ͷ··ઃఆΞϓϦΛૢ࡞ͯ͠͠·͍·͠ΐ͏ɻ ઃఆΞϓϦ͸όϯυϧ ID ͕ҟͳΔͨΊ Appium Inspector Ͱ͸ࠓͷઃఆͩͱௐ΂Δ͜ͱ͕Ͱ͖· ͤΜɻࣥච࣌఺Ͱ͸ઃఆΞϓϦͷόϯυϧ ID ͸ com.Apple.Preferences ͱͳ͍ͬͯΔͨΊόϯ υϧ ID ্͚ͩॻ͖͢Ε͹͙͢ʹ֬ೝͰ͖·͢ɻ্ॻ͖ͨ͠Βͬͦ͘͞ Appium Inspector Λ্ཱͪ ͛ͯϩέʔλͱ஋Λ֬ೝͦ͠ΕΛઃఆ͠·͢ɻ֬ೝ͕Ͱ͖ͨΒઃఆΞϓϦͷૢ࡞΋ SettingPage Ϋ ϥεʹهड़ͯ͠͠·͍·͠ΐ͏ɻ݁Ռɺ࣍ͷΑ͏ʹͳΓ·͢ɻ 45
  48. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.5 iOS Ͱಈ࡞ݕূΛࣗಈԽ͢Δ env.d.ts readonly

    IOS_PREFERRED_LANGUAGE_TEXT: string; test/pageobjects/setting.page.ts async switchToFrench() { await this.scrollDownTo(process.env.ANDROID_LANGUAGE_SETTING_TEXT); await this.languageSetting.click(); - await this.frenchOnLanguageModal.click(); - await this.okButtonOnDialog.click(); + if (driver.isAndroid) { + await this.frenchOption.click(); + await this.okButtonOnDialog.click(); + } else { + await this.preferredLanguageOnPreference.click(); + await this.frenchOption.click(); + } } - private get frenchOnLanguageModal() { + private get frenchOption() { return this.elementBy(process.env.FRENCH_TEXT); } + + private get preferredLanguageOnPreference() { + return this.elementBy(process.env.IOS_PREFERRED_LANGUAGE_TEXT); + } ·ͣ switchToFrench Ͱ৔߹෼͚Λ͍ͯ͠·͢ɻͱ͍ͬͯ΋ Android ͷॲཧ͸ม͍͑ͯ·ͤΜɻ1 ఺ɺAndroid ΋ iOS ΋ϑϥϯεޠͷબ୒ࢶ͸ϩέʔλ΁౉͢ͷ͸·ͬͨ͘ಉ͡஋ͱͳ͍ͬͯ·͢ɻ ͳͷͰ໋໊Λ Android ʹಛԽ͍ͯͨ͠ frenchOnLanguageModal ͔Β൚༻తͳ frenchOption ͱ͍ ͏໊લʹมߋ͍ͯ͠·͢ɻͦͯ͠ iOS Ͱ͸ઃఆΞϓϦભҠޙʹ࠶౓ݴޠઃఆͷ߲໨Λબ୒͢Δඞཁ ͕͋ΔͨΊ৽ͨͳॲཧΛ௥Ճ͍ͯ͠·͢ɻͦͷޙɺઃఆΞϓϦ಺Ͱݴޠͷબ୒ࢶ͕දࣔ͞ΕΔͨΊ Android ಉ༷ʹϑϥϯεޠΛબ୒͠·͢ɻAndroid ͱ͸ҧ͍બ୒ޙ͸֬ೝμΠΞϩά͕දࣔ͞Εͳ ͍ͨΊॲཧ͸͜ΕͰऴΘΓɺͱ͍ͨ͠ͱ͜ΖͰ͕͢ 1 ఺໰୊͕͋Γ·͢ɻ ·ͣ͜͜·ͰҰ౓࣮ߦͯ͠Έ·͠ΐ͏ɻ 46
  49. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.5 iOS Ͱಈ࡞ݕূΛࣗಈԽ͢Δ bun run

    wdio ͢Δͱ໰୊ͳ͘ಈ͘΋ͷͷઃఆΞϓϦͰݴޠબ୒ޙʹॲཧ͕ࢭ·ͬͯ͠·͍·͢ɻ͜Ε͸ݴޠબ୒ Λͯ͠΋ΈͯͶΞϓϦʹ໭Δॲཧ͕ͳ͍ͨΊͰ͢ɻͰ͢ͷͰɺiOS Ͱ͸͞ΒʹΈͯͶΞϓϦʹ໭͢ॲ ཧ΋௥Ճ͠·͢ɻ test/pageobjects/page.ts protected async launchIosApp() { await driver.executeScript("mobile: launchApp", [ { bundleId: process.env.IOS_BUNDLE_IDENTIFIER }, ]); this.sleep(500); } test/pageobjects/setting.page.ts async switchToFrench() { await this.scrollDownTo(process.env.ANDROID_LANGUAGE_SETTING_TEXT); await this.languageSetting.click(); await this.frenchOnLanguageModal.click(); await this.okButtonOnDialog.click(); if (driver.isAndroid) { await this.frenchOption.click(); await this.okButtonOnDialog.click(); } else { await this.preferredLanguageOnPreference.click(); await this.frenchOption.click(); + await this.launchIosApp(); } } ͜ΕͰແࣄʹϑϥϯεޠ΁੾Γସ͑Δ͜ͱ͕Ͱ͖·ͨ͠ɻ 47
  50. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.5 iOS Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ਤ 2.13:

    ϑϥϯεޠͷΞϧόϜը໘ ݴޠ੾Γସ͕͑Ͱ͖ͨΒ͋ͱ͸ετΞը໘΁ભҠ͠εΫϦʔϯγϣοτΛࡱΔ͚ͩͰ͢ɻͦ͜Ͱλ ϒҰཡʹ͋ΔετΞλϒΛௐ΂·͢ɻઌ΄Ͳ·ͰઃఆΞϓϦΛௐ΂ΔͨΊʹ Appium Inspector ͷ ઃఆΛ্ॻ͖͍ͯͨ͠ͷͰઃఆΛ໭ͯ͠࠶౓࣮ߦ֬͠ೝ͠ɺiOS ༻ʹϖʔδΦϒδΣΫτ΁൓ө͠· ͠ΐ͏ɻ env.d.ts + readonly IOS_STORE_TAB_ID: string; test/pageobjects/page.ts private get storeTab() { - return $(process.env.ANDROID_STORE_TAB_ID); + return $( + driver.isAndroid + ? process.env.ANDROID_STORE_TAB_ID + : process.env.IOS_STORE_TAB_ID, + ); } ແࣄετΞը໘Λ։͘͜ͱ͕Ͱ͖·ͨ͠ɻ 48
  51. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.5 iOS Ͱಈ࡞ݕূΛࣗಈԽ͢Δ ਤ 2.14:

    iOS ͷϑϥϯεޠ൛ετΞը໘ ͦΕͰ͸࢓্͛ʹεΫϦʔϯγϣοτͷॲཧͰ͢ɻͱݴ͍͍ͨͱ͜ΖͰ͕࣮͢͸εΫϦʔϯγϣο τʹ͍ͭͯ͸৔߹෼͚͸ඞཁ͋Γ·ͤΜɻ͢ͰʹϑΝΠϧ໊ʹ͍ͭͯ΋ϓϥοτϑΥʔϜͷҧ͍Λҙ ࣝͨ͠ॲཧͱͳ͍ͬͯ·͢ͷͰ࣮͸ݱ࣌఺Ͱ tmp σΟϨΫτϦΛݟΔͱ iOS ൛ͷεΫϦʔϯγϣο τ͕อଘ͞Ε͍ͯΔ͜ͱ͕෼͔Γ·͢ɻͨͩɺiOS ͷํ͕ඳըʹֻ͕͔࣌ؒΔͷ͔·ͩϖʔδͷҰ෦ ͕දࣔ͞Ε͍ͯ·ͤΜͶɻগ͠εϦʔϓ͢ΔॲཧΛ৳͹ͯ͠࠶౓࣮ߦͯ͠Έ·͠ΐ͏ɻ bun run wdio ͪΌΜͱը໘શମ͕ඳը͞Εͨঢ়ଶͰอଘ͞Ε͍ͯΔ͜ͱ͕֬ೝͰ͖·ͨ͠ɻ ਤ 2.15: iOS ͷϑϥϯεޠ൛ετΞը໘εΫϦʔϯγϣοτ ͱ͜ΖͰ͜͜·Ͱ࣮ߦ͍ͯ͠Δͱ iOS ͕ऴΘͬͨޙʹ Android ͕ಈ͖࢝ΊΔ͜ͱ͕෼͔Γ·͢ɻ 49
  52. ୈ 2 ষ Appium ʹΑΔϞόΠϧΞϓϦಈ࡞ݕূͷࣗಈԽೖ໳ 2.6 ͍͞͝ʹ ࣮͸ capabilities ʹ͋Δઃఆͷ਺͚ͩΠϯελϯε͕ಈ͘Α͏ʹͳ͍ͬͯ·͢ɻͦͯ͠ಉ࣌ʹىಈͰ

    ͖Δ਺͕ maxInstances ͱͳ͍ͬͯ·͢ɻ͜Ε͕ 1 ͩͬͨͷͰฒྻʹಈ͔ͣ௚ྻʹىಈ͢Δಈ࡞ͱͳ Γ݁Ռͱͯ͠ capabilities ͷઌ಄ʹ͋Δ iOS ͕ಈ͍͍͚ͯͨͩͰ iOS ͕ऴΘͬͨͷͰ࣍ʹ Android ͕ɺͱͳ͍ͬͯͨͱ͍͏Θ͚Ͱ͢ɻAppium αʔό΍ WebdriverIO ͷ࣮ߦϩάΛ֬ೝ͢Δ͏͑Ͱ͸ ౎߹͕ྑ͔ͬͨͷͰ͕͢ɺͲͪΒ΋Ͱ͖ͨࠓͱͳͬͯ͸௚ྻͰಈ͔͍ͯͯ͠΋ֻ͕͔࣌ؒΔ͚ͩͰ͢ ͶɻͳͷͰ maxInstances Λ 2 ʹมߋ͠·͠ΐ͏ɻ͢Δͱ Android ͱ iOS Λಉ࣌ʹಈ͔͢͜ͱ͕Ͱ ͖·͢ɻ wdio.conf.ts - maxInstances: 1, + maxInstances: 2, ຊষͰ঺հ͢Δ಺༰͸Ҏ্ʹͳΓ·͢ɻ 2.6 ͍͞͝ʹ ຊষͰ͸ Appium ͷಋೖ͔Β࣮ࡍʹಈ͔͢෦෼Λ঺հ͍͖ͤͯͨͩ͞·ͨ͠ɻ ࣮ࡍʹߦ͍ͬͯΔಈ࡞ݕূ༻ͷίʔυ͔ΒݟΔͱຊষͰ঺հͰ͖ͨͷ͸΄ΜͷҰ෦ͱͳ͓ͬͯΓ͍ Ζ͍Ζͱ଍Γͳ͍෦෼͸͋ΔΜͰ͕͢ɺͦΕͰ΋ͳΜͱͳ͘ Appium ͰࣗಈԽ͢ΔͨΊͷงғؾ͸ ͔ͭΊͨͷͰ͸ͳ͍Ͱ͠ΐ͏͔ɻ චऀ͸ Appium ͰτΠϧͷࣗಈԽΛ͠Α͏ͱࢥཱ͍ͬͨͱ͖ɺશମͷΞʔΩςΫνϟͷཧղͱ iOS ͷηοτΞοϓʹ͖᪴·ͨ͠ɻͦ͜Ͱࣗ਎ͷ಄ͷ੔ཧ΋݉ͶͯචΛࣥͬͯΈͨ࣍ୈͰ͕ͨ͠ɺͦͷա ఔͰಘΒΕͨ஌ࣝ͸ࠓޙ΋׆͔ͤͦ͏Ͱ͢͠ԿΑΓָ͍࣌ؒ͠Ͱͨ͠ɻϓϩάϥϛϯάΛ࢝Ίͨ͹͔ Γͷ͜Ζ͸ࣗ਎͕ॻ͍ͨॲཧ͕࣮ࡍʹಈ͘ͱ͜ΖΛݟΔͷ͕͓΋͠Ζ͔ͬͨ΋ͷͰ͕͢ɺAppium Ͱ ͸ͦΕ͕ΞϓϦ্ͰߦΘΕΔͱ͍͏఺Ͱචऀʹͱͬͯ͸ϢχʔΫͳମݧͰɺ࠶ͼ͔ͭͯͷΑ͏ͳϫΫ ϫΫ͢Δؾ࣋ͪΛऔΓ໭ͨ͠ؾ͕͠·͢ɻ Appium ͸΋ͱ΋ͱςεςΟϯάϑϨʔϜϫʔΫͰ͢ͷͰ·ͣ͸ಈ࡞ݕূ͔Β࢝Ίͯιʔείʔυ Λҭ͍ͯ͘͜ͱͰ E2E ςετͷ؀ڥΛ੔උ͢Δ͜ͱ΋ՄೳͰ͢ɻ΋͠·ͩࣗ͝਎ͷීஈͷ։ൃͷத Ͱ E2E ςετͷ؀ڥ͕੔͍ͬͯͳ͍Α͏Ͱ͋Ε͹ɺ·ͣ͸τΠϧͷ࡟ݮΛ໨ඪʹ Appium Λ࢖ͬͨ ಈ࡞ݕূ͔Β࢝ΊͯΈΔͷ͸͍͔͕Ͱ͠ΐ͏͔ɻ ຊষΛಡΜͰ 1 ਓͰ΋ Appium Λ৮ͬͯΈΑ͏ͱࢥ͍͚ͬͯͨͩͨͳΒ޾͍Ͱ͢ɻ 50
  53. ୈ 3 ষ Cloud Spanner ͷ͘͠ΈΛཧղ͢Δ 3.1 ·͕͖͑ ॳΊ·ͯ͠ɻ2023 ೥౓৽ଔͱͯ͠ೖࣾͨ͠एদͰ͢ɻX

    ͰͷΞΧ΢ϯτ໊͸ @Jaksho500 Ͱ͢ɻ ࢲ͸ΤϯδχΞͱͯ͠ɺTIPSTAR*1 ͱ͍͏αʔϏεͷ։ൃʹܞΘ͍ͬͯ·͢ɻ TIPSTAR ͷٕज़తͳಛ௃ͷ 1 ͭͱͯ͠ɺDB ʹ Cloud Spanner*2 ʢҎԼɺSpanner ʣΛར༻͠ ͍ͯΔ఺͕ڍ͛ΒΕ·͢ɻTIPSTAR ʹδϣΠϯͯ͠ॳΊͯ Spanner Λ৮Γ·͕ͨ͠ɺաڈʹ࢖༻ ܦݧ͕͋ͬͨϦϨʔγϣφϧσʔλϕʔεʢҎԼɺRDBMS ʣ΍ NoSQL ͱ͸ҟͳΔಠಛͷ࢖༻ײ ͕͋Γ·ͨ͠ɻSpanner ͷجૅͱͳΔߟ͑ํ΍ΞʔΩςΫνϟ͸ɺGoogle ʹΑͬͯ࿦จͱͯ͠ެ։ ͞Ε͍ͯ·͢ɻࢲ͸͜ͷ࿦จΛ͍ͣΕಡΈ͍ͨͱߟ͍͑ͯ·ͨ͠ɻ࿦จΛಡΉ͜ͱͰɺීஈۀ຿Ͱߦ ͏εΩʔϚઃܭ΍ΫΤϦͷνϡʔχϯάʹ͍ͭͯɺΑΓਂ͍ཧղ͕ಘΒΕΔͷͰ͸ͳ͍͔ͱظ଴ͯ͠ ͍ͨͷͰ͢ɻ͔͠͠ɺͳ͔ͳ͔࣌ؒΛ֬อͰ͖ͣೖ͔ࣾΒ 1 ೥൒͕ܦաͯ͠͠·͍·ͨ͠ɻͦ͜Ͱࠓ ճٕज़ॻయ 17 Λక੾ۦಈʹɺ࿦จΛಡΈਐΊΔ͜ͱʹ͠·ͨ͠ɻ 3.2 ຊষʹ͍ͭͯ ຊষͰ͸ɺ Google ͕ެ։͍ͯ͠Δ ʮ Cloud Spanner: Googleʟ s Globally Distributed Database*3ʯ ͱ͍͏࿦จΛಡΜͰཧղͨ͠಺༰Λ·ͱΊ·͢ɻதͰ΋ɺSpanner ͕ਫฏεέʔϥϏϦςΟͱ֎෦੔ ߹ੑͷ྆ํΛಛ௃ͱͯ࣋ͪ͠߹Θ͍ͤͯΔ͘͠ΈʹϑΥʔΧε͠ɺҎԼͷΑ͏ͳ੾ΓޱͰ঺հ͍ͯ͠ ͖·͢ɻ *1 TIPSTARʢςΟοϓελʔʣެࣜαϙʔταΠτ: https://about.tipstar.com *2 Spanner: ৗ ࣌ Ք ಇ Ͱ ɺ࣮ ࣭ త ʹ ແ ੍ ݶ ͷ ε έ ʔ Ϧ ϯ ά Λ උ ͑ ͨ σ ʔ λ ϕ ʔ ε | Google Cloud https://cloud.google.com/spanner?hl=ja *3 Spanner: Google’s Globally-Distributed Database: https://research.google/pubs/spanner-googles-globally- distributed-database-2/ 51
  54. ୈ 3 ষ Cloud Spanner ͷ͘͠ΈΛཧղ͢Δ 3.3 Cloud SpannerɾNewSQL ͱ͸

    • ਫฏεέʔϥϏϦςΟΛఏڙ͢Δ Cloud Spanner ͷΞʔΩςΫνϟ • Cloud Spanner ͷ֎෦੔߹ੑΛࢧ͑Δ͘͠Έ ཧղΛਂԽ͢ΔͨΊʹɺݱࡏ Google ͕ఏڙ͍ͯ͠Δ Spanner ͷυΩϡϝϯτ΍΄͔ͷํ͕ެ։ ͞Ε͍ͯΔղઆ΋ࢀߟʹ͠·ͨ͠ɻࢀߟʹ͍͍ͤͯͨͩͨ͞هࣄɾGoogle ͷυΩϡϝϯτ͸ɺ࠷ޙ ʹ·ͱΊͯ঺հ͍͖ͤͯͨͩ͞·͢ɻ ͞·͟·ͳ৘ใΛ΋ͱʹཧղΛਐΊͨͨΊɺ෦෼తʹ࿦จͰهࡌ͞Ε͍ͯΔ಺༰ΑΓ΋࠷৽ͷ৘ใ Λ঺հͯ͠͠·͍ͬͯΔՄೳੑ͕͋Γ·͢ɻ·ͨɺޡͬͨ಺༰Λ঺հͯ͠͠·͍ͬͯΔ෦෼΋͋Δ͔ ΋͠Ε·ͤΜɻ׮େͳ৺ͰຊষΛಡΈਐΊ͍͚ͯͨͩΕ͹޾͍Ͱ͢ɻ 3.3 Cloud SpannerɾNewSQL ͱ͸ Cloud Spanner Spanner ͱ͸ɺGoogle ͕ఏڙ͢ΔϑϧϚωʔδυܕͷϦϨʔγϣφϧσʔλϕʔεαʔϏεͰɺ NewSQL ͱݺ͹ΕΔσʔλϕʔεͷ 1 ͭͰ͢ɻ NewSQL NewSQL ͱ͸ɺैདྷͷ RDBMS ͕࣋ͭ ACID ಛੑ΍ SQL ͷڧྗͳΫΤϦػೳΛҡ࣋͠ͳ͕Βɺ NoSQL ͕ಘҙͱ͢ΔεέʔϥϏϦςΟͱύϑΥʔϚϯεͷ՝୊Λղܾ͢ΔͨΊʹੜ·Εͨσʔλ ϕʔεٕज़Ͱ͢ɻ۩ମతʹ͸ҎԼͷΑ͏ͳಛ௃͕͋Γ·͢ɻ • SQL αϙʔτ: SQL Λ࢖༻ͨ͠ΫΤϦ͕ՄೳͰɺैདྷͷ RDBMS ͱಉ༷ͷૢ࡞ײΛ࣋ͪ ·͢ɻ • εέʔϥϏϦςΟ: NoSQL σʔλϕʔε͕ಘҙͱ͢ΔਫฏεέʔϥϏϦςΟʢσʔλΛଟ͘ ͷϊʔυʹ෼ࢄͯ֨͠ೲ͠ɺෛՙ෼ࢄΛߦ͏ʣ͕ՄೳͰ͢ɻ • ڧ੔߹ੑͷఏڙ: ෳ਺ϊʔυؒͰͷσʔλͷҰ؏ੑ͕֬อ͞Ε·͢ɻ • ACID τϥϯβΫγϣϯͷαϙʔτ: τϥϯβΫγϣϯॲཧʹ͓͍ͯ΋ڧྗͳҰ؏ੑͱ৴པੑ ͕อূ͞Ε͍ͯ·͢ɻ 3.4 ਫฏεέʔϥϏϦςΟΛఏڙ͢ΔΞʔΩςΫνϟ Spanner ͸κʔϯͷηοτͱͯ͠ฤ੒͞Ε͓ͯΓɺݱࡏʢ2024/10 ݄ʣબ୒Ͱ͖Δ Spanner ͷ࠷ খߏ੒͸ɺγϯάϧϦʔδϣϯͰ 3 ͭͷκʔϯʹϨϓϦΧΛ࣋ͭߏ੒Ͱ͢*4ɻκʔϯͷηοτ͸ɺ σʔλΛϨϓϦέʔγϣϯͰ͖ΔϩέʔγϣϯͷηοτͰ΋͋Γ·͢ɻ֤κʔϯʹ͸ 1 ͭͷκʔϯϚ *4 Cloud Spanner ͷ Ϛ ϧ ν Ϧ ʔ δ ϣ ϯ ߏ ੒ ʹ ͭ ͍ ͯ ཧ ղ ͢ Δ | Google Cloud ެ ࣜ ϒ ϩ ά: https://cloud.google.com/blog/ja/topics/developers-practitioners/demystifying-cloud-spanner-multi- region-configurations 52
  55. ୈ 3 ষ Cloud Spanner ͷ͘͠ΈΛཧղ͢Δ 3.4 ਫฏεέʔϥϏϦςΟΛఏڙ͢ΔΞʔΩςΫνϟ ελͱඦ͔Β਺ઍͷεύϯαʔόɺϩέʔγϣϯϓϩΩγ͕ଘࡏ͠·͢ɻ ʢຊষ

    5 અʹκʔϯͷશମ ૾Λࣔ͢ਤΛܝࡌ͍ͯ͠·͢ɻઌʹݟ͍͍ͯͨͩͨํ͕ཧղ͕ਐΉ͔΋͠Ε·ͤΜɻ ʣ ͦΕͧΕ໾ׂΛҎԼʹࣔ͠·͢ɻ • κʔϯϚελ ʢ Zonemaster ʣ ɿκʔϯͷσʔλ؅ཧΛߦ͍·͢ɻσʔλͷ഑ஔ΍εύϯαʔό ΁ͷλϒϨοτ (෼ׂ͞Εͨσʔλ) ͷׂΓ౰ͯΛ؅ཧ͢ΔͨΊͷத৺తͳ໾ׂΛՌͨ͠·͢ɻ • εύϯαʔόʢ Spanserver ʣ ɿෳ਺ͷλϒϨοτΛ؅ཧ͠·͢ɻ • ϩέʔγϣϯϓϩΩγʢ Location Proxy ʣ ɿΫϥΠΞϯτͷσʔλϦΫΤετΛద੾ͳεύ ϯαʔόʹϧʔςΟϯά͢ΔͨΊɺޮ཰తʹσʔλΞΫηεΛॲཧ͠·͢ɻ Spanner ͷσʔλϞσϧͱεύϯαʔόʹ͍ͭͯઆ໌͠·͢ɻ σʔλϞσϧ Spanner ͷσʔλϞσϧͱ࣮૷ʹ͍ͭͯ঺հ͠·͢ɻ Spanner ͸ɺσʔλϞσϧͱͯ͠εΩʔϚఆٛ͞ΕͨηϛϦϨʔγϣφϧςʔϒϧɺΫΤϦݴޠɺ ͓Αͼ൚༻τϥϯβΫγϣϯΛΞϓϦέʔγϣϯʹఏڙ͠·͢ɻ࣮૷ͷ؍఺Ͱ͸ɺσΟϨΫτϦͱݺ ͹ΕΔόέοτந৅ԽΛαϙʔτ͍ͯ͠·͢ɻσΟϨΫτϦ͸ɺ͋ΔಛఆͷΩʔൣғʹରԠ͢Δσʔ λΛάϧʔϓԽͨ͠΋ͷͰ͢ɻσΟϨΫτϦΛ୯Ґʹσʔλ͕෼ׂ͞Ε͍ͯΔͨΊɺσΟϨΫτϦͷ αϙʔτʹΑͬͯΞϓϦέʔγϣϯ͸ɺΩʔͷબ୒ʹΑͬͯؔ࿈͢Δσʔλ͕෺ཧతʹۙ͘ʹอଘ͞ ΕΔΑ͏੍ޚ͕ՄೳʹͳΓ·͢ɻ Spanner ͸ɺετϨʔδγεςϜͱͯ͠ Colossus*5 ͱ͍͏෼ࢄϑΝΠϧγεςϜΛར༻͠σʔλ ϞσϧΛ࣮૷͍ͯ͠·͢ɻσΟϨΫτϦͷσʔλ͸̍ͭҎ্ͷλϒϨοτͱ͍͏σʔλߏ଄ʹ෼ׂ͞ ΕɺColossus ʹ؅ཧ͞Ε·͢ɻͳ͓ɺλϒϨοτ಺ʹ͸ෳ਺ͷσΟϨΫτϦͷσʔλ͕֨ೲ͞ΕΔ ৔߹΋͋Γ·͢ɻλϒϨοτ͸ɺҎԼͷΑ͏ͳϚοϐϯάσʔλΛอ͍࣋ͯ͠·͢ɻ ( key:string, timestamp:int64 ) ˠ string Spanner ͸λΠϜελϯϓΛσʔλʹׂΓ౰͓ͯͯΓɺλϒϨοτ͸σʔλͷΩʔͱλΠϜελ ϯϓͷ૊Έ߹ΘͤͰ؅ཧ͞Ε·͢ɻColossus ্ͰλϒϨοτ͸ɺλΠϜελϯϓΛΩʔʹෳ਺όʔ δϣϯ؅ཧ͞Ε͍ͯΔͨΊɺkey-value ετΞͷΑ͏ʹ΋ݟ͑·͕͢Ϛϧνόʔδϣϯσʔλϕʔ εʹ͍ۙͰ͢ɻσʔλ͕෺ཧతʹه࿥͞ΕΔ·ͰͷྲྀΕΛ؆୯ʹҎԼͷਤ 3.1 ʹࣔ͠·͢ɻຊདྷ͸ɺ σΟϨΫτϦΛεύϯαʔόʹׂΓ౰ͯΔκʔϯϚελɺσΟϨΫτϦɺλϒϨοτͷ෺ཧతͳҠಈ Λ࣮ߦ͢ΔόοΫάϥ΢ϯυλεΫɺ෺ཧతͳ؅ཧΛߦ͏εύϯαʔόͳͲ͕ଘࡏ͍ͯ͠·͢ɻ *5 A peek behind Colossus, Googleʟs file system | Google Cloud Blog: https://cloud.google.com/blog/products/storage-data-transfer/a-peek-behind-colossus-googles-file- system?hl=en 53
  56. ୈ 3 ষ Cloud Spanner ͷ͘͠ΈΛཧղ͢Δ 3.4 ਫฏεέʔϥϏϦςΟΛఏڙ͢ΔΞʔΩςΫνϟ ਤ 3.1:

    Spanner ͷσʔλϞσϧ σʔλͷಡΈऔΓ Spanner ʹ͓͚ΔηϛϦϨʔγϣφϧςʔϒϧ͸ɺओΩʔྻ͔ΒඇओΩʔྻ΁ͷϚοϐϯάΛఆ ͍ٛͯ͠·͢ɻςʔϒϧͷ৘ใ͔ΒɺλϒϨοτͷΩʔͱ Colossus ্ͷσʔλ͔Βྻ΁ͷϚοϐϯ ά͕ಘΒΕ·͢ɻσʔλಡΈऔΓͷࡍʹ͸ɺςʔϒϧͱλΠϜελϯϓΛࢦఆ͢Δ͜ͱͰద੾ͳλϒ Ϩοτ͔ΒσʔλΛಡΈऔΔ͜ͱ͕ՄೳʹͳΓ·͢ɻ σΟϨΫτϦͷσʔλྔͷ૿Ճ σΟϨΫτϦ͸ɺσʔλͷϩέʔγϣϯͷ୯ҐͰ͢ɻσΟϨΫτϦ಺ͷ͢΂ͯͷσʔλ͸ɺಉ͡Ϩ ϓϦέʔγϣϯߏ੒Λ࣋ͪ·͢ɻޙड़͢Δ paxos άϧʔϓؒͰσʔλΛҠಈ͢ΔͱɺσΟϨΫτϦ ͝ͱʹҠಈ͠·͢ɻSpanner ͸ɺσΟϨΫτϦ͕େ͖͘ͳΓ͗͢ΔͱɺσΟϨΫτϦΛෳ਺ͷϑϥά ϝϯτͱ͍͏୯Ґʹ෼ׂ͠ɺσʔλͷ؅ཧΛෳ਺ͷεύϯαʔόʹ෼ࢄ͠·͢ɻϑϥάϝϯτ͸ B πϦʔϥΠΫͳߏ଄Ͱ؅ཧ͞Ε·͢ɻσΟϨΫτϦ·Ͱͷ B πϦʔߏ଄ͱɺσΟϨΫτϦ಺ʢϑϥά ϝϯτʣͷ B πϦʔߏ଄ͱ͍͏ 2 ஈ֊ͷ֊૚తͳσʔλ؅ཧ͕ߦΘΕ͍ͯΔͱࢲ͸ཧղ͍ͯ͠·͢ɻ εύϯαʔό ֤εύϯαʔό͸ɺઌड़ͨ͠λϒϨοτ 100ʙ1000 ݸͷॲཧΛ୲౰͠·͢ɻϨϓϦέʔγϣϯΛ ࣮ݱ͢ΔͨΊɺεύϯαʔό͸֤λϒϨοτ͝ͱʹ paxos εςʔτϚγϯΛ࣮૷͍ͯ͠·͢ɻκʔ ϯ͝ͱʹಉ͡λϒϨοτͷϨϓϦΧΛ؅ཧ͢Δεύϯαʔό͕ଘࡏ͠ɺޓ͍ʹ paxos Λར༻ͯ͠߹ ҙܗ੒Λߦ͍ɺϨϓϦέʔγϣϯΛ࣮ݱ͠·͢ɻ 54
  57. ୈ 3 ষ Cloud Spanner ͷ͘͠ΈΛཧղ͢Δ 3.5 ֎෦੔߹ੑΛࢧ͑Δ͘͠Έ 3.5 ֎෦੔߹ੑΛࢧ͑Δ͘͠Έ

    ࢲ͕࿦จΛಡΉதͰײͨ͡ͷ͸ɺ Spanner ͷಛੑͰ͋Δʮ֎෦੔߹ੑʯͷॏཁੑͰ͢ɻਫฏεέʔ ϧ͢Δ෼ࢄγεςϜ্Ͱ֎෦੔߹ੑ͕࣮ݱ͍ͯ͠Δ͘͠Έ͍ͭͯࢲͷཧղΛ౿·͑ͭͭ঺հ͠·͢ɻ ֎෦੔߹ੑʢ External Consistenc ʣ͸ɺ෼ࢄσʔλϕʔεγεςϜʹ͓͚Δ࠷΋ݫີͳ੔߹ੑ ϨϕϧͰ͢ɻෳ਺ͷτϥϯβΫγϣϯΛ࣮ߦͨ͠৔߹ɺॱংΛਖ਼֬ʹ൓ө͢Δ͜ͱΛอূ͠·͢ɻͭ ·Γɺ͋ΔτϥϯβΫγϣϯ A ͕τϥϯβΫγϣϯ B ΑΓ΋ઌʹίϛοτ͞Εͨ৔߹ɺ෼ࢄ͞Εͨ σʔλʹͲ͔͜ΒΞΫηεͯ͠΋ಉ݁͡Ռ͕ಘΒΕΔ͜ͱΛอূ͢ΔಛੑͰ͢ɻ֎෦੔߹ੑΛ࣮ݱ͢ ΔͨΊɺTrueTime API ɺpaxos ɺτϥϯβΫγϣϯʹ͍ͭͯ৮ΕΔඞཁ͕͋Γ·͢ɻ TrueTime API TrueTime API ͸ɺ෼ࢄγεςϜ಺ͷ͢΂ͯͷϊʔυʹରͯ͠ݫີͳ࣌ؒ৘ใΛఏڙ͠·͢ɻ TrueTime ͸࣌ࠁΛ୯Ұͷ஋Ͱ͸ͳ͘ɺ্ݶͱԼݶΛ࣋ͭ࣌ؒൣғʢ TTinterval ʣͱͯ͠දݱ͠· ͢ɻ͜ͷ࣌ؒൣғ͸ɺTrueTime Λऔಘͨ͠ʢ TT.Now Λݺͼग़ͨ͠ʣ࣌ؒΛؚΉ͜ͱ͕อূ͞Ε ͍ͯ·͢ɻ TrueTime ͕࣌ؒൣғͱͯ࣌ؒ͠৘ใΛఏڙ͢Δͷ͸ɺαʔόؒͷ࣌ࠁͷζϨΛߟྀ͢ΔͨΊͰ ͢ɻTrueTime ͸ɺσʔληϯλʔ͝ͱʹଘࡏ͢ΔλΠϜϚελϚγϯηοτʢ GPS ࣌ܭͱݪࢠ࣌ ܭͷηοτʣͱɺϚγϯ͝ͱʹଘࡏ͢ΔλΠϜεϨʔϒσʔϞϯʹΑ࣮ͬͯ૷͞Ε͍ͯ·͢ɻλΠϜ εϨʔϒσʔϞϯ͸ɺࣗ਎ͷλΠϜϚελϚγϯηοτΛఆظతʹࢀর͠ɺαʔόͷϩʔΧϧʹଘࡏ ͢ΔγεςϜΫϩοΫͱಉظͤ͞·͢ɻ TrueTime ͷ࣌ؒൣғ͸ɺ ʢ[ t-Џ, t+ Џ ]ʣͷΑ͏ʹද͞Εɺt ͕ʮݱࡏ࣌ࠁͷਪఆ஋ʯͰ͋Γɺ TrueTime ͕γεςϜΫϩοΫΛ΋ͱʹࢉग़͓͓ͨ͠Αͦͷ࣌ࠁͰ͢ɻЏ ͸ʮෆ࣮֬ੑͷൣғʯͰ ͋ΓɺΫϩοΫʹର͢ΔζϨ΍ෆਖ਼֬͞Λද͠·͢ɻЏͷൣғ಺ʹ࣮ࡍͷਖ਼֬ͳ࣌ࠁ͕ଘࡏ͍ͯ͠Δ ͱਪఆͰ͖·͢ɻЏ ͷ஋͸ɺ֤ϙʔϦϯάͷ֤ײ֮ʹ͓͍ͯ໿̍ʙ̓ϛϦඵͷؒͰมԽ͠·͢ɻ΄ ͱΜͲͷ৔߹ Џ ͸̐ϛϦඵͰϙʔϦϯάͷײ֮͸ 30 ඵͰ͢ɻ͜ͷΑ͏ʹ෼ࢄγεςϜؒͷΫϩο ΫͷζϨΛߟྀ࣌ؒ͠ൣғΛฦ͢͜ͱͰɺαʔόؒͰτϥϯβΫγϣϯͷ࣮ߦॱং΍σʔλͷҰ؏ੑ Λอূ͢ΔͨΊͷൺֱ͕ՄೳʹͳΓ·͢ɻ Paxos paxos ͸ɺ෼ࢄγεςϜͰಈ࡞͢ΔίϯϐϡʔλͲ͏͕͠ɺ߹ҙܗ੒Λߦ͏ͨΊͷϓϩτίϧʢΞ ϧΰϦζϜʣͰ͢ɻ෼ࢄγεςϜͰ͸ɺෳ਺ͷαʔό͕ڠྗͯ͠σʔλΛॲཧ͠·͕͢ɺͦΕΒ͕ಉ ܾ͡ఆʢ߹ҙʣʹࢸΔඞཁ͕͋Γ·͢ɻpaxos ͸ɺωοτϫʔΫͷ஗Ԇ΍αʔόো֐͕ൃੜͨ͠৔߹ Ͱ΋ɺҰ؏ͨ͠߹ҙΛಘΔ͜ͱ͕Ͱ͖Δ͘͠ΈͰ͢ɻ paxos Ͱ͸ɺ෼ࢄγεςϜ಺ͷαʔό͕ɺಛఆͷఏҊʹରͯ͠߹ҙΛಘΔͨΊʹ 3 ͭͷ໾ׂΛՌͨ ͠·͢ɻ 55
  58. ୈ 3 ষ Cloud Spanner ͷ͘͠ΈΛཧղ͢Δ 3.5 ֎෦੔߹ੑΛࢧ͑Δ͘͠Έ • ϓϩϙʔβʢ

    Proposer ʣ: ఏҊΛߦ͏໾ׂͰ͢ɻ ʮ͜ͷσʔλΛ৽͍͠ਖ਼͍͠σʔλͱͯ͠࠾ ༻͠Α͏ʯͱ͍͏ఏҊΛ΄͔ͷαʔόʹૹ৴͠·͢ɻ • ΞΫηϓλʢ Acceptor ʣ: ఏҊΛड͚औΓɺ߹ҙ͢Δ͔Ͳ͏͔Λܾఆ͢Δ໾ׂͰ͢ɻෳ਺ͷ ΞΫηϓλ͕ಉҙ͢Ε͹ɺఏҊ͕ঝೝ͞Ε·͢ɻ • ϥʔφʔʢ Learner ʣ: ߹ҙܗ੒ͷϓϩηεʹ͸ࢀՃͤͣɺ߹ҙ͕੒ཱͨ͠ఏҊʢܾఆʣΛֶ ͼɺγεςϜશମʹ൓ө͠·͢ɻ Spanner ͸γϯάϧϦʔδϣϯߏ੒ͷ৔߹ɺ࠷௿Ͱ΋̏ͭͷϨϓϦΧΛҟͳΔκʔϯʹ࣋ͪ·͢ɻ ͜ͷ 3 ͭͷϨϓϦΧΛ؅ཧ͢ΔεύϯαʔόͰ paxos άϧʔϓΛߏ੒͠·͢ɻ͏͕ͪ̍ͭϦʔμʔ ͱͳΓɺ࢒Γ 2 ͭ͸ϑΥϩϫʔͱͳΓ·͢ɻϦʔμʔ͸ϓϩϙʔβͱͯ͠΄͔ͷϨϓϦΧʹσʔλͷ ߋ৽ΛఏҊ͠ɺ΄͔ͷϨϓϦΧʢ͜ͷ৔߹͸ɺΞΫηϓλʣ͔Βಉҙ͞Εͨ৔߹ʹσʔλΛߋ৽͠· ͢ɻ߹ҙܗ੒ʹ͸ϑΥϩϫʔͷա൒਺ͷ߹ҙ͕ඞཁͰɺ3 ͭͷεύϯαʔόͰ paxos άϧʔϓΛߏ੒ ͍ͯ͠Δ৔߹͸ 2 ͭͷϑΥϩϫʔ͔Βͷ߹ҙ͕ඞཁʹͳΓ·͢ɻա൒਺ͷ߹ҙ͕ಘΒΕΔͱϦʔμʔ ͸ϑΥϩϫʔʹ߹ҙΛ௨஌͠ɺσʔλʹ൓ө͠·͢ɻ ຊষͷୈ 4 અʹͯ঺հͨ͠ Spanner ͷΞʔΩςΫνϟͱຊઅͷ paxos ΛؚΊͯશମతͳΠϝʔδ ΛҎԼʹࣔ͠·͢ɻ ਤ 3.2: Spanner ͷΞʔΩςΫνϟ τϥϯβΫγϣϯ Spanner ͸ɺߴ͍εέʔϥϏϦςΟͱڧ͍σʔλ੔߹ੑΛཱ྆͢ΔͨΊʹɺಠࣗͷτϥϯβΫγϣ ϯॲཧΛఏڙ͍ͯ͠·͢ɻલड़ͷ֎෦੔߹ੑ΍ TrueTime APIɺpaxos ϓϩτίϧͱ૊Έ߹ΘͤΔ ͜ͱͰɺ෼ࢄ؀ڥͰ΋Ұ؏ੑͷ͋Δσʔλૢ࡞͕Մೳͱͳ͍ͬͯ·͢ɻSpanner ͕αϙʔτ͢Δτϥ ϯβΫγϣϯʹ͍ͭͯ঺հ͠·͢ɻ 56
  59. ୈ 3 ষ Cloud Spanner ͷ͘͠ΈΛཧղ͢Δ 3.5 ֎෦੔߹ੑΛࢧ͑Δ͘͠Έ ࿦จͰ͸ɺҎԼͷτϥϯβΫγϣϯͷ಺෦ಈ࡞ʹ͍ͭͯड़΂ΒΕ͍ͯ·ͨ͠ɻ •

    Read-Write Transactionɿݫີͳ̎ϑΣʔζϩοΫͱ paxos Λར༻ͨ͠ॻ͖ࠐΈૢ࡞ • Snapshot Transactionɿॻ͖ࠐΈͷͳ͍τϥϯβΫγϣϯͰɺγεςϜ͕બΜͩλΠϜελϯ ϓͰ࣮ߦ͞ΕɺϩοΫΛ࢖༻ͤͣʹσʔλΛಡΈऔΔૢ࡞ ͳ͓ɺGoogle ͕ެ։͍ͯ͠Δ Spanner ͷτϥϯβΫγϣϯʹ͍ͭͯ঺հ͢ΔυΩϡϝϯτ*6Λݟ Δͱɺݱࡏ Spanner ͸ҎԼͷτϥϯβΫγϣϯΛαϙʔτ͍ͯ͠ΔΑ͏Ͱ͢ɻ • ಡΈऔΓ / ॻ͖ࠐΈτϥϯβΫγϣϯʢRead-write transactionsʣ • ಡΈऔΓઐ༻τϥϯβΫγϣϯʢRead-only transactionsʣ • ύʔςΟγϣϯԽ DML τϥϯβΫγϣϯʢPartitioned DML transactionsʣ ࠓճ͸࿦จͰ঺հ͞Ε͍ͯͨτϥϯβΫγϣϯʹϑΥʔΧε͠·͢ɻ Read-Write Transaction ॻ͖ࠐΈΛߦ͏τϥϯβΫγϣϯͰ͸ݫີͳ̎ϑΣʔζϩοΫΛ࢖༻͍ͯ͠·͢ɻ·ͣ͸τϥϯβ ΫγϣϯͰඞཁ͕͋ΔσʔλͷϩοΫΛऔಘ͠·͢ɻ͢΂ͯͷϩοΫ͕औಘ͞ΕΔͱɺϩοΫղ์· ͰλΠϜελϯϓͷׂΓ౰͕ͯՄೳʹͳΓ·͢ɻΫΤϦʹै͍ɺඞཁʹԠͯ͡σʔλͷಡΈऔΓͱม ߋΛߦ͍·͢ɻSpanner Ͱ͸̎ϑΣʔζίϛοτΛ࠾༻͓ͯ͠Γɺσʔλͷૢ࡞͕׬ྃ͢Δͱίϛο τ४උʹೖΓ·͢ɻίϛοτ४උ͕Ͱ͖Δͱ TrueTime API ͔Β࣌ؒൣғͷऔಘɺpaxos ʹΑΔ߹ ҙܗ੒Λߦ͍·͢ɻ߹ҙ͕ಘΒΕΔͱίϛοτΛߦ͍ɺϩοΫΛղ์͠·͢ɻίϛοτΛߦ͏ࡍʹ͸ औಘͨ࣌ؒ͠ൣғͷ࠷େ஋ʢ࠷΋ϩʔΧϧΫϩοΫͷζϨ͕େ͖͔ͬͨ৔߹ʹߟ͑͏Δ஗͍࣌ࠁʣ͕ ܦա͢Δ·Ͱίϛοτͷ࣮ࢪΛ଴ػ͠·͢ɻ࣌ؒൣғͷ࠷େ஋·Ͱ଴ػ͢Δ͜ͱͰɺ࣮֬ʹ΄͔ͷτ ϥϯβΫγϣϯૢ࡞ͱॏෳ͕ൃੜ͠ͳ͍͜ͱΛอূ͠·͢ɻίϛοτ͞ΕΔ·Ͱ͸ɺτϥϯβΫγϣ ϯʹରͯ͠λΠϜελϯϓׂ͕Γ౰ͯΒΕΔ͜ͱ͸ͳ͘ɺผͷτϥϯβΫγϣϯʹಡΈऔΒΕΔ͜ͱ ΋͋Γ·ͤΜɻ Snapshot Transaction Spanner ͸ɺτϥϯβΫγϣϯ΍σʔλʹλΠϜελϯϓׂ͕Γ౰ͯΒΕ͓ͯΓτϥϯβΫγϣ ϯͰಡΈऔΔσʔλ͸෼ࢄͯ͠อଘ͞Ε͍ͯΔͨΊɺͦΕͧΕͷσʔλΛ࣋ͭϊʔυ͝ͱʹσʔλ ͷಡΈࠐΈର৅ͱ͢ΔεφοϓγϣοτͷλΠϜελϯϓΛࢦఆ͢Δඞཁ͕͋Γ·͢ɻର৅ͱͳΔ σʔλ͕ 1 ͭͷ paxos άϧʔϓʹଘࡏ͢Δ৔߹ɺ࠷ޙʹ ίϛοτ͞Εͨॻ͖ࠐΈͷλΠϜελϯϓ Λ΋ͱʹεφοϓγϣοτͷಡΈࠐΈΛߦ͍·͢ɻσʔλ͕ෳ਺ͷ paxos άϧʔϓʹΘͨΔ৔߹ɺ TrueTime API ͔Β ݱࡏͷ࣌ࠁʹ͓͚Δ࠷େͷՄೳͳλΠϜελϯϓΛऔಘ֤͠ paxos άϧʔϓ͔ ΒεφοϓγϣοτͷಡΈࠐΈΛߦ͍·͢ɻ *6 A peek behind Colossus, Googleʟs file system | Google Cloud Blog: https://cloud.google.com/spanner/docs/transactions?hl=ja#read-only_transactions 57
  60. ୈ 3 ষ Cloud Spanner ͷ͘͠ΈΛཧղ͢Δ 3.6 ऴΘΓʹ 3.6 ऴΘΓʹ

    ຊষͰ͸ɺGoogle ͕ެ։͍ͯ͠Δʮ Cloud Spanner: Googleʟs Globally Distributed Database ʯΛࢲ͕ಡΈɺ಺༰ʹ͍ͭͯ·ͱΊ·ͨ͠ɻSpanner ͸ɺࣗ਎͕ܞΘ͍ͬͯΔϓϩμΫτͰར༻ͯ͠ ͍ΔͨΊɺ͋Δఔ౓ͷ಺෦తͳ͘͠Έʹରͯ͠΋஌ࣝΛ࣋ͪ߹Θ͍ͤͯΔͭ΋ΓͰ͕ͨ͠ɺॳΊͯ஌ Δ͜ͱ͕ଟ͘ Spanner ʹର͢Δཧղ͕ਂ·Γ·ͨ͠ɻ ಛʹɺຊষ 4 અͰ৮ΕͨΞϓϦέʔγϣϯͰѻ͏σʔλ͕ͲͷΑ͏ʹ෺ཧతʹ؅ཧ͞Ε͍ͯΔͷ͔ ͸ֶͼʹͳΓ·ͨ͠ɻීஈεΩʔϚ΍ΠϯσοΫεΛઃܭ͢ΔࡍʹɺϗοτεϙοτͷճආͩͬͨΓ ςʔϒϧؒͷΠϯλϦʔϒʹؔͯ͠ࢥߟΛ८Βͤ·͕͢ɺࠓޙ͸ΑΓೲಘײΛ࣋ͬͯऔΓ૊ΊΔͱײ ͡·ͨ͠ɻ·ͨɺSpanner ͷΞʔΩςΫνϟ͸·ͬͨ͘஌Βͳ͔ͬͨͨΊڵຯਂ͔ͬͨͰ͢ɻ Google ͸ຊষͰऔΓ্͛ͨ࿦จҎ֎ʹ΋ɺSpanner ΍ BigtableɺBigQuery ͷ࿦จΛެ։ͯ͠ ͍·͢ɻͥͻؾʹͳͬͨํ͸ಡΜͰΈͯ͸͍͔͕Ͱ͠ΐ͏͔ʁ Spanner ͕։ൃ͞Εͨഎܠʹ͸ɺ Bigtable ͱ MegaStore ͕ؔ܎͍ͯ͠Δͱͷ͜ͱͰɺࢲ͸࣍͸ Bigtable ͷ࿦จΛಡΜͰΈ͍ͨͱࢥ ͍·͢ɻ 3.7 ࢀߟ ࿦จΛಡΈਐΊΔதͰࢀߟʹ͍͍ͤͯͨͩͨ͞هࣄΛ঺հ͍͖ͤͯͨͩ͞·͢ɻ • Spanner*7 • Google ͷ Spanner ʹؔ͢Δ࿦จͷ࿨༁ 1/6*8 • Google ͷ Spanner ʹؔ͢Δ࿦จͷ࿨༁ 2/6*9 • Google ͷ Spanner ʹؔ͢Δ࿦จͷ࿨༁ 3/6*10 • Spanner: TrueTime ͱ֎෦੔߹ੑ*11 • Cloud Spanner ͕֎෦੔߹ੑΛఏڙ͢Δཧ༝*12 *7 Spanner #ϙΤϜ - Qiita@: https://qiita.com/kumagi/items/7dbb0e2a76484f6c522b *8 Google ͷ Spanner ʹؔ͢Δ࿦จͷ࿨༁ 1/6 - 451 Unavailable For Legal Reasons: https://blog.game- programmer.jp/entry/2017/02/28/141653 *9 Google ͷ Spanner ʹؔ͢Δ࿦จͷ࿨༁ 2/6 - 451 Unavailable For Legal Reasons: https://blog.game- programmer.jp/entry/2017/03/02/201647 *10 Google ͷ Spanner ʹؔ͢Δ࿦จͷ࿨༁ 3/6 - 451 Unavailable For Legal Reasons: https://blog.game- programmer.jp/entry/2017/03/03/194051 *11 Spanner: TrueTime ͱ ֎ ෦ ੔ ߹ ੑ: https://cloud.google.com/spanner/docs/true-time-external- consistency?hl=ja#2 *12 Cloud Spanner ͕֎෦੔߹ੑΛఏڙ͢Δཧ༝: https://cloud.google.com/blog/ja/products/gcp/why-you- should-pick-strong-consistency-whenever-possible 58
  61. ୈ 4 ষ ೖ໳ Bzlmod ຊߘͰ͸ɺϏϧυπʔϧʮBazel*1ʯʹόʔδϣϯ 5 ͔Βࢼݧతʹಋೖ͞Ε࢝Ίͨɺ֎෦ґଘύο έʔδͷ؅ཧγεςϜ Bzlmod

    ʹ͍ͭͯ঺հ͠·͢ɻ ΋ͱ΋ͱ Bazel Ͱ͸ɺ֎෦ґଘύοέʔδΛ؅ཧ͢Δͷʹ WORKSPACE ϑΝΠϧΛར༻͍ͯ͠·͠ ͨɻ͜ͷϑΝΠϧʹɺ࢖͍͍ͨ֎෦ύοέʔδΛΠϯϙʔτ͢ΔΑ͏ͳίʔυΛྻڍ͢ΔΑ͏ͳΠ ϝʔδͰ͢ɻ͜ͷํ๏Ͱ͸ɺґଘύοέʔδؒͷґଘؔ܎Λ͏·͘ղܾͯ͘͠Εͳ͍ͨΊɺґଘύο έʔδͷόʔδϣϯΛ্͛Δ͚ͩͰϏϧυͰ͖ͳ͘ͳͬͨΓͯ͠·ͨ͠ɻͦΕΛղܾ͢ΔͨΊʹొ৔ ͨ͠ͷ͕ Bzlmod Ͱ͢ɻBzlmod ͸όʔδϣϯ 5 ͔Βࢼݧతʹಋೖ͞Εɺ࣍ͷϝδϟʔόʔδϣϯ ΞοϓͰ͋Δόʔδϣϯ 8 Ͱඪ४ʹͳΓɺόʔδϣϯ 9 ͔Β͸ WORKSPACE ϑΝΠϧΛར༻ͨ͠ैདྷ ͷํ๏͕࡟আ͞ΕΔ༧ఆͰ͢ɻ ͦ͜ͰຊߘͰ͸ɺ2024 ೥ 10 ݄࣌఺Ͱͷ҆ఆόʔδϣϯͰ͋Δ Bazel όʔδϣϯ 7 Λར༻ͨ͠Ξ ϓϦέʔγϣϯΛ࡞Γͳ͕ΒɺBzlmod ʹ͍ͭͯ঺հ͠·͢ɻ 4.1 αϯϓϧϓϩάϥϜ ຊ୊ʹೖΔલʹɺαϯϓϧϓϩάϥϜʹ͍ͭͯ঺հ͠·͢ɻࠓճ͸ɺGo ͱ Elm Λ༻͍ͨ؆қత ͳ Web ΞϓϦέʔγϣϯΛαϯϓϧίʔυͱͯ͠࢖͍·͢ɻεςοϓόΠεςοϓʹ֦ு͢Δ͜ͱ Ͱɺঃʑʹొ৔ਓ෺Λ૿΍͠ͳ͕Β Bazlmod ʹ͍ͭͯ঺հ͠·͢ɻ 1. HTML ϑΝΠϧΛฦ͚ͩ͢ͷ Go ΞϓϦέʔγϣϯ 2. Echo Λ࢖ͬͨ Go ΞϓϦέʔγϣϯ 3. Bazel Λ࢖ͬͯίϯςφΠϝʔδΛ࡞Δ 4. Elm Λ࢖ͬͨ Web ϖʔδΛฦ͢ 5. ͓·͚ɿ OpenAPI Λ࢖ͬͨগ͠ϦονͳΞϓϦέʔγϣϯ GitHub ϦϙδτϦ͸ matsubara0507/sample-go-bzlmod Ͱ͢*2ɻ *1 https://bazel.build/ *2 https://github.com/matsubara0507/sample-go-bzlmod 59
  62. ୈ 4 ষ ೖ໳ Bzlmod 4.2 Bazel 4.2 Bazel ·ͣ࠷ॳʹɺBazel

    ʹ͍ͭͯ঺հ͠·͢ɻBazel ͸࣍ͷΑ͏ͳಛ௃Λ࣋ͭϏϧυπʔϧͰ͢ɻ • ϏϧυΛಠࣗͷαϯυϘοΫε؀ڥͷதͰߦ͏ • ґଘؔ܎Λ໌ه͢ΔͨΊΩϟογϡޮ཰͕ྑ͍ • Starlark ͱ͍͏ Python αϒηοτͳݴޠͰ֦ுՄೳ ґଘؔ܎͕໌ه͞Ε͍ͯΔ͜ͱʹΑͬͯɺมߋʹରͯ͠࠷খݶͷϏϧυͰ੒Ռ෺ΛߏஙͰ͖ͨΓɺ ฒྻ࣮ߦʹΑͬͯߴ଎Խͨ͠ΓͰ͖·͢ɻཪΛฦ͢ͱɺ͜ͷґଘؔ܎Λ໌ه͢Δͷ͕͍ͨ΁ΜͰ͢ ͕ɺύοέʔδʹΑͬͯ͸൒ࣗಈతʹґଘؔ܎Λ໌هͨ͠෦෼Λੜ੒ͯ͘͠Ε·͢ɻ ֎෦ύοέʔδ ຊߘͷओ୊Ͱ΋͋Δɺ֎෦ύοέʔδΛ࢖͏͜ͱͰɺ؆୯ʹ͞·͟·ͳϓϩάϥϜͷϏϧυ΍ςε τͷ࣮ߦΛಉ͡ΠϯλϑΣʔεͰߦ͏͜ͱ͕Ͱ͖·͢ɻ֎෦ύοέʔδʹ͸ɺͨͱ͑͹࣍ͷΑ͏ͳ΋ ͷ͕͋Γ·͢ɻ • bazelbuild/rules_go : Go ϓϩάϥϜͷϏϧυ΍ςετͳͲ*3 • bazel-contrib/rules_oci : OCI ίϯςφΠϝʔδͷϏϧυ΍ϓογϡͳͲ*4 • kczulko/rules_elm : Elm ϓϩάϥϜͷϏϧυ΍ςετͳͲ*5 • matsubara0507/rules_openapi : openapi-generator-cli Λ࢖͑ΔΑ͏ʹ͢Δ*6 αʔυύʔςΟ੡΋ؚΊͯ͞·͟·ͳύοέʔδ͕͋Γ·͢ɻ ߏ੒ཁૉɾ༻ޠ Bazel ͷߏ੒ཁૉ΍༻ޠʹ͍ͭͯ؆୯ʹઆ໌͠·͢ɻ·ͣɺBazel Ͱ͸ GitHub ϦϙδτϦͰ؅ཧ ͢ΔΑ͏ͳ୯ҐΛϫʔΫεϖʔεͱݺͼ·͢ɻ·ͨɺϏϧυ͢ΔͨΊͷઃఆ͸ MODULE.bazel ͱ BU ILD.bazel ͷ 2 छྨͷϑΝΠϧ΁هड़͍͖ͯ͠·͢ɻ • MODULE.bazel ͸ɺϫʔΫεϖʔεͷϧʔτσΟϨΫτϦʹҰ͚ͭͩஔ͖·͢ɻґଘύοέʔ δ΍ॲཧܥɺར༻͢ΔϕʔεΠϝʔδͳͲϏϧυʹඞཁͳϦιʔεʹؔ͢ΔઃఆΛهड़͠ ·͢ɻ • BUILD.bazel ͸ɺϫʔΫεϖʔεͷσΟϨΫτϦ΁ 1 ͭͣͭஔ͘͜ͱ͕Ͱ͖·͢ɻͦͷσΟ ϨΫτϦ಺ͰϏϧυ͢Δ΋ͷࣗମͷઃఆΛهड़͠·͢ɻ *3 https://github.com/bazelbuild/rules_go *4 https://github.com/bazel-contrib/rules_oci *5 https://github.com/kczulko/rules_elm *6 https://github.com/matsubara0507/rules_openapi 60
  63. ୈ 4 ষ ೖ໳ Bzlmod 4.3 Bzlmod Λ࢖ͬͨΞϓϦέʔγϣϯ։ൃ ྆ϑΝΠϧ಺Ͱهड़͢Δؔ਺ɾϝιουͷΑ͏ͳ΋ͷΛɺBazel Ͱ͸ϧʔϧͱݺͼ·͢ɻBUILD.b

    azel Ͱهड़ͨ͠ϧʔϧ͸ɺbazel build ίϚϯυͰϏϧυ͕͞Ε·͢ɻ·ͨɺόΠφϦϑΝΠϧ ͷΑ͏ʹɺϏϧυͯ͠Ͱ͖ͨ੒Ռ෺͕࣮ߦՄೳͳ৔߹͸ bazel run ίϚϯυͰ࣮ߦ͢Δ͜ͱ΋Ͱ͖ ·͢ɻ 4.3 Bzlmod Λ࢖ͬͨΞϓϦέʔγϣϯ։ൃ લड़ͨ͠௨ΓɺεςοϓόΠεςοϓʹ Go ΞϓϦέʔγϣϯΛ֦ு͍͖ͯ͠·͢ɻ·ͣ͸ɺґଘ ύοέʔδͳͲ؅ཧ͢Δ MODULE.bazel Λ഑ஔ͠·͢ɻ MODULE.bazel module( name = "sample-go-bzlmod", version = "1.0.0", ) ͜ͷϑΝΠϧʹ௥ه͍͖ͯ͠·͢ɻͪͳΈʹɺBazel όʔδϣϯ 5ʙ7 Ͱ Bzlmod Λ࢖͏ʹ͸ -- enable_bzlmod Φϓγϣϯ͕ඞཁͰ͢ɻ·ͨɺBazel Ͱ͸ .bazelrc ϑΝΠϧΛར༻͢Δ͜ͱͰɺ σϑΥϧτͰ෇༩͢ΔΦϓγϣϯͳͲΛઃఆͰ͖·͢ɻ .bazelrc common --enable_bzlmod STEP1: HTML ϑΝΠϧΛฦ͚ͩ͢ͷ Go ΞϓϦέʔγϣϯ ·ͣ͸ɺHTML ϑΝΠϧΛฦ͚ͩ͢ͷΞϓϦέʔγϣϯͰ͢ɻBazel Ͱ Go ΛϏϧυ͢Δʹ͸ɺ rules_go ύοέʔδΛ࢖͍·͢ɻ MODULE.bazel module( name = "sample-go-bzlmod", version = "1.0.0", ) bazel_dep(name = "rules_go", version = "0.50.1") bazel_dep(name = "gazelle", version = "0.39.1") go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") go_sdk.download(version = "1.23.1") 61
  64. ୈ 4 ষ ೖ໳ Bzlmod 4.3 Bzlmod Λ࢖ͬͨΞϓϦέʔγϣϯ։ൃ bazel_dep ͰґଘύοέʔδΛهड़͍ͯ͠·͢ɻར༻Մೳͳύοέʔδ͸

    Bazel Central Registry Ͱௐ΂Δ͜ͱ͕Ͱ͖·͢*7ɻgazelle ͸ɺrules_go ʹؔ͢ΔϧʔϧΛࣗಈੜ੒ͯ͘͠ΕΔศརπʔϧ Ͱ͢*8ɻ࠷ޙͷ 2 ߦͰɺBazel Ͱ Go ίϯύΠϥΛར༻͢Δ͜ͱΛએݴ͍ͯ͠·͢ɻ લड़ͨ͠௨ΓɺMODULE.bazel ʹ͸ɺϏϧυ࣌ʹར༻͢ΔϦιʔεΛએݴ͢Δඞཁ͕͋Γ·͢ɻͦ ͷϦιʔεʹ͸ɺGo ίϯύΠϥͷΑ͏ͳॲཧܥ΋ؚ·ΕΔͨΊɺgo_sdk.download Ͱએݴ͍ͯ͠ ΔͷͰ͢ɻ͜͜Ͱએݴͨ͠ Go ίϯύΠϥ͸ɺ࣍ͷΑ͏ͳܗͰ࣮ߦͰ͖·͢ɻ % bazelisk run @rules_go//go -- mod init github.com/matsubara0507/sample-go-bzlmod INFO: Analyzed target @@rules_go~//go:go (7 packages loaded, 11370 targets configured). INFO: Found 1 target... Target @@rules_go~//go/tools/go_bin_runner:go_bin_runner up-to-date: bazel-bin/external/rules_go~/go/tools/go_bin_runner/bin/go INFO: Elapsed time: 1.030s, Critical Path: 0.03s INFO: 1 process: 1 internal. INFO: Build completed successfully, 1 total action INFO: Running command line: bazel-bin/.../bin/go mod init ... go: creating new go.mod: module github.com/matsubara0507/sample-go-bzlmod main.go ΍ go:embed ͢Δ index.html Λهड़ͨ͠Βɺ࣍ʹ BUILD.bazel ϑΝΠϧΛ༻ҙ͠· ͢ɻBUILD.bazel ʹ͸ɺGo όΠφϦΛϏϧυ͢ΔͨΊͷੜ੒ϧʔϧΛهड़͠·͢ɻGo όΠφϦͷ ੜ੒ϧʔϧ͸ɺrules_go ͷ go_binary ϧʔϧΛ࢖͍·͕͢ɺgazelle Λ࢖͑͹൒ࣗಈతʹੜ੒ͯ͠ ͘Ε·͢ɻ BUILD.bazel load("@gazelle//:def.bzl", "gazelle") gazelle(name = "gazelle") ͜ͷঢ়ଶͰ bazel run //:gazelle Λ࣮ߦ͢ΔͱɺBUILD.bazel ϑΝΠϧʹ main.go ͷத਎ Λݩʹͯ͠ go_binary ͳͲ͕ੜ੒͞Ε·͢ɻ BUILD.bazel load("@gazelle//:def.bzl", "gazelle") load("@rules_go//go:def.bzl", "go_binary", "go_library") gazelle(name = "gazelle") go_library( *7 https://registry.bazel.build/ *8 https://github.com/bazelbuild/bazel-gazelle/ 62
  65. ୈ 4 ষ ೖ໳ Bzlmod 4.3 Bzlmod Λ࢖ͬͨΞϓϦέʔγϣϯ։ൃ name =

    "sample-go-bzlmod_lib", srcs = ["main.go"], embedsrcs = ["index.html"], importpath = "github.com/matsubara0507/sample-go-bzlmod", visibility = ["//visibility:private"], ) go_binary( name = "sample-go-bzlmod", embed = [":sample-go-bzlmod_lib"], visibility = ["//visibility:public"], ) bazel build //:sample-go-bzlmod ͰϏϧυΛɺbazel run //:sample-go-bzlmod ͰϏϧ υ͔Β࣮ߦΛͰ͖·͢ɻ STEP2: Echo Λ࢖ͬͨ Go ΞϓϦέʔγϣϯ ࣍ʹ Go ͷ֎෦ύοέʔδΛ Bazel Ͱ࢖͍·͢ɻmain.go ΛɺEcho*9 Λ࢖ͬͨܗʹॻ͖׵͑ͯɺ bazel run @rules_go//go -- mod tidy Λ࣮ߦͯ͠ go.mod Λߋ৽͠·͢ɻGo ͷґଘύοέʔ δΛ Bazel Ͱѻ͏ʹ͸ɺgo mod vendor Λ࢖͏͔ɺMODULE.bazel ʹهड़͢Δඞཁ͕͋Γ·͢ɻ͜ ͜Ͱ͸ޙऀͷํ๏ΛऔΓ·͕͢ɺgazelle Λ࢖͏͜ͱͰɺ΄ͱΜͲΛ go.mod ͔Βੜ੒Ͱ͖·͢ɻ MODULE.bazel module( name = "sample-go-bzlmod", version = "1.0.0", ) ... go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") go_deps.from_file(go_mod = "//:go.mod") ͜ͷঢ়ଶͰ bazel mod tidy Λ࣮ߦ͢Δͱɺgo.mod Ͱએݴ͞Ε͍ͯΔґଘύοέʔδΛ MODUL E.bazel ΁ॻ͖Ճ͑ͯ͘Ε·͢ɻͦͷޙɺbazel run //:gazelle Λ࠶࣮ߦ͢Δͱ BUILD.bazel ͕ߋ৽͞Εͯɺbazel run //:sample-go-bzlmod ͕࣮ߦͰ͖·͢ɻ *9 https://echo.labstack.com/ 63
  66. ୈ 4 ষ ೖ໳ Bzlmod 4.3 Bzlmod Λ࢖ͬͨΞϓϦέʔγϣϯ։ൃ STEP3: Bazel

    Λ࢖ͬͯίϯςφΠϝʔδΛ࡞Δ Bazel Ͱ͸ rules_oci ͱ rules_pkg Λ࢖͏͜ͱͰɺγϯϓϧͳίϯςφΠϝʔδΛ؆୯ʹ࡞Δ͜ ͱ͕Ͱ͖·͢ɻ·ͣ͸ɺґଘ͢Δ֎෦ύοέʔδͱɺϕʔεΠϝʔδͷऔಘΛ MODULE.bazel ΁ه ड़͠·͢ɻ MODULE.bazel module( name = "sample-go-bzlmod", version = "1.0.0", ) ... bazel_dep(name = "rules_oci", version = "2.0.0") bazel_dep(name = "rules_pkg", version = "1.0.1") oci = use_extension("@rules_oci//oci:extensions.bzl", "oci") oci.pull( name = "distroless_base", image = "gcr.io/distroless/base-debian12", platforms = ["linux/amd64"], tag = "latest", ) distroless ͱ͍͏ͷ͸ɺGoogle ͕ఏڙ͍ͯ͠ΔίϯύΫτͳίϯςφΠϝʔδͰɺshell ΍ύο έʔδϚωʔδϟʔ͢Βؚ·Ε·ͤΜ*10ɻ͜ͷঢ়ଶͰ·ͨ bazel mod tidy Λ࣮ߦͯ͠ɺMODULE. bazel Λߋ৽͓͖ͯ͠·͢ɻ͋ͱ͸ɺBUILD.bazel ΁࡞੒͢ΔίϯςφΠϝʔδͷϧʔϧΛهड़͠ ·͢ɻ BUILD.bazel ... load("@rules_pkg//pkg:tar.bzl", "pkg_tar") load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load") pkg_tar( name = "tar", srcs = [":sample-go-bzlmod"], ) oci_image( name = "image", *10 https://github.com/GoogleContainerTools/distroless 64
  67. ୈ 4 ষ ೖ໳ Bzlmod 4.3 Bzlmod Λ࢖ͬͨΞϓϦέʔγϣϯ։ൃ base =

    "@distroless_base", tars = [":tar"], entrypoint = ["/sample-go-bzlmod"], ) oci_load( name = "load", image = ":image", repo_tags = ["sample-go-bzlmod:latest"], ) bazel build //:image Ͱ OCI ίϯςφΠϝʔδͷϏϧυΛɺbazel run //:load Ͱ OCI ί ϯςφΠϝʔδΛಡΈࠐΜͰ Docker ΠϝʔδͷϏϧυΛ͠·͢ɻ % bazelisk run --platforms=@rules_go//go/toolchain:linux_amd64 //:load ... INFO: Running command line: bazel-bin/load.sh Loaded image: sample-go-bzlmod:latest % docker run --rm -p 8080:8080 sample-go-bzlmod:latest ... ͪͳΈʹɺBazel Λར༻ͯ͠ Mac Ͱ Linux ༻ͷ Go όΠφϦΛϏϧυ͢Δʹ͸ --platforms=@ rules_go//go/toolchain:linux_amd64 Φϓγϣϯ͕ඞཁͰ͢ɻ STEP4: Elm Λ࢖ͬͨ Web ϖʔδΛฦ͢ Elm ΛϏϧυ͢Δʹ͸ɺrules_elm Λར༻͠·͢ɻ MODULE.bazel module( name = "sample-go-bzlmod", version = "1.0.0", ) ... bazel_dep(name = "rules_elm", version = "1.0.1") elm = use_extension("@rules_elm//elm:extensions.bzl", "elm") elm.repository( name = "elm_package_elm_core", sha256 = "6e37b11c88c89a68d19d0c7625f1ef39ed70c59e443def95e4de98d6748c80a7", strip_prefix = "core-1.0.5", 65
  68. ୈ 4 ষ ೖ໳ Bzlmod 4.3 Bzlmod Λ࢖ͬͨΞϓϦέʔγϣϯ։ൃ urls =

    ["https://github.com/elm/core/archive/1.0.5.tar.gz"], ) ... rules_elm ʹ͸ɺgazelle ͷΑ͏ʹ elm.json ϑΝΠϧ*11͔ΒґଘύοέʔδΛੜ੒͢Δπʔϧ͕ ͋Γ·ͤΜɻͰ͢ͷͰɺࣗ෼Ͱґଘ͍ͯ͠Δ Elm ͷ֎෦ύοέʔδΛྻڍ͢Δඞཁ͕͋Γ·͢ɻ ·ͨɺBazel ͰϏϧυ͠΍͍͢Α͏ʹɺ࣍ͷΑ͏ͳσΟϨΫτϦߏ੒΁มߋ͠·͢ɻ sample-go-bzlmod/ |- front/ | |- BUILD.bazel | |- embed.go | |- index.html | \- Main.elm |- BUILD.bazel |- go.mod |- main.go \- MODULE.bazel BUILD.bazel ͸ MODULE.bazel ͱҟͳΓɺσΟϨΫτϦ͝ͱ΁഑ஔͰ͖·͢ɻrules_go ͷ৔߹ ͸ɺGo ύοέʔδ͝ͱʹ഑ஔ͢ΔΑ͏ʹͳ͍ͬͯ·͢ɻfront ύοέʔδ͸ɺindex.html ͱ Elm Ͱੜ੒ͨ͠ JavaScript Λ go:embed ͢Δ͚ͩͷ΋ͷͰ͢ɻgazelle Λར༻ͭͭ͠ɺ࣍ͷΑ͏ͳ BUIL D.bazel ͕͋Γ·͢ɻ front/BUILD.bazel load("@rules_elm//elm:defs.bzl", "elm_binary") load("@rules_go//go:def.bzl", "go_library") elm_binary( name = "main", main = "Main.elm", deps = [ "@elm_package_elm_html", ], ) go_library( name = "front", srcs = ["embed.go"], embedsrcs = [ ":main", # keep "index.html", *11 Elm ϓϩδΣΫτͷઃఆϑΝΠϧͰ͢ɻґଘ͢Δ Elm ͷ֎෦ύοέʔδͳͲ΋ྻڍ͞Ε͍ͯ·͢ɻ 66
  69. ୈ 4 ষ ೖ໳ Bzlmod 4.3 Bzlmod Λ࢖ͬͨΞϓϦέʔγϣϯ։ൃ ], importpath

    = "github.com/matsubara0507/sample-go-bzlmod/front", visibility = ["//visibility:public"], ) rules_elm ͷ elm_binary Λ࢖ͬͯ͜ͷΑ͏ʹهड़͢ΔͱɺMain.elm ͔Β main.js ͕ੜ੒͞Ε ·͢ɻgazelle ͸ɺੜ੒͞Εͨ JavaScript ϑΝΠϧʹґଘ͍ͯ͠Δ͜ͱΛೝࣝͰ͖ͳ͍ͷͰɺembe dsrcs ͷ ":main" ͷ෦෼͸ࣗ෼Ͱهड़͢Δඞཁ͕͋Γ·͢ɻͪͳΈʹɺkeep ͱίϝϯτ͓ͯ͘͠ ͱɺgazelle ͕ͦͷߦΛফ͞ͳ͘ͳΓ·͢ɻmain.go ଆͷมߋ͸ɺ͜͜Ͱ͸ׂѪ͠·͢ɻ ͜ΕͰࠓ·Ͱͱಉ͡Α͏ʹɺGo όΠφϦ΍ίϯςφΠϝʔδΛ Bazel ͰϏϧυ͢ΔͱɺElm ͷ ίϯύΠϧ΋૸ͬͯ Elm Ͱੜ੒ͨ͠ JavaScript ʹґଘͨ͠ΞϓϦέʔγϣϯ͕ߏங͞Ε·͢ɻ ͓·͚ɿ OpenAPI Λ࢖ͬͨগ͠ϦονͳΞϓϦέʔγϣϯ OpenAPI Λ࢖ͬͨίʔυ͸ΨϥοͱมΘͬͯ͠·͏ͨΊɺࡉ͔͘͸ݴٴ͠·ͤΜ*12ɻBzlmod ݻ ༗ͷ໰୊ͱͯ͠ɺࣗ࡞͍ͯ͠Δ rules_openapi ͕ Bazel Central Registry ʹͳ͍ͱ͍͏ͷ͕͋Γ· ͢ɻͦ͜Ͱ͜͜Ͱ͸ɺBazel Central Registry ʹͳ͍ύοέʔδΛ Bzlmod Ͱѻ͏ํ๏ʹ͍ͭͯઆ ໌͠·͢ɻ ·ͣલఏ৚݅ͱͯ͠ɺύοέʔδ͕ Bzlmod ʹରԠ͍ͯ͠ͳ͍ͱ͍͚·ͤΜʢ͕͢͞ʹ͜͜Ͱ͸ ׂѪ͠·͢ʣ ɻBazel Central Registry ͸ bazelbuild/bazel-central-registry ͱ͍͏ GitHub Ϧϙδ τϦͰ؅ཧ͞Ε͍ͯ·͢*13ɻϦϙδτϦͷ modules σΟϨΫτϦʹɺϨδετϦ͕ѻ͍ͬͯΔύο έʔδͷઃఆ͕ॻ͔Ε͍ͯΔͷͰɺϑΥʔϚοτΛਅࣅͯ͠ PR Λग़ͤ͹ϨδετϦʹ௥Ճ͞Ε· ͢ɻϑΥʔϚοτʹ͍ͭͯ͸ɺbazel-central-registry ϦϙδτϦͷ docs ʹҰԠ͋Γ·͢ʢΘ͔Γʹ ͍͘Ͱ͕͢ʣ ɻ·ͨɺbazel-contrib/publish-to-bcr ͱ͍͏ͷΛ࢖͏͜ͱͰ൒ࣗಈతʹߋ৽ PR Λ౤ ͛ͯ͘ΕΔͦ͏Ͱ͢ɻ ਓʹΑͬͯ͸ɺ Bazel Central Registry ʹ PR Λग़ͨ͘͠ͳ͍৔߹΋͋Δͱࢥ͍·͢ɻͦͷ৔߹͸ɺ bazel-central-registry ϦϙδτϦΛϑΥʔΫͯ͠ɺΦϓγϣϯͰϨδετϦΛࢦఆ͢Δ͜ͱͰղܾͰ ͖·͢ɻͨͱ͑͹ɺrules_openapi Λ௥Ճͨ͠ϨδετϦΛ matsubara0507/bazel-central-registry ϦϙδτϦͷ rules_openapi ϒϥϯνʹ༻ҙ͠·ͨ͠*14ɻ͜ΕΛ࢖͏ʹ͸ɺ--registroy Φϓγϣ ϯͰࢦఆ͢Δ͚ͩͰ͢ɻ .bazelrc common --registry=\ https://raw.githubusercontent.com/matsubara0507/bazel-central-registry/rules_openapi *12 ίʔυ͕ΨϥοͱมΘΔͨΊɺopenapi ϒϥϯνʹ෼͚ͯ·͢ https://github.com/matsubara0507/sample-g o-bzlmod/tree/openapi *13 https://github.com/bazelbuild/bazel-central-registry *14 https://github.com/matsubara0507/bazel-central-registry/tree/rules_openapi 67
  70. ୈ 4 ষ ೖ໳ Bzlmod 4.4 ͓͠·͍ github.com Ͱ͸ͳ͘ɺraw.githubusercontent.com Λ࢖͏఺ʹ஫ҙ͍ͯͩ͘͠͞ɻҰൠతͳ

    ϓϩάϥϛϯάݴޠͱҧͬͯগ͠खؒͰ͕͢ɺ͜ΕͰಠࣗύοέʔδΛ Bzlmod Ͱ࢖͏͜ͱ͕Ͱ͖ ·͢ɻ 4.4 ͓͠·͍ Bazel ͸ۀ຿Ͱ΋࢖͓ͬͯΓ*15ɺύοέʔδͷΞοϓσʔτͰύοέʔδؒͷґଘόʔδϣϯ͕ ่ΕͯɺಾͷΤϥʔ͕ى͜Δ͜ͱ͕͠͹͠͹͋Γ·ͨ͠ɻͦͷͨΊɺBzlmod ͸ Bazel Ϣʔβʔʹ ͱͬͯɺ͓ͦΒ͘೦ئͷػೳͰ͢ɻ͜ΕͰ Bazel ͷ࢒೦ͳͱ͜Ζ͕ݮͬͨͷͰɺ͜ΕΛػʹ Bazel Λ࢖ͬͯΈ͍ͯͩ͘͞ɻ *15 ͨͩ͠ɺۀ຿ͷํ͸·ͩ Bzlmod Λ࢖͍͑ͯ·ͤΜɻ͜ΕΛػʹ׬શཧղͰ͖ͯΑ͔ͬͨͰ͢ɻ 68
  71. ୈ 5 ষ όφʔੜ੒πʔϧͰσβΠφʔΛ޾ͤʹ ͔ͨͬͨ͠࿩ 5.1 ·͕͖͑ ॳΊ·ͯ͠ɺιʔγϟϧϕοςΟϯάࣄۀຊ෦ TIPSTAR ࣄۀ෦։ൃάϧʔϓͷࢁ୩ͱਃ͠·͢ɻ

    ຊষͰ͸ɺ؆қతͳόφʔੜ੒πʔϧΛ༻͍ͯɺσβΠφʔͷ՝୊ղܾΛ໨ࢦͨ࣌͠ͷ͓࿩Λͤͯ͞ ͍͖ͨͩ·͢ɻ 5.2 എܠ ·ͣॳΊʹɺͳͥσβΠφʔͷ՝୊ղܾΛࢼΈͨͷ͔Λઆ໌͠·͢ɻTIPSTAR Ͱ͸ɺαʔϏεͷ ࢪࡦʹ߹ΘͤͯɺόφʔΛ࡞੒͢Δػձ͕ଟ͋͘Γ·͢ɻͦͷ਺͸ଟ͘ɺσβΠφʔͷํʑ͕όφʔ ࡞੒ʹඅ΍࣌ؒ͢΋ଟ͘ͳΓ·͢ɻͻͱ͑ʹόφʔΛ࡞੒͢Δ࡞ۀͱݴͬͯ΋େ͖͘ 2 छྨ͋Γ· ͢ɻҰͭ͸ɺ৽نʹθϩ͔ΒσβΠϯ͢Δ΋ͷͰ͢ɻ΋͏Ұͭ͸͢ͰʹςϯϓϨʔτ͕ଘࡏ͢Δό φʔͷจࣈ΍೔෇͚ͩΛมߋ͢ΔΑ͏ͳ؆қతͳ΋ͷ͕͋Γ·͢ɻࠓճ͸ɺޙऀͷ؆қతͳόφʔ࡞ ੒ʹϑΥʔΧε͠ɺσβΠφʔͷํʑ͕ΑΓޮ཰తʹ࡞ۀΛਐΊΒΕΔΑ͏ͳπʔϧΛ࡞੒͢Δ͜ͱ ʹ͠·ͨ͠ɻػցతʹͰ͖Δ࡞ۀΛ؆қԽ͢Δ͜ͱʹΑΓɺ୯ௐͳ࡞ۀ࣌ؒΛ࡟ݮ͠ɺσβΠφʔͷ ํʑ͕ΑΓΫΦϦςΟͷߴ͍σβΠϯʹऔΓ૊Ή࣌ؒΛ֬อͰ͖Δͱߟ͔͑ͨΒͰ͢ɻ 5.3 πʔϧͷ֓ཁ ͜ͷܭը͸͋͘·Ͱ࣮ݧతͳ΋ͷͰͨ͠ɻσβΠφʔͷํʑ͕ͲͷΑ͏ͳπʔϧΛٻΊ͍ͯΔͷ ͔ɺ·ͨͲͷΑ͏ͳػೳ͕ඞཁͳͷ͔Λ஌ΔͨΊʹɺ·ͣ͸؆қతͳόφʔੜ੒πʔϧΛ࡞੒͢Δ͜ ͱʹ͠·ͨ͠ɻπʔϧͷཁ݅͸ɺҎԼͷ௨ΓͰ͢ɻ • ܾ·ͬͨςϯϓϨʔτͷόφʔΛ਺छྨબఆ͠ɺͦΕΒͷܾ·ͬͨՕॴͷจࣈ΍਺ࣈ΍೔෇Λ ؆୯ʹมߋͰ͖Δ • όφʔͷੜ੒͸ɺೖྗͨ͠಺༰Λݩʹը૾Λੜ੒͢Δ͜ͱͰߦ͏ 69
  72. ୈ 5 ষ όφʔੜ੒πʔϧͰσβΠφʔΛ޾ͤʹ͔ͨͬͨ͠࿩ 5.4 πʔϧͷٕज़બఆ • ͦΕΒͷมߋΛϒϥ΢β্Ͱߦ͏͜ͱ͕Ͱ͖Δ • ੜ੒͞Εͨը૾͸ɺͦͷ৔Ͱμ΢ϯϩʔυͰ͖Δ

    5.4 πʔϧͷٕज़બఆ πʔϧͷ։ൃ͸ɺαʔόνʔϜͷϝϯόʔͰߦ͏ͨΊɺ౰ॳ͸ Go ݴޠͷ API Ͱ OGP Λੜ੒͢ ΔҊΛݕ౼͍ͯ͠·ͨ͠ɻ͔͠͠ɺσβΠφʔͷํʑ͕࢖͍΍͍͢Α͏ʹɺϒϥ΢β্Ͱૢ࡞Ͱ͖Δ Α͏ʹ͢Δ͜ͱ͕ॏཁͰɺͦͷͨΊʹϑϩϯτΤϯυͷ։ൃ΋ඞཁʹͳΓ·͢ɻͦ͜ʹɺ޻਺͕͔͔ Δ͜ͱ͕༧૝͞Ε·ͨ͠ɻࠓճ͸࣮ݧతʹπʔϧΛ༻ҙ͠ɺσβΠφʔͷํʑʹ࢖͍ͬͯͨͩ͘͜ͱ Ͱɺඞཁͳػೳ΍࢖͍উखΛ஌Δ͜ͱΛ໨తͱ͍ͯͨ͠ҝɺ΋͏গ͠؆қతʹ։ൃͰ͖ͳ͍͔ͱߟ͑ ·ͨ͠ɻͦ͜Ͱɺࠓճ͸ *1vercel/satori Λ༻͍ͯ௿ίετͰ։ൃΛਐΊΔ͜ͱʹ͠·ͨ͠ɻsatori ͸ Vercel ͕ఏڙ͢ΔΦʔϓϯιʔεͷϥΠϒϥϦͰ͢ɻsatori ͸ɺReact ίϯϙʔωϯτΛ࢖ͬͯ SVG Λੜ੒Ͱ͖ΔͨΊɺಈతͳάϥϑΟοΫε΍ΧελϜը૾ͷੜ੒ʹඇৗʹศརͰ͢ɻsatori Λ ༻͍Δ͜ͱͰɺϒϥ΢β্Ͱը૾Λ֬ೝͰ͖Δ؀ڥΛ؆୯ʹߏஙͰ͖·͢ɻαʔόνʔϜͷ؅ཧ্ɺ Go ݴޠͷ API Ͱ༻ҙͨ͠ํ͕͋ͱ͋ͱͷ؅ཧָ͕Ͱ͸͋ΔͷͰ͕͢ɺલड़ͨ͠௨Γࠓճ͸͋͘·Ͱ ػೳΛ༻ҙ͢Δ͜ͱͰͲͷΑ͏ͳޮՌ͕ಘΒΕΔͷ͔Λ஌Δ͜ͱ͕໨తͰ͋ΔͨΊɺ͜ͷΑ͏ͳબఆ Λߦ͍·ͨ͠ɻ 5.5 πʔϧͷ։ൃ satori Λ༻͍ͨ͜ͱͰ։ൃࣗମ͸͙͢ʹਐΊΔ͜ͱ͕Ͱ͖·ͨ͠ɻ࣮૷ࣗମ͸γϯϓϧͳઃܭͰɺ ΫΤϦύϥϝʔλʹੜ੒͍ͨ͠όφʔͷࢦఆ΍มߋՕॴͷ৘ใΛࡌͤΔ͜ͱͰɺͦͷ৔Ͱը૾Λੜ੒ ͠ϒϥ΢β্Ͱ֬ೝͰ͖ΔΑ͏ʹͳ͍ͬͯ·͢ɻՄม෦෼ʹؔͯ͠͸ɺΫΤϦύϥϝʔλͷ಺༰ʹ Ԋͬͯɺ෦඼ͱͳΔը૾Λܾ·ͬͨ৔ॴʹ഑ஔ͢Δͱ͍͏ߏ଄Ͱ͢ɻαʔό͸ Google ͷ CloudRun ʹߏங͠ɺϒϥ΢βදࣔ΋ png ʹม׵ͨ͠ը૾Λදࣔ͢ΔΑ͏ʹ͠·ͨ͠ɻ࢖༻ऀ͸ϒϥ΢β্͔ Βը૾Λอଘ͢Δ͚ͩͰੜ੒͞ΕͨόφʔΛखݩʹอଘ͢Δ͚ͩͷγϯϓϧͳػߏͰ͢ɻϦονͳػ ೳ͸Կ΋͚ͭͣʹɺඞཁ࠷௿ݶͷػೳͰఏڙ͢Δ͜ͱͰɺͰ͖Δ͚ͩૣ࣮͘੷ΛੵΉ͜ͱΛ໨ࢦ͠· ͨ͠ɻ *1 vercel/satoriɿ https://github.com/vercel/satori 70
  73. ୈ 5 ষ όφʔੜ੒πʔϧͰσβΠφʔΛ޾ͤʹ͔ͨͬͨ͠࿩ 5.5 πʔϧͷ։ൃ ਤ 5.1: Tool ͷը૾

    ࣮૷ํ਑΋ඇৗʹγϯϓϧʹͨ͠ࣄͰɺ͙͢ʹ࣮૷΋ऴΘΔ͔ͱߟ͍͑ͯ·͕ͨ͠ɺۤ࿑͢ΔϙΠ ϯτ͸ผʹ͋Γ·ͨ͠ɻ πʔϧʹదԠ͢Δόφʔͷબఆ ๯಄Ͱ࿩ͨ͠௨Γɺࠓճͷπʔϧ͸͋͘·ͰςϯϓϨʔτ͕ܾ·͍ͬͯΔόφʔͷΈ͕ର৅ͱͳ ΓɺͰ͖Δ͜ͱ͸มߋՄೳՕॴ΋͋Β͔͡ΊܾΊ͓ͯ͘ඞཁ͕͋Γ·͢ɻͦͷͨΊɺͲͷΑ͏ͳό φʔΛબఆ͢Δ͔͕ॏཁͰͨ͠ɻద༻͢Δ͜ͱͰ޻਺࡟ݮʹͭͳ͕Δόφʔ͸ͲΕ͔ɺͲ͜·ͰՄม ʹ͢Δ͔ɺՄมͷൣғͳͲΛاը΍σβΠφʔͱ࿩͠߹͍ͳ͕Βܾఆ͍͖ͯ͠·͢ɻ࣌ظ΍ঢ়گʹ Αͬͯ։࠵͞ΕΔΠϕϯτ͕มΘΓɺͦΕʹ߹Θͤͯ࢖༻͞ΕΔόφʔ΋มΘΔͷͰɺ͜ͷબఆʹ ࢥͬͨΑΓ΋࣌ؒΛඅ΍݁͢ՌʹͳΓ·ͨ͠ɻ࢖༻ස౓͕ߴ͘ޮՌ͕ಘΒΕΔόφʔ͸ͲΕ͔Λ࿩͠ ߹ͬͨ݁Ռɺॳճ͸ 6 छྨͷόφʔΛબఆ͠·ͨ͠ɻ ϑΥϯτͷ໰୊ ΋ͱ΋ͱ͸จࣈ΍਺ࣈ͸੾Γग़͠ը૾Λ࢖ΘͣʹɺϓϩάϥϜ্ͷϑΥϯτͰ࠶ݱͨ͠΋ͷΛό φʔ্ʹ഑ஔ͢Δ༧ఆͰͨ͠ɻ͔͠͠ɺόφʔʹ࢖༻͞Ε͍ͯΔϑΥϯτ͕ಛघͰɺͦΕΛϓϩάϥ Ϝ্Ͱ࠶ݱ͢Δ͜ͱ͕೉͍͠΋ͷ΋͋Γ·ͨ͠ɻݱঢ়࢖༻͞Ε͍ͯΔόφʔΛ࡞੒Ͱ͖ΔΑ͏ʹͨ͠ ͔ͬͨͨΊɺ͜ͷ Tool ͷͨΊʹ৽͘͠όφʔσβΠϯͷ࡞Γ௚͠͸ආ͚·ͨ͠ɻͦ͜Ͱɺจࣈ΍਺ 71
  74. ୈ 5 ষ όφʔੜ੒πʔϧͰσβΠφʔΛ޾ͤʹ͔ͨͬͨ͠࿩ 5.6 ఏڙͨ݁͠Ռ ࣈΛ੾Γग़͠ը૾ͱͯ͠༻ҙ͢Δ͜ͱʹ͠·ͨ͠ɻͦΕʹΑΓ࡞ۀྔ͕૿͑ɺ൚༻ੑʹ͚ܽͯ͠·͏ ܽ఺ʹͳΓ·ͨ͠ɻ ϨΠΞ΢τʹ೰·͞ΕΔ Πϕϯτͷ಺༰Λܝࡌ͢ΔόφʔʹͳΔҝɺֹۚ΍ϥϯΩϯάͷॱҐͳͲܻ਺͕มΘΔ΋ͷ΋͋

    ΓɺͦΕΒͷද่ࣔΕ΁ͷରԠͳͲ΋ඞཁͰͨ͠ɻͦΕΒΛߟྀͯ͠ɺ਺ࣈͷ੾Γग़͠ը૾΋ 1 ܻͷ ਺ࣈ͝ͱʹ༻ҙͤͣʹ 10ɺ50ɺ100ɺ1000 ͳͲɺ࢖༻͢ΔՄೳੑ͕͋Δ਺ࣈͷը૾Λ༻ҙ͠ɺͦΕ ͧΕͷαΠζΛௐ੔͢Δ͜ͱͰද่ࣔΕΛ๷͙Α͏ʹ͠·ͨ͠ɻ·ͨɺՄมՕॴ͸ܾ·͍ͬͯΔͱ͸ ͍͑ɺਓ͕࡞ͬͨ΋ͷ͸ઈົͳϨΠΞ΢τͷௐ੔͕͞Ε͍ͯ·͢ɻීஈ͸දࣔͷόϥϯε΍จࣈͷӄ ӨͳͲ΋σβΠφʔʹΑͬͯௐ੔͞Ε͍ͯΔͨΊɺͦΕΛՄมը૾ͰͲ͜·Ͱҧ࿨ײͳ͘࠶ݱͰ͖Δ ͷ͔Λݕ౼͢Δඞཁ͕͋Γ·ͨ͠ɻ࣮૷্΋ CSS Λ༻͍ͨඍௐ੔͕ඞཁͰɺͪ͜Β΋࣌ؒΛཁ͠· ͨ͠ɻ ӡ༻खॱͷ੔ཧ ݱঢ়͸اը͔ΒσβΠφʔʹόφʔͷ࡞੒ґཔΛ͢ΔϑϩʔͰ͕ͨ͠ɺࠓճͷπʔϧΛ࢖͏͜ͱ Ͱɺ࠷ऴతʹ͸σβΠφʔ΁ґཔ͢Δ͜ͱͳ͘ɺاըͷํʑ͕ࣗ෼ୡͰόφʔΛ࡞੒Ͱ͖ΔࣄΛ໨ ࢦ͠·ͨ͠ɻͦ͏͢Δ͜ͱͰɺ੍࡞ϑϩʔ͕୹ॖ͞Ε޻਺࡟ݮʹͭͳ͕͍ͬͯ͘ͱߟ͑·ͨ͠ɻͨͩ ͠ɺӡ༻্͸࡞੒ͨ͠όφʔΛ GitHub ͷ PR ͱͯ͠ग़͢ඞཁ͕͋Δҝɺاը͕ͦΕΒΛߦ͑ΔΑ ͏ʹखॱΛ੔ཧ͢ΔඞཁͳͲ΋͋Γɺͦͷ͋ͨΓ΋࿩͠߹͍ͳ͕Β੔ཧ͍͖ͯ͠·ͨ͠ɻ 5.6 ఏڙͨ݁͠Ռ ͍Ζ͍Ζͳௐ੔ͷ݁ՌແࣄʹπʔϧΛ࡞੒͠ɺ͋ͱ͸اըɺσβΠφʔʹ࢖ͬͯ΋Β͏͚ͩͱ͍͏ ஈ֊·Ͱ͚͗ͭ͜·ͨ͠ɻͦͷ݁ՌΛݩʹϒϥογϡΞοϓ͍ͯ͘͠༧ఆͰ͕ͨ͠ɺ࠷ޙʹܾఆతͳ ՝୊͕ൃੜ͠·ͨ͠ɻͦΕ͸ɺࣄۀͷঢ়گͷมԽʹΑΓࢪࡦ಺༰΋มΘΓɺࠓճ༻ҙͨ͠όφʔ͕ෆ ཁʹͳͬͨࣄͰ͢ɻͦͷͨΊɺπʔϧͷӡ༻ʹ͸ࢸΒͣɺ݁Ռͱͯ͠σβΠφʔͷ՝୊ղܾʹ͸ͭͳ ͕Βͳ͍ͱ͍͏࢒೦ͳ݁ՌʹऴΘΓ·ͨ͠ɻࠓճ͸ఏڙ·Ͱ༧૝ΑΓ͕͔͔࣌ؒͬͯ͠·ͬͨ͜ͱ΋ ͋ΓɺλΠϛϯά͕ѱ͔ͬͨͷ΋͋Γ·͢ɻ͔͠͠ɺࠜຊతͳ՝୊ͱͯ͠͸ɺTool ʹ൚༻ੑΛ࣋ͨ ͤΔ͜ͱ͕Ͱ͖ͳ͔ͬͨࣄ΍ɺࢪࡦʹେ͖͘ࠨӈ͞Εͳ͍σβΠϯͷ౷ҰԽͳͲΛઌʹਤΕͳ͔ͬͨ ࣄ͕ڍ͛ΒΕ·͢ɻݱࡏ͸ࢪࡦ΍धཁʹΑͬͯόφʔσβΠϯͷधཁ͕େ͖͘มΘΔͨΊɺͦͷ͋ͨ ΓΛ੔ཧ͢Δ͜ͱ͕ઌܾͰ͋ͬͨͱߟ͍͑ͯ·͢ɻ 5.7 ࠓޙͷల๬ ࠓճͷ݁ՌͰ՝୊͕ݟ͑·͕ͨ͠ɺࠓݱࡏ͸όφʔͷσβΠϯͷ౷ҰԽΛਤΔಈ͖͕ਐΜͰ͍· ͢ɻͦͷͨΊɺπʔϧͷ࠶ఏڙΛݕ౼͍ͯ͠·͢ɻ࣍ͦ͜͸ɺ͜ͷπʔϧΛ࣮ࡍʹӡ༻Ͱ࢖͑ΔΑ͏ ʹ͍ͨ͠ͱߟ͍͑ͯ·͢ɻ 72
  75. ୈ 5 ষ όφʔੜ੒πʔϧͰσβΠφʔΛ޾ͤʹ͔ͨͬͨ͠࿩ 5.8 ͓·͚ 5.8 ͓·͚ ࣮͸ࠓճͷόφʔ࡞੒πʔϧʹ͸΋͏Ұஈ֊্ͷߏ૝͕͋Γ·ͨ͠ɻ͜ͷ݅͸ɺܰ͘ݕূͨ͠ఔ౓ Ͱຊ֨࢝ಈ͸Ͱ͖ͳ͔ͬͨͷͰɺ͓·͚ఔ౓ʹ঺հ͍͖ͤͯͨͩ͞·͢ɻ

    ͦͷߏ૝ͱ͍͏ͷ͸ɺςϯϓϨʔτ͕ܾ·ͬͨόφʔΛվม͢ΔͷͰ͸ͳ͘ɺόφʔͷσβΠϯͦ ͷ΋ͷΛ AI ʹΑͬͯࣗಈੜ੒͢Δͱ͍͏΋ͷͰ͢ɻҊͱͯ͠͸ҎԼͷ 2 ஈ֊Ͱߏ૝͍ͯ͠·ͨ͠ɻ ·ͣ͸ɺ ʮσβΠϯͷཁૉΛ AI ʹֶशͤ͞ɺͦΕΛݩʹόφʔσβΠϯΛੜ੒͢Δʯͱ͍͏΋ͷͰ ͢ɻ͜Ε͸ɺ΄΅ਓͷखΛՃ͑ͳͯ͘΋എܠͱͳΔσβΠϯΛ AI ʹΑͬͯੜ੒͢Δͱ͍͏΋ͷͰ͢ɻ ͜Ε͕Ͱ͖Ε͹ɺσβΠφʔͷํʑ͸΄΅จࣈ΍਺ࣈͷ഑ஔͷΈʹઐ೦Ͱ͖ΔΑ͏ʹͳΓɺΑΓޮ཰ తʹ࡞ۀΛਐΊΔ͜ͱ͕Ͱ͖·͢ɻ͔͠͠ɺAI Ͱੜ੒͞Εͨը૾͸๏తͳ؍఺ͷ՝୊ͳͲ΋͋Δҝɺ ͦͷ఺΋ؚΊͯ΋͏ҰͭͷҊͱͯ͠ʮੜ੒͞ΕͨσβΠϯΛΠϝʔδ mock ͱͯ͠࢖༻Ͱ͖ΔΑ͏ʹ ͢Δʯͱ͍͏Ҋ΋ݕ౼͍ͯ͠·ͨ͠ɻσβΠϯҊΛ਺ύλʔϯͷ mock Λ༻͍ͯاըͱσβΠφʔͰ ٞ࿦͍ͯ͠ΔͷͰ͕͢ɺ͜ͷ mock Λ AI ʹΑͬͯੜ੒͢Δͱ͍͏΋ͷͰ͢ɻͦ͏͢Δ͜ͱͰɺ੍࡞ ϑϩʔ͕୹ॖ͞Ε޻਺࡟ݮʹͭͳ͕͍ͬͯ͘ͱߟ͑·ͨ͠ɻ݁࿦͔Βݴ͏ͱɺͪ͜Βͷߏ૝͸࣮ݱ· Ͱ͍࣋ͬͯ͘ͷ͕೉͍͠ͱ൑அ͠ɺࠓճ͸࣮૷ʹ͸ࢸΓ·ͤΜͰͨ͠ɻ͔͠͠ɺͦͷ಺༰Λগ͚ͩ͠ ঺հͰ͖Ε͹ͱࢥ͍ɺຊষͷ࠷ޙʹهࡌ͍͖ͤͯͨͩ͞·ͨ͠ɻ 5.9 ࢖༻ͨ͠ AI ػೳʹ͍ͭͯ ࠓճ Adobe Firefly Λ༻͍ͯ AI ʹΑΔόφʔੜ੒Λࢼͯ͠Έ·ͨ͠ɻFirefly ʹ͸ ʮੜ੒ృΓͭ Ϳ͠ʯͱʮςΩετ͔Βը૾ੜ੒ʯͱ͍͏ػೳ͕͋Γ·͢ɻ •ʮੜ੒ృΓͭͿ͠ʯ͸ը૾ͷಛఆͷ෦෼Λબ୒͠ɺͦͷ෦෼Λ AI ͕ࣗಈతʹੜ੒ͨ͠ίϯςϯ πͰృΓͭͿ͢͜ͱ͕Ͱ͖·͢ɻ •ʮςΩετ͔Βը૾ੜ੒ʯ͸ςΩετͷࢦࣔʹ͕͍ͨ͠ɺ৽͍͠ը૾Λθϩ͔Βੜ੒Ͱ͖·͢ɻ 5.10 ࣮ࡍʹ AI ੜ੒ͯ͠Έͨ݁Ռ ݕূͱͯ͠ϕʔεͱͳΔόφʔΛ༻ҙ͠ɺͦͷόφʔΛ΋ͱʹ AI ʹΑΔόφʔੜ੒Λࢼͯ͠Έ· ͨ͠ɻ 73
  76. ୈ 5 ষ όφʔੜ੒πʔϧͰσβΠφʔΛ޾ͤʹ͔ͨͬͨ͠࿩ 5.10 ࣮ࡍʹ AI ੜ੒ͯ͠Έͨ݁Ռ ਤ 5.2:

    ϕʔεͱͳΔը૾ ·ͣ͸ɺ ʮੜ੒ృΓͭͿ͠ʯͰഎܠΛมߋͯ͠Έ·͢ɻϕʔεը૾ͷϝΠϯͷʮணॱ༧૝ʯͱ͍͏ จࣈΛ࢒͠എܠΛબ୒͠·͢ɻ ਤ 5.3: എܠΛબ୒ ͨͱ͑͹ΦʔϩϥͷΑ͏ͳഎܠΛࢦఆ͢ΔͱҎԼͷΑ͏ʹͳΓ·͢ɻ 74
  77. ୈ 5 ষ όφʔੜ੒πʔϧͰσβΠφʔΛ޾ͤʹ͔ͨͬͨ͠࿩ 5.10 ࣮ࡍʹ AI ੜ੒ͯ͠Έͨ݁Ռ ਤ 5.4:

    എܠΛมߋ ͜ͷػೳ͸࢖͑ͦ͏Ͱ͸͋ͬͨͷͰ͕͢ɺ݁ہϕʔεͱͳΔσβΠϯΛ༻ҙ͢Δඞཁ͕͋ͬͨҝɺ ࠓճ͸࠾༻͠·ͤΜͰͨ͠ɻ·ͨɺࢥ͍௨ΓͷσβΠϯΛੜ੒͢Δͷ͕೉͘͠ɺࠓޙͷల๬ͱͯ͠͸ ೉͍͠ͱ൑அ͠·ͨ͠ɻ ࣍ʹɺ ʮςΩετ͔Βը૾ੜ੒ʯͰը૾Λੜ੒ͯ͠Έ·ͨ͠ɻ݁࿦͔Βݴ͏ͱɺͪ͜ΒͰ΋ࢥ͍௨ ΓͷσβΠϯΛੜ੒͢Δͷ͕೉͘͠ɺ࣮ӡ༻͍ͯ͘͠ʹ͸೉͍͠ͱ൑அ͠·ͨ͠ɻ ҎԼ͸ʮ྘৭ͷงғؾͰɺ໦΍༿ͬͺ͕ͳ͍Πϝʔδɻؐݩ͕͋Δ͜ͱɾָͦ͠͏ͳงғؾͰ͋Δ ͜ͱɾ͓ಘͰ͋Δ͜ͱɾ͓͕ۚ໥͔Δײ͕͡ग़͍ͯΔΠϝʔδʯͱ͍͏ϓϩϯϓτͰੜ੒ͨ͠ྫʹͳ Γ·͢ɻ྘৭Λࢦఆ͢Δͱ໦͕࿈૝͞ΕΔ͜ͱ͕ଟ͍Α͏Ͱɺ໦΍༿ͬͺ͕ੜ੒͞ΕΔͷΛͱΊΔͷ ΋Ұۤ࿑Ͱͨ͠ɻ 75
  78. ୈ 5 ষ όφʔੜ੒πʔϧͰσβΠφʔΛ޾ͤʹ͔ͨͬͨ͠࿩ 5.10 ࣮ࡍʹ AI ੜ੒ͯ͠Έͨ݁Ռ ਤ 5.5:

    ςΩετ͔Βը૾ੜ੒ ҎԼ͸໦ͷը૾ͷੜ੒ΛͱΊΔͨΊʹʮ๐ʯͱݴ͏ Word Λ௥Ճͯ͠Έͨ݁Ռɻ 76
  79. ୈ 5 ষ όφʔੜ੒πʔϧͰσβΠφʔΛ޾ͤʹ͔ͨͬͨ͠࿩ 5.11 ࠷ޙʹ ਤ 5.6: ςΩετ͔Βը૾ੜ੒ͦͷ 2

    จࣈ΋่Ε͍ͯͨΓɺΠϝʔδͨ͠௨ΓͷσβΠϯͱ͸ఔԕ͍݁ՌʹͳΓ·ͨ͠ɻϓϩϯϓτΛਫ਼ ࠪ͢Δ͜ͱͰɺΑΓྑ͍݁Ռ͕ಘΒΕΔ͔΋͠Ε·ͤΜ͕ɺࠓճݕূͨ͠தͰ͸ͦͷਫ਼౓ʹ͸ࢸΓ· ͤΜͰͨ͠ɻ·ͨɺΠϝʔδ௨ΓͷσβΠϯΛੜ੒͢Δͷ͕೉͍ͨ͠Ίɺ࣮ӡ༻ʹ͍࣋ͬͯ͘ʹ͸՝ ୊΋ଟ͍ͱײ͡·ͨ͠ɻࠓճ͸ਂ͘ݕূ͸Ͱ͖͍ͯ·ͤΜ͕ɺϓϩϯϓτͷਫ਼ࠪΛߦ͍ɺѻ͍ํΛ޻ ෉͢Δ͜ͱͰɺΑΓྑ͍݁Ռ͕ಘΒΕΔ͔΋͠Ε·ͤΜɻͦ͏ͳΕ͹ɺ΋͔ͨ͠͠Β࣮ӡ༻΁ͷల๬ ΋޿͕Δ͔΋͠Ε·ͤΜͶɻ 5.11 ࠷ޙʹ ࠷ޙʹͳΓ·͕͢ɺຊষͰ͸՝୊ղܾΛࢼΈͨࡍʹࢥΘ͵໰୊͕֞ؒݟ͑ͨ͜ͱΛ͓࿩͠·ͨ͠ɻ ࠓճ͸ಋೖʹ͸ࢸΓ·ͤΜͰ͕ͨ͠ɺͦͷܦݧΛ׆͔͠ɺ࣍ʹͭͳ͍͛ͯ͘͜ͱ͕େ੾ͩͱײ͡·͠ ͨɻ͜ͷऔΓ૊ΈͰݟ͑ͨ՝୊ΛղܾͰ͖Ε͹ɺ·ͨ࣍ͷεςοϓ΁ਐΉ͜ͱ͕Ͱ͖ɺ͍ͣΕ͸ϓϩ μΫτ։ൃશମͷޮ཰Խʹͭͳ͕͍ͬͯ͘ͷͩͱࢥ͍·͢ɻ੿͍಺༰ʹͳΓ·͕͢ɺগ͠Ͱ΋Έͳ͞ ΜͷࢀߟʹͳΕ͹޾͍Ͱ͢ɻ 77
  80. ୈ 6 ষ ӡ༻ೖߘσʔλΛ AI ͰνΣοΫ͢Δ࢓ ૊ΈΛ࡞ͬͨ 6.1 ·͕͖͑ ॳΊ·ͯ͠ɺࠓճॳΊٕͯज़ॻయͰࣥච͢Δ͜ͱʹͳΓ·ͨ͠ɺιʔγϟϧϕοςΟϯάࣄۀຊ෦

    TIPSTAR ࣄۀ෦։ൃάϧʔϓͷࢁԼͱਃ͠·͢ɻ ຊষͰ͸ɺࢲୡ͕೔ʑ։ൃɾӡ༻Λߦ͍ͬͯΔʮTIPSTAR*1ʯͱ͍͏αʔϏεʹ͓͍ͯɺӡ༻վ ળͱ AI Λಋೖ͍ͨ͠ͱ͍͏ٕज़తͳνϟϨϯδΛ૊Έ߹Θͤͯɺ޻਺࡟ݮʹऔΓ૊Μͩ࿩Λ͍ͯ͠ ͖͍ͨͱࢥ͍·͢ɻ ޻਺࡟ݮͷओͳ಺༰ͱͯ͠͸ GitHub Actions ͷ޻෉ͱɺAI ଆͷ࿩ʹͳ͍ͬͯ·͢ɻAI ํ໘ʹ͸ ։ൃຊ෦ͷٶ࿬͞Μʹ࣮૷ʹೖ͍͖ͬͯͨͩ·ͨ͠ɻ·ͨࠓճهࡌ͢Δ಺༰ʹ͍ͭͯ΋͝ڠྗ͍ͨͩ ͖·ͨ͠ɻ͜ͷ৔ΛआΓͯޚྱਃ্͛͠·͢ɻ 6.2 ՝୊ʹ͍ͭͯ TIPSTAR Ͱ͸೔ʑ͞·͟·ͳΩϟϯϖʔϯࢪࡦ͕։࠵͞Ε͍ͯ·͢ɻ *1 TIPSTARʢςΟοϓελʔʣެࣜαϙʔταΠτ: https://about.tipstar.com 79
  81. ୈ 6 ষ ӡ༻ೖߘσʔλΛ AI ͰνΣοΫ͢Δ࢓૊ΈΛ࡞ͬͨ 6.2 ՝୊ʹ͍ͭͯ ਤ 6.1:

    ͋Δ೔ͷϛογϣϯը໘ ྫͱͯ͠͸্هͷΑ͏ͳछྨʹͳΓ·͕͢ɺ΄͔ʹ΋͞·͟·ͳύλʔϯ͕͋ΓɺͦΕΒ͸ඇΤϯ δχΞͰ͋ΔӡӦνʔϜʹͯΩϟϯϖʔϯࢪࡦΛݕ౼ͨ͠͏͑ͰϚελσʔλͱͯ͠σʔλΛ࡞੒͠ ೖߘ͍ͯ͠·͢ɻ σʔλ͸ GitHub Λ࢖ͬͯ؅ཧ͞Ε͓ͯΓɺมߋ͸ Pull Request ʹ͢Δ͜ͱͰ಺༰ͷ֬ೝ΍όʔ δϣϯ؅ཧΛߦ͍ͬͯ·͢ɻ ͜ΕΒͷೖྗσʔλ͸ɺTIPSTAR ͷ QA νʔϜʹͯΩϟϯϖʔϯࢪࡦͷ࢓༷ॻΛνΣοΫ͠ͳ ͕Βɺ࢓༷Λຬͨ͢Ϛελσʔλͳͷ͔ͱ͍͏؍఺ͳͲͰσʔλͷνΣοΫ͕ߦΘΕ͍ͯ·͢ɻ νΣοΫʹ͔͔Δ޻਺ͱͯ͠ຖि 2-3 ೔ఔ౓ൃੜ͓ͯ͠Γɺͦͷ޻਺Λ࡟ݮ͍ͨ͠ͱ͍͏՝୊͕͋ Γ·ͨ͠ɻಉ͜͡Ζɺੈͷதతʹ AI Λۀ຿ʹ࢖ͬͯΈͨܥͷهࣄ΍ηογϣϯ΋૿͑࢝Ίɺػӡͷ ߴ·Γ΋͋ͬͨ͜ͱ͔ΒɺAI Λ׆༻ͯ͠ΈΑ͏ͱߟ͑औΓ૊ΈΛ࢝Ί·ͨ͠ɻ 80
  82. ୈ 6 ষ ӡ༻ೖߘσʔλΛ AI ͰνΣοΫ͢Δ࢓૊ΈΛ࡞ͬͨ 6.3 ։ൃʹ͍ͭͯ 6.3 ։ൃʹ͍ͭͯ

    σʔλߏ଄ͱνΣοΫํ๏ͷઃܭ ΩϟϯϖʔϯࢪࡦͷϚελσʔλʹ͸͍͔ͭ͘छྨ͸͋ΔͷͰ͕͢ɺେ·͔ͳσʔλͷߏ੒ͱͯ͠ ͸ҎԼͷΑ͏ͳ CSV ͷܗʹͳ͍ͬͯ·͢ɻ ਤ 6.2: σʔλͷΠϝʔδ ͜ͷΑ͏ͳσʔλΛ࣮ࡍͷΞϓϦͷํ͔ΒݟΔͱɺҎԼͷΑ͏ͳܗͱͳΓ·͢ɻ 81
  83. ୈ 6 ষ ӡ༻ೖߘσʔλΛ AI ͰνΣοΫ͢Δ࢓૊ΈΛ࡞ͬͨ 6.3 ։ൃʹ͍ͭͯ ਤ 6.3:

    ͋Δ೔ͷ͋Δϛογϣϯ ࢪࡦͷ಺༰ʢdescriptionʣͱɺͦΕΛߏ੒͢ΔͨΊͷύϥϝʔλʢΞϓϦέʔγϣϯϩδοΫʹؔ ΘΔ΋ͷʣ͕ 5-6 ݸ͋ͬͯ 1 ͭͷࢪࡦσʔλͱͳ͍ͬͯ·͢ɻ ͜ͷΑ͏ͳσʔλʹରͯ͠ɺࠓճ͸ 2 छྨͷνΣοΫΛऔΓೖΕͯΈΑ͏ͱͳΓ·ͨ͠ɻ1 ͭ͸ descsription ͷ಺༰Λਖ਼ղͱͯ͠ɺͦΕΛຬͨ͢ύϥϝʔλ͕ͦΕͧΕͷ߲໨ʹ͖ͭద੾ʹೖ͍ͬͯ Δͷ͔ͱ͍͏΋ͷʢ੔߹ੑνΣοΫʣ ɺ2 ͭ໨͸ֹۚදه΍ه߸ͷ࢖͍ํͳͲ͕ QA νʔϜͰνΣο Ϋ͍ͯ͠ΔϧʔϧʹԊ͍ͬͯΔ͔ͱ͍͏΋ͷʢจষߍਖ਼νΣοΫʣͰ͢ɻ ·ͨɺઌ΄Ͳ΋ॻ͍͍ͯͨΑ͏ʹϚελσʔλ͸ GitHub Ͱ Pull Request ʹΑΔࠩ෼؅ཧΛͯ͠ ͍ΔͷͰɺ GitHub Actions ্Ͱಈ࡞͢ΔνΣοΫ͢ΔπʔϧΛ Go Ͱ࡞੒͠ AI ൑ఆʹ͔͚ͯ NG ͷ΋ͷ͕͋Ε͹ɺPull Request ʹରͯ͠ίϝϯτΛ౤ߘ͢ΔΑ͏ʹ͠·ͨ͠ɻ 82
  84. ୈ 6 ষ ӡ༻ೖߘσʔλΛ AI ͰνΣοΫ͢Δ࢓૊ΈΛ࡞ͬͨ 6.3 ։ൃʹ͍ͭͯ AI ଆͷ࿩

    ͔͜͜Β͸νΣοΫʹ࢖͏ AI γεςϜͷ಺༰Ͱ͢ɻ ֓ཁͱγεςϜߏ੒ Goole Cloud ͷ Cloud Run functions *2 ্ʹϓϩϯϓτੜ੒౳֤छॲཧΛ࣮૷͠ɺνΣοΫʹ࢖ ͏ AI ʹ͸ Azure OpenAI Service Λ࢖༻͠ɺGPT-4 ϞσϧΛར༻͍ͯ͠·͢ɻ͢ͳΘͪ LLM ʹ ΑΔνΣοΫΛߦ͍ͬͯ·͢ɻ ࣍ʹ֤νΣοΫ͝ͱͷϓϩϯϓτͳͲʹ͍ͭͯͷઆ໌Ͱ͢ɻ ੔߹ੑνΣοΫ CSV ͷ֤ߦ͝ͱʹཁૉͷ஋ͱ description ͕߹͍ͬͯΔ͔Λ֬ೝ͠·͢ɻ description Λࣄલʹఆٛͨ͠ ෼ׂจࣈ ʹΑͬͯ৚݅ύʔτͱใुύʔτʹ෼ׂͯ͠ɺύϥϝʔλ ͷछྨʹΑͬͯɺͲͪΒ͔ͷύʔτ͔ඞཁͳํͷΈͷೖྗʹ༩͑Δ͜ͱͰɺGPT-4 ͷར༻ྉۚΛઅ ໿ͭͭ͠ਫ਼౓޲্Λૂ͍ͬͯ·͢ɻ Ϧετ 6.1: ෼ׂจࣈ SPLIT_WORDS = [’͢Δͱ’, ’ͤ͞Δͱ’, ’౰ͨΒͳ͔ͬͨΒ’, ’͠Α͏’] Ϧετ 6.2: ର৅จষྫ ຊ೔ݶఆͰɺTIPϚωʔͰ͞Βʹ1Ϩʔεʢ߹ܭ23Ϩʔεʣं݊Λߪೖ͢Δͱɺ ʮۚ՟(1೔໨)֫ಘνϟϯεʯͷ நબʹ1ճ௅ઓ͢Δ͜ͱ͕ग़དྷ·͢ʂ ͜ͷநબͰ͸ɺ ʮۚ՟(1೔໨)ʯ1ຕɺ ʮϓϥΠζϙΠϯτʯ50Pͷ͍ͣΕ͔Λ֫ಘ͢Δ͜ͱ͕ग़དྷ·͢ʂ ઌఔͷ෼ׂจࣈͰ෼ׂ͢ΔͱҎԼͷΑ͏ʹͳΓ·͢ɻ Ϧετ 6.3: ৚݅ύʔτ ຊ೔ݶఆͰɺTIPϚωʔͰ͞Βʹ1Ϩʔεʢ߹ܭ23Ϩʔεʣं݊Λߪೖ͢Δͱ *2 https://cloud.google.com/functions?hl=ja 83
  85. ୈ 6 ষ ӡ༻ೖߘσʔλΛ AI ͰνΣοΫ͢Δ࢓૊ΈΛ࡞ͬͨ 6.3 ։ൃʹ͍ͭͯ Ϧετ 6.4:

    ใुύʔτ ɺ ʮۚ՟(1೔໨)֫ಘνϟϯεʯͷநબʹ1ճ௅ઓ͢Δ͜ͱ͕ग़དྷ·͢ʂ ͜ͷநબͰ͸ɺ ʮۚ՟(1೔໨)ʯ1ຕɺ ʮϓϥΠζϙΠϯτʯ50Pͷ͍ͣΕ͔Λ֫ಘ͢Δ͜ͱ͕ग़དྷ·͢ʂ ϓϩϯϓτ ҎԼ͸࢖༻͍ͯ͠ΔϓϩϯϓτͷྫͰ͢ɻ Ϧετ 6.5: ϓϩϯϓτͷྫ આ໌จͷ಺༰ͱʮλʔήοτจ:ʯͷ಺༰͕ؒҧ͍ͬͯͳ͍͔Ͳ͏͔Λʮਖ਼͍͠ʯ͔ʮਖ਼͘͠ͳ͍ʯͰڭ͑ͯͩ͘ ͍͞ɻ ղ౴ϑΥʔϚοτ: ਖ਼͍͠ or ਖ਼͘͠ͳ͍_ਖ਼͘͠ͳ͍৔߹ͷΈ؆ܿʹͦ͏ߟ͑ͨཧ༝ λʔήοτจ: ڝ໊ٕ=શڝٕͰ,TIPϚωʔͰߪೖͨ͠Ϩʔεͷ਺ આ໌จ: ຊ೔ݶఆͰɺTIPϚωʔͰ͞Βʹ1Ϩʔεʢ߹ܭ23Ϩʔεʣं݊Λߪೖ͢Δͱ ࣮ࡍ͸͜Ε͚ͩͩͱޡ൑ఆ͕ଟ͘ग़ͯ͠·͏ͨΊɺ൑ఆͷ࢓ํΛৄࡉʹઆ໌ͨ͠ΓྫΛ༻͍Δ͜ͱ Ͱޡ൑ఆΛݮΒ͍ͯ͠·͢ɻ จষߍਖ਼νΣοΫ จষߍਖ਼νΣοΫ͸ɺࣄલʹఆٛͨ͠ϧʔϧͱ description Λϓϩϯϓτͱͯ͠౉ͯؒ͠ҧ͍Օॴ Λࢦఠͯ͠΋Β͍·͢ɻ ͔͠͠ɺݱঢ়ֹۚ΍ه߸ͷ൑ఆ͕ۤखͳΑ͏Ͱޡ൑ఆ͕ݟΒΕΔͨΊɺֹۚ΍ه߸पΓ͸Ұ෦ϧʔ ϧϕʔεͰ൑ఆΛͨ͠Γɺֹۚ෦෼Λ੾Γग़ͯ͠ϓϩϯϓτԽ͢ΔͳͲͯ͠޻෉͍ͯ͠·͢ɻ ൑ఆͷ޻෉ GPT-4 ͷฦ౴ʹ͸༳Β͕͗͋Γɺඞͣ͠΋ຖճਖ਼͍͠൑ఆΛͯ͘͠ΕΔΘ͚Ͱ͸͋Γ·ͤΜɻࠓ ճͷλεΫͰ͸ɺ΄ͱΜͲͷೖྗ͕ਖ਼͍͠ͱ͍͏എܠ΋͋Γɺෳ਺ճ࿈ଓͰ ‘ਖ਼͘͠ͳ͍ ‘ ൑ఆ͕ग़ ͨ৔߹ͷΈ NG Λฦ͍ͯ͠·͢ɻ ίετΛ౓֎ࢹ͢Ε͹ɺಉҰೖྗͰෳ਺ճճ౴Λͤͯ͞ɺશһҰக΋͘͠͸ଟ਺ܾΛऔΔํ๏΋ߟ ͑ΒΕ·͢ɻ BERT Λ࢖ͬͨݕূ Azure OpenAPI Service Λར༻͢Δલʹ͸ɺBERT ͷϑΝΠϯνϡʔχϯάΛ༻͍ͯಉ༷ͷ νΣοΫ͕Ͱ͖ͳ͍͔ݕূ΋ߦ͍·ͨ͠ɻ BERT ͷࣄલֶशࡁΈϞσϧΛ TIPSTAR ͷաڈͷࢪࡦσʔλͰϑΝΠϯνϡʔχϯάͯ͠෼ྨ Ϟσϧʹ͠ɺdescription ͔Β֤छύϥϝʔλ΍਺஋Λ༧ଌͯ͠ΈΔͱ͍͏΋ͷͰ͢ɻݕূͱͯ͠ɺ 84
  86. ୈ 6 ষ ӡ༻ೖߘσʔλΛ AI ͰνΣοΫ͢Δ࢓૊ΈΛ࡞ͬͨ 6.4 ӡ༻࣌ʹݟ͔ͭͬͨ՝୊ͱରԠ ͋Δύϥϝʔλ͸஋ͷछྨ͕ଟ͘ɺ͜ΕΛ͍ͬͨΜසग़ (N>=100)

    ͷ 11 Ϋϥεʹ෼͚ͯɺͦΕΒ ͷύϥϝʔλͷΈΛ࢖͍ݕূ͠·ͨ͠ɻ ݁Ռɺ͜ͷύϥϝʔλͷΫϥεਖ਼౴཰͸ 25% ఔ౓ͱͳΓɺϥϯμϜΑΓ͸ྑ͍΋ͷͷɺ࣮༻Ͱ͖ Δਫ਼౓ʹ͸ࢸΒͳ͍ͱ͍͏͜ͱʹͳΓ Azure OpenAPI Service Λར༻͢Δ͜ͱʹ͠·ͨ͠ɻ 6.4 ӡ༻࣌ʹݟ͔ͭͬͨ՝୊ͱରԠ ͔͜͜Β͸࡞͍͍ͬͯͨͩͨ AI Λ࢖͍νΣοΫΛ͔͚ΔͨΊͷɺGitHub Actions ଆͷ಺༰ʹ ͳ͍͖ͬͯ·͢ɻ GitHub Actions ͔Βͷ࿈ܞ ༻ҙ͍͍ͯͨͩͨ͠ CloudFunctions ͷΤϯυϙΠϯτʹରͯ͠ɺPull Request τϦΨͰɺΩϟ ϯϖʔϯࢪࡦͷϚελσʔλ্ͷࢪࡦͷ಺༰ʢdescriptionʣͱͦΕΛߏ੒͢ΔͨΊͷύϥϝʔλ 5-6 ݸΛ͚ͭͯϦΫΤετΛ౤͛·͢ɻ νΣοΫ͕ߦΘΕɺϨεϙϯεͱͯ͠ύϥϝʔλͷ߲໨͝ͱʹ OK or NGɺNG ͷ৔߹͸ͦ͏൑ அͨ͠ཧ༝΋ฦͬͯ͘ΔͨΊɺNG ͷΈΛरͬͯίϝϯτ͢ΔܗͰ͢ɻ ࣍ʹɺຊ֨తͳಋೖ·Ͱʹۤ࿑ͨ͠ͱ͜Ζͱ޻෉ͨ͠ͱ͜ΖΛ·ͱΊ·͢ɻओʹνΣοΫʹ͔͔Δ ॴཁ࣌ؒͷ໰୊͕͋Γ·ͨ͠ɻ ௥Ճ͞Εͨߦ͚ͩͷऔಘ ΩϟϯϖʔϯࢪࡦͷϚελσʔλ͸աڈͷσʔλ͕͍ͭ͘΋ೖ͍ͬͯ·͢ɻ ຖճաڈͷσʔλΛνΣοΫ͍ͯ͠ΔͱνΣοΫର৅ߦ͕ͱΜͰ΋ͳ͍͜ͱʹͳͬͯ͠·͏ͷͰɺ ࠩ෼ͱͯ͠ग़͍ͯΔߦ͚ͩΛ͏·͘औΓग़͢ඞཁ͕͋Γ·ͨ͠ɻ Go ͷνΣοΫπʔϧʹ͔͚Δલʹɺ GitHub Actions ͷ workflow ͔ΒγΣϧεΫϦϓτΛ࣮ߦ ͢ΔΑ͏ʹͯ͠ɺ·ͣ͸ͬ͘͟Γͱ௥Ճࠩ෼͚ͩΛऔಘ͢ΔΑ͏ʹ͠·ͨ͠ɻ Ϧετ 6.6: origin/BASE ͱ head branch Ͱͷൺֱ४උ git cat-file blob origin/"${ GitHub_BASE_REF}":"${item}" > "${TMPDIR}"/base_tmp git cat-file blob "${CHECK_BRANCH}":"${item}" > "${TMPDIR}"/merge_tmp Ϧετ 6.7: ઌ΄ͲͷϑΝΠϧؒͷࠩ෼νΣοΫ diff -u "${TMPDIR}"/base_tmp "${TMPDIR}"/merge_tmp \ | grep ^+ | grep -v ’+++’ | sed s/^+// >> "${TMPDIR}"/add_diff ͜Ε͕ۭͰ͸ͳ͚Ε͹Կ͔͠Β௥Ճࠩ෼͋Γͱ͍͏͜ͱͰɺνΣοΫʹ͔͚͍ͨͱ͍͏಺༰Ͱ͢ɻ 85
  87. ୈ 6 ষ ӡ༻ೖߘσʔλΛ AI ͰνΣοΫ͢Δ࢓૊ΈΛ࡞ͬͨ 6.4 ӡ༻࣌ʹݟ͔ͭͬͨ՝୊ͱରԠ ͔͠͠ɺ͜ͷ··Ͱ͸৽نʹ௥Ճ͞Εͨߦͳͷ͔ɺطଘͷߦʹରͯ͠ͷมߋͳͷ͔൑அ͕͖ͭ·ͤ ΜͰͨ͠ɻطଘͷߦʹ͍ͭͯ͸࣌ؒΛ৯͏͚ͩͳͷͰແࢹ͢Δͷ͕ཧ૝Ͱ͢ɻಋೖͨͯ͠ͷ͜Ζ͸ͦ

    ΕΛ͓ͯ͠Βͣର৅ߦ͕๲େʹͳΓ͕͔͔࣌ؒͬͯ͠·͏ঢ়گʹͳͬͯ͠·͍·ͨ͠ɻ ͦ͜Ͱɺaswinkarthik/csvdiff*3 ͱ͍͏ Go ͷύοέʔδΛ࢖͍ɺϑΝΠϧͷத͔Β add ͞Εͨ ߦ͚ͩΛநग़͢ΔΑ͏ʹ͢Δ͜ͱͰνΣοΫର৅ߦΛݮΒ͢͜ͱ͕Ͱ͖·ͨ͠ɻ ฒྻ࣮ߦ ผͷ໰୊ͱͯ͠ɺAzure OpenAPI Service ଆͰ͕͔͔࣌ؒͬͯ͠·͏ͨΊʹɺ1 ݅ͷνΣοΫʹ ͖ͭ 1 ෼ఔ౓͕͔͔࣌ؒΔ͜ͱ͕͋Γ·ͨ͠ɻઌఔͷରԠ͚ͩͩͱγϯϓϧʹ௥Ճ͞Εͨߦ͕ଟ͘ɺ νΣοΫ͢Δྔ͕๲େʹͳΔͱ͕͔͔࣌ؒΔ఺Ͱ͸ҰॹͩͬͨͨΊɺ׬ྃ·Ͱ 1 ࣌ؒऑ΍ͦΕҎ্ ͔͔Δ͜ͱ͕͋Γ·ͨ͠ɻ ͜ΕΛղܾ͢ΔͨΊʹ GitHub Actions ͷ matrix ػೳΛ࢖ͬͯฒྻ࣮ߦ͕Ͱ͖ΔΑ͏޻෉͢Δ͜ ͱʹ͠·ͨ͠ɻ πʔϧ͔ΒνΣοΫ͢Δํ๏͸νΣοΫ͍ͨ͠ ID ΛೖΕΔܗʹ͍ͯͨ͠ͷͰ͕͢ɺ͜͜ʹ౉͢Ҿ ਺ΛγΣϧͳͲͰؤுͬͯՃ޻͠ɺ ʰ ʢઌ΄Ͳͷʣ௥Ճߦ ID நग़ϑΝΠϧ࡞੒ -> split Ͱಛఆͷ਺͝ ͱʹ෼ׂ -> ෼ׂͨ͠ϑΝΠϧ PATH Λޙଓͷ job ͷ matrix ༻ʹ json ܗࣜʹม׵ʱ ͱ͢ΔΑ͏ ʹ͠·ͨ͠ɻ ۩ମతͳίʔυͯ͠͸ҎԼͷܗͰɺjson ΦϒδΣΫτͱͯ͠ matrix ʹ౉ͤ͹ɺͦͷͿΜฒྻͰಈ ͍ͯ͘Ε·͢ɻ Ϧετ 6.8: origin/BASE ͱ head branch Ͱͷൺֱ४උ # ର৅ͱͳΔCSVͷϦετΛ matrix job ʹ౉͢ chunk ʹ෼ׂ͢Δॲཧ zendan-job: outputs: targets: ${{ steps.get.outputs.targets }} steps: - name: લॲཧ id: get run: | # ഑ྻ TARGETS=() # ෼ׂ͞ΕͨϑΝΠϧΛ֨ೲ͢ΔσΟϨΫτϦΛ࡞੒ mkdir split_files # ࠩ෼ϑΝΠϧ͕͋Ε͹಺༰Λ෼ׂͯ͠อଘ split -l [NUM] --numeric-suffixes \ --additional-suffix=.csv [FILE_NAME] split_files/part_"${file}". # ෼ׂ͞ΕͨϑΝΠϧͷϦετΛ࡞੒ TARGETS=($(ls split_files/*)) # matrix༻ʹjsonܗࣜʹม׵ targets=$(printf ’%s\n’ "${TARGETS[@]}" | jq -R . | jq -s -c .) # ฒྻνΣοΫͤ͞Δॲཧ *3 csvdiff: https://github.com/aswinkarthik/csvdiff 86
  88. ୈ 6 ষ ӡ༻ೖߘσʔλΛ AI ͰνΣοΫ͢Δ࢓૊ΈΛ࡞ͬͨ 6.5 ͋ͱ͕͖ koudan-job: needs:

    - zendan-job strategy: matrix: target_file: ${{ fromJSON(needs.zendan.outputs.targets) }} ·ͨগ͠Ͱ΋ݟ΍͍͢Α͏ʹͱɺϑΝΠϧ෼ׂ job ͱνΣοΫΛ͔͚Δ job Λ෼ׂͨ͠ͷͰɺ෼ ׂͨ͠ϑΝΠϧ܊͸ artifact Λ࢖͍ɺޙଓͷ job ʹҾ͖౉͢Α͏ʹ͍ͯ͠·͢ɻ ʰগ͠Ͱ΋ݟ΍͍͢Α͏ʹʱͱࢥͬͯ౰࣌͸͜͏͍ͯͨ͠ͷͰ͕͢ɺࠓৼΓฦͬͯݟ͍ͯΔͱ job Λ෼ׂͨ͜͠ͱʹΑͬͯෳࡶ͕͞૿͍ͯ͠ΔΑ͏ʹ΋ࢥ͑ͯ͠·͍·͢ɻ ͨͩɺͻͱ·ͣ͸͜ͷରԠͰνΣοΫͷฒྻԽ͕Ͱ͖ɺ10 ෼ҎԼͳͲͷڐ༰Ͱ͖Δ࣌ؒͰશମత ʹॲཧͰ͖ΔΑ͏ʹͳΓ·ͨ͠ɻ ࢦఠࡁΈύϥϝʔλͷΩϟογϡ ࠷ޙ͸ಋೖͨ͋͠ͱͷ՝୊Ͱɺϋϧγωʔγϣϯͨ͠৔߹ͷରࡦʹ͍ͭͯͰ͢ɻ νΣοΫͨ݁͠Ռ͸ Pull Request ʹίϝϯτ͢ΔܗͳͷͰϋϧγωʔγϣϯ͕ى͖΍͍͢σʔλ ͷ৔߹ʹίϝϯτ਺͕૿͑ͯ͠·͍ Pull Request ࣗମ͕ݟͮΒ͘ͳͬͨΓɺ௨஌͕ແବʹඈΜͩΓ ͯ͠͠·͍͋·Γྑ͘ͳ͍ঢ়ଶʹͳͬͯ͠·͍ͬͯ·ͨ͠ɻ ·ͨɺPull Request Ͱӡ༻͍ͯ͠Δ౎߹ɺमਖ਼ίϛοτ͕ੵ·ΕΔ͜ͱ͕͋Γ·͕͢ɺίϛοτ ͕ੵ·ΕΔͨͼʹ base branch ͱͷ௥Ճࠩ෼Λநग़ͯ͠νΣοΫ͕ಈ͘ͱ͍͏ڍಈͷͨΊɺಉ͡Օ ॴ΁ͷಉ͡Α͏ͳίϝϯτ͕૿͑ͯ͠·͏ͱ͍͏໰୊΋ซͤͯ͋Γ·ͨ͠ɻ ͜Εʹର͢Δमਖ਼ͱͯ͠ɺνΣοΫࡁΈύϥϝʔλϑΝΠϧͷΑ͏ͳ΋ͷΛઃ͚Δ͜ͱʹ͠· ͨ͠ɻ ৄࡉͱͯ͠͸ɺGo ͷπʔϧଆͰνΣοΫ NG ͱͳͬͨ৔߹ʹɺdescription Ҏ֎ͷύϥϝʔλ͔Β ϋογϡ஋Λੜ੒͠ɺͦͷ݁ՌΛϑΝΠϧʹग़ྗͯ͠ artifact ʹอଘ͠·͢ɻ࣍ճ࣮ߦ࣌ʹ͸ ͦͷ artifact ͔ΒϑΝΠϧΛऔಘ͠ɺ࠶౓ Go ͷπʔϧʹ౉͠·͢ɻϋογϡ஋͕ҟͳ͍ͬͯΔ৔߹ʹͷ ΈνΣοΫΛߦ͏Α͏ʹ͠·ͨ͠ɻ ͜͏͢Δ͜ͱͰɺ͢ͰʹࢦఠࡁΈͷߦ͕ίϛοτͷͨͼʹίϝϯτ͞ΕΔ͜ͱ͸ແ͘͢͜ͱ͕Ͱ͖ ·ͨ͠ɻ 6.5 ͋ͱ͕͖ ͜͏ͯ͠ಋೖͨ͠ AI Λ࢖ͬͨσʔλνΣοΫͰ͕͢ɺ͠͹Β͘ӡ༻ͯ͠ΈΔͱ AI ͷӡ༻ͱ͍͏ ఺Ͱଞʹ΋՝୊͸͋Γͦ͏ͩͱ͍͏͜ͱ͕෼͔͖ͬͯ·ͨ͠ɻ ·ͣ͸ɺӡ༻࣌ʹݟ͔ͭͬͨ՝୊ͱରԠ ͷઅͰ৮ΕͨΑ͏ʹɺLLM ͷਫ਼౓ෆ଍ʹΑΔ΋ͷͳͷ͔ ϓϩϯϓτʹվળͷ༨஍͕͋Δͷ͔ɺϋϧγωʔγϣϯͷൃੜස౓͕ߴ͍ͱ͍͏఺Ͱ͢ɻࢪࡦͷσʔ λύλʔϯʹ΋ਵ࣌Ξοϓσʔτ͕ೖΔ͜ͱͰɺ৽ͨͳจষύλʔϯ͕ಥવೖͬͯ͘Δ৔߹͕͋Γɺ 87
  89. ୈ 6 ষ ӡ༻ೖߘσʔλΛ AI ͰνΣοΫ͢Δ࢓૊ΈΛ࡞ͬͨ 6.5 ͋ͱ͕͖ ͦͷࡍʹ΋ϋϧγωʔγϣϯ͸ى͖ͯ͠·͍·͢ɻ͜ͷ఺ʹ͍ͭͯ͸࣌ંϑΟʔυόοΫΛ͍͖ͨͩ ͭͭϓϩϯϓτͷௐ੔Λٶ࿬͞Μͱ౎౓ݕ౼͠ͳ͕Βमਖ਼͍ͯ͠·͢ɻ

    ͨͩͦΕͰ͸Ͳ͏ͯ͠΋ޙखޙखײ͕͋Γɺ͜ͷ͋ͨΓ͸ϓϩϯϓτͷ಺༰ௐ੔ΛզʑͷํͰ΋Ͱ ͖ΔΑ͏ʹͨ͠Γɺ৽͍͠ύλʔϯ΁ͷࣄલͷ४උͰ͖ΔΑ͏ʹ͢ΔͳͲɺQA νʔϜʹڠྗ͍ͨͩ ͍ͨΓ͠ͳ͕Βਫ਼౓Λ্͛ΒΕΔӡ༻ํ๏͸ͳ͍͔ͳͱݕ౼࢝͠Ί͍ͯΔஈ֊Ͱ͢ɻ ·ͨɺLLM ʹΑΔνΣοΫΛผͷख๏ʹஔ͖׵͑ͨΓɺ΋͘͠͸΋͏গ͠νΣοΫ͢Δείʔϓ ΛߜͬͯΈͨΓͱɺAI ଆͷ࢖͍ํʹ΋͍͔ͭ͘ݟ௚ͤΔ෦෼͸͋ΔΑ͏ʹ΋ײ͍ͯ͡ΔͷͰɺ։ൃ ຊ෦ͷํʹ΋૬ஊͭͭ͠஍ಓʹվળ͍͚ͯ͠Ε͹ͳͱࢥ͍ͬͯ·͢ɻ ຊষΛಡΜͰ͍͍ͨͩͨํʹগ͠Ͱ΋໾ʹͨͭ৘ใ͕͋Ε͹޾͍Ͱ͢ɻ͋Γ͕ͱ͏͍͟͝·ͨ͠ɻ 88
  90. ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳ ϕΫτϧݕࡧΛಋೖͯ͠ΈΔ ιʔγϟϧϕοςΟϯάࣄۀຊ෦ TIPSTAR ࣄۀ෦։ൃ G

    ͷ௡ాͰ͢ɻීஈ͸ TIPSTAR ͷ։ൃ νʔϜͷϚωʔδϟʔΛ͍ͯ͠·͢ɻࠓճ͸೔ৗతʹѪ༻͍ͯ͠ΔϊʔτΞϓϦ Obsidian ͷϓϥά Πϯ։ൃʹ௅ઓͯ͠ΈͨͷͰɺͦͷମݧΛڞ༗͍ͨ͠ͱࢥ͍·͢ɻObsidian ͸ීஈͷ։ൃۀ຿ʹ͓ ͚ΔϝϞ΍ΞΠσΞ੔ཧʹ׆༻͍ͯ͠ΔπʔϧͰ͋ΓɺͦͷΧελϚΠζੑͷߴ͞ʹඇৗʹऒ͔Ε ͍ͯ·͢ɻຊষͰ͸ͦΜͳ Obsidian ͷັྗ΍ࢲ͕࣮ࡍʹऔΓ૊ΜͩϓϥάΠϯ։ൃͷྲྀΕΛ঺հ͠ ·͢ɻ 7.1 Obsidian ͱ͸Կ͔ Obsidian *1 ͸ɺγϯϓϧͰ͋Γͳ͕Βڧྗͳϊʔτ؅ཧΞϓϦέʔγϣϯͰ͢ɻϚʔΫμ΢ϯܗ ࣜͰͷςΩετ࡞੒ΛجຊʹɺϢʔβʔ͸ࣗ਎ͷϊʔτΛࣗ༝ʹ੔ཧ͠ɺϦϯΫΛுΓ८ΒͤΔ͜ͱ Ͱ஌ࣝ΍ΞΠσΞΛମܥతʹ؅ཧͰ͖·͢ɻ ͞ΒʹɺGitɺiCloudɺGoogle Drive ͳͲͷ֎෦αʔϏεΛ࢖ͬͯϦϞʔτʹσʔλΛόοΫΞο ϓͨ͠Γɺෳ਺ͷ୺຤ؒͰಉظͤͨ͞Γ͢Δ͜ͱ΋༰қͰ͢ɻ༗ྉαʔϏεͰ͋Δ Obsidian Sync ʹՃೖ͢Ε͹ɺࣗಈతʹ୺຤ؒͷσʔλಉظ΍όοΫΞοϓ͕ՄೳʹͳΔͨΊɺ͞Βʹศརʹ࢖༻Ͱ ͖·͢ɻ ॊೈͰܰྔͳΞϓϦέʔγϣϯ Obsidian ͷ࠷େͷಛ௃ͷҰͭ͸ɺͦͷॊೈੑͰ͢ɻObsidian ͸ɺϢʔβʔ͕ࣗ༝ʹϓϥάΠϯΛ ௥Ճͯ͠ػೳΛ֦ுͰ͖ΔΞϓϦέʔγϣϯͰ͢ɻެࣜͷϓϥάΠϯҎ֎ʹ΋ɺίϛϡχςΟ͕࡞੒ ͨ͠਺ଟ͘ͷϓϥάΠϯ͕͋ΓɺͦΕΒΛ૊Έ߹ΘͤΔ͜ͱͰࣗ෼ͷχʔζʹ߹ͬͨΧελϚΠζ͕ ՄೳͰ͢ɻ ϓϥάΠϯΛೖΕ͗͢Δͱಈ࡞͕ॏ͘ͳΔ͜ͱ͕͋ΔͨΊɺ஫ҙ͕ඞཁͰ͢ɻద੾ͳ਺ͷϓϥάΠ *1 Obsidian https://obsidian.md/ 89
  91. ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳϕΫτϧݕࡧΛಋೖͯ͠ΈΔ 7.2 ࢲ͕ Obsidian ͕޷͖ͳཧ༝ ϯΛબͼɺObsidian

    ͷύϑΥʔϚϯεΛҡ࣋͢Δ͜ͱ͕ॏཁͰ͢ɻ อ؅ݿʢVaultʣͷ֓೦ Obsidian ͸ɺϊʔτΛʮอ؅ݿʢVaultʣ ʯͱ͍͏୯ҐͰ؅ཧ͠·͢ɻอ؅ݿͱ͸ɺϊʔτ΍ϑΝ Πϧͷू·ΓͰ͋ΓɺObsidian ಺ͰҰͭͷ࡞ۀۭؒΛҙຯ͠·͢ɻอ؅ݿ͸ɺ༻్͝ͱʹ׬શʹ෼ ͚Δ͜ͱ͕Ͱ͖ΔͨΊɺ࢓ࣄ༻ͱϓϥΠϕʔτ༻ͰϊʔτΛ෼͚ΔͳͲɺෳ਺ͷอ؅ݿΛ࢖͍෼͚Δ ͜ͱͰޮ཰తͳ؅ཧ͕ՄೳͰ͢ɻ͜ΕʹΑΓɺϓϩδΣΫτ͝ͱʹ੔ཧ͞Εͨ؀ڥΛߏஙͰ͖·͢ɻ 7.2 ࢲ͕ Obsidian ͕޷͖ͳཧ༝ ࢲ͸ 10 ೥Ҏ্ʹΘͨΓ Evernote Λ࢖͍ͬͯͯ՝ۚ΋͍ͯ͠·ͨ͠ɻ͔͠͠ɺΞϓϦέʔγϣϯ ͷෆ۩߹΍ύϑΥʔϚϯεͷ໰୊͕ଓ͖ɺ࣍ʹ࢖͏ϊʔτΞϓϦΛ୳͠·ͨ͠ɻࣗ෼͕๬ΉϊʔτΞ ϓϦΛٻΊɺϦαʔν͍ͯͨ͠ͱ͜Ζ Obsidian Λݟ͚ͭ·ͨ͠ɻ ϊʔτΞϓϦʹ๬ΜͰ͍Δͱ͜Ζ͸ɺMarkdown ܗࣜͰϊʔτΛ࡞੒Ͱ͖Δ͜ͱͱɺGit ͰόοΫ Ξοϓ؅ཧͰ͖Δ͜ͱͰ͢ɻ͜ͷػೳ͕ඇৗʹॏཁͰɺӡӦձ͕ࣾෆ҆ఆʹͳͬͨΓαʔϏε͕ऴྃ ͨ͠৔߹Ͱ΋ɺࣗ෼ͷϊʔτΛεϜʔζʹ΄͔ͷϓϥοτϑΥʔϜʹҠ؅Ͱ͖·͢ɻ ·ͨ Obsidian Ͱ͸ɺอ؅ݿʢVaultʣʹΑͬͯ͞·͟·ͳ༻్Ͱ࢖͍෼͚͕Ͱ͖·͢ɻiOSɺ AndroidɺMac ͱ͍ͬͨෳ਺ͷϓϥοτϑΥʔϜʹରԠ͓ͯ͠Γɺ୺຤ؒͷಉظػೳ΋ٻΊ͍ͯ·͠ ͨɻಉظ͸ࢲʹͱͬͯඞਢͩͬͨͷͰɺ͙͢ʹ Obsidian Syncʢ༗ྉʣʹՃೖ͠ɺ୺຤ؒͷಉظ΋շ దʹߦ͍͑ͯ·͢ɻ 10 ೥Ҏ্ͷϊʔτ͕͋ͬͨͷͰ࣌ؒΛཁͨ͠΋ͷͷɺEvernote ͔ΒͷσʔλҠ؅͕؆୯ʹߦ͑· ͨ͠ɻ ·ͱΊΔͱɺαʔϏεʹґଘ͠ͳ͍ Markdown Ͱͷσʔλ؅ཧɺϞόΠϧͱσεΫτοϓΞϓϦ ͕͋Δɺ֤σόΠεؒͰಉظ͕Ͱ͖ΔɺϓϥάΠϯʹΑΔΧελϚΠζੑ͕ߴࣗ͘෼ΦϦδφϧͷ ϊʔτΞϓϦ͕࡞ΕΔͱ͍͏఺͕ɺࢲ͕ Obsidian Λ࢖͏࠷େͷཧ༝Ͱ͢ɻ 7.3 ϓϥάΠϯ։ൃೖ໳ Obsidian ͸ɺϢʔβʔͷχʔζʹԠͯ͡ػೳΛΧελϚΠζͰ͖ΔϓϥάΠϯػೳ͕͋Γ·͢ɻ ϓϥάΠϯʹ͸ɺେ͖͘෼͚ͯίΞϓϥάΠϯͱίϛϡχςΟϓϥάΠϯͷ 2 छྨ͕͋Γ·͢ɻ ίΞϓϥάΠϯ͸ɺObsidian ຊମʹඪ४Ͱ౥ࡌ͞Ε͍ͯΔެࣜϓϥάΠϯͰ͢ɻϝϞػೳ΍όο ΫϦϯΫදࣔɺλά؅ཧͳͲɺجຊతͳϊʔτ؅ཧͷػೳΛ֦ு͢ΔͨΊͷ΋ͷ͕ଟ͘ɺϢʔβʔ͕ ༗ޮɾແޮΛ؆୯ʹ੾Γସ͕͑Ͱ͖·͢ɻରͯ͠ίϛϡχςΟϓϥάΠϯ͸ɺϢʔβʔ͕ࣗ༝ʹ։ ൃɾެ։͍ͯ͠Δ΋ͷͰ͋ΓɺObsidian ͷػೳΛ͞Βʹ֦ுͰ͖·͢ɻͨͱ͑͹ɺλεΫ؅ཧ΍εέ δϡʔϦϯάɺσʔλՄࢹԽͷػೳͳͲಛఆͷχʔζʹରԠͨ͠ଟछଟ༷ͳϓϥάΠϯ͕͋Γ·͢ɻ 90
  92. ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳϕΫτϧݕࡧΛಋೖͯ͠ΈΔ7.3 ϓϥάΠϯ։ൃೖ໳ Obsidian ͷϓϥάΠϯ։ൃ؀ڥ Obsidian ͷϓϥάΠϯ͸

    JavaScript Ͱ؆୯ʹ࡞੒Ͱ͖·͢ɻ • Node.js: ϓϥάΠϯͷϏϧυ΍ґଘؔ܎ͷ؅ཧʹඞཁͰ͢ɻ • JavaScript: Obsidian ͷϓϥάΠϯ͸ɺJavaScript Λ࢖ͬͯ։ൃ͠·͢ɻ • Obsidian API: Obsidian ͷػೳΛϓϥάΠϯͱ֦ͯ͠ு͢ΔͨΊʹ API Λ࢖͍·͢ɻ ϓϥάΠϯϓϩδΣΫτͷߏ੒ Obsidian ͷϓϥάΠϯ͸ɺಛఆͷσΟϨΫτϦߏ੒Λ࣋ͭϓϩδΣΫτͱͯ͠։ൃ͞Ε·͢ɻϓ ϩδΣΫτͷجຊߏ੒͸ҎԼͷ௨ΓͰ͢ɻ • manifest.json: ϓϥάΠϯͷ৘ใʢόʔδϣϯɺIDɺґଘؔ܎ͳͲʣΛఆٛ͠·͢ɻ • main.js: ϓϥάΠϯͷओͳϩδοΫ͕ॻ͔ΕΔϑΝΠϧͰ͢ɻObsidian API Λ࢖ͬͯɺ͞· ͟·ͳػೳΛ࣮૷͠·͢ɻ • styles.css: ϓϥάΠϯͷ UI ʹελΠϧΛ௥Ճ͢ΔͨΊͷϑΝΠϧʢඞཁͳ৔߹ʣ ɻ ͜ΕΒͷϑΝΠϧ͕࠷௿ݶඞཁͱͳΓɺσΟϨΫτϦߏ੒Λ੔͑Δ͜ͱͰϓϥάΠϯ͕ Obsidian ಺Ͱೝࣝ͞Εɺಈ࡞͠·͢ɻ ։ൃ༻ͷઃఆͱσόοάํ๏ ։ൃதͷϓϥάΠϯΛ Obsidian Ͱಈ࡞ͤ͞ΔͨΊʹ͸ɺObsidian ͷ։ൃऀϞʔυΛ༗ޮʹ͢Δඞ ཁ͕͋Γ·͢ɻ͜ΕʹΑΓɺϩʔΧϧͰ࡞੒ͨ͠ϓϥάΠϯΛςετͰ͖ɺมߋΛϦΞϧλΠϜͰ൓ өͤ͞Δ͜ͱ͕Ͱ͖·͢ɻσόοά͸ɺϒϥ΢βͷ։ൃऀπʔϧΛར༻͠ɺJavaScript ͷίϯιʔϧ ϩάΛ࢖ͬͯ֬ೝ͠·͢ɻΤϥʔνΣοΫ΋ߦ͍ͳ͕Βɺ҆ఆͨ͠ϓϥάΠϯ։ൃΛਐΊΒΕ·͢ɻ ॳΊͯͷϓϥάΠϯʮHello WorldʯΛ࡞੒ͯ͠ΈΔ ϓϥάΠϯ։ൃͷ࠷ॳͷεςοϓͱͯ͠ɺ؆୯ͳʮHello WorldʯϓϥάΠϯΛ࡞੒͠·͢ɻ͜ͷ ϓϥάΠϯ͸ɺObsidian Λ։͍ͨࡍʹʮHello Worldʯͱ͍͏ϝοηʔδ͕දࣔ͞ΕΔγϯϓϧͳ΋ ͷͰ͢ɻ main.js module.exports = class MyPlugin extends require(’obsidian’).Plugin { onload() { console.log(’HelloWorld! onload’); } 91
  93. ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳϕΫτϧݕࡧΛಋೖͯ͠ΈΔ7.3 ϓϥάΠϯ։ൃೖ໳ onunload() { console.log(’HelloWorld! onunload’);

    } }; ͜ͷίʔυͰ͸ onload ϝιουͰϓϥάΠϯ͕ಡΈࠐ·ΕͨࡍʹʮHelloWorld! onloadʯͱ͍͏ ϝοηʔδΛίϯιʔϧʹදࣔ͠ɺ onunload ϝιουͰϓϥάΠϯ͕ແޮԽ͞Εͨࡍʹ ʮHelloWorld! onunloadʯͱ͍͏ϝοηʔδΛදࣔ͠·͢ɻ·ͣ͸ɺ͜͏͍ͬͨίʔυͰ։ൃ؀ڥ͕੔͍ͬͯΔ͔ͷ ֬ೝΛ͠·͢ɻ manifest.json { "id": "hello-world-plugin", "name": "Hello World Plugin", "version": "1.0.0", "minAppVersion": "0.12.0", "description": "A simple Hello World plugin for Obsidian.", "author": "Kyohei Tsuda", "authorUrl": "https://your-website.com", "isDesktopOnly": false } • id: ϓϥάΠϯͷҰҙͷࣝผࢠɻ • name: ϓϥάΠϯͷද໊ࣔɻ • version: ϓϥάΠϯͷόʔδϣϯɻ • minAppVersion: ϓϥάΠϯ͕αϙʔτ͢Δ Obsidian ͷ࠷খόʔδϣϯɻ • description: ϓϥάΠϯͷઆ໌ɻ • author: ։ൃऀͷ໊લɻ • authorUrl: ։ൃऀͷ Web αΠτʢ೚ҙʣ ɻ • isDesktopOnly: σεΫτοϓ൛ͷΈͰಈ࡞͢Δ৔߹͸ true ʹઃఆ͠·͢ɻ σΟϨΫτϦߏ੒ ΞϓϦέʔγϣϯσΟϨΫτϦʹ͋Δ .obsidian/plugins/ ʹ֨ೲ͠·͢ɻ directory hello-world-plugin/ ᴹ 92
  94. ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳϕΫτϧݕࡧΛಋೖͯ͠ΈΔ 7.4 Obsidian ʹϕΫτϧݕࡧΛಋೖͯ͠ΈΔ ᵓᴷᴷ manifest.json

    ᵓᴷᴷ main.js ᵋᴷᴷ styles.css ࣮ߦํ๏ σΟϨΫτϦʹηοτͨ͠ΒɺίϛϡχςΟϓϥάΠϯͷઃఆ͔ΒϓϥάΠϯͷ࠶ಡΈࠐΈΛ͢Δ ͱϓϥάΠϯҰཡʹදࣔ͞Ε·͢ɻ͋ͱ͸ΦϯʗΦϑΛ࣮ߦ͢Ε͹ onload()ɺonunload() ͕ݺ͹Ε ·͢ɻ ਤ 7.1: ίϛϡχςΟϓϥάΠϯ 7.4 Obsidian ʹϕΫτϧݕࡧΛಋೖͯ͠ΈΔ ϕΫτϧݕࡧͱ͸Կ͔ ϕΫτϧݕࡧ͸ɺैདྷͷΩʔϫʔυϕʔεͷݕࡧͱ͸ҟͳΓɺσʔλΛϕΫτϧۭؒʹϚοϐϯά ͠ɺͦͷྨࣅ౓Λܭࢉͯ͠ݕࡧ݁ՌΛฦ͢ख๏Ͱ͢ɻςΩετσʔλΛϕΫτϧԽʢ਺஋ʹม׵ʣ͠ɺ ͦΕΒͷϕΫτϧͲ͏͠ͷڑ཭΍֯౓Λجʹؔ࿈ੑΛ൑அ͠·͢ɻ͜Ε͸ಛʹɺҙຯతʹྨࣅͨ͠಺ ༰Λݟ͚͍ͭͨͱ͖ʹ໾ཱͪ·͢ɻ طଘͷίϛϡχςΟϓϥάΠϯͱࣗ࡞ͷཧ༝ Obsidian ʹ͸͢ͰʹίϛϡχςΟʹΑͬͯ࡞ΒΕͨϕΫτϧݕࡧͷϓϥάΠϯ͕ଘࡏ͠·͢ɻ͜ ΕΛ࢖༻͢Δ͜ͱͰɺ؆୯ʹϕΫτϧݕࡧΛಋೖͰ͖·͢ɻ͔͠͠ࠓճ͋͑ͯࣗ࡞ΛࢼΈͨཧ༝͸ɺ ϓϥάΠϯ։ൃΛ௨ͯ͠ϕΫτϧݕࡧͷ͘͠ΈΛֶͼɺࣗ෼ͷಛఆͷཁ݅ʹԠͨ͡ΧελϚΠζΛࢪ ͔͔ͨͬͨ͠ΒͰ͢ɻ 93
  95. ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳϕΫτϧݕࡧΛಋೖͯ͠ΈΔ 7.4 Obsidian ʹϕΫτϧݕࡧΛಋೖͯ͠ΈΔ ϕΫτϧݕࡧͷ֓೦ ϕΫτϧݕࡧͰ͸ɺ֤υΩϡϝϯτ΍৘ใΛϕΫτϧۭؒʹϓϩοτ͠ɺͦΕΒͷྨࣅ౓Λܭࢉ͠

    ͯؔ࿈ੑΛ൑அ͠·͢ɻैདྷͷΩʔϫʔυݕࡧͰ͸ɺ୯ޠ͕Ұக͍ͯ͠Δ͔Ͳ͏͔͚ͩʹґଘ͠·͢ ͕ɺϕΫτϧݕࡧͰ͸ɺҙຯతͳྨࣅੑ΍ίϯςΩετ΋ߟྀ͞Ε·͢ɻ ͨͱ͑͹ɺ ʮ௼ΓʯͱʮΞ΢τυΞʯ͸ΩʔϫʔυݕࡧͰ͸ผ෺ͱͯ͠ѻΘΕ·͕͢ɺϕΫτϧݕ ࡧͰ͸ͦΕΒ͕ࣅͨ֓೦ͱͯ͠ͱΒ͑ΒΕɺؔ࿈͢Δ݁Ռ͕ಘΒΕΔՄೳੑ͕ߴ·Γ·͢ɻ ࢖༻͢ΔϥΠϒϥϦͷબఆ ϕΫτϧݕࡧͷ࣮૷ʹ͸ɺ͞·͟·ͳϥΠϒϥϦ͕͋Γ·͢ɻࠓճ͸ɺHaystack ͱ͍͏ϥΠϒϥ ϦΛ࢖༻͠·͢ɻHaystack ͸ɺυΩϡϝϯτݕࡧ΍ QA γεςϜʹ͓͍ͯ࢖ΘΕΔڧྗͳπʔϧͰɺ ϕΫτϧݕࡧͷػೳ΋උ͍͑ͯ·͢ɻଞʹ΋ɺFaiss ΍ Annoy ͳͲͷϕΫτϧݕࡧ༻ͷϥΠϒϥϦ͕ ͋Γ·͕͢ɺࠓճ͸؆ศͰػೳ͕๛෋ͳ Haystack Λબఆ͠·ͨ͠ɻ ϩʔΧϧ؀ڥͰͷઃఆͱ࣮૷४උ Haystack ΛΠϯετʔϧͯ͠ɺObsidian ͱ࿈ܞͰ͖ΔΑ͏ʹϩʔΧϧ؀ڥΛ४උ͠·͢ɻಉ࣌ʹ ґଘؔ܎ͷ͋ΔϥΠϒϥϦ΋Πϯετʔϧ͠·͢ɻ˞ Python 3 ܥͷ։ൃ؀ڥ͕ඞཁͰ͕͢ɺ؀ڥߏ ங౳ͷઆ໌͸ׂѪ͠·͢ɻ $ pip install ’farm-haystack[inference]’ torch sentence-transformers fugashi ipadic ࣍ʹɺPython Ͱ API ΛηοτΞοϓ͠ɺObsidian ͷϊʔτσʔλΛऔಘɾॲཧ͢Δ४උΛߦ͍ ·͢ɻObsidian ͷϊʔτ͸௨ৗɺMarkdown ܗࣜͰอଘ͞Ε͍ͯΔͨΊɺ͜ΕΛςΩετσʔλͱ ͯ͠ѻ͍·͢ɻ ϕΫτϧݕࡧػೳͷ࣮૷ farm-haystack Λ࢖༻ͯ͠ϕΫτϧݕࡧΛߦ͏؆୯ͳ Python εΫϦϓτΛ࡞੒͠·͢ɻ͜ΕΛޙ Ͱ JavaScript ͔Β࣮ߦ͠·͢ɻϢʔβʔ͕ೖྗͨ͠ΫΤϦͱࣄલʹొ࿥͞Εͨ೔ຊޠͷϊʔτσʔ λΛϕΫτϧۭؒʹม׵͠ɺྨࣅ౓ʹج͍ͮͯ࠷΋ؔ࿈ੑͷߴ͍υΩϡϝϯτΛݕࡧ͠·͢ɻ͜ͷϓ ϩηεͰ͸ɺจॻͷ಺༰Λ਺஋తʹදݱ͢ΔͨΊͷຒΊࠐΈϞσϧΛ࢖༻͠·͢ɻݕࡧ͸ϕΫτϧؒ ͷڑ཭ΛجʹߦΘΕɺҙຯతʹ͍ۙจॻΛޮՌతʹ୳͠ग़ͤ·͢ɻ 94
  96. ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳϕΫτϧݕࡧΛಋೖͯ͠ΈΔ 7.4 Obsidian ʹϕΫτϧݕࡧΛಋೖͯ͠ΈΔ main.py from

    haystack.document_stores import InMemoryDocumentStore from haystack.nodes import EmbeddingRetriever from haystack.pipelines import DocumentSearchPipeline import logging logging.basicConfig(level=logging.DEBUG) def run_haystack_query(query): # ຒΊࠐΈϞσϧͷ໊લΛࢦఆ͠ɺGPUΛ࢖༻͠ͳ͍ઃఆ model_name = ’distiluse-base-multilingual-cased-v2’ # υΩϡϝϯτετΞͷηοτΞοϓʢembedding_dimΛ512ʹઃఆʣ document_store = InMemoryDocumentStore(embedding_dim=512) # αϯϓϧͷ೔ຊޠϊʔτσʔλΛ༻ҙ documents = [ {"content": "ࢲ ͸ ٳ ೔ ʹ ւ ௼ Γ Λ ָ ͠ Μ Ͱ ͍ · ͢ ɻಛ ʹ େ ෺ Λ ௼ Δ ͷ ͕ ޷ ͖ Ͱ ͢ɻ", "meta": {"source": "note1.md"}}, {"content": "࠷ۙ͸ొࢁʹ΋௅ઓ͓ͯ͠ΓɺࣗવͷதͰա͕࣌ؒ͢͝ϦϑϨογϡʹͳΓ· ͢ɻ", "meta": {"source": "note2.md"}}, {"content": "ࢲ ͸ Ω ϟ ϯ ϓ ͕ झ ຯ Ͱ ɺ༑ ୡ ͱ Ұ ॹ ʹ ࢁ Ͱ ς ϯ τ Λ ு Γ · ͢ɻ", "meta": {"source": "note3.md"}}, {"content": "ಡ ॻ ͸ ࢲ ͷ େ ޷ ͖ ͳ झ ຯ ͷ Ұ ͭ Ͱ ɺಛ ʹ ϛ ε ς Ϧ ʔ খ આ Λ ޷ Μ Ͱ ͍ · ͢ɻ", "meta": {"source": "note4.md"}} ] document_store.write_documents(documents) # EmbeddingRetrieverΛ࢖༻ͯ͠ϦτϦʔόʔΛઃఆ retriever = EmbeddingRetriever( document_store=document_store, embedding_model=model_name, model_format=’sentence_transformers’, use_gpu=False ) # ຒΊࠐΈΛߋ৽ document_store.update_embeddings(retriever) # DocumentSearchPipelineΛ࢖༻ͯ͠ݕࡧΫΤϦͷ࣮ߦ pipeline = DocumentSearchPipeline(retriever=retriever) # ΫΤϦ࣮ߦ results = pipeline.run(query=query) return results if __name__ == "__main__": query = "௼Γʹ͍ͭͯ" results = run_haystack_query(query) if results: print(results) 95
  97. ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳϕΫτϧݕࡧΛಋೖͯ͠ΈΔ 7.4 Obsidian ʹϕΫτϧݕࡧΛಋೖͯ͠ΈΔ ೔ຊޠΛѻ͑Δ distiluse-base-multilingual-cased-v2

    Ϟσϧ͸ 512 ࣍ݩͷຒΊࠐΈϕΫτϧΛੜ ੒͢ΔͨΊɺembedding_dim=512 Λࢦఆͯ͠·͢ɻ ࣮ߦ݁Ռ ʮ௼Γʹ͍ͭͯʯͱ͍͏ݕࡧΛ͢Δͱɺ͜ͷΑ͏ͳ݁Ռ͕ฦ͖ͬͯ·͢ɻ͜ͷதͰ͸ note1.md ͕ ࠷΋ core ͕ߴ͍͜ͱ͕෼͔Γ·͢ɻcontext ΍ϥΠϒϥϦͷઃఆɺϞσϧΛมߋ͢Δ͜ͱͰɺΑΓߴ ਫ਼౓ͳνϡʔχϯά͕ՄೳͰ͢ɻ result { "documents": [ { "content": "ࢲ͸ٳ೔ʹւ௼ΓΛָ͠ΜͰ͍·͢ɻಛʹେ෺Λ௼Δͷ͕޷͖Ͱ͢ɻ", "score": 0.5008297958802435, "meta": { "source": "note1.md" }, }, { "content": "ࢲ͸Ωϟϯϓ͕झຯͰɺ༑ୡͱҰॹʹࢁͰςϯτΛுΓ·͢ɻ", "score": 0.5001496666252616, "meta": { "source": "note3.md" }, }, { "content": "ಡॻ͸ࢲͷେ޷͖ͳझຯͷҰͭͰɺಛʹϛεςϦʔখઆΛ޷ΜͰ͍·͢ɻ", "score": 0.500142795001931, "meta": { "source": "note4.md" }, }, { "content": "࠷ۙ͸ొࢁʹ΋௅ઓ͓ͯ͠ΓɺࣗવͷதͰա͕࣌ؒ͢͝ϦϑϨογϡʹͳΓ· ͢ɻ", "score": 0.5001341874679, "meta": { "source": "note2.md" }, } ], "root_node": "Query", "params": {}, "query": "௼Γʹ͍ͭͯ", "node_id": "Retriever" } 96
  98. ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳϕΫτϧݕࡧΛಋೖͯ͠ΈΔ 7.4 Obsidian ʹϕΫτϧݕࡧΛಋೖͯ͠ΈΔ Obsidian ͔ΒϕΫτϧݕࡧΛ࣮ߦ

    Obsidian ͔Β௚઀ Python Λݺͼग़͢͜ͱ͕Ͱ͖ͳ͍ͨΊɺflask Λ࢖͍؆୯ͳαʔόΛཱͯ· ͢ɻࠓճ͸ઌ΄ͲͷιʔείʔυΛͦͷ··ྲྀ༻͠·͢ɻ࠷΋Ϛον͢ΔυΩϡϝϯτΛฦ͠·͢ɻ αʔόίʔυ͸গʑ௕͍ͨΊׂѪ͠·͕͢ɺҎԼͷΑ͏ͳ࣮ߦ݁Ռ͕ฦͬͯ͘ΔϩʔΧϧαʔόΛཱ ͯ·͢ɻ $ curl -X POST http://localhost:5000/search \ -H "Content-Type: application/json" \ -d ’{"query": "௼Γʹ͍ͭͯ"}’ {"best_source":"note1.md"} ࣍ʹ Obsidian ଆͷϓϥάΠϯΛ࡞੒͠·͢ɻઌ΄Ͳ࡞੒ͨ͠ hello-world-plugin ʹ࣮૷͠·͢ɻ main.js const { Plugin, requestUrl, Notice, FuzzySuggestModal } = require(’obsidian’); module.exports = class MyPlugin extends Plugin { async onload() { console.log(’MyPlugin loaded’); this.addCommand({ id: ’search-and-open-note’, name: ’Search and Open Note’, callback: () => { new QueryModal(this.app, async (query) => { try { const response = await requestUrl({ url: ’http://localhost:5000/search’, method: ’POST’, contentType: ’application/json’, body: JSON.stringify({ query: query }), }); const bestSource = response.json.best_source; const file = this.app.vault.getAbstractFileByPath(bestSource); if (file && file instanceof require(’obsidian’).TFile) { this.app.workspace.getLeaf().openFile(file); } else { new Notice(‘ϑΝΠϧ͕ݟ͔ͭΓ·ͤΜ: ${bestSource}‘); } } catch (error) { 97
  99. ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳϕΫτϧݕࡧΛಋೖͯ͠ΈΔ 7.4 Obsidian ʹϕΫτϧݕࡧΛಋೖͯ͠ΈΔ console.error(error); new

    Notice(’αʔό΁ͷϦΫΤετʹࣦഊ͠·ͨ͠ɻ’); } }).open(); }, }); } onunload() { console.log(’MyPlugin unloaded’); } }; class QueryModal extends require(’obsidian’).Modal { constructor(app, onSubmit) { super(app); this.onSubmit = onSubmit; } onOpen() { const { contentEl } = this; contentEl.createEl(’h2’, { text: ’ݕࡧΫΤϦΛೖྗ͍ͯͩ͘͠͞’ }); const inputEl = contentEl.createEl(’input’, { type: ’text’ }); inputEl.focus(); inputEl.addEventListener(’keydown’, (event) => { if (event.key === ’Enter’) { this.close(); this.onSubmit(inputEl.value); } }); } onClose() { const { contentEl } = this; contentEl.empty(); } } manifest.json ΋໊લͷΈมߋ͠·ͨ͠ɻ͜͏͢Δ͜ͱͰϓϥάΠϯΛଟগѻ͍΍͘͢ͳΓ·͢ɻ 98
  100. ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳϕΫτϧݕࡧΛಋೖͯ͠ΈΔ 7.4 Obsidian ʹϕΫτϧݕࡧΛಋೖͯ͠ΈΔ manifest.json {

    "name": "Search and Open Note", } ϓϥάΠϯͷ࣮ߦ Obsidian ্ͰʲCommand + Pʳ౳Λ࣮ߦ͢ΔͱɺίϚϯυύϨοτ͕ग़ͯ͘ΔͷͰͦ͜Ͱ ʮSearchʯͱೖྗ͢Ε͹ɺࠓճ࡞੒ͨ͠ϓϥάΠϯ͕Ͱ͖ͯ·͢ɻ ਤ 7.2: ίϚϯυύϨοτ ࠓճ͸ಡॻͱೖྗ࣮͠ߦ͠·͢ɻϩʔΧϧαʔόʹΞΫηε͞ΕϕΫτϧݕࡧ͕࣮ߦ͞Ε·͢ɻ ਤ 7.3: Search and Open Note ͷ࣮ߦ ࣮ߦ͕׬ྃ͢Δͱ֘౰ͷϊʔτ͕։͖·ͨ͠ɻ ਤ 7.4: note4 ͕։͘ ͞ΒͳΔ֦ுΞΠσΞ ϕΫτϧݕࡧͷ݁ՌΛҰཡදࣔʹ͠ɺϢʔβʔ͕બ୒Ͱ͖ΔΑ͏ʹվྑ͢Δ༨஍͕͋Γͦ͏Ͱ͢ɻ ·ͨࠓճͷαϯϓϧͩͱΞϓϦέʔγϣϯʹ௚઀ϊʔτͷ৘ใΛೖΕ͍ͯͨͷͰඇৗʹඇޮ཰Ͱ͢ɻ ࢲ͸ Obsidian ϊʔτ͸͢΂ͯ GitHub ʹόοΫΞοϓ͞Ε͍ͯ·͢ɻϦϞʔταʔόΛཱͯࣗಈత ʹ GitHub ͔ΒϊʔτΛऔΓࠐΈɺϕΫτϧݕࡧ͕Ͱ͖ΔΑ͏ʹ͢Ε͹ɺ͞Βʹศརͳ΋ͷʹͳΔ͸ ͣͰ͢ɻ 99
  101. ୈ 7 ষ Obsidian ϓϥάΠϯ։ൃೖ໳ɻ؆қͳϕΫτϧݕࡧΛಋೖͯ͠ΈΔ 7.5 ऴΘΓʹ 7.5 ऴΘΓʹ ຊষͰ͸ɺϓϥάΠϯ։ൃΛ௨ͯ͡ಘͨϕΫτϧݕࡧͷ஌ݟͱɺࠓޙͷ֦ுΞΠσΞʹ͍ͭͯ·ͱ

    Ί·ͨ͠ɻ࠷ۙɺLLMʢେن໛ݴޠϞσϧʣ΍ϕΫτϧݕࡧɺͦͯͦ͠ΕΒΛ׆༻ͨ͠ػցֶशٕज़ ʹ৮ΕΔػձ͕૿͑ɺΑΓਂ͘ཧղ͢ΔͨΊʹɺීஈ࢖͍ͬͯΔ Obsidian Λ࢖ͬͯϓϥάΠϯ࡞Γ ʹ௅ઓ͠·ͨ͠ɻObsidian ͸୯ͳΔϊʔτΞϓϦʹͱͲ·Βͣɺ࢖͍ํ࣍ୈͰඇৗʹߴ౓ͳ৘ใ੔ ཧ΍ݕࡧػೳΛ࣮ݱͰ͖Δ͜ͱ͕෼͔Γ·ͨ͠ɻಛʹɺϕΫτϧݕࡧͷಋೖʹΑΓɺ๲େͳ৘ใͷத ͔Βؔ࿈ੑͷߴ͍σʔλΛޮ཰తʹநग़Ͱ͖ɺࠓޙͷԠ༻ͷՄೳੑ΋޿͕͍ͬͯ·͢ɻͨͱ͑͹ࢲͷ झຯͰ͋Δ௼ΓͳͲಛఆͷτϐοΫʹؔ࿈ͨ͠৘ใΛ͖Ε͍ʹ੔ཧ͠ɺޮ཰తʹݕࡧͰ͖ΔΦϦδφ ϧͷυΩϡϝϯτγεςϜΛ࡞Γ͍ͨͱ͍͏໨ඪ΋ݟ͖͑ͯ·ͨ͠ɻ Obsidian ͸ɺ࢖͍ํ࣍ୈͰແݶͷՄೳੑΛൿΊͨπʔϧͰ͋Γɺࢲʹͱͬͯͳͯ͘͸ͳΒͳ͍ ଘࡏͱͳ͍ͬͯ·͢ɻ͜Ε͔Β΋ࣗ෼ͷχʔζʹ߹ͬͨΧελϚΠζ΍৽͍ٕ͠ज़ͷಋೖΛଓ͚ɺ Obsidian Λ͞ΒʹਐԽ͍͖͍ͤͯͨ͞ͱࢥ͍·͢ɻ 100
  102. ஶऀ঺հ ߐാ ୓࠸ (ୈ 1 ষ୲౰, GitHub: @MokkeMeguru) ιʔγϟϧϕοςΟϯάࣄۀຊ෦ ։ൃࣨ

    ৽ن։ൃάϧʔϓͷ։ൃ޻਺Ͱ͢ɻओʹ TIPSTAR ͷ։ൃʹैࣄ͓ͯ͠Γɺ࠷ۙͰ͸ iOS ։ൃͱαʔϏεશൠͷӡ༻Λத৺ʹ׆ಈ͍ͯ͠·͢ɻ ๭ॳԻϛΫͷԻήʔָ͍͠Ͱ͢ͶɺXcode ͷϏϧυ଴ͪͷ࣌ؒ͸΋ͬͺΒ͜ΕͰ͕࣌ؒ௵ͤ ·͢ɻ ࠤʑా ૖େ (ୈ 2 ষ୲౰, GitHub: soudai-s, X: @soudai_s) ΈͯͶϓϩμΫτ։ൃ෦ͰࣸਅϓϦϯτྖҬΛத৺ʹωΠςΟϒΞϓϦ (iOSɺAndroid) ͱ αʔό (Ruby on Rails) ͷ։ൃΛ͍ͯ͠·͢ɻεϊϘʔͱϦθϩ͕޷͖ɻ एদ ৎਓ (ୈ 3 ষ୲౰, GitHub: @take-2405 X: @Jaksho500) 23 ೥౓৽ଔͱͯ͠ೖࣾ͠ɺTIPSTAR ͱ͍͏αʔϏεͷ։ൃɾӡ༻Λߦ͍ͬͯ·͢ɻ࠷ۙӳ ޠͷษڧʹ೤ΛೖΕ͍ͯ·͢ɻ দݪ ৴஧ (ୈ 4 ষ୲౰) ॴଐ͸ϞϯεταʔόνʔϜͰ Ruby ΍ Go Λॻ͍ͯΔɻϓϩάϥϛϯά͕޷͖Ͱɺͨ·ʹ ਪ͠ݴޠͷ Haskell Ͱ༡ΜͩΓ͍ͯ͠Δɻ ࢁ୩ ༏հ (ୈ 5 ষ୲౰) ιʔγϟϧϕοςΟϯάࣄۀຊ෦ TIPSTAR ࣄۀ෦ ։ൃάϧʔϓͰαʔόʔνʔϜͷϦʔ μʔΛ͍ͯ͠·͢ɻࡢ೥ 3 ਓ໨ͷࢠڙ͕ੜ·Εɺ೔ʑҭࣇʹฃಆ͓ͯ͠Γ·͢ɻ࠷ۙ͸ଉࢠͷ ۭखʹՈ଒Ͱ೤த͍ͯ͠·͢ɻ ࢁԼ কޗ (ୈ 6 ষ୲౰) ιʔγϟϧϕοςΟϯάࣄۀຊ෦ TIPSTAR ࣄۀ෦։ൃάϧʔϓॴଐɻ͜͜࠷ۙ͸ඇΤϯδ χΞ޲͚ͷۀ຿վળʹ൐͏։ൃ͕ଟΊͰ͢ɻ ௡ా ګฏ (ୈ 7 ষ୲౰ GitHub: @flatfisher) ιʔγϟϧϕοςΟϯάࣄۀຊ෦ TIPSTAR ࣄۀ෦ ։ൃάϧʔϓ ϚωʔδϟʔɻTIPSTAR ։ൃνʔϜͷϚωδϝϯτ͍ͯ͠·͢ɻٳ೔͸΄΅௼ΓΛ͍ͯ͠·͢ɻ 101
  103. MIXI TECH NOTE #12 2024 ೥ 11 ݄ 2 ೔ɹॳ൛ୈ

    1 ࡮ɹൃߦ ஶɹऀ גࣜձࣾ MIXI ༗ࢤ ൃߦॴ גࣜձࣾ MIXI ҹ࡮ॴ ೔ޫاը ɹ ˜ MIXI