ã¯ãã¯ãããéçºè
ããã°
2024-12-12T11:00:00+09:00
cookpadtech
Hatena::Blog
hatenablog://blog/12921228815724287123
ãã¬ã³ãã¯ã¼ãæ©è½ãæ°ã·ã¹ãã ã«ç§»è¡ããã¨ãã«èæ
®ãããã¨
hatenablog://entry/6802418398310512953
2024-12-12T11:00:00+09:00
2024-12-12T11:00:01+09:00 ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨æ¤ç´¢ãã¼ã ã®èç¾½ (@usulity) ã§ãã ç¶ã
ã¨é¢é£è¨äºãæ稿ããã¦ãã¾ãããæ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ããã ãã®çµ±åã«éãã¦ãæ¥æ¬ã®ã¯ãã¯ãããã®æ§ã
ãªæ©è½ãã°ãã¼ãã«çã¸ç§»æ¤ããã¾ãããä»åã¯ã移æ¤ãããæ©è½ã®ä¸ã¤ã§ããã人æ°ã®ãã¼ã¯ã¼ããã«ã¤ãã¦ã移æ¤ããéã«ã©ããªèª²é¡ããã£ã¦ã©ã解決ããã®ãã®ä¸é¨ããç´¹ä»ã§ããã°ã¨æãã¾ãã 人æ°ã®ãã¼ã¯ã¼ã 人æ°ã®ãã¼ã¯ã¼ãã¯ããã¯ãã¯ãããã§æè¿ããæ¤ç´¢ããã¦ãããã¼ã¯ã¼ãããéè¨ãã¦ãã©ã³ãã³ã°å½¢å¼ã§æ²è¼ããæ©è½ã§ãã æ¥æ¬ç人æ°ã®ãã¼ã¯ã¼ã ãã®ããã«ã人æ°ã®ãã¼ã¯ã¼ãã¯ï¼æéããã«æ´æ°ããããã®æâ¦
<p>ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨æ¤ç´¢ãã¼ã ã®èç¾½ (<a href="https://x.com/usulity">@usulity</a>) ã§ãã</p>
<p>ç¶ã
ã¨é¢é£è¨äºãæ稿ããã¦ãã¾ãããæ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ããã
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2024%2F10%2F10%2F105832" title="æ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ãã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>ãã®çµ±åã«éãã¦ãæ¥æ¬ã®ã¯ãã¯ãããã®æ§ã
ãªæ©è½ãã°ãã¼ãã«çã¸ç§»æ¤ããã¾ãããä»åã¯ã移æ¤ãããæ©è½ã®ä¸ã¤ã§ããã人æ°ã®ãã¼ã¯ã¼ããã«ã¤ãã¦ã移æ¤ããéã«ã©ããªèª²é¡ããã£ã¦ã©ã解決ããã®ãã®ä¸é¨ããç´¹ä»ã§ããã°ã¨æãã¾ãã</p>
<h2 id="人æ°ã®ãã¼ã¯ã¼ã">人æ°ã®ãã¼ã¯ã¼ã</h2>
<p>人æ°ã®ãã¼ã¯ã¼ãã¯ããã¯ãã¯ãããã§æè¿ããæ¤ç´¢ããã¦ãããã¼ã¯ã¼ãããéè¨ãã¦ãã©ã³ãã³ã°å½¢å¼ã§æ²è¼ããæ©è½ã§ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="æ¥æ¬ç人æ°ã®ãã¼ã¯ã¼ã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/argonism/20241210/20241210153659.png" alt="日本版クックパッドの人気のキーワードページ" width="1200" height="771" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>æ¥æ¬ç人æ°ã®ãã¼ã¯ã¼ã</figcaption></figure></p>
<p>ãã®ããã«ã人æ°ã®ãã¼ã¯ã¼ãã¯ï¼æéããã«æ´æ°ããããã®ææã»æé帯ã®ãã¬ã³ããåæ ãããããªãã¼ã¯ã¼ãã®ã©ã³ãã³ã°ã«ãªã£ã¦ãã¾ãã</p>
<p>ããããã¼ã¸ã®æ¤ç´¢çªã®ä¸ã«ãä¸ä½ã®ãã¼ã¯ã¼ãã表示ããã¦ããã人ç®ã«ã¤ããããæ©è½ã®ä¸ã¤ã§ãã</p>
<h2 id="ã°ãã¼ãã«ç人æ°ã®ãã¼ã¯ã¼ã">ã°ãã¼ãã«ç人æ°ã®ãã¼ã¯ã¼ã</h2>
<p>ã°ãã¼ãã«çã«ã人æ°ã®ãã¼ã¯ã¼ãæ©è½ãããã¾ãã</p>
<p>åºæ¬çãªæ©è½ã¯æ¥æ¬ã®ãã®ã¨åãã§ããã®ã¨ãã®ããæ¤ç´¢ããã¦ãããã¼ã¯ã¼ãã表ç¾ãã¦ãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="ã°ãã¼ãã«ç人æ°ã®ãã¼ã¯ã¼ã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/argonism/20241210/20241210153653.png" alt="グローバル版人気のキーワードのスクリーンショット" width="1200" height="378" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ã°ãã¼ãã«çè±èªã®äººæ°ã®ãã¼ã¯ã¼ã</figcaption></figure></p>
<p>ä»ååãçµãã ã¿ã¹ã¯ã¯ããã®ã°ãã¼ãã«çã®äººæ°ã®ãã¼ã¯ã¼ãã¨ãã¦ãæ¥æ¬ã®äººæ°ã®ãã¼ã¯ã¼ããæ¥æ¬çã®è¨ç®æ¹å¼ã§è¡¨ç¤ºãããã¨ãã´ã¼ã«ã«ãªãã¾ãã</p>
<h2 id="課é¡">課é¡</h2>
<p>ã¨ãããã¨ã§ããã°ãã¼ãã«ã«ã人æ°ã®ãã¼ã¯ã¼ãããããããæ¥æ¬èªã§ãéè¨ãå®è¡ããããã«ä¿®æ£ããã°çµããï¼ãã¨ãããã¨ãããªãã§ãããããã¤ãåé¡ãããã¾ãã</p>
<p>ãããããã°ãã¼ãã«çã¨æ¥æ¬çã¯å¥ç©ã®ã¢ããªã±ã¼ã·ã§ã³ã§ããããéãã¯è²ã
ããã¾ãããããã°ããªãããã¨ã¨ãæ´æ°é »åº¦ã®éããã¯ç§»æ¤ããä¸ã§å¤§ããªåé¡ã§ããã</p>
<h3 id="ãã°ããªã">ãã°ããªã</h3>
<p>人æ°ã®ãã¼ã¯ã¼ãæ©è½ã移æ¤ããã«ããããã¦ã¼ã¶ã移è¡ãã¦ããã¿ã¤ãã³ã°ã§ã¯ãã人æ°ã®ãã¼ã¯ã¼ãã表示ãã¦ããããã¨ããè¦æãããã¾ããã</p>
<p>人æ°ã®ãã¼ã¯ã¼ãã¯æ¤ç´¢ãã°ããè¨ç®ããããã人æ°ã®ãã¼ã¯ã¼ãã表示ããã«ã¯ã°ãã¼ãã«çã§æ¤ç´¢ãã°ãæºã¾ã£ã¦ããå¿
è¦ãããã¾ãã</p>
<p>ããããæ¥æ¬ã®ã¦ã¼ã¶ãã°ãã¼ãã«çã®æ¹ã«æµãã¾ã§ã¯ãæ¥æ¬ã®ã¦ã¼ã¶ã¯ã°ãã¼ãã«çã®æ¹ã«ã¯ããªãããæ¤ç´¢ãã°ã¯ããã¾ããã</p>
<p>ãã®ã¾ã¾ã§ã¯ç§»è¡ãã¦ããã¿ã¤ãã³ã°ã§äººæ°ã®ãã¼ã¯ã¼ãã表示ãããã¨ãã§ããªãã®ã§ããªãã¨ãã°ãã¼ãã«ã®æ¹ã«ãã°ããªãç¶æ
ã§ã人æ°ã®ãã¼ã¯ã¼ããè¨ç®ã§ããããã«ãã¦ããããã§ãã</p>
<p>ã¾ãããã¼ã¯ã¼ãã®ã¯ãªãªãã£ã®é¢ã§ãæ¸å¿µãããã¾ãã</p>
<p>ãã®æ¬¡ã§ã説æãã¾ããã人æ°ã®ãã¼ã¯ã¼ãã®è¨ç®ã¯ãéå»ã®é·ãæéã«æ¸¡ã£ããã°ã使ããã¨ã§ããã®ã¨ãã®äººæ°åº¦ãè¨ç®ãã¦ãã¾ãã</p>
<p>ãã®ããã移è¡ã®åæ段éã§ã¯ãã°ãååã«æºã¾ã£ã¦ããªããã¨ã§ã人æ°ã®ãã¼ã¯ã¼ãã®è³ªãä¸å®å®ã«ãªã£ã¦ãã¾ãæããããã¾ãã</p>
<h3 id="æ´æ°é »åº¦ã®éã">æ´æ°é »åº¦ã®éã</h3>
<p>æ¥æ¬ã®æ¹ã¯<strong>æ¯æ</strong>ã®æ´æ°ãªã®ã«å¯¾ãã¦ãã°ãã¼ãã«ã®æ¹ã¯<strong>æ¥æ¬¡</strong>ã®æ´æ°ã«ãªãã¾ãã</p>
<p>è¨ç®é »åº¦ãæ¥æ¬¡ãæ¯æãã§ã¯ãåãªãå®è¡é »åº¦ã®éã以ä¸ã®å·®ãããã人æ°åº¦ã®è¨ç®æ¹æ³ãç°ãªãã¾ãã</p>
<p>人æ°åº¦ã¯é·æçãªç®ç·ã¨çæçãªç®ç·ã®ä¸¡æ¹ã§è¦ãæã«ãç®ç«ã£ã¦æ¤ç´¢ããã¦ããèªãæ½åºãã¦ãã¾ãã</p>
<p>ç°¡ç¥åãã¾ãããä¾ãã°ã°ãã¼ãã«çã®æ¹ã§ã¯ã人æ°åº¦ãããã®ãã¼ã¯ã¼ãã®ãã®æ¥ã®æ¤ç´¢åæ°ã/ ãéå»ã«æ¸¡ã£ããã®ãã¼ã¯ã¼ãã®ï¼æ¥ã®å¹³åæ¤ç´¢åæ°ãã§è¨ç®ãã¾ãã</p>
<p>ãããããã¨ã§ããããã¼ã¯ã¼ããå¹³åãããã®æ¥å¤ãæ¤ç´¢ããã¦ãããããã®ãã¼ã¯ã¼ãã®äººæ°åº¦ã¯é«ããªãã¾ãã</p>
<p>æ¯æã®è¨ç®ã«ããã¦ãåããããªãã¨ããã¾ãããæéã®åºåãæ¹ãç°ãªãã¾ãã</p>
<p>ããã®æé帯ãã§ç®ç«ã£ã¦æ¤ç´¢ããããã¼ã¯ã¼ãã調ã¹ããã®ã§ãæ¯æã®è¨ç®ã§ã¯äººæ°åº¦ããããã®ãã¼ã¯ã¼ããï¼æéã®éã«æ¤ç´¢ãããåæ°ã-ããã®ãã¼ã¯ã¼ãããã®æé帯ã§æ¤ç´¢ãããå¹³ååæ°ãã¨ãã¦è¨ç®ãã¾ãã</p>
<p>ãã®ããã«ãæ¥æ¬¡ã¨æ¯æã§ã¯äººæ°åº¦ã®è¨ç®æ¹æ³ãç°ãªããæ¯æã§ã¯æ¥æ¬¡ããç´°ããåä½ã§å¹³åãè¨ç®ãã¾ãã</p>
<p>åç´ã«ã°ãã¼ãã«çã®äººæ°ã®ãã¼ã¯ã¼ãã®è¨ç®ãæ¯æã«åããã¦ãåç¾ã§ããªããã¨ãåãã£ãã®ã§ãã°ãã¼ãã«çã®äººæ°ã®ãã¼ã¯ã¼ãè¨ç®ã®ä»çµã¿ãå¤ããããæ°ãããããã¸ã§ããç¨æããå¿
è¦ãããã¾ãã</p>
<h2 id="解決">解決</h2>
<h3 id="æ¥æ¬çã¨ã°ãã¼ãã«ç両æ¹ã®ãã°ããåç®ãã">æ¥æ¬çã¨ã°ãã¼ãã«ç両æ¹ã®ãã°ããåç®ãã</h3>
<p>移è¡å
ã«ãã°ããªãåé¡ã解決ããããã«ã移è¡å
ã®äººæ°ã®ãã¼ã¯ã¼ãã¯æ¥æ¬ã¨ã°ãã¼ãã«ä¸¡æ¹ã®ãã°ããè¨ç®ãã¾ãã</p>
<p>幸ããæ¥æ¬çã§ã人æ°ã®ãã¼ã¯ã¼ãã®è¨ç®ã«ç¹å¥ãªæ
å ±ã¯ä½¿ã£ã¦ããªãã£ããããã°ãã¼ãã«çã®ãã°ã§æ
å ±ã足ããªãã¨ãã£ããã¨ã¯ããã¾ããã§ããã</p>
<p>ããã«ãããã°ãã¼ãã«çã«ç§»è¡åæã®ã¿ã¤ãã³ã°ã§ããè¯ãã¯ãªãªãã£ã®äººæ°ã®ãã¼ã¯ã¼ããè¨ç®ã§ããããã«ãªãã¾ãã</p>
<p>ã¾ããã¦ã¼ã¶ã®æ®µéçãªç§»è¡ãèããæã«ãããã®æ¹æ³ã¯å©ç¹ãããã¾ãã</p>
<p>æ¥æ¬çã¨ã°ãã¼ãã«ä¸¡æ¹ãããã°ãåå¾ãã¦ãããããä»®ã«ã¦ã¼ã¶ã®50%ã¯æ¥æ¬ããã50%ãã°ãã¼ãã«ã«ãããããªã±ã¼ã¹ã§ãã両æ¹ã®ãã©ãããã©ã¼ã ã§ã®ã¦ã¼ã¶è¡åãå å³ãã人æ°ã®ãã¼ã¯ã¼ããè¨ç®ã§ãã¾ãã</p>
<p>å®éã«ã One Experience ã§ã¯ã¦ã¼ã¶ãæ¥æ¬çããå°ããã¤ã°ãã¼ãã«çã®æ¹ã¸ç§»è¡ãã¦ããã¾ããã</p>
<p>ããããããã®æ¤ç´¢ãã°ãåã価å¤ã¨è¦ãªãã¦è¯ããã¯è°è«ã®ä½å°ãããã¾ããããããæ¤è¨¼ããã®ã¯å¤§å¤ãªã®ã§ãï¼ã¤ã®æ¤ç´¢ãã°ãï¼åã®æ¤ç´¢ã示ããã¨ã ã確èªãã¦ã移è¡éä¸ã§æ¥æ¬çã¨å¤§ããçµæãé¢ãã¦ããªããã°ããã¨ãã¾ããã</p>
<p>ãã®ããã«ããã¼ã¿ã½ã¼ã¹ã両æ¹ã®ãã©ãããã©ã¼ã ããåå¾ãã¦è¨ç®ãããã¨ã§ã移è¡æ®µéã«å·¦å³ãããã«äººæ°ã®ãã¼ã¯ã¼ããæä¾ãããã¨ãã§ãã¾ãã</p>
<h3 id="æ¥æ¬ç¨ã®ãããã¸ã§ããç¨æãã">æ¥æ¬ç¨ã®ãããã¸ã§ããç¨æãã</h3>
<p>æ´æ°é »åº¦ã®éãã«ãã人æ°åº¦ã®è¨ç®æ¹æ³ã®éãã大ãããããä»åã¯æ¥æ¬ç¨ã®ãããã¸ã§ããç¨æãããã¨ã«ãã¾ããã</p>
<p>ã°ãã¼ãã«çã®æ¹ã®ä»çµã¿ãæ¥æ¬çã«å¯ããæ¹åæ§ãèæ
®ãã¾ããããæ´æ°é »åº¦ä»¥å¤ã«ä»¥ä¸ã®çç±ã§å¥ã®ãããã¸ã§ãã¨ãã¦ä½ããã¨ã«ãã¾ããã</p>
<ol>
<li>ã°ãã¼ãã«çã®æ¹ã®äººæ°ã®ãã¼ã¯ã¼ãã®ã¯ãªãªãã£ããã§ãã¯ããªããéçºããã³ã¹ããé«ã</li>
<li>æ¥æ¬ã¨ãã以å¤ã®è¨èªã§ã¯ãã°ã®éã«å·®ãããããããã®ããã®ãã©ã¡ã¼ã¿èª¿æ´ã«ã³ã¹ããããã</li>
</ol>
<p>ã°ãã¼ãã«çã§ã¯ãå¤æ°ã®è¨èªã®äººæ°ã®ãã¼ã¯ã¼ããè¨ç®ãã¦ãã¾ããã°ãã¼ãã«çã®äººæ°ã®ãã¼ã¯ã¼ãã®ä»çµã¿ãå¤ããæãæ½åºããããã¼ã¯ã¼ãã®ã¯ãªãªãã£ã¸ã®å½±é¿ããã§ãã¯ãããããã§ãããè¨èªãéãã®ã§ãã§ãã¯ããã®ã大å¤ã§ãã</p>
<p>ã¾ããæ¥æ¬ç¨ã«ãã¥ã¼ãã³ã°ãã人æ°åº¦ã®è¨ç®æ¹æ³ããä»ã®è¨èªã§ãã¾ãããã¨ã¯éãã¾ãããæ¥æ¬åãã«è¨å®ãããã©ã¡ã¼ã¿ãä»ã®è¨èªã§ãé©åãã©ãããããããããã¾ãåè¨èªåãã«è¨å®ãããã®ãã³ã¹ãããããã¾ãã</p>
<p>æ¥æ¬ã¨ãã®ä»ã®è¨èªã§å¥ã
ã®ãããã¸ã§ãã¨è¨ã£ã¦ããå°æ¥çã«ã¯ãã¯ããªãã¹ãï¼ã¤ã«ã¾ã¨ããããããã«ãã¦ããããã®ã§ããªãã¹ãã°ãã¼ãã«çã®ä»çµã¿ã«ã¯ä¹ãããã«æ³¨æãã¾ããã</p>
<h2 id="çµæã¨ã¾ã¨ã">çµæã¨ã¾ã¨ã</h2>
<p>çµæã¨ãã¦ãã¦ã¼ã¶ã®ç§»è¡ãå§ã¾ãåã«å®è£
ãå®äºããç¡äºç§»è¡åæ段éã§ã人æ°ã®ãã¼ã¯ã¼ãã表示ãããã¨ãã§ãã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="ja_JP_trending_keyword"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/argonism/20241210/20241210153547.png" alt="グローバル版日本の人気のキーワード" width="1200" height="378" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ã°ãã¼ãã«çæ¥æ¬ã®äººæ°ã®ãã¼ã¯ã¼ã</figcaption></figure></p>
<p>ãã¯ããã¦ã¼ã¶ãã©ã£ã¡ã®ãã©ãããã©ã¼ã ã«å±
ã¦ã人æ°ã®ãã¼ã¯ã¼ããè¨ç®ã§ãããã¨ã¯ãOne Experience ã§ã¯ã¨ã¦ãæå¹ãªæ¹æ³ã ã£ãã¨æãã¾ãã</p>
<p>ã¾ãããããã¸ã§ããåãããã¨ã§ä»ã®è¨èªã¸ã®å½±é¿ããã¾ãæ°ã«ããå¿
è¦ããªãã£ããã¨ããéçºãã¦ããä¸ã§ã¯éè¦ã ã£ãã¨ä»æ¹ãã¦æãã¾ããä¸æ¹ã§ã人æ°ã®ãã¼ã¯ã¼ãã®è¨ç®ãæ¥æ¬èªã ãåããã¦ãããã¨ã§ã¡ã³ããã³ã¹ã³ã¹ããå¢ãã¦ãã¾ã£ã¦ããã®ã§ããããä»è¨èªã¨çµ±åãã¦ããã¨ãããã¨ãä»å¾ã®èª²é¡ã¨ãã¦æ®ã£ã¦ãã¾ãã</p>
<p>å人çãªææ³ã¨ãã¦ã¯ãæ£ãã人æ°ã®ãã¼ã¯ã¼ããè¨ç®ã§ãã¦ããã®ãæå¾ã¾ã§ä¸å®ãããã¾ããããçµæã¨ãã¦ã¯æ¥æ¬çã¨åæ§ã®ãã¼ã¯ã¼ããã°ãã¼ãã«çã§ã表示ãããã¨ãã§ãã¦è¯ãã£ãã¨æã£ã¦ãã¾ãã</p>
<p>以ä¸ãOne Experience ã«ããã人æ°ã®ãã¼ã¯ã¼ãæ©è½ã®ç§»è¡ã®è©±ã§ããã</p>
<p>é¢ç½ãã¨æã£ã¦ãã ãã£ãæ¹ã¯ããã²ãã£ã³ãã«ç»é²ã¨é«è©ä¾¡ãã¾ãä»ã«ãè²ã
ãªè§åº¦ã® One Experience 話ãæ稿ããã¦ããã¾ãã®ã§ããã²ãã¡ããã覧ãã ããã</p>
argonism
One experience æ¤ç´¢ç§»è¡ã®è©±
hatenablog://entry/6802418398310554437
2024-12-11T11:12:00+09:00
2024-12-11T11:12:00+09:00 ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨æ¤ç´¢ãã¼ã ã®ãªãªã®ã«(@orgil_)ã§ãã å
æ¥ããã®éçºè
ããã°ã§ç´¹ä»ãããOne experienceããã¸ã§ã¯ãã«ãã£ã¦ãã¯ãã¯ãããã¯ãããã¯ãåºç¤ãã°ãã¼ãã«çã®ã·ã¹ãã ã«ç§»è¡ãã¾ãããç§ã¯ãã®ããã¸ã§ã¯ãã«ããã¦æ¤ç´¢ã®é åã§ç§»è¡ã®é²è¡ãéçºãæ
å½ãã¦ãã¾ããããã®ããã°ã§ã¯æ¤ç´¢ã·ã¹ãã ã®ç§»è¡ã«ã¤ãã¦ç´¹ä»ãã¾ãã ã°ãã¼ãã«ã®ã·ã¹ãã ã«å¯ããçç± One experience ã«ãã£ã¦ã¯ãã¯ãããã¯æ¤ç´¢åºç¤ãã°ãã¼ãã«å´ã®ã·ã¹ãã ã«ç§»è¡ãã¾ãããããã¸ã§ã¯ãçºè¶³å½åãæ¥æ¬ã¨ã°ãã¼ãã«ã®ã©ã¡ãã®ã·ã¹ãã ã«å¯ããããããããªè¦³ç¹ããæ¤è¨ãã¾ãããæ¥æ¬ã§ã¯Solrâ¦
<p>ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨æ¤ç´¢ãã¼ã ã®ãªãªã®ã«(<a href="https://twitter.com/orgil_">@orgil_</a>)ã§ãã
å
æ¥ããã®éçºè
ããã°ã§ç´¹ä»ãããOne experienceããã¸ã§ã¯ãã«ãã£ã¦ãã¯ãã¯ãããã¯ãããã¯ãåºç¤ãã°ãã¼ãã«çã®ã·ã¹ãã ã«ç§»è¡ãã¾ãããç§ã¯ãã®ããã¸ã§ã¯ãã«ããã¦æ¤ç´¢ã®é åã§ç§»è¡ã®é²è¡ãéçºãæ
å½ãã¦ãã¾ããããã®ããã°ã§ã¯æ¤ç´¢ã·ã¹ãã ã®ç§»è¡ã«ã¤ãã¦ç´¹ä»ãã¾ãã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2024%2F10%2F10%2F105832" title="æ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ãã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<h2 id="ã°ãã¼ãã«ã®ã·ã¹ãã ã«å¯ããçç±">ã°ãã¼ãã«ã®ã·ã¹ãã ã«å¯ããçç±</h2>
<p>One experience ã«ãã£ã¦ã¯ãã¯ãããã¯æ¤ç´¢åºç¤ãã°ãã¼ãã«å´ã®ã·ã¹ãã ã«ç§»è¡ãã¾ãããããã¸ã§ã¯ãçºè¶³å½åãæ¥æ¬ã¨ã°ãã¼ãã«ã®ã©ã¡ãã®ã·ã¹ãã ã«å¯ããããããããªè¦³ç¹ããæ¤è¨ãã¾ãããæ¥æ¬ã§ã¯SolrãRubyãECSãªã©ã§åã<strong>Voyager</strong>ã¨ããã·ã¹ãã ãã°ãã¼ãã«ã§ã¯ElasticsearchãPythonãk8sãKafkaãªã©ãç¨ããglobal-search-v2(é称<strong>GS2</strong>)ã¨ããæ¤ç´¢ã·ã¹ãã ãåãã¦ãã¾ããæ¤è¨ã®éãã©ã¡ãã®ã·ã¹ãã ãä¸é·ä¸çã§ãç¹æ®µåªå£ãã¤ãããããã®ã§ããã</p>
<p>æ¤ç´¢ã¢ã«ã´ãªãºã ã«é¢ãã¦ã¯ä¸¡ã·ã¹ãã ã§èãæ¹ã¯ä¸ç·ã§ãããè¾æ¸ãã¼ã¹ã§ã¯ã¨ãªæ¡å¼µãããããã¥ã¡ã³ãã®ã¿ã¤ãã«ãææãªã©ã«ãããã¹ã³ã¢ãä»ããè¾æ¸ã®å±æ§ã«ãã£ã¦ç´°ãã調æ´ããããããªã¹ã³ã¢ãªã³ã°ã¢ã«ã´ãªãºã ã¯ä¸¡æ¹ã«ããã¾ãã</p>
<p>ã¾ãããã¥ã¡ã³ãã®æ¤ç´¢ã¸ã®åæ æéã¯ã©ã¡ããåçã¨å¼ã¹ããã®ã§ãããGS2ã¯Kafkaãç¨ããã¤ãã³ãå¦çã·ã¹ãã ãç¨ãã¦ãã»ã¼å³æåæ ãå®ç¾ãã¦ãã¾ããVoyagerã§ãå®æçãªåæãããããã«ãªã£ã¦ãããã¬ã·ãã®å¤æ´ãæç5åã§æ¤ç´¢çµæã«åæ ãããããã«ãªã£ã¦ãã¾ããä¸è¨ã®ããã°ã§è§¦ãã¦ããããã«ããããã¯ããæ±ããã¦ã¼ã¶ã¼ä½é¨ãå®ç¾ããããã«ã¯5åã§ããã¥ã¡ã³ããåæ ãããã°ååã§ãããããã©ã¡ãã®ã·ã¹ãã ã§ãåæ§ãªä¾¡å¤ãå±ãããã¾ãã
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2023%2F10%2F05%2F150000" title="ã¯ãã¯ãããã®æ¤ç´¢åæ æéã 1/288 ã«ããã·ã¹ãã æ¹ä¿® - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>VoyagerãGS2ããä¸çªåªãã¦ããç¹ã¯ãé度ã§ãããè°è«æç¹ã§ã¯GS2ã®p50, p95 ã¬ã¹ãã³ã¹ã¿ã¤ã ã¯Voyagerããç´4åã»ã©é
ããã®ã§ãããããã¯å¤§ããªæ¸å¿µè¦ç´ ã§ãé度ã¯æ¤ç´¢ã·ã¹ãã ã«ã¨ã£ã¦ã¨ã¦ã大äºãªææ¨ã§ããGS2ãæ¡ç¨ããéã«ãæ¥æ¬ã®å¤§ããªãã©ãã£ãã¯éãå¢ãããã¨ã§GS2ãæ´ã«é
ããªããªãããªã©ã®æ¸å¿µç¹ã¯ããã¾ããã</p>
<p>ããããã¨è°è«ãã¾ããããæçµçã«ã¯GS2ã®æ¹ã«çµ±åãããã¨ã«ãªãã¾ãããä¸çªã®æ±ºãæã¯å¤è¨èªå¯¾å¿ãVoyagerã§ã¯é£ããã£ãããã§ããGS2ã¯ãã§ã«ç´30è¨èªããµãã¼ããã¦ãã¦Elasticsearchã®Analyzerãåè¨èªã§ç´°ããè¨å®ãéç¨ãã¦ãããè¾æ¸ãè¨èªãå°åãã¨ã«å¥ãã¦ãã¾ãããã®éç¨ãVoyagerã§ããã®ãé£ãããã§ããã</p>
<p>ã¾ãè¾æ¸ã®æ±ãæ¹ã大ããéãã¾ãããæ¥æ¬ã®æ¤ç´¢ã¯é·å¹´éç¨ããã¦ãè¾æ¸ã¯æçãã¦ãããå¤æ´ãé »ç¹ã«è¡ãããªããããè¾æ¸ã®æ´æ°ã¯æ¥æ¬¡ãããã§é©ç¨ããã¾ããããããGS2ã§ã¯è¾æ¸ã®å¤æ´ãå³æåæ ã§ãããã«æ¤ç´¢çµæãå¤ããã¾ããããã¯è¾æ¸ã®ç®¡çãåå°åã®CM(ã³ãã¥ããã£ããã¼ã¸ã£ã¼)ã«ãé¡ããã¦ãã¦ãå½¼ã彼女ããè¾æ¸ãå¤æ´ããªããæ¥ã
æ¤ç´¢æ¹åãè¡ã£ã¦ããããã§ããã¾ã è¾æ¸ãä¸ããä½ã£ã¦ããæ°èãã¼ã±ãããå¤ãã£ããããè¾æ¸ã®å¤æ´ã«ããæ¤ç´¢çµæã®å¤åãå®éã«è¦ãªããè¾æ¸ã®ä½æããã¦ããããã§ãã
GS2æ¡ç¨ã«ããã£ã¦é度é¢ã§ã®æ¸å¿µã¯ããã¾ããããVoyagerãGS2ããéãçç±ã¯ããã£ã¦ãã¦ãããã¤ãGS2ã§ãå®ç¾ã§ããä»çµã¿ããã£ãããããªãã¨ãã§ãããã ã¨ãããã¨ã§é²ãã¾ããã</p>
<h2 id="æ¤ç´¢ç§»è¡ã®é²ãæ¹">æ¤ç´¢ç§»è¡ã®é²ãæ¹</h2>
<p>æ¤ç´¢ç§»è¡ã®ã´ã¼ã«ã¨ãã¦ã¯ãæ¥æ¬ã®æ¤ç´¢ã¨å®å
¨ã«åãçµæãè¿ãããã«ãããã¨ãç®æãã¾ãããGS2ã®æ¤ç´¢ãã¸ãã¯ã®èãæ¹ã¯æ¥æ¬ã¨åãã ã¨è¨ãã¾ããããå®éã®è¨ç®å¼ã¯éã£ã¦ãã¦ãæ¤ç´¢çµæã«ããããªå·®ç°ãããã¾ããé常ãªãABãã¹ããªã©ã§ãGS2ãã¼ã¹ã®ã¹ã³ã¢ãªã³ã°ã«ç½®ãæãã¦ã大ä¸å¤«ããæ¤è¨¼ããªããåãæ¿ãããã¨ãèãããã¾ãããããOne experience ããã¸ã§ã¯ãã§ã¯æ¤ç´¢ã®ã¿ãªãããã¡ã¤ã³ã®ã¬ã·ããµã¼ãã¹ããã¼ã¿ãã¼ã¹ãåºç¤ãã¾ããã¨ç§»è¡ãã¦ãããããä¸é¨ãã¦ã¼ã¶ã¼ã«åºãã¦æ¤è¨¼ãããã¨ãã§ãã¾ããã§ãããã¾ãæ¥æ¬ã®äººæ°é æ¤ç´¢ã¯ãã¬ãã¢ã ãµã¼ãã¹ã®å¤§äºãªæ©è½ã§ãããããæ
éã«ãªãå¿
è¦ããããã·ã¹ãã 移è¡ä½æ¥ã¨ã¢ã«ã´ãªãºã ã®å¤§ããªæ¹å¤ã®æ¤è¨¼ãåæã«ãããªã½ã¼ã¹ãããã¾ããã§ãããGS2ã§æ¥æ¬å°ç¨ã®æ¤ç´¢ã¯ã¨ãªãã«ãã¼ãä½ããã¨ã¯å®¹æã§ã大ããªæè¡çãªè² åµã«ããªããªãã®ã§ãã¾ãã¯åãçµæãåç¾ãããã¨ã«å¾äºãã¾ããã</p>
<h3 id="RBOææ¨">RBOææ¨</h3>
<p>æ¤ç´¢çµæãåãã«ãªã£ã¦ãããã確ãããããã«ãæ¥æ¬ã¨ã°ãã¼ãã«ã®çµæãæ¯è¼ããå¿
è¦ãããã¾ãããä»åã®ç§»è¡ã§ããµãã¤ã®æ¤ç´¢çµæã®ä¸è´åº¦ã測ãææ¨ã¨ãã¦ãRank Biased Overlap(RBO)ã¨ããææ¨ãç¨ãã¾ãããRBOã¯2ã¤ã®é ä½ä»ããªã¹ãã®é¡ä¼¼æ§ã測å®ããææ¨ã§ãç¹ã«é ä½ã®ä¸ä½ã«éç¹ãç½®ããªããããªã¹ãå
¨ä½ã®æ¯è¼ãè¡ããç¹é·ãããã¾ãã</p>
<p>RBOãè¨ç®ããéã®ä¸ä½100件ã®çµæãæ¸è¡°ãã©ã¡ã¼ã¿=0.978ã«è¨å®ãã¦æ¸¬ãã¾ããããã®ãã©ã¡ã¼ã¿ã¯ä¸ä½60件ã®ä¸è´åº¦ã§RBOææ¨ã®9å²ã®éã¿ã決ã¾ãããã«è¨å®ãã¾ãããããã¯ã¬ã·ãæ¤ç´¢ã«ããã¦ã»ã¨ãã©ã®ã¢ã¯ã»ã¹ãæåã®3ãã¼ã¸ã®é²è¦§ã«åã¾ã£ã¦ããããã§ããå®è£
ã®ããã©ã«ãã§ããæ¸è¡°ãã©ã¡ã¼ã¿=0.9ã§ã¯ä¸ä½ç´10ã¬ã·ãã ãã§RBOã¹ã³ã¢ã決ãããã¦ãã¾ããä¸ä½æ°ä»¶ã®çµæãåã£ã¦ããã©ããã«éããç½®ãããææ¨ã«ãªã£ã¦ãã¾ãããã§ãã</p>
<p>ãã®æ¯è¼ãTop20000ãã¼ã¯ã¼ãã«å¯¾ãã¦ãæ°çé ã¨äººæ°é ã®ä¸¡æ¹ã§æ¯æ¥èªåã§è¨ç®ããããã«ãã¾ãããæ¥ã
ã®RBOã®å¤åã2ä¸ãã¼ã¯ã¼ãã®RBOã®åå¸ã§é²æã測ãããã¼ã å
㧠RBOãä½ãã£ããã¼ã¯ã¼ããåæããªããã移æ¤æ©è½ã®åªå
度ãã¤ããããä»æ§ã®è¦è½ã¨ããçºè¦ããªããé²ãããã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="æåã®RBOè¨æ¸¬"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/giga811/20241210/20241210193133.png" width="640" height="480" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>æåã®RBOè¨æ¸¬</figcaption></figure></p>
<p>RBOå¤ã¨ããã«å¯¾ãããã¼ã¯ã¼ãã®ãã¹ãã°ã©ã ã§ç§»è¡ã®é²æãå¯è¦åãããã¨ãã§ãã¾ãããä¸ã®å³ã¯ä¸çªæåã®RBOåå¸ã®ã°ã©ãã§ãããã®ã¨ãã¯ãã¡ããä½ãã§ãã¦ããªãã®ã§ã»ã¨ãã©ã®ãã¼ã¯ã¼ããRBO=0.0ã¨ããçµæã§ããã
<figure class="figure-image figure-image-fotolife" title="åæã®éçºéç¨ã®å¤å"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/giga811/20241210/20241210195006.png" width="1200" height="309" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>åæã®éçºéç¨ã®å¤å</figcaption></figure>
æåã¯ä¸çªåºç¤çãªæ¤ç´¢ãµãã¼ãããã¦ããã¾ãããElasticsearchã§æ£ããAnalyzerãè¨å®ããããVoyagerã®ãã¼ã¹ã®è¨ç®å¼ãGS2ã«å®è£
ããããæ¤ç´¢æã®ã¯ã¨ãªãæ£ããå½¢æ
ç´ è§£æããããã«ããããåºç¤çãªé¨åãä½ã£ã¦ããã¾ããããã®æç¹ã§RBO=0.0çµæãã»ã¨ãã©ãªããªãã大é¨åãå³å´ã«å¯ã£ã¦ããå½¢ã«ãªãã¾ããã
<figure class="figure-image figure-image-fotolife" title="åæ¥ã®æ°çé ã¨äººæ°é ã®RBOåå¸"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/giga811/20241210/20241210195044.png" width="1200" height="483" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>åæ¥ã®æ°çé ã¨äººæ°é ã®RBOåå¸</figcaption></figure>
ã¾ãããã¯åæ¥ã®æ°çé ã¨äººæ°é ã®åå¸ã§ãã人æ°é ã®ã¿ã®æ©è½ãªã©ããããã¨ããã両æ¹ã¨ãã¢ãã¿ã¼ãã¦ããå¿
è¦ãããã¾ãããããã¸ã§ã¯ãéãã¦äººæ°é ã®ã»ããRBOæ績ã¯è¯ãå¾åã«ããã¾ãããããã¯æ°çã®ã¬ã·ãããã¥ã¡ã³ããæ¥æ¬ã¨ã°ãã¼ãã«ã§åæããºã¬ã¦ãããã人æ°é ã¯äººæ°é ã¹ã³ã¢ã®æ¯éã大ããããä¼¼ããããã¨ã«èµ·å ãã¦ãã¾ãã</p>
<p>åºç¤çãªæ©è½ã®ç§»æ¤ãä¸æ®µè½ããããã¸ã§ã¯ãã®ä¸ç¤ã§ã¯RBOå¤ãä½ãã¯ã¨ãª(0.4以ä¸)ã並ã¹ã¦ãæ¼ãã¦ããæ©è½ã®çºè¦ããæªå®è£
æ©è½ã®åªå
度ä»ããè¡ããªããé²ãã¾ãããé£ããã£ãã®ã¯RBOã0.6-0.8ãããã®ãã¼ã¯ã¼ãã®è§£æã§ãããRBOãé«ããã¼ã¯ã¼ãã¯ãããã§ã«ããç¨åº¦ä¼¼ã¦ããçµæã§ãå·®åãæ¢ãã®ãé£ãããã®ã¨ãªã£ã¦ãã¾ããããã®è¾ºã«ãªã£ã¦ããã¨åç´ã«ã¢ã«ã´ãªãºã ã®éãã ãã§ã¯ãªããããã¥ã¡ã³ãä½ææã®å½¢æ
ç´ è§£æã®å¾®å¦ãªå·®ç°ãªã©ãå½±é¿ãã¦ãããããå°éã«ç´°ãã調æ»ããªããé²ãã¾ããã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/giga811/20241210/20241210195147.gif" width="640" height="480" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span>
RBOåå¸ã®æ¨ç§»ãå¯è¦åãã¦ã¿ã¾ããã移è¡ãé²ããã¨ã«åå¸ãå³å´ã«å¯ã£ã¦ããæ§åã伺ãã¾ããRBOã¯æçµçã«æ°çé 㧠RBO>=0.8 ã®å²åã 97.2%ã人æ°é 㧠RBO >=0.8: 95.1% ã¨ããçµæã«ãªãã¾ãããããã¤ãã®æ©è½ã¯éãå½¢ã§ãã£ã¦ããããVoyagerããæã£ã¦ããããªãä»æ§ãªã©ãããã¾ããããæçµçã«ã¯ã¨ã¦ãé«ãä¸è´çã«ãªãã¾ãããã¦ã¼ã¶ã¼ãªãªã¼ã¹å¾ãKPIã大ããå¤åãããã¨ãªã移è¡ã§ãã¾ããã</p>
<h2 id="é度">é度</h2>
<p>æ¤ç´¢ç§»è¡ã«ããã¦æ¥æ¬ã®ã·ã¹ãã ã¨åçãªæ¤ç´¢çµæãåºããã¨ã¨åæã«ãåçãªé度ãç¨æãããã¨ã«æ³¨åãã¾ãããåè¿°ããããã«GS2ã¯Voyagerã®4åã»ã©é
ãã¬ã¹ãã³ã¹ã¿ã¤ã ã§ããã</p>
<p>VoyagerãGS2ã¨æ¯ã¹éãçç±ã¯ããã¤ãããã£ã¦ãã¾ãããä¸ã¤ã¯ã¯ã¨ãªæ¡å¼µã®æ¹å¼ã§ããè¾æ¸ã使ã£ãã¯ã¨ãªå±éã¯ä¸»ã«å義èªå±éã¨ãèªã®è¦ªåé¢ä¿ã«ããæ¡å¼µãããã¾ããæ¥æ¬ã§ã¯ synonym token filter ã使ç¨ãã¦å義èªå±éãè¡ãã親åé¢ä¿ã®åèªã¯ã¤ã³ããã¯ã¹æã«äºåè¨ç®ãã¦å
¥ãã¦ãã¾ãããã®ããæ¤ç´¢æã¯å
ãã¼ã¯ã¼ãã®ã¿ã®æ¤ç´¢ã§å義èªå±éã¨è¦ªåããããå¯è½ã«ãªã£ã¦ãã¾ãããã®å¤ããã«è¾æ¸ã®å¤æ´ãåæ ãããã«ã¯ãæ¥æ¬¡ããããå¾
ã¤ãããªãã§ãã対ãã¦ãGS2ã§ã¯è¾æ¸åæ ããªã¢ã«ã¿ã¤ã ã§å®ç¾ããããããã¯ã¨ãªæ¡å¼µãElasticsearchã¯ã¨ãªãã«ãæã«è¡ã£ã¦ãããããESã¯ã¨ãªãè¥å¤§åããæ¤ç´¢ãéãå¦çã«ãªãã¾ãã</p>
<p>ããä¸ã¤ã®éãçç±ã¯Solrãç¨ããæ¤ç´¢åºç¤ãæé©åããã¦ãããã¨ã§ããVoyagerã§ã¯æ§ã
ãªå·¥å¤«ãåããã¦ã¤ã³ããã¯ã¹ãµã¤ãºã極éã¾ã§æ¸ããã¦ãã¾ããã¾ã<a href="https://techlife.cookpad.com/entry/2020/11/25/080100">Solr-hakoãç¨ããã·ã¹ãã </a>ãã¨ã¦ãé«ã³ã¹ãã§é«ããã©ã¼ãã³ã¹ã§ãããGS2ã®Elasticsearchæ§æã¨æ¯è¼ãã¦ã¯ã©ã¹ã¿ãçµãã§ããªãããSolrãã¼ãã1ã¤ã³ããã¯ã¹ããæããªããããã¯ã¨ãªãã£ãã·ã¥ãå¹ççã«ä½¿ãã¾ãã</p>
<p>GS2移è¡ã«ããã¦ãå®éã®æ¥æ¬ã®ãã©ãã£ãã¯ã«è¿ãè² è·è©¦é¨ã·ããªãªãç¨æãã¦ããã©ã¼ãã³ã¹ãè¨æ¸¬ãã¦ããã¾ãããåè¿°ããSynonym token filterã親åé¢ä¿ã®äºåè¨ç®ãGS2ã«ã移è¡ãã¦æã£ã¦ããã®ã§ãGS2ã®ä»ã®è¨èªã¨æ¯è¼ãã¦ãã§ã«ããç¨åº¦éããªã£ã¦ã¾ããããã®ä¸ã§ãAPIå´ãElasticsearchã®æ§æãã¤ã³ããã¯ã¹è¨å®ãªã©ãããã模索ããªããããã©ã¼ãã³ã¹æ¹åãã¦ããã¾ãããGS2ã«ã¯Prometheus, Grafana ãç¨ããAPMæ©è½ãåãã£ã¦ããã®ã§ãæ¤ç´¢ã®ä¸é£ã®æµããã¯ã¨ãªè§£æãElasticsearchã¸ã®ãªã¯ã¨ã¹ããªã©ã®ã©ããããã«ããã¯ã«ãªã£ã¦ããã®ãæ確ã§ã注åããé åãæ確ã«ã§ãã¦ãã¾ããã</p>
<p>ã¾ãã¦ã¼ã¶ã¼ããè¦ãã<a href="https://techlife.cookpad.com/entry/2024/10/23/110000">Webãã¢ããªã®æ¤ç´¢ãã¼ã¸ã®ããã©ã¼ãã³ã¹ã測å®</a>ããªãããå¥ã®ãã¼ã ã®æ¹ã
ããæ¤ç´¢åºç¤ããåã«ããã·ã¹ãã ã®æé©åã«ãç©æ¥µçã«åãçµãã§ãã¾ããã</p>
<p>æçµçã«ã¯GS2ã¯p95ã§ã¯Voyagerããé«éãªããã©ã¼ãã³ã¹ã«ãªãã¾ãããp50ã®ãã¼ã¹ã®ã¬ã¹ãã³ã¹ã¿ã¤ã ã¯ä¸ããã¾ããããp95ã¬ãã«ã§ã¯æ¥æ¬ããå®å®ããã¬ã¹ãã³ã¹ã¿ã¤ã ãå®ç¾ã§ãã¾ããã</p>
<ul>
<li>Voyager -> GS2</li>
<li>p50: ~22ms -> ~33ms</li>
<li>p95: ~80ms -> ~50ms</li>
</ul>
<h2 id="ãããã«">ãããã«</h2>
<p>One experienceã¯UIãä½é¨ã大ããå¤ãããã¦ã¼ã¶ã¼ã«å¤§ããªè² æ
ãå¼·ããææ¦ã§ãããã¦ã¼ã¶ã¼ã®æ¹ã«ã¯ãæ¤ç´¢çµæã¨ãã®å¿çæ§ã¯åçãªãã®ãæä¾ããããã«æ¤ç´¢ãã¼ã ã¨ãã¦æ³¨åãã¦æ¥ã¾ãããçµæãã»ã¨ãã©ã®ãã¼ã¯ã¼ãã§ã¯åçãªæ¤ç´¢çµæãå®å
¨ã«GS2ã®ã·ã¹ãã ã§ç¨æãããã¨ãã§ããã¬ã¹ãã³ã¹ã¿ã¤ã ãæãªããã¨ãªãæ¤ç´¢ç§»è¡ãå®ç¾ãããã¨ãã§ãã¾ããã</p>
<p>æ¤ç´¢ç§»è¡ãçµãã£ããã¾ãçã®Globalãªæ¤ç´¢éçºãå§ã¾ãã¾ãããä¸ã¤ã®ãã¼ã ã§ç´30è¨èªã®æ¤ç´¢æ¹åã«åãçµãã§ããã¾ããã¾ãæ¥æ¬ã¨Globalã®æ¤ç´¢ã¢ã«ã´ãªãºã ããªãã¹ãæãã¦ãããã¨ãã¦ãã¾ããå®éã®ã¦ã¼ã¶ã¼ãã©ãã£ãã¯ããããªããæ¹ãã¦GS2ã«åãã£ã¦ããABãã¹ãåºç¤ãæ´»ç¨ããªãããGlobalãæ¥æ¬ä¸¡æ¹ã®ã¢ã«ã´ãªãºã ã®ããã¨ãåããããåãã¢ã«ã´ãªãºã ã«æãã¦ããããã¨æã£ã¦ãã¾ãããã¡ããè¨èªãã¨ã®å·®ç°ã¯æ®ãã¨æã£ã¦ãã¦ãå®å
¨ã«ä¸ç·ã«ããã¨ã¯æã£ã¦ãã¾ãããããäºãããå¦ã¹ãã¨ããã¯å¤ãã¯ãã§ãã</p>
<p>ã¬ã·ãæ¤ç´¢ã®è©³ç´°ãªè©±ãæ¤ç´¢ä»¥å¤ã®æ©è½ãªã©ãããã§æ¸ããããªã移è¡ã®è©±ã¯ããããããã®ã§ãããã¾ããããã©ããã®æ©ä¼ã«è©±ãããã¨æãã¾ããã¾ããOne experienceããã¸ã§ã¯ãã«ã¤ãã¦å¼ç¤¾ã®Techlifeããã°ã§ããã¾ã§ã«ããããããããããªè¨äºãæ稿ããã¦ããã¾ãã<a href="https://twitter.com/cookpad_tech">Techlifeã®ãã¤ãã¿ã¼</a>ã§éææ´æ°ãã¦ãã¾ãããã²ãã§ãã¯ãã¦ãã ããã</p>
giga811
Gradle Composite Build ãç¨ãããã«ããã¸ãã¯ã®å
±éåã«ã¤ãã¦
hatenablog://entry/6802418398304973388
2024-11-20T13:00:00+09:00
2024-11-20T13:00:00+09:00 ã¯ããã« ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ã§é·æã¤ã³ã¿ã¼ã³ä¸ã®æ¾æ¬ (@matsumo0922) ã§ããå
æ¥ãã®ããã°ã§ãå
¬éããéããã¯ãã¯ãããã§ã¯æ¥æ¬ã¨ã°ãã¼ãã«ã§ä½é¨ãçµ±ä¸ãã One Experience ã¨ããããã¸ã§ã¯ããè¡ã£ã¦ãã¾ãã One Experience 以åã§ã¯ Android éçºã«ããã¦ãæ¥æ¬ã¨ã°ãã¼ãã«ã§ã³ã¼ããã¼ã¹ãç°ãªãããããã使ç¨ãã¦ããæè¡ãã©ã¤ãã©ãªãç°ãªãç¶æ
ã§ãããç¹ã«ã°ãã¼ãã«ã®ã³ã¼ããã¼ã¹ (ä»¥ä¸ global-android ã¨å¼ã³ã¾ã) ã§ã¯ AGP ã®ãã¼ã¸ã§ã³ãä½ããå ã㦠groovy + buildSrc ã¨è¨ã£ãæ§ä¸ä»£ã®ãã«ããã¸ãã¯â¦
<h2 id="ã¯ããã«"><strong>ã¯ããã«</strong></h2>
<p>ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ã§é·æã¤ã³ã¿ã¼ã³ä¸ã®æ¾æ¬ (<a href="https://x.com/matsumo0922">@matsumo0922</a>) ã§ããå
æ¥ãã®ããã°ã§ãå
¬éããéããã¯ãã¯ãããã§ã¯æ¥æ¬ã¨ã°ãã¼ãã«ã§ä½é¨ãçµ±ä¸ãã One Experience ã¨ããããã¸ã§ã¯ããè¡ã£ã¦ãã¾ãã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2024%2F10%2F10%2F105832" title="æ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ãã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>One Experience 以åã§ã¯ Android éçºã«ããã¦ãæ¥æ¬ã¨ã°ãã¼ãã«ã§ã³ã¼ããã¼ã¹ãç°ãªãããããã使ç¨ãã¦ããæè¡ãã©ã¤ãã©ãªãç°ãªãç¶æ
ã§ãããç¹ã«ã°ãã¼ãã«ã®ã³ã¼ããã¼ã¹ (ä»¥ä¸ <code>global-android</code> ã¨å¼ã³ã¾ã) ã§ã¯ AGP ã®ãã¼ã¸ã§ã³ãä½ããå ã㦠groovy + buildSrc ã¨è¨ã£ãæ§ä¸ä»£ã®ãã«ããã¸ãã¯ãç¨ãã¦ãããããããã¸ã§ã¯ãã®é²è¡ã«æ¯éãããç¶æ
ã§ãããæ¬è¨äºã§ã¯ããããã®åé¡ãè¸ã¾ã One Experience ãããåæ»ã«é²ããããã«æ½ãããã«ããã¸ãã¯ã®æ¹åã«ã¤ãã¦ã話ããã¾ãã</p>
<h2 id="TLDR"><strong>TL;DR</strong></h2>
<ul>
<li>global-android ã§ã¯æ§ä¸ä»£ã®ãã«ããã¸ãã¯ã使ã£ã¦ãããã One Experience ç¨ã®æ©è½éçºã«æ¯éãããç¶æ
ã ã£ã</li>
<li>ã©ã¤ãã©ãªç®¡çã VersionCatalog ã¸ãã¹ã¯ãªããã Kotlin DSL ã¸ç§»è¡ããã¨å
±ã«ãGradle Composite Build + Convention Plugins ãç¨ãã¦ãã«ããã¸ãã¯ã®å
±éåãè¡ã£ã</li>
<li>æçµçã«ãåã¢ã¸ã¥ã¼ã«ã® <code>build.gradle.kts</code> ã¯é常ã«ç°¡æ½ã«ãªãã大æµã®ãã«ããã¸ãã¯ãå
±éåãããéçºãããå¹ççã«é²ããããããã«ãªã£ã</li>
</ul>
<h2 id="åé¡ç¹"><strong>åé¡ç¹</strong></h2>
<p>global-android ã® Gradle ãã«ãã«ã¯ä»¥ä¸ã®ãããªåé¡ç¹ãããã¾ããã</p>
<ul>
<li>dependencies.gradle ãç¨ããæåã®ã©ã¤ãã©ãª & ãã¼ã¸ã§ã³ç®¡ç</li>
<li>groovy ã§æ¸ããããã«ããã¸ãã¯</li>
<li>大éã«ã¢ã¸ã¥ã¼ã«ãããã«ãé¢ããããä¸é¨ãã¸ãã¯ãå
±éåããã¦ããªã</li>
</ul>
<p><figure class="figure-image figure-image-fotolife" title="global-android ã§ã®ã©ã¤ãã©ãªç®¡ç"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsumo0922/20241118/20241118160440.png" width="1200" height="658" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>global-android ã§ã®ã©ã¤ãã©ãªç®¡ç</figcaption></figure></p>
<p>ã¾ããã©ã¤ãã©ãªã®ãã¼ã¸ã§ã³ç®¡çã«ã¤ãã¦ãä¸ã®ç»åã®ããã«ãglobal-android ã§ã¯ã©ã¤ãã©ãªã®å®ç¾©ï¼ãã¼ã¸ã§ã³ãå«ãï¼ã gradle ã® <code>ExtraPropertyExtension</code> ãç¨ãã¦è¨è¿°ããåã¢ã¸ã¥ã¼ã«ã«é
å¸ãã¦ãã¾ããããã®ææ³ã®è©³ç´°ã«ã¤ãã¦ã¯å²æãã¾ããããæ³åã®éãã©ã¤ãã©ãªç®¡ç & ãã¼ã¸ã§ã³ç®¡çãè¤éã«ãªããã㤠IDE ã®æå©ãã renovate ã dependabot ã¨è¨ã£ããµã¼ãã¹ã使ããã¨ãã§ããªããããéçºè
ã®ä½é¨ãæªããã¦ãã¾ããã</p>
<p>次ã«ãã«ããã¸ãã¯ã«ã¤ãã¦ãglobal-android ã«ã¯ buildSrc ãå°å
¥ããã¦ãã¾ããããè¨è¿°ããã¦ããã®ã¯ CI/CD ã§ç¨ããããå
±éã¿ã¹ã¯ã®å®ç¾©ã®ã¿ã§ãããå®éã®ãã«ããã¸ãã¯ã¯åã¢ã¸ã¥ã¼ã«ã® <code>.gradle</code> ãã¡ã¤ã«ã«åæ£ãã¦ãã¾ãããã¨ããã®ããbuildSrc ã¯ãã«ããã¸ãã¯ãè¨è¿°ããã®ã«æé©ãªå ´æã§ã¯ãããã®ã®ããã¹ã¦ã® gradle build ã®ããããã¹ã§ããããããããããã«ãã§ã³ã¼ããã³ã³ãã¤ã« & ãã§ãã¯ãè¡ã£ã¦ãã¾ãããã§ãããã¡ããåå以å¤ã¯ãã£ãã·ã¥ãç¨ãããã¾ãããglobal-android ã®ãããªå·¨å¤§ããã¸ã§ã¯ãã§ã¯ç¡è¦ã§ããªãã³ã¹ããçºçãã¾ããå ãã¦ãbuildSrc ã¸ã®å¤æ´ã¯ããã¸ã§ã¯ãå
¨ä½ã® classpath å¤æ´ãã¤ã¾ããã£ãã·ã¥ãç¡å¹ã«ãªãã¨ããæå³ãæã¤ãããbuildSrc ã¸ãã«ããã¸ãã¯ã追å ããã®ã¯æ
éã«ãªããããå¾ã¾ããã</p>
<pre class="code" data-lang="" data-unlink>$ find . -type f -name '*.gradle' -exec wc -l {} + | tail -n1 # ããã¸ã§ã¯ãå
¨ä½ã® Groovy ãã¡ã¤ã«ã®è¡æ°
3736 total</pre>
<p>ããã§ãã©ã¤ãã©ãªã®ç®¡çã VersionCatalog ã«ç§»è¡ãã¤ã¤ã gradle ã® <code>Conposite Build</code> 㨠<code>Convention Plugins</code> ã¨è¨ã£ãææ³ãç¨ãããã«ããã¸ãã¯ã®ã¿ãããã¸ã§ã¯ãããåé¢ãããã¨ã§ã³ã¹ãã®åæ¸ããã«ããã¸ãã¯ã®å
±éåãè¨ããã¨ã«ãã¾ããã</p>
<h2 id="Composite-Build-ã¨ã¯"><strong>Composite Build ã¨ã¯</strong></h2>
<p>ä¸è¨ã§è¨ãã°ããããã¸ã§ã¯ãããåãé¢ããããã«ããã®ãã¨ã§ããComposite Build<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup> å
ã®å build 㯠<code>include build</code> ã¨å¼ã°ããinclude build å士ã¯ãã¸ãã¯ãå
±æãããåå¥ã«æ§æ & å®è¡ããã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="Convention Plugins ãç¨ããããã¸ã§ã¯ãã®ä¾"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsumo0922/20241118/20241118160605.png" width="1200" height="676" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Convention Plugins ãç¨ããããã¸ã§ã¯ãã®ä¾<sup id="fnref:2"><a href="#fn:2" rel="footnote">2</a></sup></figcaption></figure></p>
<p>ä¸ã®ä¾ã§ã¯ <code>my-app</code> 㨠<code>my-utils</code> ã Composite Build ãç¨ãã¦ä¸ã¤ã®ããã¸ã§ã¯ãã«ã¾ã¨ãã¦ãã¾ãã<code>my-app</code> 㯠<code>my-utils</code> ã«ä¾åãæã¤ãã¨ãã§ãã¾ããããã®å ´åã®ä¾åã¯ãç´æ¥çãªä¾åãã«ã¯ãªãã¾ããããããã®ã¢ã¸ã¥ã¼ã«ã¯ include build ã§ãããããç´æ¥çã« <code>my-utils</code> ãåç
§ãããã¨ã¯ã§ãããbuild ãéãã¦çæãããå®è¡å¯è½ãã¡ã¤ã«ï¼ãã¤ããªï¼ãåç
§ãããã¨ã«ãªãã¾ãããã®ãããbuildSrc ã®ãããªå®è¡é度ããã£ãã·ã¥ã®åé¡ãçºçãããã«ããããããã¸ãã¯ãè¨è¿°ãããã¨ãå¯è½ã«ãªãã¾ãã</p>
<h2 id="Convention-Plugins-ã¨ã¯"><strong>Convention Plugins ã¨ã¯</strong></h2>
<p>Convention Plugins<sup id="fnref:3"><a href="#fn:3" rel="footnote">3</a></sup> ã¨ã¯ãã«ããã¸ãã¯ã Gradle Plugin System ãç¨ãã¦å
±éåããPlugin ã¨ãã¦é
å¸ããææ³ã®ãã¨ãæãã¾ããPlugin ã®ä½ææ¹æ³ã«ã¤ãã¦ã¯ <code>Standalone Gradle Plugin</code> 㨠<code>Precompiled Script Plugin</code> ãåå¨ãã¾ããããããã® Plugin ã®ä½ææ¹æ³ã«ã¤ãã¦ã¯å²æãã¾ãããä»å㯠Composite Build ã使ç¨ããé½åä¸ãç´æ¥ã¹ã¯ãªãããã¡ã¤ã«ãåç
§ã§ããªãããã<code>Standalone Gradle Plugin</code> ãç¨ãããã¨ã«ãã¾ããã</p>
<p>ã¾ã¨ããã¨ã<code>Composite Build</code> ãç¨ãã¦ã¢ã¸ã¥ã¼ã«ãä½æãï¼<code>build-logic</code> ã¢ã¸ã¥ã¼ã«ã¨ãã¾ãï¼ãä¸ã§ <code>Convention Plugins</code> ãç¨ãã¦ãã«ããã¸ãã¯ãå
±éåãããã¨è¨ã£ãææ³ãåããã¨ã«ãã¾ããã</p>
<h2 id="å®è£
"><strong>å®è£
</strong></h2>
<h3 id="æ§æ"><strong>æ§æ</strong></h3>
<p>æåã«ãæçµçãªãã£ã¬ã¯ããªæ§æã示ãã¾ãã</p>
<pre class="code" data-lang="" data-unlink>global-android/
âââ build-logic/
â âââ src/
â â âââ main/
â â âââ java/
â â âââ convention/
â â â âââ FeaturePlugin.kt
â â âââ primitive/
â â âââ ApplicationPlugin.kt
â â âââ ComposePlugin.kt
â â âââ CommonPlugin.kt
â â âââ FlavorPlugin.kt
â âââ build.gradle.kts
â âââ settings.gradle.kts
âââ cookpad/
âââ ...</pre>
<p>大ã¾ãã«ã¯ä¸è¬çãªã¢ã¸ã¥ã¼ã«ã®æ§æã¨å¤ããããã¾ããããã ä¸ã¤æ³¨æãå¿
è¦ãªç¹ã¯ã<code>build-logic</code> 㯠<code>cookpad</code> ã¢ã¸ã¥ã¼ã«ã« include build ããããããGradle çã«ã¯ä¸ã¤ã®ããã¸ã§ã¯ãã¨ãã¦æ±ãããã¨ãããã¨ã§ãããã®ãããç´ä¸ã« <code>settings.gradle.kts</code> ãé
ç½®ãã¦ä¾åã®è§£æ±ºæ¹æ³ã Library Repository ã宣è¨ããå¿
è¦ãããã¾ããä»å㯠<code>build-logic</code> å
㧠VersionCatalog ãç¨ãããããVersionCatalog ã®è¨è¿°ãå¿
è¦ã§ãã</p>
<p><code>src/</code> ãã£ã¬ã¯ããªä»¥ä¸ã« Plugin ãé
ç½®ãã¾ããã¯ãã¯ãããã§ã¯ãåä¸ã®æ©è½ãæ§æãæä¾ãã Plugin ã Primitive PluginãPrimitive Plugin ãã¾ã¨ãä¸è¬åãã Plugin ã Convention Plugin ã¨å¼ã¶ãã¨ã«ããããããã® Plugin ããã£ã¬ã¯ããªãåãã¦é
ç½®ãã¦ãã¾ãã</p>
<h3 id="build-logic-ã¢ã¸ã¥ã¼ã«ã®ä½æ"><strong>build-logic ã¢ã¸ã¥ã¼ã«ã®ä½æ</strong></h3>
<p>ã«ã¼ãç´ä¸ã« <code>build-logic</code> ã¢ã¸ã¥ã¼ã«ãä½æãã¾ããã¢ã¸ã¥ã¼ã«ã®ä½ææ¹æ³ã¯åãã¾ããããAndroidStudioã®ã³ã³ããã¹ãã¡ãã¥ã¼ããä½æãã¦ãã¾ãã¨ãä¸è¦ãª proguard ãã¡ã¤ã«ãªã©ãçæããããã<code>settings.gradle.kts</code> ã«ã¢ã¸ã¥ã¼ã«ã¨ãã¦è¿½å ããããªã©ã®ãç¯ä»ãçºçããããããã¾ãããããã§ãã¾ãããåè¿°ã®éãã<code>build-logic</code> 㯠Gradle çã«ã¯ä¸ã¤ã®ããã¸ã§ã¯ãã§ãããã <code>settings.gradle.kts</code> ãé
ç½®ãã¦ãã ããã</p>
<pre class="code" data-lang="" data-unlink>dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
// ã¢ããªããã¸ã§ã¯ãå´ã® VesionCatalog ãåç
§ãã
from(files("../gradle/libs.versions.toml"))
}
}
}
rootProject.name = "build-logic"</pre>
<p>å
容ã¯é常ã®ããã¸ã§ã¯ãã® <code>settings.gradle.kts</code> ã¨åæ§ã§ããContent Filtering ãªã©ã®è¨è¿°ãããã«è¡ãã¾ããä¸ã¤ä¾å¤çãªãã¨ã¯ VersionCatalog ãæ示çã«è¨è¿°ãã¦ãããã¨ã§ããæ¬æ¥ Gradle ã¯<code>./gradle/libs.versions.toml</code> ã«ãã¡ã¤ã«ãé
ç½®ããã¦ããã¨èªåçã« <code>libs</code> ã¨ããååã§æ¡å¼µããããã£ãçæãã¾ããã<code>build-logic</code> ããè¦ãã° toml ãã¡ã¤ã«ã¯ <code>../gradle/libs.versions.toml</code> ã«é
ç½®ããã¦ãããããæ示çã«è¨è¿°ããå¿
è¦ãããã¾ãããã¡ããã<code>build-logic</code> åºæã® toml ãã¡ã¤ã«ãå¥éç¨æããå ´åã¯ãã®è¨è¿°ã¯å¿
è¦ããã¾ããã</p>
<p><code>build.gradle.kts</code> ã«ã¤ãã¦ããé常ã®ããã¸ã§ã¯ãã¨ãªããå¤ããã¾ããã</p>
<pre class="code" data-lang="" data-unlink>plugins {
`kotlin-dsl`
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
dependencies {
implementation(libs.android.gradlePlugin)
implementation(libs.kotlin.gradlePlugin)
}
</pre>
<h3 id="æ¡å¼µé¢æ°ã®ä½æ"><strong>æ¡å¼µé¢æ°ã®ä½æ</strong></h3>
<p><code>build-logic</code> å
ã§ã¯ VersionCatalog ã®æ¡å¼µããããã£ãç¨æããã¦ããªãã£ããã<code>implementation()</code> ã <code>api()</code> ã¨è¨ã£ã DSL ãç¨æããã¦ããªããããèªåã§å®è£
ããå¿
è¦ãããã¾ãã</p>
<pre class="code" data-lang="" data-unlink>// VersionCatalog ãåå¾ããããã®æ¡å¼µããããã£
internal val Project.libs: VersionCatalog
get() = extensions.getByType<VersionCatalogsExtension>().named("libs")
internal fun VersionCatalog.version(name: String): String {
return findVersion(name).get().requiredVersion
}
internal fun VersionCatalog.library(name: String): MinimalExternalModuleDependency {
return findLibrary(name).get().get()
}
internal fun VersionCatalog.plugin(name: String): PluginDependency {
return findPlugin(name).get().get()
}
internal fun VersionCatalog.bundle(name: String): Provider<ExternalModuleDependencyBundle> {
return findBundle(name).get()
}</pre>
<pre class="code" data-lang="" data-unlink>// é常ã®ã©ã¤ãã©ãªã implementation ããããã®æ¡å¼µé¢æ°
internal fun DependencyHandlerScope.implementation(artifact: Project) {
add("implementation", artifact)
}
// bundle ãªã©ã implementation ããããã®æ¡å¼µé¢æ°
internal fun DependencyHandlerScope.implementation(artifact: MinimalExternalModuleDependency) {
add("implementation", artifact)
}
// é常ã®ã©ã¤ãã©ãªã api ããããã®æ¡å¼µé¢æ°
...</pre>
<p>å ãã¦ãPlugin å
㧠Extension ã便å©ã«å©ç¨ããããã®æ¡å¼µé¢æ°ãç¨æãã¦ããã¾ããGradle 7.1 ãã <code>BaseAppModuleExtension</code> 㨠<code>LibraryExtension</code> ã®åºåºã¯ã©ã¹ã <code>CommonExtension</code> ã«å¤æ´ããã¦ãããããå®è£
ãå
±éåã§ããããã«ãªã£ã¦ãã¾ãã</p>
<pre class="code" data-lang="" data-unlink>// é常㮠build.gradle.kts ã® android ãããã¯ã«ç¸å½
internal fun Project.androidExt(configure: BaseExtension.() -> Unit) {
(this as ExtensionAware).extensions.configure("android", configure)
}
// Android Project ã® build.gradle.kts ã®ã¹ã³ã¼ã
internal fun Project.commonExt(configure: CommonExtension<*, *, *, *, *, *>.() -> Unit) {
val plugin = if (isApplicationProject()) BaseAppModuleExtension::class.java else LibraryExtension::class.java
(this as ExtensionAware).extensions.configure(plugin, configure)
}
// ãã® Project ã Application Project ã§ãããå¤å®
internal fun Project.isApplicationProject(): Boolean {
return project.extensions.findByType(BaseAppModuleExtension::class.java) != null
}
// ãã® Project ã Library Project ã§ãããå¤å®
internal fun Project.isLibraryProject(): Boolean {
return project.extensions.findByType(LibraryExtension::class.java) != null
}</pre>
<h3 id="Plugin-ã®å®è£
"><strong>Plugin ã®å®è£
</strong></h3>
<p>global-android ã§ã¯æçµçã«ä»¥ä¸ã®ãã㪠Plugin æ§æã«ãªã£ã¦ãã¾ãã</p>
<ul>
<li>Convention Plugin
<ul>
<li><code>cookpad.convention.android.feature</code></li>
</ul>
</li>
<li>Primitive Plugin
<ul>
<li><code>cookpad.primitive.android.application</code></li>
<li><code>cookpad.primitive.android.library</code></li>
<li><code>cookpad.primitive.android.compose</code></li>
<li><code>cookpad.primitive.android.flavor</code></li>
<li><code>cookpad.primitive.android.lint</code></li>
<li><code>cookpad.primitive.detekt</code></li>
<li><code>cookpad.primitive.common</code></li>
</ul>
</li>
</ul>
<p>Primitive Plugin ã§ã¯åä¸ã®æ©è½ãæ§æãæä¾ããä»ã® Primitive Plugin ã«ä¾åãããã¨ããªãããã«ããå¿
è¦ãããã¾ããConvention Plugin 㯠Primitive Plugin ãåç
§ãããã¨ã¯ã§ãã¾ããããã®éã¯ã§ãã¾ãããã¾ããConvention Plugin å士ã®åç
§ããªãããã«æ³¨æãã¦ãã ããã</p>
<p>global-android ã§ã® Plugin å®è£
ä¾ãããã¤ãæãã¦ããã¾ãã</p>
<h4 id="FeaturePlugin"><strong>FeaturePlugin</strong></h4>
<p><code>cookpad.convention.android.feature</code> 㯠global-android ã«å¤§éã«åå¨ãã Feature ã¢ã¸ã¥ã¼ã«ãåç
§ãã Convention Plugin ã¨ãã¦å®è£
ããã¦ãã¾ããPrimitive Plugin ãçºããã ãã§ãªããã¢ã¸ã¥ã¼ã«ã®ä¾åé¢ä¿ãã©ã¤ãã©ãªã®ä¾åãããã§å®ç¾©ãããã¨ã«ãããFeature ã¢ã¸ã¥ã¼ã«ã¨ãã¦ã®å®è£
ãå¼·å¶ãããã¨ãå¯è½ã«ãªã£ã¦ãã¾ãã</p>
<pre class="code" data-lang="" data-unlink>package convention
class FeaturePlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
ããããããããã // å¿
è¦ãª Primitive Plugin ãè¨è¿°
apply("cookpad.primitive.android.library")
apply("cookpad.primitive.android.compose")
apply("cookpad.primitive.android.flavor")
apply("cookpad.primitive.android.lint")
apply("cookpad.primitive.common")
apply("cookpad.primitive.detekt")
}
dependencies {
ããããããããã // Feature ã¢ã¸ã¥ã¼ã«ãä¾åãã¹ãã¢ã¸ã¥ã¼ã«ãè¨è¿°
implementation(project(":core"))
implementation(project(":entity"))
implementation(project(":usecase"))
implementation(project(":repository"))
implementation(project(":view-components"))
// å
±éã®ã©ã¤ãã©ãªãªã©ãè¨è¿°
implementation(libs.bundle("kotlin"))
implementation(libs.bundle("koin"))
implementation(libs.library("androidx-appcompat"))
implementation(libs.library("google-material"))
testImplementation(libs.bundle("test"))
}
}
}
}</pre>
<h4 id="LibraryPlugin"><strong>LibraryPlugin</strong></h4>
<p><code>cookpad.primitive.android.library</code> 㯠Library ã¢ã¸ã¥ã¼ã«ãå©ç¨ãã Plugin ã§ããConvention Plugin ãããåç
§ããã¦ãã¾ããããã® Plugin ã§ã¯ <code>targetSdkVersion</code> ã <code>compileSdkVersion</code>ããã®ä»æ§ã
ãªãã«ããªãã·ã§ã³ãªã©ãè¨è¿°ãã¦ãã¾ãã<code>CommonExtension</code> ãå©ç¨ãã¦è¨å®ã§ããé
ç®ã¯ <code>configureAndroid</code> ã¨ããé¢æ°ã«åãåºãã<code>ApplicationPlugin</code> ã¨å
±éåãã¦ãã¾ãã</p>
<pre class="code" data-lang="" data-unlink>package primitive
class LibraryPlugin: Plugin<Project> {
override fun apply(target: Project) {
with(target) {
// LibraryPlugin 㯠"com.android.library" ã®ã¿ã apply
// ãã®ä»ã®æ©è½ãå¿
è¦ãªå ´åã¯å¥ã® Plugin ãä½æãã
pluginManager.apply("com.android.library")
extensions.configure<LibraryExtension> {
// Android Project ã«å¿
è¦ãªè¨å®
configureAndroid(this)
defaultConfig.targetSdk = libs.version("targetSdk").toInt()
buildFeatures.viewBinding = true
buildFeatures.buildConfig = true
}
}
}
}
internal fun Project.configureAndroid(commonExtension: CommonExtension<*, *, *, *, *, *>) {
commonExtension.apply {
defaultConfig {
ããããããã// global-android ã§ã¯ minSdkVersion ã compileSdkVersion ãªã©ã VersionCatalog ã«è¨è¿°ãã¦ãã
minSdk = libs.version("minSdk").toInt()
compileSdk = libs.version("compileSdk").toInt()
}
testOptions {
animationsDisabled = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
encoding = "UTF-8"
}
packaging {
resources.excludes.addAll(
listOf(...)
)
}
}
}</pre>
<h4 id="FlavorPlugin"><strong>FlavorPlugin</strong></h4>
<p><code>cookpad.primitive.android.flavor</code> 㯠Build Flavor ãè¨å®ãã Plugin ã§ããå
æ¥ãã®ããã°ã«ãæ稿<sup id="fnref:4"><a href="#fn:4" rel="footnote">4</a></sup>ãããéãã global-android ã§ã¯æ¥æ¬ã®ã³ã¼ããã¼ã¹ãçµ±åããéã« applicationId ãªã©ã®é½åä¸ã Build Flavor ãç¨ãã¦æ¥æ¬åããã«ããã°ãã¼ãã«åããã«ãã«åãæ¿ããæ¹å¼ãæ¡ç¨ãã¦ãã¾ãããããã°ç¨ãGlobal Productionç¨ãJP Production ç¨ãªã©ã¨è¤æ°ã®ãã«ãè¨å®ãåã¢ã¸ã¥ã¼ã«ã«è¨è¼ããã®ã¯æ¥µãã¦éå¹çã§ãããããPlugin ã«çºããããã«ãã¦ãã¾ãã</p>
<pre class="code" data-lang="" data-unlink>package primitive
class FlavorPlugin: Plugin<Project> {
override fun apply(target: Project) {
with(target) {
ãã // Build Flavor ãè¨å®
configureFlavors()
configureVariantFilter()
}
}
}
private fun Project.configureFlavors() {
androidExt {
// ã°ãã¼ãã«ã¨æ¥æ¬ã§ãã¼ã¸ã§ãã³ã°ãç°ãªããããããããã®ãã¼ã¸ã§ã³ãåå¾
val globalVersion = GlobalVersion.build(project)
val jpVersion = JpVersion.build(project)
flavorDimensions(Dimension.REGION, Dimension.DEPLOYMENT_TRACK)
productFlavors {
// Global ãªã¼ã¸ã§ã³ãè¨å®
create("global") {
dimension = Dimension.REGION
isDefault = true
}
// JP ãªã¼ã¸ã§ã³ãè¨å®
create("jp") {
dimension = Dimension.REGION
extra.apply {
set(ExtraKey.ApplicationId, "com.cookpad.android.activities")
set(ExtraKey.VersionCode, jpVersion.getVersionCode(project))
set(ExtraKey.VersionName, jpVersion.getVersionName(project))
}
}
// Production ãã©ãã¯ãè¨å®
create("production") {
dimension = Dimension.DEPLOYMENT_TRACK
isDefault = true
}
...
}
productFlavors.all {
if (isApplicationProject()) {
if (extra.has(ExtraKey.ApplicationId)) {
applicationId = extra[ExtraKey.ApplicationId].toString()
}
if (extra.has(ExtraKey.VersionCode)) {
versionCode = extra[ExtraKey.VersionCode].toString().toInt()
}
if (extra.has(ExtraKey.VersionName)) {
versionName = extra[ExtraKey.VersionName].toString()
}
...
}
}
}
}
private fun Project.configureVariantFilter() {
androidExt {
variantFilter {
if (flavors.map { it.name }.contains(Flavor.DEVELOPERS_SANDBOX)) {
ignore = true
}
}
}
}</pre>
<h4 id="Tips-KTS-ãã¡ã¤ã«ãç¨ãã¦-Convention-Plugins-ãä½ã"><strong>Tips: KTS ãã¡ã¤ã«ãç¨ã㦠Convention Plugins ãä½ã</strong></h4>
<p>ä»å㯠<code>org.gradle.Plugin</code> ãå®è£
ããææ³ãã¨ã£ã¦ãã¾ããã<code>*.gradle.kts</code> ãã¡ã¤ã«ãç¨ã㦠Convention Plugins ãä½ãæ¹æ³ãåå¨ãã¾ãã</p>
<p>å®è£
ã¯é常ã«ç°¡å㧠<code>my-convention-plugin.gradle.kts</code> ã¨è¨ã£ããã¡ã¤ã«ã <code>build-logic</code> å
ã® <code>src/</code> ãã£ã¬ã¯ããªã«é
ç½®ããã ãã§ãã<code>src/</code> å
ã«é
ç½®ããã <code>*.gradle.kts</code> ãã¡ã¤ã«ã¯ Plugin ã¯ã©ã¹ã«ããªã³ã³ãã¤ã«ãããä¾ã®å ´å㯠<code>my-convention-plugin</code> ã®é¨åã key ã¨ãã¦ä½¿ããã¨ãã§ããããã«ãªãã¾ããå ãã¦ãå¾è¿°ãã Plugin ã®ç»é²ãä¸è¦ã ã£ããã<code>settings.gradle.kts</code> ã®ãã¸ãã¯ãå
±éåã㦠Convention Plugins ã«ãããã¨ãã§ããããªã©ãæ§ã
ãªã¡ãªãããåå¨ãã¾ãã</p>
<p>é常ã®ãã«ããã¸ãã¯å
±éåã«ã¯é£½ããï¼ã¨ããæ¹ã¯ãkotlinx-rpc ã®ãªãã¸ããªã®ä¸ã§å
·ä½çãªå®è£
ä¾ãè¦ãã¾ãã®ã§ããã²åèã«ãã¦ãã ããã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2FKotlin%2Fkotlinx-rpc%2Ftree%2Fmain%2Fgradle-conventions%2Fsrc%2Fmain%2Fkotlin" title="kotlinx-rpc/gradle-conventions/src/main/kotlin at main · Kotlin/kotlinx-rpc" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/Kotlin/kotlinx-rpc/tree/main/gradle-conventions/src/main/kotlin">github.com</a></cite></p>
<h3 id="Plugin-ã®ç»é²"><strong>Plugin ã®ç»é²</strong></h3>
<p>ä½æãã Plugin ã¯ã¹ã¿ã³ãã¢ãã¼ã³ã® JAR ãã¡ã¤ã«ã¨ãã¦ããªã³ã³ãã¤ã«ããã Binary Plugin ã§ãããããã¢ããªããã¸ã§ã¯ãããåç
§ããããã«ã¯ Gradle ã« Plugin ãç»é²ãã¦ãããå¿
è¦ãããã¾ãã</p>
<p>åè¿°ãã <code>build-logic</code> ã® <code>build.gradle.kts</code> å
ã§ä»¥ä¸ã®ããã«ã㦠Plugin ãç»é²ãã¾ã</p>
<pre class="code" data-lang="" data-unlink>gradlePlugin {
plugins {
// Convention Plugins
register("ConventionFeature") {
id = "cookpad.convention.android.feature"
implementationClass = "convention.FeaturePlugin"
}
// Primitive Plugins
register("PrimitiveApplication") {
id = "cookpad.primitive.android.application"
implementationClass = "primitive.ApplicationPlugin"
}
register("PrimitiveLibrary") {
id = "cookpad.primitive.android.library"
implementationClass = "primitive.LibraryPlugin"
}
...
}
}</pre>
<h3 id="Include-Build-ã®è¨å®"><strong>Include Build ã®è¨å®</strong></h3>
<p>以ä¸ã§ <code>build-logic</code> ã¢ã¸ã¥ã¼ã«ã®è¨è¿°ã¯ã»ã¼å®äºãããããæå¾ã«ã¢ããªããã¸ã§ã¯ãå´ã§ <code>build-logic</code> ã include build ã¨ãã¦æå®ããå ãã¦ããã¾ã§è¨è¿°ãã Plugin ã«å®è£
ã移ãã¦ããã¾ãã</p>
<p>include build ã¨ãã¦æå®ããæ¹æ³ã¯ç°¡åã§ãã¢ããªããã¸ã§ã¯ãå´ã® <code>settings.gradle.kts</code> ã«ä»¥ä¸ã®ããã«å¤æ´ãå ããã ãã§ããééã£ã¦é常ã®ã¢ã¸ã¥ã¼ã«ã¨åæ§ã« <code>include</code> ããªãããã«æ°ãã¤ãã¦ãã ããã</p>
<pre class="code" data-lang="" data-unlink>pluginManagement {
includeBuild("build-logic") // build-logic ã¢ã¸ã¥ã¼ã«ã include build ãã
repositories {
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
repositories {
google()
mavenCentral()
}
}</pre>
<p>æå¾ã«åã¢ã¸ã¥ã¼ã«ã® <code>build.gradle</code> ã«è¨è¿°ãã¦ãããã«ããã¸ãã¯ã Plugin ã«ç§»è¡ãã¾ãã以ä¸ã®ç»åã¯ããã¢ã¸ã¥ã¼ã«ã® <code>build.gradle</code> ã«ãã£ãè¨è¿°ã Plugin ã«ç§»è¡ããä¾ã§ãããµã¤ãºã®é¢ä¿ä¸ãç¹ã«è¤éãªãã¸ãã¯ãè¨è¿°ããã¦ããªãã¢ã¸ã¥ã¼ã«ã® <code>build.gradle</code> ãä¾ã¨ãã¦ãã¾ãããbuild variant ã build flavor ãè¨è¿°ãã¦ããã¢ã¸ã¥ã¼ã«ã®å ´åãããã«å¤ãã®è¡æ°ãåæ¸ãããã¨ãã§ãã¾ãããã»ã¼å
¨ã¦ã®è¨è¿°ã Plugin ãç¨ãã¦å
±éåã§ãã¦ãããããæçµç㪠<code>build.gradle.kts</code> ã®ã¢ã¸ã¥ã¼ã«åºæã®è¨è¿°ã¯ <code>namespace</code> ã®ã¿ã¨ãªã£ã¦ãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="ããã¢ã¸ã¥ã¼ã«ã® build.gradle.kts"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsumo0922/20241118/20241118160316.png" width="1200" height="738" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ããã¢ã¸ã¥ã¼ã«ã® build.gradle.kts</figcaption></figure></p>
<h2 id="ã¾ã¨ã"><strong>ã¾ã¨ã</strong></h2>
<p>æçµçãªããã¸ã§ã¯ãå
¨ä½ã® <code>build.gradle.kts</code> ã®è¡æ°ã¯ä»¥ä¸ã®ã¨ããã§ãã</p>
<pre class="code" data-lang="" data-unlink>$ find . -type f -name '*.gradle.kts' -exec wc -l {} + | tail -n1 # å
¨ä½ã®è¡æ°
1823 total</pre>
<p>Gradle Composite Build + Convention Plugins ãç¨ãããã«ããã¸ãã¯ã®å
±éåã«ãããè¨è¿°éãååè¿ãåæ¸ãããã¨ãå¯è½ã¨ãªãã¾ããã</p>
<p>ãã®æ¹åã¯ç§ã One Experience ã®éçºã«æºããéã«ä¸çªæåã«è¡ã£ããã®ã§ããåè¿°ãã Build Flavor ã®è¨å®ã«å ããCIããªãªã¼ã¹ãªã©ã³ã¼ããã¼ã¹ãçµ±åãããã¨ã§ããã¸ã§ã¯ãã¨ãã¦ãããè¤éãªãã«ããã¸ãã¯ãæããããå¾ãªããªãã ããã¨äºæ³ããæ¬æ ¼çãªéçºã«å
¥ãåã«ä»åã®æ¹åãè¡ãã¾ãããå®éãããã¸ã§ã¯ããé²ããã«ããã£ã¦ä»åã話ããããããªè¤éãªãã¸ãã¯ã幾度ã¨ãªã追å ããã¦ãããä»åã®æ¹åããããã®ãã¸ãã¯ãããç°¡æ½ãã¤ç°¡æã«å°å
¥ãããã¨ãã§ããç¶æ
ã¨ãã¦ãã¾ããglobal-android ã®ãããªå·¨å¤§ãªããã¸ã§ã¯ãã§ãã«ããã¸ãã¯ã«æãå
¥ããã®ã¯é常ã«å°é£ã§ããããä»åã®æ¹åãéçºè
èªèº«ã®ä½é¨ãå¼ãã¦ã¯ One Experience ããã¸ã§ã¯ãå
¨ä½ã®é²è¡ã«ãåä¸ã«ã大ããè²¢ç®ãããã¨ãã§ããã¨èªè² ãã¦ãã¾ãã</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn:1">
<a href="https://docs.gradle.org/current/userguide/composite_builds.html">https://docs.gradle.org/current/userguide/composite_builds.html</a><a href="#fnref:1" rev="footnote">↩</a></li>
<li id="fn:2">
<a href="https://docs.gradle.org/current/userguide/composite_builds.html">https://docs.gradle.org/current/userguide/composite_builds.html</a><a href="#fnref:2" rev="footnote">↩</a></li>
<li id="fn:3">
<a href="https://docs.gradle.org/current/userguide/custom_plugins.html#sec:convention_plugins">https://docs.gradle.org/current/userguide/custom_plugins.html#sec:convention_plugins</a><a href="#fnref:3" rev="footnote">↩</a></li>
<li id="fn:4">
<a href="https://techlife.cookpad.com/entry/mobile_one_experience">https://techlife.cookpad.com/entry/mobile_one_experience</a><a href="#fnref:4" rev="footnote">↩</a></li>
</ol>
</div>
matsumo0922
ã¬ã·ãIDãªã³ã¯ç§»è¡ã®è©±
hatenablog://entry/6802418398303665900
2024-11-15T11:25:00+09:00
2024-11-15T11:25:00+09:00 ããã«ã¡ã¯ã2024å¹´4æãããæ°åã§ã¬ã·ãäºæ¥é¨ãããã¯ãéçºã°ã«ã¼ãã«æå±ãã¦ãããå¼µé ã§ããç§ãæ°åå
¥ç¤¾ãã¦æåã«åãçµããã¨ã¨ãªã£ãã®ãããã®ããã°ã§ä»¥åãç´¹ä»ããOne Experienceããã¸ã§ã¯ãã§ãã ãã®ããã¸ã§ã¯ãã«ã¯ããã¼ã¿ç§»è¡ãå¤è¨èªå¯¾å¿ãªã©ãç§ãçµé¨ãã¦ããªãã£ãã¿ã¹ã¯ãããããããã¾ãããç§ãæå±ãã¦ãããã¼ã ã§ã¯ã¹ã¯ã©ã éçºã«ãã£ã¦ããã¸ã§ã¯ããé²ãã¦ããã¾ããåãã¦ãã®ä½å¶ã§å¤§ããªããã¸ã§ã¯ãã«åå ãããã¨ã¯ã大å¤ã§ãããé¢ç½ãä½é¨ã§ãããç§ã¯ãã®ä¸ã§ããã¼ã¿ç§»è¡ã®ã¬ã·ãIDãªã³ã¯ã®ç§»è¡ãæ
å½ãããã¾ããã ç§ãæåã«ãã®ã¿ã¹ã¯ãæºãã£ã¦ããæã«ãè²ã
ãªçç±ã§â¦
<p>ããã«ã¡ã¯ã2024å¹´4æãããæ°åã§ã¬ã·ãäºæ¥é¨ãããã¯ãéçºã°ã«ã¼ãã«æå±ãã¦ãããå¼µé ã§ããç§ãæ°åå
¥ç¤¾ãã¦æåã«åãçµããã¨ã¨ãªã£ãã®ãããã®ããã°ã§ä»¥åãç´¹ä»ããOne Experienceããã¸ã§ã¯ãã§ãã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2024%2F10%2F10%2F105832" title="æ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ãã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>ãã®ããã¸ã§ã¯ãã«ã¯ããã¼ã¿ç§»è¡ãå¤è¨èªå¯¾å¿ãªã©ãç§ãçµé¨ãã¦ããªãã£ãã¿ã¹ã¯ãããããããã¾ãããç§ãæå±ãã¦ãããã¼ã ã§ã¯ã¹ã¯ã©ã éçºã«ãã£ã¦ããã¸ã§ã¯ããé²ãã¦ããã¾ããåãã¦ãã®ä½å¶ã§å¤§ããªããã¸ã§ã¯ãã«åå ãããã¨ã¯ã大å¤ã§ãããé¢ç½ãä½é¨ã§ãããç§ã¯ãã®ä¸ã§ããã¼ã¿ç§»è¡ã®ã¬ã·ãIDãªã³ã¯ã®ç§»è¡ãæ
å½ãããã¾ããã</p>
<p>ç§ãæåã«ãã®ã¿ã¹ã¯ãæºãã£ã¦ããæã«ãè²ã
ãªçç±ã§ãã¹ãããã¾ãããæ¬ç¨¿ã¯ãã®ã¿ã¹ã¯ã®æ°äººãªãã®æ¯ãè¿ãããã次ã«ãã大ããªããã¸ã§ã¯ãã«æºããæã«ãã©ã対å¦ããã°åé¡ãåé¿ã§ãããã¨ããç¥è¦ãã¾ã¨ãããã¨æãã¾ãã</p>
<h2 id="ã¬ã·ãIDãªã³ã¯ã®ç§»è¡ã«ã¤ãã¦">ã¬ã·ãIDãªã³ã¯ã®ç§»è¡ã«ã¤ãã¦</h2>
<p>æ¥æ¬çã§ã¯ã¬ã·ãã«ã決ã¾ã£ããã©ã¼ãããã§ã¬ã·ãã®IDãæ¸ãã¨ãã¬ã·ãã表示ããæã«ãã®ã¬ã·ãIDãæã¤ã¬ã·ããã¼ã¸ã«é£ã¶ãªã³ã¯ãä»ãããã¾ããã以ä¸ã®ç»åã®ããã«ãä¾ãã°ã¬ã·ãã®æé ã§ãã¬ã·ãID:12345ã®ã½ã¼ã¹ãã¨è¨æ³ã§æ¸ãã¨ããã¬ã·ãID:12345ãã®é¨åããªã³ã¯ã«ãªãã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/malcarol/20241113/20241113173613.png" width="990" height="186" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>äºå調æ»ã«ããã¨ããã®ãããªã¬ã·ãIDã¯æ¥æ¬çã®ã¬ã·ãã«ããã¦ç´¹ä»æãææãæé ãã³ãã»ãã¤ã³ããªã©ã®ã¨ããã«ããã¾ããã</p>
<p>ã¨ããã§ä»åãã¬ã·ãã®ãã¼ã¿ã¯æ¥æ¬çã®ãã¼ã¿ãã¼ã¹ããã°ãã¼ãã«çã®ãã¼ã¿ãã¼ã¹ã«ç§»è¡ãã¦ãã¾ãããã®ã¨ãã¬ã·ãã®IDãå¤ããã®ã§ãä½ãããªãã¾ã¾ã ã¨ãªã³ã¯å
ã®æ´åæ§ãå´©ãã¦ãã¾ãã¾ãããã®ããã¬ã·ãIDãªã³ã¯ã«ã¤ãã¦ãä¿®æ£ãå¿
è¦ã§ãããã°ãã¼ãã«çã®ã¬ã·ãã®IDã¯ç§»è¡ãã¦ã¿ãªãã¨æ±ºã¾ãã¾ãããæ
ã«ãå
ã«ã¬ã·ãã®ãã¼ã¿ã移è¡ãããã®ãã¨ã¬ã·ãIDãªã³ã¯ãç½®æãããã¨ã«ãã¾ãããä»åã®ã¿ã¹ã¯ã¯ãªã³ã¯å
ã¨ãªãã¬ã·ãã®IDããã°ãã¼ãã«çã®IDã«ç½®æãããã¨ã§ãã</p>
<h2 id="ã¬ã·ãIDãªã³ã¯ç§»è¡ã®æ¯ãè¿ã">ã¬ã·ãIDãªã³ã¯ç§»è¡ã®æ¯ãè¿ã</h2>
<p>ã¬ã·ãIDãªã³ã¯ç§»è¡ã«åãçµã¿å§ããé ãç§ã¯ã¾ã æ
£ãã¦ããªããã¨ãç¥ããªããã¨ãå¤ãããã¾ãç´°ãã調æ»ã¯ã§ãã¦ãã¾ããã§ããããã®çµæã¨ãã¦ããã¤ãã®ä¸å
·åãçãã§ãã¾ãã¾ããã幸ãOne Experienceã®ãªãªã¼ã¹ã®åã«èµ·ãã£ãä¸å
·åã ã£ãã®ã§ã¦ã¼ã¶ã¼ã¸ã®å½±é¿ã¯ãªãã£ãã®ã§ãããããã§ã¯ããããæ¯ãè¿ãããã¨æãã¾ãã</p>
<h3 id="ç¶æ
é·ç§»ã®èæ
®æ¼ã">ç¶æ
é·ç§»ã®èæ
®æ¼ã</h3>
<p>æåã«åãçµãã ã®ã¯ãæ¥æ¬çããã°ãã¼ãã«çã¸ç¶ç¶çã«ãã¼ã¿ç§»è¡ãã¦ããé¨åã§ã®ç½®æã§ãããã¼ã¿ç§»è¡ãè¡ã£ã¦ããé¨åã®ãã£ããã¢ãããé£ããã£ãã®ã§ãã³ã¼ããè¦ãªããå¸åãããã¨æãã¾ãããå®éãã£ããã¢ããã¯ããã«çµãããã¿ã¹ã¯ãå§ãã次ã®æ¥ã«ã¯pull requestãåºãã¾ãããä¸å®ãªã¨ãããè²ã
ããã¾ããããã³ã¼ããèªã¿ãªããåé¡ãªããã確èªããApproveãããã£ã¦ãDeployãã¾ããã</p>
<p>ããã§åé¡ãçºçãã¾ãããæ¥æ¬çã§ã¬ã·ããç·¨éãã¦ããã°ãã¼ãã«çã§ã¬ã·ãIDãå¤ãããªãã£ããã§ããããã§ãã¼ã¿ç§»è¡ã®ãã¹ããã¡ã¤ã«ãããã¬ã·ãç·¨éã®ã·ãã¥ã¨ã¼ã·ã§ã³ããã¹ããã¦ããç®æãè¦ã¤ãã¦ãã·ãã¥ã¬ã¼ããã¦è©¦ãã¾ãããDebugãã¦ã³ã¼ãã追ã£ã¦ããããã¬ã·ãç·¨éã®éã«ã¯èªåãå®è£
ã追å ããã¨ããã¨ã¯å¥ã®ç®æã®ã³ã¼ããåãã¦ããã¨æ°ä»ãã¾ããããã®å¾ãä¸å¯§ã«ã¬ã·ã移è¡å
¨ä½ã®ç¶æ
é·ç§»ãèããããããç¶æ³ãèæ
®ãã¦ããã¹ãã§ã·ãã¥ã¬ã¼ããã¾ãããå
¨é·ç§»ãã¿ã¼ã³ã§å¯¾å¿æ¸ã¿ã ã¨ç¢ºä¿¡ãã¦ãå®è£
å®äºã¨ãã¾ããã</p>
<p>æ¯ãè¿ã£ã¦ã¿ãã¨ãæåã«ã³ã¼ããã¿ãæã«ãã¬ã·ãç·¨éã«ã¤ãã¦ã®å®è£
ããããã¨ã«ã¯æ°ä»ãã¦ãããã¨ãæãåºãã¾ããããã®ã¨ãèªåãå®è£
ããã¨ããã ãã§è¶³ãã¦ããã®ãä¸å®ã§ãå¨ãã®ååã«ã¯ç¸è«ãã¦ãã¾ããããå®è£
ã«è©³ããå
輩ã«ã¯ç¸è«ãã¦ãã¾ããã§ãããã¾ãããã¹ãããã¦ããæ¡ä»¶ã網ç¾
çã«è©¦ãã¦ãã§ããã ãåç¾ãã¦æ¼ãããªãã確èªããæ¹ãè¯ãã£ãã¨æãã¾ãã</p>
<h3 id="ä¸å¯è§£ãªã¯ã¨ãªçµæ">ä¸å¯è§£ãªã¯ã¨ãªçµæ</h3>
<p>ãã¼ã¿ç§»è¡ã®å®è£
ã§ã¯ãç¶ç¶çãªãã¼ã¿ç§»è¡ã¨ã¯å¥ã«ãã¾ã¨ã¾ã£ããã¼ã¿ãä¸æ¬ã§ãããå¦çãããã¨ã§ãã¼ã¿ç§»è¡ãè¡ãå®è£
ãåå¨ãã¾ãããããã«ã¤ãã¦ãã¬ã·ãIDãªã³ã¯ç§»è¡ãè¡ãå¿
è¦ããã£ãã®ã§ãããç½ ãããããã¦ãé£ããã£ãã§ããããã¤ããç´¹ä»ãã¾ãã</p>
<p>ãããå¦çã«ãã£ã¦ç§»è¡ããã¦ããã¬ã·ãã«å¯¾ãã¦ãå¾ããã¬ã·ãIDãªã³ã¯ã®æ¸ãæããè¡ããã¨ãèãã¾ããã©ã®ã¬ã·ãã«ã¤ãã¦æ¸ãæããã¹ããå¤æããããã®æ¹æ³ã¨ãã¦ãã¬ã·ãã®æçµæ´æ°æå»ãæ¥æ¬çã¨ã°ãã¼ãã«çã§æ¯è¼ãããã¨ã§æ±ºãããã¨ã¨ãã¾ãããããã§æ¥æ¬çã®æçµæ´æ°æå»ã¯Queueryï¼ãã
ããï¼ã¨ããå
製ãã¼ã«ã§SQLã¯ã¨ãªãå®è¡ãã¦å¾ã¦ãã¾ãã</p>
<p>以åã«ãä¼¼ããããªå®è£
ãããçµé¨ããã£ãã®ã§ãèªä¿¡æºã
ã§pull requestãåºãã¦ãæ©éApproveãããã£ã¦å®è¡ãã¾ãããæ¤è¨¼ç°å¢ã§ç¢ºããã¦ãä½åãä¾ãè¦ãã¨æ¸ãå¤ãã£ããã¨ã確èªãã¾ããã</p>
<p>ããã§ãã¾ãåé¡ãçºçãã¾ããããã¼ã¿ã調æ»ããããæ¸ãæãã£ã¦ãªãã¬ã·ãIDãåå¨ãã¦ããã¨æ°ä»ããã®ã§ããããã§ã¾ã調æ»ãå§ãã¾ãã¦ãã³ã¼ããèªãã§ãè²ã
ãã°ãåºãããªã¨ããã«ãã°ã®ä»®èª¬ãç«ã¦ã¦ã¾ãããããã§ãçµå±è¦ã¤ãããªãã£ãã®ã§ãã¡ãã£ã¨ç¦ã£ãã¨ãããããã¾ãã¦ãå
輩ã¨ãã¢ãããå§ãã¾ããã</p>
<p>å
輩ã¨ä¸ç·ã«èª¿æ»ããã¾ãã¦ãããããQueueryããè¿ã£ã¦ããã¬ã·ãã®ãªã¹ãã«æ¸ãæãã対象ã¬ã·ããå
¥ã£ã¦ãªãã®ãããããªãã¨èãå§ãã¾ãããããããæå
ã§åãSQLã¯ã¨ãªãå®è¡ããçµæã«ã¯å¯¾è±¡ã¬ã·ããå
¥ã£ã¦ããã®ã§ãçµæãéããã¨ã¯ããããªããªã¨æåã¯æã£ã¦ãã¾ãããã¨ããããå®è£
ã®æ¹ã®ä¸éå¤æ°ãstep by stepã«è¦ããã確ãã«å¯¾è±¡ã¬ã·ããå
¥ã£ã¦ãªããã¨ãããã¾ãããä½æ
ãããªã£ã¦ããã®ãå
¨ãåããããååã¨å
±ã«èª¿æ»ãç¶ãã¾ãããæçµçã«ãQueueryã®å ´åã¨æå
ã®å ´åã§SQLã¯ã¨ãªã®ä¸ã§ã®ã¨ã¹ã±ã¼ãã®æåãç°ãªã£ã¦ããã®ãåå ã¨åããã¾ããã</p>
<h3 id="ããã«ãã§ãã¯ã®éè¦ã">ããã«ãã§ãã¯ã®éè¦ã</h3>
<p>ã¬ã·ãIDãªã³ã¯ã®ãããå¦çãå®éã«åããåã«ãç¶ç¶çãªç§»è¡ã®ã¨ãã®å¤±æãæãåºããå
¨ä½çãªãã¼ã¿æ¤è¨¼ãããæ¹ãããã¨æãã¾ãããããã§ã¬ã·ãIDã«é¢é£ãããã°ãåºãããªä»®èª¬ãç«ã¦ãæ¤è¨¼ãããã¨ã«ãã¾ãããããããã°ç§ã¯ã¢ã¤ãã¢ãããããåºããã¨ãå¾æãªã®ã§ãæ¢åã®ãã¼ã¿ã®ä¸å
·åãçµæ§è¦ã¤ãã¾ãããå®è¡ããåã«è¦ã¤ãã£ã¦è¯ãã£ãã¨æãã¾ããã</p>
<p>ãã®å¾ãããã¤ãã®åé¡ãåãæããªãããã¬ã·ãIDãªã³ã¯ã移è¡ãããã¨ãã§ãã¾ãããæçµçã«ç¡äºã«ç§»è¡ã§ãã¦è¯ãã£ãã§ããæåã«ãã¹ãããç¦ã£ããã©ãé£ããä»æ§ããããç½ ã¿ãããªãã°ãé£ããã®ããããããªãã失æãã¦ãç¦ãããç¸è«ãã¦ãã£ã¬ã³ã¸ãã¦ãããã¨ã大äºã ã¨é常ã«ãããã¾ããããããã ãã ãåºæ¥ãããã«ãªã£ããã¤ã³ãããªã¨æãã¾ããã</p>
<h2 id="次ã«æ´»ãããã¤ã³ã">次ã«æ´»ãããã¤ã³ã</h2>
<p>ä»åã®æ¯ãè¿ãã¯ããããã以ä¸ã®3ã¤ã®ãã¿ã¼ã³ã«åããããã¨æãã¾ãã</p>
<h3 id="ãã¤ã³ãï¼æªç¥ã®åå¨ãèªèã詳ãã人ãåä¾ã®ç¥æµãå¸åãã">ãã¤ã³ãï¼ï¼æªç¥ã®åå¨ãèªèãã詳ãã人ãåä¾ã®ç¥æµãå¸åãã</h3>
<p>ã¢ã¯ã·ã§ã³ãã¨ãåã«ãä½ã追å ã§ç¢ºèªããªãã¨ãã¡ããç¥ããªãã¾ã¾ã«ãä»ã¾ã¾ã§ã®ç¥èã ãã§å¯¾å¿ãã¦ããã¹ãå ´åã§ããæ°åã®æã«ãç¹ã«One Experienceã®ãããªå¤§ããããã¸ã§ã¯ãã®ä¸ã§ãå
¨é¢çã«ç´°ããæãã¦ãããã人ã¯å°ãªãã®ã§ãçµå±å人å¤æã®æãå¤ãã§ããããç¨åº¦ã®ãã¹ã¯èµ·ãããã®ã§ãããæè¨ã«ãã¹ãã ã¨æãã¾ããããã®ãããªç¶æ³ã§ãã§ãããã¨ã¯ããã¨æãã¾ãã</p>
<p>ãã¨ãã°ç¥ããªãã¿ã¹ã¯ãããã£ãã¨ãã課é¡ã®ç解ã足ããªã解å度ãä½ãã¾ã¾ã§ã¿ã¹ã¯ãé²ããªããã¨ã§ããèªä¿¡ã¯ãããã¨ã ã¨æãã¾ãããæ°åã¨ãã¦ç解ããã®ã¯ãå®å
¨ç解ã¨ã®è·é¢ããããã¨ãèªèãã¹ãã§ããããããã¬ãã¥ã¯ã¼ãç¥ããªããã¨ãããã®ã§ããã®æãç¥ããªããã¨ããããã確èªããããã®ãªãµã¼ãããæ°è»½ã«ä»ã®äººã«èãã¦ãããã¨ã§ãæ¹åããã ããã¨æãã¾ãã</p>
<h3 id="ãã¤ã³ãï¼è£éç¯å²ã模索ãé©åã«å
輩ã¨ã·ã³ã¯ãã">ãã¤ã³ãï¼ï¼è£éç¯å²ã模索ããé©åã«å
輩ã¨ã·ã³ã¯ãã</h3>
<p>ã¢ã¯ã·ã§ã³ãã¨ãã¨ãã大éãªææ決å®ããããã©ãå人ã®è£éã§ã§ããªããã¨ã決ãã¡ãã£ãå ´åã§ããç¶æ
é·ç§»ã®èæ
®æ¼ããèµ·ãããã¨ãã®ããã«ãéä¿¡ã«ãã£ã¦åé¡ãèµ·ãããã¾ãã</p>
<p>ããã«ã¤ãã¦ã¯ã¢ã¯ã·ã§ã³ãã¨ãæã«ãå
輩ã¨ã®å ±åã¨ç¸è«ãå¿
è¦ã§ããããã ãã§ã¯ãªããå
輩ããå©è¨ãããã£ãæã«ãã課é¡ã¨è©±ãå®å
¨ã«å¸åãã¦ãããè°è«ãã¦ãããã¨ã大äºã§ããè¦ããã«ãã¢ã¯ã·ã§ã³ãã¨ãåã«å
輩ã®è¨ã£ã¦ãããã¨ãç解ãã¦ãããèªåãä½ãããã¤ãããå ±åããç®æ¨ã¨èªèãã·ã³ã¯ãã¦åãã¦ãã¢ã¯ã·ã§ã³ãã¨ãã¹ãã ã¨æãã¾ãã</p>
<h3 id="ãã¤ã³ãï¼æªç¶ã«é²ãèæ
®æ¼ã確èªãã«ã¼ãã³åãã">ãã¤ã³ãï¼ï¼æªç¶ã«é²ããèæ
®æ¼ã確èªãã«ã¼ãã³åãã</h3>
<p>å®éã«ãæ°åã«éããããããããªãã人éã¯ãã¹ãããã¨ãå¿
ãããçãç©ãªã®ã§ããã®ãã¹ããçµé¨ãæã£ã¦ã2度ç®ããªãããã«ããã®ãããã¨æãã¾ãããã®ããã«ã確èªãã¤ã³ããæ¸é¢åããããä»åã®ãã¹ã®ãã£ã¼ãããã¯ãå¸åãããã§ãã¾ããç¹ã«éè¦ãããªãã®ãä»ã®äººã¨å
±æãã¦ãè¯ãã§ãããã</p>
<p>ããã¦ã次ã«ä¼¼ããããªãã¨ããã£ããã網ç¾
çãªç¢ºèªããã¡ãã¨æ ããè¡ãã¹ãã§ããèªåã®å¤æ´ã®å½±é¿åãç解ãã¤ã¤ãã¦ã¼ã¶ã¼ã®ããã«ããèæ
®æ¼ã確èªãã«ã¼ãã³åããæ¹ãããã¨æãã¾ãã</p>
<h2 id="æå¾ã«">æå¾ã«</h2>
<p>ä»åã¯ãOne Experienceããã¸ã§ã¯ãã®ç°å¢ã§ã®ã¬ã·ãIDãªã³ã¯ç§»è¡ã®æã®èªåãªãã®æ¯ãè¿ãããã¾ãããç§ããã®ã¿ã¹ã¯ãå§ãã¦ãæ³å®å¤ã®åé¡ãããããã§ã¾ãããé£ããä»æ§ããã¤ã¿ã¹ã¯ãªã®ã§ããã¾ãã§ããªãã£ãã¨ãããããã¾ããããã®æ¯ãè¿ãã¨çµé¨ã次ã«ä¼¼ããããªã¿ã¹ã¯ãããæã«ãã§ããã ãåèã«ãããã¨èãã¦ãã¾ãã</p>
malcarol
iOSã¢ããªã«ãããè¤æ°ãªãªã¼ã¹ã«è·¨ã£ãæ©è½æ¹åã®éçºäºä¾ç´¹ä»
hatenablog://entry/6802418398303600210
2024-11-14T11:14:00+09:00
2024-11-14T11:14:00+09:00 ã¬ã·ãäºæ¥é¨ã®Haurta (@0x746572616e79 )ã§ããã°ãã¼ãã«ãµã¼ãã¹ã¨ã®çµ±åããã¸ã§ã¯ã(One Experienceããã¸ã§ã¯ã)ã«ä¼´ãiOSã¢ããªã±ã¼ã·ã§ã³ãã°ãã¼ãã«ã¨æ¥æ¬ã§å¥ã
ã®ã¢ããªã±ã¼ã·ã§ã³ãéçºãã¦ããä½å¶ããä¸å¤ãã¦ãã°ãã¼ãã«ã®ã¢ããªã±ã¼ã·ã§ã³ãã¼ã¹ã®éçº(ã°ãã¼ãã«ç)ã¸ç§»è¡ãé²ãã¾ããã ã°ãã¼ãã«ã¨æ¥æ¬ã§ç°ãªãã¢ããªã±ã¼ã·ã§ã³ãéçºãã¦ãããããåãã¯ãã¯ãããã§ãç´°ããªæåã®éããè¦ããã¾ããæ°ã«ãªãæåããªããã©ãããã¼ã ã§ä½åº¦ãã¦ã©ã¼ã¯ã¹ã«ã¼ãéããçµæãã¬ã·ãã¨ãã£ã¿ã¼ããããã£ã¼ã«è¨å®ç»é¢ã§ä½¿ããããã©ãããã«ã¼ã®æåãåé¡ã¨ãã¦æµ®ä¸ãã¾ãâ¦
<p>ã¬ã·ãäºæ¥é¨ã®Haurta (<a href="https://x.com/0x746572616e79">@0x746572616e79
</a>)ã§ããã°ãã¼ãã«ãµã¼ãã¹ã¨ã®çµ±åããã¸ã§ã¯ã(One Experienceããã¸ã§ã¯ã)ã«ä¼´ãiOSã¢ããªã±ã¼ã·ã§ã³ãã°ãã¼ãã«ã¨æ¥æ¬ã§å¥ã
ã®ã¢ããªã±ã¼ã·ã§ã³ãéçºãã¦ããä½å¶ããä¸å¤ãã¦ãã°ãã¼ãã«ã®ã¢ããªã±ã¼ã·ã§ã³ãã¼ã¹ã®éçº(ã°ãã¼ãã«ç)ã¸ç§»è¡ãé²ãã¾ããã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2Fmobile_one_experience" title="ã¢ãã¤ã«ã¢ããªã® One Experience - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>ã°ãã¼ãã«ã¨æ¥æ¬ã§ç°ãªãã¢ããªã±ã¼ã·ã§ã³ãéçºãã¦ãããããåãã¯ãã¯ãããã§ãç´°ããªæåã®éããè¦ããã¾ããæ°ã«ãªãæåããªããã©ãããã¼ã ã§ä½åº¦ãã¦ã©ã¼ã¯ã¹ã«ã¼ãéããçµæãã¬ã·ãã¨ãã£ã¿ã¼ããããã£ã¼ã«è¨å®ç»é¢ã§ä½¿ããããã©ãããã«ã¼ã®æåãåé¡ã¨ãã¦æµ®ä¸ãã¾ããã</p>
<p>ãã©ãããã«ã¼ã®æ¹åãéè¦ãªã¿ã¹ã¯ã¨ãã¦åãçµããã¨ã«ãã¾ãããããã©ãããã«ã¼ã«éããOne Experienceããã¸ã§ã¯ããå§ã¾ã£ã¦ããã¯ã°ãã¼ãã«çã®ã³ã¼ããã¼ã¹ãèªãã¨ããããã®ã¹ã¿ã¼ãã«ãªãããããã®ã¿ã¹ã¯ã®å®äºã«ã©ã®ãããæéããããã®ãæ¨æ¸¬ãã¥ããç¶æ
ã§ããã</p>
<p>ä¸è¬çã«å¤§ããªã¿ã¹ã¯ãå°ããåå²ãããã¨ã¯è¡ããã¾ãããå
·ä½çãªåå²æ¹æ³ãé²è¡ã®ææ³ã«ã¤ãã¦ã¯ãæ
£ããçµé¨ã ã£ãã試è¡é¯èª¤ãå¿
è¦ã§ãã</p>
<p>ãã®è¨äºã§ã¯ããã©ãããã«ã¼ã®æ¹åãéãã¦ãã¿ã¹ã¯åå²ã¨ããã¸ã§ã¯ãã®é²ãæ¹ã«ã¤ãã¦å¹¾ã¤ãã®å¦ã³ããã£ããããã©ã®ããã«ã¿ã¹ã¯ãå解ãé²ãã¦ãã£ãã®ããç´¹ä»ãã¾ãã</p>
<h2 id="ãã©ãããã«ã¼">ãã©ãããã«ã¼</h2>
<p>ã¯ãã¯ãããã«ããã¦ãã©ãããã«ã¼ã¯é常ã«éè¦ãªæ©è½ã®ä¸ã¤ã§ãæ§æ¥æ¬çãã°ãã¼ãã«çã¨ãã«ãã©ãããã«ã¼ã¯æ¨æºã®PHPickerViewControllerã使ç¨ãããä½ç¨®é¡ãç®çã¨ä½é¨ã«ãã£ãèªåã®ãã©ãããã«ã¼ãå®è£
ãã¦ãã¾ããã</p>
<p>æ§æ¥æ¬çã§ã¯ãã¦ã¼ã¶ã¼ãã¬ã·ãã«åçã追å ããéããã©ãããã«ã¼ãæåã«èµ·åããããããã«ã¡ã©ã¹ã¯ãªã¼ã³ã«é·ç§»ã§ãããã¿ã³ãé
ç½®ããã¦ãã¾ãããããã«å¯¾ããã°ãã¼ãã«çã§ã¯ãã«ã¡ã©ã¹ã¯ãªã¼ã³ãæåã«èµ·åããã«ã¡ã©ã¹ã¯ãªã¼ã³ã«ãã©ãããã«ã¼ã¸ã®é·ç§»ãã¿ã³ãè¨ç½®ããã¦ãã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="æé åçãè¼ãããã¨ããæã«è¡¨ç¤ºãããç»é¢"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tera_ny/20241113/20241113121736.png" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>æé åçãè¼ãããã¨ããæã«è¡¨ç¤ºãããç»é¢</figcaption></figure></p>
<p>ã°ãã¼ãã«çã®ã«ã¡ã©æ©è½ã¯ã·ã³ãã«ãªãããå¤ãã®ã¦ã¼ã¶ã¼ãå¤é¨ã®ã«ã¡ã©ã¢ããªãå©ç¨ããã ããã¨èãããã¾ãããã®ç¶æ³ã§ã¯ãç»åãã¢ãããã¼ããããã³ã«ã«ã¡ã©ã¹ã¯ãªã¼ã³ãèµ·åããã®ã¯ä¸ä¾¿ã§ããã¾ããXï¼æ§Twitterï¼ãInstagramã¨ãã£ãSNSã¢ããªã§ã¯ããã©ãããã«ã¼ãå
ã«è¡¨ç¤ºããå
é¨ã«ã«ã¡ã©ã¢ã¯ã»ã¹ã®å°ç·ãè¨ããã®ãä¸è¬çã§ããããã§ãã°ãã¼ãã«çããã©ãããã«ã¼ãæåã«èµ·åããæµãã«æ¹åãããã¨ã«ãªãã¾ããã</p>
<h2 id="åé¡è§£æ±ºã¸ã®éçãç«ã¦ã">åé¡è§£æ±ºã¸ã®éçãç«ã¦ã</h2>
<p>ã°ãã¼ãã«çã¯åæ©è½ãã©ã®ããã«å®è£
ããã¦ããããç»é¢ã®æ§é ãå®è£
ãç解ããã¨ããããæ¹åãé²ãã¾ããå½åã¯ç»é¢ã®å
¥ãæ¿ãã ãã§æ¸ãã¨èãã¦ãã¾ããããã³ã¼ããªã¼ãã£ã³ã°ãé²ãããã¡ã«ããªãã®å·¥æ°ãå¿
è¦ã§ãããã¨ãå¤æãã¾ããã</p>
<p>ã°ãã¼ãã«çã¯Coordinator Patternãç¨ããç»é¢é·ç§»ãæ¡ç¨ãã¦ããããã©ãããã«ã¼ã«ã¯ã«ã¡ã©ã¸ã®é·ç§»ãè¨ç½®ããå¿
è¦ãããã¾ããããã®æãæ¦ç®ã§10æé以ä¸ã®å·¥æ°ããããå¯è½æ§ãæãã¤ã¤ããã©ãããçæããã°è¯ãã®ãå
·ä½çãªè¦ç©ãããã§ãã¦ãã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="ã°ãã¼ãã«çã®ã¢ã¼ããã¯ãã£"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tera_ny/20241113/20241113114007.png" width="1200" height="861" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ã°ãã¼ãã«çã®ã¢ã¼ããã¯ãã£</figcaption></figure></p>
<p>ã¬ã·ãäºæ¥é¨ã¯ã¹ã¯ã©ã éçºãæ¡ç¨ãã¦ãããã¿ã¹ã¯ã®åªå
é ä½ãç¯å²ã決å®ããããã«ããå·¥æ°ã®è¦ç©ãããå¿
è¦ã§ããé常ã«æ£ç¢ºã§ããå¿
è¦ã¯ããã¾ãããã大éæéããè¦ç©ããã§ã¯ä¸éæãªããåªå
é ä½å¤æãé£ãããªãã¾ãã</p>
<p>æ¹åã¿ã¹ã¯ã«ã¯ç ´å£çãªå¤æ´ãå«ã¾ãã¦ãããè¤æ°ã¹ããªã³ãã«ããã£ã¦éçºãå¿
è¦ãªå ´åã¢ãã¤ã«ç¹æã®åé¡ãèããå¿
è¦ãããã¾ããã¢ãã¤ã«ã¢ããªã¯ã¹ããªã³ããã¨ã«ãªãªã¼ã¹<a href="#f-80add568" id="fn-80add568" name="fn-80add568" title="ã¬ã·ãäºæ¥é¨ã§ã¯1ã¹ããªã³ããä¸é±éã§åãã¦ããããé±æ¬¡ãªãªã¼ã¹ãè¡ã£ã¦ãã¾ã">*1</a>ããã¦ãããããæªå®æã®æ©è½ãé²åºããªãããã«ããªããã°ãªãã¾ããããã®ãããmainãã©ã³ãã¸ç´°ããå¤æ´ãå ãã¦ããã®ããéçºãã©ã³ããäºåã«ç¨æãã¾ã¨ãã¦mainãã©ã³ãã«åãè¾¼ãæ¹æ³ãæ¡ç¨ãã¹ããã©ãããæ¤è¨ãã¾ããã</p>
<p>mainãã©ã³ãã«ç´æ¥ç´°ããªå¤æ´ãç©ã¿éããæ¹æ³ã§ã¯ãã³ãããã®ç²åº¦ãã³ã³ããã¼ã«ãããããåå¤æ´ã®å½±é¿ç¯å²ãå°ããä¿ã¤ãã¨ãã§ãã¾ããã¾ããææ°ã®mainã«è¿½å ãã¦ãããããå®å®æ§ã確ä¿ããããã§ããããããéçºä¸ã®æ©è½ãæªå®æã®å¤æ´ããªãªã¼ã¹ã§é²åºãã¦ãã¾ããªãããå³éãªç®¡çãæ±ãããã¾ãã</p>
<p>ä¸æ¹ãéçºãã©ã³ããç¨ããæ¹æ³ã§ã¯ãmainãã©ã³ãã«å½±é¿ãä¸ãããã¨ãªãæ©è½ã追å ãå¤æ´ãããã¨ãã§ãã¾ãããã ãéçºãã©ã³ãã§ã®ä½æ¥ãé²ãä¸ã§mainãã©ã³ãã«ã並è¡ãã¦å¥ã®å¤æ´ãå ãããããã¨ãã»ã¨ãã©ã§ãã³ã³ããªã¯ãã®ãªã¹ã¯ãé«ããªãã¾ããã¾ããmainãã¼ã¸ãããã¿ã¤ãã³ã°ã§å
¨ã¦ã®å·®åããã§ãã¯ããå¿
è¦ããããã¬ãã¥ã¼ã³ã¹ãã®å¢å ã«ã¤ãªããã¾ããã¢ã«ãã¡ãªãªã¼ã¹ã§ã¯ãéçºãã©ã³ããç¨ãã¦å¤æ´ãç©ã¿éãã¦ãã¾ããããmainãã©ã³ãã¨å¤§ããªå·®åãçºçãã¦ãã¾ããã³ãããã®çµ±åæã«å¤å¤§ãªå·¥æ°ãããããã¨ãããã¾ããã</p>
<p>æçµçã«ã¯mainãã©ã³ãã¸å¤æ´ãç©ã¿ä¸ãã¦ããæ¹æ³ã§é²ãããã¨ã決ãã¾ããã</p>
<h2 id="ãªãªã¼ã¹ãè·¨ãæ©è½éçº">ãªãªã¼ã¹ãè·¨ãæ©è½éçº</h2>
<p>mainãã©ã³ãã«å¤æ´ãç©ã¿ä¸ãã¦ããã¨æ±ºãã¾ããããã§ã¯ã©ãç ´å£çãªå¤æ´ãéãã¦ããã¹ãã§ããããï¼</p>
<h3 id="Feature-Toggle">Feature Toggle</h3>
<p>ã°ãã¼ãã«çã«ã¯Feature Toggle <a href="#f-4fa7bcab" id="fn-4fa7bcab" name="fn-4fa7bcab" title="ä¸è¬çã«ã¯Feature Flagã¨ãå¼ã°ãã¦ãã(https://martinfowler.com/articles/feature-toggles.html">*2</a> ãç¨æããã¦ãã¾ããA/Bãã¹ããªã©ã§åºã使ããã¦ããã代表çãªãµã¼ãã¹ã¨ãã¦Firebase Remote Config <a href="#f-d7238a0c" id="fn-d7238a0c" name="fn-d7238a0c" title="https://firebase.google.com/products/remote-config?hl=ja">*3</a> ãæåã§ãã詳ããã¯åãä¸ãã¾ããããOne Experienceåãã®æ©è½ãã°ãã¼ãã«çã¸å®è£
ãå±éããéã«ãFeature Toggleãå©ç¨ããã¦ãã¾ãããé·ç§»å
ã®åãæ¿ãããUITableViewã§è¡¨ç¤ºããã³ã³ãã³ãã®åºãåããªã©è²ã
ãªã¦ã¼ã¹ã±ã¼ã¹ã§ã®å©ç¨ãã§ãã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">class</span> <span class="synIdentifier">Coordinator</span><span class="synSpecial">:</span> <span class="synType">InteractorDelegate</span> {
<span class="synIdentifier">...</span>
<span class="synPreProc">func</span> <span class="synIdentifier">interactorWantsToInsertStepAttachments</span>() {
<span class="synStatement">if</span> appContext.featureToggle.supports(.ãã©ãããã«ã¼ãå
ã«èµ·åãã) {
startPhotoPickerFlow()
} <span class="synStatement">else</span> {
startCameraFlow()
}
}
}
</pre>
<p>ä»åã®ãã©ãããã«ã¼æ¹åã¿ã¹ã¯ã§ã¯Coordinatorã®ç»é¢é·ç§»å¦çã«ã¦Feature Toggleãå©ç¨ãåºãåããè¡ãæ¹æ³ãæ¡ç¨ãã¾ãããå¥ã®æ¹æ³ã¨ãã¦Feature Toggleããªã³ã®ã¨ãã«ã¯ãã©ãããã«ã¼ãããªãã®ã¨ãã«ã¯ã«ã¡ã©ã¹ã¯ãªã¼ã³ãåæ表示ããåä¸ã®Coordinatorãå®ç¾©ããæ¹æ³ãæ¤è¨ãã¾ããããå®è£
ã®ç°¡ç´ åã¨ç®¡çã®ãããããèããã¨ç¬ç«ããCoordinatorãç¨æããæ¹ãé©ãã¦ããã¨ä»åã¯å¤æãã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="ãããã°ã¡ãã¥ã¼ããFeatureToggleã®åãæ¿ããã§ãã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tera_ny/20241113/20241113115659.png" width="1113" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ãããã°ã¡ãã¥ã¼ããFeatureToggleã®åãæ¿ããã§ãã</figcaption></figure></p>
<p>éçºä¸ã¯ç«¯æ«å
ã®FeatureToggleãå¼·å¶çã«æå¹åããã©ãããã«ã¼ãç«ã¡ä¸ããæ¬çªç°å¢ã§ã¯ä»¥åã®ã«ã¡ã©ãç«ã¡ä¸ãããã¨ã§æå³ããéçºä¸ã®å®è£
ãã¦ã¼ã¶ã¼ã®æ¹ã«è¦ãã¦ãã¾ãäºæ
ãé²ããã¨ãã§ãã¾ãã</p>
<h3 id="ã¿ã¹ã¯ã¨ä»æ§ãæ´çãã">ã¿ã¹ã¯ã¨ä»æ§ãæ´çãã</h3>
<p>Feature Toggleã«ãã£ã¦å
¨ã¦ã®åé¡ã解決ãããã¨ããã¨ãããã§ã¯ããã¾ãããFeature Toggleã§åå²å
ãå¤ããã®ãè¯ãããã ã¨ããã®ã¯ãããã¾ããããä¾ç¶ã¨ãã¦ã©ããããã®å·¥æ°ãããããããããªãããã¿ã¹ã¯ã¨ä»æ§ãæ´çããå¿
è¦ãããã¾ããé·ç§»å
ã®ç»é¢ãFeature Toggleã§åå²ã§ããããã«ãªã£ãã®ã§å
¨ãæ°ããç»é¢ãå®ç¾©ãã¦è¡¨ç¤ºãããã¨ãã§ãã¾ãããã¾ã0ãããã©ãããã«ã¼ãã«ã¡ã©ã¹ã¯ãªã¼ã³ãå®è£
ããã®ã¯äºåº¦æéã§ãã®åå·¥æ°ãããã£ã¦ãã¾ãã¾ããæéãçãããã«ãå¯è½ãªéããæ¢åã®ã³ã³ãã¼ãã³ããå®è£
ã¯ä½¿ãåãããããã§ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="(å·¦)æ¹ååã®ã¨ã©ã¼è¡¨ç¤ºã(å³)æå¾
ããã¨ã©ã¼ç»é¢"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tera_ny/20241113/20241113121752.png" width="1200" height="902" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>(å·¦)æ¹ååã®ã¨ã©ã¼è¡¨ç¤ºã(å³)æå¾
ããã¨ã©ã¼ç»é¢</figcaption></figure></p>
<p>ãã ãæ¢åã®ãã©ãããã«ã¼ã¯ä½¿ãåãã«ã¯å°ã
é£ããå®è£
ã«ãªã£ã¦ãã¾ããããã¨ãã°ããã¤ã¹å
ã®åçã«ã¢ã¯ã»ã¹ããããã®æ¨©éããªã¯ã¨ã¹ããã<code>PHPhotoLibrary.requestAuthorization</code>ã¯ã«ã¡ã©ã¹ã¯ãªã¼ã³ã表示ãã¦ããã¿ã¤ãã³ã°ã§å®è¡ãã¦ãããã権éããªãæã¯å°ç¨ã®ã¨ã©ã¼ã³ã³ãã¼ãã³ãããã©ãããã«ã¼ã§è¡¨ç¤ºããã®ã§ã¯ãªããUIAlertControllerãå©ç¨ããã¨ã©ã¼è¡¨ç¤ºãè¡ã£ã¦ãã¾ããããã®ã¾ã¾ç»é¢ãå
¥ãæ¿ãã¦ãã¾ãã¨ããã©ãããã«ã¼ã®ã¢ã¯ã»ã¹ãæå¦ããã¨ãã«ã¡ã©ã¹ã¯ãªã¼ã³ã¸ã®å°ç·ããªããªã£ã¦ãã¾ãä½é¨ãæªãã§ããã¨ã©ã¼ç»é¢ã®è¿½å ãã¢ã¯ã»ã¹æ¨©éããªã¯ã¨ã¹ãããã¿ã¤ãã³ã°èª¿æ´ãªã©ãæ°ããCoordinatorãå®ç¾©ããåã«ãã©ãããã«ã¼èªä½ã®æ©è½è¿½å ãå¿
è¦ã ã¨ãããã¨ããããã¾ããã</p>
<p>å°ããã¤åãçµãã¹ãã¿ã¹ã¯ãæ確ã«ãªããåªå
é ä½ãã¾ãè¦ãã¦ãã¾ãããæ°ããCoordinatorã追å ããåã«ãã©ãããã«ã¼ã®æ©è½è¿½å ãé²ããå¿
è¦ãããã¾ããããã©ãããã«ã¼ã«ã«ã¡ã©ã»ã«ã追å ãããã¨ã©ã¼ç»é¢ã追å ããã¨ãã£ãå¤æ´ã¯ããããç¬ç«ããã¿ã¹ã¯ã¨ãã¦é²ãããã¨ãã§ãã¾ããã¾ããæ°ããCoordinatorã追å ããã®ãFeatureToggleãå©ç¨ãããã¨ã§ã¦ã¼ã¶ã¼ã®ç®ã«è§¦ãããã¨ãªãéçºãé²ãããããã¨ããããã¾ããã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tera_ny/20241113/20241113120551.png" width="1200" height="821" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ããã¾ã§èª¿æ»æéå«ãã¦3hç¨åº¦ã§æ´çãè¨è¨ããããã£ã¨ããç¨åº¦æ£ç¢ºã«å·¥æ°ãè¦ç©ãããã¨ãã§ãã¾ãããããã§ä»Epicã¤ã·ã¥ã¼ã¨ã®åªå
é ä½ã決ãã¿ã¹ã¯ã¨ãã¦é²ãããã¨ãã§ããããã«ãªãã¾ãã</p>
<h2 id="ã¾ã¨ã">ã¾ã¨ã</h2>
<p>ãã©ãããã«ã¼æ¹åã¿ã¹ã¯ãä¾ã«ã¿ã¹ã¯ã®å解ã¨ãªãªã¼ã¹ãè·¨ãã æ©è½éçºã®äºä¾ã«ã¤ãã¦ãç´¹ä»ãã¾ãããç¹ã«ã大ããªã¿ã¹ã¯ãç´°ããå解ããããã«ã¢ãã¤ã«ç¹æã®ãªãªã¼ã¹å¶ç´ãèæ
®ãã対å¿ãå¿
è¦ãªç´°ããã¿ã¹ã¯ããªã¹ãã¢ããããé²ãæ¹ã¯ä»ã®ã¿ã¹ã¯ã«ãå¿ç¨ã§ããã¨æãã¾ãã</p>
<p>ã¾ããä»åã¯é·ç§»å
ã®ã³ã³ããã¼ã«ã«Feature Toggleãæ´»ç¨ããæ¯è¼ç綺éºãªè¨è¨ãå®ç¾ãã¾ããããFeature Toggleã®éç¨ã«ã¯æ³¨æç¹ãåå¨ãã¾ããå®éã«ãFeature Toggleã使ã£ãã¢ããã¼ãããã¾ããããªãã£ãã±ã¼ã¹ãããã¤ãçµé¨ãã¦ãã¾ããæ©ä¼ãããã°ãFeature Toggleåä½ã®ã話ãã§ããã°ã¨æãã¾ãã</p>
<p>ãã®è¨äºã§ç´¹ä»ããæ¹æ³ãèãæ¹ããçããã®ä»å¾ã®ã¢ããªã±ã¼ã·ã§ã³éçºã«ããã¦ä½ãããã®åèã«ãªãã°å¹¸ãã§ãã</p>
<div class="footnote">
<p class="footnote"><a href="#fn-80add568" id="f-80add568" name="f-80add568" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ã¬ã·ãäºæ¥é¨ã§ã¯1ã¹ããªã³ããä¸é±éã§åãã¦ããããé±æ¬¡ãªãªã¼ã¹ãè¡ã£ã¦ãã¾ã</span></p>
<p class="footnote"><a href="#fn-4fa7bcab" id="f-4fa7bcab" name="f-4fa7bcab" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">ä¸è¬çã«ã¯Feature Flagã¨ãå¼ã°ãã¦ãã(<a href="https://martinfowler.com/articles/feature-toggles.html">https://martinfowler.com/articles/feature-toggles.html</a></span></p>
<p class="footnote"><a href="#fn-d7238a0c" id="f-d7238a0c" name="f-d7238a0c" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://firebase.google.com/products/remote-config?hl=ja">https://firebase.google.com/products/remote-config?hl=ja</a></span></p>
</div>
tera_ny
ã¡ãã£ã¨è¤éãªãµã¤ããã¼ãHotwireã§ç°¡åã«ä½ããã
hatenablog://entry/6802418398303359771
2024-11-13T13:00:00+09:00
2024-11-13T13:00:00+09:00 ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ãããã¯ãéçºã°ã«ã¼ãã®æ¸¡éï¼@taso0096ï¼ã§ãã ã¯ãã¯ãããã¯æè¿ãOne Experienceã¨ããããã¸ã§ã¯ãã«ãã£ã¦æ¥æ¬çã¨ã°ãã¼ãã«çã®ã·ã¹ãã ãçµ±åããã¾ããã ã©ã¡ãã®ã·ã¹ãã ãRailsã§å®è£
ããã¦ããã¨ããç¹ã¯åãã§ãããçµ±åå
ã¨ãªã£ãã°ãã¼ãã«çã§ã¯Hotwireã使ããã¦ãã¾ãã*1ããã®ãããOne Experienceé¢é£ã®éçºã§ã¯Hotwireãç©æ¥µçã«æ´»ç¨ããã¦ãã¾ããæ¬è¨äºã§ã¯ãããªHotwireã®å¤ãã®æ©è½ã使ããããã¹ã¯ãããçã®ãµã¤ããã¼ã«ã¤ãã¦ãç´¹ä»ãã¾ãã ãã¹ã¯ãããçã§è¡¨ç¤ºããããµã¤ããã¼ ã¡ãã£ã¨è¤éãªãµã¤ããã¼ â¦
<p>ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ãããã¯ãéçºã°ã«ã¼ãã®æ¸¡éï¼<a href="https://x.com/taso0096">@taso0096</a>ï¼ã§ãã
ã¯ãã¯ãããã¯æè¿ãOne Experienceã¨ããããã¸ã§ã¯ãã«ãã£ã¦æ¥æ¬çã¨ã°ãã¼ãã«çã®ã·ã¹ãã ãçµ±åããã¾ããã
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2024%2F10%2F10%2F105832" title="æ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ãã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe>
ã©ã¡ãã®ã·ã¹ãã ãRailsã§å®è£
ããã¦ããã¨ããç¹ã¯åãã§ãããçµ±åå
ã¨ãªã£ãã°ãã¼ãã«çã§ã¯Hotwireã使ããã¦ãã¾ãã<a href="#f-15f87afa" id="fn-15f87afa" name="fn-15f87afa" title="æ¥æ¬çã®ã¹ãã¼ããã©ã³Webã®ä¸»è¦ãã¼ã¸ã§ã¯Next.jsãæ¡ç¨ããã¦ãã¾ããï¼ https://techlife.cookpad.com/entry/2020/12/01/093000 ï¼">*1</a>ããã®ãããOne Experienceé¢é£ã®éçºã§ã¯Hotwireãç©æ¥µçã«æ´»ç¨ããã¦ãã¾ããæ¬è¨äºã§ã¯ãããªHotwireã®å¤ãã®æ©è½ã使ããããã¹ã¯ãããçã®ãµã¤ããã¼ã«ã¤ãã¦ãç´¹ä»ãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="ãã¹ã¯ãããçã§è¡¨ç¤ºããããµã¤ããã¼"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taso0096/20241112/20241112132100.png" width="1200" height="689" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ãã¹ã¯ãããçã§è¡¨ç¤ºããããµã¤ããã¼</figcaption></figure></p>
<h1 id="ã¡ãã£ã¨è¤éãªãµã¤ããã¼">ã¡ãã£ã¨è¤éãªãµã¤ããã¼</h1>
<p>One Experienceã«ä¼´ããã°ãã¼ãã«çã«ãã¨ãã¨åå¨ããUIã®ã¾ã¾ç§»è¡ããã®ã§ã¯ãªããããã¤ãç»é¢æ§æã®å¤æ´ãå
¥ããäºã«ãªãã¾ãããç¹ã«ãã¹ã¯ãããçã«ããã¦ã¯ãèªåã®ã³ã³ãã³ãã«ããç´ æ©ãã¢ã¯ã»ã¹ã§ããããã«ããããã«ãµã¤ããã¼ã®å°å
¥ã決ã¾ãã¾ããã</p>
<p>ãã®ãµã¤ããã¼ã§ã¯ãä¸è¬çãªããã²ã¼ã·ã§ã³ã¡ãã¥ã¼ã®ã»ãã«ãããããã¨å¼ã°ããã³ã³ãã³ãã表示ãã¦ãã¾ãããããããã¨ã¯ã¦ã¼ã¶ã®ä¿åã¬ã·ããæ稿ã¬ã·ããªã©ãæ´çããããã®æ©è½ã§ããã¾ãããããããã«ä¿åããã¬ã·ãã¯ã¦ã¼ã¶ã¼ããã®æã§ããã©ã«ããä½æãã¦åé¡ã§ãã¾ãããã®ã¨ããã¬ã·ãããã©ã«ãã®æ°ãå¤ãã¦ã¼ã¶ã¼ããã§ãå
åç´ æ©ã表示ããããã§ããæ´ã«ããµã¤ããã¼ã®å¤ã§ã¬ã·ããä¿åãããã¨ãããªãã¼ãããã¨ããµã¤ããã¼ã®ä¸ã®è¡¨ç¤ºã追å¾ããå¿
è¦ãããã¾ãã以ä¸ãæ´çããã¨ãä½ããããã®ã¯ä»¥ä¸ã®ãããªãã®ã¨ãããã¨ã«ãªãã¾ãã</p>
<ul>
<li>ã³ã³ãã³ããå¤ãèªã¿è¾¼ã¿ã«æéããããå ´åãèæ
®ãã¦éåæã§èªã¿è¾¼ã</li>
<li>ãã©ã«ããå¤ãå ´åã§ãå
¨ã¦ã®ãã©ã«ããèªã¿è¾¼ãã</li>
<li>ã¬ã·ãã®ä¿åããã©ã«ãã®ä½æãªã©ã®æä½æã«è¡¨ç¤ºãåæãã</li>
</ul>
<p>ãããã®è¦ä»¶ã¯Hotwireã使ãã°ç°¡åã«å®è£
ãããã¨ãã§ãã¾ããããã§ã¯Hotwireã®åæ©è½ã軽ã説æãã¤ã¤ãããããã©ããã£ãå®è£
ãããã®ããç´¹ä»ãã¾ãã</p>
<h1 id="Turbo-Frames">Turbo Frames</h1>
<p>Turbo Framesã¨ã¯ãã¼ã¸å
¨ä½ããªãã¼ãããã«é¨åçãªæ´æ°ãå¯è½ã«ããããã®æ©è½ã§ããé¨åæ´æ°ãããç®æãTurbo Framesã®ã¿ã°ã§å²ããã¨ã§ããã®ã¿ã°å
ã®é¨åæ´æ°ãè¡ããã¾ããã¾ããiframeã®ããã«URLãæå®ãããã¨ã§å
¨ãå¥ã®ãã¼ã¸ãåãè¾¼ããã¨ãå¯è½ã§ãããã®å ´åã¯ã¿ã°ã®ä¸èº«ãæåã«ã¬ã³ããªã³ã°ããããã®å¾ã«éåæã§å¥ã®ãã¼ã¸ãèªã¿è¾¼ã¾ãã¾ãã</p>
<p>ä»åå®è£
ãããµã¤ããã¼ã§ã¯ãéåæã§ã³ã³ãã³ããèªã¿è¾¼ãããã«Turbo Framesãå©ç¨ãã¾ããããã®ããã«Turbo Framesã§è¡¨ç¤ºãããå°ç¨ãã¼ã¸ãæ°è¦ä½æããå³ã®èµ¤æ å
ã§èªã¿è¾¼ãããã«ãã¾ããããã®èµ¤æ ã®é¨åã§ã¯ãã¼ãã£ã³ã°ãæåã«è¡¨ç¤ºããããã¼ã¸ã®èªã¿è¾¼ã¿å¾ã«éåæã§ã³ã³ãã³ããèªã¿è¾¼ã¾ãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="Turbo Framesã®ç¯å²"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taso0096/20241112/20241112132506.png" width="1200" height="689" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Turbo Framesã®ç¯å²</figcaption></figure></p>
<p>å¤ãã®å ´åãRailså´ã§å®è£
ãã¦ããæ¢åã®ãã¼ã¸ã®ãã¸ãã¯ãªã©ããã®ã¾ã¾æµç¨ãã¦ç°¡åã«åãè¾¼ããã¨ããã®ãTurbo Framesã®ã¡ãªãããã¨æãã¾ããä¸æ¹ããã®ä¾ã§ã¯æ°è¦ãã¼ã¸ãããããä½æãã¾ãããããã¯ãã©ã«ãã®ãã¼ã¸ãã¼ã·ã§ã³å®è£
ãã·ã³ãã«ã«ããããã§ãStimulus ã§å®è£
ãã¦ãã¾ãã詳細ã¯å¾ã»ã©è§£èª¬ãã¾ãã</p>
<p>ãªããTurbo Framesã¯lazy loadingãå¯è½ã§ãããä»åãè¨å®ãã¦ãã¾ãããµã¤ããã¼ã¯ãã¹ã¯ãããçã§ã¯ã¹ã¯ãã¼ã«ä½ç½®ãªã©ã«é¢ä¿ãªã常ã«è¡¨ç¤ºããããã®ã§ãããã¹ããçã ã¨ããã§ã¯ãªãããã§ãã</p>
<h2 id="ãã¼ã¸é·ç§»ã«ä¼´ããªã»ãã">ãã¼ã¸é·ç§»ã«ä¼´ããªã»ãã</h2>
<p>Turbo Framesã«ãã£ã¦éåæã§ã®ã³ã³ãã³ãã®åå¾ãå®ç¾ã§ãã¾ããããããããã®ã¾ã¾ã§ã¯ãã¼ã¸é·ç§»ãããã³ã«ã³ã³ãã³ãã®ååå¾ãè¡ããã¦ãã¾ãã¾ããå ´åã«ãã£ã¦ã¯ããã§ãåé¡ãªãã§ãããä»åã¯ãã¼ã¸ãã¼ã·ã§ã³ã«ãã£ã¦èªã¿è¾¼ã¾ãããã©ã«ãã®æ
å ±ããªã»ããããããã¨ãé¿ãããã¨èãã¾ãããããã§ãªãã¨ããã¼ã¸é·ç§»ãã度ã«ãµã¤ããã¼ããã¼ãã£ã³ã°ã«ãã£ã¦ä¸ç¬ä½¿ããªããªããã¨ã¯ããªãä¸ä¾¿ã ã¨æãã¾ãã</p>
<p>ããã§data-turbo-permanentå±æ§ã¨ãããã®ã使ç¨ãã¾ãã<a href="#f-4d37b0c5" id="fn-4d37b0c5" name="fn-4d37b0c5" title="https://turbo.hotwired.dev/handbook/building#persisting-elements-across-page-loads">*2</a>ã以ä¸ã®ããã«ãã®å±æ§ãä»ä¸ãããDOMã¯ãã¼ã¸é·ç§»æã«ãDOMãç¶æããã¾ãã</p>
<pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">data</span><span class="synIdentifier">-turbo-permanent></span>sidebar<span class="synIdentifier"></</span><span class="synStatement">div</span><span class="synIdentifier">></span>
</pre>
<p>ããã«ããä¸åº¦èªã¿è¾¼ãã Turbo Framesã®ã³ã³ãã³ãã¯é常ã®ç»é¢é·ç§»ã§ã¯ãªã»ãããããªããªãã¾ãããããå度èªã¿è¾¼ãã«ã¯ãã©ã¦ã¶ã®ãªãã¼ããJavaScriptã«ããåèªã¿è¾¼ã¿ã®å¦çãå¿
è¦ã«ãªãã¾ãã</p>
<h2 id="ã¬ã¹ãã³ã¹">ã¬ã¹ãã³ã¹</h2>
<p>Turbo Framesãåå¾ããHTMLã¯ãã¼ã¸ã«åãè¾¼ã¾ããé¨åã ãã§ã¯ããã¾ãããlayoutãã³ãã¬ã¼ããã使ç¨ããã¾ãããActionViewã§ã¬ã³ããªã³ã°ããããã¼ã¸å
¨ä½ãã¬ã¹ãã³ã¹ã¨ãã¦è¿ããã¾ãããã®ãããActionViewã®ä¸ã®ä¸è¦ç´ ã®ã¿ãåãè¾¼ã¿ããã¨ãã£ãå ´åã¯ãã以å¤ã®ã¬ã¹ãã³ã¹ã¯ç ´æ£ããã¦ãã¾ãã¾ããããããã®ã¬ã¹ãã³ã¹ã¯ä»¥ä¸ã®ããã«ãªã£ã¦ããããã©ã¦ã¶å´ã®Hotwireã©ã³ã¿ã¤ã ã§è§£éããã¦ç»é¢ã«åãè¾¼ã¾ãã¾ãã</p>
<pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier"><</span><span class="synStatement">html</span><span class="synIdentifier">></span>
<span class="synIdentifier"><</span><span class="synStatement">head</span><span class="synIdentifier">></</span><span class="synStatement">head</span><span class="synIdentifier">></span>
<span class="synIdentifier"><</span><span class="synStatement">body</span><span class="synIdentifier">></span>
<span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier">></span>ç ´æ£ãããè¦ç´ <span class="synIdentifier"></</span><span class="synStatement">div</span><span class="synIdentifier">></span>
<span class="synIdentifier"><</span>turbo-<span class="synStatement">frame</span><span class="synIdentifier"> </span><span class="synType">id</span><span class="synIdentifier">=</span><span class="synConstant">"dom_id"</span><span class="synIdentifier">></span>åãè¾¼ã¿ããè¦ç´ <span class="synIdentifier"></</span>turbo-<span class="synStatement">frame</span><span class="synIdentifier">></span>
<span class="synIdentifier"></</span><span class="synStatement">body</span><span class="synIdentifier">></span>
<span class="synIdentifier"></</span><span class="synStatement">html</span><span class="synIdentifier">></span>
</pre>
<p>å¤å°ã®ç¡é§ã¯çãã¦ãã¾ãã¾ãããä¸ã§ãæ¸ããããã«æ¢åã®ãã¼ã¸ãã»ã¼ãã®ã¾ã¾æµç¨å¯è½ã§ããã¨ããã¡ãªããã®æ¹ã大ããã¨èãã¾ãããªããä»åã¯æ°è¦ãã¼ã¸ãä½æãActionViewå
¨ä½ãåãè¾¼ã¾ããå½¢ã«ãããããããããç ´æ£ãããè¦ç´ ã¯åå¨ãã¾ããã</p>
<h1 id="Turbo-Streams">Turbo Streams</h1>
<p>Turbo Streamsã¨ã¯ãªã¢ã«ã¿ã¤ã ã§ã®ãã¼ã¿æ´æ°ãç°¡åã«ããããã®æ©è½ã§ãããã¼ã¸ã«å¯¾ãã¦DOMã®è¿½å ã»å¤æ´ã»åé¤ãªã©ãå¯è½ã§ãããè¤æ°ç®æãåæã«æ´æ°ãããã¨ãã§ãã¾ããä»åã¯ã¬ã·ããæ°è¦ã§ä¿åããéã®ã¬ã·ãæ°ã®æ´æ°ãããã©ã«ãèªä½ã®ç·¨éãåæ ããããã«ä½¿ç¨ãã¾ããã</p>
<h2 id="ã¬ã¹ãã³ã¹-1">ã¬ã¹ãã³ã¹</h2>
<p>Turbo Framesã¨éããTurbo Streamsã§ã¯ãã¼ã¸å
¨ä½ã§ã¯ãªããå·®åã®ã¿ããµã¼ãã¼ããã¬ã¹ãã³ã¹ãã¾ããDOMãã©ã®ããã«æ±ããã«ã¤ãã¦ã¯Turbo Streamsã®ã¢ã¯ã·ã§ã³ã«ãã£ã¦æ示ããã¾ããä¾ãã°ã¦ã¼ã¶ãæ°è¦ã§ã¬ã·ãããã©ã«ãã«è¿½å ããå ´åãèãã¾ãããã®å ´åã¯ç»é¢ã®èµ¤æ é¨åãå
¨ã¦æ´æ°ããã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="Turbo Streamsã«ãã£ã¦æ´æ°ãããè¦ç´ "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taso0096/20241112/20241112133016.png" width="1200" height="690" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Turbo Streamsã«ãã£ã¦æ´æ°ãããè¦ç´ </figcaption></figure></p>
<p>ãã®æã«è¿ãããã¬ã¹ãã³ã¹ã¯ãããã以ä¸ã®ããã«ãªãã¾ãã</p>
<pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier"><</span>turbo-stream<span class="synIdentifier"> </span><span class="synType">action</span><span class="synIdentifier">=</span><span class="synConstant">"replace"</span><span class="synIdentifier"> </span><span class="synType">target</span><span class="synIdentifier">=</span><span class="synConstant">"dom_id"</span><span class="synIdentifier">></span>ä¿åãã¿ã³ã®HTML<span class="synIdentifier"></</span>turbo-stream<span class="synIdentifier">></span>
<span class="synIdentifier"><</span>turbo-stream<span class="synIdentifier"> </span><span class="synType">action</span><span class="synIdentifier">=</span><span class="synConstant">"replace"</span><span class="synIdentifier"> </span><span class="synType">target</span><span class="synIdentifier">=</span><span class="synConstant">"dom_id"</span><span class="synIdentifier">></span>ãããããã¦ã³ã®HTML<span class="synIdentifier"></</span>turbo-stream<span class="synIdentifier">></span>
<span class="synIdentifier"><</span>turbo-stream<span class="synIdentifier"> </span><span class="synType">action</span><span class="synIdentifier">=</span><span class="synConstant">"replace"</span><span class="synIdentifier"> </span><span class="synType">target</span><span class="synIdentifier">=</span><span class="synConstant">"dom_id"</span><span class="synIdentifier">></span>ãããããã®ããã¹ã¦ãã®HTML<span class="synIdentifier"></</span>turbo-stream<span class="synIdentifier">></span>
<span class="synIdentifier"><</span>turbo-stream<span class="synIdentifier"> </span><span class="synType">action</span><span class="synIdentifier">=</span><span class="synConstant">"replace"</span><span class="synIdentifier"> </span><span class="synType">target</span><span class="synIdentifier">=</span><span class="synConstant">"dom_id"</span><span class="synIdentifier">></span>ãããããã®ãä¿åæ¸ã¿ãã®HTML<span class="synIdentifier"></</span>turbo-stream<span class="synIdentifier">></span>
<span class="synIdentifier"><</span>turbo-stream<span class="synIdentifier"> </span><span class="synType">action</span><span class="synIdentifier">=</span><span class="synConstant">"prepend"</span><span class="synIdentifier"> </span><span class="synType">target</span><span class="synIdentifier">=</span><span class="synConstant">"dom_id"</span><span class="synIdentifier">></span>ãããããã®ãæ°è¦ãã©ã«ããã®HTML<span class="synIdentifier"></</span>turbo-stream<span class="synIdentifier">></span>
<span class="synIdentifier"><</span>turbo-stream<span class="synIdentifier"> </span><span class="synType">action</span><span class="synIdentifier">=</span><span class="synConstant">"replace"</span><span class="synIdentifier"> </span><span class="synType">target</span><span class="synIdentifier">=</span><span class="synConstant">"dom_id"</span><span class="synIdentifier">></span>éç¥ã®HTML<span class="synIdentifier"></</span>turbo-stream<span class="synIdentifier">></span>
</pre>
<p>ãã®ã¬ã¹ãã³ã¹ãHotwireã©ã³ã¿ã¤ã ã解éãã¦ç»é¢ã®æ´æ°ãè¡ããã¾ãã</p>
<h2 id="ãªãã¤ã¬ã¯ã">ãªãã¤ã¬ã¯ã</h2>
<p>ãµã¤ããã¼ã®åæãå®è£
ããã«ããã£ã¦ãããã¾ã§ã¯ç»é¢ã®æ´æ°ãä¸è¦ã ã£ãããã¤ãã®æ¢åã®ãªã¯ã¨ã¹ãã®formatãHTMLããTurbo Streamsã«ç½®ãæããå¿
è¦ãããã¾ãããåºæ¬çã«åé¡ãªãç½®ãæãããã¨ãå¯è½ã§ãããããªãã¤ã¬ã¯ããå¿
è¦ãªå ´åã¯å°ã工夫ããå¿
è¦ãããã¾ããã</p>
<p>ä¾ãã°ã¦ã¼ã¶ããã©ã«ãã®ãã¼ã¸ãããã®ãã©ã«ããåé¤ããå ´åãèãã¾ãããã®å ´åã¯ãµã¤ããã¼ã®ããããããã対象ã®ãã©ã«ãã®DOMãåé¤ããä¸ã§ãããããã®ãããã«ãªãã¤ã¬ã¯ãããã¨ããä»æ§ã«ãªã£ã¦ãã¾ããããã¯ããã©ã«ãã®ã¢ã¯ã·ã§ã³ã§ã¯å¯¾å¿ã§ããªãããã以ä¸ã®ãããªãªãã¤ã¬ã¯ãã®ããã®ã«ã¹ã¿ã ã¢ã¯ã·ã§ã³ã追å ãããã¨ã§å¯¾å¿ãã¾ããã</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink>Turbo<span class="synStatement">.</span>StreamActions<span class="synStatement">.</span>redirect <span class="synStatement">=</span> <span class="synStatement">function</span> <span class="synSpecial">()</span> <span class="synSpecial">{</span>
Turbo<span class="synStatement">.</span><span class="synIdentifier">visit</span><span class="synSpecial">(</span><span class="synStatement">this.</span>target<span class="synSpecial">)</span>
<span class="synSpecial">}</span>
</pre>
<p>Viewå´ã§ã¯é常ã®ã¢ã¯ã·ã§ã³ã¨ã»ã¨ãã©åãããã«å¼ã³åºããã¨ãå¯è½ã§ãã</p>
<pre class="code" data-lang="" data-unlink><%= turbo_stream.action :redirect, path %></pre>
<p>å®éã®ã¬ã¹ãã³ã¹ã¯ä»¥ä¸ã®ããã«ãªããHotwireã©ã³ã¿ã¤ã ã§è§£éããã¦ãªãã¤ã¬ã¯ããå®è¡ããã¾ãã</p>
<pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier"><</span>turbo-stream<span class="synIdentifier"> </span><span class="synType">action</span><span class="synIdentifier">=</span><span class="synConstant">"redirect"</span><span class="synIdentifier"> </span><span class="synType">target</span><span class="synIdentifier">=</span><span class="synConstant">"path"</span><span class="synIdentifier">></</span>turbo-stream<span class="synIdentifier">></span>
</pre>
<p>ã«ã¹ã¿ã ã¢ã¯ã·ã§ã³ã¯ä»»æã®JavaScriptãç°¡åã«å®è¡ã§ããããããªã便å©ãªæ©è½ã§ããããããã ããã¨è¨ã£ã¦ããã©ã«ãã¢ã¯ã·ã§ã³ã§å¯¾å¦å¯è½ãªæ©è½ã«å¯¾ãã¦ã«ã¹ã¿ã ã¢ã¯ã·ã§ã³ãä½æãã¦ãã¾ãã¨ã³ã¼ãã®ä¸è²«æ§ã失ããã¦ãã¾ããã注æãå¿
è¦ã§ãã</p>
<h1 id="Stimulus">Stimulus</h1>
<p>Stimulusã¨ã¯HTMLã¨JavaScriptãé©åã«åãé¢ãã¦æ¸ãããã®æ çµã¿ã§ããããã«ãã£ã¦HTMLã ããè¦ãæã«ã©ããªæåãããã®ãããããããããããã³ã¼ãèªä½ã®åå©ç¨æ§ãé«ããã¡ãªãããããã¾ããCSSãHTMLã®classå±æ§ãä»ãã¦ç´ã¥ãã¦ããããã«ãStimulusã§ã¯HTMLã®ãã¼ã¿å±æ§ãä»ãã¦ä»»æã®JavaScriptã«ããæä½ãå¯è½ã«ãã¾ãããã®JavaScriptã¯controllerã¨ããåä½ã§åãããã¦ãããcontrollerã®ã¡ã½ãããDOMã®ã©ã¤ããµã¤ã¯ã«ãã¤ãã³ããããã¯ã«å®è¡ããã¨ããã®ã主ãªæ©è½ã§ããããã«ãã£ã¦åå©ç¨ããããJavaScriptã«ãªããããªä»çµã¿ã«ãªã£ã¦ãã¾ããä»åã¯ãã¼ã¸ãã¼ã·ã§ã³ã®ããã®ç¡éã¹ã¯ãã¼ã«ã¨ã¢ã¯ãã£ãç¶æ
ã®æ´æ°ã®ããã«ä½¿ããã¾ããã</p>
<h2 id="ç¡éã¹ã¯ãã¼ã«">ç¡éã¹ã¯ãã¼ã«</h2>
<p>Turbo Framesã®ã»ã¯ã·ã§ã³ã§ãã©ã«ãã®ãã¼ã¸ãã¼ã·ã§ã³ã«ã¤ãã¦è§£èª¬ãã¾ãããããã使ããããããããã«ç¡éã¹ã¯ãã¼ã«ã«å¯¾å¿ãã¾ããã°ãã¼ãã«çã«ã¯å
ã
ç¡éã¹ã¯ãã¼ã«ç¨ã®controllerãå®è£
ããã¦ããããããããã®ã¾ã¾ä½¿ç¨ãã¾ãããå®è£
ã¨ãã¦ã¯ã·ã³ãã«ã§ãã¹ã¯ãã¼ã«ã¤ãã³ããç£è¦ãé¾å¤ãè¶
ããã次ã®ãã¼ã¸ãéåæéä¿¡ã§èªã¿è¾¼ãã¨ãããã®ã§ããStimulusã®targetsã¨valuesã®æ©è½ã使ã£ã¦ãªã¹ãã®ä¸èº«ã¯ã©ã®DOMãªã®ãã次ã®ãã¼ã¸ã®URLã¯ä½ãªã®ãã¨ãã£ãæ
å ±ã管çãã¦ãã¾ãã</p>
<p>ãªããHotwireã«ãããç¡éã¹ã¯ãã¼ã«ã®å®è£
ã¨ãã¦Turbo Framesã®é
延èªã¿è¾¼ã¿ãæ´»ç¨ãããã®ãããã¾ãããã®å ´åã¯èªåã§ã¯JavaScriptãä¸åæ¸ããã«ç¡éã¹ã¯ãã¼ã«ã®å®è£
ãå¯è½ã§ãããã ããåç´ãªãã¼ã¸ãã¼ã·ã§ã³ã«controllerãç»é²ã ãããã°æ¸ãStimulusã¨æ¯è¼ããã¨ãViewã«å°ãæãå
¥ããå¿
è¦ãããç¹ã«ã¯æ³¨æãå¿
è¦ã§ããä»åã¯ä¾¿å©ã«ä½¿ããæ¢åå®è£
ããã£ãã®ã§Turbo Framesã«ããç¡éã¹ã¯ãã¼ã«ãããã¦æ¡ç¨ãããããªãã¨ã¯ãã¾ããã§ããã</p>
<h2 id="ã¢ã¯ãã£ãç¶æ
ã®æ´æ°">ã¢ã¯ãã£ãç¶æ
ã®æ´æ°</h2>
<p>Turbo Framesã®ã»ã¯ã·ã§ã³ã§èª¬æããããã«ããµã¤ããã¼ã¯ç»é¢é·ç§»ã«ãããªã»ãããåé¿ããããã«data-turbo-permanentå±æ§ãæå®ããã¦ãã¾ããããããããã«ãã£ã¦ç¾å¨ã®ãã¼ã¸ã«å¿ãã¦ãããããã®ãªã³ã¯ã®ã¢ã¯ãã£ãç¶æ
ãæ´æ°ããæ©è½ãå£ãã¦ãã¾ãã¾ãããããã«å¯¾å¦ããã«ã¯JavaScriptã«ãã£ã¦ãã¼ã¸é·ç§»ãæ¤åºãã¦ã¢ã¯ãã£ãç¶æ
ãæ´æ°ããå¿
è¦ãããã¾ããä½æããcontrollerã®ä¸èº«èªä½ã¯åç´ãªãã®ã§ãããã¡ã½ãããå¼ã³åºãããã®ã¤ãã³ãã®æå®ã ãå°ãç¹æ®ã«ãªã£ã¦ãã¾ããStimulusã§ã¯data-actionå±æ§ã使ã£ã¦ã©ã®ã¤ãã³ãã«ããã¯ãã¦ã¡ã½ãããå®è¡ãããæå®ãããã¨ãã§ãã¾ãããã®ã¨ããåºæ¬çã«ã¯å±æ§ãæå®ãããDOMã«å¯¾ããã¤ãã³ããåç
§ãã¾ããããã¼ã¸é·ç§»ã®ãããªã°ãã¼ãã«ã®ã¤ãã³ãããã¯ãããå ´åã¯<code>@document</code>ã®ãããªsuffixãæå®ãããã¨ã§å¯¾å¿ã§ãã¾ã<a href="#f-9a1db471" id="fn-9a1db471" name="fn-9a1db471" title="https://stimulus.hotwired.dev/reference/actions#global-events">*3</a>ã</p>
<pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">data</span><span class="synIdentifier">-</span><span class="synType">action</span><span class="synIdentifier">=</span><span class="synConstant">"turbo:visit@document->controller#method"</span><span class="synIdentifier">></</span><span class="synStatement">div</span><span class="synIdentifier">></span>
</pre>
<h1 id="ã¾ã¨ã">ã¾ã¨ã</h1>
<p>Hotwireãæ´»ç¨ããã¡ãã£ã¨è¤éãªãµã¤ããã¼ã®å®è£
ã«ã¤ãã¦ãç´¹ä»ãã¾ãããHotwireã®ä»çµã¿ãå©ç¨ãããã¨ã§ã¤ã³ã¿ã©ã¯ãã£ããªUIã®ããã®JavaScriptãã»ã¨ãã©æ¸ããã«ä¸»è¦ãªæ©è½ã®å®è£
ãã§ãããã¨æãã¾ããå人çã«ã¯å
ã
ã¯Next.jsãæ¸ãã¦ãããã¨ãããJavaScriptã¯å¥½ãã§ãããRailsãæ¸ãä¸ã§Hotwireã¯ããªãè¯ãã§ããä»çµã¿ã ã¨æãã¦ãã¾ããHotwireã¯ã¾ã 使ãå§ããã°ããã®æè¡ã§ãã®ã§ãæ°ããç¥è¦ãæºã¾ã£ããã¾ãå
±æãããã¨æãã¾ãã</p>
<div class="footnote">
<p class="footnote"><a href="#fn-15f87afa" id="f-15f87afa" name="f-15f87afa" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">æ¥æ¬çã®ã¹ãã¼ããã©ã³Webã®ä¸»è¦ãã¼ã¸ã§ã¯Next.jsãæ¡ç¨ããã¦ãã¾ããï¼ <a href="https://techlife.cookpad.com/entry/2020/12/01/093000">https://techlife.cookpad.com/entry/2020/12/01/093000</a> ï¼</span></p>
<p class="footnote"><a href="#fn-4d37b0c5" id="f-4d37b0c5" name="f-4d37b0c5" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://turbo.hotwired.dev/handbook/building#persisting-elements-across-page-loads">https://turbo.hotwired.dev/handbook/building#persisting-elements-across-page-loads</a></span></p>
<p class="footnote"><a href="#fn-9a1db471" id="f-9a1db471" name="f-9a1db471" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://stimulus.hotwired.dev/reference/actions#global-events">https://stimulus.hotwired.dev/reference/actions#global-events</a></span></p>
</div>
taso0096
Simple Custom Compose Layout
hatenablog://entry/6802418398301565839
2024-11-12T13:14:00+09:00
2024-11-12T13:14:00+09:00 ããã«ã¡ã¯ããã¦ã£ãªã¢ã ãã§ããã¯ãã¯ãããã®Androidã¨ã³ã¸ãã¢ã§ãã ç§ã®æ¥æ¬èªã¯ã¾ã ä¸æã§ã¯ãªãã®ã§ãããããè±èªã§æ¸ãã¾ãï¼ Self-introduction (èªå·±ç´¹ä») Hi, my name is William, I'm an Android Engineer from the Cookpad's recipe team. I was originally in the global recipe team in Bristol, Uk, but I'm rehired to join the recipe team in Japan instead last year â¦
<p>ããã«ã¡ã¯ããã¦ã£ãªã¢ã ãã§ããã¯ãã¯ãããã®Androidã¨ã³ã¸ãã¢ã§ãã
ç§ã®æ¥æ¬èªã¯ã¾ã ä¸æã§ã¯ãªãã®ã§ãããããè±èªã§æ¸ãã¾ãï¼</p>
<h3 id="Self-introduction-èªå·±ç´¹ä»">Self-introduction (èªå·±ç´¹ä»)</h3>
<p>Hi, my name is William, I'm an Android Engineer from the Cookpad's recipe team. I was originally in the global recipe team in Bristol, Uk, but I'm rehired to join the recipe team in Japan instead last year 2023.
It is my first time ever in my life to write a techblog, please forgive me for the messy and unorganised structures, ãããããé¡ããã¾ãï¼</p>
<h3 id="Statement">Statement</h3>
<p>If you have been reading our techblog recently, you must be aware of what is currently going on with Cookpad! If you haven't, then I'll summarise it in a paragraph.</p>
<blockquote><p>In the past, Cookpad recipe app operated separately in Japan, and rest of the world, Global. But now, weâve merged into one, and we call it 'One experience.'</p></blockquote>
<p>It was a long, challenging project, but also an exciting one! Looking back, what followed was the massive backlog of tasks created by the merger. There were so many things we wanted to accomplish, but we were often unable to due to time constraints, shifting priorities, or the limitations of the existing legacy architecture.</p>
<p>One day, if I still remember what it was all about, Iâll try to write about them. For now, I'll just write about something we face on a daily basis instead!</p>
<h3 id="Recent-Challenges-We-Faced-in-Android-Development">Recent Challenges We Faced in Android Development</h3>
<p>Have you ever received a UI/UX requirement that isn't natively supported out of the box? One such requirement we received recently is:</p>
<blockquote><p>Hide the ingredients section if there's not enough vertical space in the recipe card to prevent the cards in the list from expanding due to a long title or smaller screen sizes, which could result in an inconsistent UI.</p></blockquote>
<p><figure class="figure-image figure-image-fotolife" title="Recipe Card"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kinyeutan/20241110/20241110215219.png" width="962" height="426" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Recipe Card</figcaption></figure></p>
<p>Above is an existing Compose component that we have currently in <code>Your Collection</code> tab screen (also known as <code>ããã</code> tab screen when you switch to JP region in the app). We have a different version of this component depending on the recipe type but ultimately they all have the same content:</p>
<p><figure class="figure-image figure-image-fotolife" title="Recipe card components"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kinyeutan/20241110/20241110222712.png" width="962" height="426" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Recipe card components</figcaption></figure></p>
<ol>
<li>Recipe title</li>
<li>Recipe ingredient list</li>
<li>Recipe author information</li>
<li>Recipe actionable buttons</li>
<li>Recipe image withãå
¬éæ¸ã¿ãlabel, this label appears when it is a published recipe belonging to the user.</li>
</ol>
<h3 id="The-Problem">The Problem</h3>
<p><figure class="figure-image figure-image-fotolife" title="When there's not enough space"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kinyeutan/20241110/20241110223644.png" width="794" height="352" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>When there's not enough space</figcaption></figure></p>
<p>As you can see, the ingredient list section overlaps with the author information section when the recipe title spans two lines. This happens for a few reasons.</p>
<p>One of them is that the recipe image needs to be in a 3:4 aspect ratio; another is that the actionable buttons need to be positioned at the bottom of the card; and, additionally, the recipe height is now set to <code>160.dp</code>.</p>
<p>So, thereâs a <code>Modifier.weight(1f)</code> and a <code>Modifier.height(160.dp)</code> applied somewhere...</p>
<p>This will be more likely to happen when the user enlarges their device's default font size or screen size, which will cause the ingredient list (2) to overlap with other components if there isnât enough space for it.</p>
<h3 id="How-to-fix-it">How to fix it</h3>
<p>Now we need to make the ingredient list (2) disappear if there isnât enough space for it, let's dissect the component!</p>
<p><figure class="figure-image figure-image-fotolife" title="Normal recipe card component"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kinyeutan/20241110/20241110232900.png" width="1182" height="426" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Normal recipe card component</figcaption></figure></p>
<p>From the figure above, we identify the three components of the recipe card: the top, bottom, and optional center (ignoring the recipe image).</p>
<p>Logically, after the top and bottom are drawn, we need to calculate the remaining vertical space and then determine the required height to display the ingredient list.</p>
<p>After researching it for a while, it was actually easier than I originally thought. It's quite straight forward after going through Android documentation here
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.android.com%2Fdevelop%2Fui%2Fcompose%2Flayouts%2Fcustom" title="Custom layouts  | Jetpack Compose  | Android Developers" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://developer.android.com/develop/ui/compose/layouts/custom">developer.android.com</a></cite></p>
<h3 id="Its-time-for-the-classic-hello-world-testing">It's time for the classic hello world testing</h3>
<p>I decided to try using the <code>Layout</code> Compose component to solve the problem above. Below is the composable preview of <code>hello world</code> test I used to experiment with the composable.</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kinyeutan/20241111/20241111000837.png" width="296" height="158" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<pre class="code" data-lang="" data-unlink>Layout(
modifier = modifier,
content = {
Text("hello world")
},
) { measurables, constraints ->
val placables = measurables.map { it.measure(constraints) }
val text = placables[0]
layout(width = constraints.maxWidth, height = constraints.maxHeight) {
text.placeRelative(0, 0)
}
}</pre>
<p><code>val placables</code> is a list of components that need to be placed in the layout, determined by what we pass into the content parameter above. Another example will be:</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kinyeutan/20241111/20241111000908.png" width="390" height="200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<pre class="code" data-lang="" data-unlink>Layout(
modifier = modifier,
content = {
Text("hello world")
Text("goodbye world")
},
) { measurables, constraints ->
val placables = measurables.map { it.measure(constraints) }
val helloworld = placables[0]
val goodbyeworld = placables[1]
layout(width = constraints.maxWidth, height = constraints.maxHeight) {
helloworld.placeRelative(0, 0)
goodbyeworld.placeRelative(0, helloworld.height)
}
}</pre>
<p>Now, <code>val placables</code> is a list containing the <code>Text("hello world")</code> node and the <code>Text("goodbye world")</code> node.</p>
<p>When using <code>.placeRelative(x, y)</code> to position the node in the layout, the coordinates are relative to the current layout's (0, 0) point, which starts at the top-start corner on LTR devices and the top-end corner on RTL devices.</p>
<p>Alternatively, you can use <code>.place(x, y)</code> to position the node, but note that <code>.place(x, y)</code> ignores the RTL context, so it will always position the node relative to the top-left corner, regardless of whether the configuration is RTL or not.</p>
<h3 id="Implementation">Implementation</h3>
<p>So, after familiarising ourselves with <code>Layout</code>, we can now start implementing the custom composable. Since we identified that there are three parts in the composable, we call them top, center, and bottom:</p>
<pre class="code" data-lang="" data-unlink>Layout(
modifier = modifier,
content = {
Column {
top() // The top part will consist of the recipe title.
}
Column {
center() The center will contain the ingredient list.
}
Column {
bottom() // The bottom part will include the author information and the actionable buttons.
}
},
) { measurables, constraints -> {
...
}</pre>
<p>I wrapped them in <code>Column</code> as it's a column design and I can define the custom view composable parameter as <code>ColumnScope.() -> Unit</code>.</p>
<p>The next step is to place them in the layout</p>
<pre class="code" data-lang="" data-unlink>Layout(
modifier = modifier,
content = {...},
) { measurables, constraints -> {
val placables = measurables.map { it.measure(constraints) }
val topPlacable = placables[0]
val centerPlacable = placables[1]
val bottomPlacable = placables[2]
layout(width = constraints.maxWidth, height = constraints.maxHeight) {
topPlacable.place(0, 0)
bottomPlacable.place(0, constraints.maxHeight - bottomPlacable.height)
}
}</pre>
<p>The code above will position the top component at the top-start and the bottom component at the bottom-start. Once the top and bottom components are in place, we will need to measure the available vertical space to determine if there is enough room to place the center component, which is the ingredient list.</p>
<pre class="code" data-lang="" data-unlink>Layout(
modifier = modifier,
content = {...},
) { measurables, constraints -> {
val placables = measurables.map { it.measure(constraints) }
val topPlacable = placables[0]
val centerPlacable = placables[1]
val bottomPlacable = placables[2]
layout(width = constraints.maxWidth, height = constraints.maxHeight) {
topPlacable.place(0, 0)
bottomPlacable.place(0, constraints.maxHeight - bottomPlacable.height)
val availableVerticalHeight = constraints.maxHeight - topPlacable.measuredHeight - bottomPlacable.measuredHeight
if (centerPlacable.measuredHeight <= availableVerticalHeight) {
centerPlacable.place(0, topPlacable.measuredHeight)
}
}
}</pre>
<p>The steps to calculate the available height are as follows:</p>
<ol>
<li>First, we get the maximum height of the parent layout.</li>
<li>Using the parent layout's height, we subtract the measured height of the top component and the measured height of the bottom component.</li>
<li>With the remaining vertical height, we compare it to the center component's measured height and determine if there's enough space for it.</li>
<li>If there is enough space for the center component, we place it below the top component. Otherwise, we do nothing.</li>
</ol>
<p>With that in place, we can now use it in the recipe card composable and check if it works! The results are as follows as we tweak the device's font size and screen size:</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kinyeutan/20241111/20241111005817.png" width="1200" height="256" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Yay! It works, and now it's in production! It seems to be fine, as there haven't been any crash logs related to it, so that's good news... so far.</p>
<p>Below is the actual display of the custom view in my device after changing the default system font size from x1.0 to x1.3.</p>
<p><figure class="figure-image figure-image-fotolife" title="How it looks like in my Samsung S23 Ultra device with system font size enlarged by x1.3"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kinyeutan/20241111/20241111010158.jpg" width="560" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>How it looks like in my Samsung S23 Ultra device with system font size enlarged by x1.3</figcaption></figure></p>
<p>If the top component or the bottom component takes up more vertical space than is available, they will overlap. However, this depends on the specific feature using the layout. In our current use case, this is acceptable, since the top component is simply a recipe title with a maximum of two lines, and the bottom component is just author information (with a maximum of one line) and actionable buttons with fixed sizes. Therefore, we don't need to handle this edge case at this time, according to the YAGNI (You Aren't Gonna Need It) principle.</p>
<h3 id="The-final-code">The final code</h3>
<pre class="code" data-lang="" data-unlink>@Composable
fun ResponsiveColumn(
top: @Composable ColumnScope.() -> Unit,
center: @Composable ColumnScope.() -> Unit,
modifier: Modifier = Modifier,
bottom: @Composable ColumnScope.() -> Unit
) {
Layout(
modifier = modifier,
content = {
Column {
top()
}
Column {
center()
}
Column {
bottom()
}
},
) { measurables, constraints ->
val placables = measurables.map { it.measure(constraints) }
val topPlacable = placables[0]
val centerPlacable = placables[1]
val bottomPlacable = placables[2]
layout(width = constraints.maxWidth, height = constraints.maxHeight) {
topPlacable.placeRelative(0, 0)
bottomPlacable.place(0, constraints.maxHeight - bottomPlacable.height)
val availableVerticalHeight = constraints.maxHeight - topPlacable.measuredHeight - bottomPlacable.measuredHeight
if (centerPlacable.measuredHeight <= availableVerticalHeight) {
centerPlacable.placeRelative(0, topPlacable.measuredHeight)
}
}
}
}</pre>
<h3 id="Closing-statement">Closing statement</h3>
<p>Building your custom Composable layout is actually quite straightforward and fun, although I deliberately left out some explanation in the post such as the layout's measurables and constraints.. please do read the Android documentation for their explanation! You can also check out in the code too if you prefers that.</p>
<p>There's also another use case for a similar technique inside <code>Modifier.layout {}</code> when implementing a horizontally-scrolling, full-width carousel in a lazy grid layout with content padding. This ensures that the carousel can scroll past the content padding. Iâll write about that in the future if I have the opportunity to do so.</p>
<p>If you're interested in the recipe I used in this post, feel free to take a look at the link provided below! Although itâs in English, itâs quite simple and straightforward. It took me a lot of trial and error to get the taste as close as possible to the one I had in Sweden when I was working there.</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcookpad.com%2Fjp%2Fr%2F16442143" title="Fish soup (fisksoppa) ð¸ðª Recipe by William" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://cookpad.com/jp/r/16442143">cookpad.com</a></cite></p>
<p><span style="font-size: 80%">* Clicking the link should open your Cookpad app if it's installed, it should work even regardless of which Play Store / App Store you installed it from ^^</span></p>
<p>I hope to improve my writing and provide more interesting use cases in the future. Thank you very much for reading!</p>
kinyeutan
ã¢ãã¤ã«ã¢ããªã® One Experience
hatenablog://entry/6802418398302391771
2024-11-11T11:11:11+09:00
2024-11-11T11:17:13+09:00 ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ã§Androidã¢ããªéçºããã¦ããããã¾ã«ã大好ãã§ãã 好ããªã¤ã¸ã³ã«ã¼ãã¯è¡åºã¨è¿æ¾éå·¦è¡éãæè¿æ°ã«ãªãã«ã¼ãã¯æ¾å°¾èèã§ãã ãã®ããã°ã®æ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ããã¨ããè¨äºã§ãæ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããµã¼ãã¹ã®çµ±åãè¡ããããã¨ãããã¸ã§ã¯ãã®å称ã One Experience ã§ãã£ããã¨ã«ã¤ãã¦èª¬æãããã¾ããã ãã¡ãã One Experience ããã¸ã§ã¯ãã«ã¤ãã¦ã¯ Web ã ãã§ãªãã¢ãã¤ã«ã¢ããªã«ã¤ãã¦ãè¡ããã¦ãããç¾å¨æ¥æ¬åãã«ãªãªã¼ã¹ããã¦ããã¯ãã¯ãããã¢ããªã¯ãã¯ãã¯ãããããµã¼ãã¹ãå±éãã¦ãããã¹ã¦ã®â¦
<p>ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ã§Androidã¢ããªéçºããã¦ããããã¾ã«ã大好ãã§ãã<br/>
好ããªã¤ã¸ã³ã«ã¼ãã¯<a href="https://one-draw.jp/ijinden/cardlist/001/cardlist_img.html">è¡åº</a>ã¨<a href="https://one-draw.jp/ijinden/cardlist/003/cardlist_img.html">è¿æ¾éå·¦è¡é</a>ãæè¿æ°ã«ãªãã«ã¼ãã¯<a href="https://one-draw.jp/ijinden/cardlist/003/cardlist_img.html">æ¾å°¾èè</a>ã§ãã</p>
<p>ãã®ããã°ã®<a href="https://techlife.cookpad.com/entry/2024/10/10/105832">æ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ãã</a>ã¨ããè¨äºã§ãæ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããµã¼ãã¹ã®çµ±åãè¡ããããã¨ãããã¸ã§ã¯ãã®å称ã One Experience ã§ãã£ããã¨ã«ã¤ãã¦èª¬æãããã¾ããã<br/>
ãã¡ãã One Experience ããã¸ã§ã¯ãã«ã¤ãã¦ã¯ Web ã ãã§ãªãã¢ãã¤ã«ã¢ããªã«ã¤ãã¦ãè¡ããã¦ãããç¾å¨æ¥æ¬åãã«ãªãªã¼ã¹ããã¦ããã¯ãã¯ãããã¢ããªã¯ãã¯ãã¯ãããããµã¼ãã¹ãå±éãã¦ãããã¹ã¦ã®å°åã§ã®è¡¨ç¤ºã«å¯¾å¿ãã One Experienceçã®ã¢ããªã«ãªã£ã¦ãã¾ãã<br/>
ãã®è¨äºã§ã¯ãã¢ãã¤ã«ã¢ããªã® One Experience ã«ã¤ãã¦ãã©ã®ãããªä½æ¥ãè¡ãããã®ã大ã¾ãã«æ¦è¦ã説æãããã¨æãã¾ãã</p>
<h1 id="æ¦è¦">æ¦è¦</h1>
<p>åæã¨ãã¦ãã¯ãã¯ãããã§ã¯ä»¥åãã JPã¢ããªã¨ Globalã¢ããªã¨ãã2ã¤ã®ã¢ããªããªãªã¼ã¹ããã¦ãã¾ããã<br/>
ãã®2ã¤ã®ã¢ããªã¯å®å
¨ã«å¥ã®ã¢ããªã¨ãã¦éçºããã¦ãã¦ãã³ã¼ãã®å
±æçã¯ã»ã¼ 0% ãèªè¨¼ãAPIãªã©ããã¯ã¨ã³ãã®æ§æãã¾ã£ããç°ãªããã®ã§ããã<br/>
ä»åè¡ã£ãã¢ãã¤ã«ã¢ããªã® One Experience ã¨ã¯ãä¸è¨ã§ãã㨠Globalã¢ããªã®ã³ã¼ã㧠JPã¢ããªãä¸æ¸ããã¦ã¢ãããã¼ãããä½æ¥ã«ãªãã¾ãã<br/>
Globalã¢ããªã¨JPã¢ããªã¯ã©ã¡ããæ¢åã¦ã¼ã¶ã¼ããããããããããç¾æ®µéã§ã¯2ã¤ã®ã¢ããªã1ã¤ã«çµ±åãããã¨ã¯è¡ããã«ã1ã¤ã®ã³ã¼ãããªãã¸ããªãã2ã¤ã®ã¢ããªããªãªã¼ã¹ãã¦ãããã¨ã«ãã¦ãã¾ãã</p>
<p>ã²ã¨ã¤ã®ãªãã¸ããªããè¤æ°ã®ã¢ããªãé
ä¿¡ããã¨ããã®ã¯ Kindle ã¹ãã¢çã¢ããªã®é
ä¿¡ãAndroidTVã¢ããªã®é
ä¿¡ãªã©ã§ããããæ§æã§ãããããã«ãã£ã¦å¥ã®ã¢ããªãå®å
¨ã«ä¸æ¸ãããã¨ããã®ã¯ããªãçããã¨æãã¾ãã<br/>
ããã¾ã«ã大好ãã®Androidã¢ããªéçºæ´ã¯ããããé·ãã®ã§ããããã®ä½æ¥ãè¡ã£ãã®ã¯åãã¦ã§ããã</p>
<p>ãã®è¨äºã§ã¯ãã¢ããªãå¥ã®ã³ã¼ããã¼ã¹ã§ä¸æ¸ãããéã«ã©ããã£ãèæ
®ãå¿
è¦ã ã£ããã大ã¾ãã«èª¬æãã¦ãããã¨æãã¾ãã<br/>
One Experience ã«ããã¢ãã¤ã«ã¢ããªã®æ©è½é¢ã®å¤æ´ãç´°ããæè¡ä¸ã®å·¥å¤«ãªã©ã¯å¾ç¶ã®å¥è¨äºã§èª¬æããã¦ããäºå®ãªã®ã§ããã®è¨äºã§ã¯JPã¢ããªã®ä¸æ¸ãã«å¿
è¦ã ã£ãä½æ¥ã®æ¦è¦ã«ã¤ãã¦èª¬æãã¦ããã¾ãã<br/>
ã¾ãããã®è¨äºã§ã¯ä¸»ã« Android ã®ç¨èªã§èª¬æãã¦ããã¾ããã iOS ã¢ããªã«ã¤ãã¦ãã ãããåããããªé°å²æ°ã ã¨æã£ã¦ãã ããã</p>
<h2 id="ã¢ãã¤ã«ã¢ããªåºæã®ç¹æ§">ã¢ãã¤ã«ã¢ããªåºæã®ç¹æ§</h2>
<p>詳ããä½æ¥å
容ã«å
¥ãåã«ãã¢ãã¤ã«ã¢ããªã®ãªãªã¼ã¹ã«é¢ããç¹æ§ã«ã¤ãã¦èª¬æãããã¨æãã¾ãã<br/>
ãã§ã«ã¢ãã¤ã«ã¢ããªã®éçºè
ã¯ããç¥ã£ã¦ãããã¨ã§ããããã®ç¹æ§ã One Experience ã®ãªãªã¼ã¹ãè¤éãªãã®ã«ãã¦ãããããããããã¦èª¬æãã¾ãã</p>
<h3 id="ãã¼ã«ããã¯ãé£ããå®è³ªã§ããªã">ãã¼ã«ããã¯ãé£ãã(å®è³ªã§ããªã)</h3>
<p>ã¢ãã¤ã«ã¢ããªã¯ã常ã«ä»¥åã®ãã®ããã大ãã version code ãæã¤ã¢ããªã§ããä¸æ¸ãã§ãã¾ããã<br/>
ãã®ç¹æ§ã«ãããä¸åº¦ One Experienceçã§ä¸æ¸ããããã¢ããªãJPçã«ãã¼ã«ããã¯ããããã«ã¯ãJPçã®ãã¼ã¸ã§ã³ã One Experienceçããã大ããå¤ã«å¤æ´ããä¸ã§å度ä¸æ¸ãããå¿
è¦ãããã¾ãã<br/>
ãã®æ¹æ³ã§ãã¼ã«ããã¯ãè¡ãã¨2ã¤ã®ãªãã¸ããªéã§ãã¼ã¸ã§ã³ãç´°ãã管çããå¿
è¦ãããããããªãªã¼ã¹ããã¼ãã¨ã¦ãè¤éã«ãªãã¾ãã</p>
<p>ããã«ãJPçã¸ã®ãã¼ã«ããã¯ãè¡ã£ãå ´åãJPç -> One Experienceç ã¨ã¢ãããã¼ãããã¦ã¼ã¶ã¼ã ãã§ãªãã One Experienceçãæ°è¦ã«ã¤ã³ã¹ãã¼ã«ããã¦ã¼ã¶ã¼ãJPçã§ä¸æ¸ãããã¦ãã¾ãã¾ãã<br/>
JPç -> One Experienceç ã¸ã®ã¢ãããã¼ãæã«èªè¨¼æ
å ±ãä¸é¨ã®ãã¼ã«ã«ãã¼ã¿ããã¤ã°ã¬ã¼ã·ã§ã³ãããã¨ã¯æ±ºãã¦ãã¾ããããéæ¹åã®ã¢ãããã¼ããJPç -> One Experienceç ã2åç¹°ãè¿ããå ´åã®ãµãã¼ãã¯ãã¾ãã«ã大å¤ãããããããªãªã¼ã¹ã«é¢ããå¶ç´ã¨ã㦠<strong>One Experienceçããã®ãã¼ã«ããã¯ã¯è¡ããªã</strong>ã¨æ±ºãã¾ããã<br/>
ããã«ããèæ
®äºé
ãããªãæ¸ãã One Experience ã«éä¸ãã¦é²ãã¦ããææ表示ã«ããªã£ãã®ã§ããã®ææ決å®ãã§ãããã¨ã¯è¯ãã£ãã¨æãã¾ãã</p>
<h3 id="ã¢ããªãã¦ã¼ã¶ã¼ã®æã«å±ãã¾ã§ã«æéããããbugfix-ãæåå¤æ´ãç¬æã«é©ç¨ã§ããªã">ã¢ããªãã¦ã¼ã¶ã¼ã®æã«å±ãã¾ã§ã«æéãããã(bugfix ãæåå¤æ´ãç¬æã«é©ç¨ã§ããªã)</h3>
<p>ã¢ãã¤ã«ã¢ããªã§ã¯ãã¢ããªããµãããããããã¨ããªãªã¼ã¹ãããã¾ã§ã«å¯©æ»ããããããã«å
¬éãããã¨ãã¦ã¼ã¶ã¼ã®ç«¯æ«ã«ã¤ã³ã¹ãã¼ã«ãããã¾ã§ã¯æéããããã¾ãã<br/>
ãã㯠bugfix ãªã©ã®ãªãªã¼ã¹ã§ãåæ§ã§ãä¸å
·åãä¿®æ£ãã¦ãã¦ã¼ã¶ã¼ã®æå
ã®ä¸å
·åãçºçãã¦ãããã¼ã¸ã§ã³ãä¸æ¸ãããããã«ã¯æ°æ¥æããå ´åãããã¾ãã</p>
<p>ç¹ã« One Experience ãªãªã¼ã¹ã®åæ段éã§ã¯æ§ã
ãªä¸å
·åãäºæ³ãããããããã©ãããã©ã¼ã ã®<a href="https://support.google.com/googleplay/android-developer/answer/6346149?hl=ja">段éçãªå
¬é</a>æ©è½ãå©ç¨ããæ§åãè¦ãªããå°ããã¤å
¬éçãä¸ãã¦ãããã¨ã«ãã¾ããã One Experience ã§ã¯ãååã®ãªãªã¼ã¹ãã100% ãªãªã¼ã¹ã¾ã§ããã3é±éæãã£ã¦ãã¾ãã ãã¼ã«ããã¯ãé£ãã&æ´æ°ã«æéããããã¨ããç¶æ³ã§å¾è¿°ã®èªè¨¼æ
å ±ã®ãã¤ã°ã¬ã¼ã·ã§ã³ã失æããã¨ä½ãããããã¾ãã«ãªã£ã¦ãã¾ãã®ã§ãååã®ãªãªã¼ã¹ãããã¤ã°ã¬ã¼ã·ã§ã³ã«æåããã¦ã¼ã¶ã¼ã観測ãããã¾ã§ã¯ããªããããããã¦ãã¾ããã</p>
<h2 id="JPçã¢ããªããªãªã¼ã¹ããããã«è¡ã£ãä½æ¥">JPçã¢ããªããªãªã¼ã¹ããããã«è¡ã£ãä½æ¥</h2>
<p>ããã§ã¯ One Experience ãªãªã¼ã¹æã«å®è£
ãå¿
è¦ã ã£ãé
ç®ã«ã¤ãã¦ç°¡åã«åæãã¾ãã<br/>
ããã«ãããé
ç®ä»¥å¤ãããããã®ä¿®æ£ãå
¥ã£ã¦ãã¾ãããç¹ã«éè¦ãªãã®ã«ã¤ãã¦è¨è¿°ãã¦ãã¾ãã</p>
<h3 id="æ¥æ¬ãªã¼ã¸ã§ã³ã¸ã®å¯¾å¿">æ¥æ¬ãªã¼ã¸ã§ã³ã¸ã®å¯¾å¿</h3>
<p>Globalã¢ããªã¯ãã¨ãã¨å¤è¨èªå¯¾å¿ãã¦ããã®ã§ãã¢ããªå
ã« ja-JP ãªã¼ã¸ã§ã³è¨å®ã追å ãã翻訳ãªã½ã¼ã¹ã追å ããã°æ¥æ¬èªå¯¾å¿ã§ããç¶æ
ã§ããã<br/>
æååã®ç¿»è¨³ãç»å以å¤ã§ããã«ããã¼ã¸ã®URLãããä¸é¨ã®å®è£
ã¯ãªã¼ã¸ã§ã³ãã¨ã«å¦çãåãæ¿ãã¦ãã¦ã One Experience ã§ã¯ ja-JP ãªã¼ã¸ã§ã³ç¹æã®å¦çãããã¤ã追å ãã¦ãã¾ãã<br/>
ja-JP ãªã¼ã¸ã§ã³ã ãã®åå²ãå°æ¥çã«ãªããªãã®ããããããä»æ§ã®ã¾ã¾ã§ãã£ã¨ããã®ãã¯ã¾ã 決ã¾ã£ã¦ããªãç®æããããä»å¾ã¯ãããã£ããªã¼ã¸ã§ã³åºæã®å®è£
ç®æã®ä¿å®æ§ãé«ãã¦ãããã¨è¯ããªã¨èãã¦ãã¾ãã</p>
<h3 id="JPã¢ããªãã«ãè¨å®-ã®è¿½å ">JPã¢ããªãã«ãè¨å® ã®è¿½å </h3>
<p>Globalã¢ããª(Android)ããã¸ã§ã¯ãã«ã¯ãã¨ã㨠Flavor ã«ãã£ã¦ãã«ãããã¢ããªã® ApplicationId ã versionName ãªã©ãåãæ¿ããæ©è½ãå®è£
ããã¦ãã¾ããã<br/>
JPã¢ããªã§ã¯ãããã£ãåãæ¿ãã¯ãã¹ã¦ã¢ã¸ã¥ã¼ã«ãåãæ¿ãããã¨ã§è¡ã£ã¦ããã®ã§ã Flavor ã§åãæ¿ãããã¨ã«å°ãæµæããã£ãã®ã§ãããå
ã
ã®å®è£
ããã¼ã¹ã«ãªãã¡ã¯ã¿ãªã³ã°ãå ãããã¨ã§ Flavor ãã¼ã¹ã§ã®åãæ¿ãã«ãã£ã¦å®è£
ãããã¨ãã§ãã¾ããã</p>
<h3 id="ãã¼ã¸ã§ãã³ã°">ãã¼ã¸ã§ãã³ã°</h3>
<p>JPçã¢ããªã¯æ°å¹´åãããªãªã¼ã¹å¹´ã»ãªãªã¼ã¹é±çªå·ãã¼ã¹ã§èªåçã«ãã¼ã¸ã§ã³ãæ¡çªãã¦ãã¾ããã<br/>
対ãã¦Global çã¯æåã®ã»ãã³ãã£ãã¯ãã¼ã¸ã§ãã³ã°ã§ã majorãminor ãç¹°ãä¸ããã¿ã¤ãã³ã°ã«ã¤ãã¦ã¯ãã¾ãæ確ã«ãªã£ã¦ãã¾ããã§ããã<br/>
JP ã¢ããªã®ã»ãã version code ã大ããã£ããã¨ãé±æ¬¡ãªãªã¼ã¹ã¨ããããã¼ã¯One Experienceå¾ãå¤ãããªãã£ããã¨ãããJP/Global両æ¹ã®ã¢ããªã§ãªãªã¼ã¹å¹´ã»ãªãªã¼ã¹é±çªå·ãã¼ã¹ã®ãã¼ã¸ã§ãã³ã°ã«åããããã¨ã«ãã¾ããã</p>
<h3 id="ãªãªã¼ã¹ããã¼">ãªãªã¼ã¹ããã¼</h3>
<p>ãã¨ãã¨JPçã¢ããªã¯ããªãèªååããã<a href="https://techlife.cookpad.com/entry/2018/09/14/090000">é±æ¬¡ã®ãªãªã¼ã¹ããã¼</a>ãæ¡ç¨ãã¦ãã¾ããã<br/>
Globalã¢ããªãé±æ¬¡ãªãªã¼ã¹ã§ããããéç¨æ¹æ³ã«ã¯å¤§ããªéããããã¾ããã<br/>
ä¸çªå¤§ããªéãã¯ã³ã¼ãããªã¼ãºããµããããã®ã¿ã¤ãã³ã°ãèªååããã¦ãããããªãªã¼ã¹ããã¼ã¸ã£ã¼ã¨ãªã£ã人éãæåã§ã¿ã¤ãã³ã°ã決ãã¦ãããã¨ã§ãã<br/>
Global çã¢ããªã§ã¯æ©è½ãç»é¢ã®æ´æ°ã«å¯¾ãã¦ç¿»è¨³ãªã½ã¼ã¹ã®æ´æ°ãå¾
ã¤å¿
è¦ããããæã¡åãã§ãªãªã¼ã¹ããã¼ã¸ã£ã¼ã«ãªã£ã人éã®æ´»åæéã«ãæå·®ããã£ãããããªãªã¼ã¹ããã¼ã¸ã£ã¼ãã³ã¼ãããªã¼ãºã®ã¿ã¤ãã³ã°ã調æ´ã§ããã»ããé½åãè¯ãã£ãã®ã§ãã<br/>
æåã¯JPã®ããã«èªååããã»ããè¯ãã¨èãã¦ãã¾ãããã Global çããã¼ã¹ã«éçºãã¦ãããã¡ã«èããæ¹ãã Globalçã®ãªãªã¼ã¹ããã¼ã«åããããã¨ã«ãã¾ããã<br/>
ç¾å¨ã®ãªãªã¼ã¹ããã¼ã§ã¯ãã³ã¼ãããªã¼ãºããµããããããªãªã¼ã¹ãªã©ã®å¦çã Global 㨠JP ã§ã»ã¼å®å
¨ã«åæãã¦è¡ããã¦ãã¾ãã</p>
<h3 id="Firebase-ããã¸ã§ã¯ãã®åãæ¿ã">Firebase ããã¸ã§ã¯ãã®åãæ¿ã</h3>
<p>JP 㨠Global ã¯ããããå¥ã
ã«éçºã»éç¨ããã¦ããã¢ããªã ã£ãããã Firebase ããã¸ã§ã¯ããå®å
¨ã«åé¢ããã¦ããç¶æ
ã§ããã<br/>
æåã¯ãã«ãããã¢ããªã«ãã£ã¦ Firebase ããã¸ã§ã¯ããåãæ¿ããæ¹éãæ¤è¨ããã®ã§ããã以ä¸ã®çç±ã«ããã Firebase ããã¸ã§ã¯ãã Global ã使ã£ã¦ãããã®ã«çµ±ä¸ãããã¨ã«ãã¾ãã</p>
<ul>
<li>社å
ã® push éç¥éä¿¡ãµã¼ãã¹ããã«ãproject ããµãã¼ãã§ããããã«æ¹ä¿®ãå¿
è¦</li>
<li>ã³ã¼ããã¼ã¹ãå®å
¨ã«åãæ¿ãããããCrashlytics ã«éãããã¯ã©ãã·ã¥æ
å ±ã®å¾åã大ããå¤ãã
<ul>
<li>One Experience 以éã®ã¯ã©ãã·ã¥æ
å ±ã ã管çã§ããã°è¯ã</li>
</ul>
</li>
<li>ã¢ããªå
ã® Firebase Analytics ãã°ãå®å
¨ã«åãæ¿ããããã Firebase Analytics ã®ãã°å
容ã大ããå¤ãã
<ul>
<li>One Experienceåå¾ã®æ¯è¼ããããã®ã§é£ç¶æ§ã¯ãã£ãã»ããè¯ããããã¨ãã¨éè¦ãªãã°ã¯ FirebaseAnalytics ã§ã¯ãªãèªåã®ãã°ã§æ¯è¼ããæåã ã£ãã®ã§ãè´å½çã§ã¯ãªã</li>
</ul>
</li>
<li>Firebase Dynamic Links ãè¤æ°ã®ããã¸ã§ã¯ãããçæããããªã</li>
</ul>
<h3 id="ã¢ããªã®ãã¤ã°ã¬ã¼ã·ã§ã³ã«ã¤ãã¦">ã¢ããªã®ãã¤ã°ã¬ã¼ã·ã§ã³ã«ã¤ãã¦</h3>
<p>JPçã¢ããªãã One Experienceçã¢ããªã«ã¢ãããã¼ãããã¨ãããã¡ãã¨ãã¤ã°ã¬ã¼ã·ã§ã³å¦çãå®è£
ãã¦ããªããã°ã¢ããªãããã°ã¢ã¦ããããã¹ã¦ã®ãã¼ã«ã«ãã¼ã¿ã«ã¢ã¯ã»ã¹ã§ããªããªãã¾ãã<br/>
One Experienceçã¢ããªã§ã¯ãJPçã¢ããªãå©ç¨ãã¦ããã¦ã¼ã¶ã¼ããã®ã¾ã¾å©ç¨ã§ããããã«ãèªè¨¼æ
å ±ãä¸é¨ã®ãã¼ã«ã«ä¿åæ
å ±ã«ã¢ã¯ã»ã¹ã§ããããã«ç¹å¥ãªå®è£
ãå
¥ãã¦ãã¾ãã</p>
<p>ç¹ã«èªè¨¼æ
å ±ã®ãã¤ã°ã¬ã¼ã·ã§ã³ã«é¢ãã¦ã¯ããã ãã®ããã« Globalã¢ããªã« AccountManager ã®å®è£
ãå
¥ãã¦ãããè²ã
ãªä»çµã¿ãå
¥ã£ã¦ããã®ã§ããã説æããã¨ããã ãã§ä¸ã¤ã®è¨äºã«ãªã£ã¦ãã¾ãã®ã§ã¾ãå¥ã®æ©ä¼ã«æ¸ããã¨ã«ãã¾ãã</p>
<h2 id="ã¾ã¨ã">ã¾ã¨ã</h2>
<p>One Experienceçã¢ããªããªãªã¼ã¹ããããã®åãçµã¿ã«ã¤ãã¦èª¬æãã¾ããã<br/>
æ©è½é¢ãã³ã¼ãé¢ã§ã¯ãã®è¨äºã§ç´¹ä»ãã以å¤ã§ãæ§ã
ãªå¤æ´ãããã¾ãããããã§ã¯ããã¸ã§ã¯ãå
¨ä½ã«é¢ãããããªå¤§ã¾ããªãã®ã«çµã£ã¦ç´¹ä»ããã¦ããã ãã¾ããã<br/>
ãããããã¢ãã¤ã«ã¢ããªã® One Experience ã«é¢ããè¨äºã¯å
¬éäºå®ãªã®ã§ãä»å¾ã®æ´æ°ã«ããæå¾
ãã ããã</p>
nein37
Flux + Helm ã«ãããå³æãã¼ã«ããã¯
hatenablog://entry/6802418398299536451
2024-10-28T20:43:40+09:00
2024-10-28T20:43:40+09:00 ããã«ã¡ã¯ãSRE ã®å°å· (@coord_e) ã§ããå
æ¥ã®æ稿ã«ãã£ãéããã¯ãã¯ãããã¯ã¬ã·ããµã¼ãã¹ãã°ãã¼ãã«çã«çµ±åãã¾ããããµã¼ãã¹ã®çµ±åã«ä¼´ã£ã¦ãéçºãéç¨ã®ã¤ã³ãã©ãã°ãã¼ãã«ãã¼ã ã§å©ç¨ããã¦ãããã®ã使ããã¨ã«ãªãã¾ããã éç¨ã¤ã³ãã©ã®ä¸ã§ãç¹ã«å¤§ããªéãã¨ãã¦ãæ¥æ¬ã¨ã°ãã¼ãã«çã§ã¯ã³ã³ãããªã¼ã±ã¹ãã¬ã¼ã·ã§ã³ã®ä»çµã¿ãç°ãªã£ã¦ãã¾ããæ¥æ¬ã§ã¯ Amazon Elastic Container Service (ECS) ã使ã£ã¦ã³ã³ãããå®è¡ãã¦ãã¾ãããã°ãã¼ãã«çã§ã¯ Amazon Elastic Kubernetes Service (EKS) ã®ä¸ã§ã³ã³ããâ¦
<p>ããã«ã¡ã¯ãSRE ã®å°å· (@coord_e) ã§ããå
æ¥ã®æ稿ã«ãã£ãéããã¯ãã¯ãããã¯<a href="https://cookpad.com/">ã¬ã·ããµã¼ãã¹</a>ãã°ãã¼ãã«çã«çµ±åãã¾ããããµã¼ãã¹ã®çµ±åã«ä¼´ã£ã¦ãéçºãéç¨ã®ã¤ã³ãã©ãã°ãã¼ãã«ãã¼ã ã§å©ç¨ããã¦ãããã®ã使ããã¨ã«ãªãã¾ããã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2024%2F10%2F10%2F105832" title="æ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ãã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>éç¨ã¤ã³ãã©ã®ä¸ã§ãç¹ã«å¤§ããªéãã¨ãã¦ãæ¥æ¬ã¨ã°ãã¼ãã«çã§ã¯ã³ã³ãããªã¼ã±ã¹ãã¬ã¼ã·ã§ã³ã®ä»çµã¿ãç°ãªã£ã¦ãã¾ããæ¥æ¬ã§ã¯ Amazon Elastic Container Service (ECS) ã使ã£ã¦ã³ã³ãããå®è¡ãã¦ãã¾ãããã°ãã¼ãã«çã§ã¯ Amazon Elastic Kubernetes Service (EKS) ã®ä¸ã§ã³ã³ãããå®è¡ãã¦ãã¾ãã</p>
<p>ã¾ãéçºé¢ã§ã¯ãããã¤ããã¼ã«å¤§ããªéããããã¾ããæ¥æ¬ã§ã¯ãã¢ããªã±ã¼ã·ã§ã³ã®æ°ãããªãã¸ã§ã³ã®ãããã¤ã¯ ChatOps ã«ãã£ã¦è¡ãªã£ã¦ãã¾ãããmain ãã©ã³ãã« PR ããã¼ã¸ãããCI ãã¤ãã©ã¤ã³ãæ°ãããªãã¸ã§ã³ã®ã³ã³ããã¤ã¡ã¼ã¸ããã«ãããå¾ãéçºè
ã Slack ãã£ã³ãã«ã§ã³ãã³ããå®è¡ï¼çºè¨ï¼ãããã¨ã§ããã®ãªãã¸ã§ã³ã®ãããã¤ãè¡ãã¾ããæ¥æ¬çã§ã®éçºããã¼ã¯ä¸ã®è¨äºã«è©³ããè¨è¼ããã¦ãã¾ãã
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2018%2F04%2F02%2F140846" title="Web ã¢ããªã±ã¼ã·ã§ã³ãææ¡ããããã®ã³ã³ã½ã¼ã« - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>ä¸æ¹ãã°ãã¼ãã«çã§ã¯ãã¢ããªã±ã¼ã·ã§ã³ãªãã¸ããªã§ã® PR ãã¼ã¸å¾ã«èªåã§ãããã¤ã¾ã§ãè¡ããã¾ããå¾ã«è©³ãã説æãã¾ããããã㯠<a href="https://fluxcd.io/">Flux</a> ã¨ãã OSS ãæ´»ç¨ãã¦å®ç¾ããã¦ãã¾ããå
¨ä½çã« GitOps ã®æµãã«ä¹ã£ã¦ãããã¢ããªã±ã¼ã·ã§ã³ã® Git ãªãã¸ããªã¸ã® push ãèµ·ç¹ã¨ãã¦ã®ã¡ã®ãããã¤ã®å
¨ã¦ã®è¡ç¨ãèªåã§é²è¡ããããã«ãªã£ã¦ãã¾ãã
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffluxcd.io%2Fflux%2Fconcepts%2F%23gitops" title="Core Concepts" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://fluxcd.io/flux/concepts/#gitops">fluxcd.io</a></cite></p>
<p><figure class="figure-image figure-image-fotolife" title="ã°ãã¼ãã«çãã©ãããã©ã¼ã ã®èªåãããã¤ã®æµããç¢å°ã¯æ
å ±ã®æµãã表ãã¦ãããªã¯ã¨ã¹ãã®æ¹åã¨ã¯å¿
ãããä¸è´ãã¾ãã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/coorde/20241028/20241028111530.png" width="1200" height="512" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ã°ãã¼ãã«çãã©ãããã©ã¼ã ã®èªåãããã¤ã®æµããç¢å°ã¯æ
å ±ã®æµãã表ãã¦ãããªã¯ã¨ã¹ãã®æ¹åã¨ã¯å¿
ãããä¸è´ãã¾ãã</figcaption></figure></p>
<p>ãªããã³ã³ãããªã¼ã±ã¹ãã¬ã¼ã·ã§ã³ããããã¤ã®æ¹æ³å«ããOne Experience å¾ã«ã°ãã¼ãã«ã¨æ¥æ¬ã®éã§ã¤ã³ãã©ãã©ããã¦ãããã¯è°è«ã®æä¸ã§ããæ¬ç¨¿ã§ç´¹ä»ããææ³ã¯ãåºæ¬çã«çæçã«éç¨ä¸ã®åé¡ç¹ã解決ããããã«ãã©ã¼ã«ã¹ããé¸æããã¦ãã¾ãã</p>
<h1 id="ãªãã¼ãã«ãããã¼ã«ããã¯ã¨ãã®èª²é¡">ãªãã¼ãã«ãããã¼ã«ããã¯ã¨ãã®èª²é¡</h1>
<p>ãã¦ãæ°ãããªãã¸ã§ã³ããããã¤ããå¾ã«ããããåå ã¨ãªã£ãåé¡ãçºè¦ããå ´åããã®å¤æ´ãéããã«åãæ¶ãå¿
è¦ãããã¾ãï¼ãã¼ã«ããã¯ï¼ãããã¾ã§ãã°ãã¼ãã«çã§ã¯ãã¼ã«ããã¯ã¯ã³ãããã®ãªãã¼ãã«ãã£ã¦è¡ããã¦ãã¾ãããå¤æ´ãåãæ¶ãã³ããããæ°ãã«ç©ã¿ãããããããã¤ããã¨ããæµãã§ãããã㯠GitOps ã®æµãããé¸ãããã¨ãªããé常ã®ãããã¤ã¯ã¼ã¯ããã¼ã«ä¹ã£ã¦ãªãã¬ã¼ã·ã§ã³ãã§ããã¨ããç¹ã§åªãã¦ãã¾ããããããã°ãã¼ãã«çã§ã®éçºãé²ããã«ã¤ãã¦ããªãã¼ãã«ãããã¼ã«ããã¯ã®èª²é¡ãããã¤ãããã£ã¦ãã¾ããã</p>
<ul>
<li>é常ã®ãããã¤ããã¼ã«ä¹ã£ã¦ãããããå¤æ´ãå·»ãæ»ãã¾ã§æéããããã¾ããç¹ã« CI ä¸ã§ã®ãã¹ãå®è¡ã®ãªã¼ãã¼ããããç¡è¦ã§ãã¾ããã
<ul>
<li>ä¸é¨ Flaky ãªãã¹ããåå¨ãã¦ãããããããªãã©ã¤ãã¦ããã¨ãã¹ããå
¨ã¦éãã¾ã§é·ãæéãããã£ã¦ãã¾ãå ´åãããã¾ãã</li>
<li>ããã«ãããã¤ã¯ç´åã«è¡ãããç´åã«ä»ã®ãããã¤ãèµ·ãã¦ããã¨ãã®ãããã¤ãçµããã¾ã§å¾
ã¤å¿
è¦ãããã¾ãã</li>
</ul>
</li>
<li>é常ã®ãããã¤ããã¼ã«ä¹ã£ã¦ãããããå¤æ´ã«æ¿èªãå¿
è¦ã§ããç§ãã¡ã¯ GitHub ä¸ã§ main ãã©ã³ãã¸ã®ãã¼ã¸ã«ä¸å以ä¸ã® Approve ãå¿
é ã¨ãã¦ãã¾ãããé害対å¿ã«ããã¦ã¯ãã®ãªã¼ãã¼ããããããã¾ãã
<ul>
<li>ãã¡ããé害çºçæã«ã¯ããããã¤ãã¹ãã¦ãã¼ã¸ã§ããããã«ç¹æ¨©ãç¨æãã¦ããæ¹æ³ãããå¾ã¾ãããé害çºçæã«ã®ã¿ç¹æ¨©ã使ãã¨ããå¤æãå¶å¾¡ã¯é£ãããªããã¨ãäºæ³ããã¾ãã</li>
</ul>
</li>
</ul>
<p>åºæ¬çã«ãé害çºçæã«ã¯ãã¦ã¼ã¶ã¼ã¸ã®å½±é¿ãæå°éã«ã¨ã©ããããã«å³åº§ã«ãã¼ã«ããã¯ãå®äºãããã§ãããããããªãã¼ãã«ãã£ã¦ãã¼ã«ããã¯ãè¡ãã¨ã©ããã¦ãæéããããããã¦ãã¾ãã¾ãããã¡ãããããã¤ããã¼ãé«éåããã®ã¯æå¹ã§ããããã¹ãã®å®è¡ãã¤ã¡ã¼ã¸ã®ãã«ãã¯é¿ããããªããããä¾ãã°åå ã®ç¹å®ãã1å以å
ã«ãã¼ã«ããã¯ãéå§ããã¨ãã£ããã¨ã¯é£ããã§ãããã</p>
<p>ããã¾ã§ã®æ¥æ¬çã®éçºã§ã¯åé¡çºçæã«1åããããããã¼ã«ããã¯ãéå§ã§ãã¦ãã¾ãããä»å One Experience ã§æ¥æ¬ãã¼ã ãã°ãã¼ãã«çã®éçºã«åæµãã¾ãããããã¼ã«ããã¯æé ãæ´åããã¦ããããé害ãçºçããéã«ããã«å復ã§ããã« 40 åã»ã©ãµã¼ãã¹ããã¦ã³ããã¦ãã¾ãåºæ¥äºãããã¾ãããããããã£ããã«ãæ¥æ¬çã§ã®éçºã¨åæ§ã«ã°ãã¼ãã«çã«ãå³æã«ãã¼ã«ããã¯ãå®è¡ã§ããä»çµã¿ãæ´åãããã¨ã«ãã¾ããã</p>
<p>ç§ãã¡ãå©ç¨ãã¦ãã Flux ã§ã¯ããããã§ã¹ããåæãã¦ãããªãã¸ããªã§ã®ãªãã¼ãã«ãã£ã¦ãã¼ã«ããã¯ãå®ç¾ããã®ãçã®ããã§ã<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>ãããããå¾è¿°ããããã«ç§ãã¡ã¯ãããã§ã¹ããªãã¸ããªã®èªåæ´æ°ãè¡ã£ã¦ãããããããæ¢ããå¿
è¦ããã£ãããã¾ã Helm Controller ã®ãããã¤å¾
ã¡ã®åé¡ããã£ããã¨ãåãªããããã§ã¹ããªãã¸ããªã®ãªãã¼ãã§ã¯å³æãã¼ã«ããã¯ã®è¦ä»¶ãæºãããã¨ãã§ãã¾ããã§ãããããã§ãç§ãã¡ã¯é常ã®ãããã¤ããã¼ããã¯å¤ãããå³æãã¼ã«ããã¯ã®ããã®ç¬èªã®ãªãã¬ã¼ã·ã§ã³ãæ§ç¯ãããã¨ã«ãã¾ããã</p>
<h1 id="GitOps-ããå¤ãã-ã©ãã§æµããæ¢ããã">GitOps ããå¤ãã: ã©ãã§æµããæ¢ããã</h1>
<p>é常ã®ãããã¤ããã¼ããå¤ããã¨ãããã¨ã¯ãèªåãããã¤ã®æµããããç¹ã§åæ¢ãããã¨ãæå³ãã¾ããã¾ããç¾å¨ã®ã°ãã¼ãã«çã®ãããã¤ã®è©³ããæµããä¸ã®å³ã«ç¤ºãã¾ãããªããã°ãã¼ãã«çã§ã¯ Deployment ãå«ãã¢ããªã±ã¼ã·ã§ã³ã®ãªã½ã¼ã¹ã¯ <a href="https://helm.sh/">Helm</a> ãã£ã¼ãã¨ãã¦ããã±ã¼ã¸åããã¦ãããHelm ãªãªã¼ã¹ã® Values ãããããã¤ããã¤ã¡ã¼ã¸ã®ã¿ã°ã注å
¥ãã¦ãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="ã°ãã¼ãã«çãã©ãããã©ã¼ã ã®èªåãããã¤ã®æµããç¢å°ã¯æ
å ±ã®æµãã表ãã¦ãããªã¯ã¨ã¹ãã®æ¹åã¨ã¯å¿
ãããä¸è´ãã¾ãã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/coorde/20241028/20241028111508.png" width="1200" height="162" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ã°ãã¼ãã«çãã©ãããã©ã¼ã ã®èªåãããã¤ã®æµããç¢å°ã¯æ
å ±ã®æµãã表ãã¦ãããªã¯ã¨ã¹ãã®æ¹åã¨ã¯å¿
ãããä¸è´ãã¾ãã</figcaption></figure></p>
<ol>
<li>ã¢ããªã±ã¼ã·ã§ã³ã®ãªãã¸ããªã§æ°ããã³ãããã push ãããã¨ãCI ãã³ã³ããã¤ã¡ã¼ã¸ããã«ãã ECR ãªãã¸ããªã«ã¤ã¡ã¼ã¸ã push ãã¾ãã</li>
<li>ããã Flux ã® <a href="https://fluxcd.io/flux/guides/image-update/">Image Update Automation æ©è½</a>ãèªåçã«æ¤ç¥ããã¢ããªã±ã¼ã·ã§ã³ã«å¯¾å¿ãã <a href="https://fluxcd.io/flux/components/helm/api/v2/#helm.toolkit.fluxcd.io/v2.HelmRelease">HelmRelease</a> ã® <code>.spec.values</code> ã«è¨è¿°ãããã¤ã¡ã¼ã¸ã®ã¿ã°ãæ´æ°ããã³ããããä½æãã¦ãããã§ã¹ããªãã¸ããªã¸ push ãã¾ããHelmRelease ã¨ããã®ã¯ Flux ã Helm ã®ãªãªã¼ã¹ã管çããããã«ç¨ããã«ã¹ã¿ã ãªã½ã¼ã¹ã§ã<code>.spec.values</code> ã« Helm ãªãªã¼ã¹ã® Values ãè¨è¿°ãã¦ãã㨠<a href="https://fluxcd.io/flux/components/helm/">Helm Controller</a> ãèªå㧠<code>helm install</code> ã <code>helm upgrade</code> ãå®è¡ãã¾ãã</li>
<li>ãããã§ã¹ããªãã¸ããªã®å
容㯠<a href="https://kustomize.io">Kustomize</a> ã§æ§æããã¦ãããFlux ã® <a href="https://fluxcd.io/flux/components/kustomize/">Kustomize Controller</a> ããããã§ã¹ããªãã¸ããªã®å
容ãèªåçã«ã¯ã©ã¹ã¿ã¸åæ ããããã«è¨å®ããã¦ãã¾ããããã«ããå
ã»ã© push ããã <code>.spec.values</code> ã®å¤æ´ãã¯ã©ã¹ã¿å
ã® HelmRelease ãªãã¸ã§ã¯ãã«åæ ããã¾ãã</li>
<li>HelmRelease ãªãã¸ã§ã¯ããå¤æ´ãããã¨ãFlux ã® Helm Controller ããããæ¤ç¥ããèªåçã« <code>helm upgrade</code> ãå®è¡ãã¾ããããã«ãã£ã¦æçµçã«æ°ããã¤ã¡ã¼ã¸ã®ã¿ã°ã Deployment ã® spec ã¾ã§åæ ãããDeployment ã®ãã¼ã«ã¢ã¦ããèµ·ããã¾ãã</li>
</ol>
<p>ãã¼ã«ããã¯ã«ããã¦ã¯ãã¢ããªã±ã¼ã·ã§ã³ã® Deployment ã® spec ã«è¨è¿°ããã¦ããã¤ã¡ã¼ã¸ã®ã¿ã°ãåé¡çºç以åã®ãã®ã«æ¸ãæãããã¨ãç®æ¨ã¨ãªãã¾ããåã«ç´æ¥ Deployment ãæ¸ãæããã®ã¯ããã®å¾ãªãã¸ããªã« push ããã㨠Flux ããããä¸æ¸ããã¦ãã¾ãããé©åã§ã¯ããã¾ãããã§ã¯ãã©ã®ããã«ãã¦ãããéæããã¨è¯ãã§ããããã</p>
<h2 id="æ¹æ³1-Helm-ããä¸æµã§ã¤ã¡ã¼ã¸ã®ã¿ã°ãæ»ã">æ¹æ³1. Helm ããä¸æµã§ã¤ã¡ã¼ã¸ã®ã¿ã°ãæ»ã</h2>
<p>ã¾ãèããããã®ããHelmRelease ã¾ã§ã®é¨åã§ãããã¤ã®æµããããæ¢ãããã¼ã«ããã¯å
ã®ã¤ã¡ã¼ã¸ã®ã¿ã°ãå¼·å¶çã«ä½¿ãããã¨ããæ¹æ³ã§ããä¸ã®å³ã§ããã¨ã次ã®ã©ã¡ããã«ãªãã§ããã:</p>
<ul>
<li>ã¤ã¡ã¼ã¸ã®ã¿ã°ã <a href="https://fluxcd.io/flux/components/image/reflector-api/v1beta2/#image.toolkit.fluxcd.io/v1beta2.ImagePolicy">ImagePolicy</a> ã§åºå®ãã(2) ãå®è³ªçã«åæ¢ãã</li>
<li>(3) ãåæ¢ããHelmRelease ã® <code>.spec.values</code> ã«ããã¤ã¡ã¼ã¸ã®ã¿ã°ãç´æ¥æ¸ãæãã</li>
</ul>
<p>ã©ã¡ãã®ããæ¹ã§ã HelmRelease ã® <code>.spec.values</code> ãæ»ãããããæ¤ç¥ãã Flux ã® Helm Controller ã <code>helm upgrade</code> ãå®è¡ãã¦æ£ãã Deployment ã® spec ã«ããã¤ã¡ã¼ã¸ã®ã¿ã°ãå¤æ´ãããã¼ã«ããã¯ãå®ç¾ã§ããã§ãããã</p>
<p><figure class="figure-image figure-image-fotolife" title="Helm ããä¸æµã§ã¤ã¡ã¼ã¸ã®ã¿ã°ãæ»ãå ´åã®ãã¼ã«ããã¯ãé»è²ã ImagePolicy ã§åºå®ããæ¹æ³ã赤ã HelmRelease ã® .spec.values ãæ¸ãæããæ¹æ³"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/coorde/20241028/20241028111554.png" width="1200" height="282" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Helm ããä¸æµã§ã¤ã¡ã¼ã¸ã®ã¿ã°ãæ»ãå ´åã®ãã¼ã«ããã¯ãé»è²ã ImagePolicy ã§åºå®ããæ¹æ³ã赤ã HelmRelease ã® .spec.values ãæ¸ãæããæ¹æ³</figcaption></figure></p>
<p>ãããããã®æ¹æ³ã§ã¯é·ãã®å¾
ã¡æéãçºçãã¦ãã¾ãåé¡ãèãããã¾ããHelm Controller ã¯ããã©ã«ã㧠<code>helm upgrade</code> å®è¡å¾ã«å種ãªã½ã¼ã¹ã ready ã«ãªãã¾ã§å¾
ã¤ããã«ãªã£ã¦ãããç§ãã¡ããã®æåãæ¡ç¨ãã¦ãã¾ãï¼<code>helm upgrade --wait</code> ã¨åãæåï¼ã
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffluxcd.io%2Fflux%2Fcomponents%2Fhelm%2Fapi%2Fv2%2F%23helm.toolkit.fluxcd.io%2Fv2.Upgrade" title="Helm API reference v2" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://fluxcd.io/flux/components/helm/api/v2/#helm.toolkit.fluxcd.io/v2.Upgrade">fluxcd.io</a></cite></p>
<p>ããã¦ãHelm Controller ã¯ã²ã¨ã¤ã® Helm ãªãªã¼ã¹ã«å¯¾ã㦠<code>helm upgrade</code> ãç´åã«è¡ãããããã¼ã«ããã¯ãããã¨ããéã«é²è¡ä¸ã®ãããã¤ãããã¨ãããçµããã¾ã§ãããªãã¡ Pod ãå
¨é¨å
¥ãæ¿ãã£ã¦ ready ã«ãªãã¾ã§å¾
ã¤ãã¨ã«ãªã£ã¦ãã¾ãã¾ããç¾ç¶ãããã«ã¯å ´åã«ãã£ã¦ 5 åãè¶
ããæéãå¿
è¦ã§ãä¸å»ãæ©ããã¼ã«ããã¯ãè¡ãããç¶æ³ã«ããã¦ãããå¾
ã¤ã®ã¯é©åã§ã¯ããã¾ãããããã¦ããã®ãããã¤å®äºå¾
ã¡ãä¸æããæ¹æ³ã¯ä»ã®æãªãããã§ãããã¡ãã Helm Controller ãåèµ·åããã°æ¢ã¾ãã¾ããããã¼ã«ããã¯å¯¾è±¡ã® Helm ãªãªã¼ã¹ã¨ã¯é¢ä¿ã®ãªã Helm ãªãªã¼ã¹ã®å¶å¾¡ã«ãå½±é¿ãããããçã®è¯ãããæ¹ã¨ã¯è¨ãã¾ããã</p>
<p>ãããã®çç±ããããã¼ã«ããã¯ã®é©ç¨ã§ Helm Controller ã«é ¼ããªãæ¹æ³ãæ¡ç¨ããå¤æããã¾ããã</p>
<h2 id="æ¹æ³2-ç´æ¥-Helm-ãªãªã¼ã¹ã®ãªãã¸ã§ã³ãæ»ã">æ¹æ³2. ç´æ¥ Helm ãªãªã¼ã¹ã®ãªãã¸ã§ã³ãæ»ã</h2>
<p>Helm ã«ã¯ Helm ã®ãªãã¸ã§ã³ç®¡çãããã¾ãã<code>helm upgrade</code> ã®ãã³ã« Helm ã¯ã¯ã©ã¹ã¿å
ã« Helm ãªãªã¼ã¹ã®å®å
¨ãªãããã§ã¹ãæ
å ±ã®å±¥æ´ãä¿åãã¦ãã¾ããHelm ã§ã¯ <code>helm rollback</code> ã³ãã³ãã§ãã®æ
å ±ã使ã£ã¦ä»¥åã®ãªãªã¼ã¹ã®ç¶æ
ã復å
ãããã¨ãã§ãã¾ãã
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhelm.sh%2Fdocs%2Fhelm%2Fhelm_rollback%2F" title="Helm Rollback" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://helm.sh/docs/helm/helm_rollback/">helm.sh</a></cite></p>
<p>ããã使ãå ´åãä¸æµãã <code>.spec.values</code> ãæ´æ°ãããæã« Helm Controller ããªãªã¼ã¹ã®ç¶æ
ãä¸æ¸ããã¦ãã¾ããªãããã« HelmRelease ã®åæãåæ¢ããå¿
è¦ãããã¾ããä¸ã®å³ã§ãã㨠(4) ãåæ¢ããæå¾ã® Deployment ãç´æ¥ï¼Helm ã®å®è£
ã使ã£ã¦ï¼æ¸ãæããã¢ããã¼ãã«ãªãã¾ãããã®æ¹æ³ãªãæ¢ã« Helm Controller ã«ãã <code>helm upgrade</code> ãé²è¡ä¸ã§ãå³åº§ã«ãã¼ã«ããã¯ãéå§ã§ãã¾ã<sup id="fnref:2"><a href="#fn:2" rel="footnote">2</a></sup>ãDeployment ã¯ãã¼ã«ã¢ã¦ãã®éä¸ã§ãã£ã¦ãå¤æ´ãããã°å³åº§ã«æ°ãããã¼ã«ã¢ã¦ããéå§ããããã<code>helm rollback</code> 㧠Deployment ã® spec ãæ¸ãæãã次第ããã«ã¤ã¡ã¼ã¸ãæ»ãå§ãã¾ãã
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fkubernetes.io%2Fdocs%2Fconcepts%2Fworkloads%2Fcontrollers%2Fdeployment%2F%23rollover-aka-multiple-updates-in-flight" title="Deployments" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#rollover-aka-multiple-updates-in-flight">kubernetes.io</a></cite></p>
<p>ãã®æ¹æ³ã¯ç´ã¡ã«ãã¼ã«ããã¯ãéå§ã§ããã¨ããç¹ã«å ãã¦ãã¤ã¡ã¼ã¸ã®ã¿ã°ä»¥å¤ã®è¦ç´ ã®ãã¼ã«ããã¯ã«ã使ããã¨ããç¹ã§åªãã¦ãã¾ããããã¾ã§èª¬æãã¦ãã¾ããã§ããããç§ãã¡ã¯ã¢ããªã±ã¼ã·ã§ã³ã¤ã¡ã¼ã¸ã«å ãã¦ã¢ããªã±ã¼ã·ã§ã³ã® Helm ãã£ã¼ãããèªä½ãå¤æ´ãããã¨ãããã¾ããä¾ãã°ãã³ã³ããã«å²ãå½ã¦ããªã½ã¼ã¹éã渡ãç°å¢å¤æ°ãå¤æ´ããå ´åãããã«ãããã¾ãããã®ãããªå¤æ´ãåãæ¶ããããããªæã«ãã<code>helm rollback</code> ã«ãã£ã¦åãæç¶ãã§å¯¾å¿ã§ããããã§ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="helm rollback ã使ãå ´åã®ãã¼ã«ããã¯"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/coorde/20241028/20241028111612.png" width="1200" height="268" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>helm rollback ã使ãå ´åã®ãã¼ã«ããã¯</figcaption></figure></p>
<h1 id="ChatOps-ã³ãã³ãã¨ãã¦ã®å®è£
">ChatOps ã³ãã³ãã¨ãã¦ã®å®è£
</h1>
<p>ã¤ã¾ããFlux ã«ãã HelmRelease ã®ç®¡çãåæ¢ãã<code>helm rollback</code> ãæå
ããå®è¡ããã°è¯ãã®ã§ããããï¼ãããããã¼ã«ããã¯ãå®è¡ããã®ã¯éçºè
ã§ãããã¤ã³ãã©ã®ç®¡çè
ã§ã¯ããã¾ããããã®ç¹ã§ãæå
ããå®è¡ããã«ã¯æ¬¡ã®ãããªåé¡ãããã¾ã:</p>
<ul>
<li><code>helm rollback</code> ã¯ããã®ãã£ã¼ãã«å«ã¾ãããªã½ã¼ã¹ãããããæ´æ°ããæä½ã§ãããå®è¡ã«ã¯ç¸å¿ã®æ¨©éãå¿
è¦ã¨ãã¾ããéçºè
ã«ã¯ãã®ãããªæ¨©éãããã¾ããï¼ããä»ä¸ããã®ãé©åã§ã¯ããã¾ããï¼ã</li>
<li><code>helm rollback</code> ããã¼ã«ããã¯å
ã¨ãã¦åãä»ããã®ã¯ Helm ãªãªã¼ã¹ã®ãªãã¸ã§ã³ã§ãããã¢ããªã±ã¼ã·ã§ã³ã® Git ãªãã¸ã§ã³ï¼ã³ãããããã·ã¥ï¼ã§ã¯ããã¾ãããããã¦ãéçºè
ã¯ã¢ããªã±ã¼ã·ã§ã³ã®ã³ãããããã·ã¥ãã Helm ãªãªã¼ã¹ã®ãªãã¸ã§ã³ãæ¢ãæ¹æ³ãç¥ããªããããããã対å¿ã®é
ããä¸æ£ç¢ºãã«ç¹ããã¾ãã
<ul>
<li>ã¡ã³ãã¼ã«ãã£ã¦ Kubernetes ãç¾è¡ã®ãããã¤ããã¼ã«å¯¾ããç¿ç度ã¯ã¾ã¡ã¾ã¡ã§ããããããã£ãæåã®ãªãã¬ã¼ã·ã§ã³ã®æ£ããæé ãè¦ã¤ãã¦å®è¡ããã®ã«ã¯æéãããã£ã¦ãã¾ãããããã¾ããã</li>
</ul>
</li>
</ul>
<p>ã¤ã¾ããéçºè
ã®ä»£ããã« <code>helm rollback</code> ãå®è¡ãã主ä½ãå¿
è¦ã«ãªãã¾ããWeb ã¢ããªã±ã¼ã·ã§ã³ã¨ãã¦ç¨æãããªã©ããã¤ãæ¹æ³ã¯èãããã¾ãããä»å㯠Slack ã®ãã£ãããããã®ã³ãã³ããå®è£
ãã¦ãããã <code>helm rollback</code> ãå®è¡ãããã¨ã«ãã¾ãã (ChatOps)ãChatOps ã«ãããã¼ã«ããã¯ã«ã¯æ¬¡ã®ãããªå©ç¹ãããã¾ã:</p>
<ul>
<li>ãã¼ã«ããã¯ãè¡ã£ãã¨ããäºå®ãå³åº§ã«å
±æãããã³ãã¥ãã±ã¼ã·ã§ã³ã容æã«ãªãã¾ãã</li>
<li>è¨é²ãããæ®ãããã¼ããªã³ã¯ã¨ãã¦ç¨ãããã¨ãã§ãã¾ãã</li>
<li>ãªãã¬ã¼ã·ã§ã³ã®æ¹æ³ã«ã¤ãã¦ãå®éã«ãã®ãªãã¬ã¼ã·ã§ã³ãå®è¡ãã¦ããªã人ãç¥ããã¨ãã§ããä»å¾ã®ãªãã¬ã¼ã·ã§ã³ã«æ´»ãããã¨ãã§ãã¾ãã</li>
</ul>
<p>ãã®ãããªæ§è³ªã¯ãç¹ã«ãã¼ã«ããã¯ã®ãããªè¿
éãªå¯¾å¿ã¨ã³ãã¥ãã±ã¼ã·ã§ã³ã両ç«ããªããã°ãªããªãå±é¢ã«ããã¦é常ã«æå¹ã§ããç§ãã¡ã¯æ¥æ¬ã§é·ãé ChatOps ã«ãããããã¤ã¨ãã¼ã«ããã¯ãå®è·µãã¦ãããã¨ããããä»åãéçºè
åãã®ã¤ã³ã¿ã¼ãã§ã¤ã¹ã¨ã㦠ChatOps ãæ¡ç¨ãããã¨ã«æ±ºãã¾ããã幸ããç§ãã¡ã¯ ChatOps ã³ãã³ãã容æã«éçºã§ããåºç¤ãæ´åãã¦ãããä»åãããã«ä¹ããã¨ã§ Slack ã¨ã®ã¤ã³ã¿ã¼ãã§ã¼ã¹ã«ã¤ãã¦æèããã« ChatOps ã³ãã³ããéçºãããã¨ãã§ãã¾ããã</p>
<h2 id="ãã¼ã«ããã¯ã®ã¹ã©ãã·ã¥ã³ãã³ã">ãã¼ã«ããã¯ã®ã¹ã©ãã·ã¥ã³ãã³ã</h2>
<p>Slack ä¸ã§æ¬¡ã®ããã«çºè¨ãããã¨ã§ãã¼ã«ããã¯ãéå§ã§ãããããªå®è£
ãä½æãã¾ããããªã laboty ã¨ããã®ã¯ãå
ã»ã©èª¬æããå
製 ChatOps åºç¤ã®ååã§ãã</p>
<pre class="code" data-lang="" data-unlink>/laboty global-web-platform rollback {ã¢ããªã±ã¼ã·ã§ã³å} {ãã¼ã«ããã¯å
ã®ã³ãããããã·ã¥} </pre>
<p>æ¥æ¬ã®éçºããã¼ã§ã¯ãããã¤ãæ示çãªæä½ã ã£ãããããã¼ã«ããã¯ã§ã¯ä¸ã¤åã®ãªãã¸ã§ã³ã«æ»ãã¦ãã¾ãããä¸æ¹ã°ãã¼ãã«çã®èªåãããã¤ã®ç°å¢ä¸ã§ã¯ããã¼ã«ããã¯å
ã¨ãªããªãã¸ã§ã³ã¯æããã§ã¯ãªããéçºè
ãæ示çã«æå®ããå¿
è¦ãããã¾ãããã㧠<code>helm rollback</code> ã®ããã«ãã¼ã«ããã¯å
ã® Helm ãªãªã¼ã¹ã®ãªãã¸ã§ã³ãç¥ãå¿
è¦ãããã¾ãããç§ãã¡ã®å®è£
ã§ã¯éçºè
ãæå®ããã¢ããªã±ã¼ã·ã§ã³ã®ã³ãããããã·ã¥ãã対å¿ãã Helm ãªãªã¼ã¹ã®ãªãã¸ã§ã³ãèªåçã«æ±ºå®ããããã«ãªã£ã¦ãã¾ãã</p>
<p>ã¾ããç§ãã¡ã¯èªåãããã¤ã®çµæã Slack ãã£ã³ãã«ã«éç¥ãã¦ãã¾ããããã«ã¯ Flux ã® <a href="https://fluxcd.io/flux/monitoring/alerts/">Alert æ©è½</a>ã使ã£ã¦ããã®ã§ããããããã¤é²è¡ä¸ã« <code>helm rollback</code> ã§å²ãè¾¼ãã å ´åã«ãã¼ã«ããã¯ã®å®äºããããã¤å®äºã¨ãã¦éç¥ããã¦ãã¾ãããããããã¾ããããã®ããã<code>helm rollback</code> ã®å®è¡åã«ãããã¤éç¥ã®ããã® Alert ãåæ¢ãã¦ãã¾ãã</p>
<p>ã¾ã¨ããã¨ããã®ã¹ã©ãã·ã¥ã³ãã³ãã¯ä¸ã®æä½ãé çªã«è¡ãã¾ãã</p>
<ul>
<li><code>flux suspend helmrelease {ã¢ããªã±ã¼ã·ã§ã³å}</code></li>
<li><code>flux suspend alert {ã¢ããªã±ã¼ã·ã§ã³å}-deploy-notifier</code></li>
<li><code>helm rollback {ã¢ããªã±ã¼ã·ã§ã³å} {ãã¼ã«ããã¯å¯¾è±¡ã®ãªãã¸ã§ã³}</code></li>
</ul>
<p>å®éã«ã¯ããããã®æä½ã¯ CLI ã®å¼ã³åºãã§ã¯ãªã Go API ã使ã£ã Kubernetes API ã®å¼ã³åºãã¨ãã¦å®è£
ããã¦ãã¾ããFlux ã® suspend æä½ã¯å¯¾è±¡ãªãã¸ã§ã¯ãã® <code>.spec.suspend</code> ãã£ã¼ã«ãã true ã«ãããã¨ã§å®ç¾ã§ããããã <code>sigs.k8s.io/controller-runtime/pkg/client</code> ããã±ã¼ã¸ã使ã£ã¦ãªãã¸ã§ã¯ãã®ãããæä½ã¨ãã¦å®è£
ãã¦ãã¾ãã<code>helm rollback</code> ã«ã¤ãã¦ã¯ Helm ã Go SDK ãæä¾ãã¦ãããããããã®ã¾ã¾å©ç¨ãã¦ãã¾ãã
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhelm.sh%2Fdocs%2Ftopics%2Fadvanced%2F%23go-sdk" title="Advanced Helm Techniques" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://helm.sh/docs/topics/advanced/#go-sdk">helm.sh</a></cite></p>
<p><figure class="figure-image figure-image-fotolife" title="/laboty global-web-platform rollback ãå®éã«å®è¡ããæ§å"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/coorde/20241028/20241028104825.png" width="1200" height="250" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>/laboty global-web-platform rollback ãå®éã«å®è¡ããæ§å</figcaption></figure></p>
<h2 id="復æ§ã®ã¹ã©ãã·ã¥ã³ãã³ã">復æ§ã®ã¹ã©ãã·ã¥ã³ãã³ã</h2>
<p>ä¸ã§èª¬æããéããä»åå®è£
ãããã¼ã«ããã¯æä½ã¯ãèªåãããã¤ãåæ¢ãã¾ããããã¯ãã¼ã«ããã¯ãè¦ãããããªç·æ¥æã«ã¯é©åã§ãããã¢ããªã±ã¼ã·ã§ã³ãªãã¸ããªä¸ã§åé¡ã解æ¶ãããããã®ç¶æ
ã§ãããã¤ãè¡ããéçºãåéããããã«èªåãããã¤ãåéããå¿
è¦ãããã¾ããç§ãã¡ã¯ããã®ããã«æ¬¡ã®ãããªã¹ã©ãã·ã¥ã³ãã³ããå®è£
ãã¾ããã</p>
<pre class="code" data-lang="" data-unlink>/laboty global-web-platform recover-from-rollback {ã¢ããªã±ã¼ã·ã§ã³å} {å復ããã³ãããããã·ã¥} </pre>
<p>ãã®ã¹ã©ãã·ã¥ã³ãã³ãã¯ãèªåãããã¤ãåéããããã®ãã®ã§ãããããå復ããã³ãããããã·ã¥ã渡ãå¿
è¦ã¯ãªãã¨æãããããããã¾ãããããããå
ã«èª¬æããç§ãã¡ã®ãããã¤ããã¼ã§ã¯ãã¢ããªã±ã¼ã·ã§ã³ãªãã¸ããªã® main ãã©ã³ãã« PR ããã¼ã¸ããã¦ãã HelmRelease ã® <code>.spec.values</code> ã¾ã§ã¤ã¡ã¼ã¸ã®ã¿ã°ãä¼æ¬ããã¾ã§ããç¨åº¦æéããããã¾ããããã¦éçºè
ã recover-from-rollback ãå®è¡ããã¿ã¤ãã³ã°ã§ã¾ã å復ããã³ãããã HelmRelease ã«åæ ããã¦ããªãã¨ãèªåãããã¤ãåéããé端ã«å¤ãï¼å復åã®ï¼ã¤ã¡ã¼ã¸ã®ãããã¤ãå§ã¾ã£ã¦ãã¾ãã¾ãããã®ãããªãã¹ãé²ããæå³ããã³ãããã®ã¤ã¡ã¼ã¸ãããããã¤ãåéãããã¨ã確å®ã«ãããããå復ããã³ãããããã·ã¥ãå¼æ°ã¨ãã¦åãåã£ã¦ããã確èªãããããªå®è£
ã«ãã¦ãã¾ããå
·ä½çã«ã¯ããã®ã¹ã©ãã·ã¥ã³ãã³ãã¯ä¸ã®æä½ãé çªã«è¡ãã¾ãã</p>
<ul>
<li>ã¢ããªã±ã¼ã·ã§ã³ã® HelmRelease ã® <code>.spec.values</code> ã«å復ããã³ãããããã·ã¥ã®ã¤ã¡ã¼ã¸ã¿ã°ãè¨è¿°ããã¦ãããã¨ã確èª</li>
<li><code>flux resume alert {ã¢ããªã±ã¼ã·ã§ã³å}-deploy-notifier</code></li>
<li><code>flux resume helmrelease {ã¢ããªã±ã¼ã·ã§ã³å}</code></li>
</ul>
<p><figure class="figure-image figure-image-fotolife" title="/laboty global-web-platform recover-from-rollback ãå®éã«å®è¡ããæ§å"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/coorde/20241028/20241028104847.png" width="1200" height="166" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>/laboty global-web-platform recover-from-rollback ãå®éã«å®è¡ããæ§å</figcaption></figure></p>
<h1 id="ã¾ã¨ã">ã¾ã¨ã</h1>
<p>Flux 㨠Helm ã使ã£ã¦ GitOps ããã¦ããã°ãã¼ãã«çãã©ãããã©ã¼ã ã«ããã¦ãéçºè
ã«ããå³æãã¼ã«ããã¯ãå®ç¾ããæ¹æ³ã«ã¤ãã¦ç´¹ä»ãã¾ãããåé ã§ç´¹ä»ããã¨ããããªãã¼ãã«ãã£ã¦ãã¼ã«ããã¯ããã¦ããæã¯é害çºçãã復æ§ã¾ã§40åã»ã©ããã£ã¦ãã¾ããã¨ããã£ãã®ã§ããããã®ããã¿ã®å°å
¥ã«ãã£ã¦åé¡ã®çºè¦å¾ç´ã¡ã«ãã¼ã«ããã¯ãéå§ã§ããããã«ãªãã¾ããã</p>
<p>ãã¡ããé害æã«ãµã¼ãã¹ã¸ã®å½±é¿ãæå°éã«ã¨ã©ããã¨ããæå³ã§ããã¼ã«ããã¯ã¯å¤§äºãªãã¨ã§ããããµã¼ãã¹éçºã®å´é¢ã«ããã¦ãããã¤ã§ãç´ã¡ã«ãã¼ã«ããã¯ã§ããã¨ããèªç¥ã¯é度ãä¸ããããã«å¤§äºã ã¨èãã¦ãã¾ããä»åã®å³æãã¼ã«ããã¯ã®å°å
¥ã¯ããããã£ãé¢ã§ãæå³ã®ããåãçµã¿ã«ãªã£ãã®ã§ã¯ãªããã¨æãã¾ãã</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn:1">
<a href="https://github.com/fluxcd/flux2/discussions/2916">https://github.com/fluxcd/flux2/discussions/2916</a><a href="#fnref:1" rev="footnote">↩</a></li>
<li id="fn:2">
helm rollback ã¯ãªãªã¼ã¹ã®ã¹ãã¼ã¿ã¹ã pending-upgrade ã ã£ãã¨ãã¦ãç¡è¦ãã¾ã<a href="#fnref:2" rev="footnote">↩</a></li>
</ol>
</div>
coorde
æ¥æ¬ã¨ã¢ã¡ãªã«ã®å¤ªå¹³æ´ãè¶
ããããã©ã¼ãã³ã¹æ¹åã®åãçµã¿: CloudWatch RUM ãä¸å¿ã¨ããããã©ã¼ãã³ã¹æ¸¬å®ã¨ãã¼ã¿æ´»ç¨æ¹æ³
hatenablog://entry/6802418398298034958
2024-10-23T11:00:00+09:00
2024-10-23T11:34:33+09:00 ã¯ããã« ããã«ã¡ã¯ãã¯ãã¯ããã SRE ã® @mozamimy ã§ããå
æ¥ãã®éçºè
ããã°ã§ One Experience ããã¸ã§ã¯ãã«ã¤ãã¦ã®ç´¹ä»ãããã¾ããã ãã®ããã¸ã§ã¯ãã«ããã¦ããããã¯æ¥æ¬çããã°ãã¼ãã«çã¸ã®ç§»è¡ã®éã®å
¨è¬çãªããã©ã¼ãã³ã¹å¨ãã«ã¤ãã¦åãçµãã§ãã¾ããã ããã©ã¼ãã³ã¹ã¨ä¸è¨ã§ãã£ã¦ãããã®ä¸ã«ã¯ãããã¯ã¼ã¯ãã¢ããªã±ã¼ã·ã§ã³ã¬ã¤ã¤ã§ã®ã¬ã¤ãã³ã·ãMySQL ãªã©ã®ããã«ã¦ã§ã¢ã§ã®ã¬ã¤ãã³ã·ãªã©ãæ§ã
ãªè¦å ãé¢ãã£ã¦ãã¾ãããããã®æ¹åã«ããã¦ãä½ãããéè¦ãªã®ã¯ã¾ã観測ãããã¨ã§ãã移è¡ã«ããã¦åãçµãã æ§ã
ãªä½æ¥ã®ãã¡ãããã§ã¯ CloudWatchâ¦
<h2 id="ã¯ããã«">ã¯ããã«</h2>
<p>ããã«ã¡ã¯ãã¯ãã¯ããã SRE ã® <a href="https://x.com/mozamimy">@mozamimy</a> ã§ããå
æ¥ãã®éçºè
ããã°ã§ One Experience ããã¸ã§ã¯ãã«ã¤ãã¦ã®ç´¹ä»ãããã¾ããã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2024%2F10%2F10%2F105832" title="æ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ãã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>ãã®ããã¸ã§ã¯ãã«ããã¦ããããã¯æ¥æ¬çããã°ãã¼ãã«çã¸ã®ç§»è¡ã®éã®å
¨è¬çãªããã©ã¼ãã³ã¹å¨ãã«ã¤ãã¦åãçµãã§ãã¾ããã</p>
<p>ããã©ã¼ãã³ã¹ã¨ä¸è¨ã§ãã£ã¦ãããã®ä¸ã«ã¯ãããã¯ã¼ã¯ãã¢ããªã±ã¼ã·ã§ã³ã¬ã¤ã¤ã§ã®ã¬ã¤ãã³ã·ãMySQL ãªã©ã®ããã«ã¦ã§ã¢ã§ã®ã¬ã¤ãã³ã·ãªã©ãæ§ã
ãªè¦å ãé¢ãã£ã¦ãã¾ãããããã®æ¹åã«ããã¦ãä½ãããéè¦ãªã®ã¯ã¾ã観測ãããã¨ã§ãã移è¡ã«ããã¦åãçµãã æ§ã
ãªä½æ¥ã®ãã¡ãããã§ã¯ CloudWatch RUM ã Calibre ã¨ãã£ããã¼ã«ãç¨ãã Web ãã©ã¦ã¶ããã®ã¢ã¯ã»ã¹ã®ããã©ã¼ãã³ã¹è¦³æ¸¬ã«ç¦ç¹ãå½ã¦ã¦ç´¹ä»ãã¾ãã</p>
<h2 id="ãã©ãããã©ã¼ã 移è¡ã«ããããã©ã¼ãã³ã¹ã®å£åãã§ããéãé¿ããã">ãã©ãããã©ã¼ã 移è¡ã«ããããã©ã¼ãã³ã¹ã®å£åãã§ããéãé¿ããã</h2>
<p>å
è¿°ã® One Experience ã®ç´¹ä»è¨äºã§ãæ¸ããã¦ããéããæ¥æ¬çã¨ã°ãã¼ãã«çã¯å®å
¨ã«ç¬ç«ãããã©ãããã©ã¼ã ä¸ã§åä½ãã¦ãã¾ããããªãã¡æ¥æ¬çã¨ã°ãã¼ãã«çã§ã¯ããã©ã¼ãã³ã¹ç¹æ§ãã¾ã£ããç°ãªãã¨ãããã¨ã§ãã</p>
<p>ã¾ãããã¼ã¿ã®çµ±åã«ãã MySQL é¢é£ã®ãã¼ã¿å¢å ã«ããããã©ã¼ãã³ã¹ç¹æ§ã®å¤åãæ¸å¿µäºé
ã®ä¸ã¤ã§ãããã°ãã¼ãã«çã¨æ¥æ¬çã®ãã¼ã¿éãæ¯è¼ããã¨æ¥æ¬çã®æ¹ã大ãããçµ±åå¾ã«ã¯ãã¼ã¿éã大å¹
ã«å¢å ããããã§ãããã¼ã¿ç§»è¡ã®ä½æ¥ã®ä¸é¨ã§ããç¶ç¶çãªãã¼ã¿ç§»è¡ã«ã¤ãã¦ã¯ãå
æ¥é´æ¨ã«ããè¨äºãå
¬éããã¦ãã¾ãã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2024%2F10%2F16%2F101605" title="DMS ãå©ç¨ããç¶ç¶çãªãã¼ã¿å¤æ´æ¤ç¥ - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>ãã®ä»ã«ãã¦ã¼ã¶ã«ã¨ã£ã¦ç¢ºå®ã«æªå½±é¿ãããã¨è¦è¾¼ã¾ãã¦ããã®ããã·ã¹ãã ãåä½ãã¦ãããªã¼ã¸ã§ã³ã®éãã«ããã¬ã¤ãã³ã·ã®å¢å ã§ãã両æ¹ã¨ã AWS ãå©ç¨ãã¦ãã¾ãããæ¥æ¬ç㯠ap-northeast-1 (æ±äº¬) ã«ãã°ãã¼ãã«ç㯠us-east-1 (ãã¼ã¸ãã¢åé¨) ã«ãããã¤ããã¦ãã¾ã<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>ã</p>
<p>One Experience ã§ã¯ã°ãã¼ãã«çã«æ¥æ¬çãçµ±åããã¨ããæ¹éã«ãªã£ã以ä¸ããã®ã¾ã¾ã®ã¢ã¼ããã¯ãã£ã ã¨æ¥æ¬ããã®ãã©ãã£ãã¯ã¯å¤ªå¹³æ´ã¨åç±³ã横æãããã¨ã«ãªãããããããã¯ã¼ã¯èµ·å ã®ã¬ã¤ãã³ã·ã®å¢å ã¯é¿ãããã¾ããããã®å·®ãåããããã«ã</p>
<ul>
<li>ã°ãã¼ãã«çã® Rails ã¢ããªã±ã¼ã·ã§ã³ã®å®è£
ãæ¹åãã¦ããã©ã¼ãã³ã¹ãä¸ãã</li>
<li>MySQL ã¯ã¨ãªã®æ¹åãé©åãªãã£ãã·ã¥ã®å©ç¨ãªã©ã§ããã©ã¼ãã³ã¹ãä¸ãã</li>
<li>ããã³ãã¨ã³ãã®å®è£
ãæ¹åãã¦è¦ããã®ããã©ã¼ãã³ã¹ãä¸ãã</li>
<li>ãã«ããªã¼ã¸ã§ã³ãããã¤: æ¥æ¬ã¦ã¼ã¶ããè¿ãå ´æã«ãµã¼ããç½®ã</li>
</ul>
<p>ãªã©ãªã©ã移è¡ã«ã¤ãã¦åãå§ãã段éã§ããããã¨æ¹åããä½å°ã»æ段ããããã¨ã¯æ¼ ç¶ã¨åãã£ã¦ãã¾ããããã ãåãçµã¿ã«ãã£ã¦ã¯å®è£
ã»éç¨ã³ã¹ãã大ãããããã¾ãã¯ç¾ç¶ããã£ããã¨ææ¡ãã¦ä½ããåãçµãã§ããã¨ããããèããå¿
è¦ãããã¾ãããã¾ããããã©ã¼ãã³ã¹å·®ãåããããã®å¤æææã¨ãã¦ã®è¨æ¸¬ããã¡ããã§ãããããã¯ããã¨ãã¦å®éã«ç§»è¡ãé²ãã¦ããã«ããã£ã¦æ¥æ¬çã¨ã°ãã¼ãã«çã®ããã©ã¼ãã³ã¹å·®ã®æ¥ã
ã®å¤åããã©ãã¯ããå¿
è¦ãããã¾ããã</p>
<p>ã¦ã¼ã¶ããè¦ãç·åçãªããã©ã¼ãã³ã¹ã測å®ããããã®ææ³ã¨ãã¦ãRUM (Real User Monitoring) ããã³ synthetic monitoring ãããã¾ããOne Experience ã§ã¯ RUM ã¨ã㦠Amazon CloudWatch RUM (ä»¥ä¸ CloudWatch RUM ã¨è¡¨è¨) ãæ¡ç¨ããsynthetic monitoring ã¨ã㦠<a href="https://calibreapp.com/">Calibre</a> ãæ¡ç¨ãã¾ãããCloudWatch RUM ã®å©ç¨ã«ããã£ã¦ãããã工夫ããç¹ãããã®ã§ãæ¬ç¨¿ã§ã¯ç¹ã« CloudWatch RUM ã«ã¤ãã¦æ·±æããã¦ããã¾ãã</p>
<h2 id="CloudWatch-RUM-ã®å°å
¥ã¨æ´»ç¨ããããã®å·¥å¤«">CloudWatch RUM ã®å°å
¥ã¨æ´»ç¨ããããã®å·¥å¤«</h2>
<h3 id="RUM-ã«ã¤ãã¦">RUM ã«ã¤ãã¦</h3>
<p>RUM (Real User Monitoring) ã¯ã¦ã¼ã¶ããè¦ãç·åçãªããã©ã¼ãã³ã¹ãè¨æ¸¬ããä¸ã§ã¡ã¤ã³ã¨ãªããã®ã§ãã¯ã©ã¤ã¢ã³ããµã¤ãã§è¨æ¸¬ããããã©ã¼ãã³ã¹ææ¨ã¨ãªãæ°å¤ãã¨ã©ã¼ãªã©ãåéããåæã»å¯è¦åããããã®ãã¼ã«ã§ãã</p>
<p>One Experience ã«ããã¦ã¯ RUM ã§åéãã <a href="https://web.dev/articles/vitals">Core Web Vitals</a> ã®ãã¡ãLCP (Largest Contentful Paint) ã KPI ã¨ãã¦å©ç¨ãããã¨ã«ãã¾ããã詳細ãªèª¬æã¯ãªã³ã¯å
ã«è²ãã¾ãããLCP ã¨ã¯ã¦ã§ããã¼ã¸ä¸ã®ãã£ã¨ã大ããªé¢ç©ãå ããç»åã¾ã㯠HTML ã¨ã¬ã¡ã³ããæç»ãããã¾ã§ã®æéãæãã¾ãã</p>
<p>RUM ãå®ç¾ããããã®ãµã¼ãã¹ã¯ Datadog ãªã©ãã¯ããããããã¨é¸æè¢ã¯ããã¾ããã以ä¸ã®çç±ãã CloudWatch RUM ãé¸æãã¾ããã</p>
<ul>
<li>競åã®ã½ãªã¥ã¼ã·ã§ã³ã¨æ¯ã¹ã¦æ¯è¼çå®ä¾¡</li>
<li>æ¢ã« AWS ãå©ç¨ãã¦ããã社å
ã§ã®æç¶ãã®ãããããçããããå°å
¥ã®ãã¼ãã«ãä½ã</li>
<li>çã®ãã°ã CloudWatch Logs ã«åºããã®ã§é«åº¦ãªåæããããããAWS ã®å¥ãµã¼ãã¹ã¨ã®é£æºãå¯è½</li>
</ul>
<h3 id="CloudWatch-RUM-ã®å°å
¥">CloudWatch RUM ã®å°å
¥</h3>
<p>CloudWatch RUM ã® Rails ã¢ããªã±ã¼ã·ã§ã³ã¸ã®çµã¿è¾¼ã¿ã AWS å´ã§ã®ãªã½ã¼ã¹ã®æºåã¯ç¹ã«é£ãããã¨ããªãã<a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-RUM-get-started.html">ããã¥ã¡ã³ã</a>ã«ãããã£ã¦ä»¥ä¸ã®ãããªæé ãè¸ãã°å®éã«ã¤ãã³ããã¼ã¿ãéä¿¡ãããç¶æ
ã«ãªãã¾ããã</p>
<ol>
<li>Cognito identity pool ãç¨æ</li>
<li>RUM ã® app monitor ãä½æ</li>
<li>2 ã§ä½æãã app monitor ã«ãã°ãéä¿¡ã§ãã権éãæ㤠IAM role ãä½æ</li>
<li>identity pool ã« IAM role ãç´ä»ã</li>
<li>Rails ã¢ããªã±ã¼ã·ã§ã³ã®ããã³ãã¨ã³ãã« RUM ç¨ã®ã³ã¼ãã¹ããããã追å </li>
</ol>
<p>対象ã¨ãªã Rails ã¢ããªã±ã¼ã·ã§ã³ã¯æ¥æ¬çã¨ã°ãã¼ãã«çã®ä¸¡æ¹ã«ãªãã¾ãããapp monitor ã¯ããã¦ä¸ã¤ã«ãã¾ãããæ¥æ¬çã»ã°ãã¼ãã«çã¨ãã«åä¸ã® web origin ã§ãã <a href="https://cookpad.com/">https://cookpad.com</a> ãå©ç¨ãã¦ãããã¨ã¨ãã®ã¡ã»ã©ç´¹ä»ããåæã«ããã¦ã¤ãã³ããã¼ã¿ãä¸ã¤ã® CloudWatch Logs ã°ã«ã¼ãã«ã¾ã¨ã¾ã£ã¦ããã»ããé½åãããããã§ãã</p>
<p>å°å
¥èªä½ã¯ç°¡åã§ããã詳細ãªåæãè¡ãããã«ããã¤ãã®è¨å®ãè¡ãå¿
è¦ããã£ãã®ã§ã以ä¸ã®ã»ã¯ã·ã§ã³ã§ãããã«ã¤ãã¦èª¬æãã¾ãã</p>
<h4 id="CloudWatch-Logs-ã¸ã®ã¨ã¯ã¹ãã¼ã">CloudWatch Logs ã¸ã®ã¨ã¯ã¹ãã¼ã</h4>
<p>ã¤ãã³ããã¼ã¿ã®è©³ç´°ãªåæããããå ´åãapp monitor ã®ä½ææã« CloudWatch Logs ã«åºåããè¨å®ãå
¥ããã¨ä¾¿å©ã§ããã¾ããã®éããã°ã°ã«ã¼ãã« expire ãè¨å®ãã¦ããã¨ããã§ããããCloudWatch Logs ã®ã³ã¹ãã®å¤ãããã°ã®åãè¾¼ã¿ãå ããã¨ã¯ãããã¹ãã¬ã¼ã¸ã«ãããã³ã¹ããç¡è¦ã§ãã¾ããã</p>
<h4 id="è¦ä»¶ã«å¿ãã-attribute-ã®è¿½å ">è¦ä»¶ã«å¿ãã attribute ã®è¿½å </h4>
<p>Rails ã¢ããªã±ã¼ã·ã§ã³ã® JavaScript ã³ã¼ã㧠RUM ã¯ã©ã¤ã¢ã³ããåæåããéãä»»æã® attribute ã追å ãããã¨ãã§ãã¾ããä»åã¯ãã°ãã¼ãã«çãæ¥æ¬çãåºå¥ããããã® railsAppãRails ã®ã³ã³ããã¼ã©ãåºå¥ããããã® railsControllerãRails ã®ã¢ã¯ã·ã§ã³ãåºå¥ããããã® railsAction ãããããè¨å®ãããã¨ã§åæãããããã¾ãããããããè¨å®ãã¦ãããã¨ã§ CloudWatch RUM ã®ã³ã³ã½ã¼ã«ã§ã¤ãã³ããçµãè¾¼ãã§åæã§ãã¾ãããã¨ãã°ãããã©ã«ãã§ä»ä¸ããã countryCode ãªã©ã® attribute ãçµã¿åããã¦ä»¥ä¸ã®ããã«ãã£ã«ã¿ããã¨ããã°ãã¼ãã«çã®ã¬ã·ã詳細ãã¼ã¸ã«æ¥æ¬ããã¹ãã¼ããã©ã³ã§ã¢ã¯ã»ã¹ããã¤ãã³ããã«çµãè¾¼ããã¨ãã§ãã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/mozamimy/20241022/20241022135117.png" width="1076" height="430" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h4 id="ãµã³ããªã³ã°ã¬ã¼ãã®è¨å®">ãµã³ããªã³ã°ã¬ã¼ãã®è¨å®</h4>
<p>RUM ã¯ã©ã¤ã¢ã³ãã®è¨å®ã®éã«ã¯é©åãªãµã³ããªã³ã°ã¬ã¼ããå®ããå¿
è¦ãããã¾ãã100% ã«è¿ä»ããã°è¿ä»ããã»ã©ãããã精度ã§ãã¼ã¿ãå¾ããã¾ããå½ç¶ã³ã¹ãã¯å¢å ãã¾ããCloudWatch RUM ã¯ã¤ãã³ãæ°ã«ãã課éãªã®ã§ãã©ãã£ãã¯éããããããã®æéãäºæ¸¬ããããã§ãããã£ããã¯ã©ã¦ãç ´ç£ããªããããããç¨åº¦è¦ç©ãã£ãä¸ã§å°ãªãå²åããå§ãã¦ãAWS Cost Explorer ãçºããªããè¦ä»¶ãå¶ç´ã«å¿ãã¦é©åã«èª¿æ´ããã¨ããã§ãããã</p>
<h3 id="åéãããã¼ã¿ã-AWS-ã³ã³ã½ã¼ã«ããåæãã©ãã¯ãã">åéãããã¼ã¿ã AWS ã³ã³ã½ã¼ã«ããåæã»ãã©ãã¯ãã</h3>
<p>ã¤ãã³ããã¼ã¿ã®åéãéå§ããã°ã以ä¸ã®ã¹ã¯ãªã¼ã³ã·ã§ããã®ããã« CloudWatch RUM ã®ã³ã³ã½ã¼ã«ãããã£ã«ã¿ãæéã§æãä¸ãã¦ããå½¢ã§ããã©ã¼ãã³ã¹ã«ã¤ãã¦åæãããã¨ãã§ãã¾ããã¨ã©ã¼ã JavaScript ã«ãã HTTP(S) éä¿¡ã®å®è¡ãã»ãã·ã§ã³ãã¨ã«ã¤ãã³ãã確èªãããªã©ãã¤ã³ã¯ãªã¡ã³ã¿ã«ã«åæããããããã©ã¼ãã³ã¹ã«ã¤ãã¦ãã£ã¨çºãããã§ãã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/mozamimy/20241022/20241022135335.png" width="1200" height="1082" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h4 id="75-ãã¼ã»ã³ã¿ã¤ã«ã§-LCP-ã確èªããã">75 ãã¼ã»ã³ã¿ã¤ã«ã§ LCP ã確èªããã</h4>
<p>ãã£ã½ãã§å°ãèéã®å¹ããªãã¨ããããããç¹ã« LCP ã® 75 ãã¼ã»ã³ã¿ã¤ã«ã®å¤ãã³ã³ã½ã¼ã«ä¸ã§ç¢ºèªã§ããªããã¨ã¯åé¡ã§ããã</p>
<p><a href="https://web.dev/articles/defining-core-web-vitals-thresholds">Core Web Vitals</a> ããå¼ç¨ãã以ä¸ã®å³ã®ããã«ãLCP ã®è¯ãæªããå¤æãããããå¤ã¨ãã¦ä¸è¬çã« 75 ãã¼ã»ã³ã¿ã¤ã«ãç¨ããã¨ããã¨ããã¦ãã¾ããå¹³åå¤ã§ã¯ãã¼ã¿ã«åããããå ´åã«ææ¨ã¨ãã¦é©åã§ãªããªã£ã¦ãã¾ãå ´åãããããã§ãããã¡ããè¦ä»¶ã«ãã£ã¦ãã®æ¡ä»¶ãã«ã¹ã¿ãã¤ãºã§ãã¾ãããOne Experience ã«ããã¦ã¯åºæ¬ã® 75 ãã¼ã»ã³ã¿ã¤ã«ã§ 2.5 ç§ä»¥å
ãåºæºã¨ãããã¨ã«æ±ºãã¦ããã®ã§ãå¹³åå¤ããè¦ãããªããã¨ã¯åé¡ã§ããã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/mozamimy/20241022/20241022135318.png" width="1200" height="597" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ãã®ç¹ã«ã¤ãã¦ã¯ AWS ã«æ¢ã«è¦æãããã¦ãã¾ããããããèªåã§è§£æ±ºã§ããªããèãã¦ã¿ã¾ãããããããããã«ãã£ã³ã°ãããã¯ã®çµã¿åããã§ã¦ã¼ã¶ãã¨ã®è¦æ±ã«æè»ã«å¯¾å¿ã§ããã®ã AWS ã®å¼·ã¿ã§ãã</p>
<h3 id="CloudWatch-RUM-ã§åéãããã¼ã¿ãå©ç¨ããããããã«éè¨ãã">CloudWatch RUM ã§åéãããã¼ã¿ãå©ç¨ããããããã«éè¨ãã</h3>
<p>ãã¦ãããã¾ã§ã®æµãã§ã³ã³ã½ã¼ã«ã«é ¼ããã« RUM ã®ãã¼ã¿ãç¬èªã«éè¨ãã¦åæãããã¨ããã¢ããã¼ã·ã§ã³ã«ã¤ãã¦èª¬æãã¾ããããã®ã»ã¯ã·ã§ã³ã§ã¯ããããå®éã«ã©ã®ããã«å®ç¾ããããèãã¦ã¿ã¾ãã</p>
<h4 id="CloudWatch-Logs-Insights-ãå©ç¨ãã">CloudWatch Logs Insights ãå©ç¨ãã</h4>
<p>ã¯ããã«æãã¤ãã®ã CloudWatch Logs Insights ã§ããRUM ã®ãã¼ã¿ã¯ JSON æååã¨ã㦠CloudWatch Logs ã«ã¨ã¯ã¹ãã¼ãããã¦ããã®ã§èªç¶ã« Insights ãå©ç¨ã§ãã¾ããGrafana ããã¼ã¿ã½ã¼ã¹ã¨ã㦠CloudWatch Logs Insights ããµãã¼ããã¦ããã®ã§ããããå©ç¨ããã° Grafana ã§ããã·ã¥ãã¼ããä½ãããã§ãã</p>
<p>SQL ã«è¦ªããã§ãã身ã¨ãã¦æ§æã«ã¡ãã£ã¨ã¯ã»ãæãã¾ããããã¨ãã°ä»¥ä¸ã®ãããªã¯ã¨ãªã§ãæ¥æ¬ããã¢ãã¤ã«ããã¤ã¹ã§ã¬ã·ããã¼ã¸ã«ã¢ã¯ã»ã¹ããã¨ãã® LCP ã p75 ã§éè¨ãã¦æ±ãããã¨ãã§ãã¾ããæ¼ãå¨ã¨ãªã filter ããã¤ãã§ã¤ãªãã§ä¸ããã¬ã³ã¼ããæµãã¦ãããæå¾ã« stats ã§éè¨ããã¨ããã¤ã¡ã¼ã¸ã§ããã</p>
<pre class="code plain" data-lang="plain" data-unlink>filter event_type = "com.amazon.rum.largest_contentful_paint_event"
| filter metadata.railsApp = "Global"
| filter metadata.railsController = "recipes"
| filter metadata.railsAction = "show"
| filter metadata.countryCode = "JP"
| filter (metadata.deviceType = "mobile" or metadata.deviceType = "tablet")
| stats pct(event_details.value, 75)
</pre>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/mozamimy/20241022/20241022135410.png" width="1200" height="321" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ããã§ãã§ãããã§ãã... ã¨ã¯ããã¾ãããã¹ã¯ãªã¼ã³ã·ã§ããã®éè¨çµæã® <strong>7.8 GB (!)</strong> ã¨ããå¤ã«æ³¨ç®ãã¦ãã ãããããã¯è¦ãã¾ã¾ã¹ãã£ã³éã§ãããã«æ¯ä¾ãã¦ééçã³ã¹ãã¨ã¯ã¨ãªå®è¡æéããããã¾ããä¸è¿°ã®ä¾ã§ã¯æéã1 æ¥ã«çµã£ã¦éè¨ãã¦ãã®ã¹ãã£ã³éã¨ãªã£ã¦ãããã¢ãããã¯ãªåæãªã大ããªåé¡ã«ãªãã¾ããããæéã伸ã°ããä¸ã§ Grafana ä¸ã«ãããããã¤ã³ãä½ã£ã¦è¡¨ç¤ºãããã¨ãããã ãããããã®ã¯ã¨ãªãçºè¡ããããã¨ã«ãªã£ã¦ãã¾ãã®ã§å®ç¨çã§ã¯ããã¾ããã§ããã</p>
<h4 id="Timestream-ãå©ç¨ãããµããªã¼ãã¼ãã«ã®ä½æ">Timestream ãå©ç¨ãããµããªã¼ãã¼ãã«ã®ä½æ</h4>
<p>ãã®ãããªã·ãã¥ã¨ã¼ã·ã§ã³ã¯ CloudWatch Logs Insights ã«éããä¸è¬çãªãã¼ã¿åæããããã§ãããã®ãããªå ´åãéè¨ãå®æå®è¡ãã¦å°ç¨ã®ãã¼ãã«ã«ä¿åãã¦ããã®ã常å¥æ段ã§ãã</p>
<p>ã§ã¯ã©ãã«éè¨çµæãä¿åããã®ãã¨ãããã¨ãåé¡ã¨ãªãã¾ãããããã§ã¯ <a href="https://aws.amazon.com/jp/timestream/">Amazon Timestream for LiveAnalytics</a> (ä»¥ä¸ Timestream ã¨è¡¨è¨ãã¾ã) ãæ¡ç¨ãã¾ãããTimestream 㯠AWS ã®ããã¼ã¸ããªæç³»åãã¼ã¿ãã¼ã¹ã§ããããã¼ã¦ã¼ã¹ã«èãããã¨ãç¹é·ã¨ãã¦ãã¾ãããã©ã¤ããªä½¿ãæ¹ã§ãã³ã¹ããé常ã«å°ãªãæ¸ã¿ãéã«ãã¼ã¿ãå
¥ãã¦ã¯ã¨ãªã§ãã便å©ã¹ãã¬ã¼ã¸ã§ãããã¨ãå人çã«ã¯é
åã ã¨æãã¦ãã¾ããDynamoDB ãä¼¼ããããªç¨éã§ä½¿ãã¾ãããã·ã³ãã«ãª KVS ã§ã¯å¾®å¦ã«ãããã¨ããã«æãå±ããªãã¦ã¼ã¹ã±ã¼ã¹ãã«ãã¼ãã¦ããã¨ããã好ãã§ãã</p>
<p>ãã¦ã以ä¸ã« Timestream ããã³ Lambda ãç¨ããéè¨ã·ã¹ãã ã®æ¦è¦ã示ãã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/mozamimy/20241022/20241022135704.png" width="746" height="391" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ç¢å°ã¯ã¤ãã³ããã¼ã¿ã®æµãã示ãã¦ãããCloudWatch RUM ããã¨ã¯ã¹ãã¼ããããçã®ã¤ãã³ããã¼ã¿ã CloudWatch Logs ã«éãããCloudWatch Logs Insights API ãå©ã Lambda function ã Timestream table ã«çµæãä¿åããéçºè
ããã® Timestream table ã« Grafana ãéãã¦ã¯ã¨ãªããã¨ããå½¢ã«ãªã£ã¦ãã¾ãããã® Lambda function 㯠EventBridge ã«ãã£ã¦æ¥æ¬¡ã§å®è¡ãããããã«è¨å®ããã¦ãã¾ãã</p>
<p>ãã® Timestream ãã¼ãã«ã«å¯¾ãã¦ããã¨ãã°ä»¥ä¸ã®ãããªã¯ã¨ãªãå®è¡ããã¨ä»¥ä¸ã®ãããªçµæãè¿ã£ã¦ãã¾ããSQL 風ã«ã¯ã¨ãªã§ããã®ã§è³ã«ããããã§ãã</p>
<pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">select</span>
*
<span class="synSpecial">from</span>
cookpad_rum.global_web_lcp_jp
<span class="synSpecial">where</span>
time <span class="synStatement">between</span> ago(7d) <span class="synStatement">and</span> now()
<span class="synSpecial">order</span> <span class="synSpecial">by</span>
time <span class="synSpecial">desc</span>
;
</pre>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/mozamimy/20241022/20241022135841.png" width="1200" height="424" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>d_ ãã¬ãã£ãã¯ã¹ããã¤ã«ã©ã ã dimension (ãã¼ã®ãããªãã®) ã¨ãã¦è¨å®ãã¦ãã¾ããããã§ã¯ããã¤ã¹ã»Rails ã¢ã¯ã·ã§ã³ã»Rails ã³ã³ããã¼ã©ã dimension ã«ããããã«å¯¾ã㦠LCP ãä¿åããã¨ããå½¢ã«ãªã£ã¦ãã¾ãã</p>
<p>å®éã« Grafana ããã·ã¥ãã¼ãã«è¨å®ããã¦ããã¯ã¨ãªã¯ä»¥ä¸ã®ãããªæãã«ãªã£ã¦ãã¾ãããã¼ã»ã³ã¿ã¤ã«ãããã¤ã¹ãããã·ã¥ãã¼ãã®ãã«ãã¦ã³ããå¤æ´ã§ããããã« Grafana ã®å¤æ°ãå©ç¨ãã¦ãããã¨ãããã®ã¾ã¾ã ã¨ã®ã¶ã®ã¶ãã¦åããã«ããå¾åããªãããã«è¦ãããããããã«ç§»åå¹³åãã¨ã£ã¦ããã¨ããããã½ã§ãã</p>
<pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">select</span>
time
, <span class="synIdentifier">avg</span>(lcp_p${statistics}_ms) over (<span class="synSpecial">order</span> <span class="synSpecial">by</span> time <span class="synSpecial">rows</span> <span class="synStatement">between</span> <span class="synConstant">5</span> preceding <span class="synStatement">and</span> <span class="synSpecial">current</span> <span class="synSpecial">row</span>) <span class="synSpecial">as</span> JP
<span class="synSpecial">from</span>
cookpad_rum.global_web_lcp_jp
<span class="synSpecial">where</span>
d_controller = <span class="synSpecial">'</span><span class="synConstant">recipe</span><span class="synSpecial">'</span>
<span class="synStatement">and</span> d_action = <span class="synSpecial">'</span><span class="synConstant">show</span><span class="synSpecial">'</span>
<span class="synStatement">and</span> d_device = <span class="synSpecial">'</span><span class="synConstant">$device</span><span class="synSpecial">'</span>
;
</pre>
<p>ããã¾ã§ã®ä½æ¥ã«ãã£ã¦ã以ä¸ã®ãããªæã㧠Grafana 㧠LCP ãå¯è¦åãããã¨ãã§ãã¾ãã<sup id="fnref:2"><a href="#fn:2" rel="footnote">2</a></sup>ãæ°´è²ã®ç·ã¯ One Experience ã® web çã®ç§»è¡ãå®å
¨ã«å®äºããæ¥ã§ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/mozamimy/20241022/20241022140001.png" width="1200" height="621" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ã¡ãªã¿ã«æ¥æ¬çã®ã¬ã·ããã¼ã¸ã® LCP ã¯ããã 0.9s ã§ãæ¥æ¬ããã¢ã¯ã»ã¹ããã°ãã¼ãã«çã® LCP ã¯ããã 1.3s ã§ããå
ããæªåãã¦ãããã¨ã¯å¦å®ã§ãã¾ãããã太平æ´ãã¾ãããã¨ã«ãã 150ms ã»ã©ã®ããã«ãã£ãããç¶æ
ã§ãCore Web Vitals ã§åºæºã¨ããã¦ãã 2.5s ããã¯éãã§ãããã¾ãã¾ãæªããªãã¨è¨ã£ã¦å·®ãæ¯ããªãã®ã§ã¯ãªãã§ããããã</p>
<p>ãã®ããã«ãã¦ãTimestream ãå©ç¨ã㦠RUM ã§åéããçã®ãã¼ã¿ã Grafana ããå©ç¨ããããå½¢ã«å¤ãããã¨ã§ãCloudWatch Metrics ã Prometheus ãéãã¦ã¯ã¨ãªã§ããä»ã®ã·ã¹ãã ã¡ããªã¯ã¹ã¨ãããã¦å®ä¾¡ãã¤é«éã«è¡¨ç¤ºã§ããããã«ãªãã¾ãããã¾ããGrafana ã®ä»ã«ã QuickSight ãªã©ã® BI ãã¼ã«ã¨é£æºãããã¨ãã§ããRedshift ã Athena ãéãã¦ã¯ã¨ãªã§ããå¤æ§ãªãã¼ã¿ã½ã¼ã¹ã¨çµã¿åããããªã©å¿ç¨ã®å¹
ãåºããã¾ãã</p>
<h3 id="å®éã«ç§»è¡ããåã«æ¥æ¬ããã¢ã¯ã»ã¹ããå ´åã®ããã©ã¼ãã³ã¹ã«ã¤ãã¦ç¥ããã">å®éã«ç§»è¡ããåã«æ¥æ¬ããã¢ã¯ã»ã¹ããå ´åã®ããã©ã¼ãã³ã¹ã«ã¤ãã¦ç¥ããã</h3>
<p>ãã¦ãããã¾ã§ã®è©±ã§ãCloudWatch ã§åéãã RUM ãã¼ã¿ã®åæãã§ããããã«ãªãã¾ãããããã RUM ã«ããã¦ã¯ãå®éã«ã¦ã¼ã¶ã« RUM ã¯ã©ã¤ã¢ã³ããéãã¦ãã¼ã¿ãéã£ã¦ãããå¿
è¦ãããã¾ããããªãã¡ãå®éã«ãªãªã¼ã¹ããã¾ã§ãã¼ã¿ãã¨ããªããã¨ãããã¨ã§ãã</p>
<p>ãã®ãããªåé¡ããããããä¸è¬çã« RUM 㨠synthetic monitoring ã®ä¸¡æ¹ã使ãåããã¨ããã¨ããã¦ãã¾ããsynthetic monitoring ã¯ãµã¼ãä¸ã®ãããã¬ã¹ãã©ã¦ã¶ãã人工çãªãªã¯ã¨ã¹ããçºçãããRUM ã®ããã«ã¡ããªã¯ã¹ãåéãã¦ããã©ã¼ãã³ã¹æ¹åã«å½¹ç«ã¦ããã¼ã«ã§ãããã¼ã«ã«ããæ©è½ã®å·®ç°ãããã¾ãããããã©ã¼ãã³ã¹ã®æ°å¤ãæç»æã®åç»ãåç·ç¶æ³ã®ã·ãã¥ã¬ã¼ã·ã§ã³ (å
ã±ã¼ãã«ãã¢ãã¤ã«åç·ãªã©)ãããã©ã¼ãã³ã¹æ¹åã®ããã®ãã³ããªã©ãæä¾ãããã®ãä¸è¬çã§ãã</p>
<p>One Experience ã§ã¯ãRUM 㨠synthetic monitoring ãçµã¿åããã¦ã以ä¸ã®ããã«ãã¦ãªãªã¼ã¹åãã観測ãè¡ã£ã¦ãã¾ããã</p>
<ul>
<li>ã°ãã¼ãã«çã«å°æ¹¾ããã¢ã¯ã»ã¹ããã¨ãã® RUM ãã¼ã¿ãæ´»ç¨ãã</li>
<li>synthetic monitoring ã¨ã㦠<a href="https://calibreapp.com/">Calibre</a> ãæ¡ç¨ãã¦ãã¹ããè¨å®ãã</li>
</ul>
<p>ã°ãã¼ãã«ç㯠One Experience 以åããå¤ãã®å½ã¨å°åã«åãã¦ãµã¼ãã¹ãå±éãã¦ãããå°æ¹¾ããã®ä¸ã®ä¸ã¤ã§ããå°æ¹¾ã«ã¯æ¥æ¬ã¨ä¼¼ãæ¯è¼çé«éãªãããã¯ã¼ã¯ç°å¢ããããå°ççã«ãè¿ãå ´æã«ããã¾ãããã®ãããå°æ¹¾ããéããã¦ãã RUM ã®ãã¼ã¿ã¯ãã³ããã¼ã¯ã¨ãã¦å©ç¨ããããã¨èãã¾ããã</p>
<p>å°æ¹¾ã® RUM ãã¼ã¿ã ãã§ã¯ä¸ååãªã¨ãããè£ããããå
è¿°ã® <a href="https://calibreapp.com/">Calibre</a> ãå°å
¥ãã¦ã°ãã¼ãã«çã®æ¥æ¬åããã¼ã¸ (<a href="https://cookpad.com/ja">https://cookpad.com/jp</a>) ã«å¯¾ãã¦ãã¢ã¯ã»ã¹å
ã®ãã±ã¼ã·ã§ã³ãæ¥æ¬ã«è¨å®ãã¦ãã¹ããè¨å®ãã¾ãããæ¬ç¨¿ã®è¶£æ¨ããå¤ãããã詳細ãªèª¬æã¯å²æãã¾ãããæ¯è¼çå®ä¾¡ã§æ¢ã«å©ç¨å®ç¸¾ããã£ããã¨ã¨ãæã
ã«ã¨ã£ã¦å¿
è¦ååãªæ©è½ãæãã¦ããã®ã Calibre ãæ¡ç¨ããçç±ã§ãã</p>
<p>Synthetic monitoring ã«ãã決ã¾ã£ããã¼ã¸ã¸ã®äººå·¥çãªãªã¯ã¨ã¹ãã§ã¯ãã©ããã¦ãåç·ç¶æ³ãå«ããå®éã®ã¦ã¼ã¶ã®ç¶æ³ã¨ä¹é¢ãã¦ãã¾ãé¨åã¯ããã¾ãããããã¾ããªå¾åã¯æ´ãã¾ããã©ãã«æ¹åããä½å°ãããã®ãã調æ»ããå©ãã«ãªãã¾ãããCalibre ãã¯ããã¨ãã synthetic monitoring ãã¼ã«ã«å®è£
ããã¦ãããæ¹åç¹ãã¢ããã¤ã¹ãã¦ãããæ©è½ãæ´»ç¨ãã¾ããã</p>
<p>å®æãã¹ãå®äºæã« webhook ãä»ãã¦å¤é¨ã«éç¥ããæ©è½ã Calibre ã«ã¯ããã®ã§ããããã®éç¥ãåã㦠Timestream ã«ãã¼ã¿ãæ ¼ç´ããGrafana ã§å¯è¦åããä»çµã¿ãä½ãã¾ãããããã¯ãã㧠Lambda ã® Function URL ãæ´»ç¨ãã¦ããããFunction URL ã®å¼±ç¹ãè£ãã¡ãã£ã¨ããèªè¨¼ã®ä»çµã¿ãå
¥ãã¦ã¿ãããTimestream ãæ´»ç¨ããããªã©å人çã«é¢ç½ãã¨æã£ã¦ããè¦ç´ ãè©°ã¾ã£ãä»çµã¿ãªã®ã§ãããè¨äºãã©ãã©ã大ãããªãã®ã§ããã§ã¯å²æãã¾ããã¨ãããã以ä¸ã®ã¹ã¯ãªã¼ã³ã·ã§ããã®ãããªæã㧠synthetic monitoring ã®ã¡ããªã¯ã¹ã Grafana ã«åºããããã«ãªãã¾ããã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/mozamimy/20241022/20241022140019.png" width="1200" height="293" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h2 id="ã¾ã¨ã">ã¾ã¨ã</h2>
<p>æ¬ç¨¿ã§ã¯ãOne Experience ã«ãããããã©ã¼ãã³ã¹é¢é£ã®åãçµã¿ã®ãã¡ãã¦ã¼ã¶ããä¸çªè¿ãå ´æã§ã®ããã©ã¼ãã³ã¹æ
å ±ãåéãã¦å¯è¦åãããã¨ã«ãã©ã¼ã«ã¹ãã¦èª¬æãã¾ããã</p>
<p>ãããã®ä»çµã¿ãæ´ããä¸ã§ãä»ã®ã·ã¹ãã ã¡ããªã¯ã¹ãæä½æ¥ã§è¡ããã¹ãã«ããå®æ§çãªåé¡ã®èª¿æ»ããã³ãããã©ã¼ãã³ã¹ã®è¦³æ¸¬çµæãçµã¿åããã¦ããã©ã¼ãã³ã¹ãæ¹åãã¦ãã¾ãããä¸éã One Experience ããªãªã¼ã¹ãããç¾å¨ããã¯ãã¯ããããå¿«é©ã«ä½¿ã£ã¦ãããããã®ããã©ã¼ãã³ã¹æ¹åã®åãçµã¿ã¯ç¶ãã¦ãã¾ãã</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn:1">
æ¥æ¬ããã¢ã¯ã»ã¹ããå ´åãæ±äº¬ã§ 5msãç±³å½è¥¿é¨ã§ 100msãç±³å½æ±é¨ã§ 150ms ãããããã® RTT ã®ç®å®ã¨ããã¦ãã¾ã<a href="#fnref:1" rev="footnote">↩</a></li>
<li id="fn:2">
One Experience ãªãªã¼ã¹åã®ãµã³ãã«æ°ãå°ãªããã¡ãããã®ä»çµã¿ãå
¥ãã¦ããããã7 æä¸æ¬ãããã®æ°å¤ãã¨ãã§ããªããã¨ã«ãªã£ã¦ãã¾ã£ã¦ãã¾ãã...ã<a href="#fnref:2" rev="footnote">↩</a></li>
</ol>
</div>
mozamimy
DMS ãå©ç¨ããç¶ç¶çãªãã¼ã¿å¤æ´æ¤ç¥
hatenablog://entry/6802418398296291522
2024-10-16T10:16:05+09:00
2024-10-16T10:16:05+09:00 SRE ã®é´æ¨ (id:eagletmt) ã§ããå
æ¥ããã®éçºè
ããã°ã§èµ¤æ¾ãã One Experience ããã¸ã§ã¯ãã«ã¤ãã¦ã®ç´¹ä»ãããã¾ããã èªåããã® One Experiene ããã¸ã§ã¯ãã«æºãã£ã¦ããããã®ããã¸ã§ã¯ããå§ã¾ã£ãã¡ããã©1å¹´ãããåã«ã¯ã¤ã®ãªã¹ã«ãããªãã£ã¹ã«è¡ããã°ãã¼ãã«çã®ã·ã¹ãã ãéçºã»éç¨ãã¦ããã¡ã³ãã¼ã¨ç´æ¥é¡ãåããããããã¦ãã¾ãããèªå㯠One Experience ããã¸ã§ã¯ãã«ããã¦ä¸»ã«ãã¼ã¿ç§»è¡ã«ã¤ãã¦æ
å½ãã¦ãã¾ããããã¼ã¿ç§»è¡ã«å¿
è¦ãªä½æ¥ã¯ããã¤ãããã¾ããããã®è¨äºã§ã¯ãã®ä¸ããæ¥æ¬çã®ã·ã¹ãã ã®ãã¼ã¿ãã¼ã¹ã§çºçãããã¼ã¿â¦
<p>SRE ã®é´æ¨ (<a href="http://blog.hatena.ne.jp/eagletmt/">id:eagletmt</a>) ã§ããå
æ¥ããã®éçºè
ããã°ã§èµ¤æ¾ãã One Experience ããã¸ã§ã¯ãã«ã¤ãã¦ã®ç´¹ä»ãããã¾ããã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2024%2F10%2F10%2F105832" title="æ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ãã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>èªåããã® One Experiene ããã¸ã§ã¯ãã«æºãã£ã¦ããããã®ããã¸ã§ã¯ããå§ã¾ã£ãã¡ããã©1å¹´ãããåã«ã¯ã¤ã®ãªã¹ã«ãããªãã£ã¹ã«è¡ããã°ãã¼ãã«çã®ã·ã¹ãã ãéçºã»éç¨ãã¦ããã¡ã³ãã¼ã¨ç´æ¥é¡ãåããããããã¦ãã¾ãããèªå㯠One Experience ããã¸ã§ã¯ãã«ããã¦ä¸»ã«ãã¼ã¿ç§»è¡ã«ã¤ãã¦æ
å½ãã¦ãã¾ããããã¼ã¿ç§»è¡ã«å¿
è¦ãªä½æ¥ã¯ããã¤ãããã¾ããããã®è¨äºã§ã¯ãã®ä¸ããæ¥æ¬çã®ã·ã¹ãã ã®ãã¼ã¿ãã¼ã¹ã§çºçãããã¼ã¿ã®å¤æ´ãã©ã®ããã«ãã¦ã°ãã¼ãã«çã®ã·ã¹ãã ã®ãã¼ã¿ãã¼ã¹ã«ç¶ç¶çã«åæ ãããã®é¨åã«ã¤ãã¦ç´¹ä»ãã¾ãã</p>
<h1 id="One-Experience-ã§ã®ãã¼ã¿ç§»è¡">One Experience ã§ã®ãã¼ã¿ç§»è¡</h1>
<p>赤æ¾ã®è¨äºã«ãæ¸ããã¦ããããã«ãæ¥æ¬çã¨ã°ãã¼ãã«çã¯å®å
¨ã«ç¬ç«ããã·ã¹ãã ã¨ãã¦åãã¦ãããAurora MySQL ãå©ç¨ãã¦ããã¨ããå
±éç¹ã¯ããã¤ã¤ããã¼ã¿ãã¼ã¹ãµã¼ãã¯ããããç¬ç«ãã¦ãã¾ãããã°ãã¼ãã«çã®ã·ã¹ãã ã¯ããã¤ãã®ã¬ã·ããµã¼ãã¹ãè²·åãåãè¾¼ã¿ãªããæé·ãã¦ããæ´å²ãããã¾ãããåãè¾¼ã¿å
ã®ãµã¼ãã¹ãä¸åº¦åæ¢ãã¦ãããã¼ã¿ã移è¡ããã®ãåºæ¬ã§ããã</p>
<p>ä»åã®å ´åãåãè¾¼ã¿å
ã®ãµã¼ãã¹ã«ãããæ¥æ¬çã®ã·ã¹ãã ãä¸åº¦åæ¢ãã¦ãããã¼ã¿ç§»è¡ãè¡ãã¨ããæ段ãåãå¯è½æ§ãèãã¾ããããå©ç¨è
ã®å¤ãæ¥æ¬çã®ã·ã¹ãã ã«åæ¢æéãè¨ãããã¨ã«ã¯é«ããªã¹ã¯ãããã¾ãããã°ãã¼ãã«çã«ç§»è¡ãããã¼ã¸ã§ã³ã®ã¢ãã¤ã«ã¢ããªã段éçã«ãªãªã¼ã¹ããè¨ç»ãæ¬çªç°å¢ã®å®ãã¼ã¿ã使ã£ã¦å°äººæ°ã«å¯¾ãã¦äºåã«ã¦ã¼ã¶ãã¹ããè¡ãè¨ç»ãæ©æã«æ±ºå®ãããã®ã§ã両æ¹ã®ã·ã¹ãã ã稼åãããªãããã¦ã³ã¿ã¤ã ç¡ãã§ãã¼ã¿ç§»è¡ãè¡ãæ¹éã«ãªãã¾ããããã®ããã«ã¯æ¥æ¬çã®ã·ã¹ãã ã§çºçãããã¼ã¿ã®å¤æ´ãç¶ç¶çã«ã°ãã¼ãã«çã®ã·ã¹ãã ã«åæ ããããã¿ãå¿
è¦ã¨ãªãã¾ããå®éããã®ããã㧠One Experience ã®ãã¼ã«ã¢ã¦ãã段éçã«å®å
¨ã«é²ãããã¨ãã§ãã¾ããã</p>
<h1 id="ãã¼ã¿å¤æ´ãæ¤ç¥ããæ¹æ³">ãã¼ã¿å¤æ´ãæ¤ç¥ããæ¹æ³</h1>
<p>ãã¼ã¿ã®å¤æ´ãç¶ç¶çã«åæ ããã«ã¯ãã¾ãã¯ãã¼ã¿ã®å¤æ´ãæ¤ç¥ããå¿
è¦ãããã¾ããæ¥æ¬çã®ã·ã¹ãã ã§ã¯ããã¤ã¯ããµã¼ãã¹éã§ãã¼ã¿é£æºãããããã« Ping ã¨ããã©ã¤ãã©ãªãç¨æã Rails ã¢ããªãã Amazon SNS ã«ã¤ãã³ããçºè¡ããããã«ãã¦ãã¾ãããPing ã«ã¤ãã¦ã¯éå»ã®è¨äº <a href="https://techlife.cookpad.com/entry/2017/05/10/130000">https://techlife.cookpad.com/entry/2017/05/10/130000</a> ã§ãå°ã触ãããã¦ãã¾ãã</p>
<p>ãããããã® Ping ãé·å¹´éç¨ãã¦ãããã¡ã«åºã¦ãã課é¡ã®1ã¤ã¨ãã¦ä¿¡é ¼æ§ããã¾ãé«ããªãã¨ããç¹ãããã¾ãããRails ã®ã¬ã¤ã¤ã§ã¤ãã³ããçºè¡ããããããã¨ãã°ãã¼ã¿ãæ´æ°ããã®ã«ã¤ãã³ããçºè¡ããå®è£
ãå¿ãã¦ãã¾ã£ããããã¼ã¿ãæ´æ°ãã¦ããã¤ãã³ããçºè¡ããã¾ã§ã®éã«äºæãã¬ä¾å¤ãªã©ã§å¦çãä¸æãã¦ãã¾ã£ãããã¤ãã³ããçºè¡ããå®è£
ãçµç±ããªããããªã¤ã¬ã®ã¥ã©ã¼ãªæ´æ°å¦çãå®è¡ãã¦ãã¾ã£ããã¨ããã¼ã¿ãæ´æ°ããã¦ããã®ã«ã¤ãã³ããçºè¡ãããªãã±ã¼ã¹ãããã¤ãèãããã¾ããã</p>
<p>ä»åã¯ããä¿¡é ¼æ§ã®é«ãæ¹æ³ã¨ã㦠MySQL ã® binlog ã使ããã¨ãèãã¾ãããbinlog ã§ããã°ã©ã®ãããªæ段ã§æ´æ°ããã¦ããã¼ã¿ã®å¤æ´ãæ¤ç¥ã§ãããã§ããä¸æ¹ã§ binlog ãæ±ããããªã½ããã¦ã§ã¢ãèªä½ãããã¨ã«ã¯ãããã¼ãã«ãããã¾ãããbinlog ä¸ã®ã¤ãã³ããæ£ãã解éããå¿
è¦ãããã¾ãããbinlog ãã©ãã¾ã§èªãã ããä¸è²«æ§ãæã£ã¦ã©ããã«æ°¸ç¶åããå¿
è¦ããã£ããã¨ãèæ
®ããªããã°ãªããªãç¹ãããã¤ãããããã§ããããã§ãAWS Database Migration Service (DMS) ã«çç®ãã¾ãããDMS ã¯ãã®åã®éããã¼ã¿ãã¼ã¹ç§»è¡ç¨ã®ãµã¼ãã¹ã§ããããã®æ©è½ã®1ã¤ã¨ã㦠MySQL ã® binlog ãèªã㧠Amazon Kinesis Data Streams ã«ãã®å
容ãéä¿¡ãããã¨ãã§ãã¾ãã<br/>
<a href="https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Task.CDC.html">https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Task.CDC.html</a><br/>
<a href="https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.Kinesis.html">https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.Kinesis.html</a><br/>
Kinesis Data Streams ã«ãã¼ã¿ãã¹ããªã¼ã ãããã¨ãã§ããã°ããã®å
㯠AWS Lambda ãèµ·åããã Kinesis Client Library (KCL) ã使ã£ãããããã¨ãã§ããã®ã§ããã¼ã¿ã®å¦çæ¹æ³ã®å¹
ãä¸æ°ã«åºããã¾ããDMS ã® transformation rules ã§ãç°¡åãªãã¼ã¿ã®å å·¥ã¯ã§ãã¾ãããæ¥æ¬çã®ã·ã¹ãã ã¨ã°ãã¼ãã«çã®ã·ã¹ãã ã§ã¯ MySQL ã®ãã¼ãã«ã®ã¹ãã¼ããå½ç¶ç°ãªã£ã¦ããèªç±åº¦é«ããã¼ã¿ãå å·¥ãã¦ç§»è¡ããå¿
è¦ããã£ããããä»å㯠Kinesis Data Streams ãã Lambda ãèµ·åãã¦ãã¼ã¿ãå å·¥ããªããã°ãã¼ãã«çã®ã·ã¹ãã ã«ç¶ç¶çã«ãã¼ã¿å¤æ´ãä¼ãããã¨ã«ãã¾ããã</p>
<h1 id="DMS-ãå©ç¨ããç¶ç¶çãªãã¼ã¿å¤æ´æ¤ç¥">DMS ãå©ç¨ããç¶ç¶çãªãã¼ã¿å¤æ´æ¤ç¥</h1>
<p>DMS ãå©ç¨ããç¶ç¶çãªãã¼ã¿å¤æ´æ¤ç¥ã®æ¦è¦ã¯ä»¥ä¸ã®ããã«å³ç¤ºã§ãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="DMS ãå©ç¨ããç¶ç¶çãªãã¼ã¿å¤æ´æ¤ç¥"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eagletmt/20241015/20241015161220.png" width="1200" height="335" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>DMS ãå©ç¨ããç¶ç¶çãªãã¼ã¿å¤æ´æ¤ç¥</figcaption></figure></p>
<h2 id="Aurora-MySQL---DMS">Aurora MySQL -> DMS</h2>
<p>ã¾ããäºåæºåã¨ã㦠Aurora MySQL 㧠binlog ãä½ãããè¨å®ãã¦ããã¾ããããã¯ãã©ã¡ã¼ã¿ã°ã«ã¼ãã§è¨å®ã§ãã¾ãã<br/>
<a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/USER_LogAccess.MySQL.BinaryFormat.html">https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/USER_LogAccess.MySQL.BinaryFormat.html</a><br/>
Aurora MySQL ã® binlog ãèªããããªæ©è½ã¯ DMS ã§ã¯ CDC ã¨å¼ã°ãã¦ãã¾ããAurora MySQL ãã½ã¼ã¹ã¨ããã¨ã³ããã¤ã³ããä½æããã¬ããªã±ã¼ã·ã§ã³ã¤ã³ã¹ã¿ã³ã¹ã¨ CDC ãæå®ããã¬ããªã±ã¼ã·ã§ã³ã¿ã¹ã¯ãä½æããã°ããã«ç¨æã§ãã¾ãã<br/>
ã¾ããä»åã®ç¨éã§ã¯å¤æ´åã®å¤ã¨å¤æ´å¾ã®å¤ã®ä¸¡æ¹ãç¥ãããã¨ããè¦ä»¶ãããã¾ããããã¨ãã°ã©ã®ã«ã©ã ãå¤æ´ãããã®ããç¥ãããå ´åãå¤æ´åã®å¤ãå¿
è¦ã«ãªãã¾ããDMS ã® Before Image ã¨ããæ©è½ã使ãã¨å¤æ´åã®å¤ãå«ã㦠Kinesis Data Streams ã«éããã¨ãã§ãã¾ãã<br/>
<a href="https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.Kinesis.html#CHAP_Target.Kinesis.BeforeImage">https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.Kinesis.html#CHAP_Target.Kinesis.BeforeImage</a><br/>
ãã®ç¹ã DMS ã®å©ç¨ã決ããçç±ã®1ã¤ã§ãã</p>
<p>DMS ã® CDC ãå©ç¨ããä¸ã§æ³¨æããå¿
è¦ãããã®ã¯ LOB ã®æ±ãã§ããLOB ã¨ã¯ large binary object ãæå³ãã DMS ã®ç¨èªã§ãAurora MySQL ã«ããã¦ã¯ mediumtext åã®å¤ãªã©ã該å½ãã¾ãã<br/>
<a href="https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.MySQL.html">https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.MySQL.html</a><br/>
LOB ã®ã«ã©ã ã®å¤ã¯ããã©ã«ãã§ã¯å«ã¾ããªããããmediumtext åãªã©ãããå ´å㯠LOB ã«é¢ããè¨å®ãå¿
è¦ã«ãªãã¾ãã<br/>
<a href="https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Tasks.LOBSupport.html">https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Tasks.LOBSupport.html</a><br/>
ãã㦠LOB ã®æ±ãæ¹ã«ã¤ãã¦ã¯ããã¤ã種é¡ãããã¾ãããKinesis Data Streams ãã¿ã¼ã²ããã¨ãã¦ããå ´å㯠Limited LOB mode ãã使ããªãã¨ããå¶éãããã¾ãã<br/>
<a href="https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.Kinesis.html#CHAP_Target.Kinesis.Limitations">https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.Kinesis.html#CHAP_Target.Kinesis.Limitations</a><br/>
ã¾ããBefore Image æ©è½ã使ãã¨ãã«ã LOB ã«ã¯å¶éãããã¾ããä»åã®ã±ã¼ã¹ã§ã¯ LOB ã«è©²å½ããã«ã©ã ãä¸é¨å«ã¾ãã¦ããã®ã§ Limited LOB mode ãæå¹ã«ãã¤ã¤ããã®ã«ã©ã ã¯å¤æ´åã®å¤ãç¥ãå¿
è¦ããªãã£ãã®ã§ãã®ã«ã©ã ã§ã¯ Before Image æ©è½ã¯ç¡å¹åãã¾ããã</p>
<h2 id="DMS---Kinesis-Data-Streams">DMS -> Kinesis Data Streams</h2>
<p>ãã®é¨å㯠DMS ã®ã¬ããªã±ã¼ã·ã§ã³ã¿ã¹ã¯ã®ã¿ã¼ã²ããã« Kinesis Data Streams ã«åããã¨ã³ããã¤ã³ããæå®ããã ããªã®ã§ã¨ãã«ããã¨ãã£ã工夫ã¯ããã¾ãããæ¥æ¬çã®ã·ã¹ãã ã¯ãã¤ã¯ããµã¼ãã¹ã«åå²ããã¦ããããããã¤ãã® Aurora MySQL ã¯ã©ã¹ã¿ãããã¼ã¿ç§»è¡ããå¿
è¦ããããAurora MySQL ã¯ã©ã¹ã¿æ¯ã« DMS ã®ã¬ããªã±ã¼ã·ã§ã³ã¿ã¹ã¯ãä½æãã¤ã¤ãéä¿¡å
ã® Kinesis Data Streams ã¯1ã¤ã ãã«ãã¾ããã</p>
<h2 id="Kinesis-Data-Streams---Lambda">Kinesis Data Streams -> Lambda</h2>
<p>Lambda ã® event source mapping ã«ãã㦠Kinesis Data Streams ãæå®ãããã¨ã§ãKinesis Data Streams ã«æµãã¦ãããã¼ã¿ãå
¥åã¨ã㦠Lambda ãèµ·åãããã¨ãã§ãã¾ãã<br/>
<a href="https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html">https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html</a><br/>
ããå¹ççã«ãã¼ã¿ãå¦çãããããããç¨åº¦ã¤ãã³ããæºãã¦ããã¾ã¨ã㦠Lambda ãèµ·åãããã¨ãã§ãã¾ã (ãããã batching)ã batching ãè¡ããã¨ã§ããå¹ççã«ãã¼ã¿ãå¦çãããã¨ãã§ãã¾ãããbatching ã§ã¾ã¨ããããã¤ãã³ãã®ä¸é¨ã ãå¦çã«å¤±æããã¨ãã®ãã¨ãèæ
®ããå¿
è¦ãã§ã¦ãã¾ãããã®ãããªç¶æ³ã«å¯¾å¿ãããããLambda ã§ã¯ ReportBatchItemFailures ãæå¹åãããã¨ã§é¨åçãªå¤±æãæ±ããã¨ãã§ãã¾ãã<br/>
<a href="https://docs.aws.amazon.com/lambda/latest/dg/services-kinesis-batchfailurereporting.html">https://docs.aws.amazon.com/lambda/latest/dg/services-kinesis-batchfailurereporting.html</a><br/>
ä»åã®ãã¼ã¿ç§»è¡ã§ããããæ´»ç¨ããbatching ãè¡ããªããå¿
è¦ãªãªãã©ã¤åæ°ãæ¸ãããã«ãã¦ãã¾ãã</p>
<h2 id="Lambda---Global-system">Lambda -> Global system</h2>
<p>Kinesis Data Streams ãçµç±ããªãã DMS ããã£ããã£ããå¤æ´åã®å¤ã¨å¤æ´å¾ã®å¤ãããã¦ãã®ã¡ã¿ãã¼ã¿ãå
¥åã¨ã㦠Lambda ãèµ·åããã¾ããLambda ã¯ã¡ã¿ãã¼ã¿ã«å«ã¾ãã¦ãããã¼ãã«åãå¤æ´ãè¡ãããã«ã©ã åãè¦ãªãããã°ãã¼ãã«çã®ã·ã¹ãã ã«å¿
è¦ãªãã¼ã¿ãçµã¿ç«ã¦ã¦éä¿¡ãã¾ãããã¼ã¿ã®ç¨®é¡ã«ãã£ã¦ã¯ãæ¥æ¬çã®ã·ã¹ãã ã§ã¯2ã¤ä»¥ä¸ã®ãã¼ãã«ã§è¡¨ç¾ãã¦ãããã¼ã¿ãã°ãã¼ãã«çã®ã·ã¹ãã ã§ã¯1ã¤ã®ãã¼ãã«ã«ãããã³ã°ããå¿
è¦ãããã±ã¼ã¹ããããDMS ããã£ããã£ãã1ã¤ã®ãã¼ãã«ã®å¤æ´å
容ã ãã§ã¯å¿
è¦ãªãã¼ã¿ã足ããªããã¨ãããã¾ããããã®å ´åã¯ãã¼ã¿å
ã® Aurora MySQL ã¯ã©ã¹ã¿ã«ã¯ã¨ãªãç´ãã¦ãã¼ã¿ãè£ããã¨ã«ãã¾ãããå³å¯ãªæå³ã§ã¯ä¸è²«æ§ãã¨ãã¦ããªãæ¹æ³ã§ããããã®ãããªæ¥½è¦³çãªæ¹æ³ã§ãã¼ã¿å¤æ´ãåæ ãã¤ã¤ãå¾ãããã¼ã¿ç§»è¡ã®å¤±æã«æ°ä»ããã¨ãã«ç§»è¡ãç´ãã¦æ´åæ§ãã¨ãã¨ããæ¹éã§ãã¼ã¿ç§»è¡ãé²ãã¾ããã</p>
<h2 id="ã³ã¹ã">ã³ã¹ã</h2>
<p>ãã®æ§æã«ããã¦ã¯ä¸»ã«</p>
<ul>
<li>DMS</li>
<li>Kinesis Data Streams</li>
<li>Lambda</li>
</ul>
<p>ã¨ãã AWS ã®ãµã¼ãã¹ãå©ç¨ãã¦ãã¾ãããæéé¢ã§æ¯é
çã ã£ãã®ã¯ DMS ã®ã¬ããªã±ã¼ã·ã§ã³ã¤ã³ã¹ã¿ã³ã¹ãKinesis Data Streams ã®ã·ã£ã¼ãããã㦠Lambda ã®ãã°ãä¿åãã CloudWatch Logs ã§ãããbatching ã®å¹æããããLambda ã®æéã¯ãããã¨æ¯ã¹ãã¨ã»ãã®ãããã§ããæéãã©ããããå¿
è¦ãã¯ãã¼ã¿ã®å¤æ´é »åº¦ãã©ãããããã«å¤§ããä¾åãã¾ãããã¯ãã¯ãããã¨ãããµã¼ãã¹ã¯æ´æ°ç³»ãããåç
§ç³»ã®ã»ãããã£ã¨å¤ãæ§è³ªãæã£ã¦ãããããDMS ã®ã¬ããªã±ã¼ã·ã§ã³ã¤ã³ã¹ã¿ã³ã¹ã¯ dms.t3.small ã§ååã§ããããKinesis Data Streams ã¯åä¸ã·ã£ã¼ãã§ãã»ã¨ãã©éã«åãè¦æ¨¡ã§ãããä¸æ¹ã§ã¤ã³ãã©ç®¡çã¯ãã¹ã¦ AWS ã«ä»»ãããã¨ãã§ãã¦ããã®ã§ãéç¨ã®æéãå«ããã³ã¹ãã¯ããªãå®ãã¨è¨ããã¨æãã¾ãã</p>
<h1 id="ã¾ã¨ã">ã¾ã¨ã</h1>
<p>One Experience ããã¸ã§ã¯ãã«ããããã¼ã¿ç§»è¡ãæ
å½ãããã®ä¸é¨ã§ããæ¥æ¬çã®ã·ã¹ãã ã®ãã¼ã¿ãã¼ã¹ã§çºçãããã¼ã¿ã®å¤æ´ãã°ãã¼ãã«çã®ã·ã¹ãã ã®ãã¼ã¿ãã¼ã¹ã«ç¶ç¶çã«åæ ããæ¹æ³ã«ã¤ãã¦ç´¹ä»ãã¾ãããbinlog ãèªãããã®ã©ã¤ãã©ãªã¯ä¸ã®ä¸ã«ããã¤ããããæåãªã¨ããã 㨠Go åãã®ã¢ã¸ã¥ã¼ã«ã® <a href="https://pkg.go.dev/github.com/go-mysql-org/go-mysql">github.com/go-mysql-org/go-mysql</a> ãå®è£
ãã¦ããããã¾ãããDMS ãå©ç¨ãããã¨ã§é¢åãªç®æã AWS ã®ããã¼ã¸ããµã¼ãã¹ã«ä»»ãããã¨ãã§ããã¨æãã¾ããä»åã¯ç°ãªãã·ã¹ãã éã®åæ¹åã®ç¶ç¶çãªãã¼ã¿ç§»è¡ã®ããã«å©ç¨ãã¾ããããæåã®ã»ã¯ã·ã§ã³ã§è§¦ãã Ping ã®ããã«ãµã¼ãã¹éã®ãã¼ã¿é£æºã®åºç¤ã¨ãã¦ãå©ç¨ã§ãããã§ãããæå¤ã¨å¿ç¨ç¯å²ã¯åºãããããã¾ããã</p>
eagletmt
æ¥æ¬ã¨ã°ãã¼ãã«ã®ã¯ãã¯ããããçµ±åãã¾ãã
hatenablog://entry/6802340630912747374
2024-10-10T10:58:32+09:00
2024-11-07T11:57:25+09:00 ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ãããã¯ãéçºã°ã«ã¼ãã®èµ¤æ¾(@ukstudio)ã§ãã æ¨å¹´ã®10æé ããã¬ã·ãäºæ¥é¨ã§ã¯ã²ã¨ã¤ã®å¤§ããªããã¸ã§ã¯ãã«åãçµãã§ãã¾ããããã®ããã¸ã§ã¯ãã¯ç¤¾å
ã§ã¯One experienceã¨å¼ã°ãã¦ãã¾ããæ¬è¨äºã§ã¯ãã®One experienceã«ã¤ãã¦ãç´¹ä»ãã¾ãã One experienceã¨ã¯ãªã«ã ã¯ãã¯ãããã§ã¯ã¬ã·ããµã¼ãã¹ãæ¥æ¬ãå«ãã71ã¶å½ã29è¨èªã§å±éãã¦ãã¾ããããã¾ã§ã¯æ¥æ¬ã®ãµã¼ãã¹ã¨æµ·å¤ã®ãµã¼ãã¹ã¯ç¬ç«ããå¥ã®ã·ã¹ãã ã§å¥ã®ãµã¼ãã¹ã¨ãã¦å±éãã¦ãã¾ãããæ¥æ¬ã®ã¬ã·ããµã¼ãã¹ã¯æ¥æ¬ã®çµç¹ããæµ·å¤ã®ã¬ã·ããµã¼ãã¹ã¯ã¤ã®ãªã¹ã«ãªãã£ã¹ãâ¦
<p>ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ãããã¯ãéçºã°ã«ã¼ãã®èµ¤æ¾(<a href="https://twitter.com/ukstudio">@ukstudio</a>)ã§ãã</p>
<p>æ¨å¹´ã®10æé ããã¬ã·ãäºæ¥é¨ã§ã¯ã²ã¨ã¤ã®å¤§ããªããã¸ã§ã¯ãã«åãçµãã§ãã¾ããããã®ããã¸ã§ã¯ãã¯ç¤¾å
ã§ã¯One experienceã¨å¼ã°ãã¦ãã¾ããæ¬è¨äºã§ã¯ãã®One experienceã«ã¤ãã¦ãç´¹ä»ãã¾ãã</p>
<h2 id="One-experienceã¨ã¯ãªã«ã">One experienceã¨ã¯ãªã«ã</h2>
<p>ã¯ãã¯ãããã§ã¯<a href="https://cookpad.com">ã¬ã·ããµã¼ãã¹</a>ãæ¥æ¬ãå«ãã71ã¶å½ã29è¨èªã§å±éãã¦ãã¾ããããã¾ã§ã¯æ¥æ¬ã®ãµã¼ãã¹ã¨æµ·å¤ã®ãµã¼ãã¹ã¯ç¬ç«ããå¥ã®ã·ã¹ãã ã§å¥ã®ãµã¼ãã¹ã¨ãã¦å±éãã¦ãã¾ãããæ¥æ¬ã®ã¬ã·ããµã¼ãã¹ã¯æ¥æ¬ã®çµç¹ããæµ·å¤ã®ã¬ã·ããµã¼ãã¹ã¯ã¤ã®ãªã¹ã«ãªãã£ã¹ãç½®ãæµ·å¤ã®çµç¹ã«ãã£ã¦éçºã»éå¶ãè¡ãªã£ã¦ãã¾ããã</p>
<p>One experienceã¯ãã®ç¬ç«ãã2ã¤ã®ãµã¼ãã¹ã1ã¤ã«çµ±åããå½å
ã»å½å¤é¢ä¿ãªããå
¨ã¦ã¼ã¶ã¼ã«åãä½é¨ãæä¾ãããã¨ãç®çã¨ããããã¸ã§ã¯ãã§ãã</p>
<p>ç¾å¨ããã®One experienceããã¸ã§ã¯ãã¯ã»ã¼å®äºãã¦ããããã§ã«æµ·å¤ã§éçºããã¦ããã·ã¹ãã (以å¾ã°ãã¼ãã«ç) ã«æ¥æ¬ã®ã¬ã·ããµã¼ãã¹ãçµ±åããã¦ãã¾ããä¸é¨ã®å¤ããã¼ã¸ã§ã³ã®ã¢ãã¤ã«ã¢ããªã使ã£ã¦ããã¦ã¼ã¶ã¼ãé¤ãã¦ã»ã¼ãã¹ã¦ã®æ¥æ¬ã®ã¦ã¼ã¶ã¼ã®ã¢ã¯ã»ã¹ãã°ãã¼ãã«çã·ã¹ãã ã«ãã£ã¦å¦çãããããã«ãªã£ã¦ãã¾ãã</p>
<h2 id="ãªã½ã¼ã¹ãéä¸ãã大ããªã¤ã³ãã¯ããçã¿åºã">ãªã½ã¼ã¹ãéä¸ããã大ããªã¤ã³ãã¯ããçã¿åºã</h2>
<p>ãªãç§ãã¡ã¯ã¬ã·ããµã¼ãã¹ãçµ±åãããã¨ã«ããã®ã§ããããã</p>
<p>ç§ãã¡ã¯ãæ¯æ¥ã®æçã楽ãã¿ã«ããããããã·ã§ã³ã«æ²ããå
¨åã§åãçµãã§ãã¾ãããã®ããã·ã§ã³ã®å®ç¾ã®ããã«ã¯æ§ã
ãªãã£ã¬ã³ã¸ãéãã¦å¤§ããªã¤ã³ãã¯ããçã¿åºãã¦ããå¿
è¦ãããã¾ãã</p>
<p>ã§ãããæ¥æ¬ã¨æµ·å¤ã®ãµã¼ãã¹ãããããå¥ã®çµç¹ã§éçºããã¦ããã¨ãéçºã®ããã®ãªã½ã¼ã¹ãåæ£ãã¦ãã¾ãã¾ãããã©ã¡ããã®ã¿ã«ä½ãããæ©è½ã¯å½ç¶ãã®ãµã¼ãã¹ã使ã£ã¦ããã¦ã¼ã¶ã¼ã«ããå±ãã¾ããã</p>
<p>ããã§ã¯ãªãããã大ããªä¾¡å¤ãããããããã®äººã«å±ããããã«ã2ã¤ã®ã¬ã·ããµã¼ãã¹ã1ã¤ã«çµ±åããå
¨å¡ã1ã¤ã®ãµã¼ãã¹ãéçºãããã¨ã¨ãªãã¾ããã</p>
<h2 id="æ¥æ¬ã®ã¬ã·ããµã¼ãã¹ãæµ·å¤ã®ã¬ã·ããµã¼ãã¹ã¸çµ±åãã">æ¥æ¬ã®ã¬ã·ããµã¼ãã¹ãæµ·å¤ã®ã¬ã·ããµã¼ãã¹ã¸çµ±åãã</h2>
<p>ã¬ã·ããµã¼ãã¹ãçµ±åãããã¨ã決ã¾ã£ããã¨ãæ¥æ¬ã®ã·ã¹ãã ããã¼ã¹ã«çµ±åããã®ããã°ãã¼ãã«çã®ã·ã¹ãã ããã¼ã¹ã«çµ±åããã®ãè°è«ãã¾ããã</p>
<p>æ¥æ¬çãã°ãã¼ãã«çã主è¦ãªWebã¢ããªã±ã¼ã·ã§ã³ãRailsã§å®è£
ããã¦ããã¨ããç¹ã§ã¯åãã§ãããã¤ã³ãã©ãWebããã³ãã¨ã³ãã§æ¡ç¨ãã¦ããæè¡ã¹ã¿ãã¯ããéåæã¸ã§ãããããå®è¡ã®ããã®ä»çµã¿ãªã©æ§ã
ãªç¹ãç°ãªã£ã¦ãã¾ããåæ§ã«ã¢ãã¤ã«ã¢ããªã±ã¼ã·ã§ã³ãæ¡ç¨ãã¦ããã¢ã¼ããã¯ãã£ãªã©æ¥æ¬çã¨ã°ãã¼ãã«çã¨ã§ç°ãªã£ã¦ãã¾ããã</p>
<p>ãã®ããã«æ¥æ¬çã¨ã°ãã¼ãã«çã«ã¯æ§ã
ãªéãããããã©ã¡ãã«çµ±åããã«ãã¦ããªãããã®ã¡ãªããã»ãã¡ãªãããåå¨ãã¾ãããã®ããã§ãæçµçã«ãã°ãã¼ãã«çããã¼ã¹ã«æ¥æ¬ã®ãµã¼ãã¹ãçµ±åãããã¨ãã決æãè¡ãã¾ããã16å¹´éç¨ããã¦ããæ¥æ¬ã®Railsã¢ããªãã10å¹´éç¨ããã¦ããã°ãã¼ãã«ã®Railsã¢ããªã¸çµ±åããããã¨ã¨ãªã£ãã®ã§ãã</p>
<p>決å®ã®èæ¯ã¨ãã¦ãæµ·å¤ã§å±éãã¦ããã¬ã·ããµã¼ãã¹ã¯ä»ã®å½ãå°åã®ã¬ã·ããµã¼ãã¹ãè²·åãåãè¾¼ããã¨ã§æé·ãã¦ããçµç·¯ãããããã®ããå¤é¨ã®ãã¼ã¿ãªã©ãåãè¾¼ãããã®æ©è½ãæ¢ã«åå¨ãã¦ãã¾ãããã¾ããè¤æ°å°åã»è¨èªã§ãµã¼ãã¹ãå±éãã¦ãããããå½éåã®ä»çµã¿ãåãã£ã¦ãã¾ãããã®ãããªä»çµã¿ããªãæ¥æ¬çã·ã¹ãã ã«çµ±åããããããã°ãã¼ãã«çã·ã¹ãã ã«çµ±åããæ¹ããã¼ã¿ã«ã§ãã£ã¨ãç´ æ©ãã·ã¹ãã ãçµ±åã§ããã¨å¤æãããããä»åã®æ±ºå®ã«è³ãã¾ããã</p>
<h2 id="ãµã¼ãã¹ã®çµ±åã¯é£ãã">ãµã¼ãã¹ã®çµ±åã¯é£ãã</h2>
<p>ã°ãã¼ãã«çã«æ¥æ¬ã®ã¬ã·ããµã¼ãã¹ã移è¡ãããã¨ã決ã¾ã£ãã¨ã¯è¨ããèãããã¨ã¯ã¨ã¦ãå¤ãããã¾ããã</p>
<p>ä¾ãã°ã°ãã¼ãã«çã¨æ¥æ¬çã®ã¬ã·ããµã¼ãã¹ã§ã¯ã¬ã·ããæ稿ããããæ¢ããã¨ãã§ããã¨ããã³ã¢ãªé¨åã¯ä¸ç·ã§ãããã°ãã¼ãã«çã«ãããªãæ©è½ãæ¥æ¬çã«ãããªãæ©è½ãã°ãã¼ãã«çã«ãæ¥æ¬çã«ããããä»æ§ãç°ãªãæ©è½ãUIãéããã®ãªã©æ§ã
ãªéããããã¾ãã</p>
<p>ãããã«ã¤ãã¦ã¯ä¸»ã«ä»¥ä¸ã®è¦³ç¹ããæ´çãè¡ãã¾ããã</p>
<ul>
<li>æ¥æ¬çã§å®è£
ããã¦ãã¦ãOne experienceããã¸ã§ã¯ãå®äºå¾ãæ®ãããæ©è½ã移æ¤ãã
<ul>
<li>= æ¥æ¬çã§å®è£
ããã¦ãã¦ãOne experienceããã¸ã§ã¯ãå®äºå¾ã«ãªããªãæ©è½ã決ãã</li>
</ul>
</li>
<li>ã°ãã¼ãã«çã§å®è£
ããã¦ãã¦ãOne experienceããã¸ã§ã¯ãå®äºå¾ã«æ®ããªãæ©è½ã¯åé¤ãã</li>
</ul>
<p>ç¹ã«æ¥æ¬çããã°ãã¼ãã«çã¸ã®æ©è½ã®ç§»æ¤ã¯ããã®ã¾ã¾ç§»æ¤ããã®ã§ã¯ãªããå©ç¨ãã¦ããã¦ããã¦ã¼ã¶ã¼ã«ã¨ã£ã¦ããã¹ãå½¢ã¯ä½ãªã®ããæ¹ãã¦èããªãã移æ¤ãè¡ãã¾ããããã®ããä¸é¨ã®æ©è½ã¯æ¥æ¬çã§æä¾ãã¦ãããã®ã¨å¤ãã£ã¦ãããã®ãããã¾ãã</p>
<p>ã¾ããæ¤ç´¢ã¯ã¬ã·ããµã¼ãã¹ã«ããã¦é常ã«éè¦ãªè¦ç´ ã§ãããç¹ã«äººæ°é ã¯ææä¼å¡åãã®æ©è½ã§ãããã£ã¦ãæ¥æ¬çã·ã¹ãã ã§å®ç¾ã§ãã¦ããã®ã¨åçã®å質ã§ã°ãã¼ãã«çã®æ¤ç´¢ã·ã¹ãã ã§ãæä¾ã§ããå¿
è¦ãããã¾ããã</p>
<p>ããã¦ãã°ãã¼ãã«çã¯æ¥æ¬çã¨ã¯å¥ã®ã·ã¹ãã ã¨ãã¦ç¨¼åãã¦ãããµã¼ãã¹ãªãããå½ç¶ãã¼ã¿ãã¼ã¹ãå¥ã§éç¨ããã¦ãã¾ããããã¯æ¥æ¬çã®ãã¼ã¿ãã°ãã¼ãã«çã®ãã¼ã¿ãã¼ã¹ã«æã£ã¦ããå¿
è¦ãããã¨ãããã¨ã§ããã°ãã¼ãã«çã¯æ¥æ¬ã®éçºçµç¹ã¨ã¯å®å
¨ã«ç¬ç«ãã¦0ããéçºããã¦ãããããæ¥æ¬çã¨ã°ãã¼ãã«çã§ã¯ãã¼ã¿ãã¼ã¹ã®ã¹ãã¼ãã«ãéããããã¾ããããã¼ã¿ã移è¡ããã«ããã£ã¦ãã¦ã³ã¿ã¤ã ãè¨ãããã©ããã§å¯¾å¿ã®é£æ度ãå¤ãã£ã¦ãã¾ããä»åããã¼ã¿ç§»è¡ã«ã¤ãã¦ã¯ä¸¡ã·ã¹ãã ã¨ããã¦ã³ã¿ã¤ã ãªãã§ãã¼ã¿ç§»è¡ãå®ç¾ã§ãã¾ããã</p>
<p>ãã®ãããªèª²é¡ãã²ã¨ã¤ã²ã¨ã¤è§£æ±ºããç¡äºçµ±åãå®äºããããã¨ãã§ãã¾ããããã¾ã ä»å¾ã®åå°ãã§ããã ãã¨ãè¨ãã¾ããä»å¾ã¯ããããããã®ä¾¡å¤ãå±ããããããå¼ãç¶ãéçºãç¶ãã¦ããã¾ããã¾ããä»åã®çµ±åã«ããã¬ã·ããµã¼ãã¹ãå©ç¨ãã¦ãã ãã£ã¦ããã¦ã¼ã¶ã¼ã«ã¨ã£ã¦ä½é¨ã大ããå¤ããã¾ãããç¾å¨ãæ§ã
ãªãæè¦ãå¯ãããã¦ããããããã«ãç®ãéããªããéææ¹åãç¶ãã¦ãã¾ãã</p>
<h2 id="ãããã«">ãããã«</h2>
<p>以ä¸ãOne experienceããã¸ã§ã¯ãã®æ¦è¦ã§ãã</p>
<p>One experienceããã¸ã§ã¯ãã¯ãæåã«ç«ã¦ãè¨ç»ããã®ã¾ã¾æå¾ã¾ã§å®è¡ããããã§ã¯ããã¾ãããéä¸ã§ã¦ã¼ã¶ã¼ã¤ã³ã¿ãã¥ã¼ããã¹ããå®æ½ãã¦ããã®çµæãè¸ã¾ãã¦å®è£
ãããã®ãå¤ãããã¨ãä½åº¦ãããã¾ãããæåã¯ãªããäºå®ã ã£ãæ©è½ãæçµçã«å¾©æ´»ããããã¨ãããã¾ãã</p>
<p>æçµçãªå½¢ãã¯ã£ããããªãã¾ã¾ããã¸ã§ã¯ããé²ããã®ã¯å¿ççã«ãããªãã®è² è·ãããã¾ããããã¦ã¼ã¶ã¼ã«ãããã価å¤ãå±ããããã«ã¯ãã®ããæ¹ã§è¯ãã£ãã¨ä»ã§ã¯æã£ã¦ãã¾ããæåã«ãã¹ã¦ã決ãã¦ãã¾ãã¨ããããä½ãåããã¨ã ããç®çã«ãªã£ã¦ãã¾ããããªãããã§ãã</p>
<p>ãã®ä¸ç¢ºå®ãªç¶æ³ã§ãOne experienceããã¸ã§ã¯ããå®éã§ããã®ã¯ããã¼ã¿ãã¤ã°ã¬ã¼ã·ã§ã³ãæ¡ç¨ããæè¡ã¹ã¿ãã¯ãªã©ã®æè¡çãªæ¹éãæåã«æ±ºãæå¾ã¾ã§ã»ã¼ãã¬ãªãã£ããã¨ãããã¸ã§ã¯ãã®åé åã®ãªã¼ãã¼ãã¡ããã£ããã¨åã
ã®è²¬ä»»ãæãããé£æºã§ããããã§ãããã</p>
<p>é·ããéç¨ãã¦ãããµã¼ãã¹ã®ã·ã¹ãã ãçµ±åãããã¨ã¯ããã§ã¯æ¸ããããªã課é¡ãããããããã¾ããã</p>
<p>ãã®è¨äºã§è»½ã触ãããã®ãããã¾ãããããã¤ãä¾ãæããã¨ä»¥ä¸ã®ãããªãã®ãããã¾ãã</p>
<ul>
<li>両ã·ã¹ãã ã稼åãããã¾ã¾ãã©ããã£ã¦æ¥æ¬çã®ãã¼ã¿ã®å¤æ´ãã°ãã¼ãã«çã«ãªã¢ã«ã¿ã¤ã ã«åæ ããã®ã</li>
<li>æ¥æ¬ã®æ¤ç´¢ãã°ãã¼ãã«çã®ã·ã¹ãã ã«ã©ã移æ¤ããã</li>
<li>æ¥æ¬ã®ãã¼ã 主å°ã§ä½ãæ©è½ãã©ã®ããã«ãã¦å½éå対å¿ããã</li>
<li>ã°ãã¼ãã«çã¸ã®ç§»è¡ã«ããæ¥æ¬ããã®ã¢ã¯ã»ã¹ã«ãããããã©ã¼ãã³ã¹ã®å£åãã§ããéã軽æ¸ããã</li>
<li>ãªã©ãªã©</li>
</ul>
<p>ãããã«ã¤ãã¦ãä»å¾ãã®ããã°ã§çºä¿¡ãã¦ããäºå®ã§ãããã²å¼ç¤¾ã®Xã¢ã«ã¦ã³ãããã©ãã¼ãã¦æ´æ°ããå¾
ã¡ãã ããã</p>
<p><a href="https://twitter.com/cookpad_tech">https://twitter.com/cookpad_tech</a></p>
ukstudio
Cookpad Summer Internship 2024ã«åå ãã¾ãã
hatenablog://entry/6802340630910914839
2024-10-02T16:58:12+09:00
2024-10-02T16:58:12+09:00 ã¯ããã« ããã«ã¡ã¯ã9æããã¯ãã¯ãããã§1ã¶æéãµãã¼ã¤ã³ã¿ã¼ã³ã·ããã«åå ãã¦ããä¸å°¾ã§ãã ä»åã¯ã¯ãã¯ãããã§1ã¶æééããã¦ã¿ã¦ããã£ããã¨ãæãããã¨ãã¬ãã¼ããã¦ããã¾ãã èªå·±ç´¹ä» ç§ã¯ç¾å¨ãç«å½é¤¨å¤§å¦ã®å¦é¨3å¹´ã§ãæ
å ±ç³»ãå°æ»ãã¦ãã¾ããä»åã®ã¤ã³ã¿ã¼ã³ã·ããã§ã¯é æ¹ã«ä½ãã§ããã¨ãããã¨ããããåãã®1é±éã¯ãªãã£ã¹ã«åºç¤¾ãã以éã¯ãªã¢ã¼ãã§å¤åãã¦ãã¾ããã宿æ³ããããã«ãããªãã£ã¹ã¾ã§ãå¾æ©2åãããã§æ¿ã¢ãã§ããã ã¤ã³ã¿ã¼ã³ã·ããã«åå ããã¾ã§ ã¯ãã¯ãããã¯æè¡åã®é«ãä¼ç¤¾ã¨ããã¤ã¡ã¼ã¸ã§ããã´ããã¶ã¤ã³ã好ãã ã£ãã¨ãããã¨ããã(ããããã§ãããï¼)ãåéãâ¦
<h2 id="ã¯ããã«">ã¯ããã«</h2>
<p>ããã«ã¡ã¯ã9æããã¯ãã¯ãããã§1ã¶æéãµãã¼ã¤ã³ã¿ã¼ã³ã·ããã«åå ãã¦ããä¸å°¾ã§ãã
ä»åã¯ã¯ãã¯ãããã§1ã¶æééããã¦ã¿ã¦ããã£ããã¨ãæãããã¨ãã¬ãã¼ããã¦ããã¾ãã</p>
<h2 id="èªå·±ç´¹ä»">èªå·±ç´¹ä»</h2>
<p>ç§ã¯ç¾å¨ãç«å½é¤¨å¤§å¦ã®å¦é¨3å¹´ã§ãæ
å ±ç³»ãå°æ»ãã¦ãã¾ããä»åã®ã¤ã³ã¿ã¼ã³ã·ããã§ã¯é æ¹ã«ä½ãã§ããã¨ãããã¨ããããåãã®1é±éã¯ãªãã£ã¹ã«åºç¤¾ãã以éã¯ãªã¢ã¼ãã§å¤åãã¦ãã¾ããã宿æ³ããããã«ãããªãã£ã¹ã¾ã§ãå¾æ©2åãããã§æ¿ã¢ãã§ããã</p>
<h2 id="ã¤ã³ã¿ã¼ã³ã·ããã«åå ããã¾ã§">ã¤ã³ã¿ã¼ã³ã·ããã«åå ããã¾ã§</h2>
<p>ã¯ãã¯ãããã¯æè¡åã®é«ãä¼ç¤¾ã¨ããã¤ã¡ã¼ã¸ã§ããã´ããã¶ã¤ã³ã好ãã ã£ãã¨ãããã¨ããã(ããããã§ãããï¼)ãåéãå§ã¾ãåããã¤ã³ã¿ã¼ã³ã·ããã«åå ãããã¨æã£ã¦ãã¾ããããããªã¨ãããã¤ãã®ããã«éæ³ã®ã¹ãã¬ããã·ã¼ãã¨ããã¤ã³ã¿ã¼ã³ã·ããã®æ
å ±ãéã¾ããµã¤ããç£è¦ãã¦ããã¨ãããã¯ãã¯ãããã®åéãè¦ã¤ãããããããã¾å¿åãã¾ãããæè¡èª²é¡ãæè¡é¢æ¥ãçµãã¦ãåæ ¼ã®é£çµ¡ãããã ããæã¯ã¨ã¦ãå¬ããã£ãã§ããã¿ãªã¨ã¿ããè¡ããï¼ï¼ï¼ã¨æã£ã¦ãããããªãã£ã¹ã移転ãã¦ãã¦æ¸è°·é§
ã®é£ã®æ± 尻大æ©ã§ãããã¿ãªãããã¯ãã¯ãããã¯ä»ã¯æ± 尻大æ©ã«ããã¾ãï¼ï¼ï¼</p>
<h2 id="ä¼ç¤¾ã®é°å²æ°">ä¼ç¤¾ã®é°å²æ°</h2>
<p>æè¡ã好ãã§ãæ§æ ¼ã¯ç©ãããªäººãå¤ãã¨æãã¾ãã
åé±ã«ã¤ã³ã¿ã¼ã³çã®ããã«æ親ä¼ãéãã¦ããã£ãã®ã§ãããããã§ãæè¡ã®ãã£ããã¢ããæ¹æ³ããããæè¡åããã£ã¨æ·±ãããããªã©ã®è³ªåãæ©ãã§ãããã¨ã話ãã¨ã親身ã«ãªã£ã¦è²ã
æãã¦ãã ããã¾ãããä»ã¾ã§ã¯ã¼ããããã¦ããèªåã«ã¨ã£ã¦ç®æãã¹ãã¨ã³ã¸ãã¢åãæ確ã«ãªããåè«æãã§äººçãå¤ãã£ãã¨æãã¾ããèªåã¯æè¡ãããã好ããªã®ã§ãã¿ããªã§ã¯ã¤ã¯ã¤æè¡ã«ã¤ãã¦è©±ãã¦æ¥½ããã£ãã§ãã
ã¾ããSlackã§ã®ä¼è©±ãçãã§ãæ°è»½ã«è³ªåãææ¡ãã§ããç°å¢ã§ããããã®ãããã§ããã¾ã話ãããã¨ã®ãªãæ¹ã«ãæ°è»½ã«ã¡ã³ã·ã§ã³ãã¦ã¿ã¹ã¯ã«ã¤ãã¦ã®è³ªåããããã¨ãã§ãã¾ããã</p>
<h2 id="ã¤ã³ã¿ã¼ã³ã·ããã®å
容">ã¤ã³ã¿ã¼ã³ã·ããã®å
容</h2>
<h3 id="ã¿ã¹ã¯">ã¿ã¹ã¯</h3>
<p>æåã¯onboardingã¿ã¹ã¯ã®ãããªæãã§ãç°¡åãªWebããã³ãã®ä¿®æ£ãªã©ããå§ãã¾ãããããããå¾ã
ã«å¤§ããã®ã¿ã¹ã¯ã触ããã¦ããããæ°è¦æ©è½ã®éçºã«ãæºãããã¨ãã§ãã¾ãããç§ã¯Railsã触ãã®ã¯åãã¦ã ã£ãã®ã§ãããåã¿ã¹ã¯ã§å°ããã¤è§¦ãé åãéã£ãã®ã§ãå¾ã
ã«Railsã®ãã¨ãããã£ã¦ãã¦æ¥½ããã£ãã§ããã¾ããDWHã«å¯¾ãã¦SQLã¯ã¨ãªãå©ãã¦å¯¾è±¡ã¬ã³ã¼ãã®æ°ã調æ»ããããä½æãããããå¦çã®å®è¡æéãè¨æ¸¬ãããã¯ãå®éã«éç¨ããã¦ãã大è¦æ¨¡ãªãããã¯ããªãã§ã¯ã¨ããæãã§æ¥½ããã£ãã§ãã
ã©ã®ãããªã¿ã¹ã¯ããããããããã¢ãªã³ã°ãã¦å°éãã¦ãããã®ã§ããããããã¨ãããã°ã©ãã©ãææ¦ã§ããç°å¢ã ã¨æãã¾ãï¼</p>
<h3 id="ã¹ã¯ã©ã ">ã¹ã¯ã©ã </h3>
<p>ç§ã¯ã¹ã¯ã©ã éçºãããã®ãåãã¦ã ã£ãã®ã§ãè¯ãçµé¨ã«ãªãã¾ãããç¾å¨ã®ã¯ãã¯ãããã®ã¹ã¯ã©ã éçºã§ã¯ã1é±éã1ã¹ããªã³ãã¨ãã¦ãé±ã«1度ã¹ããªã³ãã®ãã©ã³ãã³ã°ãã¬ãã¥ã¼ãè¡ãã¾ããã¾ããæ¯æ¥éã¾ã£ã¦é²æå ±åããããã¤ãªã¼ã¹ã¯ã©ã ã¨ãããã®ãããã¾ãããã¼ã ã£ã¦æãããã¦è¯ãã§ããï¼ï¼ï¼
ã¹ã¯ã©ã éçºã§ã¯ããã¼ã ã¨ãã¦ã®èª²é¡ãé²æãå
¨å¡ã§å
±æãã¦åãçµããã¨ãã§ããã®ã§ãå
¨ä½åãææ¡ããããã®ãè¯ãã¨æãã¾ãããã¾ãããã¤ãªã¼ã¹ã¯ã©ã ã§ãã¼ã ã¡ã³ãã¼ããããã®å£°ãèããã¨ãã§ããã®ã§ã親è¿æãé«ã¾ã£ã¦è©±ãããããããªã£ãã®ãããã£ãã§ãããã¼ã ã¨ãã¦ã®çµæåã¯ééããªãé«ã¾ãã¨æãã¾ãã
ä¸æ¹ã§ããã©ã³ãã³ã°ãã¬ãã¥ã¼ã§ããªãæéãåããã¦ãã¾ãã®ã§ãã¡ãªããããã¡ãªããããããããããªã¨æãã¾ããã</p>
<h3 id="ã¡ã³ã¿ãªã³ã°">ã¡ã³ã¿ãªã³ã°</h3>
<p>ãµãã¼ãã¯é常ã«å
å®ãã¦ãã¾ããã
ã¡ã³ã¿ã¼ã®æ¹ã¨æ¯æ¥1on1ãã¼ãã£ã³ã°ãéãã¦ãã¿ã¹ã¯ã®é²æãæ©ã¿ã®å
±æãã¡ãã£ã¨ããéè«ãªã©ããã¦ãã¾ãããã¾ãã1on1以å¤ã®æéã§ãããä»è©±ãã¾ããï¼ãã¨å£°ããããã¨æ°è»½ã«Slackã®ããã«ãã¼ãã£ã³ã°ã§è©±ããã¨ãã§ãã¾ãããã¨ã¦ãã³ãã¥ãã±ã¼ã·ã§ã³ã®åããããæ¹ã§ãåå¼·ã«ãªãé¨åãå¤ãã£ãã§ãã
ã¡ã³ã¿ã¼ã®æ¹ããä¼ã¿ã®æéããã£ãããããã®ã§ããããã®æéã¯å¥ã®æ¹ãã¡ã³ã¿ã¼ä»£çããã£ã¦ãã ããããµãã¼ãé¢ã§å°ã£ããã¨ã¯å
¨ãããã¾ããã§ããã</p>
<h3 id="ææçºè¡¨">ææçºè¡¨</h3>
<p>ã¤ã³ã¿ã¼ã³æéã®ç·æ¬ã¨ãã¦ãæå¾ã«ææçºè¡¨ãè¡ãã¾ããã
ææçºè¡¨ã¨ããã¨å°ãç·è¿«ããã¤ã¡ã¼ã¸ã湧ãã¦ãã¾ãããããã¾ããããã¯ã¤ã¯ã¤ã¨ããè³ãããªé°å²æ°ã§ããã
ã¹ã©ã¤ãã使ã£ã¦10-15åãããçºè¡¨ããã質çå¿çããã£ã¼ãããã¯ãè¡ãã¾ããã
èªåã®çºè¡¨ã«å¯¾ãã¦è³ªåããªã¢ã¯ã·ã§ã³ããããããã¦ãããã¦å¬ããã£ãã§ãï¼ï¼
<figure class="figure-image figure-image-fotolife" title="ææçºè¡¨ä¸ã®Slack"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/E/Exzrg/20241002/20241002164400.png" width="964" height="984" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ææçºè¡¨ä¸ã®Slack</figcaption></figure></p>
<h2 id="æå¾ã«">æå¾ã«</h2>
<p>ä»æ§ã«é¢ãã¦ã®ææ¡ãããããæ°è¦æ©è½ã®éçºã«æºãããã¦ããã ããªã©ãæ¬å½ã«1人ã®ç¤¾å¡ã®ããã«åããã¨ãã§ãã¦æ¥½ããã£ãã§ãã
1ã¶æéæ¬å½ã«ãããã¨ããããã¾ããï¼ï¼</p>
Exzrg
éçºç°å¢ã®ãã¼ã¿ãã¼ã¹ã§ãæ¬çªç°å¢ç¸å½ã®ãã¼ã¿ã使ã
hatenablog://entry/6802340630909597944
2024-10-01T10:55:03+09:00
2024-11-07T11:57:41+09:00 ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ããã¯ã¨ã³ãåºç¤ã°ã«ã¼ãã®ç³å·ã§ãã 2014 å¹´ããã®ããã°ã«ãéçºç°å¢ã®ãã¼ã¿ãã§ããã ãæ¬çªã«è¿ã¥ãããã¨ããã¿ã¤ãã«ã®è¨äºãæ稿ããã¾ããã ã¯ãã¯ãããã§ã¯ãã¦ã¼ã¶ã¼ãããå®éã«ä½é¨ãã¦ããç¶æ³ã¨è¿ãç¶æ³ãåç¾ããªããéçºãããã¨ã«ä¾¡å¤ãããã¨èãã¦ãã¾ããæè¡çã«ã¯ãæåããã¬ã³ã¼ãããããããããã¨ã«ãã£ã¦ããã©ã¼ãã³ã¹åé¡ã«æ°ä»ãããããªããªã©ã®é·æãããã¾ããããµã¼ãã¹éçºã¨ãã¦ããå®éã®ã¦ã¼ã¶ã¼ããã®ä½é¨ãæéã§ãªãã£ã¦ç´ æ©ããã£ã¼ãããã¯ã«ã¼ããåããããã«ãªãã¨ããé·æãããã¾ãã ãã®æ
£ç¿ã¯ 2014 å¹´ã®è¨äºãã 10 å¹´çµã£ãä»ã§ãç¶ãã¦ãâ¦
<p>ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ããã¯ã¨ã³ãåºç¤ã°ã«ã¼ãã®ç³å·ã§ãã</p>
<p>2014 å¹´ããã®ããã°ã«ãéçºç°å¢ã®ãã¼ã¿ãã§ããã ãæ¬çªã«è¿ã¥ãããã¨ããã¿ã¤ãã«ã®è¨äºãæ稿ããã¾ããã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2014%2F10%2F03%2F110806" title="éçºç°å¢ã®ãã¼ã¿ãã§ããã ãæ¬çªã«è¿ã¥ãã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>ã¯ãã¯ãããã§ã¯ãã¦ã¼ã¶ã¼ãããå®éã«ä½é¨ãã¦ããç¶æ³ã¨è¿ãç¶æ³ãåç¾ããªããéçºãããã¨ã«ä¾¡å¤ãããã¨èãã¦ãã¾ããæè¡çã«ã¯ãæåããã¬ã³ã¼ãããããããããã¨ã«ãã£ã¦ããã©ã¼ãã³ã¹åé¡ã«æ°ä»ãããããªããªã©ã®é·æãããã¾ããããµã¼ãã¹éçºã¨ãã¦ããå®éã®ã¦ã¼ã¶ã¼ããã®ä½é¨ãæéã§ãªãã£ã¦ç´ æ©ããã£ã¼ãããã¯ã«ã¼ããåããããã«ãªãã¨ããé·æãããã¾ãã</p>
<p>ãã®æ
£ç¿ã¯ 2014 å¹´ã®è¨äºãã 10 å¹´çµã£ãä»ã§ãç¶ãã¦ãã¾ããä¸æ¹ã§ãã®å®ç¾ææ³ã«ã¤ãã¦ã¯å¤åãç¶ãã¦ãã¾ãããç¾å¨ã®ã¯ãã¯ãããã§ã¯ç¶æ³ã«å¿ãã¦ããã¤ãã®æ段ã使ãåãã¦ãã¾ãããããã®æ段ã«ã¤ãã¦ã¯ä»ã¾ã§ãã¾ãå
¬éããã¦ããªãã£ããããªæ°ãããããããã®è¨äºã§ã¯ãã®ãã¡ã®ã²ã¨ã¤ããç´¹ä»ãããã¾ãã</p>
<p>ãªããã®è¨äºã§ç´¹ä»ããææ³ã¯ç¤¾å
ã§æ°å¹´ä½¿ã£ã¦ãããã®ã§ã以åå¼ç¤¾ã«å¨ç±ãã¦ããè
å (@sgwr_dts) ãä½æãããã®å¾å¥¥æ (@hfm)ãé´æ¨ (<a href="http://blog.hatena.ne.jp/eagletmt/">id:eagletmt</a>)ãå°å· (@coord_e)ãããã³èªåãå«ãããã¼ã ã¨ãã¦éçºãã¦ãã¾ããããæ¿ç¥ãããã ããã</p>
<h2 id="åæ">åæ</h2>
<p>ã¯ãã¯ãããã§ã¢ããªã±ã¼ã·ã§ã³ããããã¤ãããç°å¢ã¨ãã¦ã¯æ¬çªç°å¢ã®ä»ã«ãæ¬çªç°å¢ã¸åºãåã®æ¤è¨¼ä½æ¥ã§ä½¿ãç°å¢ï¼æ¤è¨¼ç°å¢ï¼ãããã¾ããããããµãã¤ã¯ AWS ä¸ã«ãããã¤ããã¦ãã¾ããããã¨ã¯å¥ã«éçºè
ã²ã¨ãã²ã¨ãã®æå
ã®ãã½ã³ã³ã§ã¢ããªã±ã¼ã·ã§ã³ãåããç°å¢ãéçºç°å¢ã¨å¼ã¶ãã¨ã«ãã¾ãããããã®ãããã¯æç®ã«ãã£ã¦å¾®å¦ã«å¼ã³æ¹ãç°ãªãã®ã§ããã®è¨äºã§ã¯ãããã®å¼ã³æ¹ã使ããã¨ã«ãã¾ãã</p>
<p>ã¯ãã¯ãããã§ã¯ RDBMS ã¨ã㦠Amazon Aurora MySQL ãå¤ç¨ãã¦ãããç¹ã« cookpad.com ãæä¾ããã¢ããªã±ã¼ã·ã§ã³ç¾¤ã使ã RDBMS ã®å¤ã㯠Aurora MySQL ã§ããAurora ã®ãªãªã¼ã¹ã <a href="https://aws.amazon.com/about-aws/whats-new/2014/11/12/introducing-amazon-rds-for-aurora/">2014 å¹´ 11 æããã</a> ã®ã§ãæ©é 10 å¹´åã«ã¯ãªãã£ããã®ãåºã¦ãã¾ãããã</p>
<p>ããã§ã¯ãããããAurora ã対象ã¨ãã¦ãå®æçã«æ¬çªç°å¢ã®ãã¼ã¿ãã¼ã¹ã®è¤è£½ãä½ã£ã¦æ¤è¨¼ç°å¢ãéçºç°å¢ã®ãã¼ã¿ãã¼ã¹ã¨ãã¦ä½¿ãããæ¹ã«ã¤ãã¦ã説æãã¾ãã</p>
<h2 id="Aurora-ã¯ã©ã¹ã¿ã¼ã®ãªã¹ãã¢">Aurora ã¯ã©ã¹ã¿ã¼ã®ãªã¹ãã¢</h2>
<p>ãã¦ãAurora ã§ã¯<a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-restore-snapshot.html">ãªã¹ãã¢</a>ã¨ããæä½ãè¡ããã¨ã§æ¢åã® DB ã¯ã©ã¹ã¿ã¼ãå
ã«åããã¼ã¿ãæã£ãå¥ã® DB ã¯ã©ã¹ã¿ã¼ãä½æã§ãã¾ããAurora ã®ãªã¹ãã¢ã¯ãããªãã«éããã¯ã©ã¹ã¿ã¼ã® VolumeBytesUsed ã 1 ãã©ãã¤ããè¶
ãã¦ãã¦ã 5 åç¨åº¦ã§å®äºãã¾ãããã®ãã¨åºæ¥ä¸ãã£ãã¯ã©ã¹ã¿ã¼ã« DB ã¤ã³ã¹ã¿ã³ã¹ãã²ã¨ã¤ä½ãä½æ¥ã« 10 åç¨åº¦ãããã®ã§ããã¡ãã®æ¹ãé·ãã§ãã</p>
<p>ãããã¦åºæ¥ä¸ãã£ãã¯ã©ã¹ã¿ã¼ããã®ã¾ã¾æ¤è¨¼ç°å¢ã¨ãã¦ä½¿ããã¨ã©ã¯ãªã®ã§ããããã®ã¾ã¾ã ã¨ã»ãã¥ãªãã£ããã©ã¤ãã·ã¼ä¸ã®æ¸å¿µãããã¾ãããã¨ãã°ã¦ã¼ã¶ã¼ããã®ã¡ã¼ã«ã¢ãã¬ã¹ãéçºè
å
¨å¡ãèªãã¦ãã¾ãã¨åé¡ãªããã§ãããããé¿ããããããªã¹ãã¢ã«ãã£ã¦åºæ¥ä¸ãã£ãã¯ã©ã¹ã¿ã¼ã«å
¥ã£ã¦ãããã¼ã¿ã®ãã¹ãã³ã°ãè¡ãã¾ãã</p>
<p>ã©ã®ãã¼ã¿ããã¹ã¯ãã¦ã©ã®ãã¼ã¿ããã®ã¾ã¾æ®ããã¯ãDB ã¯ã©ã¹ã¿ã¼ãã¨ã«è¨å®ãã¡ã¤ã«ãä½ã£ã¦ç®¡çãã¦ãã¾ãããã¨ãã°ä»¥ä¸ã® Jsonnet ãã¡ã¤ã«ã®ãããªæãã§ãããã®è¨å®ã§ã¯ãã©ã®ãã¼ãã«ãæ®ãã¦ããã許å¯ããä¸è¦§ãæã£ã¦ããã¦ããã以å¤ã®ãã¼ãã«ã¯ truncate ã§ç©ºã£ã½ã«ãã¦ãã¾ãã¾ãã</p>
<pre class="code jsonnet" data-lang="jsonnet" data-unlink>local allowed_tables = [
'recipes',
'ingredients',
'users',
// ãªã©ãªã©â¦â¦
];
{
database: 'global_main',
truncate: true,
only: allowed_tables,
pre_queries: [],
rules: [],
post_queries: [],
}</pre>
<p>ä¸ã®ä¾ã«ã¯æ¸ãã¾ããã§ããããç¹å®ã®ãã¼ãã«ã®ç¹å®ã®ã«ã©ã ã ãå¦çããã¯ã¨ãªãæµããã¨ãã§ãã¾ãããã¨ãã° credentials ãã¼ãã«ã® email ã«ã©ã ã®å¤ããã¹ã¦ <code>${user_id}@example.com</code> ã®ãããªããã¼æååã«æ¸ãæãã¦ãã¾ãããªã©ã§ãã</p>
<p>ãã ãç¹å®ã®ãã¼ãã«ã®ã«ã©ã ãæ¸ãæããå¦çã¯ããã¼ãã«ã巨大ãªå ´åã¯é·ãæéããããã¾ããå®éã«ãã£ãä¾ã¨ãã¦ãããä¸ã«æ¸ãã credentials ãã¼ãã«ã®æ¸ãæãã¯ãã¼ã¿éãå°ããå ´åã¯ãã¾ãããã®ã§ãããæ¬çªç°å¢ã®å·¨å¤§ãªãã¼ã¿ã§è©¦ãã¨ä¸æ©çµã£ã¦ãã¯ã¨ãªå®è¡ãçµããã¾ããã§ããããã¡ãã DB ã¤ã³ã¹ã¿ã³ã¹ã®ã¿ã¤ããã©ãããæ¸ãæãããããã§å®è¡æéã¯å¤ããã®ã§ããã®ãããã¯å
¼ãåãã«ãªãã¾ããcredentials ãã¼ãã«ã«ã¤ãã¦ã¯è¦ä»¶ã®æ¹ãå¤ããå
¨ã¬ã³ã¼ããæ®ããã¨ã¯è«¦ããä¸åº¦ã¬ã³ã¼ãããã¹ã¦æ¶ãããã¨ç¹å®ã®ã¹ã¿ããã¦ã¼ã¶ã¼ã ãæ°ããã¬ã³ã¼ããä½ãå¦çãå
¥ãã¦åé¿ãã¾ããã</p>
<p>ãªãããã®ãã¹ãã³ã°ã®ä»çµã¿ã¯ç¤¾å
çã«ã¯æ°ãããã®ã§ã¯ãªãã2020 å¹´ã«å
¬éãããAmazon RDS/Auroraãã¯ãã¼ã³ããã·ã¹ãã ãä½ã£ã話ãã§ãåãä»çµã¿ã使ã£ã¦ãã¾ããè¨å®ãã¡ã¤ã«ãå
±ç¨ãã¦ãã¾ãããã¡ãã¯æ¤è¨¼ç°å¢ãéçºç°å¢ã¨ç´æ¥ã¯é¢ä¿ãªããåã«è¤è£½ãä½ã£ã¦ã¯ã¨ãªããã©ã¼ãã³ã¹ãè¨æ¸¬ãããªã©ã®ç¨éã§ä½¿ããã®ã§ãã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2020%2F08%2F20%2F090000" title="Amazon RDS/Auroraãã¯ãã¼ã³ããã·ã¹ãã ãä½ã£ã話 - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<h2 id="å®æçãªå®è¡">å®æçãªå®è¡</h2>
<p>ãã®è¨äºã®æåã®æ¹ã§å®æçã«è¤è£½ãä½ã£ã¦ããã¨æ¸ããããã«ãæ¤è¨¼ç°å¢ã®ãã¼ã¿ãã¼ã¹ã«ã¤ãã¦ããã¼ã¿ãã¼ã¹ãæ°ãããªã¹ãã¢ãå¤ãæ¹ã¨å
¥ãæ¿ããä½æ¥ããæ¥æ¬¡ã®ãããã¸ã§ãã¨ãã¦å®è¡ãã¦ãã¾ãã</p>
<p>ãã®éãAurora ã®ãªã¹ãã¢æä½ã¯ãã©ã¡ã¼ã¿ã¼ã°ã«ã¼ããªã©ã®è¨å®å¤ãæå®ããªãã¨ããã©ã«ãã®ãã®ã使ããããããå¤ãæ¹ã«è¨å®ããã¦ãããã®ãå¼ãç¶ãããã«ãã¦ãã¾ããDB ã¦ã¼ã¶ã¼ãæ¹ãã¦ä½ãå¿
è¦ããããå¤ãæ¹ã®ãã¼ã¿ãã¼ã¹ã®ã¦ã¼ã¶ã¼æ
å ±ã¨æ¨©éã調ã¹æ°ããæ¹ã«åä½æãã¾ãã</p>
<p>ã¾ããæ¤è¨¼ç°å¢ã«ãããªãã¬ã³ã¼ãã«ã¤ãã¦ã移è¡å¦çãè¡ã£ã¦ãã¾ããå®ã¯ãªã¹ãã¢ããå¾ã®ãã¼ã¿ãã¼ã¹ã§ã¯åãã¼ãã«ã®ä¸»ãã¼ã® AUTO_INCREMENT ã大ããªå¤ã«è¨å®ãã¦ãã¾ããããã«ããããããã®ã¬ã³ã¼ãã«ã¤ãã¦ä¸»ãã¼ããããå¤ãã大ããå ´åã¯æ¤è¨¼ç°å¢ã®ã¿ã«ãããã¼ã¿ã ã¨å¤æããå¤ãæ¹ã®ãã¼ã¿ãã¼ã¹ããæ°ããæ¹ã®ãã¼ã¿ãã¼ã¹ã¸ã¬ã³ã¼ããæµãè¾¼ãã§ãã¾ããããã«ããéçºè
ã«ããæ¤è¨¼ç°å¢ã§ã®ä½æ¥ããªãã¹ãå£ããªãããã«ãã¦ãã¾ãã</p>
<p>å¤é¨ãã¼å¶ç´ãããå ´åã¯å°ããã£ããã§ãæ¤è¨¼ç°å¢ã«ã®ã¿åå¨ããã¬ã³ã¼ãã®åç
§ãã¦ããå
ãæ¬çªç°å¢ã®ã¬ã³ã¼ãã®å ´åãåç
§å
ã®ã¬ã³ã¼ããå
¥ãæ¿ãã®ã¿ã¤ãã³ã°ã§åé¤ããã¦ããå¯è½æ§ãããã¾ãããã®ãããªå ´åã©ããããã¯åãã¼ã ã«ä»»ãã¦ãã¦ããã¨ãã°ã¨ãããã¼ã ã§ã¯ãã®ãããªå¤é¨ãã¼ã®ä¸æ´åãè¦ã¤ãã¦æ¤è¨¼ç°å¢å´ã®ã¬ã³ã¼ããæ¶ãã¦ãã¾ãå¦çãå
¥ãã¦ãã¾ãã</p>
<p>å¤ãæ¹ã¨æ°ããæ¹ã®ãã¼ã¿ãã¼ã¹ãå
¥ãæ¿ããã¨æ¸ãã¾ããããããã¯å¯¾è±¡ã®ãã¼ã¿ãã¼ã¹ãåç
§ããããã® DNS ã¬ã³ã¼ããä½ããDNS ã¬ã³ã¼ãã®åç
§å
ãæ¸ãæãããã¨ã§å®ç¾ãã¦ãã¾ããããã«ããæ¥ç¶å
ãæã£ã¦ããæ
å ±ã¯æ¸ãæããã«ãã¼ã¿ãã¼ã¹ãå
¥ãæ¿ãããã¾ãã</p>
<h2 id="ã¯ãã¹ãªã¼ã¸ã§ã³ãªã¼ãã¬ããªã«">ã¯ãã¹ãªã¼ã¸ã§ã³ã»ãªã¼ãã¬ããªã«</h2>
<p>ä»åã®ãã¼ã«ã§ä½ããããã¼ã¿ãã¼ã¹ã«å¯¾ãã¦ã¯ãæå
ã®éçºç°å¢ããã§ãæ¥ç¶ã§ããããã«è¨å®ãã¦ãã¾ããå¼ç¤¾ã§ã¯ <a href="https://www.twingate.com/">Twingate</a> ãå©ç¨ãã¦éçºè
ãæå
ããã¯ã©ã¦ãã¸å®å
¨ã«æ¥ç¶ã§ããããã«ãã¦ããããã®ä¸ç°ã§æ¥ç¶ã許å¯ãã¦ãã¾ãã</p>
<p>ããã§åé¡ã«ãªãã®ã¯éä¿¡é度ã§ããAWS ã«ãããã¼ã¿ãã¼ã¹ã«å¯¾ãã¦ãæ¤è¨¼ç°å¢ã§ã¯åãã AWS ã«ããã¢ããªã±ã¼ã·ã§ã³ã¨ã®éä¿¡ã«ãªãã¾ãããéçºç°å¢ã§ã¯æå
ã®ãã½ã³ã³ã§åãã¢ããªã±ã¼ã·ã§ã³ã¨ã®éä¿¡ã«ãªãã¾ãããã®éä¿¡ã«æéããããã¨ãã¡ãã£ã¨æå
ã§ãµã¼ãã¼ã¢ããªãåããã¦ã¬ã¹ãã³ã¹ãè¦ããã¨ãã¦ããè¤æ°ã®ã¯ã¨ãªãå®è¡ããããã«ä½åãéä¿¡ãå¾å¾©ãã¦ã¬ã¹ãã³ã¹ã¾ã§ã®æéãé
ããªããã¦ã¼ã¶ã¼ä½é¨ã«ä¹é¢ãçã¾ãã¦ãã¾ãã¾ããä»ã®ç°å¢ã¨æ¯ã¹ã¦ã¯ã¨ãª 1 åããã 100 ããªç§é
ããªã£ãã¨ãã¦ããç´å㧠10 ã¯ã¨ãªãã¦ããã¨åè¨ 1 ç§é
ããªã£ã¦ãã¾ãã¾ããæ£ç´ã¾ã¨ãã«ç¢ºèªã§ãã¾ããã</p>
<p>ç¹ã«ãç®çã®ãã¼ã¿ãã¼ã¹ãæµ·ãè¶ããåããå´ã«ä½ç½®ãã¦ããå ´åã¯åä»ã§ããå®éããã¼ã¿ãã¼ã¹ã¯ã¢ã¡ãªã«ã«ããã¤ã¤éçºè
ã¯æ¥æ¬ã«å±
ãã¨ãã£ãç¶æ³ãèµ·ãã£ã¦ãã¾ããã¯ã¨ãªãæµ·ã®ä¸ãè¡ã£ããæ¥ãããã¦ããã£ããæéããããã¾ãã</p>
<p>ããã§ãã®ãããªå ´åã¯ãAurora ãåãã¦ãã<a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Replication.CrossRegion.html">ãªã¼ã¸ã§ã³ãè·¨ãã§ãªã¼ãã¬ããªã«ãä½æã§ããæ©è½</a>ã使ã£ã¦ãã¾ãï¼ã¯ãã¹ãªã¼ã¸ã§ã³ã»ãªã¼ãã¬ããªã«ï¼ãã¤ã¾ãã©ã¤ã¿ã¼ã¤ã³ã¹ã¿ã³ã¹ã¯ã¢ã¡ãªã«ã«ç½®ãã¤ã¤ããªã¼ãã¼ã¤ã³ã¹ã¿ã³ã¹ã¯æ±äº¬ã«ãããã®ãåç
§ã§ããããã«ãã¾ããã¯ãã¯ãããã®ãµã¼ãã¹ã¯å¤ãã®é¨å㧠read heavy ãªã®ã§ãéçºç°å¢ã§ããããªãã®å¿çæéã§ç¢ºèªã§ããããã«ãªãã¾ãã</p>
<p>ãã ããã¯ãã¹ãªã¼ã¸ã§ã³ã»ãªã¼ãã¬ããªã«ã®ä½æã«ã¯å¤å°æéããããã¾ãããã¾å®éã«åããã¦ããä¾ã§ã¯ 2 æéå¼±ãè¦ãã¾ãã1 æ¥ã¯è¶
ãã¦ããªãã®ã§è¨±å®¹ç¯å²ã§ã¯ããã®ã§ãããå®è¡å¤±ææã®ãªãã©ã¤ã¾ã§èããã¨å°ãé¢åãªç¹ã§ã¯ããã¾ãã</p>
<h2 id="é·æã¨çæ">é·æã¨çæ</h2>
<p>以ä¸ãæ¤è¨¼ç°å¢ãéçºç°å¢ã§æ¬çªç°å¢ç¸å½ã®ãã¼ã¿ã使ããããã«ããããã«ä½¿ã£ã¦ããä»çµã¿ã®ã²ã¨ã¤ã解説ãã¾ããããã®ããã«æ¬çªç°å¢ç¸å½ã®æ¤è¨¼ãã§ããããã«ãããã¨ã§ããµã¼ãã¹éçºãé«éã«é²ããããããã«ãã¦ãã¾ããã¾ãããã§ã¯èª¬æãã¾ããã§ããããæ¬çªç°å¢ã® DB ãã¼ã¸ã§ã³ãä¸ãããã¨ãå
ã«æ¤è¨¼ç°å¢ã§è©¦ããããã«ããªã¹ãã¢ããå¾ã« DB ãã¼ã¸ã§ã³ãå¤æ´ã§ããæ©è½ãå
¥ãã¦ãããããã¦ãã¾ãã</p>
<p>ä»åã®ä»çµã¿ã¯ãã¹ãã³ã°ããããªãã«æè»ã«ã§ããã¨ããæå³ã§æ±ããããã社å
ã§ã¯è¤æ°ã®ãã¼ã¿ãã¼ã¹ã§å©ç¨ãã¦ãã¾ããä¸æ¹ã§æ¬çªç°å¢ã«èµ·ãã£ãå¤æ´ã®åæ ã¯ç¿æ¥ã¨ãªããããæ¬çªç°å¢ã§ã®ãã¼ã¿å¤æ´ã®å¢ããåç¾ããããããªå ´åã«ã¯ä¸åãã§ããã¤ãã³ãããªãã³ãªé¨åã®æ¤è¨¼ãååçã«ã¯è¡ãã¥ããããªã©ã§ããã</p>
<p>ãªã¢ã«ã¿ã¤ã ã«è¿ãç¶æ
ã§æ¬çªç°å¢ã«è¿ããã¼ã¿ãç¨æãããå ´åã¯ãMySQL ã® binlog ãç´æ¥ä½¿ã£ãä»çµã¿ããAWS Database Migration Service (DMS) ã使ã£ãä»çµã¿ãèãããã¾ããããããç¶æ³ã«ãã£ã¦ã¯ä¾¿å©ãªã®ã§ãããAUTO_INCREMENT ã®èª¿æ´ã®ç°¡æãããã¹ãã³ã°ã®æè»æ§ãå£ããã¨ãã®ã¡ã³ããã³ã¹ã®å®¹æããªã©ã«å·®ç°ãããã¾ããç¹ã«ä½ããããå£ããã¨ãã«ç´ãã®ãããé¢åãªãããæ¥æ¬¡å
¥ãæ¿ãã§å
åãªå ´åã¯ããããããã«ãã¦ãã¾ãã</p>
<p>ã¨ããã§â¦â¦ãéä¸ã§ãã¼ã¿ãã¼ã¹ãã¢ã¡ãªã«ã«ããäºä¾ã®è©±ããã¾ããããä¸ä½ããã¯ã©ããªå ´åãªã®ã§ããããï¼ã詳細ã¯ãã®è¨äºã§ã¯ã¾ã ç§å¯ï¼ãå¾æ¥ãç´¹ä»äºå®ã§ãã®ã§ãå¼ç¤¾ã® X ã¢ã«ã¦ã³ãããã©ãã¼ãã¦æ´æ°ããå¾
ã¡ãã ããã</p>
<p><a href="https://twitter.com/cookpad_tech">https://twitter.com/cookpad_tech</a></p>
nekketsuuu
iOSDC Japan 2024ã«ç¤¾å¡2åãç»å£ & ã¹ãã³ãµã¼ä¼ç»ã®ãæ¡å
hatenablog://entry/6801883189129866366
2024-08-21T09:58:37+09:00
2024-08-22T10:29:27+09:00 ããã«ã¡ã¯ï¼ã¯ãã¯ãããã§ã¢ãã¤ã«ã¢ããªéçºã¨ã³ã¸ãã¢ããã¦ããæ°å (@tk108gabalian) ã§ãã ç¾å¨ã¯ãã¯ãããã§ã¯ç©ºåã®ã¤ã¸ã³ãã³ãã¼ã ãå°æ¥ãã¦ããã¾ããã¤ã¸ã³ãã³ã¨ããã®ã¯ãã¤ã½ã¼ããçºå£²ããã¦ãããã¬ã¼ãã£ã³ã°ã«ã¼ãã²ã¼ã ã§ãã社å
ã§ã¯æ¥ã
ã¤ã¸ã³ãã³å¯¾å¿ç¤¾å¡éã®ããã«ãç¹°ãåºãããã¦ãã¾ãããã¤ããã¥â¦â¦ã¤ã¸ã³ãã³ï¼ ãã¦ãä»å¹´ãiOSDCã8/22(æ¨)ã8/24(å)ã«éå¬ããã¾ããï¼ ã¯ãã¯ãããã¯ã´ã¼ã«ãã¹ãã³ãµã¼ã¨ãã¦åè³ããã¦ããã ãã¾ãã ãã¼ã¯ç´¹ä» ä»åã¯ãã¯ãããããã¯2åãç»å£ãããã¾ãï¼ Day 1 8/23(é) 17:40ã Track B ã«â¦
<p>ããã«ã¡ã¯ï¼ã¯ãã¯ãããã§ã¢ãã¤ã«ã¢ããªéçºã¨ã³ã¸ãã¢ããã¦ããæ°å (<a href="https://twitter.com/tk108gabalian">@tk108gabalian</a>) ã§ãã</p>
<p>ç¾å¨ã¯ãã¯ãããã§ã¯ç©ºåã®ã¤ã¸ã³ãã³ãã¼ã ãå°æ¥ãã¦ããã¾ããã¤ã¸ã³ãã³ã¨ããã®ã¯ãã¤ã½ã¼ããçºå£²ããã¦ãããã¬ã¼ãã£ã³ã°ã«ã¼ãã²ã¼ã ã§ãã社å
ã§ã¯æ¥ã
ã¤ã¸ã³ãã³å¯¾å¿ç¤¾å¡éã®ããã«ãç¹°ãåºãããã¦ãã¾ãããã¤ããã¥â¦â¦ã¤ã¸ã³ãã³ï¼</p>
<p>ãã¦ãä»å¹´ãiOSDCã8/22(æ¨)ã8/24(å)ã«éå¬ããã¾ããï¼
ã¯ãã¯ãããã¯ã´ã¼ã«ãã¹ãã³ãµã¼ã¨ãã¦åè³ããã¦ããã ãã¾ãã</p>
<h1 id="ãã¼ã¯ç´¹ä»">ãã¼ã¯ç´¹ä»</h1>
<p>ä»åã¯ãã¯ãããããã¯2åãç»å£ãããã¾ãï¼</p>
<h2 id="Day-1">Day 1</h2>
<h2 id="823é-1740">8/23(é) 17:40ã</h2>
<h3 id="Track-B-ã«ã¼ãã¼ãºLT5å">Track B ã«ã¼ãã¼ãºLTï¼5åï¼</h3>
<ul>
<li>ç»å£è
: æ°å / <a href="https://twitter.com/tk108gabalian">@tk108gabalian</a></li>
<li>ã¿ã¤ãã«: iOS17ã®ScrollViewã¯ã¡ãã£ã¨ã§ããå</li>
</ul>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fiosdc-japan-2024%2Fproposal%2F04726719-f798-4942-86e7-56742791a5fd" title="iOS17ã®ScrollViewã¯ã¡ãã£ã¨ã§ããå" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://fortee.jp/iosdc-japan-2024/proposal/04726719-f798-4942-86e7-56742791a5fd">fortee.jp</a></cite></p>
<p>SwiftUIã®ç»å ´å½åã¯ã«ã¹ã¿ãã¤ãºæ§ã«ä¹ããã¨æããã¦ããScrollViewã§ãããããæ°å¹´ã®ã¢ãããã¼ãã§æ§ã
ãªæ©è½ã追å ãããã§ãããã¨ãå¢ãã¦ãã¾ããã
ãã®ã»ãã·ã§ã³ã§ã¯ãiOS17ã§è¿½å ãããmodifierãä¸å¿ã«ãSwiftUIã®ScrollViewãã©ã®ããã«é²åããã®ããå
·ä½çãªä¾ã交ãã¦ãç´¹ä»ãã¾ãã
ScrollViewã使ã£ã¦ã¡ãã£ã¨ãªãããªUIãæ§ç¯ãããã¨ã«èå³ãããæ¹ã¯ãã²ã覧ãã ããï¼</p>
<h2 id="Day2">Day2</h2>
<h2 id="824å-1125">8/24(å) 11:25ã</h2>
<h3 id="Track-C-ã¹ãã³ãµã¼ã»ãã·ã§ã³20å">Track C ã¹ãã³ãµã¼ã»ãã·ã§ã³ï¼20åï¼</h3>
<ul>
<li>ç»å£è
: paruru / <a href="https://twitter.com/0x746572616e79">@ 0x746572616e79</a></li>
<li>ã¿ã¤ãã«: æ°åã®iOSã¨ã³ã¸ãã¢ã"ã¯ãã¯ãããã®ä»"ãç´¹ä»ï¼</li>
</ul>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fiosdc-japan-2024%2Fproposal%2F4e501f95-ea97-4299-97f8-3195597a638c" title="æ°åã®iOSã¨ã³ã¸ãã¢ã"ã¯ãã¯ãããã®ä»"ãç´¹ä»ï¼" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://fortee.jp/iosdc-japan-2024/proposal/4e501f95-ea97-4299-97f8-3195597a638c">fortee.jp</a></cite></p>
<p>ãã®ã»ãã·ã§ã³ã§ã¯ãå¼ç¤¾æ°åã®paruruãã¬ã·ãäºæ¥é¨ã§ä½ãåãçµãã§ããã®ãããç´¹ä»ãã¾ãã
ã¢ããªã®å質ãä¿ã¤åãçµã¿ããã¶ã¤ã³ã·ã¹ãã ããªãªã¼ã¹ããã¼ãæ¥æ¬ã¨æµ·å¤åãã¢ããªã®éã... ãªã©ã®ãã¼ã¯ã¼ãã«èå³ãããæ¹ã¯ãã²ã覧ãã ããï¼</p>
<h1 id="ããã«ãã£ã®ç´¹ä»">ããã«ãã£ã®ç´¹ä»</h1>
<p>å½æ¥ãã¼ã¹ã®ä¼ç»ã«åå ããã ããæ¹ã«å
çã§ãã¼ãã¯ãªãããé
å¸ããã¦ããã ãã¾ãï¼
éå°æ¸ã¿ã®è¢ãå°ãããéã«æ¯éãæ´»ç¨ãã ããã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toya108_climbing/20240821/20240821094942.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toya108_climbing/20240821/20240821094945.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toya108_climbing/20240821/20240821094948.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h1 id="ãã¼ã¹ã®ç´¹ä»">ãã¼ã¹ã®ç´¹ä»</h1>
<p>ã¯ãã¯ãããã¯ä»å¹´ããã¼ã¹ãåºå±ãã¾ãï¼
ãã¼ã¹ã§ã¯å
ã«ãç´¹ä»ããããã«ãã£ãé
å¸ããã»ããã¯ãã¯ãããã®ã¢ã¼ããã¯ãã£ãéçºæ¹æ³ã«ã¤ãã¦ãç´¹ä»ãã¾ãã
èå³ã®ããæ¹ã¯ãã²ãã¼ã¹ã¾ã§ãè¶ããã ããï¼
ãã¡ãããä¸è¨ä»¥å¤ã®ç®çã§å¼ç¤¾ã®ã¨ã³ã¸ãã¢ã¨ã話ãã«æ¥ã¦ããã ãã¦ãæ§ãã¾ããã
ã¡ã³ãã¼ä¸åãå½æ¥ãã¼ã¹ã§ãå¾
ã¡ãã¦ããã¾ãï¼</p>
<p><figure class="figure-image figure-image-fotolife" title="2022å¹´ã®ãã¼ã¹ã®æ§åã§ãã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toya108_climbing/20240821/20240821094951.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>2022å¹´ã®ãã¼ã¹ã®æ§åã§ãã</figcaption></figure></p>
<h1 id="ãããã«">ãããã«</h1>
<p>ä»å¹´ã¯çæãç¶ãã¦ãããiOSDCå½æ¥ãå³ããæããäºæ³ããã¾ãããæãã«è² ããã«ã«ã³ãã¡ã¬ã³ã¹ã楽ããã§ããã¾ãããï¼</p>
toya108_climbing
Cookpad Drinkup at RubyKaigi 2024 ãéãããã«æ°ã«ãã¦ãããã¨
hatenablog://entry/6801883189111735352
2024-06-05T09:00:03+09:00
2024-06-07T09:28:26+09:00 RubyKaigi 2024 ãéããã¾ãããã¯ãã¯ãããã¯åè³ãã¦ãããæ親ä¼ãéãã¾ããããã®è¨äºã§ã¯ãã«ã³ãã¡ã¬ã³ã¹ã§æ親ä¼ãéãã«ããã£ã¦æ°ãã¤ãã¦ãããã¨ãããã¾ããã£ããã¨ããã¾ããããªãã£ããã¨ãã¾ã¨ãã¾ããRubyKaigi ã«é¢ããããæè¡è
ã³ãã¥ããã£ãçãä¸ããæ段ã®ã²ã¨ã¤ã¨ãã¦ã覧ãã ããã
<p>ã¬ã·ãäºæ¥é¨ããã¯ã¨ã³ãåºç¤ã°ã«ã¼ãã®ç³å·ã§ãã</p>
<p>2024 å¹´ 5 æ 15 æ¥ãã 17 æ¥ã«ããã¦ã<a href="https://rubykaigi.org/2024/">RubyKaigi 2024</a> ãéããã¾ãããã¯ãã¯ããã㯠Wi-Fi ã¹ãã³ãµã¼ã¨ãã¦åè³ãã¦ãããã¾ã 16 æ¥ã®å¤ã«ã¯ Cookpad Drinkup at RubyKaigi 2024 ã¨ç§°ãã¦æ親ä¼ãéãã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="ã¯ãã¯ãããä¸è¡"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/nekketsuuu/20240604/20240604133534.jpg" alt="RubyKaigi 2024 のロゴが大きく描かれた壁の前で人々が並んで写真に写っています。" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ã¯ãã¯ãããä¸è¡</figcaption></figure></p>
<p><figure class="figure-image figure-image-fotolife" title="æ親ä¼ã®ãåºã®æ§å"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/nekketsuuu/20240604/20240604131619.jpg" alt="居酒屋の入口が写っています。ひとりの男性が入口のそばで手を広げて参加者を歓迎しています。" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>æ親ä¼ã®ãåºã®æ§å</figcaption></figure></p>
<p>ãã®è¨äºã§ã¯ãã«ã³ãã¡ã¬ã³ã¹ã§æ親ä¼ãéãã«ããã£ã¦æ°ãã¤ãã¦ãããã¨ãããã¾ããã£ããã¨ããã¾ããããªãã£ããã¨ãã¾ã¨ãã¾ããRubyKaigi ã«é¢ããããæè¡è
ã³ãã¥ããã£ãçãä¸ããæ段ã®ã²ã¨ã¤ã¨ãã¦ã覧ãã ããã</p>
<h2 id="Drinkup-ã®ç®ç">Drinkup ã®ç®ç</h2>
<p>ä»åã® Cookpad Drinkup at RubyKaigi 2024 ã¯èªåãåãã¾ã¨ããæ
å½ãã¾ããã</p>
<p>ãã® drinkup ã®åå åéãã¼ã¸ã«ã¯ãåå è
å士ã®äº¤æµã®å ´ã¨ãã¦æ¯éãå©ç¨ãã ãããã¨ããä¸æãè¼ãã¦ãã¾ãããã«ã³ãã¡ã¬ã³ã¹ã«ããã¦ä¼æ¥ãæ親ä¼ãéãã¨ããã®ã¯ããã¡ããå¼ç¤¾ã®ãã¨ããã£ã¨ç¥ã£ã¦ãããããã¨ããåºå ±ã»æ¡ç¨çãªç®çãããã¾ãããåæã«ãæã
ãæ®æ®µä½¿ã£ã¦ããæè¡ã®ã³ãã¥ããã£ããã£ã¨çãä¸ãããã¨ããæå³ãããã¾ããã¯ãã¯ãããã§ã¯ Ruby ãè¤æ°ã®ãµã¼ãã¹ã§ããã¼ã«å©ç¨ãã¦ãã¾ããæã
ã¨ãã¦ããRuby ãæ»ã¬ãã¨å°ã訳ã§ãããã®ããèªåãå«ã㦠Ruby ã®ã¦ã¼ã¶ã¼ãã¢ããã¼ãããããã¦ã¼ã¶ã¼å士ã®ãããã¯ã¼ã¯ãåºãã¦äººã
ãããããã¨ãå¢ããã¦ãã£ããã¨ãã£ãæ´»åã«ã¯æå³ãããã®ã§ãã</p>
<p>ç¹ã«ï¼æè¿ã®ï¼RubyKaigi ã§ã¯ãåãã¼ã¯ã®å¾ã«è³ªçå¿çã®æéãè¨ãããã¦ãã¾ãããä¼æ©æéã¯ããã¾ãããã¹ãã³ãµã¼ãã¼ã¹ãã¾ãã£ãããã¡ãã£ã¨ããä¼è©±ãéçºããã¦ãããããã¨éãã¦ãã¾ãç¨åº¦ã®æéã§ãããã®ãããã¼ã¯ã«ã¤ãã¦ã®éè«ãããããã®æ¯è¼çè½ã¡çããå ´ã¨ãã¦ãå©ç¨ãã¦ããã ããããªã¨ããæããããã¾ããã</p>
<p>èªåãã³ãã¥ããã£éå¶ã§è¿·ã£ãã¨ãå人çã«åèã«ãã¦ããæ¸ç±ãã¢ã¼ãã»ãªãã»ã³ãã¥ããã£ãï¼Jono Bacon èãæ¸å·ããã 訳ï¼ã«è¼ã£ã¦ããè¨èã¨ãã¦ãããã¡ããªã¼ã®ä¾¡å¤ãåµé ãããã¨ããè¨ãåããããã¾ããåã«åãçéã«å±
ã人ãã¨ããã ãã«çã¾ãããåãæè¡ãæ¥ã
使ããåãæèãå
±æããããã¡ããªã¼ãã¨è©±ãã¦ä½ããçã¾ããå ´ã«ãªãã°ãæé«ã§ããã</p>
<h2 id="æ°ã«ããã¦ãããã¨">æ°ã«ããã¦ãããã¨</h2>
<p>ãããã£ãæå³ã§ drinkup ãéãããã«ãéå¬ã«åãã¦æ°ã«ãã¦ããç´°ãããã¨ãããã¤ãããã¾ããããã«åæãã¦ã¿ã¾ãã</p>
<h3 id="åå è
ãåãããã空éã«ãã">åå è
ãåãããã空éã«ãã</h3>
<p>ãåºãé¸ã¶éãåºãã®é¨å±ã«å¸ã並ãã§ãã¦ãç¾å³ããã飯ã飲ã¿ç©ãæã«åãã¤ã¤ãã©ã¡ããã¨ããã¨åããã¨ã«æ¯éãåããããªãåºãé¸ã³ã¾ãããçããã«ã¯ä¼è©±ããã¦ã»ããã®ã§ä»åã¯æå³çã«è¦³å
åãã®è¦ä¸ç©ã¯èæ
®ããå¤ãã¾ãããåããªããéä¸ã§èªç±ã«å¸æ¿ããã¦ãããããªä¼ãªã®ã§ãããç¨åº¦ç§»åã®èªç±ãå¹ãé£äºãç®æãå½¢ã§ããèªåã¯èãã¦ãã¾ããã§ãããä»ç¤¾ããã®äºä¾ã ã¨ãããªã³ã¯ãµã¼ãã¼å¶ã ã£ãã®ã§é£²ã¿ç©ãåãããã«äººãåºå
¥ããéå£ãæ¹æããã¦è²ããªäººã¨åããã¨ããä¾ãããããã§ããã</p>
<p>ãã ãä»åæ²ç¸ã§ã®éå¬ã¨ãªã£ã RubyKaigi ã«ããã¦ã¯ãé æ¹ã«ãããåºã®ç´°ããé¨åã¾ã§ç¢ºèªããã®ããªããªãé£ããå´é¢ãããã¾ããããã㧠RubyKaigi éå¶ã®ä¸ã«ããã£ãããç¾å°ã®æ¹ã
ã®ã³ã¡ã³ãã伺ã£ããï¼ãã®ç¯ã¯ãããã¨ããããã¾ããï¼ãé£ã¹ãã°ããã Google ãããããã«ããåçãããè¦ãããæçµçã«ã¯ãåºã®æ¹ã«ç¸è«ãããã¨ãå¤ãã«ç¢ºèªããã¾ãããå²ã¨ã®ãªã®ãªã¾ã§ãããããã¦ãã¾ãããããã£ãã®ãã¨ä¸åº¦äºåã«æ²ç¸ã§ç¾å°è¦å¯ãã¦ãè¯ãã£ããããããªãã§ããèªåã¯æ¬è·ã®ã½ããã¦ã§ã¢ã¨ã³ã¸ãã¢ã®ä»äºãããã®ã§ããªããªãã³ã¬ã°ã£ãããã£ã¦ããããªãã®ãé£ããã¨ããã§ã¯ããã¾ããã</p>
<p>ãåºé¸ã³ã®ä»ã«ãéå¬ããªã·ã¼ã示ãã¦ãããã¨ãéè¦ã ã¨èãã¦ãã¾ããä»å㯠<a href="https://rubykaigi.org/2024/policies/">RubyKaigi å
¬å¼ãå®ãã¦ããããªã·ã¼</a>ã«åã£ã¦ããããã®ãã¨ã表示ãã¦ãã¾ãããã¾ãããªã·ã¼éåãè¦åããããã¨ãå ±åããããã«å¼ç¤¾å´ã®é£çµ¡å
ãç¨æããåå è
ã®ã¿ãè¦ããæ
å ±ã¨ã㦠connpass ã«æ²è¼ãã¦ããã¾ããã</p>
<p>ããªã·ã¼ã®å»¶é·ç·ä¸ã¨ãã¦ãç©ççãªã»ãã¥ãªãã£ãã©ã確ä¿ããããèæ
®ã«ã¯å
¥ãã¦ããã¾ãããåå è
ã§ã¯ãªãæ¹ãå¼·å¼ã«å
¥ã£ã¦æ¥ãªãããã«ã©ãããã¨ããããããã®ã§ãããå³ãã話ã¨ãã¦ã¯ <a href="https://www.publickey1.jp/blog/23/it_2025.html">2023 å¹´ 12 æã®äºä¾</a>ãã¾ã è¨æ¶ã«æ°ããã¨ããã§ããä»åã¯ãåºãã¾ãã¾ã貸ãåãã«ããã®ã§ãåºã®å
¥å£ã®ã¨ããã§å®ããã¨ã«ããæåã®å
¥å ´ã®ã¿ã¤ãã³ã°ã§ã¯èª°ãå
¥ã£ã¦ããã®ãå¼ç¤¾ã®ç¤¾å¡ãå¤æãè½ã¡çããå¾ã¯ãåºã®æ¹ã«ãä»»ããã¦ãã¾ãããã¾ããåºãé¸ã¶æç¹ã§ãããã¾ãã«ãç¹è¯è¡ã«è¿ããããåºã¯é¿ãã¦ãã¾ãããèãããã ã£ãããããã¾ããããè¿ãã«å®¢å¼ããç«ã¡ä¸¦ã¶å ´æããã£ãã®ã§ãã</p>
<p>åçæ®å½±ã«ã¤ãã¦ã工夫ã§ãããã¤ã³ããããã¾ããDrinkup ã§ã¯ãéå¶ã¨ãã¦ã¯åå è
ã®æ¹ã
ãçãä¸ãã£ã¦ããæ§åãè¨é²ã«æ®ããããªãã¾ãããã¾ãåå è
å士ã«ããã¦ã <a href="https://x.com/search?q=%23rubyfriends">#rubyfriends</a> ãªã©ã®äº¤æµã®ããã«åçãæ®ããããªãã§ããããRubyKaigi ã§ã¯å
¬å¼ã®åãçµã¿ã¨ãã¦ãåæã®ç´ã®è²ã§åçæ®å½±ã®å¯å¦ãåã
人ã表æã§ããããã«ãªãã£ã¦ãã¾ãããæã
ã® drinkup ã§ãã³ã¬ãå©ç¨ããã¦ããã ããåå è
ã«ã¯åæãæã£ã¦ãã¦ãããããã«ãã¦ãã¾ãããåæããããã¨ã«ãã£ã¦ RubyKaigi åå è
ã ã¨ç¢ºèªã§ããã®ã¨åå è
å士ã§ååãåããã¨ããã®ãããã便å©ã§ãããäºåã»äºå¾ã¤ãã³ãã§ä¼¼ãæ¹å¼ãæ¡ç¨ãªãã£ã¦ããä¼ãããã¾ããããã¾ãã失æã¨ãã¦ã¯ãåçãæ®ãããããªãããã¨æã£ã¦å®é¨çã«ãã©ãããããã¹ãä½ã£ã¦æã£ã¦ãã£ãã®ã§ãããããã¯æ®ã©æ©è½ãã¾ããã§ãããåçªãããããªã</p>
<p><figure class="figure-image figure-image-fotolife" title="åæã®ç´ã®æ§åã https://rubykaigi.org/2024/onsite/ããå¼ç¨"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/nekketsuuu/20240604/20240604131908.jpg" alt="名札がふたつ表示されています。ひとつは黄色で、写真撮影 OK を示しています。もうひとつは白色で、写真撮影 NG を示しています。" width="800" height="411" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>åæã®ç´ã®æ§åã <a href="https://rubykaigi.org/2024/onsite/">https://rubykaigi.org/2024/onsite/</a> ããå¼ç¨</figcaption></figure></p>
<h3 id="å¤æ§ãªåå è
ãéã¾ããããã«ãã">å¤æ§ãªåå è
ãéã¾ããããã«ãã</h3>
<p>ã¾ããåå è
ã®å±¤ãä¸æ¥µéä¸ããªãããã«ããã¦ãã¾ãããRubyKaigi ã«ã¯ä¸çããåå è
ãéã¾ãã¾ããã¢ã¡ãªã«ãã¨ã¼ãããã¯ãã¡ããããã以å¤ã®å ´æãå«ãä¸çä¸ããéã¾ãã¾ããå½ç¶ãã¾ãã¾ãªããã¯ã°ã©ã¦ã³ããæã£ãæ¹ã
ãããã£ãããã®ã§ãã§ããéãåºãç¯å²ã®æ¹ãåå ããããããã«å¿ããã¾ããã話é¡ãå¤ç¨®å¤æ§ãªãã®ãéã¾ãã¨é¢ç½ããã§ãããã</p>
<p>åå åéã®ãã¼ã¸ãæ¥è±ä½µè¨ã«ãªã£ã¦ããã®ããã®ä¸ç°ã§ãããããã RubyKaigi ã®å
¬ç¨èªã¯è±èªãªã®ã§ããç¾å°ã®åä»ã«ããã¦ãæ¥è±å¯¾å¿ã§ãã</p>
<p>åå è
ãåéãå§ããæå»ãã¿ã¤ã ã¾ã¼ã³ãæ°ã«ãã¦ãããã¦äººå£ãå¤ãããªå ´æã®çå¤ä¸ã«ãªããªãããã«â¦â¦ç¨åº¦ã®ç¢ºèªããã¦ãã¾ãããå¾ããæãã¤ããã®ã§ãããããã¯ãã¨ãã°åå è
æ ã 4 çåã㦠6 æéãã¨ã«å¢æ ãã¦ãããã§ãè¯ãã£ãããããã¾ããããã çµæã¨ãã¦ã¯åééå§ãã¦å³åº§ã«æ ãåã¾ãåããã¨ã¯ãªãããã¤ãã®å¾ããã£ããã¨ãã£ã³ã»ã«ã¨å¿åãç¹°ãè¿ããè£æ¬ æ ãç¹°ãä¸ãã£ã¦ããã®ã§ãè¥å¹²æ°ã«ãéãã§ã¯ãã£ãã®ã§ããããããããæ¬å½ã¯ RubyKaigi å½æ¥ã«åå ããããªã£ãã¨ãã¦ããã©ãã¨ç«ã¡å¯ãããããã®åå æ¹æ³ã«ãããã¨ãã話ã¯ãã£ãã®ã§ãããäºç®ãäºåæºåã¨ã®å
¼ãåãããªããªãé£ããã§ããã</p>
<p>é£ããã£ãã®ã¯é£ã¹ãããªããã®ã«å¯¾ãã対å¿ã§ãã好ãå«ããã¢ã¬ã«ã®ã¼ãå®æä¸ã®çç±ãªã©ã人ããããé£ã¹ãããªããã®ã¯åå¨ãã¾ããå°äººæ°ã®éã¾ãã§ããã°ãã¾ãé¿ãã¦èª¿æ´ã§ããã§ãããããä»åã®ä¼ã¯å¤§äººæ°ãªä¸ãéå¬ç´åã¾ã§åå è
ã確å®ãã¾ãããã¾ããåºã®é½åã¨ãã¦ã大人æ°ã®æçãä½ãããã«ã©ããã¦ã大ç¿æçã¯é¿ãã¥ããã§ãããã®ç¹ã«ã¤ãã¦ã¯äºåã«ãåºã®æ¹ã¨ç¸è«ããäºåã®é£çµ¡ãããã°é½åº¦å¯¾å¿ãèããã¨ããæ¹å¼ã«ããåå è
ãç¸è«ã§ããé£çµ¡å
ãç¨æãã¦ããã¾ããããã ãã£ã¨è¯ãæ¹æ³ã¯ãããããªæ°ãããã®ã§ãä»å¾ã模索ãããã¨ããã§ãã</p>
<p>ã¾ããä»ç¤¾ããããªãã£ã¦ãã¦è¯ããªã¨æã£ãã®ããåå æ ãããã¤ãã«åºåãããæ¹ã§ããRubyKaigi ååå ã®æ¹åãã®æ ãä½ã£ãããæµ·å¤å¢åãã®æ ãä½ã£ããããªã©ããã¾ãåãããã¦ãããã¯ããã§å¤§å¤ããã§ãããdrinkup åå ã«éãã¦ä¸æ
£ããªæ¹ã§ãä¸å©ã«ãªããããªããããªä»çµã¿ãä½ã£ã¦ããã®ã¯æç¨ããã«æãã¦ãã¾ãã</p>
<h3 id="ãã®ä»ç´°ãããã¨">ãã®ä»ç´°ãããã¨</h3>
<ul>
<li>ä¼å ´ã«è¿ããåºãé¸ã¶ãä»åé¸ãã ãåºã¯ãRubyKaigi ã®ä¼å ´ããå¾æ© 2 åã»ã©ã®å ´æã§ããããã®çµæãããããã¾ã§ä¼å ´ã§è°è«ãã¦ãã移åããããä¸åº¦ããã«ã«å¸°ã£ã¦è·ç©ãæ´çãã¦ãã移åãããã§ããã®ã§ã¯ãªããã¨æãã¾ãã</li>
<li>ã¤ãã³ãéå¶å´ã¨é£æºãåããéå§æéã®ç®å®ã§ãã£ããã¦ã§ããµã¤ãã¸ã®æ²è¼ã§ãã£ãããç¸è«ãã¦ããã¹ããã¨ãããã¾ããã</li>
<li>ã¤ãã³ãç¨ã®ããã¼ãä½ããSNS ã«æ稿ãããããªã£ãããã¾ããä»åã¯ç¤¾å
ã®ãã¶ã¤ãã¼ã®æ¹ã«ãé¡ããã¦ä½ã£ã¦ãããã¾ããã</li>
<li>ã¢ã«ã³ã¼ã«ãåã¾ããªãæ¹ã§ã楽ãããããã«ãããã¤ãã³ããçºä¿¡ããå´ã¨ãã¦ã¯ãé度ã«ãé
ã強調ãããããªè¨ãæ¹ã¯æ§ãã¦ãã¾ãããã¾ã drinkup ã£ã¦è¨ã£ã¡ãã£ã¦ããã§ããâ¦â¦ãã¾ããä¼å ´ãå±
é
å±ã§ãã£ã以ä¸ãä¸å®ã®åå ãã¥ããã¯ãã£ãããããã¾ããã</li>
<li>ãåºã¸ã®æ¯æãæ¹æ³ãäºåã«ç¢ºèªããã大人æ°ã ã¨éé¡ã大ããã«ãªãã®ã§ãã®å ´ã§æãã®ãé£ãããªãã¾ããä»åã¯å¾æ¥ã®éè¡æ¯ãè¾¼ã¿ãå¯è½ã ã£ãã®ã§ãããã¾ãããæ¯ãè¾¼ã¿ã¾ã§ã«ãããæ¥æ°ãå«ããåºå´ã¨äºåã«é£æºãå¿
è¦ã§ãã</li>
<li>connpass ã§ãéè¤åå ã許å¯ããªããã«è¨å®ãããåæé帯ã«æ§ã
ãªã¤ãã³ããéããããã¨ã¯äºæ³ã§ããã®ã§ããªãã¹ãå³å¯ãªåå è
æ°ãä¿ã¤ããã«éè¤ã¯è¨±ãã¾ããã§ããã</li>
<li>connpass ã®åºå¸ç®¡çæ©è½ãæ´»ç¨ãããå½æ¥ã©ãªããæ¢ã«ããã£ããã£ãã®ãææ¡ãããããconnpass ã®æ©è½ã使ãã¾ãããå
·ä½çã«ã¯å
¥å£ã®ã¨ãã㧠connpass ã®åä»ç¥¨çªå·ãè¦ãã¦ããã ããåæã¨çªå·ã§ç
§åã㦠connpass ã®åºå¸ãã§ãã¯ããã¯ã¹ã«ãã¼ã¯ãã¦ãã¾ãããå
·ä½çã«ã¯ãã½ã³ã³ãéãã¦ãã¼ã¸å
æ¤ç´¢ã§ããããããã®ã§ããããããã¹ããã ã¨æä½ãã¥ããã¦å³ããã£ãã ããã¨æãã¾ãã</li>
</ul>
<p>ä»å㯠connpass ãããå©ç¨ããã®ã§ connpass ã®è¨èã§æ¸ãã¦ãã¾ãããDoorkeeper ãããªã©ä»ã®ãµã¼ãã¹ãå©ç¨ãããéãä¼¼ããããªæãã«ãªãã¯ãã§ããDoorkeeper ã«ã¯ãã±ããã®ãã¼ã³ã¼ããèªã¿åã£ã¦åºæ¬ 管çããæ©è½ãããã®ã§ãããå¤äººæ°ã®ã¤ãã³ãã§ã¯ä¾¿å©ã§ããããä»åã® drinkup ã¯å¼ç¤¾ç¤¾å¡ãé¤ã㦠50 åã»ã©ã®è¦æ¨¡ã§ããã</p>
<h2 id="çµã³">çµã³</h2>
<p>ã¨ããããã§ãèªåãä»å Cookpad Drinkup at RubyKaigi 2024 ãéãã«ããã£ã¦æ°ã«ãã¦ãããã¨ãã¾ã¨ãã¦ã¿ã¾ãããèªåèªèº«ãã®ãããªä¸ç¹å®ã®æ¹ãå¤äººæ°éã¾ãä¼ã主å¬ããã®ã¯åãã¦ã§ãä¸å®ã«ãªãã¿ã¤ãã³ã°ãä½åããã£ãã®ã§ãä¼¼ããããªæ¹ã®å©ãã«ãªãã¨è¯ããªã¨æãã¾ãã</p>
<p>ããã§ã¯ã¾ã次ã®ã¤ãã³ãã§ãä¼ããããã¾ãããð</p>
<hr />
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2024%2F06%2F04%2F120000" title="ã¯ãã¯ããã㯠RubyKaigi 2024 ã«åå ãã¦ãã¾ããï¼ã¤ãã³ãã¬ãã¼ã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techlife.cookpad.com/entry/2024/06/04/120000">techlife.cookpad.com</a></cite></p>
nekketsuuu
ã¯ãã¯ããã㯠RubyKaigi 2024 ã«åå ãã¦ãã¾ããï¼ã¤ãã³ãã¬ãã¼ã
hatenablog://entry/6801883189111448809
2024-06-04T12:00:00+09:00
2024-06-06T23:22:24+09:00 ã¯ããã« ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ãããã¯ãéçºã°ã«ã¼ãã®å å
(@Sota_Horiuchi)ã§ããæ®æ®µã¯ããã¯ã¨ã³ãã®éçºãè¡ã£ã¦ããæ°å2å¹´ç®ã®ã¨ã³ã¸ãã¢ã§ãã RubyKaigi 2024ã 2024 å¹´ 5 æ 15 æ¥ãã 17 æ¥ã«æ²ç¸çé£è¦å¸ã§éå¬ãããã¯ãã¯ãããããã¯ç·å¢ 24 åãåå ãã¾ãããåå ããã¡ã³ãã¼ã®ãã¡ 13 åãæ°å 3 å¹´ç®ã¾ã§ã®ã¨ã³ã¸ãã¢ã§ããã社å
ã®è¥æããã¯ã¨ã³ãã¨ã³ã¸ãã¢ãã»ã¼åå ãã¦ãã¾ããã ã¾ããã¯ãã¯ããã㯠Wi-Fi ã¹ãã³ãµã¼ã¨ãã¦åè³ãã¦ãããæ´ã« 16 æ¥ã®å¤ã«ã¯ Cookpad Drinkup at RubyKaigi 202â¦
<h2 id="ã¯ããã«">ã¯ããã«</h2>
<p>ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ãããã¯ãéçºã°ã«ã¼ãã®å å
(<a href="https://x.com/Sota_Horiuchi">@Sota_Horiuchi</a>)ã§ããæ®æ®µã¯ããã¯ã¨ã³ãã®éçºãè¡ã£ã¦ããæ°å2å¹´ç®ã®ã¨ã³ã¸ãã¢ã§ãã</p>
<p><a href="https://rubykaigi.org/2024/">RubyKaigi 2024</a>ã 2024 å¹´ 5 æ 15 æ¥ãã 17 æ¥ã«æ²ç¸çé£è¦å¸ã§éå¬ãããã¯ãã¯ãããããã¯ç·å¢ 24 åãåå ãã¾ãããåå ããã¡ã³ãã¼ã®ãã¡ 13 åãæ°å 3 å¹´ç®ã¾ã§ã®ã¨ã³ã¸ãã¢ã§ããã社å
ã®è¥æããã¯ã¨ã³ãã¨ã³ã¸ãã¢ãã»ã¼åå ãã¦ãã¾ããã
ã¾ããã¯ãã¯ããã㯠Wi-Fi ã¹ãã³ãµã¼ã¨ãã¦åè³ãã¦ãããæ´ã« 16 æ¥ã®å¤ã«ã¯ Cookpad Drinkup at RubyKaigi 2024 ã¨ç§°ãã¦æ親ä¼ãéãã¾ããã
ããªã³ã¯ã¢ããã®è©±ã¯ã¾ãå¾æ¥è¨äºã¨ãã¦å
¬éãããã¨æãã¾ãã</p>
<p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Cookpad Drinkupãããå°ãã§ãªã¼ãã³ã§ãð»ããå¾
ã¡ãã¦ããã¾ããï¼ <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw">#rubykaigi</a> <a href="https://twitter.com/hashtag/rubykaigi_cookpad?src=hash&ref_src=twsrc%5Etfw">#rubykaigi_cookpad</a><a href="https://t.co/vksM1bZ9kW">https://t.co/vksM1bZ9kW</a> <a href="https://t.co/2Q6rOV0uqi">pic.twitter.com/2Q6rOV0uqi</a></p>— Cookpad Tech Life (@cookpad_tech) <a href="https://twitter.com/cookpad_tech/status/1791042673396289785?ref_src=twsrc%5Etfw">2024å¹´5æ16æ¥</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p>
<h2 id="RubyKaigi-2024-ã«ã¤ãã¦">RubyKaigi 2024 ã«ã¤ãã¦</h2>
<p>ã»ãã·ã§ã³ã¯å
¨é¨ã§ 53ï¼å»å¹´ 51 ï¼ã§ãã¹ãã³ãµã¼æ°ã¯ 97ï¼å»å¹´ 89 ï¼ã§ãããã¹ãã³ãµã¼æ°ã¯å人ã¹ãã³ãµã¼ãåå¨ãã¦ãã 2011 å¹´ãæãã°éå»æ大ã®å¤ã§ããã
ä»åæã大ããå¤ãã£ã¦ããã®ã¯ã¤ãã³ãã®æ°ã§ãããä»å¹´ã¯ 23ï¼å»å¹´ 9 ï¼ã®ã¤ãã³ãï¼ãã¼ãã£ï¼ãè¨å®ããã¦ãã¾ãããå¤ãªå¤ãªé£è¦å¸ã®ã©ãã㧠Rubyist ã®éã¾ããéããã¦ãããèªåãé£æ¥ãã¾ãã¾ãªã¨ããã§é£²ã¿ãããããªæ¹ã¨äº¤æµãã§ãã¾ããã
ã¾ããå¤ã ãã§ãªãæ¼ã«ã¯ä»ç¤¾ã®ã¨ã³ã¸ãã¢å士ã§éã¾ãã©ã³ãã«åããã¨ããåãçµã¿ãã¡ãã»ãèµ·ãã£ã¦ãããä»ç¤¾ã® Rubyist ã®æ¹ã¨äº¤æµãããã¨ãã§ãã¾ããã</p>
<h2 id="RubyKaigi-2024ã®å
¨ä½æ">RubyKaigi 2024ã®å
¨ä½æ</h2>
<p>ä»åã® RubyKaigi ã®ã»ãã·ã§ã³ããã¼ãå¥ã«ãã£ããåé¡ãã¦ã¿ãã¨ãããã©ã¼ãã³ã¹ã«é¢ããã»ãã·ã§ã³ãå¤ãè¦ããã¾ããã</p>
<p>ããã©ã¼ãã³ã¹ã«ã¤ãã¦ã¯ Matz ããã® Keynote ã§ã第1ã«ããã©ã¼ãã³ã¹ã第2ã«ããã©ã¼ãã³ã¹...ã¨ã«ããããããã¯ï¼ãï¼ããã©ã¼ãã³ã¹æ¹åã«æ³¨åãã¦ããããã¨è¿°ã¹ããã¦ãã¾ãããå
·ä½çã«ã¯ VM ã®é«éåãã¡ã¢ãªå¹çã®æ¹åãã¹ã¬ããï¼ããã©ã¼ãã³ã¹æ¹åã¨ããéè¦ã¸ã®å¯¾å¿ãã½ããã¦ã§ã¢é¢ã§ã®é«éåçã§ãä»å¾ã©ãã ãæ¹åãã¦ããã楽ãã¿ã§ãã</p>
<p>ã¾ããããã©ã¼ãã³ã¹ä»¥å¤ã§ã¯ãåããã¼ãµã¼ã wasm ã®è©±ãå¤ãããã¾ãããåã«ã¤ãã¦ã¯ day 3 ã® Ruby Committers and the World ã§ãç½ç±ããè°è«ãèµ·ããé¢ç½ãã£ãã§ããRuby ã®åã«ã¤ãã¦ã¯ã¾ã ã¾ã æ¹ãä»ãããã«ããã¾ãããã
ãã¼ãµã¼ã®è©±ã§è¨ãã°ãMatz ããããã·ã³ã¿ãã¯ã¹ã»ã¢ã©ããªã¢ã ã¨ãããå°ãªãã¨ãä»å¹´ãã£ã±ãã¯ææ³ã«æãå ããªãããã«ãããã¼ãµå¨ãã®æ¹åã«æ³¨åãã¦ãããã¨ããææ¡ããªããã¾ãããææ¸ããã¼ãµã® Prism ã¨ææ³å®ç¾©ããçæãã Lrama ãä»å¾ããããã©ã®ããã«çºå±ããäºãã«å½±é¿ãä¸ãã¦ããã®ã注ç®ã§ããä¸ã¯ã¾ãã«å¤§ãã¼ãµæ代ã</p>
<h2 id="ããã©ã¼ãã³ã¹">ããã©ã¼ãã³ã¹</h2>
<p>å
ã»ã©RubyKaigi 2024ã§ã¯ããã©ã¼ãã³ã¹ã®è©±ãå¤ãã£ãã¨è¿°ã¹ã¾ããããããã§ã¯ç¹ã«å°è±¡ã«æ®ã£ãããã©ã¼ãã³ã¹ã®ãã¼ã¯ã«ã¤ãã¦ç´¹ä»ãã¾ãã</p>
<p>åãä¸ããã®ã¯Rubyãã®ãã®ã®å®è¡é度ã®åä¸ãç®çã¨ããã<a href="https://rubykaigi.org/2024/presentations/tenderlove.html">Speeding up Instance Variables with Red-Black Trees</a> ã§ãã</p>
<p>æ¬ãã¼ã¯ã¯ Ruby ã®ã¤ã³ã¹ã¿ã³ã¹å¤æ°ã¸ã®ã¢ã¯ã»ã¹ã平衡äºåæ¢ç´¢æ¨ã§ãã赤é»æ¨ã§é«éåããã¨ããå
容ã§ããæ¢ã«ãã®å
容㯠Ruby 3.4ã«ãã¼ã¸ããã¦ãããPull Request ã¯å³ã§ã (<a href="https://github.com/ruby/ruby/pull/8744">https://github.com/ruby/ruby/pull/8744</a> )ã
ãã®é«éåã«ããã¦ã¯ãRuby 3.2 ã§å°å
¥ããããã¤ã³ã¹ã¿ã³ã¹å¤æ°ã®ç®¡çæ¹æ³ã§ãã Object Shape ãåæã¨ãªã£ã¦ãã¾ããï¼ RubyKaigi 2022ã§CRubyã«å®è£
ãããæã®çºè¡¨ <a href="https://rubykaigi.org/2022/presentations/jemmaissroff.html">https://rubykaigi.org/2022/presentations/jemmaissroff.html</a> ï¼</p>
<p>ãã®ãã¼ã¯ãåãä¸ããçç±ã¨ãã¦ã¯ãç§èªèº«ããã¼ã¿æ§é ãã¢ã«ã´ãªãºã ã使ã£ãé«éåã«ã¤ãã¦èå³ããã£ãã®ã¨ãæã£ãããåæã¨ãªããã® Object Shape ã®æ¥æ¬èªè§£èª¬ãå°ãªããObject Shape ãç解ããä¸ã§èª°ãã®åèã«ãªãã°ã¨æãåãä¸ãã¾ããã</p>
<p>ãããã Ruby (CRuby) ã§ã¯ã¤ã³ã¹ã¿ã³ã¹å¤æ°ãé
åã§ä¿åãã¦ãããã<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>ãã¤ã³ã¹ã¿ã³ã¹å¤æ°ã«ã¢ã¯ã»ã¹ããã«ã¯ãå½è©²é
åã«ããã¦ã¤ã³ã¹ã¿ã³ã¹å¤æ°ã¨å¯¾å¿ãã¦ããè¦ç´ ã®indexãç¥ãå¿
è¦ãããã¾ããRuby VM ã§ã¯ã¤ã³ã©ã¤ã³ãã£ãã·ã¥ã¨å¼ã°ããããã¿ã«ãããindex ããã£ãã·ã¥ãããã¨ã§é«éåãè¡ãªã£ã¦ãã¾ããããããåãã¤ã³ã¹ã¿ã³ã¹å¤æ°åãããã¯ã©ã¹ãè¤æ°ç»å ´ããã¨ãã£ãã·ã¥ããã¾ãæ©è½ããªãã¨ããåé¡ãããã¾ããããããªæãRuby 3.2ã§å°å
¥ããã Object Shape ãããã解決ãã¾ããã Object Shape ã«ãããå¥ã®ã¤ã³ã¹ã¿ã³ã¹ã ããååã¤ã³ã¹ã¿ã³ã¹å¤æ°ã®indexãåãã§ãããã¨ãããã¨ãèªèãããã¨ãã§ããããã«ãªããindex ãåããªã®ã§ä½¿ãã¾ãããã¨ãã¦ããã£ãã·ã¥ãããçãåä¸ããããã¨ãã§ããããã«ãªãã¾ããã</p>
<p>ããã§ããã£ãã·ã¥ãããããªãï¼ãã£ãã·ã¥ãã¹ï¼ãèµ·ããäºä¾ã¯ããªãããããã§ãä¾ãã°ä¸ã®ãããªã¡ã¢å㯠Object Shape ã¨ç¸æ§ãæªãããã§ãã</p>
<pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">class</span> <span class="synType">Sample</span>
<span class="synPreProc">def</span> <span class="synIdentifier">initialize</span>(x)
<span class="synIdentifier">@hoge</span> = x
<span class="synPreProc">end</span>
<span class="synPreProc">def</span> <span class="synIdentifier">foo</span>(x)
<span class="synIdentifier">@_foo</span> ||= x
<span class="synPreProc">end</span>
<span class="synPreProc">def</span> <span class="synIdentifier">bar</span>(x)
<span class="synIdentifier">@_bar</span> ||= x
<span class="synPreProc">end</span>
<span class="synPreProc">end</span>
</pre>
<p>Object Shape ã§ã¯ã¤ã³ã¹ã¿ã³ã¹å¤æ°ã®åå¼ã³åºãã«ãã Shape ã決å®ããã¾ãããã®ãããã¤ã³ã¹ã¿ã³ã¹ã«ããã foo 㨠bar ã®æåã®å¼ã³åºãã®é çªãç°ãªãã ãã§éã Shape ã«ãªã£ã¦ãã¾ãããã£ãã·ã¥ã使ããã¨ãã§ãã¾ãããçµæã¨ãã¦ãã£ãã·ã¥ãã¹ãèµ·ããã¾ãããã£ãã·ã¥ãã¹ãèµ·ããã¨ã¤ã³ã¹ã¿ã³ã¹å¤æ°ã® index ãç²å¾ããå¿
è¦ãããã¾ããObject Shape ã使ã£ãå ´åããã®ã¯ã©ã¹ãè¤é㪠Shape ã§ãªãå ´åã¯ã¤ã³ã¹ã¿ã³ã¹å¤æ°ã® index ãç²å¾ããããã« O(|ã¤ã³ã¹ã¿ã³ã¹å¤æ°|) ã®è¨ç®å¦çãå¿
è¦ã§ããã
ãã®ããããã£ãã·ã¥ãã¹æã® index ã®é«éåå¾ãæ±ãããã¦ãã¾ããã</p>
<p>Object Shape èªä½ã¯ããããæ¨æ§é ã§ããããã® Shape ã管çãã¦ãã¦ä¸ã¤ããããã®ã¤ã³ã¹ã¿ã³ã¹å¤æ°ã® index ãåãã¼ãã«æ ¼ç´ããã¦ãã¾ããæ¬ãã¼ã¯ã®ã¢ã¤ãã£ã¢ã¯ãã®æ¨æ§é ãã§ããã ãåä¸ã®é«ãã«ãããã¨ã§ index ã®æ¢ç´¢ãæ©ãããã¨ãããã®ã§ãã</p>
<p>å人çãªææ³ã¨ãã¦ã赤é»æ¨ã¯åã平衡äºåæ¢ç´¢æ¨ã§ããAVLæ¨ã«æ¯ã¹ã¦æ¨ã®å½¢ãæªã«ãªãããããã¨ãç¥ããã¦ããã®ã§ããã¼ãã®æ¤ç´¢ã¯ AVL æ¨ã®æ¹ãéãã¯ãã§ãããã®ãããAVL æ¨ã§å®è£
ããå ´åã¨ã©ã¡ããéãã®ãæ°ã«ãªãã¾ããã
ã¾ããæ¬ãã¼ã¯ãè´ãã¾ã§ã¯ Object Shape ã«ã¤ãã¦ãã¾ã詳ããç¥ããªãã£ãã®ã§ãæ¬ã»ãã·ã§ã³ã«ãã Object Shape ã®æ°æã¡ãå¤å°æãããã¨ãã§ãã¾ããã
å ãã¦ãRuby ã®é«éåã®ãã Object Shape ã«åªããã³ã¼ãï¼ã¤ã³ã¹ã¿ã³ã¹å¤æ°ã®ä»£å
¥ã¯åãé åºã§è¡ãæ¹ãè¯ãï¼ãæ¸ããã¨ããæ°ã«ãªãã¾ããã
ãã ä¸æ¹ã§ç¤¾å
ã®äººã¨è°è«ãããéããRuby ã®ã¦ã¼ã¶ã¼ã¨ãã¦ã¯ã©ãæ¸ãã¦ãããç¨åº¦éããªã£ã¦ã»ãããã¨ãã話é¡ã«ãªããæ®æ®µãã Object Shape ãèããªããã³ã¼ããæ¸ããã¨ã¯é£ããããã¨ãã話ã«ãªãã¾ããã
ãã¡ããæ®æ®µãã常ã«æèãããã¨ã¯é£ããã§ãããä¾ãã°ã¡ã¢åãã§ããã ãæ§ããã¨ããã³ã³ã¹ãã©ã¯ã¿ã«ãããå¤æ°åæåã®éã«å¤æ°ã®é çªãä¸å®ã«ãããã¼ã«ã使ãï¼ããã®ãï¼ï¼ã¨ãã§ããç¨åº¦ Object Shape ãã¬ã³ããªã¼ãªã³ã¼ããæ¸ããã¨ãã§ããã®ããªã¨ãæãã¾ãããåè<sup id="fnref:2"><a href="#fn:2" rel="footnote">2</a></sup></p>
<p>ä»å¾ãã©ã®ãããªé«éåææ³ãåºã¦ããã楽ãã¿ã§ãã</p>
<h2 id="çµããã«">çµããã«</h2>
<p>次åã® RubyKaigi 2025 ã¯æåªçæ¾å±±å¸ã«ã¦éå¬äºå®ã§ãã
æåªçã«ã¯åã¨åå°ã®ç¥é ãã®èå°ã«ãªã£ãéå¾æ¸©æ³ããããã£ã©ã®ããªã£ãã<sup id="fnref:3"><a href="#fn:3" rel="footnote">3</a></sup>ãªã©ãããã®ã§ã¨ã¦ã楽ãã¿ã§ãã</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn:1">
<a href="https://github.com/ruby/ruby/blob/fd549b229b0822198ddc847703194263a2186ed1/include/ruby/internal/core/robject.h#L97">https://github.com/ruby/ruby/blob/fd549b229b0822198ddc847703194263a2186ed1/include/ruby/internal/core/robject.h#L97</a><a href="#fnref:1" rev="footnote">↩</a></li>
<li id="fn:2">
<a href="https://island94.org/2023/10/writing-object-shape-friendly-code-in-ruby">https://island94.org/2023/10/writing-object-shape-friendly-code-in-ruby</a><a href="#fnref:2" rev="footnote">↩</a></li>
<li id="fn:3">
<a href="https://www.barysan.net">https://www.barysan.net</a><a href="#fnref:3" rev="footnote">↩</a></li>
</ol>
</div>
horiso0921
NLP2024 ã«åå ãã¾ãã
hatenablog://entry/6801883189100841820
2024-04-25T13:03:54+09:00
2024-04-25T13:03:54+09:00 ããã«ã¡ã¯ï¼ æè¡é¨æ©æ¢°å¦ç¿ã°ã«ã¼ãã®å±±å£ (@altescy) ã§ãã å
æãç¥æ¸ã«ã¦éå¬ãããè¨èªå¦çå¦ä¼ç¬¬30åå¹´æ¬¡å¤§ä¼ (NLP2024)ã«åããæ©æ¢°å¦ç¿ã°ã«ã¼ãã®æ·±æ¾¤ (@fufufukakaka)ã¨å
±ã«åå ãã¦ãã¾ããã æ¨å¹´ã«å¼ãç¶ãä»å¹´ãéå»æå¤ã®åå è
æ°ã¨ãªããè¨èªå¦çç 究ã®çãä¸ãããå®æãã¾ããã ç¹ã«å»å¹´ã®å¹´æ¬¡å¤§ä¼ (NLP2023) ã®ã¿ã¤ãã³ã°ã§ GPT-4 ãçºè¡¨ããã¦ä»¥éãèªç¶è¨èªå¦çã®ç 究ã¯å¤§ããªè»¢ææãè¿ãã¦ããã¨æãã¾ãã 大è¦æ¨¡è¨èªã¢ãã« (LLM) ãç 究ã®ä¸»æµã¨ãªãä¸ãã©ããªèª²é¡ãçºè¦ãããã®ããæå¾
ããã£ã¦åå ãã大ä¼ã¨ãªãã¾ããã ãã®è¨äºã§ã¯ â¦
<p>ããã«ã¡ã¯ï¼
æè¡é¨æ©æ¢°å¦ç¿ã°ã«ã¼ãã®å±±å£ (<a href="https://x.com/altescy">@altescy</a>) ã§ãã</p>
<p>å
æãç¥æ¸ã«ã¦éå¬ããã<a href="https://www.anlp.jp/nlp2024/">è¨èªå¦çå¦ä¼ç¬¬30åå¹´æ¬¡å¤§ä¼ (NLP2024)</a>ã«åããæ©æ¢°å¦ç¿ã°ã«ã¼ãã®æ·±æ¾¤ (<a href="https://x.com/fukkaa1225">@fufufukakaka</a>)ã¨å
±ã«åå ãã¦ãã¾ããã
æ¨å¹´ã«å¼ãç¶ãä»å¹´ãéå»æå¤ã®åå è
æ°ã¨ãªããè¨èªå¦çç 究ã®çãä¸ãããå®æãã¾ããã</p>
<p>ç¹ã«å»å¹´ã®å¹´æ¬¡å¤§ä¼ (NLP2023) ã®ã¿ã¤ãã³ã°ã§ GPT-4 ãçºè¡¨ããã¦ä»¥éãèªç¶è¨èªå¦çã®ç 究ã¯å¤§ããªè»¢ææãè¿ãã¦ããã¨æãã¾ãã
大è¦æ¨¡è¨èªã¢ãã« (LLM) ãç 究ã®ä¸»æµã¨ãªãä¸ãã©ããªèª²é¡ãçºè¦ãããã®ããæå¾
ããã£ã¦åå ãã大ä¼ã¨ãªãã¾ããã</p>
<p>ãã®è¨äºã§ã¯ NLP2024 ã«ã¦ã¯ãã¯ãããããçºè¡¨ãã 2 ã¤ã®ç 究ã¨ããã®ä»ã®èå³æ·±ãã£ãç 究ã«ã¤ãã¦ããã¤ãç´¹ä»ãã¾ãã</p>
<h2 id="çºè¡¨å
容ã®ç´¹ä»">çºè¡¨å
容ã®ç´¹ä»</h2>
<p>ã¯ãã¯ãããããã¯ä»¥ä¸ 2 ã¤ã®ç 究ãçºè¡¨ãã¾ããã</p>
<ul>
<li><a href="#p2-11-sequential-recommendation-%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E6%83%85%E5%A0%B1%E3%82%92%E6%B4%BB%E7%94%A8%E3%81%97%E3%81%9F%E6%9C%AA%E7%9F%A5%E3%82%A2%E3%82%A4%E3%83%86%E3%83%A0%E3%81%B8%E3%81%AE%E5%AF%BE%E5%87%A6%E6%B3%95%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E5%88%86%E6%9E%90">P2-11: Sequential Recommendation ã«ãããããã¹ãæ
å ±ãæ´»ç¨ããæªç¥ã¢ã¤ãã ã¸ã®å¯¾å¦æ³ã«é¢ããåæ</a></li>
<li><a href="#p3-8-recipests-%E3%83%AC%E3%82%B7%E3%83%94%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E9%A1%9E%E4%BC%BC%E6%80%A7%E8%A9%95%E4%BE%A1">P3-8: RecipeSTS: ã¬ã·ãã®ããã®é¡ä¼¼æ§è©ä¾¡</a></li>
</ul>
<h3 id="P2-11-Sequential-Recommendation-ã«ãããããã¹ãæ
å ±ãæ´»ç¨ããæªç¥ã¢ã¤ãã ã¸ã®å¯¾å¦æ³ã«é¢ããåæ">P2-11: Sequential Recommendation ã«ãããããã¹ãæ
å ±ãæ´»ç¨ããæªç¥ã¢ã¤ãã ã¸ã®å¯¾å¦æ³ã«é¢ããåæ</h3>
<ul>
<li>èè
: 深澤ç¥æ´ãå±±å£æ³°å¼</li>
<li>è«æ: <a href="https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/P2-11.pdf">https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/P2-11.pdf</a></li>
</ul>
<p>æ¨è¦ã¢ãã«ã®ä¸ç¨®ã¨ãã¦ãSequential Recommendation Modelãããã¾ããããã¯ã¢ã¤ãã IDã®ç³»åæ
å ±ãåºã«æ¬¡ã«ã¢ã¯ã·ã§ã³ãã¹ãã¢ã¤ãã ãæ¨è¦ããã¢ãã«ã§ãããã®ã¢ãã«ã¯æªç¥ã®ã¦ã¼ã¶ã¼ã§ãã£ã¦ããæ¢ç¥ã®ã¢ã¤ãã ã§æ§æãããå±¥æ´ãããã°æ¨è¦ãå¯è½ã§ããããããæªç¥ã®ã¢ã¤ãã ãå
¥åã¨ãã¦ä¸ããããå ´åããã®ã¢ã¤ãã ã¯ã¢ãã«ã«ã¨ã£ã¦out-of-vocabularyã§ãããå
¥åã«ä½¿ç¨ãããã¨ã¯ã§ãã¾ãããå®éã®ãµã¼ãã¹ã§æ¨è¦ã¢ãã«ãéç¨ããä¸ã§ã¯ãå¦ç¿æã«åå¨ããªãã£ãæ°çã¢ã¤ãã ãç¡è¦ããããå¾ãªãã§ãããä»®ã«ããããã¢ã¤ãã ã ããè¦ã¦ããã¦ã¼ã¶ãããã¨ããã¨ããã®ã¦ã¼ã¶ã«ã¯æ¨è¦ã表åºã§ãã¾ããã
ãã®ç 究ã§ã¯ãæªç¥ã®ã¢ã¤ãã ãå
¥åãããéã«æãå¹æçã«ãããåãæ±ãæ¹æ³ãæ¤è¨ãã¾ããã</p>
<p>æ¬ç 究ã§ã¯ã¯ãã¯ãããã«ãããå®éã®é²è¦§å±¥æ´ãã¼ã¿ãåã³NII IDRã§å
¬éãã¦ããã¤ããã½ãã¼ã¿ã»ããã使ç¨ãã¾ãããSequential Recommendation Modelã¨ãã¦ã¯ã2021å¹´SIGIRã§çºè¡¨ãããCOREã使ç¨ãã¾ãã</p>
<p>ä»åãæªç¥ã¢ã¤ãã ãåãæ±ãæ¹æ³ã¨ãã¦3ã¤ã®ææ³ãææ¡ãã¾ããã</p>
<ol>
<li>ããã¹ãæ
å ±ãç¨ããæªç¥ã¢ã¤ãã ã®IDåãè¾¼ã¿æ¨æ¸¬(Embedding Mapping)
<ul>
<li>æªç¥ã¢ã¤ãã ãæã¤æ
å ±ã¨ãã¦ããã¹ãæ
å ±(æ¬ç 究ã§ã¯ã¬ã·ãã¿ã¤ãã«)ã使ç¨ãã¾ãã</li>
<li>äºãå¦ç¿ãããæ¨è¦ã¢ãã«ã®IDåãè¾¼ã¿ã¨ã¢ã¤ãã ã®ããã¹ãæ
å ±ã対ã«ãã¦ãããã¹ãæ
å ±ããIDåãè¾¼ã¿ãæ¨å®ããã¢ãã«ãå¦ç¿ãã¾ãã</li>
<li>æªç¥ã¢ã¤ãã ãå
¥åãããéã«ã¯ããã®ã¢ã¤ãã ã®ããã¹ãæ
å ±ãç¨ãã¦IDåãè¾¼ã¿ãæ¨å®ãããã®IDåãè¾¼ã¿ãç¨ãã¦æ¨è¦ãè¡ãã¾ãã</li>
<li>ãã®ææ³ã®æ§ç¯ã«ã¯BERTåã³LSTMã使ç¨ãã¾ããã</li>
</ul>
</li>
<li>é¡ä¼¼åº¦ãé«ãã¢ã¤ãã ã«ããç½®æ(Replace Similar Item)
<ul>
<li>æªç¥ã¢ã¤ãã ãå
¥åãããéã«ããã®ã¢ã¤ãã ã¨ããã¹ãã®é¡ä¼¼åº¦ãé«ãæ¢ç¥ã¢ã¤ãã ãå
¥åå±¥æ´ããæ¢ãåºãããã®ã¢ã¤ãã ãæªç¥ã¢ã¤ãã ã®ä»£ããã«å
¥åã¨ãã¦ä½¿ç¨ãã¾ãã</li>
</ul>
</li>
<li>æªç¥ã¢ã¤ãã ãå
¥åå±¥æ´ããé¤å¤(Ignore)
<ul>
<li>æªç¥ã¢ã¤ãã ãå
¥åãããéã«ããã®ã¢ã¤ãã ãå
¥åå±¥æ´ããé¤å¤ããæ®ãã®ã¢ã¤ãã ã®ã¿ã使ç¨ãã¦æ¨è¦ãè¡ãã¾ãã</li>
</ul>
</li>
</ol>
<p>å±¥æ´ã®å
é ã¾ãã¯æ«å°¾ããæ大5ã¤ãæªç¥ã¢ã¤ãã ã§ããã¨ä»®å®ãã¾ããæªç¥ã¢ã¤ãã ã¨ãªã£ããã®ã«ã¤ãã¦ã¯ãæ¨è¦ã¢ãã«ãã対象ã®IDåãè¾¼ã¿ãåé¤ãã¦å
¥åã«å©ç¨ã§ããªãããã«ãã¾ãã</p>
<p>以ä¸ãå®é¨çµæã¨ãªãã¾ããããããã®ãã¼ã¿ã»ããã«å¯¾ãã¦ãåææ³ãé©ç¨ããéã®NDCG@50ã®çµæã§ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="NDCG@50 ã®çµæä¸è¦§"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/altescy/20240423/20240423182832.png" width="1200" height="670" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>NDCG@50 ã®çµæä¸è¦§</figcaption></figure></p>
<p>ã¾ãåããåºæ¬çãªãã¨ã¨ãã¦ãå±¥æ´ã®å
é å´ãããæ«å°¾å´(ããæè¿ã®ã¢ã¤ãã )ãæªç¥ã§ããå ´åã®æ¹ã精度影é¿ã大ãããã¨ãè¦ã¦åãã¾ããåææ³ã«é¢ããçµæã¨ãã¦ã¯ãä»åææ¡ããææ³ã§ãã Embedding Mapping ãä»ææ³ã¨æ¯ã¹ã¦ååãªç²¾åº¦ãåºãã«ã¯è³ãããå¤ãã®å ´åã§ã·ã³ãã«ãªå¥ææ³ãä¸åãçµæã¨ãªãã¾ãããä»å対象ã¨ããç³»åã10è¦ç´ 以ä¸ã®ãã®ã®ã¿ã対象ã¨ãã¦ãããã¨ããããåç´ã«æªç¥ã¢ã¤ãã ãé¤å¤ãã Ignore ãã»ã¨ãã©ã®ã±ã¼ã¹ã§æãè¯ãæ§è½ã示ãã¾ããããã ãé²è¦§ãã¼ã¿ã»ã¤ããã½ãã¼ã¿ã®ã©ã¡ãã対象ã¨ãã¦ãããã«ãã£ã¦è¥å¹²çµæã¯ç°ãªã£ã¦ãããé¡ä¼¼ã¢ã¤ãã ã並ã³ãããé²è¦§ãã¼ã¿ã«å¯¾ãã¦ã¯ Replace Similar Item ãæå¹ã«åãå ´é¢ãããã¾ããã</p>
<p>ãã®çµæã«é¢é£ãã¦ä»¥ä¸ã®ãããªåæãè¡ãã¾ããã
äºåã«ã¬ã·ãããã¹ãã§å¦ç¿ãããfastTextãã¯ãã«ã§ã³ãµã¤ã³é¡ä¼¼åº¦ã0.8以ä¸ã¨ãªãã¿ã¤ãã«é¡ä¼¼ãã¢ã100çµæ½åºãã¾ãããã®å¾ãEmbedding Mappingï¼LSTMï¼ã¨COREã®IDåãè¾¼ã¿ãã¯ãã«ã使ã£ã¦ãã¢ã®ã³ãµã¤ã³é¡ä¼¼åº¦ãè¨ç®ãããã®å¹³åãç®åºãã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="é¡ä¼¼ãã¢ã®èª¿æ»"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/altescy/20240423/20240423182908.png" width="670" height="226" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>é¡ä¼¼ãã¢ã®èª¿æ»</figcaption></figure></p>
<p>ãã®çµæãè¦ãã¨ãæ¬æ¥è¿ã¥ãããã£ã Embedding Mapping(LSTM) 㨠CORE ã¨ã®éã§å
¨ãç°ãªãå¾åãè¦ããã¦ãã¾ããfastTextãã¯ãã«ã®é¡ä¼¼åº¦ãé«ãã¬ã·ãå士ã§ãã£ã¦ããCORE ã®IDåãè¾¼ã¿ã®é¡ä¼¼åº¦ã¯æ¯è¼çä½ãã§ãããã¨ããããã¾ããã
ãã®ãã¨ãããä»åææ¡ãã Embedding Mapping ã§ã¯æããããªãæ§è³ªã CORE ã® IDåãè¾¼ã¿ã«åãã£ã¦ãããã¨ãèãããã¾ããIDåãè¾¼ã¿ã復å
ããããã«å¦ç¿ãããã¨ã§æ§è³ªãç²å¾ã§ããªãããã¨èãã¦åãæãã£ãã®ã§ããã¾ã æ¹åã§ããç¹ãããããã§ããã</p>
<p>Sequential Recommendation Model ã¯ãªã³ã©ã¤ã³æ¨è¦ãå®è£
ããä¸ã§é常ã«éè¦ãªé¸æè¢ã®ä¸ã¤ã§ããä¸æ¹ã§å®ãµã¼ãã¹ã§ã®éç¨ãèããã¨ãã¢ãã«å¦ç¿å¾ã«ç»é²ãããæ°çã¢ã¤ãã ãä¸æãæ¨è«ã«å©ç¨ã§ããããã«ãªãã°ãä»ã¾ã§ä»¥ä¸ã«ã¦ã¼ã¶ã®è¡åãæããããããã«ãªãã¯ãã§ãã
ä»å¾ã®ç 究ã§ã¯ãããåºããã©ã¡ã¼ã¿è¨å®ã®ã§å®é¨ãè¡ãã¨å
±ã«ããè¯ã IDåãè¾¼ã¿ã®æ¨å®æ¹æ³ã模索ãããªã©ã«åªãã¦ããããã¨æãã¾ãã</p>
<h3 id="P3-8-RecipeSTS-ã¬ã·ãã®ããã®é¡ä¼¼æ§è©ä¾¡">P3-8: RecipeSTS: ã¬ã·ãã®ããã®é¡ä¼¼æ§è©ä¾¡</h3>
<ul>
<li>èè
: å±±å£æ³°å¼ã深澤ç¥æ´ãå島ç´</li>
<li>è«æ: <a href="https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/P3-8.pdf">https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/P3-8.pdf</a></li>
</ul>
<p>ã¬ã·ãã¯èªç¶è¨èªã§æ¸ãããææ¸å½¢æ
ã®ä¸ç¨®ã§ãããã®ã®ãé常ã®ããã¹ãã¨ã¯ç°ãªãç¹å¾´ãæã£ã¦ããããæ¢åã®ãã¼ã¿ã»ããã§è©ä¾¡ãããåºç¤ã¢ãã«ãã¬ã·ããä¸æãæ±ãããã©ããã¯å®ãã§ã¯ããã¾ããã
ãã®ç 究ã§ã¯è¨èªã¢ãã«ã®ã¬ã·ãå¦çè½åãç解ããããã®ç¬¬ä¸æ©ã¨ãã¦ãã¬ã·ãã¿ã¤ãã«ã対象ã«ãã STS (Semantic Textual Similarity) ãã¼ã¿ã»ãããæ§ç¯ããæ¢åã®è¨èªã¢ãã«ã®è©ä¾¡ã¨ä»å¾ã®ç 究æ¹éã示ãã¾ããã</p>
<p>ãã¼ã¿ã»ããã®ä½æã«ããã£ã¦ã¯ã2ã¤ã®ç°ãªãã¬ã·ãã¿ã¤ãã«ã®ãã¢ã«å¯¾ãã¦ä»¥ä¸ã®ã¢ããã¼ã·ã§ã³åºæºã«åºã¥ã人æã«ããã¢ããã¼ã·ã§ã³ãå®æ½ãã¾ããã
500件ã®ãã¢ã«ã¤ãã¦ã1ãã¢ããã 5 人ã®ä½æ¥è
ã 0 ~ 5 ã®ã¹ã³ã¢ãä»ä¸ãããã®å¹³åå¤ãæ£è§£ã®ã¹ã³ã¢ã¨ãã¦æ¡ç¨ãã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="RecipeSTS ã®ã¢ããã¼ã·ã§ã³åºæº"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/altescy/20240423/20240423182934.png" width="1094" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>RecipeSTS ã®ã¢ããã¼ã·ã§ã³åºæº</figcaption></figure></p>
<p>ä½æãã RecipeSTS ãã¼ã¿ã»ãããç¨ãã¦è¨èªã¢ãã« (BERT / T5) ã®æ§è½ãè©ä¾¡ããçµæã以ä¸ã®å³ã«ãªãã¾ãã
åè¨èªã¢ãã«ããä½æããã¬ã·ãã¿ã¤ãã«ã®åãè¾¼ã¿è¡¨ç¾ã®ã³ãµã¤ã³é¡ä¼¼åº¦ã¨ãã¢ããã¼ã·ã§ã³ãããã¹ã³ã¢ã®ã¹ãã¢ãã³é ä½ç¸é¢ä¿æ°ã示ãã¦ãã¾ãã
ã¾ãã<code>+ fine-tuning</code> ã¯äºåå¦ç¿æ¸ã¿ã¢ãã«ã«å¯¾ãã¦ç¬èªã®ã¬ã·ããã¼ã¿ã§è¿½å å¦ç¿ããã¢ãã«ã表ãã¦ãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="RecipeSTS ã®è©ä¾¡çµæ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/altescy/20240423/20240423183004.png" width="1137" height="780" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>RecipeSTS ã®è©ä¾¡çµæ</figcaption></figure></p>
<p>ä»å試ããä¸ã§æãé«ãæ§è½ã示ããã®ã¯ã¬ã·ããã¼ã¿ã§è¿½å å¦ç¿ãã BERT ã¢ãã«ã§ããã
åããã¦è©ä¾¡ãè¡ã£ã <a href="https://github.com/yahoojapan/JGLUE?tab=readme-ov-file#jsts">JSTS</a> ã®çµæã¨æ¯ã¹ãã¨ãæ¢åã®è¨èªã¢ãã«ã¯ä¸è¬çãªããã¹ãã«æ¯ã¹ã¦ã¬ã·ãããã¹ãã®å¦çã¯ä¸å¾æãªå¾åãããããã«è¦ãã¾ãã</p>
<p>ã¾ãã追å 㧠OpenAI Embedding API ã使ã£ãè©ä¾¡ãä¸å³ä¸é¨ã«è¨è¼ãã¾ããã
è«æå·çæç¹ã§ã¯ text-embedding-3 ã®å
¬éåã§ãã£ããã追å å¦ç¿ããã¢ãã«ãæãé«ãæ§è½ã示ãã¦ãã¾ããããtext-embedding-3-large ã¯ä»åæ¯è¼ããã¢ãã«ã®ä¸ã§æé«æ§è½ãéæãã¦ãã¾ãã
ããã§ããã¯ã JSTS ã®çµæã¨æ¯ã¹ãã¨ã¬ã·ãããã¹ããä¸å¾æã¨ããå¾åã¯ããããã§ãã¬ã·ãå¦çã«ããã課é¡ã¯ä¾ç¶ã¨ãã¦æ®ããã¦ããã¨è¨ããã§ãããã</p>
<p><figure class="figure-image figure-image-fotolife" title="RecipeSTS ã®äºä¾"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/altescy/20240423/20240423183030.png" width="1200" height="381" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>RecipeSTS ã®äºä¾</figcaption></figure></p>
<p>ããã¤ãã®äºä¾ãããã¯ã¢ãããã¦ã¿ãã¨ãä¸å³ã® (a)ã(b)ã(d) ã®ããã«è¡¨å±¤çãªé¡ä¼¼ã»ç¸éã®å½±é¿ã§æå³çãªé¡ä¼¼æ§ãæãããã¦ããªãã±ã¼ã¹ãè¤æ°åå¨ãã¾ããã
ã¾ããäºä¾ (c) ã¯ã©ã¡ããããªã¤ã¹ã¿ã¼çããã§ãããã®ã®ã調çæ³ãé£æãªã©çç®ãã観ç¹ã«ãã£ã¦é¡ä¼¼æ§ã®è©ä¾¡ãå¤åããã±ã¼ã¹ã¨èãããã¾ãã
ã¬ã·ãã®é¡ä¼¼æ§è©ä¾¡ã«ããã¦ã¯ãããå¤é¢çãªåºæºãå¿
è¦ã«ãªãããã§ãã</p>
<p>é¡ä¼¼æ§è©ä¾¡ã¯ã¢ãã«é¸æãªã©æ©æ¢°å¦ç¿ã¿ã¹ã¯ã®åºç¤ã®ã¿ã§ãªããæ¤ç´¢ãæ¨è¦ã¨ãã£ãå¿ç¨ã«ããã¦ãéè¦ãªè¦ç´ ã§ãã
ä»å¾ã®ç 究ã§ã¯ã調çæ³ã»é£æã»å³ä»ãã¨ãã£ãããã¬ã·ãã«ç¹åããå¤é¢çãªåºæºã«åºã¥ããã¼ã¿ã»ããã®æ§ç¯ããã¬ã·ãã«é©ããåºç¤ã¢ãã«ã®éçºã«åãçµã¿ããã¨èãã¦ãã¾ãã</p>
<h2 id="æ°ã«ãªã£ãçºè¡¨">æ°ã«ãªã£ãçºè¡¨</h2>
<p>以ä¸ã¯ NLP2024 ã§çºè¡¨ãããç 究ã®ä¸ãããå±±å£ã»æ·±æ¾¤ãç¹ã«èå³æ·±ãã£ããã®ãããã¯ã¢ãããã¦ç´¹ä»ãã¾ãã</p>
<h3 id="A4-3-LLM-ã®åºåçµæã«å¯¾ãã人éã«ããè©ä¾¡åæã¨GPT-4-ã«ããèªåè©ä¾¡ã¨ã®æ¯è¼åæ">A4-3: LLM ã®åºåçµæã«å¯¾ãã人éã«ããè©ä¾¡åæã¨GPT-4 ã«ããèªåè©ä¾¡ã¨ã®æ¯è¼åæ</h3>
<ul>
<li>ç´¹ä»: å±±å£</li>
<li>è«æ: <a href="https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/A4-3.pdf">https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/A4-3.pdf</a></li>
</ul>
<p>é¡ã®éããLLMã®åºåçµæã人éãGPT-4ã§è©ä¾¡ããçµæãæ¯è¼åæããã¨ããç 究ã§ãã
<code>GPT3.5-turbo-1106</code> (GPT-3.5) 㨠<code>houou-instruction-7b-v2</code> (houou) ã対象ã«ãRakuda ãã³ããã¼ã¯ãç¨ãã¦ããããã®å¿çã«ã¤ãã¦é¢é£æ§ã»æ£ç¢ºæ§ãªã©è¤æ°ã®è¦³ç¹ã§è©ä¾¡ãè¡ã£ã¦ãã¾ãã
å®é¨ã®çµæã人éã¨GPT-4ã®å¤æã«ã¯ä¹é¢ããããGPT-4 㯠houou ã®æ¹ãåªãã¦ããã¨è©ä¾¡ããä¸æ¹ã人é㯠GPT-3.5 ã®æ¹ãåªãã¦ããã¨è©ä¾¡ããã±ã¼ã¹ãå¤ãã£ãã¨ã®ãã¨ã§ãã</p>
<p>houou ã¯å
·ä½çãªæ°å¤ãæ
å ±ãå«ãå¿çãçæããå¾åãããããããGPT-4 ã¯ããããæ
å ±ã®å
·ä½æ§ãè©ä¾¡ããã¨èãããã¦ãã¾ãã
ãããã人éãäºå®ç¢ºèªãå«ã㦠houou ãè©ä¾¡ããã¨ããããã«ã·ãã¼ã·ã§ã³ãå¤ããç¹ã«æ£ç¢ºæ§ã®ç¹ã§å£ã£ã¦ããã¨å¤æãããããã§ãã
houou ã®å¦ç¿ã«å©ç¨ããã ichikara-instruction ãã¼ã¿ã»ããã¯å
·ä½çãªæ
å ±ãå«ãä¾ãå¤ãããã®å¾åã houou ã®åºåã«ãåæ ããã¦ããã¨èãããã¦ãã¾ãã</p>
<p>ãã®ç 究ãè¦ãã¨ãLLMãè©ä¾¡ã«å©ç¨ãããã¨ãã¤ã³ã¹ãã©ã¯ã·ã§ã³ãã¼ã¿ã»ãããè¨è¨ã»æ§ç¯ããé£ãããæãã¾ãã
æ
å ±ã®å
·ä½æ§ã¨æ£ç¢ºæ§ã®ãã¬ã¼ããªãã«ã¤ãã¦ç¤ºåãå¾ãããèå³æ·±ãçºè¡¨ã§ããã</p>
<h3 id="P6-25-èªå·±èªç¥ã¯-LM-as-KB-ã®ä¿¡é ¼æ§ãé«ããã">P6-25: èªå·±èªç¥ã¯ LM as KB ã®ä¿¡é ¼æ§ãé«ããã</h3>
<ul>
<li>ç´¹ä»: å±±å£</li>
<li>è«æ: <a href="https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/P6-25.pdf">https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/P6-25.pdf</a></li>
</ul>
<p>çå½å¤ã§åçå¯è½ãªQAã¿ã¹ã¯ (StrategyQA) ã対象ã«ãäºæ¸¬ã®ä¸ç¢ºå®æ§ãèæ
®ãã¦å¿çãè¡ãä»çµã¿ãææ¡ããç 究ã§ãã
äºæ¸¬ãä¸ç¢ºå®ãªå ´åã¯è³ªåãç価ãªå½é¡éåã»è«çå¼ã¸ã¨å帰çã«åå²ããããããã®å½é¡ã«å¯¾ãã¦åçãå¾ããã¨ã§ä¸ãããã質åã«çããã¨ããææ³ (Back-off LMKB) ãææ¡ãã¦ãã¾ãã
ä¸ç¢ºå®æ§ãèæ
®ããªãå ´åãéæ¥è¨¼æããªãå ´åã¨æ¯ã¹ã¦ãææ¡ææ³ãç¨ãããã¨ã§ããæ£ç¢ºãªåçãå¾ããããã¨ã示ãã¦ãã¾ãã</p>
<p>å¿çãçå½å¤ã§ãããã¨ãå©ç¨ãã¦è³ªåãè«çå¼ã«å解ããã¨ããçºæ³ã¯åççã§èå³æ·±ãã¨æãã¾ããã
æåã®åçã§çå½ä¸æã ã£ã質åã«ããã¦ãéæ¥è¨¼æã«ããæ£ç¢ºãªåçãå¾ããã¦ãã¦ãææ¡ææ³ã®æå¹æ§ã示ããã¦ãã¾ãã
ä¸æ¹ã課é¡ã«ãæ¸ããã¦ããããã«ä¸ç¢ºå®æ§ã®æ¨å®ãå½é¡éåã®çæ精度ã«ã¤ãã¦ã¯ä»å¾ã®çºå±ãæå¾
ããã¾ãã</p>
<p>å人çã«èªå·±èªç¥çãªã¢ããã¼ã㯠LLM ãã¯ããã¨ãã AI ã·ã¹ãã ã®è½ååä¸ã«ã¤ãªããã®ã§ã¯ãªããã¨æå¾
ãã¦ãã¾ãã
ã¢ãã«èªèº«ãåºåãå帰çã«æ¤è¨¼ããã¨ããä»çµã¿ã¯ãä»ã®ã¿ã¹ã¯ã«ãé©ç¨ã§ããå¯è½æ§ãããã¨æãã¾ããã</p>
<h3 id="P10-6-äºåå¦ç¿æ¸ã¿ã®åæ£è¡¨ç¾ã¯è¡¨å±¤çãªç¥èãç²å¾ãã¦ããã">P10-6: äºåå¦ç¿æ¸ã¿ã®åæ£è¡¨ç¾ã¯è¡¨å±¤çãªç¥èãç²å¾ãã¦ããã</h3>
<ul>
<li>ç´¹ä»: å±±å£</li>
<li>è«æ: <a href="https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/P10-6.pdf">https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/P10-6.pdf</a></li>
</ul>
<p>ç¾å¨ã® LLMãä¾ãã° GPT-3.5 ã§ã¯ ãã人é¡å¦è
ãã® 3 æåç®ã¯ä½ã§ããï¼ãã¨ãã£ã表層ã«é¢é¢ãã質åã«æ£ããåçã§ããªãå ´åãå¤ãããã¾ãã
ãã®ç 究ã§ã¯Word2Vecã»BERTã»T5ã»Llama2 ãªã©è¤æ°ã®å¦ç¿æ¸ã¿ã®è¨èªã¢ãã«ã対象ã«ãåæ£è¡¨ç¾ãçæçµæãç¨ãã¦ã¢ãã«ã«è¡¨å±¤çãªæ
å ±ãã©ã®ç¨åº¦å«ã¾ãã¦ãããã調æ»ãã¦ãã¾ãã
æåæ°ãæ§ææåã®äºæ¸¬ã¨ãã£ãã¿ã¹ã¯ãéãã¦ãå¦ç¿æ¸ã¿è¨èªã¢ãã«ã表層ã®æ
å ±ãé¨åçã«ã¯ç²å¾ãã¦ãããã®ã®ãåºåã«è¡¨å±¤ã®ç¥èãåæ ããããã¨ãä¸å¾æã§ãã£ãããåºç¾ä½ç½®ãé åºã®æ
å ±ã¯ååç²å¾ã§ãã¦ããªããã¨ã示ããã¦ãã¾ãã</p>
<p>ç¹ã«èå³æ·±ãã£ãã®ã¯ãæåæ°ãäºæ¸¬ããã¿ã¹ã¯ã«ããã¦åæ£è¡¨ç¾ããäºæ¸¬ããå ´åã¨ããã¹ãçæã§äºæ¸¬ããå ´åã®æ§è½ã®å·®ã§ãã
BERT ã Llama2 ã«ããã¦ãåæ£è¡¨ç¾ãå©ç¨ããå ´åã«ã¯ããç¨åº¦äºæ¸¬ã§ãã¦ãããã®ã®ãããã¹ãçæã§äºæ¸¬ããå ´åã«ã¯ãã®æ§è½ã大ããä½ä¸ããã¨ã®ãã¨ã§ãã
ã¢ãã«ã®å
é¨ã«è¡¨å±¤ã®æ
å ±ãç²å¾ã§ããã¨ãã¦ããåºåã®ã¡ã«ããºã ã«ãã£ã¦ãããåæ ã§ãã¦ããªãå¯è½æ§ãããããã§ãã</p>
<p>æåæ°å¶éã®ããè¦ç´ãªã©ãã¿ã¹ã¯ã«ãã£ã¦ã¯è¡¨å±¤ã®æ
å ±ãéè¦ã«ãªãå ´åãããã¯ãã§ãã
è¨èªã¢ãã«ã®æ¯ãèããè½åãç解ããããã«ã¯ãæå³çãªè©ä¾¡ã¨åããã¦è¡¨å±¤ã®æ
å ±ãæ±ãè½åã«ã¤ãã¦èãããã¨ã大åã ã¨æãã¾ããã</p>
<h3 id="A10-4-å¹³åãã¼ãªã³ã°ã«ããæåãè¾¼ã¿ã®åæ¤è¨-å¹³åã¯ç¹ç¾¤ã®è¦ç´ã¨ãã¦ååã">A10-4: å¹³åãã¼ãªã³ã°ã«ããæåãè¾¼ã¿ã®åæ¤è¨: å¹³åã¯ç¹ç¾¤ã®è¦ç´ã¨ãã¦ååã?</h3>
<ul>
<li>ç´¹ä»: 深澤</li>
<li>è«æ: <a href="https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/A10-4.pdf">https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/A10-4.pdf</a></li>
</ul>
<p>æç« ãªã©ããã¯ãã«åãããã¨ã¯è¿å¹´ã®æ¤ç´¢æ¡å¼µçæ(RAG) ãªã©ãå®è£
ããä¸ã§å¿
è¦ä¸å¯æ¬ ãªæè¡ã¨ãã¦èªèããã¦ãã¦ãã¾ãããã¡ãã®ç 究ã§ã¯æåãè¾¼ã¿ãä½ãéã«æãããç¨ããããå¹³åãã¼ãªã³ã°ããåèªåãè¾¼ã¿ã®ç©ºéçãªåºããã®æ
å ±ãæ½°ãã¦ãã¾ãåé¡ãææãã¦ãã¾ããã¤ã¾ãããæå³ã®ç°ãªãç¹ç¾¤ãªã®ã«å¹³åãè¿ããªããã¨ããåé¡ã§ãã
ããã確ãããããã®å®é¨ã¨ãã¦ãWMD(Word Mover's Distance) ã«ããç¹ç¾¤ã¨ãã¦ã®é¡ä¼¼åº¦ã¨å¹³åãã¼ãªã³ã°å¾ã®L2è·é¢ã人æè©ä¾¡ã«ããé¡ä¼¼åº¦ãç¨æããããããã®é¡ä¼¼åº¦ãæ¯è¼ãã¦ãã¾ããçµæã¨ãã¦ãWMDã«ããé¡ä¼¼åº¦ãä½ãå ´åã«å¹³åãã¼ãªã³ã°å¾ã®L2è·é¢ãé«ãã±ã¼ã¹ãããã¤ãåå¨ãã¦ãããã¨ã確èªã§ããã¨ã®ãã¨ã§ãããã®çµæã¯çµé¨çã«å¹³åãã¼ãªã³ã°ãæå¹ã§ãããã¨ã示ãã¦ãã¾ãããåæã«èæ
®ããªããã°ãªããªãã±ã¼ã¹ãæããã¨ã示ãã¦ãã¾ãã</p>
<p>ç¹ç¾¤ãç¹ç¾¤ã®ã¾ã¾æãããããªã¼ãºããã«ãªã¢ãã«ãç¨æã§ããã°ããã®ã§ãããåºæ¬çã«ç¹ã¨ãã¦å§ç¸®ããã¦ããå¹³åãã¼ãªã³ã°ã®æ¹ãç¾æç¹ã§ã¯ãã¯ãæ±ããããã§ãããã ãå人çã«ãå¹³åãã¼ãªã³ã°ã¨ããæä½ãè¨èã®æå³ãæ£ããæãããã¦ãããã¨ããã¨çåããããã¨å¸¸ã
æãã¦ããããããã¡ãã®ç 究ã«ãããã¯ã¨ã¹ãã§ã³ã¯é常ã«å
±æã§ãã¾ããã
ä»å調æ»ãã STS ãã¼ã¿ã§ã¯å¹³åãã¼ãªã³ã°ã§ã»ã¨ãã©ã®ã±ã¼ã¹ã«å¯¾å¿ã§ãã¦ãã¾ãããããã¡ã¤ã³ãçµã£ãããã¦ã¿ãã¨ç¹æã®ãã¡ã¤ã³ã§ã¯åé¡ãçºçãããããªã©ããããããããªãã¨æã£ã¦ãã¾ããã¨ã¦ãä»å¾ãæ°ã«ãªãèå³æ·±ãç 究ã§ããã</p>
<h3 id="E6-2-æå³å¤åã®çµ±è¨çæ³åã¯1000å¹´æãç«ã¤">E6-2: æå³å¤åã®çµ±è¨çæ³åã¯1000å¹´æãç«ã¤</h3>
<ul>
<li>ç´¹ä»: 深澤</li>
<li>è«æ: <a href="https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/E6-2.pdf">https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/E6-2.pdf</a></li>
</ul>
<p>æå³å¤åã®çµ±è¨çæ³åã¨ã¯ãé »åº¦èªã»ã©æå³å¤åã®åº¦åããå°ããå¤ç¾©èªã»ã©æå³å¤åã®åº¦åãã大ãããªãã¨ãããACL2016 ã«ã¦ Hamilton ããçºè¡¨ãããã®ãæãã¦ãã¾ããå
ã®ç 究ã«ãããæéç¯å²ã¯ 1800å¹´ãã2000å¹´ã§ãããããã¡ãã®ç 究ã§ã¯èæ¸ã対象ã«å«ãããã¨ã§ãHamilton ãçºè¦ããæå³å¤åã®çµ±è¨çæ³åã1000å¹´çµéãã¦ãæãç«ã¤ãã¨ã調ã¹ã¦ãã¾ããã·ã¼ãèªãè¨å®ããèæ¸ãæ§æããã©ãã³èªã¨ã©ãã³èªããã¨ã«ãã¦æ´¾çããããã³ã¹èªã¨ã®éã§æå³å¤åãæãç«ã¤ãã調ã¹ãã¨ãããé »åº¦ãé«ãã»å¤ç¾©æ§ã®ä½ãã©ãã³èªèªæºã»ã©ããã³ã¹èªå½¢ã¨ã®æå³ã®ãããå°ãããªãå¾åãè¦ããã1000å¹´åä½ã§ãã£ã¦ãæå³å¤åã®çµ±è¨çæ³åãæãç«ã¤ãã¨ã示ããã¨ã®ãã¨ã§ããã</p>
<p>å人çã« Hamiltion ã®ç 究ã¯å½æèªãã ã¨ãããã¨ã¦ãå°è±¡ã«æ®ã£ã¦ãã¾ããããã¡ãã®ç 究ã¯èæ¸ã«çç®ãã¦ãã®æéç¯å²ãåºããåæãè¡ããã¨ããã®ãã¦ãã¼ã¯ã ãªã¨æããç´¹ä»ããã¦ããã ãã¾ãããæå³å¤åã®æ³åãé·ãæéãæ¸ãã¦ãå¤åããªãæ®éçãªãã®ã ã¨ããã¨ãä»å¾ãåæ§ã®å¤åãä»æã
ãæ±ã£ã¦ããè¨èã§ãèµ·ãããã¨ãããã¨ã«ãªãã¾ããä¾ãã°ãã«ãã¨ã¼ã¸ã§ã³ã·ãã¥ã¬ã¼ã·ã§ã³ãªã©ã§äººå·¥è¨èªã®ã¢ããªã³ã°ãè¡ãéãªã©ã«ãä»åã®æ³åãåãå
¥ãããã¨ã§ããèªç¶ãªè¨èªçæãå¯è½ã«ãªãããããã¾ãããé常ã«èå³æ·±ãç 究ã§ããã</p>
<h3 id="B7-4-æèæ§é ãå©ç¨ããåãè¾¼ã¿è¡¨ç¾å¦ç¿ã®ææ¡">B7-4: æèæ§é ãå©ç¨ããåãè¾¼ã¿è¡¨ç¾å¦ç¿ã®ææ¡</h3>
<ul>
<li>ç´¹ä»: 深澤</li>
<li>è«æ: <a href="https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/B7-4.pdf">https://www.anlp.jp/proceedings/annual_meeting/2024/pdf_dir/B7-4.pdf</a></li>
</ul>
<p>æç« ãç»åã«å¯¾ããåãè¾¼ã¿è¡¨ç¾ã¯ãæ¤ç´¢ãå§ãã¨ããæ§ã
ãªã¢ããªã±ã¼ã·ã§ã³ã§å©ç¨ããã¦ãã¾ãããã®ç 究ã§ã¯ãæèæ§é ãå©ç¨ãããã¨ã§åãè¾¼ã¿è¡¨ç¾ã®è¡¨ç¾è½åãåä¸ãããããã®æé©è¼¸éãç¨ããæ師ãªãåãè¾¼ã¿å¦ç¿ææ³ãææ¡ãã¦ãã¾ããBERT ã対象ã¨ãã¦èããæãå
è¡ç 究ã§ã¯ CLS ãã¼ã¯ã³ã®ã¿ã«çç®ããå¦ç¿ãè¡ããã¾ããä¸æ¹ã§ææ¡ææ³ã§ã¯æé©è¼¸éãç¨ãã¦ã·ã£ããã«ããæç« ã®åãã¼ã¯ã³ã«ã¤ãã¦è¼¸éã³ã¹ããæå°åããããã«å¦ç¿ããããã¨ã§ãæèæ§é ãèæ
®ããåãè¾¼ã¿è¡¨ç¾ãç²å¾ãããã¨ã«æåãã¦ãã¾ããå¾ãããã¢ãã«ã¯ SimCSE ãªã©ã®å
è¡ç 究ã§ææ¡ãããã¢ãã«ããã STS ã¿ã¹ã¯ãªã©ã«ããã¦é«ãæ§è½ã示ãã¦ããã¨ã®ãã¨ã§ãã</p>
<p>ãã¡ãã®ç 究ãç´¹ä»ããã¦ããã ããçç±ã¯ãå
ã«ç¤ºããå¹³åãã¼ãªã³ã°ã«é¢ãã調æ»ãè¡ã£ãè«æã¨åãã¢ããã¼ã·ã§ã³ã«ãããã®ã§ããã¤ã¾ããå¹³åãã¼ãªã³ã°ã§æ½°ãã»CLSã ãã«çç®ããããããã£ã¨è¯ãæ¹æ³ãããã®ã§ã¯ãªãããã¨ããåãã§ãããã¡ãã®è«æã¯æé©è¼¸éã«ãã£ã¦ç¹ç¾¤å
¨ä½ã§ã®å¦ç¿ãè¡ããã¨ããã¢ããã¼ãã§ãé常ã«ç´å¾ãããããã®ã§ãããçºè¡¨å¾ã®è³ªçå¿çã§ãç¹ç¾¤å
¨ä½ãè¦ãåã©ããã¦ãè¨ç®æéãããã£ã¦ãã¾ããã¨ããåé¡ãããã¨ã®ãã¨ã§ããããä»å¾ã追ãããããç 究ã ãªãã¨æãã¾ããã</p>
<h2 id="ãããã«">ãããã«</h2>
<p>ä»åã®è¨äºã§ã¯ NLP2024 ã®åå ã¬ãã¼ãããå±ããã¾ããã</p>
<p>åé ã«ãæ¸ããéããå»å¹´ã¨æ¯è¼ã㦠NLP2024 ã¯ç¹ã« LLM ã®åå¨æãå¼·ãæãã大ä¼ã ã£ãããã«æãã¾ãã
LLM ã®æ§ç¯ãè©ä¾¡ã®ç 究ã¯ãã¡ããããã®ä»ã®ç 究ã«ããã¦ã LLM ã¨ã®æ¯è¼ã LLM ã®æ´»ç¨ãæèããç 究ãå¤ãè¦ããã¾ããã
ãã£ã¨ãã®æµãã¯ã¾ã ãã°ããç¶ãã®ã§ãããã
æ¿åã®ææãè¿ããä¸ãä»å¤§ä¼ã§å¾ãããç¥è¦ããã¨ã« LLM ãã¯ããã¨ããææ°ã®è¨èªå¦çæè¡ãå®éã®ãµã¼ãã¹ã«ãæ´»ç¨ãã¦ããããã¨æãã¾ãï¼</p>
altescy
AWS å
ã§å¤§è¦æ¨¡è¨èªã¢ãã«ãå©ç¨ã§ãã Amazon Bedrock ã使ã£ã¦ä½ã RAG ã¢ããªã±ã¼ã·ã§ã³
hatenablog://entry/6801883189051716186
2023-10-19T13:48:02+09:00
2023-10-19T13:50:33+09:00 ããã«ã¡ã¯ãæ©æ¢°å¦ç¿ã°ã«ã¼ãã®æ·±æ¾¤(@fukkaa1225)ã§ãã å
æ¥ãAmazon Bedrock ãä¸è¬å©ç¨ã§ãããã(GA)ã«ãªãã¾ãã ãæ¬è¨äºã§ã¯ãã¡ããç¨ã㦠RAG(Retrieval-augmented generation) ã¢ããªã±ã¼ã·ã§ã³ãä½æãã¦ã¿ãæ§åã¨ãä» LLM ã¢ãã«ã¨ã®æ¯è¼çµæã«ã¤ãã¦ãç´¹ä»ãã¾ãã Amazon Bedrock ã¨ã¯ aws.amazon.com å
¬å¼ãµã¤ãããæè¨ãå¼ç¨ãã¾ãã Amazon Bedrock ã¯ãAmazon ã主è¦ãª AI ã¹ã¿ã¼ãã¢ããä¼æ¥ãæä¾ããåºç¤ã¢ãã« (FM) ã API ãéãã¦å©ç¨ã§ããããã«ããå®å
¨ããâ¦
<p>ããã«ã¡ã¯ãæ©æ¢°å¦ç¿ã°ã«ã¼ãã®æ·±æ¾¤(<a href="https://twitter.com/fukkaa1225">@fukkaa1225</a>)ã§ãã</p>
<p>å
æ¥ãAmazon Bedrock ãä¸è¬å©ç¨ã§ãããã(GA)ã«ãªãã¾ãã ãæ¬è¨äºã§ã¯ãã¡ããç¨ã㦠RAG(Retrieval-augmented generation) ã¢ããªã±ã¼ã·ã§ã³ãä½æãã¦ã¿ãæ§åã¨ãä» LLM ã¢ãã«ã¨ã®æ¯è¼çµæã«ã¤ãã¦ãç´¹ä»ãã¾ãã</p>
<h2 id="Amazon-Bedrock-ã¨ã¯">Amazon Bedrock ã¨ã¯</h2>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fbedrock%2F" title="åºç¤ã¢ãã«ã«ããçæç³» AI ã¢ããªã±ã¼ã·ã§ã³ã®æ§ç¯ - Amazon Bedrock - AWS" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://aws.amazon.com/jp/bedrock/">aws.amazon.com</a></cite></p>
<p>å
¬å¼ãµã¤ãããæè¨ãå¼ç¨ãã¾ãã</p>
<blockquote><p>Amazon Bedrock ã¯ãAmazon ã主è¦ãª AI ã¹ã¿ã¼ãã¢ããä¼æ¥ãæä¾ããåºç¤ã¢ãã« (FM) ã API ãéãã¦å©ç¨ã§ããããã«ããå®å
¨ããã¼ã¸ãåãµã¼ãã¹ã§ãããã®ããããã¾ãã¾ãª FM ããé¸æãã¦ãã¦ã¼ã¹ã±ã¼ã¹ã«æãé©ããã¢ãã«ãè¦ã¤ãããã¨ãã§ãã¾ããAmazon Bedrock ã®ãµã¼ãã¼ã¬ã¹ã¨ã¯ã¹ããªã¨ã³ã¹ã«ãããããã« FM ãéå§ããããFM ãç°¡åã«è©¦ããããç¬èªã®ãã¼ã¿ã使ç¨ã㦠FM ããã©ã¤ãã¼ãã«ã«ã¹ã¿ãã¤ãºããããAWS ã®ãã¼ã«ãæ©è½ã使ç¨ã㦠FM ãã¢ããªã±ã¼ã·ã§ã³ã«ã·ã¼ã ã¬ã¹ã«çµ±åãã¦ãããã¤ãããã§ãã¾ããAmazon Bedrock ã®ã¨ã¼ã¸ã§ã³ãã¯ãã«ããã¼ã¸ãåã§ããããããã¼ã¯ç¬èªã®ç¥èæºã«åºã¥ãã¦ææ°ã®åçãæä¾ããå¹
åºãã¦ã¼ã¹ã±ã¼ã¹ã®ã¿ã¹ã¯ãå®äºã§ããçæç³» AI ã¢ããªã±ã¼ã·ã§ã³ãç°¡åã«ä½æã§ãã¾ãã</p></blockquote>
<p>æ®ã©ã®ä¼æ¥ã«ã¨ã£ã¦ãç¾æç¹ã§ LLM ã使ãã¨ãã«ã¯ OpenAI ãæä¾ãã GPT-3.5-turbo, GPT-4 ã使ããã¨ãã»ã¼å¯ä¸ã®é¸æè¢ã«ãªã£ã¦ãããã¨æãã¾ããå¼ç¤¾ã GPT ã·ãªã¼ãºã® API ãæ´»ç¨ãã¦ç¤¾å
ç ChatGPT ãå±éãã¦ãã¾ãã
ä¸æ¹ã§ãOpenAI ãç¨ããä¸ã§ããã¤ãèããªãã¦ã¯ãªããªãåé¡ãããã¾ããä¾ãã°ã権é管çã API Key ã«ãã£ã¦ãªããã¦ããããåãæ±ãã«æ³¨æããå¿
è¦ããã£ãããOpenAI ã¨éä¿¡ããå¿
è¦ããã以ä¸ã»ãã¥ãªãã£è¦ä»¶ãæºãããªãã±ã¼ã¹ããããªã©ã®ç¹ãæãããã¾ãã
ããã«å¯¾ã㦠Amazon Bedrock 㯠AWS ã®ãµã¼ãã¹ã§ãããã IAM ã§ã®æ¨©é管çãå¯è½ã§ããéä¿¡ã AWS å
ã§å®çµãã¦ãããVPC ã¨æ¥ç¶ã§ããã®ãå¬ãããã¤ã³ãã§ããã¢ãã«ã«ã¤ãã¦ããGPT ã·ãªã¼ãºã«å¹æµããååãªæ§è½ãæã£ããã®ãç¨æããã¦ãã¾ãã</p>
<h3 id="Claude-ã¨ã¯">Claude ã¨ã¯</h3>
<p>Amazon Bedrock ã§å©ç¨ã§ããã¢ãã«ã¯ããã¤ãããã¾ãããæ¥æ¬èªã§ã®è³ªåå¿çã«é©ãããã®ã¨ãªãã¨å®è³ªçã«ä½¿ããã¢ãã«ã¯ Claude ã·ãªã¼ãºã§ãã
Claude ã·ãªã¼ãºã¯ Anthropic ãæä¾ãã¦ããã¢ãã«ã§ããChatbot-arenaã»Nejumi JGLUE ã¹ã³ã¢ãªã¼ãã¼ãã¼ã ãªã©ããã¤ãã®ãã³ããã¼ã¯ã§ Claude ã·ãªã¼ãºã®è½å㯠GPT ã·ãªã¼ãºã«å¹æµããã¹ã³ã¢ãåºãã¦ãã¾ãã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwandb.ai%2Fwandb%2FLLM_evaluation_Japan%2Freports%2FLLM-JGLUE---Vmlldzo0NTUzMDE2%3FaccessToken%3Du1ttt89al8oo5p5j12eq3nldxh0378os9qjjh14ha1yg88nvs5irmuao044b6eqa" title="Nejumi LLMãªã¼ãã¼ãã¼ã" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://wandb.ai/wandb/LLM_evaluation_Japan/reports/LLM-JGLUE---Vmlldzo0NTUzMDE2?accessToken=u1ttt89al8oo5p5j12eq3nldxh0378os9qjjh14ha1yg88nvs5irmuao044b6eqa">wandb.ai</a></cite></p>
<p>Amazon Bedrock ã§ã¯ Playground 㧠Claude ã·ãªã¼ãºã¨ã®ãã£ããã試ããã¨ãã§ãã¾ãã試ãã¦ã¿ãã¨ãæ³å以ä¸ã«æµæ¢ãªæ¥æ¬èªã§åã£ã¦ããããã¨ããããã¾ããä¸å³ã®ããã«å¤é£¯ã®ç®ç«ãææ¡ãã¦ããã¾ããã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/fufufukakaka/20231019/20231019132857.png" width="1200" height="627" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Claude ã·ãªã¼ãºã®åªãã¦ããç¹ã¨ãã¦ãå
¥åã§ãããã¼ã¯ã³æ°ã®å¤ãã¨ä¾¡æ ¼ã®å®ããæãããã¾ããClaude ã·ãªã¼ãºã«å
¥åå¯è½ãªãã¼ã¯ã³æ°ã¯è
å¨ã® 10ä¸ãã¼ã¯ã³ã§ããã㯠GPT-4 ã® 8192 ãã¼ã¯ã³ã¨æ¯è¼ããã¨å§åçãªæ°åã§ãã
ã¾ããä¾¡æ ¼ã GPT-4 ã¨æ¯ã¹ãã¨é常ã«å®ãã§ãã2023/10/12 æç¹ã§ <a href="https://aws.amazon.com/jp/bedrock/pricing/">Build Generative AI Applications with Foundation Models - Amazon Bedrock Pricing - AWS</a> ãè¦ã㨠Claude 2 ã®å¤æ®µã¯æ²è¼ããã¦ããªãããã Claude ã®å¤æ®µã§æ¯è¼ãè¡ãã¾ããåä½ã¯ãã«ã§ãã</p>
<table>
<thead>
<tr>
<th> Model </th>
<th> Input </th>
<th> Output </th>
</tr>
</thead>
<tbody>
<tr>
<td> Claude(100k) </td>
<td> 0.011 </td>
<td> 0.032 </td>
</tr>
<tr>
<td> GPT-4(8k) </td>
<td> 0.03 </td>
<td> 0.06 </td>
</tr>
<tr>
<td> GPT-4(32k) </td>
<td> 0.06 </td>
<td> 0.12 </td>
</tr>
<tr>
<td> GPT-3.5-turbo(4k) </td>
<td> 0.0015 </td>
<td> 0.002 </td>
</tr>
<tr>
<td> GPT-3.5-turbo(16k) </td>
<td> 0.003 </td>
<td> 0.004 </td>
</tr>
</tbody>
</table>
<p>Claude 㨠GPT-4 ã®ã¿ã®æ¯è¼ã§è¨ãã°ãClaude 㯠1/3 以ä¸ã®å¤æ®µã¨ãªã£ã¦ãã¾ããããã§ãã¦ãã³ããã¼ã¯ä¸ã§ã¯ããªãè¯ãåè² ããã¦ããã®ã§ãé常ã«åªããé¸æè¢ã§ããã¨è¨ããã§ããããGPT-3.5-turbo ã¨æ¯è¼ãã¦ãã¾ã㨠GPT-3.5-turbo ã®å®ããéç«ã¡ã¾ãããã¼ã¯ã³æ°ãå¿çã®è³ªã«é¢ãã¦æºè¶³ã§ãããªãã°ãã¯ã GPT-3.5-turbo ã¯æåãªé¸æè¢ã§ã¯ããã¾ãã
ä¸æ¹ã§ 10ä¸ãã¼ã¯ã³ãå®ç¾ããªãã AWS å
ã§éä¿¡ãå®çµã§ããååãªæ§è½ãæã£ã LLM ãç¨ãããã¨ãã§ãã Bedrock ã¯ããã ãã§ååãªã¡ãªãããæã£ã¦ããã¨æãã¾ãã</p>
<h2 id="社å
ææ¸ã«å¯¾ãã-RAG-ã¢ããªã±ã¼ã·ã§ã³ãä½æãã">社å
ææ¸ã«å¯¾ãã RAG ã¢ããªã±ã¼ã·ã§ã³ãä½æãã</h2>
<p>Cookpad ã«ã¯ Groupad ã¨å¼ã°ãã社å
wiki ã®ãããªãã®ãããã¾ããããªãé·å¹´éç¨ããã¦ãããæ§ã
ãªç¥è¦ãèç©ããã¦ãã¦æ¥ã
ã®ä»äºãå©ãã¦ããã¦ãã¾ãã
ä¸æ¹ã§åé¡ããããGroupad ã«å¯¾ããæ¤ç´¢ã·ã¹ãã ã¯ãã¾ããã¥ã¼ãã³ã°ãè¡ã£ã¦ããªããããå義èªè§£æ±ºãªã©ãããããã»ããçµæãå¾ãã®ã«è¦å´ãããã¨ãããã¾ããã
ããã§ãã¦ã¼ã¶ã¼ããã®ã¯ã¨ãªã«åºã¥ãã¦å¤é¨ãã¼ã¿ããé¢é£ããããã¥ã¡ã³ããæ¤ç´¢ããæ¤ç´¢çµæã prompt ã«åãè¾¼ã㧠LLM ã«çµæãçæããã¦è¡¨ç¤ºããã¢ããªã±ã¼ã·ã§ã³ (Retrieval-augmented generationãRAG) ãä½æãããã¨ã«ãã¾ããã LLM ãç¨ã㦠semantics ãèæ
®ãããã¯ãã«æ¤ç´¢ã¨è³ªåå¿çãå®è£
ãããã¨ã§ããã¥ã¼ãã³ã°ã®æéãªãä»ãããå¹
åºãæ¤ç´¢çµæãå¾ãããã ããã¨èãã¾ããã</p>
<p>以ä¸ãå®éã«ä½ã£ã¦ã¿ãæ§åããç´¹ä»ãã¾ãã
RAG ã¢ããªã±ã¼ã·ã§ã³ãä½ãããã«å¿
è¦ãªã³ã³ãã¼ãã³ãã¯ããã¤ãåå¨ãã¾ãã代表çãªãã®ã¨ãã¦ã¯ä»¥ä¸ã®ãããªãã®ãã¨æãã¾ãã</p>
<ul>
<li>Vector DB ... RAG ã®ã½ã¼ã¹ã¨ãªãæ
å ±(ããã§ã¯ Groupad ã®ææ¸ç¾¤)ããã¯ãã«ã¨ãã¦ä¿æãã¦ããããã® DB
<ul>
<li>Embedding Function ... ãã¯ãã« DB ã«ææ¸ã追å ããéã«ææ¸ããã¯ãã«ã«å¤æãã</li>
<li>Retriever ... ãã¯ãã« DB ããæ¤ç´¢ã¯ã¨ãªã«ãããããææ¸ãåå¾ãã</li>
</ul>
</li>
<li>LLM ... ã¯ã¨ãªããå¾ãããé¢é£ææ¸ãå
ã«ãLLM ã«ãã対話å¿çãè¡ã</li>
</ul>
<p>ãã®å
ãLLM 㯠Amazon Bedrock ã§ä½¿ããã¨ãã§ãã Claude 2 ãç¨ãã¾ãã Vector DB 㯠Chroma DB ãç¨ãããã¨ã«ãã¾ãã
ç°¡åãªæ§æå³ã¨ãã¦ã¯ä»¥ä¸ã®ãããªãã®ã«ãªãã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/fufufukakaka/20231019/20231019132901.png" width="1200" height="658" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ãªããBedrock ã«ã¯ç¥èãã¼ã¹ã¨æ¥ç¶ãã¦è³ªåå¿çãè¡ãã¢ããªã±ã¼ã·ã§ã³ãä½ãããã®æ©è½ãåå¨ãã¾ãã<a href="https://aws.amazon.com/jp/bedrock/knowledge-bases/">検索拡張生成 (RAG) - Amazon Bedrock のナレッジベース - AWS</a>
ãã®å ´å Embedding ã«ã¯ Bedrock ãæä¾ãã Amazon Titan ã使ããã¨ã«ãªãã¾ããã§ãããAmazon Titan ã¯ç¾æç¹ã§ã¯è±èªã«ã®ã¿å¯¾å¿ããã¢ãã«ã§ãæ¥æ¬èªã®ããã¹ãã«å¯¾ããåãè¾¼ã¿è¡¨ç¾ãå¾ãã®ã«é©ããã¢ãã«ã¨ãªã£ã¦ããªããããä»åã¯èªåãã¡ã§ huggingface hub ããé©ããã¢ãã«ããã¦ã³ãã¼ããã¦ä½¿ããã¨ã¨ãã¾ãã</p>
<h3 id="Vector-DB">Vector DB</h3>
<p>embedding function ã«ã¯ <code>oshizo/sbert-jsnli-luke-japanese-base-lite</code> ãå©ç¨ãã¾ããããããç¨ã㦠Groupad ã®ææ¸ç¾¤ããã¯ãã«åãã Chroma DB ã«ä¿åãã¾ãã
ãã¯ãã«æ¤ç´¢ãããã ãã®ã¯ã©ã¤ã¢ã³ããç¨æãããªãä¾ãã°ä»¥ä¸ã®ãããªãã®ãèãããããã¨æãã¾ãã</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">VectorSearcher</span>:
<span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, db_path: <span class="synIdentifier">str</span>) -> <span class="synIdentifier">None</span>:
embedding_function = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=<span class="synConstant">"oshizo/sbert-jsnli-luke-japanese-base-lite"</span>)
client = chromadb.PersistentClient(path=db_path)
self.collection = client.create_collection(<span class="synConstant">"groupad"</span>, embedding_function=embedding_function)
<span class="synStatement">def</span> <span class="synIdentifier">set_groupad_data</span>(self, data_path: <span class="synIdentifier">str</span>) -> <span class="synIdentifier">None</span>:
logger.info(<span class="synConstant">"set groupad data"</span>)
<span class="synComment"># columns: id, title, content, created_at</span>
df = pd.read_csv(data_path)
self.collection.add(
ids=df[<span class="synConstant">"id"</span>].apply(<span class="synIdentifier">str</span>).values.tolist(),
documents=df[<span class="synConstant">"content"</span>].values.tolist(),
metadatas=[{<span class="synConstant">"title"</span>: title, <span class="synConstant">"created_at"</span>: created_at} <span class="synStatement">for</span> title, created_at <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(df[<span class="synConstant">"title"</span>].values, df[<span class="synConstant">"created_at"</span>].values)],
)
logger.info(<span class="synConstant">"set done"</span>)
<span class="synStatement">def</span> <span class="synIdentifier">search</span>(self, query: <span class="synIdentifier">str</span>, top_k: <span class="synIdentifier">int</span> = <span class="synConstant">10</span>) -> <span class="synIdentifier">list</span>[<span class="synIdentifier">tuple</span>[<span class="synIdentifier">str</span>, <span class="synIdentifier">str</span>]]:
res = self.collection.query(query_texts=[query], n_results=top_k)
<span class="synStatement">return</span> res
</pre>
<p>ä»å㯠RAG ã LangChain ãç¨ãã¦å®è£
ãããããå®éã«ã¯ãã®ãã¯ãã«æ¤ç´¢ã¯ã©ã¹ã¯ç¨ãã¾ããã</p>
<h3 id="LLM-Claude-2">LLM: Claude 2</h3>
<p>ã§ã¯ç¶ãã¦ãä»åã®ç®çã§ãã Claude 2 ã Amazon Bedrock ããå©ç¨ããè¨å®ããã¾ãã</p>
<p>Bedrock ã® client ã¯ã·ã³ãã«ã« <code>import boto3; client = boto3.client('bedrock')</code> ã§ä½¿ããããã«ãªã£ã¦ãã¾ãã<a href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock.html">Bedrock - Boto3 1.28.66 documentation</a></p>
<p>LangChain ã« Bedrock ã使ãããªãã·ã§ã³ãåå¨ããã®ã§ãä»åã¯Vector DB ã¨æ¥ç¶ããé¨åãããã«é ¼ããã¨ã«ãã¾ãã
以ä¸ã®ããã«ç°¡åã«æ¸ãã¾ãã®ã§ããã¨ã¯ãã¤ãã® OpenAI ãªã©ã使ãã¨ãã¨åãããã«ä½¿ããã¯ãã§ãã</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> langchain.llms <span class="synPreProc">import</span> Bedrock
llm = Bedrock(
credentials_profile_name=<span class="synConstant">"bedrock"</span>,
model_id=<span class="synConstant">"anthropic.claude-v2"</span>,
model_kwargs={
<span class="synConstant">"max_tokens_to_sample"</span>: <span class="synConstant">1000</span>
}
)
</pre>
<h3 id="RAG-å
¨ä½å">RAG å
¨ä½å</h3>
<p>ChromaDB ã®è¨å®ãªã©ããã¹ã¦ LangChain ä¸ã§è¡ãããã«ãã¦ã以ä¸ã®ããã«ããã° RAG ãå®è£
ã§ãã¾ãã</p>
<p>ãªã ChromaDB ã®è¨å®ä¸ãsqlite ã®ãã¼ã¸ã§ã³ã 3.35.5 以ä¸ã§ãªãã¨ãããªããããäºåã« sqlite ã®è¨å®ãå¿
è¦ãªå ´åãããã¾ãã
(ãã«ãã㦠<code>export LD_LIBRARY_PATH=sqlite-3.42.0/.libs</code> ãæå®ãããªã©)</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> langchain.chains <span class="synPreProc">import</span> RetrievalQA
<span class="synPreProc">from</span> langchain.document_loaders.csv_loader <span class="synPreProc">import</span> CSVLoader
<span class="synPreProc">from</span> langchain.embeddings <span class="synPreProc">import</span> HuggingFaceEmbeddings
<span class="synPreProc">from</span> langchain.llms <span class="synPreProc">import</span> Bedrock
<span class="synPreProc">from</span> langchain.vectorstores <span class="synPreProc">import</span> Chroma
<span class="synStatement">def</span> <span class="synIdentifier">build_qa_chain</span>(file_path: <span class="synIdentifier">str</span>) -> RetrievalQA:
loader = CSVLoader(file_path=file_path, source_column=<span class="synConstant">"content"</span>)
data = loader.load_and_split()
embeddings = HuggingFaceEmbeddings(
model_name=<span class="synConstant">"oshizo/sbert-jsnli-luke-japanese-base-lite"</span>
)
vectorstore = Chroma.from_documents(documents=data, embedding=embeddings)
llm = Bedrock(
credentials_profile_name=<span class="synConstant">"bedrock"</span>,
model_id=<span class="synConstant">"anthropic.claude-v2"</span>,
model_kwargs={<span class="synConstant">"max_tokens_to_sample"</span>: <span class="synConstant">1000</span>},
)
qa_chain = RetrievalQA.from_chain_type(llm, retriever=vectorstore.as_retriever())
<span class="synStatement">return</span> qa_chain
</pre>
<p>å®éã«ä½¿ãã¨ãã¯ä»¥ä¸ã®ããã«ãã¦å©ç¨ãããã¨ãã§ãã¾ãã</p>
<pre class="code lang-python" data-lang="python" data-unlink>qa_chain({<span class="synConstant">"query"</span>: <span class="synConstant">"ã»ãã»ã"</span>})
</pre>
<h2 id="å®éã®åºåãè¦ã¦ã¿ã">å®éã®åºåãè¦ã¦ã¿ã</h2>
<p>ãã¦ãããã¾ã§ RAG ã®å®è£
æ¹éã«ã¤ãã¦ç¤ºãã¦ãã¾ããã社å
ããã¥ã¡ã³ãã対象ã¨ããå®éã®ä¾ããç´¹ä»ãããã¨ã¯ã§ãã¾ããããä»åã¯ä»£æ¿ãã¼ã¿ã¨ãã¦æ·±æ¾¤ã Techlife ã§å
¬éããè¨äºãã½ã¼ã¹ã¨ãã¦ãRAG ã®æåã確ããããã¨ã¨ãã¾ãã</p>
<p>ä»åã®ã³ã¼ãã§ã¯ content ã«ã©ã ã ããåãè¾¼ãããã«ãã¦ããã®ã§ã以ä¸ã®è¨äºã® markdown ãæã£ã¦ã㦠idx,content ã«ã©ã ã®å½¢å¼ã§ csv <code>"data/fukasawa_techlife.csv"</code> ã«ä¿åãã¾ãã</p>
<ul>
<li><a href="https://techlife.cookpad.com/entry/nlp2023-attendance">NLP2023 に参加しました:聴講編 - クックパッド開発者ブログ</a></li>
<li><a href="https://techlife.cookpad.com/entry/cookpad-mart-item-recommendation">クックパッドマートにおける item-to-item レコメンデーションの変遷 - クックパッド開発者ブログ</a></li>
<li><a href="https://techlife.cookpad.com/entry/2021/11/04/090000">RecBole を用いてクックパッドマートのデータに対する50以上のレコメンドモデルの実験をしてみた - クックパッド開発者ブログ</a></li>
<li><a href="https://techlife.cookpad.com/entry/2021/09/06/130000">Cookpad Summer Internship 2021 10 Day Techコースを開催しました! - クックパッド開発者ブログ</a></li>
<li><a href="https://techlife.cookpad.com/entry/2020/05/15/120000">系列ラベリングによる NPS コメントのポジティブ・ネガティブ部分の抽出 - クックパッド開発者ブログ</a></li>
</ul>
<p><code>qa_chain = build_qa_chain("data/fukasawa_techlife.csv")</code> ã¨ã㦠qa_chain ãåãåºãã°è³ªåå¿çãè¡ããã¤ãã©ã¤ã³ãç¨æã§ãã¾ãã
åºåãè¦ã¦ã¿ã¾ãããã</p>
<pre class="code lang-python" data-lang="python" data-unlink>In [<span class="synConstant">4</span>]: <span class="synIdentifier">print</span>(qa_chain({<span class="synConstant">"query"</span>: <span class="synConstant">"深澤ãããåãçµãã§ããã¬ã³ã¡ã³ãã«ã¤ãã¦æãã¦ãã ãã"</span>}))
{<span class="synConstant">'query'</span>: <span class="synConstant">'深澤ãããåãçµãã§ããã¬ã³ã¡ã³ãã«ã¤ãã¦æãã¦ãã ãã'</span>,
<span class="synConstant">'result'</span>: <span class="synConstant">'''</span>
<span class="synConstant">ã§ã¯ã深澤ãããåãçµãã§ããã¯ãã¯ããããã¼ãã®ã¬ã³ã¡ã³ãã«ã¤ãã¦è¦ç¹ãã¾ã¨ãã¾ãã</span>
<span class="synConstant">- æåã¯Item2Vecã§ã¬ã³ã¡ã³ãã¼ã·ã§ã³ãå®è£
ãã¦ãã¾ãããããªãã©ã¤ã³ã®è©ä¾¡ææ¨ãæããããªãã£ãããä»ã®ææ³ãæ¢ãã¦ãã¾ãã</span>
<span class="synConstant">- ããã§RecBoleãå©ç¨ãã¦30ã¢ãã«ä»¥ä¸ã®å®é¨ãè¡ã£ãçµæãRecVAEãè¯å¥½ãªææ¨ã示ãã¾ãã</span>
<span class="synConstant">- RecVAEã¨Item2Vecããªã³ã©ã¤ã³ã§æ¯è¼ãããããã¤ã³ã¿ã¼ãªã¼ãã³ã°ã«ãããã¹ããå®æ½ãã¾ãã</span>
<span class="synConstant">- ãã®çµæãRecVAEã®æ¹ãææ¨çã«è¯ããItem2VecããRecVAEã¸ã®ç§»è¡ã決å®ãã¾ãã</span>
<span class="synConstant">深澤ããã¯Item2Vecããå§ã¾ããRecBoleãå©ç¨ããå®é¨ãéããæçµçã«RecVAEã¸ç§»è¡ãããã¨ãåããã¾ããã¬ã³ã¡ã³ãã¼ã·ã§ã³ç²¾åº¦ã®æ¹åã«åããæç¶çãªåãçµã¿ã伺ãã¾ãã</span>
<span class="synConstant">'''</span>}
</pre>
<p>質åå¿çã¨ãã¦ã¯ããªãå®ç§ã§ããã<a href="https://techlife.cookpad.com/entry/cookpad-mart-item-recommendation">クックパッドマートにおける item-to-item レコメンデーションの変遷 - クックパッド開発者ブログ</a> ã§ã®å
容ã§ãã</p>
<pre class="code lang-python" data-lang="python" data-unlink>In [<span class="synConstant">5</span>]: <span class="synIdentifier">print</span>(qa_chain({<span class="synConstant">"query"</span>: <span class="synConstant">"深澤ãããåãçµãã§ããä»äºã«ã¤ãã¦æãã¦ãã ãã"</span>}))
{<span class="synConstant">'query'</span>: <span class="synConstant">'深澤ãããåãçµãã§ããä»äºã«ã¤ãã¦æãã¦ãã ãã'</span>,
<span class="synConstant">'result'</span>: <span class="synConstant">'''</span>
<span class="synConstant">ã¯ãã深澤ããã®ä»äºã«ã¤ãã¦ã¾ã¨ãã¦ã¿ã¾ããã</span>
<span class="synConstant">- ã¯ãã¯ãããã«å
¥ç¤¾å¾ã主ã«èªç¶è¨èªå¦çãæ´»ç¨ããããã¸ã§ã¯ããæ
å½ããã¦ãã¾ãã</span>
<span class="synConstant">- NPSã¢ã³ã±ã¼ãã®èªååé¡ã·ã¹ãã ãæ§ç¯ããããæ¬è¨äºã§ç´¹ä»ããã¦ããããã«ãNPSã³ã¡ã³ãã®ãã¸ãã£ã/ãã¬ãã£ãé¨åã®æ½åºã·ã¹ãã ã®éçºã«åãçµãã§ãã¾ãã</span>
<span class="synConstant">- æçæé ã®èªåçæããã¸ã§ã¯ãã«ãé¢ãã£ã¦ãã¦ãæé éã®ç
§å¿è§£æãæé ããææãèªåæ½åºããç 究ãªã©ãè¡ã£ã¦ãã¾ãã</span>
<span class="synConstant">- Google BigQueryãæ´»ç¨ãã大è¦æ¨¡ãã¼ã¿åæãè¡ã£ã¦ãããã¬ã·ãé²è¦§ãã°ã®åæçµæãåºã«ã¬ã·ãæ¤ç´¢ã®æ¹åãå³ããªã©ãã¦ãã¾ãã</span>
<span class="synConstant">- æ¨è¦ã·ã¹ãã ã®ç 究éçºã«ãæºãã£ã¦ãããMatrix Factorizationã popularity-based ã¢ãã«ãªã©ãç¨ããã¬ã³ã¡ã³ãã¼ã·ã§ã³ã¨ã³ã¸ã³ã®éçºããã¦ãã¾ãã</span>
<span class="synConstant">- åæ¥è«æã§ã¯ã½ã¼ã·ã£ã«ã¡ãã£ã¢ããã¹ã(Twitter)ã®ã»ã³ãã¡ã³ãåæãç 究ãã¦ãã¾ããã</span>
<span class="synConstant">- è«æå·çãå¦ä¼çºè¡¨ãç©æ¥µçã«è¡ã£ã¦ãããèªç¶è¨èªå¦çåéãä¸å¿ã«ç 究éçºãé²ãã¦ãã¾ãã</span>
<span class="synConstant">以ä¸ã®ããã«ãã¯ãã¯ãããã«ããã深澤ããã®ä»äºã¯ãèªç¶è¨èªå¦çã¨ãã¼ã¿åæãçµã¿åãããç 究éçºãä¸å¿ã ã¨è¨ãããã§ãã</span>
<span class="synConstant">'''</span>}
</pre>
<p><code>æçæé ã®èªåçæããã¸ã§ã¯ã</code> ã <code>ã½ã¼ã·ã£ã«ã¡ãã£ã¢ããã¹ã(Twitter)ã®ã»ã³ãã¡ã³ãåæ</code> ã¯ãã¦ããªãã®ã§ hallucination ãè¦ããã¾ããã大ä½ã®é°å²æ°ã¯ãã£ã¦ãã¾ããã¡ãã£ã¨ç¯å²ã®åºãèãæ¹ãããã®ãè¯ããªãã£ãã®ããããã¾ããã</p>
<pre class="code lang-python" data-lang="python" data-unlink>In [<span class="synConstant">6</span>]: <span class="synIdentifier">print</span>(qa_chain({<span class="synConstant">"query"</span>: <span class="synConstant">"深澤ããã¯ã¤ã³ã¿ã¼ã³ã·ããã«ããããã£ã¦ãããã¨ãããã®ã§ããï¼"</span>}))
{<span class="synConstant">'query'</span>: <span class="synConstant">'深澤ããã¯ã¤ã³ã¿ã¼ã³ã·ããã«ããããã£ã¦ãããã¨ãããã®ã§ããï¼'</span>,
<span class="synConstant">'result'</span>: <span class="synConstant">'''</span>
<span class="synConstant">ã¯ãã深澤ããã¯ã¯ãã¯ãããã§ã¤ã³ã¿ã¼ã³ã·ããã«ãããã£ã¦ãã¾ããã</span>
<span class="synConstant">深澤ããã¯ã¯ãã¯ãããã®ç 究éçºé¨ã«æå±ãã¦ãã¦ãéå»ã«ãµãã¼ã¤ã³ã¿ã¼ã³ã·ããã®ã¡ã³ã¿ã¼ãåããçµé¨ãããããã§ãã</span>
<span class="synConstant">ä¾ãã°ããã®ããã°è¨äºã§ã¯2020å¹´ã®ãµãã¼ã¤ã³ã¿ã¼ã³ã·ããã§æ·±æ¾¤ãããã¡ã³ã¿ã¼ãåããæ§åãæ¸ããã¦ãã¾ãã</span>
<span class="synConstant">https://techlife.cookpad.com/entry/2020/09/14/140000</span>
<span class="synConstant">ãã®è¨äºã«ããã¨ã深澤ããã¯æ©æ¢°å¦ç¿ã³ã¼ã¹ã®ã¡ã³ã¿ã¼ã¨ãã¦ãã¤ã³ã¿ã¼ã³å¦çã®æè¡æå°ãè¡ã£ãããã§ããã¾ããã¤ã³ã¿ã¼ã³ã·ããã®ããã°ã©ã ä½æã«ãé¢ãã£ãã¨ã®ãã¨ã§ããä»ã«ãã深澤ããã¯éå»ã®ã¤ã³ã¿ã¼ã³ã·ããã§æ©æ¢°å¦ç¿ã®è¬ç¾©ãè¡ã£ãããã¤ã³ã¿ã¼ã³å¦çã®ç 究éçºã«ã¢ããã¤ã¹ãããããããªã©ãã¤ã³ã¿ã¼ã³ã·ããã«ç©æ¥µçã«é¢ãã£ã¦ããããã§ãããã®ããã深澤ããã¯ã¤ã³ã¿ã¼ã³ã·ããã«ãããã£ãçµé¨ãè±å¯ã§ãã¤ã³ã¿ã¼ã³å¦çã®è²æã«åãå
¥ãã¦ããã¨ã³ã¸ãã¢ã®ããã§ãã</span>
<span class="synConstant">'''</span>}
</pre>
<p>åçãã¦ãã URL ã <code>æ©æ¢°å¦ç¿ã³ã¼ã¹ã®ã¡ã³ã¿ã¼ã¨ãã¦ãã¤ã³ã¿ã¼ã³å¦çã®æè¡æå°</code> ã¨ããããããã¾ã hallucination ãèµ·ãã¦ãã¾ããä¸æ¹ã§äºå®ãåå¨ãã¦ããã<code>ã¤ã³ã¿ã¼ã³ã·ããã®ããã°ã©ã ä½æã«ãé¢ãã£ã</code> ã¨ããé¨åãªã©ã¯äºå®ã§ãã</p>
<h3 id="GPT-4-ã¨ã®åçæ¯è¼">GPT-4 ã¨ã®åçæ¯è¼</h3>
<p>claude-v2 ã«ã¯ hallucination ãè¦ããã¾ããããGPT-4 ã§ã¯ã©ãã§ãããããåºåãæ¯è¼ããããã«ã³ã¼ãã«ä»¥ä¸ã®å¤æ´ãå ãã¾ã:</p>
<ul>
<li><code>llm</code> ã®é¨åã <code>langchain.chat_models.ChatOpenAI</code> ãç¨ããããã«ãã</li>
<li>GPT-4 ã® Max Token Size 8192 ã«åãã㦠<code>langchain.text_splitter.RecursiveCharacterTextSplitter</code> ã max_token ãã©ã¡ã¼ã¿ã®èª¿æ´ãè¡ã</li>
</ul>
<p>æçµçã«ã¯ä»¥ä¸ã®ãããªã³ã¼ãã§å®è¡ãã¾ããã</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">build_qa_chain2</span>(file_path: <span class="synIdentifier">str</span>) -> RetrievalQA:
loader = CSVLoader(file_path=file_path, source_column=<span class="synConstant">"content"</span>)
data = loader.load_and_split(RecursiveCharacterTextSplitter(chunk_size=<span class="synConstant">2048</span>, chunk_overlap=<span class="synConstant">0</span>))
embeddings = HuggingFaceEmbeddings(
model_name=<span class="synConstant">"oshizo/sbert-jsnli-luke-japanese-base-lite"</span>
)
vectorstore = Chroma.from_documents(documents=data, embedding=embeddings)
llm = ChatOpenAI(
model_name=<span class="synConstant">"gpt-4"</span>,
temperature=<span class="synConstant">0</span>,
max_tokens=<span class="synConstant">4096</span>
)
qa_chain = RetrievalQA.from_chain_type(
llm,
retriever=vectorstore.as_retriever(),
chain_type=<span class="synConstant">"stuff"</span>
)
<span class="synStatement">return</span> qa_chain
</pre>
<p>Claude 2 ã«å¯¾ãã¦è¡ã£ãã®ã¨åã質åãæãã¦ã¿ãçµæã以ä¸ã®ãããªçµæãå¾ããã¾ããã</p>
<pre class="code lang-python" data-lang="python" data-unlink>In [<span class="synConstant">71</span>]: <span class="synIdentifier">print</span>(qa_chain({<span class="synConstant">"query"</span>: <span class="synConstant">"深澤ãããåãçµãã§ããã¬ã³ã¡ã³ãã«ã¤ãã¦æãã¦ãã ãã"</span>}))
{<span class="synConstant">'query'</span>: <span class="synConstant">'深澤ãããåãçµãã§ããã¬ã³ã¡ã³ãã«ã¤ãã¦æãã¦ãã ãã'</span>, <span class="synConstant">'result'</span>: <span class="synConstant">'深澤ããã¯ãã¬ã³ã¡ã³ãç³»ã®ããã¸ã§ã¯ãRecBoleã«åãçµãã§ãã¾ããRecBoleã¯åç¾æ§ã«åãçµãããã¸ã§ã¯ãã§ã50以ä¸ã®ã¬ã³ã¡ã³ãã¢ãã«ãä¸ã¤ã®ã³ãã³ãã§è©¦ããã¨ãã§ãã¾ãã深澤ããã¯ãã¯ãã¯ããããå±éãã¦ããäºæ¥ã®ä¸ã¤ã§ããã¯ãã¯ããããã¼ãã®ãã¼ã¿ã使ã£ã¦ããããã®ã¬ã³ã¡ã³ãã¢ãã«ã試ãå®é¨ãè¡ãã¾ããããã®çµæããã¹ããã¼ã¿ã®6000ã¦ã¼ã¶ã«å¯¾ãã¦2000ã¦ã¼ã¶(ä¸åã®ä¸)ã«æ£ããæ¨è¦ãè¡ããã¨ãã§ããã¢ãã«ãçºè¦ã§ãã¾ããã'</span>}
In [<span class="synConstant">72</span>]: <span class="synIdentifier">print</span>(qa_chain({<span class="synConstant">"query"</span>: <span class="synConstant">"深澤ãããåãçµãã§ããä»äºã«ã¤ãã¦æãã¦ãã ãã"</span>}))
{<span class="synConstant">'query'</span>: <span class="synConstant">'深澤ãããåãçµãã§ããä»äºã«ã¤ãã¦æãã¦ãã ãã'</span>, <span class="synConstant">'result'</span>: <span class="synConstant">'深澤ããã¯ã¯ãã¯ãããã®ç 究éçºé¨ã§åãã¦ãããã¬ã³ã¡ã³ãç³»ã®ããã¸ã§ã¯ãRecBoleã«æ³¨ç®ãã¦ãã¾ããRecBoleãç¨ãã¦ãã¯ãã¯ããããå±éãã¦ããäºæ¥ã®ä¸ã¤ã§ããã¯ãã¯ããããã¼ãã®ãã¼ã¿ã使ã£ã¦ã50以ä¸ã®ã¬ã³ã¡ã³ãã¢ãã«ã®å®é¨ãè¡ãã¾ããããã®çµæããã¹ããã¼ã¿ã®6000ã¦ã¼ã¶ã«å¯¾ãã¦2000ã¦ã¼ã¶(ä¸åã®ä¸)ã«æ£ããæ¨è¦ãè¡ããã¨ãã§ããã¢ãã«ãçºè¦ã§ããã¨ã®ãã¨ã§ãã'</span>}
In [<span class="synConstant">73</span>]: <span class="synIdentifier">print</span>(qa_chain({<span class="synConstant">"query"</span>: <span class="synConstant">"深澤ããã¯ã¤ã³ã¿ã¼ã³ã·ããã«ããããã£ã¦ãããã¨ãããã®ã§ããï¼"</span>}))
{<span class="synConstant">'query'</span>: <span class="synConstant">'深澤ããã¯ã¤ã³ã¿ã¼ã³ã·ããã«ããããã£ã¦ãããã¨ãããã®ã§ããï¼'</span>, <span class="synConstant">'result'</span>: <span class="synConstant">'æèããã¯æ·±æ¾¤ãããã¤ã³ã¿ã¼ã³ã·ããã«é¢ä¸ãã¦ãããã©ããã¯æããã«ãªãã¾ããã'</span>}
</pre>
<p>GPT-4 ã¯ã¬ã³ã¡ã³ãã¨ä»äºã«é¢ãã質åã«ã¤ãã¦ã¯ Claude 2 ã¨åããããªè¿çããã¦ãã¾ããä¸æ¹ãã¤ã³ã¿ã¼ã³ã·ããã®è©±é¡ã«é¢ãã¦ã¯ãã¾ãè¦ã¤ããããªãã£ãã®ãçããããªããã¨ããè¿çã«ãªãã¾ãããhallucination ãæãããããªå¦ç¿ã®çµæã¨ãã¦ããã®ããã«å¤ãã§ã¯ãªãç¡é£ãªåçã«çµå§ãã¦ããã®ããããã¾ããã</p>
<p>Claude 2 㯠context window ã大ããåé·ãç³»åãå¦çã§ãã¾ãããé·è·é¢ä¾åããã¾ãæ±ãã hallucination ãèµ·ãã£ãå¯è½æ§ãèãããã¾ããããã ã¨ããã° max_token ãã©ã¡ã¼ã¿ã調æ´ãããã¨ã§ hallucination ãæå¶ã§ããããããã¾ããã</p>
<p>LLM ã®ããããå¾åã®éãã«ã¤ãã¦ã¯ã¾ã ãã¾ã調æ»ã§ãã¦ããªããããä»å¾æ¯è¼æ¤è¨ã§ããã°ã¨æã£ã¦ãã¾ãã</p>
<h2 id="ã¾ã¨ã">ã¾ã¨ã</h2>
<p>Amazon Bedrock ãæä¾ãã¦ãã Claude 2 㨠LangChain ãçµã¿åãã㦠RAG ã¢ããªã±ã¼ã·ã§ã³ãä½æãã¦ã¿ã¾ããã
Claude 2 㯠OpenAI ã®ã¢ãã«ã¨æ¯ã¹ã¦ãæ§è½ãã³ã¹ãã®ç¹ã§é
åã§ãããããã AWS ã®ãµã¼ãã¹ã¨ãã¦ç¨ãããã¨ãã§ããã®ã¯é常ã«ä¾¿å©ã ãªã¨æãã¾ããã
Amazon Bedrock ã®å°æ¥ã«ãã£ã¦ç¤¾å
ããã¥ã¡ã³ã ã«å¯¾ãã RAG ã¢ããªã±ã¼ã·ã§ã³ãä½ããã¼ãã«ã¯ããªãä¸ãã£ãã¨æãã¦ãã¾ããç©æ¥µçã«ç¤¾å
ã§è©¦ãã¦ãããããã¯ã¦ã¼ã¶ã®æ¹ã«ã使ã£ã¦ããã ãããã㪠LLM ã¢ããªã±ã¼ã·ã§ã³ãä½ãéã®ç¥è¦ãããã¦ããããã§ãã</p>
<p>ãã®è¨äºãèªãã§ããã ããããã¨ããããã¾ããã
ã¯ãã¯ãããæ©æ¢°å¦ç¿ã°ã«ã¼ãã§ã¯å¼ãç¶ãæå
端ã®æ©æ¢°å¦ç¿ã®æè¡ããããã¯ãã§æ´»ããã¹ãã試è¡é¯èª¤ã¨éçºãé²ãã¦ããã¾ãã</p>
fufufukakaka
Hatamoto ãã¢ãã¤ã«ã¢ããªã«é¢ããæ
å ±ãä¸å
管çããããã®Webã¢ããªã±ã¼ã·ã§ã³ã
hatenablog://entry/6801883189051182465
2023-10-18T10:00:00+09:00
2023-10-18T10:00:22+09:00 ããã«ã¡ã¯ãå
ã¢ãã¤ã«åºç¤é¨ï¼ç¾ã¯ãã¯ããããã¼ããããã¯ãéçºé¨ï¼ã®å¤§å·ï¼@aomathwiftï¼ã§ãã ã¯ãã¯ãããã§ã¯ãã¬ã·ããµã¼ãã¹ã®ã¯ãã¯ãããã¢ããªã ãã§ãªããçé®® EC ãµã¼ãã¹ã®ã¯ãã¯ããããã¼ããã¯ãããè¤æ°ã® iOS ã¢ããªãéçºãã¦ãã¾ãã è¤æ°ã®ã¢ããªãéçºããä¸ã§ãæ©è½ãã®ãã®ã®éçºä»¥å¤ã«éçºè
ãæ°ã«ãããªãã¨ãããªããã¨ãããã¤ãããã¾ãã ä¾ãã°ãå種証ææ¸ã®æå¹æéãã©ã¤ãã©ãªã®é¸å®ããã¼ã¸ã§ã³ã¢ãããªã©ã§ããéçºè
ã¯ãããã®é¢å¿äºã«å¯¾ãã¦ãå
¨ã¦ã®ã¢ããªã§åãããã«æ³¨æãã¦ããªããã°ããã¾ããã ã¯ãã¯ãããã§ã¯ããHatamotoãã¨ãã Web ã¢ããªã±ã¼ã·â¦
<p>ããã«ã¡ã¯ãå
ã¢ãã¤ã«åºç¤é¨ï¼ç¾ã¯ãã¯ããããã¼ããããã¯ãéçºé¨ï¼ã®å¤§å·ï¼<a href="https://twitter.com/aomathwift">@aomathwift</a>ï¼ã§ãã</p>
<p>ã¯ãã¯ãããã§ã¯ãã¬ã·ããµã¼ãã¹ã®ã¯ãã¯ãããã¢ããªã ãã§ãªããçé®® EC ãµã¼ãã¹ã®ã¯ãã¯ããããã¼ããã¯ãããè¤æ°ã® iOS ã¢ããªãéçºãã¦ãã¾ãã</p>
<p>è¤æ°ã®ã¢ããªãéçºããä¸ã§ãæ©è½ãã®ãã®ã®éçºä»¥å¤ã«éçºè
ãæ°ã«ãããªãã¨ãããªããã¨ãããã¤ãããã¾ãã
ä¾ãã°ãå種証ææ¸ã®æå¹æéãã©ã¤ãã©ãªã®é¸å®ããã¼ã¸ã§ã³ã¢ãããªã©ã§ããéçºè
ã¯ãããã®é¢å¿äºã«å¯¾ãã¦ãå
¨ã¦ã®ã¢ããªã§åãããã«æ³¨æãã¦ããªããã°ããã¾ããã</p>
<p>ã¯ãã¯ãããã§ã¯ããHatamotoãã¨ãã Web ã¢ããªã±ã¼ã·ã§ã³ã§ãããã®ã¢ããªã«é¢ããæ
å ±ãä¸å
管çãã¦ãã¾ãã
æ¬è¨äºã§ã¯ãiOS ã¢ããªã«ã¾ã¤ããæ
å ±ç®¡çã®ä¸ã§çãã課é¡ã¨ãHatamoto ããããã©ã®ããã«è§£æ±ºãã¦ããããç´¹ä»ãã¾ãã</p>
<h1 id="Hatamoto-å°å
¥åã®èª²é¡">Hatamoto å°å
¥åã®èª²é¡</h1>
<p>ã¯ãã¯ãããã§ã¯ãã¢ãã¤ã«åºç¤ã¨ããã°ã«ã¼ãã App Store Connect ãã¯ããã¨ããã¢ããªéçºã«å¿
è¦ãªãµã¼ãã¹ã®ã¯ã¼ã¯ã¹ãã¼ã¹ã®ç®¡çè
ã¨ãã¦ã®å½¹å²ãæ
ã£ã¦ãã¾ããã¢ããªã®è¨¼ææ¸ã®çºè¡çã®ãªãã¬ã¼ã·ã§ã³ã¯ãã¢ãã¤ã«åºç¤ã°ã«ã¼ãã§è¡ã£ã¦ãã¾ãã</p>
<p>ãã®ã¢ãã¤ã«åºç¤ã°ã«ã¼ãã«æå±ããã¨ã³ã¸ãã¢ç®ç·ã§ãHatamoto å°å
¥åã¯ç¤¾å
ã¢ããªã®è¨¼ææ¸ç®¡çãã©ã¤ãã©ãªã®æ´æ°ã«é¢ãã¦ããã¤ãã®èª²é¡ãããã¾ããã</p>
<h2 id="éçºè
ãèªçºçã«è¨¼ææ¸ã®æå¹æéã«æ°ã¥ãå¿
è¦ããã">éçºè
ãèªçºçã«è¨¼ææ¸ã®æå¹æéã«æ°ã¥ãå¿
è¦ããã</h2>
<p>iOSã¢ããªã§ããã·ã¥éç¥ãéä¿¡ããã«ã¯ãApple Push Notification Serviceï¼ä»¥ä¸ APNs ï¼ã®è¨¼ææ¸ãå¿
è¦ã§ãããã®è¨¼ææ¸ã¯1å¹´ã¨ããæå¹æéãæã¤ãããç¶ç¶çã«ããã·ã¥éç¥ãéãç¶ããããã«ã¯æéãåããåã«æ´æ°ãã¦å·®ãæ¿ããå¿
è¦ãããã¾ãã
ãããã®è¨¼ææ¸ã®å·®ãæ¿ããå¿ãã¦æå¹æéãéããã¨ãã¢ããªã®ããã·ã¥éç¥ãçªç¶å±ããªããªã£ã¦ãã¾ãã¾ãã
ãã®ãããã¢ããªéçºè
é㯠APNs 証ææ¸ã®æå¹æéãææ¡ããæéãåããåã«èªçºçã«ã¢ãã¤ã«åºç¤ã°ã«ã¼ãã«æ´æ°ãä¾é ¼ããå¿
è¦ãããã¾ããã</p>
<p>ã¾ããã¯ãã¯ãããã§ã¯ã<a href="https://developer.apple.com/jp/programs/enterprise/">Apple Developer Enterprise Program</a> ã«å å
¥ãã¦ããã社å
ã§åä½ç¢ºèªããããã®ã¢ããªãé
ä¿¡ããã®ã«å©ç¨ãã¦ãã¾ãããã® Apple Developer Enterprise Program ã使ã£ã¦ã¢ããªãé
ä¿¡ããéã«ããé常㮠Apple Developer Program ã使ã£ãé
ä¿¡ã¨åæ§ã« Provisioning Profile ãå¿
è¦ã«ãªãã¾ãã
ãããã® Provisioning Profile ã®æ´æ°ãå¿ãã¦æå¹æéãéããã¨ã社å
ã§ãã§ã«é
ä¿¡æ¸ã¿ã ã£ãã¢ããªãçªç¶å©ç¨ã§ããªããªãã¨ãããéçºãé²ããä¸ã§ã®åé¡ãèµ·ããã¾ããããã«ãé常㮠Apple Developer Program ã§ä½æãã App Store é
ä¿¡ç¨ã® Provisioning Profile ããæ´æ°ãå¿ãã㨠App Store ã«ã¢ãããã¼ããããã¤ããªããã«ãã§ããªãåé¡ã«ç´é¢ãã¾ãã
ãããã£ã¦ããããã APNs 証ææ¸ã¨åæ§ã«ãæéãåããåã«æ°ã¥ãã¦æ´æ°ãä¾é ¼ããå¿
è¦ãããã¾ããã</p>
<h2 id="æ´æ°ãå¿
è¦ãªã©ã¤ãã©ãªã使ãç¶ãã¦ãããã¨ã«æ°ã¥ããªã">æ´æ°ãå¿
è¦ãªã©ã¤ãã©ãªã使ãç¶ãã¦ãããã¨ã«æ°ã¥ããªã</h2>
<p>ã¯ãã¯ãããã§éçºãã¦ãã iOS ã¢ããªã§ã¯ããªã¼ãã³ã½ã¼ã¹ã®ã©ã¤ãã©ãªãã社å
ã©ã¤ãã©ãªã¾ã§ãæ§ã
ãªã©ã¤ãã©ãªãå©ç¨ãã¦ãã¾ãã
ã¢ãã¤ã«åºç¤ã°ã«ã¼ãã§ã¯ãããã©ã¤ãã©ãªã§èå¼±æ§ãå«ãã ãã¼ã¸ã§ã³ããªãªã¼ã¹ãããããæ¢åã®æåãå£ããããªåé¡ã»å¤æ´ã®ãããã¼ã¸ã§ã³ããªãªã¼ã¹ããããããå ´åã«ããã®ã©ã¤ãã©ãªã使ç¨ãã¦ããã¢ããªã®éçºè
ã«è¿
éã«å
±æãããã¨ããéè¦ãããã¾ããã<a href="#f-6b93ebfd" name="fn-6b93ebfd" title="社å
ã§ã¯ã¢ããªãã¨ã«ã©ã¤ãã©ãªã®æ´æ°ãç£è¦ããã¹ã¯ãªãããèªåã§æ¸ãã¦é±æ¬¡ã§å®è¡ãã¦ãã¾ããããå©ç¨ãããã¼ã¸ã§ã³ãåºå®ãã¦ããå ´åããæ´æ°ã®ç·æ¥æ§ãé«ãå ´åã«ãããæ©è½ããªãã¨ããäºæ
ãããã¾ãããç¾å¨ã¯ãRenovate ã®ãããªãã¼ã«ãå©ç¨ããã®ã解決æ段ã®ä¸ã¤ã ã¨æãã¾ãã">*1</a></p>
<p>ããããåã¢ããªã§ä½¿ããã¦ããã©ã¤ãã©ãªã¨ãã®ãã¼ã¸ã§ã³ã常ã«äººåã§è¿½ãç¶ãããã¨ã¯é£ãããå©ç¨ãã¦ããã¢ããªå´ã§å®éã«åé¡ã«ç´é¢ãã¦ãã対å¦ããã¨ãããã¨ãå¤ãã®ãå®æ
ã§ããã</p>
<h1 id="Hatamotoã®æ©è½">Hatamotoã®æ©è½</h1>
<p>ä¸è¨ã®èª²é¡ã®è§£æ±ºãã¢ããã¼ã·ã§ã³ã«ãHatamoto ã«ã¯ä»¥ä¸ã®æ©è½ãå®è£
ããã¦ãã¾ãã</p>
<h2 id="証ææ¸ã®æå¹æéãç£è¦ãææ¥ãè¿ã¥ããã-Issue-ã起票ãã">証ææ¸ã®æå¹æéãç£è¦ããææ¥ãè¿ã¥ããã Issue ã起票ãã</h2>
<p>åã¢ããªã® APNs 証ææ¸ã«å ããProvisioning Profile ã»éçºè
証ææ¸ã®æå¹æéãä¸è¦§ãã¦ç®¡çã§ãã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/aomathwift/20231017/20231017135006.png" width="1200" height="585" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ã¾ãããã®åéããæå¹æéãæ¯æ¥å®æå®è¡ãããã¸ã§ãã§ç£è¦ããæå¹æéã1ã¶æ以å
ã«è¿ã¥ããå ´åã«ã¢ãã¤ã«åºç¤ã°ã«ã¼ãã¸ã®ä¾é ¼ç¨ãªãã¸ããªå®ã«æ´æ°ä¾é ¼ Issue ãèªåã§ç«ã¦ããã¨ãã§ãã¾ãã</p>
<h2 id="åã¢ããªã§ä½¿ããã¦ããã©ã¤ãã©ãªãä¸è¦§ãã">åã¢ããªã§ä½¿ããã¦ããã©ã¤ãã©ãªãä¸è¦§ãã</h2>
<p>ã¢ããªãã¨ã«ã使ç¨ãã¦ããã©ã¤ãã©ãªã¨ãã®ãã¼ã¸ã§ã³ãä¸è¦§ã§ãã¾ãã
iOS ã¯ã©ã¤ãã©ãªã®ç®¡çæ¹æ³ã SwiftPMãCocoaPodsã Carthage ã¨è¤æ°åå¨ããã®ã§ãããããã«å¯¾ãç°ãªãåéæ¹æ³ã§åéãã¦ãã¾ãã
ã¾ãããã®åéããæ
å ±ãæ´»ãããããã®ã©ã¤ãã©ãªãå©ç¨ãã¦ããã¢ããªããåç
§ã§ãã¾ãã
ãã®æ©è½ã¯ <a href="https://techlife.cookpad.com/entry/2017/03/23/115619">gem_collector</a> ã«ã¤ã³ã¹ãã¤ã¢ããã¦ãã¾ãã</p>
<h2 id="ã©ã¤ãã©ãªãå©ç¨ãã¦ããã¢ããªã«åãã¦ã®ä¸æ¬ã¢ãã¦ã³ã¹ããã">ã©ã¤ãã©ãªãå©ç¨ãã¦ããã¢ããªã«åãã¦ã®ä¸æ¬ã¢ãã¦ã³ã¹ããã</h2>
<p>ãããã¾ã gem_collector ã«ã¤ã³ã¹ãã¤ã¢ãåããæ©è½ã«ãªãã¾ãããç¹å®ã®ã©ã¤ãã©ãªã使ç¨ãã¦ããã¢ããªã®éçºãªãã¸ããªã«å¯¾ãã¦ãä¸æã« Issue ã起票ãããã¨ãã§ãã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/aomathwift/20231017/20231017135118.png" width="1200" height="417" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ããã¯ãã©ã¤ãã©ãªå´ã«åé¡ãè¦ã¤ããæ©æ¥ã«ã¢ãããã¼ããä¾é ¼ãããå ´åãã社å
ã©ã¤ãã©ãªã«ç ´å£çãªå¤æ´ãå ãã£ãå ´åãªã©ã«å©ç¨ãããæ³å®ã§ä½ããã¦ãã¾ãã</p>
<p>ã¡ãªã¿ã« Hatamoto ã¨ããååã¯ãã¢ãã¤ã«ã¢ããªã«ã¾ã¤ãããã¼ã¿ããç£è¦ãããã¨ããæå³åãããç£è¦å½¹âè¡å
µâæ¦å½æ代ã«ãããæ¦å°ã®è¿è¡å
µï¼ææ¬ã¨ããæµãã§åä»ãããã¦ãã¾ãã</p>
<h1 id="åæ©è½ã®å®ç¾æ¹æ³">åæ©è½ã®å®ç¾æ¹æ³</h1>
<p>åæ©è½ã¯ä»¥ä¸ã®ããã«å®è£
ãã¦ãã¾ãã</p>
<h2 id="証ææ¸ã®æå¹æé管ç">証ææ¸ã®æå¹æé管ç</h2>
<p>å証ææ¸ã®æå¹æé管çã¯ããããã以ä¸ã®å³ã®ãããªæµãã§å®ç¾ãã¦ãã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/aomathwift/20231017/20231017135226.png" width="1200" height="737" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>APNs 証ææ¸ã®æ´æ°ã®éã¯ãæå¹ãªè¨¼ææ¸ã®æç¡ã確èªããå¿
è¦ã«å¿ãã¦æ°è¦ä½æããã¨ããä½æ¥ãã¾ã¨ãã¦è¡ã fastlane ã® <a href="https://docs.fastlane.tools/actions/pem/">pem</a> ã¨ããã¢ã¯ã·ã§ã³ãå©ç¨ãã¦ãã¾ã<a href="#f-ebb25834" name="fn-ebb25834" title=" App Store Connect API ã«ã¯è¨¼ææ¸ãä½æããæ©è½ããªããããä½æå¦çãå®å
¨ã«èªååãããã¨ã¯ç¾ç¶ã§ãã¾ãã">*2</a>ã</p>
<p>ãã®æ©è½ãå©ç¨ããããã«ã¯ãApple Developer ã® AppManager 以ä¸ã®ã¦ã¼ã¶ã¼ã§ãã°ã¤ã³ããå¿
è¦ãããããã®ãã°ã¤ã³ã®éã«ä»è¦ç´ èªè¨¼ãæ±ãããããã¨ãããAPNs 証ææ¸ã®æ´æ°ã¯ã¢ãã¤ã«åºç¤ã°ã«ã¼ãã®ã¡ã³ãã¼ã«ãã£ã¦æåã§å®è¡ããã¦ãã¾ãã
ãã®æ´æ°ã®éãä½æããã証ææ¸ã Amazon S3 ï¼ä»¥ä¸ S3 ï¼ã®ãã±ããã«ã¢ãããã¼ãããããã«ãã¦ããã®ã§ããã®ä¸èº«ãè¦ã¦æå¹æéããã§ãã¯ãã¦ãã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/aomathwift/20231017/20231017135317.png" width="1200" height="632" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Provisioning Profileã»éçºè
証ææ¸ã®æå¹æéã®åå¾ã«ã¯ <a href="https://developer.apple.com/documentation/appstoreconnectapi/profiles">App Store Connect API</a> ã使ã£ã¦ãã¾ãã</p>
<p>æå¹æéãè¿ã¥ãã¦ãããã©ããã¯ãæ¯æ¥å®æå®è¡ãããã¸ã§ã<a href="#f-b4e41c88" name="fn-b4e41c88" title="ã¯ãã¯ãããã§å©ç¨ããã¦ãã Ruby 製㮠Web ãã¼ã¹ã®ã¸ã§ãã¹ã±ã¸ã¥ã¼ã©ã¼ Kuroko2 ãå©ç¨ãã¦ãã¾ã">*3</a>ã§ä»æ¥ã®æ¥ä»ã¨æå¹æéãæ¯è¼ãã¦ç¢ºèªãã¦ãã¾ãã</p>
<h2 id="使ç¨ã©ã¤ãã©ãªã®åé">使ç¨ã©ã¤ãã©ãªã®åé</h2>
<p>åã¢ããªãå©ç¨ãã¦ããã©ã¤ãã©ãªã®åå¾ã¯ãããã±ã¼ã¸ããã¼ã¸ã£ã¼ãã¨ã«ç°ãªãæ¹æ³ã§å®ç¾ãã¦ãã¾ãã</p>
<h3 id="CarthageSwiftPM">CarthageãSwiftPM</h3>
<p><a href="https://docs.github.com/en/rest?apiVersion=2022-11-28">GitHub API</a> ã使ã£ã¦åãªãã¸ããªå
ã® Cartfile.resolvedãPackage.resolved ã®ä¸èº«ãåå¾ããã¼ã¹ãã¦ãã¾ããåãã GitHub API çµç±ã§ãå©ç¨ãã¦ããã©ã¤ãã©ãªã®ææ°ãã¼ã¸ã§ã³ãåå¾ãã¦ãã¾ãã</p>
<h3 id="CocoaPods">CocoaPods</h3>
<p>CocoaPods ã§è§£æ±ºãããã©ã¤ãã©ãªã®ãªã¹ãã¨ããããã®ãã¼ã¸ã§ã³ãè¨è¿°ããã Podfile.lock ã«ã¯ããã®ã©ã¤ãã©ãªã®ãªãã¸ã㪠URL ãè¨è¼ããã¾ããããã®ãããSwiftPM ã Carthage ã¨åæ§ã« Podfile.lock ã®ä¸èº«ãåºã« GitHub API ãå©ç¨ãã¦ã©ã¤ãã©ãªã®ææ°ãã¼ã¸ã§ã³ãåå¾ããã¨ããã®ã¯é£ããã§ãã
ãããã£ã¦ãå©ç¨ãã¦ãã CocoaPods ã©ã¤ãã©ãªã®åå¾åã³ææ°ã® podspec ã®æ
å ±ã®åå¾ã¯ãåã¢ããªã®ã¡ã¤ã³ãã©ã³ãã® CI ã§å®è¡ããããã«ãã¾ãããããã§åéããæ
å ±ã S3 ã«ã¢ãããã¼ãããã¢ãããã¼ããããæ
å ±ãåæ§ã« Kuroko2 ã®ã¸ã§ããå®æå®è¡ãã¦åå¾ã DB ã«ç»é²ãããã¨ããæµãã«ãã¦ãã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/aomathwift/20231017/20231017135946.png" width="1200" height="569" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h1 id="Hatamoto-ãå®éã«éç¨ãã¦ã¿ã¦">Hatamoto ãå®éã«éç¨ãã¦ã¿ã¦</h1>
<p>Hatamoto ãå®éã«éç¨ãã¦ã¿ã¦ãæãè¯ãã£ãã®ã¯è¨¼ææ¸ã®æå¹æéãæ°ã«ããå¿
è¦ããªããªã£ããã¨ã§ãã証ææ¸ã¯åã¢ããªã®ç®¡çè
ããã¦ã³ãã¼ããã¦å·®ãæ¿ãããã¨ã«ãªãã®ã§ã管çè
ã§ããã¢ãã¤ã«åºç¤ã°ã«ã¼ãã®ãªãã¬ã¼ã·ã§ã³ãæ¸ãã¾ããã</p>
<p>ã¾ããåã¢ããªã使ã£ã¦ããã©ã¤ãã©ãªãä¸è¦§ã§ããã¨ããã®ã¯ãæãã®å¤ç®¡çè
以å¤ã®éçºè
ã®ç®ç·ããå½¹ã«ç«ã¤å ´é¢ãããã¾ããã
ãã¨ãã°ãæ°ããã©ã¤ãã©ãªãå°å
¥ãããã¨ããã¨ãã«ã社å
ã«ãã®ã©ã¤ãã©ãªã使ã£ã¦ããã¢ããªãä»ã«ç¡ããã調ã¹ãå°å
¥æ¹æ³ã確èªãããã¨ãã£ãæ´»ç¨ä¾ãããã¾ããã</p>
<p>éã«ãã©ã¤ãã©ãªãå©ç¨ãã¦ããã¢ããªã«åãã¦ã®ä¸æ¬ã¢ãã¦ã³ã¹ã¯æªã æ´»ç¨ããã¦ãã¾ããã対å¿ãå¿
è¦ãªåé¡ãã¿ã¤ãã£ãã¨ãã«ãåã¢ããªã§éçºè
å´ãèªçºçã« Issue ãç«ã¦ã¦å¯¾å¿ãã¦ãããã¨ãå¤ããéçºè
éã®å¯¾å¿åã®é«ãæ
ã«ãã¾ãéè¦ããªãæ©è½ã¨ãªã£ã¦ãã¾ãã¾ããã</p>
<h1 id="ãããã«">ãããã«</h1>
<p>ãã®è¨äºã§ã¯ãã¢ããªã®å種証ææ¸ãåããã¸ã§ã¯ããä¾åãã¦ããéçºãã¼ã«ã»ã©ã¤ãã©ãªãªã©ã iOS ã¢ããªã«é¢ããæ
å ±ãä¸å
管çããããã® Web ã¢ããªã±ã¼ã·ã§ã³ãHatamotoããç´¹ä»ãã¾ããã
ãã®ãã¼ã«ã®ãããã§ã iOS ã¢ããªãéçºãã¦ããä¸ã§é¢åã¨ãªã管çé¨åãå¹çåã§ãã¦ãã¾ããApp Store Connect API ã®æ©è½ãå¢ããã°ã証ææ¸ã®çæããã¼ãªã©æ´ã«èªååã§ããé¨åãå¢ãããã便å©ã«å©ç¨ãããã¨ãã§ãããã§ãã</p>
<p>ãã® Hatamoto ã¯ä¸»ã« App Store Connect API 㨠GitHub API ãfastlane ã使ã£ã¦æ¯è¼çåç´ãªå¦çã§å®ç¾ããã¦ãã¾ããè¤æ°ã¢ããªã«ããã証ææ¸çã®æ
å ±ç®¡çã«æ©ãæ¹ã¯ãHatamoto ã®ãããªWebã¢ããªã«ãã管çã試ãã¦ã¿ã¦ã¯ãããã§ããããã</p>
<div class="footnote">
<p class="footnote"><a href="#fn-6b93ebfd" name="f-6b93ebfd" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">社å
ã§ã¯ã¢ããªãã¨ã«ã©ã¤ãã©ãªã®æ´æ°ãç£è¦ããã¹ã¯ãªãããèªåã§æ¸ãã¦é±æ¬¡ã§å®è¡ãã¦ãã¾ããããå©ç¨ãããã¼ã¸ã§ã³ãåºå®ãã¦ããå ´åããæ´æ°ã®ç·æ¥æ§ãé«ãå ´åã«ãããæ©è½ããªãã¨ããäºæ
ãããã¾ãããç¾å¨ã¯ã<a href="https://github.com/marketplace/renovate">Renovate</a> ã®ãããªãã¼ã«ãå©ç¨ããã®ã解決æ段ã®ä¸ã¤ã ã¨æãã¾ãã</span></p>
<p class="footnote"><a href="#fn-ebb25834" name="f-ebb25834" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"> App Store Connect API ã«ã¯è¨¼ææ¸ãä½æããæ©è½ããªããããä½æå¦çãå®å
¨ã«èªååãããã¨ã¯ç¾ç¶ã§ãã¾ãã</span></p>
<p class="footnote"><a href="#fn-b4e41c88" name="f-b4e41c88" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">ã¯ãã¯ãããã§å©ç¨ããã¦ãã Ruby 製㮠Web ãã¼ã¹ã®ã¸ã§ãã¹ã±ã¸ã¥ã¼ã©ã¼ Kuroko2 ãå©ç¨ãã¦ãã¾ã</span></p>
</div>
aomathwift
éçºãå¿«é©ã«ããiOSã¢ããªå
ãã°ç¢ºèªãã¼ã«
hatenablog://entry/820878482972950423
2023-10-13T17:30:00+09:00
2023-10-13T17:35:20+09:00 ã¯ãã¯ãããã§ã¯ãiOSã¢ããªå
ã®è¡åãã°ããããã¯ã¼ã¯éä¿¡ãã°ãè¦ããã使ããããããããã°ç¢ºèªãã¼ã«ããæ´»ç¨ãã¦ãã¾ãããã®ä½¿ãæ¹ãèæ¯ãå®è£
æã®ç¥è¦ãªã©ã«ã¤ãã¦è©³ãããç´¹ä»ãã¾ãã
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231013/20231013164516.png" alt="開発を快適にするiOSアプリ内ログ確認ツール(履歴・定義辞書・チェックリスト)" width="1200" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ããã«ã¡ã¯ï¼ã¬ã·ãäºæ¥é¨ã®è¤å(<a href="https://twitter.com/yujif_">@yujif_</a>) ã§ãã</p>
<p>ã¯ãã¯ãããiOSã¢ããªã®éçºè
ç¨æ©è½ã¨ãã¦ããã°ç¢ºèªãã¼ã«ããä½ã£ã¦ã¿ã¾ããã社å
ã§1年以ä¸éç¨ãã¦å¥½è©ãªã®ã§ããã®çµç·¯ãå¦ã³ãã¾ã¨ãã¦ã¿ã¾ãã</p>
<p>iOSDC Japan 2022 ã§ã¯ã<a href="https://www.youtube.com/watch?list=PLod2oSGQp3W6tx5JMQntpuZ-fNpP4Y_Hh&v=4NGvI9PoAIQ">ã¢ãã¤ã«ã¢ããªã®è¡åãã°ã®ãä»è¾¼ã¿ããå¿«é©ã«ãã</a>ãã¨é¡ãã¦é¢é£ããå
容ãçºè¡¨ãã¦ãã¾ãããã¡ãã®è³æãåããã¦ã覧ãã ããã</p>
<p><iframe id="talk_frame_920663" class="speakerdeck-iframe" src="//speakerdeck.com/player/32c68404daf74ab8b42c2a004415922a" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/yujif/iosdc-japan-2022-mobile-app-logging">speakerdeck.com</a></cite></p>
<ul class="table-of-contents">
<li><a href="#ã¢ããªå
ãã°ç¢ºèªãã¼ã«ã¨ã¯">ã¢ããªå
ãã°ç¢ºèªãã¼ã«ã¨ã¯</a><ul>
<li><a href="#1-ãã°å±¥æ´">1. ãã°å±¥æ´</a><ul>
<li><a href="#ãã°ã®å
容確èªã楽ã«ãªã">ãã°ã®å
容確èªã楽ã«ãªã</a></li>
</ul>
</li>
<li><a href="#2-ãã°å®ç¾©è¾æ¸">2. ãã°å®ç¾©è¾æ¸</a><ul>
<li><a href="#ç¥ããªããã°ã®æå³ãåãã">ç¥ããªããã°ã®æå³ãåãã</a></li>
<li><a href="#ãã°ãæ¥åã«æ´»ããããããªã">ãã°ãæ¥åã«æ´»ããããããªã</a></li>
</ul>
</li>
<li><a href="#3-ãã°éä¿¡ãã§ãã¯ãªã¹ã">3. ãã°éä¿¡ãã§ãã¯ãªã¹ã</a><ul>
<li><a href="#ãã°ãéããã¦ããªã">ãã°ãéããã¦ããªãï¼ï¼</a></li>
<li><a href="#QAä½æ¥ã§ãã°ã®å®è£
æ¼ããããã">QAä½æ¥ã§ãã°ã®å®è£
æ¼ããããã</a></li>
<li><a href="#å®è£
æ
å½è
以å¤ã§ãåæ
ã§ãã">å®è£
æ
å½è
以å¤ã§ãåæ
ã§ãã</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#å®è£
æ¹æ³">å®è£
æ¹æ³</a><ul>
<li><a href="#1-éä¿¡æ¸ã¿ãã°ãèªã¿åºããããã«ãã">1. éä¿¡æ¸ã¿ãã°ãèªã¿åºããããã«ãã</a><ul>
<li><a href="#osLogger-ã§æ¸ãè¾¼ã">os.Logger ã§æ¸ãè¾¼ã</a></li>
<li><a href="#OSLogStore-ã§èªã¿åºã">OSLogStore ã§èªã¿åºã</a></li>
</ul>
</li>
<li><a href="#2-ãã°ç¢ºèªãã¼ã«ã§æ±ãããããã¼ã¿ã«å¤æãã">2. ãã°ç¢ºèªãã¼ã«ã§æ±ãããããã¼ã¿ã«å¤æãã</a><ul>
<li><a href="#OSLogEntry-ã®ã¡ãã»ã¼ã¸ããã³ã¼ããã">OSLogEntry ã®ã¡ãã»ã¼ã¸ããã³ã¼ããã</a></li>
<li><a href="#è¡åãã°ã®å®ç¾©ããã¥ã¡ã³ãã¨ã®ç´ä»ã">è¡åãã°ã®å®ç¾©ããã¥ã¡ã³ãã¨ã®ç´ä»ã</a></li>
</ul>
</li>
<li><a href="#3-便å©ãªæ©è½ãè²ã
å®è£
ãã">3. 便å©ãªæ©è½ãè²ã
å®è£
ãã</a><ul>
<li><a href="#ãã§ãã¯ãªã¹ãæ©è½">ãã§ãã¯ãªã¹ãæ©è½</a></li>
<li><a href="#ãããã¯ã¼ã¯éä¿¡ã®ãã°ã«ã対å¿">ãããã¯ã¼ã¯éä¿¡ã®ãã°ã«ã対å¿</a><ul>
<li><a href="#å®è£
ã«é¢ãã¦èª¿æ»ãããããã">å®è£
ã«é¢ãã¦èª¿æ»ãããããã</a></li>
<li><a href="#Simulatorã§ã¯URLã³ãã¼ã«">Simulatorã§ã¯URLã³ãã¼ã«</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><a href="#æ¯ãè¿ã£ã¦">æ¯ãè¿ã£ã¦</a><ul>
<li><a href="#ããã£ããã¨">ããã£ããã¨</a><ul>
<li><a href="#ãã°ã®å®è£
確èªãã¤ãããªããªã">ãã°ã®å®è£
ã»ç¢ºèªãã¤ãããªããªã</a></li>
<li><a href="#èªç±ã«å®é¨ã§ããç°å¢ã§éã¹ã">èªç±ã«å®é¨ã§ããç°å¢ã§éã¹ã</a></li>
<li><a href="#欲ãããã®ãä½ããã¨æ¥½ãã">欲ãããã®ãä½ããã¨æ¥½ãã</a></li>
</ul>
</li>
<li><a href="#æ¹åããããã¨">æ¹åããããã¨</a><ul>
<li><a href="#ãã°ã®èªã¿è¾¼ã¿ãéãããã">ãã°ã®èªã¿è¾¼ã¿ãéãããã</a></li>
</ul>
</li>
<li><a href="#ã¾ã¨ã">ã¾ã¨ã</a></li>
</ul>
</li>
</ul>
<h1 id="ã¢ããªå
ãã°ç¢ºèªãã¼ã«ã¨ã¯">ã¢ããªå
ãã°ç¢ºèªãã¼ã«ã¨ã¯</h1>
<p>ã¯ãã¯ãããiOSã¢ããªã«å
èµãããããã°é¢é£ã®éçºè
ç¨ãã¼ã«<a href="#f-899f9b8c" name="fn-899f9b8c" title="ãã®ãã¼ã«ã¯éçºçãã«ãã®ã¿ã«å«ã¾ãã¦ãããApp Storeçã§ã¯å©ç¨ã§ãã¾ããã">*1</a>ã§ãã
ããã°å±¥æ´ãããã°å®ç¾©è¾æ¸ãããã°éä¿¡ãã§ãã¯ãªã¹ããã®3ã¤ã®æ©è½ãããã¾ãã</p>
<h2 id="1-ãã°å±¥æ´">1. ãã°å±¥æ´</h2>
<p><figure class="figure-image figure-image-fotolife" title="ã¯ãã¯ãããiOSã¢ããªå
ã®ãã°ç¢ºèªãã¼ã«"></p>
<div class="images-row mceNonEditable"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231012/20231012151415.png" width="609" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:300px" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231012/20231012151136.png" width="609" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:300px" itemprop="image"></span></div>
<p><figcaption>ã¯ãã¯ãããiOSã¢ããªå
ã®ãã°ç¢ºèªãã¼ã«</figure></p>
<p>ã¦ã¼ã¶ã¼ã®è¡åãã°ï¼ä¾ï¼ãã¿ã³ã®ã¿ãããç¹å®ã®è¦ç´ ã®è¡¨ç¤ºãªã©ï¼ãAPIãµã¼ãã¼ã¨ã®éä¿¡ãã°ããã¯ãã¯ãããiOSã¢ããªã®ä¸ã§ããã«ç¢ºèªã§ãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="éçºçã®ã¯ãã¯ãããiOSã¢ããªå
ã§ããã°ç¢ºèªãã¼ã«ãç´ æ©ã表示ã§ããæ§å"></p>
<div class="images-row mceNonEditable"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231012/20231012150813.gif" alt="開発版のクックパッドiOSアプリ内で、ログ確認ツールを素早く表示できる様子" width="590" height="1176" loading="lazy" title="" class="hatena-fotolife" style="width:300px" itemprop="image"></span></div>
<p><figcaption>ãã°ç¢ºèªãã¼ã«ãç´ æ©ã表示ã§ããæ§å<a href="#f-315eaa85" name="fn-315eaa85" title="SwiftUIã§ä½ãããç»é¢ãªã®ã§ .blur(radius:) ãé©å½ã«ã¤ããã ãã§ç°¡åã«ã¼ããã¦ãããããGIFãã¤ããã¨ãã«ä¾¿å©ã§ãã">*2</a></figcaption></figure></p>
<p>ãã®ãã¼ã«ã¯ããã¤ã¹ã®ã·ã§ã¤ã¯ï¼Simulatorã§ã¯ â â Z <code>control + command + Z</code>ï¼ã§ã表示ã§ããã©ã®ç»é¢ããã§ãæ°è»½ã«ä½¿ãã¾ãã</p>
<h3 id="ãã°ã®å
容確èªã楽ã«ãªã">ãã°ã®å
容確èªã楽ã«ãªã</h3>
<p>å¿
è¦ãªæ
å ±ã ãã«çµãã種é¡å¥ã«è²åããããã¨ã§ãããã¨è¦ã¦ææ¡ãããããã¦ãã¾ãã</p>
<p>ããã¾ã§ããã¬ã¼ããã®åºå㯠Xcode å
ã®ã³ã³ã½ã¼ã«<a href="#f-2a16df53" name="fn-2a16df53" title="ãªããXcode 15 ã§ã¯ Debug Console ãå¼·åãããéè¦åº¦ããã°ã®ç¨®é¡ãã¨ã«ãã£ã«ã¿ãªã³ã°ã§ãããªã©ä¾¿å©ã«ãªãã¾ãããhttps://developer.apple.com/videos/play/wwdc2023/10226/ ">*3</a>ã Console.app ãªã©ã§ç¢ºèªã§ãã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="[Before] Xcodeã®ã³ã³ã½ã¼ã«ã«æµãããã°ã¯è¦ã¥ãã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231008/20231008020615.png" alt="Xcode 14のスクリーンショット。コンソールに行動ログの中身が表示されている。" width="600" height="322" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>[ãã°ç¢ºèªãã¼ã«ããªãã£ãé ] Xcodeã®ã³ã³ã½ã¼ã«ã«æµãããã°ã¯è¦ã¥ãã</figcaption></figure></p>
<p>ããããé¢ä¿ã®ãªãæ
å ±ãããããæµãã¦ããããè¦ãç®ãJSONãã®ã¾ã¾ã ã£ãããå·®åãåããã¥ããã£ããã¨ã人éã«ã¨ã£ã¦ã¯ç²ãããã®ã§ããã</p>
<p><strong>è¡åãã°ã®æ§æè¦ç´ </strong></p>
<ul>
<li>ãã®ãã°ç¹æã®ä»å æ
å ±
<ul>
<li>ä¾ï¼å¯¾è±¡ãªã½ã¼ã¹IDãæ¤ç´¢ãã¼ã¯ã¼ã ãªã©</li>
</ul>
</li>
<li>å
¨ãã°å
±éã§ä»å ããã¦ããæ
å ±
<ul>
<li>ä¾ï¼ã¦ã¼ã¶ã¼IDã端æ«OSãã¼ã¸ã§ã³ãã¢ããªãã¼ã¸ã§ã³ ãªã©</li>
</ul>
</li>
</ul>
<p>1è¡ã®ãã°ã«ã¯æ§ã
ãªæ
å ±ãå«ã¾ãã¾ãããå
¨ã¦ã®ããããã£ã常ã«è¦ããããã§ã¯ããã¾ãããåºæã®æ
å ±ã ãã«çµã£ã¦ãå
容ã®ç¢ºèªã«éä¸ãããããã¾ããã</p>
<h2 id="2-ãã°å®ç¾©è¾æ¸">2. ãã°å®ç¾©è¾æ¸</h2>
<p><figure class="figure-image figure-image-fotolife" title="iPadã§è¦ãããã°å®ç¾©ä¸è¦§ã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231008/20231008022235.png" alt="" width="1200" height="801" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>å
¨ãã°å®ç¾©ãä¸è¦§è¡¨ç¤ºãã横ææ¤ç´¢ãã§ãããiPadã«ã対å¿ã</figcaption></figure></p>
<p>éä¿¡ããããã°ã ãã§ãªãããã¾å®ç¾©ããã¦ããå
¨ã¦ã®ãã°ã«ã¤ãã¦ãè¾æ¸ã®ããã«èª¿ã¹ããã¾ãã</p>
<h3 id="ç¥ããªããã°ã®æå³ãåãã">ç¥ããªããã°ã®æå³ãåãã</h3>
<p><strong>ãã°å®ç¾©ããã¥ã¡ã³ãã®æ´»ç¨</strong></p>
<p>ã¯ãã¯ãããã§ã¯ãMarkdownå½¢å¼ã®ãã°å®ç¾©ããã¨ã«åå®å
¨ãªãã°å®è£
ç¨ã³ã¼ããçæããä»çµã¿ã3年以ä¸éç¨ãã¦ãã¾ãã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2020%2F11%2F05%2F110000" title="ããã¥ã¡ã³ããã¼ã¹ã®åå®å
¨ãªã¢ãã¤ã«ã¢ããªè¡åãã°åºç¤ã®æ§ç¯ - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techlife.cookpad.com/entry/2020/11/05/110000">techlife.cookpad.com</a></cite></p>
<p>ãã®ä»çµã¿ã®ãããã§ããã°å®ç¾©ä¸ã¤ä¸ã¤ã«å¿
ã説ææãç¨æããã¦ãã¾ããä½ã®ããã«å°å
¥ããããã°ãªã®ãããã©ã¡ã¼ã¿ã«ã¯ã©ã®ãããªå¤ãå
¥ãã®ãã注æç¹ã¯ä½ããå½æã®ãã°è¨è¨è
ãè¨ããããã¥ã¡ã³ãããææ¡ã§ãã¾ãã</p>
<p><strong>æ
å½é åå¤ã«ãç®ãåãããã</strong></p>
<p>ãããªä¾¿å©ãªãã°å®ç¾©Markdownã§ããã沢山㮠<code>.md</code> ãã¡ã¤ã«ããã ç½®ãã¦ããã ãã§ã¯æ´»ç¨ããã¾ããã
èªåã®æ
å½é åã®ãã°ã¯è©³ããã¦ããä»ã®èª°ããå
¥ãããã°ã¯ä½ããã£ããããªãã¨ãªããªãè¦ãªããã®ã§ãã</p>
<p>ã¢ããªå
ã§è¦ããããªããã¨ã§ã触ã£ã¦ãããã¡ã«èªç¶ã¨ããããªã®ãã£ããã ï¼ããããã¨ãã«ä½¿ãããð¡ãã¨ãã£ãå¢çãè¶ããçºè¦ãçã¾ããã®ãçã£ãé¨åãããã¾ãã</p>
<h3 id="ãã°ãæ¥åã«æ´»ããããããªã">ãã°ãæ¥åã«æ´»ããããããªã</h3>
<p><figure class="figure-image figure-image-fotolife" title="ãã°å®ç¾©ããã社å
ã®åæSQLä¾ãããã«æ¢ãã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231008/20231008023549.png" alt="ログ定義画面の「Bdash ServerでSQL例を探す」ボタンをタップすることで、社内のデータ分析SQLと実行結果にすぐたどり着けている図" width="1200" height="789" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ãã°å®ç¾©ããã社å
ã®åæSQLã®ä¾ãããã«æ¢ãã</figcaption></figure></p>
<p>ãã°ãè¦ã«ãã人ã¯ä½ãã調æ»ã»åæããã人ã®ã¯ããªã®ã§ããããæå©ããããªã³ã¯ãç¨æãã¦ãã¾ãã</p>
<p><strong>éè¨ã»åæã¸ã®ã·ã§ã¼ãã«ãã</strong></p>
<p>ä¾ãã°ããã¼ã®è¡¨ç¤ºãã¿ããã®ãã°ã§ããã°ãéè¨SQLãæ¸ãã¦æ½çã®å¹ææ¤è¨¼ããããã§ãã</p>
<p>ã¯ãã¯ãããã§ã¯ããã¼ã¿åæSQLãå
±æã§ãã社å
Webãµã¼ãã¹ãBdash Serverãããã使ããã¦ãã¾ããããã§ãåãã°å®ç¾©ã«é¢ããSQLä¾ãBdash Serverã§ããã«æ¤ç´¢ã§ãããã¿ã³ãã¤ãã¾ããï¼ä¸å³ï¼ã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2021%2F06%2F11%2F120000" title="ãã¼ã¿åæ SQL ã¨ãã®å®è¡çµæãå
±æã»æ¤ç´¢ã§ããã¢ã㪠Bdash Server ãä½ãã¾ãã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techlife.cookpad.com/entry/2021/06/11/120000">techlife.cookpad.com</a></cite></p>
<p>ããã®çµæãæ°ã«ãªããã©ãSQLãæ¸ãã®ãã¡ãã£ã¨â¦â¦ãã¨ãã人ãããããã§ã«èª°ããä½ã£ãçµæã§äºè¶³ãããªãå³è§£æ±ºã§ãã¾ãããå°ãéãã¨ãã¦ãåèã«ã§ããSQLãããã ãã§æ¸ãããããªãã¾ãã</p>
<p>æºã¾ã£ã¦ããç¥è¦ããªãããã«ä½¿ããããã«ãã¦ãå
¨ç¤¾ã§ã®çç£æ§åä¸ãçã£ã¦ãã¾ãã</p>
<h2 id="3-ãã°éä¿¡ãã§ãã¯ãªã¹ã">3. ãã°éä¿¡ãã§ãã¯ãªã¹ã</h2>
<p><figure class="figure-image figure-image-fotolife" title="ãã°é信確èªãã§ãã¯ãªã¹ãã«4ã¤ã®ãã°å®ç¾©ã並ãã§ããå³ã4ã¤ã®ãã¡ã3ã¤ã¯ãéä¿¡æ¸ã¿ãã ãã1ã¤ã ããæªéä¿¡ãã¨å¼·èª¿è¡¨ç¤ºããã¦ããã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231008/20231008153505.png" alt="ログ送信確認チェックリストに4つのログ定義が並んでいる図。4つのうち、3つは「送信済み」だが、1つだけ「未送信」と強調表示されている。" width="601" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:300px" itemprop="image"></span><figcaption>ãã§ãã¯ãªã¹ãã«è¿½å ããã°ãæªéä¿¡ã®ãã°ããã¶ãåºããã</figcaption></figure></p>
<p>åãã°å®ç¾©ããã§ãã¯ãªã¹ãã«ç»é²ãã¦ããã°ããã°éä¿¡æã«èªåã§ãã§ãã¯ããããã®ãã°ãéä¿¡æ¸ã¿<a href="#f-b74e4bdd" name="fn-b74e4bdd" title="ããã§ã®ãéä¿¡æ¸ã¿ãã¨ã¯ãã¢ããªã®èµ·åããçµäºã¾ã§ã®éã§ã®è©±ã§ããã¢ããªãåèµ·åããã¨ããã¹ã¦ãæªéä¿¡ãã«æ»ãã¾ãã">*4</a>ãã©ãããä¸ç¬ã§ç¢ºèªã§ãã¾ãã</p>
<h3 id="ãã°ãéããã¦ããªã">ãã°ãéããã¦ããªãï¼ï¼</h3>
<p>æ°æ©è½ããªãªã¼ã¹ãã¦ããåæãããã¨ããããå¿
è¦ãªãã°ã®ä¸é¨ãéãã¦ããªããã¨ã«æ°ã¥ããããªã¡ã¼ï¼å®è£
ãæ¼ãã¦ã¾ãã ð¦ãã¨ç¦ãããããªå¤±æãå®éã«ããã¾ããã</p>
<p><strong>ä¾ï¼ãå®æ便ãæ©è½ãæ°ãã«ãªãªã¼ã¹ããå ´å</strong></p>
<ul>
<li>è¦ããææ¨
<ul>
<li>å®æ便ååç»é²æã®ãã¡ãã«</li>
<li>ãã£ã³ãã¼ã³ãã¨ã®å¹æ</li>
<li>å®æ便解é¤æ°</li>
<li>å®æ便ã¦ã¼ã¶ã¼ã®LTV
ãªã©</li>
</ul>
</li>
</ul>
<p>æ½çã«é¢ããææ決å®è
ã¨ãæçµçã«ä½ãç¥ãããã®ããã®èªèãæãããã¨ã§ãå¿
è¦ãªãã°ãæ´ãåºãã¾ããææ¨ã«ãã£ã¦ã¯ããµã¼ãã¼å´ã®ãã¼ã¿ã§äºè¶³ãããã®ãããã°ãã¢ãã¤ã«ã¢ããªå´ã§ã®è¡åãã°ãå¿
è¦ä¸å¯æ¬ ãªå ´åãããã¾ãã</p>
<ul>
<li>å¿
è¦ãªãã°
<ul>
<li>å®æ便ã®åå詳細ç»é¢ã®è¡¨ç¤º</li>
<li>å®æ便ç»é²ãã¿ã³ã®ã¿ããï¼âå®æ便ç»é²ç¢ºèªç»é¢ã®è¡¨ç¤ºï¼</li>
<li>ãã£ã³ãã¼ã³ããã¼ã®è¡¨ç¤º</li>
<li>ãã£ã³ãã¼ã³ããã¼ã®ã¿ãã</li>
<li>å®æ便解é¤ãã¿ã³ã®ã¿ãã</li>
<li>â¦â¦
ãªã©ãªã©</li>
</ul>
</li>
</ul>
<p>ãããããã®ã¯ãã¦ã¼ã¶ã¼ç¶æ
次第ã§ã¯éããªããã°ããããã¨ã§ããä¾ãã°ãå®æ便ã®ååã¦ã¼ã¶ã¼éå®ã®ããã¼ã®è¡¨ç¤ºãã°ã¯ãä¸åº¦ã§ãå®æ便ç»é²ãããã¦ã¼ã¶ã¼ããã¯éãããªãã¯ãã§ããä»ã«ããç¡æä¼å¡ã¨ææä¼å¡ã®å·®ããã£ã³ãã¼ã³ã®æµå
¥çµè·¯ãã¨ã®å·®ãªã©ãçµã¿åãã次第ã§ã©ãã©ãè¤éåãã¦ããã¾ãã</p>
<p>ä»æ§ãè¤éã«ãªãã¨ãã¹ãããããã§ãããå質ä¿è¨¼ã®ãã¹ããæéããããã¾ããå¿
è¦ãªãã°ãæ¼ããªããã¹ã¦éãã®ã¯çµæ§å¤§å¤ã§ãããã§ãã¯ãªã¹ãæ©è½ã¯ãã®å¯¾çã®ããã«ä½ã£ã¦ã¿ã¾ããã</p>
<h3 id="QAä½æ¥ã§ãã°ã®å®è£
æ¼ããããã">QAä½æ¥ã§ãã°ã®å®è£
æ¼ããããã</h3>
<p><figure class="figure-image figure-image-fotolife" title="ãã°éä¿¡ãã§ãã¯ãªã¹ãã®ä½¿ãæ¹"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231009/20231009193458.gif" width="720" height="260" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ãã°éä¿¡ãã§ãã¯ãªã¹ãã®ä½¿ãæ¹</figcaption></figure></p>
<p>ã¾ãåæã«å¿
è¦ãªãã°å®ç¾©ãä¸éããã§ãã¯ãªã¹ãã«å
¥ãã¾ãã次ã«ãæ³å®ãããã¦ã¼ã¶ã¼ã¨åæ§ã®æµãã§ã¢ããªãæä½ãã¾ãã</p>
<p>æä½ãçµããã¨ãããã¹ã¦éä¿¡æ¸ã¿ãã¨ãªã£ã¦ãããªãåé¡ããã¾ãããã¢ããªããªãªã¼ã¹ãã¦OKã§ãããããæªéä¿¡ããããªãããã°éä¿¡ã®å®è£
æ¼ããã©ããã«ããã¨ãããã¨ã§ãã</p>
<p>ãã®ãã§ãã¯ãªã¹ãæ©è½ã使ãã°ãå質ä¿è¨¼ï¼QAï¼ã®æåãã¹ãã®æéã§ãã¤ãã§ã«ãã°ã®å®è£
æ¼ããæ¤åºã§ãã¾ãã</p>
<h3 id="å®è£
æ
å½è
以å¤ã§ãåæ
ã§ãã">å®è£
æ
å½è
以å¤ã§ãåæ
ã§ãã</h3>
<p>ã¢ããªåä½ã§å®çµããã®ã§ãXcodeãªã©ã®éçºç°å¢ãå¿
è¦ãªã誰ã§ãå®æ½ã§ãã¾ããè¤æ°ãã¿ã¼ã³ã®æ¤è¨¼ããã¨ã³ã¸ãã¢ã«éãããã¼ã ã§åæ
ãã¦ä¸æ°ã«é²ããããã®ã¯ããããç¹ã§ãã</p>
<h1 id="å®è£
æ¹æ³">å®è£
æ¹æ³</h1>
<p>ã¢ããªå
ãã°ç¢ºèªãã¼ã«ãå®ç¾ããã«ã¯ãã©ãããã°ããã§ããããï¼</p>
<p>ã¾ããéä¿¡æ¸ã¿ãã°ãèªã¿åºãããã¨ã次ã«ããããããã°å®ç¾©ããã¥ã¡ã³ãã¨ãã¾ãç´ä»ãã¦æ±ãããã¨ããã®2ã¤ãå¿
è¦ã§ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="ã¯ãã¯ãããiOSã¢ããªã®ãã°é¢é£ã®å®è£
æ¦è¦å³"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231009/20231009204815.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ã¯ãã¯ãããiOSã¢ããªã®ãã°é¢é£ã®å®è£
æ¦è¦å³</figcaption></figure></p>
<p>éä¿¡æ¸ã¿ãã°ã端æ«å
ã§ä¿æãããªãããã¡ã¤ã«ã¸ã®åºåãã¤ã³ã¡ã¢ãªã§ã®ä¿æãªã©ããã¤ãæ¹æ³ãèãããã¾ãã
ä»åã¯ãiOSã®çµ±åãã®ã³ã°ã·ã¹ãã ï¼ä»¥ä¸ãOSãã°ï¼ãæ´»ç¨ãããã¨ã«ãã¾ããã</p>
<p><a href="https://developer.apple.com/documentation/os/logging">Logging | Apple Developer Documentation</a></p>
<h2 id="1-éä¿¡æ¸ã¿ãã°ãèªã¿åºããããã«ãã">1. éä¿¡æ¸ã¿ãã°ãèªã¿åºããããã«ãã</h2>
<p>ã¯ãã¯ãããiOSã¢ããªã§ã¯ããã°åéã©ã¤ãã©ãªã¨ã㦠<code>Puree-Swift</code> ã使ã£ã¦ãã¾ãã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2018%2F02%2F28%2F113000" title="è¯ãæãã«ãã°ãåéããã©ã¤ãã©ãªãPuree-Swiftããªãªã¼ã¹ãã¾ãã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techlife.cookpad.com/entry/2018/02/28/113000">techlife.cookpad.com</a></cite></p>
<h3 id="osLogger-ã§æ¸ãè¾¼ã">os.Logger ã§æ¸ãè¾¼ã</h3>
<p>以ä¸ã®ãããªã³ã¼ãã§ãç°¡åã«OSãã°ã«åºåã§ãã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">import</span> os
<span class="synComment">// ãã°ã®åºèªããããããã« subsystem 㨠category ãæå®</span>
<span class="synPreProc">let</span> <span class="synIdentifier">logger</span> <span class="synIdentifier">=</span> Logger(
subsystem<span class="synSpecial">:</span> <span class="synType">Bundle.main.bundleIdentifier!</span>,
category<span class="synSpecial">:</span> <span class="synConstant">"ActivityLog"</span><span class="synComment">// è¡åãã°ã®å ´åã®ä¾</span>
)
<span class="synPreProc">let</span> <span class="synIdentifier">logDataString</span> <span class="synIdentifier">=</span> <span class="synConstant">"""</span>
<span class="synConstant">{</span>
<span class="synConstant"> "</span>user_id<span class="synConstant">": 1234567890,</span>
<span class="synConstant"> "</span>event_category<span class="synConstant">": "</span>recipe_detail<span class="synConstant">",</span>
<span class="synConstant"> "</span>event_name<span class="synConstant">": "</span>tap_save_button<span class="synConstant">",</span>
<span class="synConstant"> "</span>recipe_id<span class="synConstant">": 123456</span>
<span class="synConstant">}</span>
<span class="synConstant">"""</span>
<span class="synComment">// ãã°ãæ¸ãè¾¼ã</span>
logger.notice(<span class="synConstant">"</span><span class="synSpecial">\(</span>logDataString<span class="synSpecial">)</span><span class="synConstant">"</span>)
</pre>
<p>ä¾ãã°ã以ä¸ã®ããã« OSãã°åºåç¨ã® <code>Puree-Swift</code> ã®Outputãå®ç¾©ãã¦Configuration ã«å ããã¨ããã°ãµã¼ãã¼ã¸éä¿¡ããããã°ã¨åãå
容ãã端æ«å
ã®OSãã°ã«ãåºåããã¾ãã</p>
<div style="border: 1px solid #aaa; border-radius: 4px; padding: 0.5em;">
<details>
<summary>ãã詳細ãªPuree-Swift ã® Output å®è£
ä¾ã¯ãã¡ã</summary>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">import</span> Foundation
<span class="synPreProc">import</span> os
<span class="synPreProc">import</span> Puree
<span class="synStatement">final</span> <span class="synPreProc">class</span> <span class="synIdentifier">OSLogOutput</span><span class="synSpecial">:</span> <span class="synType">InstantiatableOutput</span> {
<span class="synPreProc">let</span> <span class="synIdentifier">tagPattern</span><span class="synSpecial">:</span> <span class="synType">TagPattern</span>
<span class="synStatement">private</span> <span class="synPreProc">let</span> <span class="synIdentifier">logger</span><span class="synSpecial">:</span> <span class="synType">os.Logger</span> <span class="synComment">// iOS 14+ ã§å©ç¨å¯è½</span>
<span class="synStatement">required</span> <span class="synIdentifier">init</span>(logStore<span class="synSpecial">:</span> <span class="synType">LogStore</span>, tagPattern<span class="synSpecial">:</span> <span class="synType">TagPattern</span>, options<span class="synSpecial">:</span> <span class="synType">OutputOptions?</span>) {
<span class="synIdentifier">self</span>.tagPattern <span class="synIdentifier">=</span> tagPattern
logger <span class="synIdentifier">=</span> Logger(
subsystem<span class="synSpecial">:</span> <span class="synType">Bundle.main.bundleIdentifier!</span>,
category<span class="synSpecial">:</span> <span class="synConstant">"ActivityLog"</span> <span class="synComment">// è¡åãã°ã®å ´åã®ä¾</span>
)
}
<span class="synPreProc">func</span> <span class="synIdentifier">emit</span>(log<span class="synSpecial">:</span> <span class="synType">LogEntry</span>) {
<span class="synStatement">guard</span> <span class="synPreProc">let</span> <span class="synIdentifier">userData</span> <span class="synIdentifier">=</span> log.userData <span class="synStatement">else</span> {
assertionFailure(<span class="synConstant">"logEntry must have userData"</span>)
<span class="synStatement">return</span>
}
<span class="synStatement">guard</span> <span class="synPreProc">let</span> <span class="synIdentifier">payload</span> <span class="synIdentifier">=</span> <span class="synStatement">try</span>? JSONSerialization.jsonObject(with<span class="synSpecial">:</span> <span class="synType">userData</span>, options<span class="synSpecial">:</span> <span class="synSpecial">[]</span>) <span class="synStatement">as?</span> <span class="synSpecial">[</span><span class="synType">String</span><span class="synSpecial">:</span><span class="synType"> Any</span><span class="synSpecial">]</span> <span class="synStatement">else</span> {
assertionFailure(<span class="synConstant">"Cannot decode userData as JSONObject."</span>)
<span class="synStatement">return</span>
}
<span class="synStatement">if</span> <span class="synPreProc">let</span> <span class="synIdentifier">logDataString</span> <span class="synIdentifier">=</span> prettyJSONString(payload) {
logger.notice(<span class="synConstant">"</span><span class="synSpecial">\(</span>logDataString, privacy<span class="synSpecial">:</span> .<span class="synStatement">public</span><span class="synSpecial">)</span><span class="synConstant">"</span>)
<span class="synComment">// ããã©ã«ãã§ã¯æ
å ±ããã¹ã¯ãããããéçºçãã«ãã®ã¿ãªã®ã§ã`.public` ã«ãã¦ãã</span>
}
}
<span class="synStatement">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">prettyJSONString</span>(_ object<span class="synSpecial">:</span> <span class="synType">Any</span>) <span class="synSpecial">-></span> <span class="synType">String?</span> {
<span class="synStatement">guard</span> <span class="synPreProc">let</span> <span class="synIdentifier">data</span> <span class="synIdentifier">=</span> <span class="synStatement">try</span>? JSONSerialization.data(withJSONObject<span class="synSpecial">:</span> <span class="synType">object</span>, options<span class="synSpecial">:</span> <span class="synSpecial">[</span><span class="synType">.prettyPrinted, .sortedKeys, .withoutEscapingSlashes</span><span class="synSpecial">]</span>) <span class="synStatement">else</span> {
<span class="synStatement">return</span> <span class="synConstant">nil</span>
}
<span class="synStatement">return</span> String(data<span class="synSpecial">:</span> <span class="synType">data</span>, encoding<span class="synSpecial">:</span> .utf8)
}
}
</pre>
</details>
</div>
<p>ãã® os ãã¬ã¼ã ã¯ã¼ã¯ã® Logger ã§ããã以ä¸ã®ãããªç¹å¾´ãããã¾ãã</p>
<ul>
<li>é常ã«å¹ççã§ãã¢ããªã®åä½é
延ãªã使ãã<a href="#f-158f8cb4" name="fn-158f8cb4" title="https://developer.apple.com/videos/play/wwdc2020/10168/ ãã">*5</a></li>
<li>Console.app ã Xcode ã®ã³ã³ã½ã¼ã«ã§ãã°ã確èªã§ãã</li>
<li>ã»ã³ã·ãã£ããªæ
å ±ã¯ãã¹ã¯ã§ããï¼æå®ãã¦å
¬éãã§ããï¼</li>
</ul>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.apple.com%2Fdocumentation%2Fos%2Flogger" title="Logger | Apple Developer Documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://developer.apple.com/documentation/os/logger">developer.apple.com</a></cite></p>
<h3 id="OSLogStore-ã§èªã¿åºã">OSLogStore ã§èªã¿åºã</h3>
<p>ã¢ããªå
ãã°ç¢ºèªãã¼ã«ã§å©ç¨ããéã¯ã<code>OSLogStore</code> ã使ã£ã¦OSãã°ããèªã¿åºãã¦ãã¾ãããã㯠iOS 15以éã§å©ç¨ã§ãã¾ãã</p>
<p> <a href="https://developer.apple.com/documentation/oslog/oslogstore">OSLogStore | Apple Developer Documentation</a></p>
<pre class="code lang-swift" data-lang="swift" data-unlink>
<span class="synPreProc">import</span> OSLog
<span class="synPreProc">protocol</span> <span class="synIdentifier">OSLogEntriesDataStoreProtocol</span><span class="synSpecial">:</span> <span class="synType">AnyObject</span> {
<span class="synPreProc">func</span> <span class="synIdentifier">fetchEntries</span>() async <span class="synStatement">throws</span> <span class="synSpecial">-></span> <span class="synSpecial">[</span><span class="synType">OSLogEntry</span><span class="synSpecial">]</span>
}
<span class="synStatement">final</span> <span class="synPreProc">class</span> <span class="synIdentifier">OSLogEntriesDataStore</span><span class="synSpecial">:</span> <span class="synType">OSLogEntriesDataStoreProtocol</span> {
<span class="synPreProc">func</span> <span class="synIdentifier">fetchEntries</span>() async <span class="synStatement">throws</span> <span class="synSpecial">-></span> <span class="synSpecial">[</span><span class="synType">OSLogEntry</span><span class="synSpecial">]</span> {
<span class="synPreProc">let</span> <span class="synIdentifier">store</span> <span class="synIdentifier">=</span> <span class="synStatement">try</span> OSLogStore(scope<span class="synSpecial">:</span> .currentProcessIdentifier)
<span class="synPreProc">let</span> <span class="synIdentifier">predicate</span> <span class="synIdentifier">=</span> NSPredicate(format<span class="synSpecial">:</span> <span class="synConstant">"subsystem == %@"</span>, Bundle.main.bundleIdentifier<span class="synIdentifier">!</span>)
<span class="synStatement">return</span> <span class="synStatement">try</span> store.getEntries(matching<span class="synSpecial">:</span> <span class="synType">predicate</span>)
.reversed()
<span class="synComment">// Workaround: `store.getEntries(with: .reverse, matching: predicate)` ã§éé ï¼æ°ãããã°ãå
ï¼ã«è¿ãããã¯ãã ããiOS 16æç¹ã§ã¯æ©è½ããªããããããã§éé ã«ãã¦ããã</span>
<span class="synComment">// â»è¿½è¨ï¼iOS 17ã§ç´ã£ã¦ãã¾ããï¼</span>
}
}
</pre>
<h2 id="2-ãã°ç¢ºèªãã¼ã«ã§æ±ãããããã¼ã¿ã«å¤æãã">2. ãã°ç¢ºèªãã¼ã«ã§æ±ãããããã¼ã¿ã«å¤æãã</h2>
<h3 id="OSLogEntry-ã®ã¡ãã»ã¼ã¸ããã³ã¼ããã">OSLogEntry ã®ã¡ãã»ã¼ã¸ããã³ã¼ããã</h3>
<p>èªã¿åºãã <code>OSLogEntry</code> ã® <code>composedMessage</code> ã«ã¯è¡åãã°ã®ä¸èº«ãå
¥ã£ã¦ãã¾ããããã®æç¹ã§ã¯ãã ã®JSONæååã§ãã以ä¸ã®ãããªã³ã¼ãã§ä¸èº«ããã³ã¼ããã¦ã¢ããªå
ãã°ç¢ºèªãã¼ã«ã§æ±ãããããã¾ãã</p>
<p><code>OSLogEntry</code> ã®ã¾ã¾ã§ã¯ <code>category</code> <a href="#f-73cb7b3b" name="fn-73cb7b3b" title="https://developer.apple.com/documentation/oslog/oslogentrywithpayload/3366053-category">*6</a> ï¼å
ã»ã©ã®ä¾ã§ã¯ "ActivityLog" ã¨ããå¤ï¼ãåç
§ã§ããªãã®ã§ã<code>OSLogEntryWithPayload</code> ã«ãã¦ã³ãã£ã¹ããã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">import</span> OSLog
<span class="synPreProc">struct</span> <span class="synIdentifier">LogEntryResolver</span> {
<span class="synComment">/// OSLogEntryããå¿
è¦ãªæ
å ±ãåãã ãã¦ãã¢ããªå
ãã°ç¢ºèªãã¼ã«ã§æ±ããããã¢ãã«ã«å¤æãã¾ã</span>
<span class="synStatement">static</span> <span class="synPreProc">func</span> <span class="synIdentifier">resolve</span>(entry<span class="synSpecial">:</span> <span class="synType">OSLogEntry</span>) <span class="synSpecial">-></span> <span class="synType">CookpadLogEntry?</span> {
<span class="synStatement">guard</span> <span class="synPreProc">let</span> <span class="synIdentifier">entryWithPayload</span> <span class="synIdentifier">=</span> entry <span class="synStatement">as?</span> <span class="synType">OSLogEntryWithPayload</span> <span class="synStatement">else</span> { <span class="synStatement">return</span> <span class="synConstant">nil</span> }
<span class="synStatement">guard</span> <span class="synPreProc">let</span> <span class="synIdentifier">payload</span> <span class="synIdentifier">=</span> decode(message<span class="synSpecial">:</span> <span class="synType">entry.composedMessage</span>, category<span class="synSpecial">:</span> <span class="synType">entryWithPayload.category</span>) <span class="synStatement">else</span> { <span class="synStatement">return</span> <span class="synConstant">nil</span> }
<span class="synStatement">return</span> CookpadLogEntry(id<span class="synSpecial">:</span> <span class="synType">UUID</span>().uuidString, date<span class="synSpecial">:</span> <span class="synType">entry.date</span>, payload<span class="synSpecial">:</span> <span class="synType">payload</span>)
}
}
</pre>
<h3 id="è¡åãã°ã®å®ç¾©ããã¥ã¡ã³ãã¨ã®ç´ä»ã">è¡åãã°ã®å®ç¾©ããã¥ã¡ã³ãã¨ã®ç´ä»ã</h3>
<p>ã¯ãã¯ãããã§ã¯ãMarkdownå½¢å¼ã®ãã°å®ç¾©ããåå®å
¨ãªãã°å®è£
ç¨ã³ã¼ããçæããããã« <code>daifuku</code> <a href="#f-6841f8d7" name="fn-6841f8d7" title="ã¡ãªã¿ã« daifuku ã®ç±æ¥ã¯ã大ç¦å¸³ããããã¯ãã¯ãããã§ã¯ã2020å¹´é ã«æ°ãããã°ã®ä»çµã¿ãé称ã大統ä¸ã¢ã¯ãã£ããã£ãã°ããå°å
¥ããéã«ããã¹ã¦ã®ã«ã©ã ã1ã¤ã®ãã¼ãã«ã«æ¨ªé·ã«åå¨ããéæ£è¦åãããã大ç¦å¸³åãã¼ãã«ãã«è¡åãã°ãéç©ããããã«ãªãã¾ãããhttps://techlife.cookpad.com/entry/2020/12/29/004145 ">*7</a> ã¨ããã©ã¤ãã©ãªã使ã£ã¦ãã¾ãã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fcookpad%2Fdaifuku" title="GitHub - cookpad/daifuku: A markdown parser and compiler for log definitions in mobile applications" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>ä»åã¯ãã®ä»çµã¿ãå¿ç¨ããã¢ããªå
ãã°ç¢ºèªãã¼ã«ãããã°å®ç¾©ãã¨ã®è§£èª¬æ
å ±ãåç
§ã§ããã³ã¼ããèªåçæããããã«ãã¾ããã<a href="#f-ef4c1da7" name="fn-ef4c1da7" title="ããã§ã¯è©³ç´°ãçãã¦ãã¾ããããã¢ã¢ããªãå¾ã
å
¬éã§ããã°ã¨æã£ã¦ãã¾ãã">*8</a></p>
<p><strong>ãã°å®ç¾©ãã¨ã®è§£èª¬æ
å ±ã®ç¨æ</strong></p>
<p>ä¾ãã°ãä¸è¨ã®ããã«è§£èª¬æ
å ±ã®ããã®structãå®ç¾©ãã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">ActivityLogDefinition</span><span class="synSpecial">:</span> <span class="synType">Hashable</span> {
<span class="synComment">/// ãã°ã¤ãã³ãã«ãã´ãªï¼ä¾: `sagasu` ï¼</span>
<span class="synPreProc">var</span> <span class="synIdentifier">category</span><span class="synSpecial">:</span> <span class="synType">Category</span>
<span class="synComment">/// ãã°ã¤ãã³ãï¼ä¾ï¼`show_content`ï¼</span>
<span class="synPreProc">var</span> <span class="synIdentifier">event</span><span class="synSpecial">:</span> <span class="synType">Event</span>
<span class="synPreProc">struct</span> <span class="synIdentifier">Event</span><span class="synSpecial">:</span> <span class="synType">Hashable</span> {
<span class="synComment">/// ãã°ã¤ãã³ãåï¼ä¾ï¼`show_content`ï¼</span>
<span class="synPreProc">var</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> <span class="synType">String</span>
<span class="synComment">/// ãã°ã¤ãã³ãã®è§£èª¬æï¼ä¾ï¼`ãããã¿ãã®ã³ã³ãã³ããç»é¢ã«è¡¨ç¤ºãããæã«éä¿¡ããã¾ãã`ï¼</span>
<span class="synPreProc">var</span> <span class="synIdentifier">description</span><span class="synSpecial">:</span> <span class="synType">String</span>
<span class="synComment">/// ãã°ã¤ãã³ãã«ä»å ããããã©ã¡ã¼ã¿ã¼</span>
<span class="synPreProc">var</span> <span class="synIdentifier">parameterNotes</span><span class="synSpecial">:</span> <span class="synSpecial">[</span><span class="synType">ParameterNote</span><span class="synSpecial">]</span>
<span class="synComment">/// åãã°ã«ä»å ããããã©ã¡ã¼ã¿ã¼ã«ã¤ãã¦ã®è§£èª¬</span>
<span class="synPreProc">struct</span> <span class="synIdentifier">ParameterNote</span><span class="synSpecial">:</span> <span class="synType">Hashable</span> {
<span class="synComment">/// ãã©ã¡ã¼ã¿ã¼ã®ãã¼åï¼ä¾ï¼`hashtag_ids`ï¼</span>
<span class="synPreProc">var</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> <span class="synType">String</span>
<span class="synComment">/// ãã©ã¡ã¼ã¿ã¼ã®è§£èª¬æï¼ä¾ï¼`表示ãããããã·ã¥ã¿ã°ID`ï¼</span>
<span class="synPreProc">var</span> <span class="synIdentifier">description</span><span class="synSpecial">:</span> <span class="synType">String</span>
<span class="synComment">/// ãã©ã¡ã¼ã¿ã¼ã®Swiftã§ã®ååï¼ä¾ï¼ `String?` ï¼</span>
<span class="synPreProc">var</span> <span class="synIdentifier">swiftType</span><span class="synSpecial">:</span> <span class="synType">String</span>
}
}
}
</pre>
<p>é©å½ã«Ruby ã¹ã¯ãªãããæ¸ãã¦ã<code>daifuku</code>ã使ã£ã¦ãã°å®ç¾©Markdownã®æ
å ±ãæ±ãããã³ãã¬ã¼ãããã¨ã«ãã°è§£èª¬æ
å ±ã Swift ã® enum ã¨ãã¦èªåçæãã¾ãã</p>
<p>Markdown ãããã°å®ç¾©ã® enum ãçæããRubyã¹ã¯ãªããã®ä¾
<a href="https://github.com/cookpad/daifuku/blob/e3cbfd1066fd7704b8210696aa90d5546ff6857d/example/iOS/generate-log-classes.rb">https://github.com/cookpad/daifuku/blob/e3cbfd1066fd7704b8210696aa90d5546ff6857d/example/iOS/generate-log-classes.rb</a></p>
<div style="border: 1px solid #aaa; border-radius: 4px; padding: 0.5em;">
<details>
<summary>èªåçæç¨ã®ãã³ãã¬ã¼ããã¡ã¤ã« (.erb) ã®ä¾</summary>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">// This file is automatically generated by generate-log-classes.</span>
<span class="synPreProc">extension</span> <span class="synIdentifier">ActivityLogDefinition</span> {
<span class="synPreProc">enum</span> <span class="synIdentifier">Category</span><span class="synSpecial">:</span> <span class="synType">String</span>, Hashable, CaseIterable {
<span class="synIdentifier"><%-</span> categories.each <span class="synStatement">do</span> <span class="synIdentifier">|</span>category<span class="synIdentifier">|</span> <span class="synIdentifier">-%></span>
<span class="synStatement">case</span> <span class="synIdentifier"><%=</span> category.variable_name <span class="synIdentifier">%></span> <span class="synIdentifier">=</span> <span class="synConstant">"<%= category.name %>"</span>
<span class="synIdentifier"><%-</span> end <span class="synIdentifier">-%></span>
}
}
<span class="synPreProc">extension</span> <span class="synIdentifier">ActivityLogDefinition.Category</span> {
<span class="synPreProc">var</span> <span class="synIdentifier">description</span><span class="synSpecial">:</span> <span class="synType">String</span> {
<span class="synStatement">switch</span> <span class="synIdentifier">self</span> {
<span class="synIdentifier"><%-</span> categories.each <span class="synStatement">do</span> <span class="synIdentifier">|</span>category<span class="synIdentifier">|</span> <span class="synIdentifier">-%></span>
<span class="synStatement">case</span> .<span class="synIdentifier"><%=</span> category.variable_name <span class="synIdentifier">%></span><span class="synSpecial">:</span>
<span class="synType">return</span> <span class="synConstant">"""</span>
<span class="synConstant"> <%- category.descriptions.flat_map(&:lines).each do |description_line| -%></span>
<span class="synConstant"> <%= description_line.strip %></span>
<span class="synConstant"> <%- end -%></span>
<span class="synConstant"> """</span>
<span class="synIdentifier"><%-</span> end <span class="synIdentifier">-%></span>
}
}
<span class="synPreProc">var</span> <span class="synIdentifier">events</span><span class="synSpecial">:</span> <span class="synSpecial">[</span><span class="synType">ActivityLogDefinition.Event</span><span class="synSpecial">]</span> {
<span class="synStatement">switch</span> <span class="synIdentifier">self</span> {
<span class="synIdentifier"><%-</span> categories.each <span class="synStatement">do</span> <span class="synIdentifier">|</span>category<span class="synIdentifier">|</span> <span class="synIdentifier">-%></span>
<span class="synStatement">case</span> .<span class="synIdentifier"><%=</span> category.variable_name <span class="synIdentifier">%></span><span class="synSpecial">:</span>
<span class="synIdentifier"><%-</span> <span class="synStatement">if</span> category.available_events.empty? <span class="synIdentifier">-%></span>
<span class="synStatement">return</span> []
<span class="synIdentifier"><%-</span> <span class="synStatement">else</span> <span class="synIdentifier">-%></span>
<span class="synStatement">return</span> [
<span class="synIdentifier"><%-</span> category.available_events.each <span class="synStatement">do</span> <span class="synIdentifier">|</span>event<span class="synIdentifier">|</span> <span class="synIdentifier">-%></span>
.<span class="synIdentifier">init</span>(
name<span class="synSpecial">:</span> <span class="synConstant">"<%= event.name %>"</span>,
description<span class="synSpecial">:</span> <span class="synConstant">"""</span>
<span class="synConstant"> <%- event.descriptions.flat_map(&:lines).each do |description_line| -%></span>
<span class="synConstant"> <%= description_line.strip %></span>
<span class="synConstant"> <%- end -%></span>
<span class="synConstant"> """</span>,
parameterNotes<span class="synSpecial">:</span> <span class="synSpecial">[</span>
<span class="synType"> <%- event.columns.each do |column| -%></span>
<span class="synType"> .init</span><span class="synSpecial">(</span>
<span class="synType"> name: "<%= column.original_name %>"</span>,
<span class="synType"> description: """</span>
<span class="synType"> <%- column.descriptions.flat_map</span><span class="synSpecial">(</span><span class="synType">&:lines</span><span class="synSpecial">)</span><span class="synType">.each do |description_line| -%></span>
<span class="synType"> <%= description_line.strip %></span>
<span class="synType"> <%- end -%></span>
<span class="synType"> """</span>,
<span class="synType"> swiftType: "<%= column.swift_type %>"</span>
<span class="synType"> </span><span class="synSpecial">)</span><span class="synType">,</span>
<span class="synType"> <%- end -%></span>
<span class="synType"> </span><span class="synSpecial">]</span>
),
<span class="synIdentifier"><%-</span> end <span class="synIdentifier">-%></span>
]
<span class="synIdentifier"><%-</span> end <span class="synIdentifier">-%></span>
<span class="synSpecial"><%- </span><span class="synIdentifier">end</span><span class="synSpecial"> -%></span>
}
}
}
</pre>
</details>
</div>
<p>ãããã¦ç¨æãã解説æ
å ±ããéä¿¡æ¸ã¿ãã°ã¨ç´ä»ãã¾ãã</p>
<p><strong>éä¿¡æ¸ã¿ãã°ã¨ã®ç´ä»ã</strong></p>
<p>éä¿¡æ¸ã¿ãã°ã®ä¸èº«ã«å«ã¾ãã <code>eventCategory</code> 㨠<code>eventName</code> ã®å¤ãããã©ã®ãã°å®ç¾©ãã¯ä¸æã«å®ã¾ãã¾ããä¸è¨ã®ããã«ãéä¿¡æ¸ã¿ãã°ã®ãã¤ãã¼ããããã°å®ç¾©è§£èª¬æ
å ±ãåç
§ã§ããããã«ãã¾ããã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">extension</span> <span class="synIdentifier">ActivityLogPayload.DefinitionKey</span> {
<span class="synComment">/// è¡åãã°ã®å®ç¾©ãã¨ã®è§£èª¬æ
å ±</span>
<span class="synPreProc">var</span> <span class="synIdentifier">definition</span><span class="synSpecial">:</span> <span class="synType">ActivityLogDefinition</span> {
<span class="synStatement">guard</span>
<span class="synPreProc">let</span> <span class="synIdentifier">category</span> <span class="synIdentifier">=</span> ActivityLogDefinition.Category(rawValue<span class="synSpecial">:</span> <span class="synType">eventCategory</span>),
<span class="synPreProc">let</span> <span class="synIdentifier">event</span> <span class="synIdentifier">=</span> category.events.first(<span class="synStatement">where</span><span class="synSpecial">:</span> { <span class="synIdentifier">$0</span>.name <span class="synIdentifier">==</span> eventName })
<span class="synStatement">else</span> {
fatalError(<span class="synConstant">"ãã°å®ç¾©ãè¦ã¤ããã¾ããã§ãã"</span>)
}
<span class="synStatement">return</span> ActivityLogDefinition(category<span class="synSpecial">:</span> <span class="synType">category</span>, event<span class="synSpecial">:</span> <span class="synType">event</span>)
}
}
</pre>
<h2 id="3-便å©ãªæ©è½ãè²ã
å®è£
ãã">3. 便å©ãªæ©è½ãè²ã
å®è£
ãã</h2>
<p>ãã¨ã¯ãæ±ããããããéä¿¡æ¸ã¿ãã°ãã¨ã解説æ
å ±ããææã¨ãã¦ãèªç±ã«æçãã¦å¥½ã¿ã®ç»é¢ãã¤ããã ãã§ããããã§ã¯éå¤ã«ããã¤ãã®ãããã¯ããç´¹ä»ãã¾ãã</p>
<h3 id="ãã§ãã¯ãªã¹ãæ©è½">ãã§ãã¯ãªã¹ãæ©è½</h3>
<p>ãã§ãã¯ãªã¹ãæ©è½ã¯ã次ã®ãããªåç´ãªå®è£
ã§ãã</p>
<ul>
<li>ãã§ãã¯ãªã¹ãã«ç»é²ãããã°å®ç¾©ã®ãã¼ã UserDefaults ã§ä¿æãã¦ããã</li>
<li>éä¿¡æ¸ã¿ãã°ã®ä¸ã«ããã®ãã¼ã¨ä¸è´ãããã°ã1ã¤ã§ãããã°ããã§ãã¯ãªã¹ãä¸ã§ãã®ãã°å®ç¾©ããéä¿¡æ¸ã¿ãã«ããã</li>
</ul>
<h3 id="ãããã¯ã¼ã¯éä¿¡ã®ãã°ã«ã対å¿">ãããã¯ã¼ã¯éä¿¡ã®ãã°ã«ã対å¿</h3>
<p>è¡åãã°ã ãã§ãªãããããã¯ã¼ã¯éä¿¡ã®ãã°ãè¦ãããããã«ãã¦ãã¾ããå®éã«ã¢ããªãæä½ããªãããã©ã®APIã¨ã³ããã¤ã³ããã©ã®ã¿ã¤ãã³ã°ã§ä½¿ããã¦ããã®ããããã«ç¢ºèªã§ããã®ã¯ä¾¿å©ã§ãã</p>
<p><figure class="figure-image figure-image-fotolife" title=" Request ã Response ã®è©³ç´°ãè¯ãæãã«è¡¨ç¤º"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231011/20231011172953.png" width="500" height="987" loading="lazy" title="" class="hatena-fotolife" style="width:300px" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231011/20231011131320.gif" width="590" height="1176" loading="lazy" title="" class="hatena-fotolife" style="width:300px" itemprop="image"></span><figcaption> Request ã Response ã®è©³ç´°ãè¯ãæãã«è¡¨ç¤º<a href="#f-4164d41f" name="fn-4164d41f" title="ãã®JSONã®è¡¨ç¤ºé¨åã¯ãååã®Niaããã®
[https://zenn.dev/niaeashes/articles/fca80c3ae3f8b4:title] ã使ããã¦ãããã¾ãããhttps://gist.github.com/niaeashes/e2c927c8d5ddac3b161e2dbe6f0e75b8">*9</a></figcaption></figure></p>
<p><a href="https://www.charlesproxy.com/">Charles</a> ã <a href="https://proxyman.io/">Proxyman</a> ãªã©ã®ãµã¼ããã¼ãã£ã¼ã¢ããªã®ã»ããé«æ©è½ã§ãã網ç¾
æ§ãé«ã<a href="#f-99417883" name="fn-99417883" title="ã¢ããªå
ãã°ç¢ºèªãã¼ã«ã§ã¯ãèªç¤¾ã®APIã¯ã©ã¤ã¢ã³ããçµç±ããéä¿¡ã®ã¿ã«å¯¾å¿ãã¦ãã¾ããä¾ãã°ã Firebase ãªã©ãµã¼ããã¼ãã£ã¼ã©ã¤ãã©ãªã®éä¿¡ã¯å¯¾è±¡å¤ã§ãã">*10</a>ã§ããã常ã«èµ·åãã¦ããã¨ãéããªãã§ããããã使ãããã¨ãã«ã¡ãã£ã¨æéããããã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title=""><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231011/20231011182939.png" width="609" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:300px" itemprop="image"></span><figcaption>é害çºçæã®èª¿æ»ã«ãå½¹ç«ã£ã</figcaption></figure></p>
<p>ä¾ãã°ãç¹å®ã®ç»é¢ãã¨ã©ã¼ã«ãªãã¨ãã£ãé害ãçºçããã¨ããã¢ããªåä½ã§ãç´ æ©ã調æ»ã§ããã®ã¯ä¾¿å©ã§ãããçºçæ¡ä»¶ã®ç¹å®ãåå ã®åãåããã¹ã ã¼ãºã«ã§ããã¨ãããç¦ããã«å¯¾å¿ã§ãã¾ãã</p>
<p>ãªãããããã¯ã¼ã¯éä¿¡ã®ãã°ã«ã¤ãã¦ã¯ OSãã°ã«ã¯éãããã¡ã¢ãªä¸ã«ä¿æãã¦ãã¾ãã <a href="#f-8d0b9558" name="fn-8d0b9558" title="å
ã
ã¯URLã¨ã¹ãã¼ã¿ã¹ã³ã¼ãç¨åº¦ã®ç°¡ç´ ãªæ
å ±ã ãã ã£ãã®ã§OSãã°ã«å
¥ãã¦ãã¾ããããååã®Vincent ãã ã response body ãå«ãã対å¿ããGraphQL ã® POST request ã¸ã®å¯¾å¿ããã¦ããã¾ãããAPIã¯ã©ã¤ã¢ã³ãã« interceptor ã¨ãã¦è¿½å ããä¸å®éã¾ã§ã¡ã¢ãªä¸ã«ä¿æããããã«ãªã£ã¦ãã¾ãã">*11</a></p>
<h4 id="å®è£
ã«é¢ãã¦èª¿æ»ãããããã">å®è£
ã«é¢ãã¦èª¿æ»ãããããã</h4>
<p><figure class="figure-image figure-image-fotolife" title="å®è£
ã®èª¿æ»ãæããªã³ã¯"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231011/20231011181717.png" width="1200" height="853" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ãããã¯ã¼ã¯éä¿¡ã®ãã°ãããAPIä»æ§ã®èª¿æ»ãã½ã¼ã¹ã³ã¼ãæ¤ç´¢ãããã§ãã</figcaption></figure></p>
<p>è¡åãã°ã®éè¨ã»åæã·ã§ã¼ãã«ããã¨åæ§ã«ããããã¯ã¼ã¯éä¿¡ã®ãã°ãè¦ã«ãã人ã¯APIãå®è£
ã«é¢ãã¦è²ã
調æ»ããããã¯ãã ã¨ãããã¨ã§ã以ä¸ã®ç¤¾å
Webãµã¼ãã¹ã¸ã®ãªã³ã¯ãç¨æãã¦ãã¾ãã</p>
<ul>
<li>APIãµã¼ãã¼ã«å¯¾ãã¦å®éã®ãªã¯ã¨ã¹ããæ軽ã«è©¦ãããAPI3 Consoleã</li>
<li>APIãµã¼ãã¼ã®ã¹ãã¼ãå®ç¾©ããããã¥ã¡ã³ããæä¾ãããGarage Playground<a href="#f-b6ce1ca1" name="fn-b6ce1ca1" title="ã¯ãã¯ãããã§ã¯ Garage ã¨å¼ã°ããRESTful Web API éçºã楽ã«ããã©ã¤ãã©ãªãæ¨æºçã«ä½¿ããã¦ãã¾ããhttps://techlife.cookpad.com/search?q=Garage">*12</a>ã</li>
<li>ã½ã¼ã¹ã³ã¼ããã¿ã¹ã¯ãããã¸ã§ã¯ãã®ç®¡çããã¦ãããGitHub Enterpriseã</li>
</ul>
<p>ä¾ãã° ãå®è£
ç®æã GitHub Enterpriseï¼GHEï¼ã§è¡¨ç¤ºããããã¿ã³ã¯ãã¬ãã¸ããªå
ã®Swiftã³ã¼ãã®æ¤ç´¢çµæã®URLãéãã ãã§ãããç§ã§å©ç¨ç®æãè¦ã¤ããã®ã¯æã£ã¦ãã以ä¸ã«ä¾¿å©ã§ãã</p>
<p>å°ãã¿ã§ããã次ã®ãããªå·¥å¤«ãå
¥ãã¦ãã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">// recipeID, userID ãªã©ãå«ãURLã¯ãGHEæ¤ç´¢æã«ãããããä¸ä¾¿ãªã®ã§ * ã«å¤æãã¦ãã</span>
<span class="synComment">// ï¼ä¾: `/v1/recipes/:id`, `/v1/users/:id/visited_recipes` ãªã©ï¼</span>
<span class="synPreProc">let</span> <span class="synIdentifier">query</span> <span class="synIdentifier">=</span> request.url.path.replacingOccurrences(of<span class="synSpecial">:</span> <span class="synConstant">"/([0-9]+)(/|$)"</span>, with<span class="synSpecial">:</span> <span class="synConstant">"/*$2"</span>, options<span class="synSpecial">:</span> .regularExpression)
</pre>
<h4 id="Simulatorã§ã¯URLã³ãã¼ã«">Simulatorã§ã¯URLã³ãã¼ã«</h4>
<p><figure class="figure-image figure-image-fotolife" title="å®æ©ã§ã¯ãã©ã¦ã¶ã§éããSimulatorã§ã¯URLãã¯ãªãããã¼ãã«ã³ãã¼ãã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231011/20231011051235.gif" width="590" height="470" loading="lazy" title="" class="hatena-fotolife" style="width:300px" itemprop="image"></span><figcaption>å®æ©ã§ã¯ãã©ã¦ã¶ã§éããSimulatorã§ã¯URLãã³ãã¼ãã</figcaption></figure></p>
<p>ããã«å°ãã¿ã§ãããiOS Simulatorã§éçºä¸ã«ãã®ãã¿ã³ã使ãã¨ãSimulatorå
ã®Safariãéãã¦ãã¾ãä¸ä¾¿<a href="#f-4ecb99da" name="fn-4ecb99da" title="Simulatorå
ã®Mobile Safariã§ã使ãããã¨ã¯ä½¿ãã¾ããããã°ã¤ã³ãå¿
è¦ã§ããã¼ã¼ã¼ããã¨ãªã£ã¦ãã¾ãã¾ãããmacOSå´ã§éããã»ããå¿«é©ããã§ãã">*13</a>ã ã£ãã®ã§ããããªå¯¾çããã¾ããã
Simulatorå®è¡æ㯠URLãã¯ãªãããã¼ãã«ã³ãã¼ããã®ã§ macOSå´ ã§ããéãã¾ããiPhone/iPadã®å®æ©ã§ã¯å®æ©ã®ãã©ã¦ã¶ãéãã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">// ãªã³ã¯ãã¿ã³ã®å®è£
ä¾</span>
<span class="synPreProc">var</span> <span class="synIdentifier">body</span><span class="synSpecial">:</span> <span class="synType">some</span> View {
<span class="synPreProc"> #if</span> targetEnvironment(simulator)
buttonForSimulator(targetURL)
<span class="synPreProc"> #else</span>
buttonForRealDevice(targetURL)
<span class="synPreProc"> #endif</span>
}
<span class="synStatement">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">buttonForSimulator</span>(_ targetURL<span class="synSpecial">:</span> <span class="synType">URL</span>) <span class="synSpecial">-></span> <span class="synType">some</span> View {
CopyTextButton(
stringToCopy<span class="synSpecial">:</span> <span class="synType">targetURL.absoluteString</span>,
labelTitleForCopied<span class="synSpecial">:</span> <span class="synConstant">"URLãã³ãã¼ãã¾ããï¼Simulatorå
ã®Safariã§éãã¨ä¸ä¾¿ãªã®ã§ï¼"</span>
) {
label
}
}
<span class="synStatement">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">buttonForRealDevice</span>(_ targetURL<span class="synSpecial">:</span> <span class="synType">URL</span>) <span class="synSpecial">-></span> <span class="synType">some</span> View {
Link(destination<span class="synSpecial">:</span> <span class="synType">targetURL</span>) {
label
}
.contextMenu { <span class="synComment">// å®æ©ã§ãé·æ¼ãã¡ãã¥ã¼ããä¸å¿ã³ãã¼ã§ããããã«ãã¦ãã</span>
CopyTextButton(stringToCopy<span class="synSpecial">:</span> <span class="synType">targetURL.absoluteString</span>) {
Label(<span class="synConstant">"URLãã³ãã¼"</span>, systemImage<span class="synSpecial">:</span> <span class="synConstant">"doc.on.doc"</span>)
}
}
}
</pre>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">import</span> SwiftUI
<span class="synPreProc">struct</span> <span class="synIdentifier">CopyTextButton</span><span class="synSpecial"><</span><span class="synIdentifier">Content</span><span class="synSpecial">: </span><span class="synType">View</span><span class="synSpecial">>:</span> <span class="synType">View</span> {
<span class="synPreProc">var</span> <span class="synIdentifier">stringToCopy</span><span class="synSpecial">:</span> <span class="synType">String</span>
<span class="synPreProc">var</span> <span class="synIdentifier">labelTitleForCopied</span><span class="synSpecial">:</span> <span class="synType">String</span>
<span class="synType">@ViewBuilder</span> <span class="synType">var</span> content<span class="synSpecial">:</span> <span class="synType">Content</span>
<span class="synIdentifier">init</span>(stringToCopy<span class="synSpecial">:</span> <span class="synType">String</span>, labelTitleForCopied<span class="synSpecial">:</span> <span class="synType">String</span> <span class="synIdentifier">=</span> <span class="synConstant">"ã³ãã¼ãã¾ããï¼"</span>, <span class="synType">@ViewBuilder</span> <span class="synType">content</span><span class="synSpecial">: ()</span> <span class="synSpecial">-></span> <span class="synType">Content</span>) {
<span class="synIdentifier">self</span>.stringToCopy <span class="synIdentifier">=</span> stringToCopy
<span class="synIdentifier">self</span>.labelTitleForCopied <span class="synIdentifier">=</span> labelTitleForCopied
<span class="synIdentifier">self</span>.content <span class="synIdentifier">=</span> content()
}
<span class="synType">@State</span> <span class="synType">private</span> <span class="synPreProc">var</span> <span class="synIdentifier">isCopied</span> <span class="synIdentifier">=</span> <span class="synConstant">false</span>
<span class="synPreProc">var</span> <span class="synIdentifier">body</span><span class="synSpecial">:</span> <span class="synType">some</span> View {
Button {
UIPasteboard.general.string <span class="synIdentifier">=</span> stringToCopy
print(<span class="synConstant">"[LogChecker] Copied to the pasteboard: </span><span class="synSpecial">\(</span>stringToCopy<span class="synSpecial">)</span><span class="synConstant">"</span>)
Task {
<span class="synStatement">defer</span> { isCopied <span class="synIdentifier">=</span> <span class="synConstant">false</span> }
isCopied <span class="synIdentifier">=</span> <span class="synConstant">true</span>
<span class="synStatement">try</span>? await Task.sleep(<span class="synStatement">for</span><span class="synSpecial">:</span> .seconds(<span class="synConstant">3</span>))
}
} label<span class="synSpecial">:</span> {
<span class="synStatement">if</span> isCopied {
Label(labelTitleForCopied, systemImage<span class="synSpecial">:</span> <span class="synConstant">"doc.on.doc"</span>)
.font(.callout)
.foregroundColor(.secondary)
.imageScale(.small)
} <span class="synStatement">else</span> {
content
}
}
}
}
</pre>
<h1 id="æ¯ãè¿ã£ã¦">æ¯ãè¿ã£ã¦</h1>
<h2 id="ããã£ããã¨">ããã£ããã¨</h2>
<p>ãã®ãã°ç¢ºèªãã¼ã«ã¯ãè²ããªé¢ã§éçºã楽ããã§ãã¾ããã</p>
<h3 id="ãã°ã®å®è£
確èªãã¤ãããªããªã">ãã°ã®å®è£
ã»ç¢ºèªãã¤ãããªããªã</h3>
<p>ã大äºã ãã©æ£ç´é¢åãªä½æ¥ãã¨ãæãã¦ãããã°ã®å®è£
ã確èªãå¹¾åãå¿«é©ã«ã§ããã¨æãã¾ããå人çã«ã¯ã¢ããªå
ãã°ç¢ºèªãã¼ã«ã使ãã¯ããã¦ããã¯ãã¡ãã£ã¨æ¥½ããã¾ã§ãããã¨ããæ°æã¡ã«å¤åãã¦ãã¾ãããååãããSlackãªã©ã§ããããè¦ããããªã£ã¦ãï¼ããã¯ã¡ããã¡ãã«å©ãã£ã¦ãããã課éããããã¨ãã£ããã¸ãã£ããªåå¿ããããã¦ãã¾ãã</p>
<h3 id="èªç±ã«å®é¨ã§ããç°å¢ã§éã¹ã">èªç±ã«å®é¨ã§ããç°å¢ã§éã¹ã</h3>
<p>Viewã«ã¤ãã¦ã¯ããã¹ã¦SwiftUIã§å®è£
ãã¾ããã</p>
<p>æ®æ®µãä¸è¬ã¦ã¼ã¶ã¼åãã«éçºãã¦ããç»é¢ã¯ SwiftUI ï¼å ´åã«ãã£ã¦ã¯ UIKitï¼ãæ¡ç¨ãã¦ãã¾ãããå
¨ä½çã«ã¯ VIPER ã¢ã¼ããã¯ãã£ã§ãç»é¢é·ç§»ã UINavigationController ããã¼ã¹ã«ä½¿ã£ã¦ãã¾ãã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2021%2F11%2F01%2F090000" title="UINavigationControllerãã«ã¹ã¿ãã¤ãº ãOSã®å½±é¿ãåãã¥ããã«ã¹ã¿ã ããã²ã¼ã·ã§ã³ã®å®è£
ã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techlife.cookpad.com/entry/2021/11/01/090000">techlife.cookpad.com</a></cite></p>
<p>ä»åã¯éçºè
ç¨ãã¼ã«ã¨ãããã¨ããã£ã¦ããµãã¼ãOSãã¼ã¸ã§ã³ãä¸å
·åãªã©ã¯ããã¾ã§æ°ã«ããªãã¦ãæ¸ãç¶æ³ã§ãããããããããããæ©ä¼ã«ç©æ¥µçã«æ°ããæè¡ã試ãã¦ãç¥è¦ã貯ããã»ããæã¾ããã§ãããã</p>
<p>ãã¼ã ã§åæãã¨ãããã°ç¢ºèªãã¼ã«ã«é¢ãã¦ã¯ <code>@available(iOS 16.0, *)</code> ï¼ä»ãªã iOS 17ï¼ãã¤ãã¦ãææ°ã®SwiftãSwiftUIã®æ©è½ã使ãæ¾é¡ã«ãã¾ãããå¶ç´ãªãæè¡ã楽ãããã¨ã³ã¸ãã¢ã«ã¨ã£ã¦ã®ãªã¢ã·ã¹ã®ãããªå ´æã§ãã</p>
<p>ä¾ãã° <code>NavigationStack</code> ã <code>NavigationSplitView</code> ãªã©ãæ®æ®µä½¿ã£ã¦ããªãSwiftUIã®ç»é¢é·ç§»é¢é£ã試ãã¦ãã¾ãã
æ£è¦è¡¨ç¾ã使ãç®æã§ã¯ã<code>RegexBuilder</code> ã試ãã¾ããã</p>
<p>ã<code>ViewThatFits</code>ã使ãã°ãç°¡åã«è§£æ±ºã§ãã¦ããã便å©ã ãããã®æ¸ãæ¹ãiOS 17ãã deprecated ã«ãªãã®ãï¼ãããã便å©ã ãã©ããã®æåã¯æ°ãä»ããªãã¨ä¸å
·åãçã¿åºãããã â¦â¦ã</p>
<p>ãã®ããã«èªç±ã«å®è·µãã¦å¾ãããç¥è¦ãèæè¦ã¯ããã 楽ããã ãã§ã¯ãªããè¿ãå°æ¥ã®ã¦ã¼ã¶ã¼åãæ©è½ã®éçºãã¹ã ã¼ãºã«ãã¦ãã¨ã¦ãå½¹ç«ã¡ã¾ãã</p>
<h3 id="欲ãããã®ãä½ããã¨æ¥½ãã">欲ãããã®ãä½ããã¨æ¥½ãã</h3>
<p>ãã£ãããããªã次ã
ã¨å®ç¾ããã®ãç´ç²ã«æ¥½ããã£ãã§ãã <a href="#f-17ee0445" name="fn-17ee0445" title="Cookpad TechConf 2022ã®LTã§ãããã¡ããã¡ã楽ããã£ãä»äºã®è©±ãããã¦ã»ãããiOSã¢ããªã®ãã°ç·¨ããã¨ãã¦çºè¡¨ãã¦ãã¾ããåç»: https://youtu.be/2HitJxXXzwY?t=1325 ">*14</a></p>
<p><figure class="figure-image figure-image-fotolife" title="æ¹åã®ç¡éã«ã¼ããçéã§é²ãã®ã¯æ¥½ãã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_f/20231013/20231013155021.png" width="1200" height="825" loading="lazy" title="" class="hatena-fotolife" style="width:300px" itemprop="image"></span><figcaption>æ¹åã®ç¡éã«ã¼ããçéã§é²ãã®ã¯æ¥½ãã</figcaption></figure></p>
<p>éçºè
åããã¼ã«ã¯ã¦ã¼ã¶ã¼ãèªåã§ãããã®ã§ããã¼ãºã®ç解ããçã«è§£æ±ºã§ãã¦ããã®ãã®å®æãããã§ãã¾ãã
ä½ã£ã¦ã¿ã¦ã試ãã«ä½¿ã£ã¦ãæ°ããªçºè¦ããã£ã¦ã¾ãä½ãããã®æ¹åãçéã§é²ãããã¾ãã</p>
<p>æ¥åã«ãã£ããå½¹ç«ã¤ãä»äºãã§ã¯ãããã®ã®ã楽ããã¦ã¤ããã£ã¦ãã¾ãã趣å³ãã§ããããã趣å³ã®ä»äºãã¨ããè¨èã社å
ã§æµè¡ãã¦ãã¾ããããã®ã¢ããªå
ãã°ç¢ºèªãã¼ã«ã趣å³ã®ä»äºã®ä¸ä¾ã§ãã</p>
<h2 id="æ¹åããããã¨">æ¹åããããã¨</h2>
<h3 id="ãã°ã®èªã¿è¾¼ã¿ãéãããã">ãã°ã®èªã¿è¾¼ã¿ãéãããã</h3>
<p>OSãã°ã¯æ¸ãè¾¼ã¿ã¯è¯ãã§ãããèªã¿è¾¼ã¿ã¯é
ãããã§ãã
<code>os.signpost</code> 㨠Instruments ã使ã£ã¦è¨æ¸¬ãã¦ã¿ãã¨ã<code>.getEntries</code><a href="#f-77c1a087" name="fn-77c1a087" title="https://developer.apple.com/documentation/oslog/oslogstore/3204125-getentries">*15</a> ã®1è¡ã ãã§å§åçã«æéãããã£ã¦ãã¾ãã</p>
<p>åä½ç°å¢ã«ãã£ã¦å¤§ããå·®ããããæ°ã«ãªããªãç¨åº¦ã®ã¨ããããã°10ç§è¿ãããã£ã¦ãããã«ä½¿ãã¥ããã¨æããã¨ããããã¾ããSimulatorã¨å®æ©ã®å·®ãOSãã°ã«æºã¾ã£ãéã®å·®ãªã©ããã¤ãè¦å ããããããªæ°ããã¤ã¤ã詳ããã¯ã¾ã 調ã¹ããã¦ãã¾ããã</p>
<p>ç»é¢è¡¨ç¤ºæ¯ã«æ´æ°ããã¨èªã¿è¾¼ã¿ã§å¾
ã¡ãããã®ã§ãä»ã¯ãã£ãã·ã¥å±¤ãæãã§æ´æ°é »åº¦ãä¸ãã¦ãã¾ãã</p>
<h2 id="ã¾ã¨ã">ã¾ã¨ã</h2>
<p>ä»åã¯ãã¢ããªå
ãã°ç¢ºèªãã¼ã«ã®æ©è½ãå®è£
æ¹æ³ãåãã£ããã¨ã«ã¤ãã¦ãç´¹ä»ãã¾ããã</p>
<p>ã¾ã æ¹åã®ä½å°ã¯ããã¾ãããã¡ãã£ã¨ãã工夫ã®ç©ã¿éãã«ãã£ã¦éçºãå¿«é©ã«ããç®çã¯ä¸å®éæã§ããã¨æãã¦ãã¾ãã</p>
<p>æ¥ã
ã®ãµã¼ãã¹éçºãããè¯ãããããã«ããã®è¨äºãä½ãå°ãã§ãåèã«ãªã£ãã幸ãã§ãã</p>
<div class="footnote">
<p class="footnote"><a href="#fn-899f9b8c" name="f-899f9b8c" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãã®ãã¼ã«ã¯éçºçãã«ãã®ã¿ã«å«ã¾ãã¦ãããApp Storeçã§ã¯å©ç¨ã§ãã¾ããã</span></p>
<p class="footnote"><a href="#fn-315eaa85" name="f-315eaa85" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">SwiftUIã§ä½ãããç»é¢ãªã®ã§ .blur(radius:) ãé©å½ã«ã¤ããã ãã§ç°¡åã«ã¼ããã¦ãããããGIFãã¤ããã¨ãã«ä¾¿å©ã§ãã</span></p>
<p class="footnote"><a href="#fn-2a16df53" name="f-2a16df53" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãªããXcode 15 ã§ã¯ Debug Console ãå¼·åãããéè¦åº¦ããã°ã®ç¨®é¡ãã¨ã«ãã£ã«ã¿ãªã³ã°ã§ãããªã©ä¾¿å©ã«ãªãã¾ããã<a href="https://developer.apple.com/videos/play/wwdc2023/10226/">https://developer.apple.com/videos/play/wwdc2023/10226/</a> </span></p>
<p class="footnote"><a href="#fn-b74e4bdd" name="f-b74e4bdd" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">ããã§ã®ãéä¿¡æ¸ã¿ãã¨ã¯ãã¢ããªã®èµ·åããçµäºã¾ã§ã®éã§ã®è©±ã§ããã¢ããªãåèµ·åããã¨ããã¹ã¦ãæªéä¿¡ãã«æ»ãã¾ãã</span></p>
<p class="footnote"><a href="#fn-158f8cb4" name="f-158f8cb4" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://developer.apple.com/videos/play/wwdc2020/10168/">https://developer.apple.com/videos/play/wwdc2020/10168/</a> ãã</span></p>
<p class="footnote"><a href="#fn-73cb7b3b" name="f-73cb7b3b" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://developer.apple.com/documentation/oslog/oslogentrywithpayload/3366053-category">https://developer.apple.com/documentation/oslog/oslogentrywithpayload/3366053-category</a></span></p>
<p class="footnote"><a href="#fn-6841f8d7" name="f-6841f8d7" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text">ã¡ãªã¿ã« daifuku ã®ç±æ¥ã¯ã大ç¦å¸³ããããã¯ãã¯ãããã§ã¯ã2020å¹´é ã«æ°ãããã°ã®ä»çµã¿ãé称ã大統ä¸ã¢ã¯ãã£ããã£ãã°ããå°å
¥ããéã«ããã¹ã¦ã®ã«ã©ã ã1ã¤ã®ãã¼ãã«ã«æ¨ªé·ã«åå¨ããéæ£è¦åãããã大ç¦å¸³åãã¼ãã«ãã«è¡åãã°ãéç©ããããã«ãªãã¾ããã<a href="https://techlife.cookpad.com/entry/2020/12/29/004145">https://techlife.cookpad.com/entry/2020/12/29/004145</a> </span></p>
<p class="footnote"><a href="#fn-ef4c1da7" name="f-ef4c1da7" class="footnote-number">*8</a><span class="footnote-delimiter">:</span><span class="footnote-text">ããã§ã¯è©³ç´°ãçãã¦ãã¾ããããã¢ã¢ããªãå¾ã
å
¬éã§ããã°ã¨æã£ã¦ãã¾ãã</span></p>
<p class="footnote"><a href="#fn-4164d41f" name="f-4164d41f" class="footnote-number">*9</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãã®JSONã®è¡¨ç¤ºé¨åã¯ãååã®Niaããã®
<a href="https://zenn.dev/niaeashes/articles/fca80c3ae3f8b4">SwiftUI で JSON を表示する View</a> ã使ããã¦ãããã¾ããã<a href="https://gist.github.com/niaeashes/e2c927c8d5ddac3b161e2dbe6f0e75b8">https://gist.github.com/niaeashes/e2c927c8d5ddac3b161e2dbe6f0e75b8</a></span></p>
<p class="footnote"><a href="#fn-99417883" name="f-99417883" class="footnote-number">*10</a><span class="footnote-delimiter">:</span><span class="footnote-text">ã¢ããªå
ãã°ç¢ºèªãã¼ã«ã§ã¯ãèªç¤¾ã®APIã¯ã©ã¤ã¢ã³ããçµç±ããéä¿¡ã®ã¿ã«å¯¾å¿ãã¦ãã¾ããä¾ãã°ã Firebase ãªã©ãµã¼ããã¼ãã£ã¼ã©ã¤ãã©ãªã®éä¿¡ã¯å¯¾è±¡å¤ã§ãã</span></p>
<p class="footnote"><a href="#fn-8d0b9558" name="f-8d0b9558" class="footnote-number">*11</a><span class="footnote-delimiter">:</span><span class="footnote-text">å
ã
ã¯URLã¨ã¹ãã¼ã¿ã¹ã³ã¼ãç¨åº¦ã®ç°¡ç´ ãªæ
å ±ã ãã ã£ãã®ã§OSãã°ã«å
¥ãã¦ãã¾ããããååã®<a href="https://twitter.com/vincentisambart">Vincent ãã</a> ã response body ãå«ãã対å¿ããGraphQL ã® POST request ã¸ã®å¯¾å¿ããã¦ããã¾ãããAPIã¯ã©ã¤ã¢ã³ãã« interceptor ã¨ãã¦è¿½å ããä¸å®éã¾ã§ã¡ã¢ãªä¸ã«ä¿æããããã«ãªã£ã¦ãã¾ãã</span></p>
<p class="footnote"><a href="#fn-b6ce1ca1" name="f-b6ce1ca1" class="footnote-number">*12</a><span class="footnote-delimiter">:</span><span class="footnote-text">ã¯ãã¯ãããã§ã¯ Garage ã¨å¼ã°ããRESTful Web API éçºã楽ã«ããã©ã¤ãã©ãªãæ¨æºçã«ä½¿ããã¦ãã¾ãã<a href="https://techlife.cookpad.com/search?q=Garage">https://techlife.cookpad.com/search?q=Garage</a></span></p>
<p class="footnote"><a href="#fn-4ecb99da" name="f-4ecb99da" class="footnote-number">*13</a><span class="footnote-delimiter">:</span><span class="footnote-text">Simulatorå
ã®Mobile Safariã§ã使ãããã¨ã¯ä½¿ãã¾ããããã°ã¤ã³ãå¿
è¦ã§ããã¼ã¼ã¼ããã¨ãªã£ã¦ãã¾ãã¾ãããmacOSå´ã§éããã»ããå¿«é©ããã§ãã</span></p>
<p class="footnote"><a href="#fn-17ee0445" name="f-17ee0445" class="footnote-number">*14</a><span class="footnote-delimiter">:</span><span class="footnote-text">Cookpad TechConf 2022ã®LTã§ãããã¡ããã¡ã楽ããã£ãä»äºã®è©±ãããã¦ã»ãããiOSã¢ããªã®ãã°ç·¨ããã¨ãã¦çºè¡¨ãã¦ãã¾ããåç»: <a href="https://youtu.be/2HitJxXXzwY?t=1325">https://youtu.be/2HitJxXXzwY?t=1325</a> </span></p>
<p class="footnote"><a href="#fn-77c1a087" name="f-77c1a087" class="footnote-number">*15</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://developer.apple.com/documentation/oslog/oslogstore/3204125-getentries">https://developer.apple.com/documentation/oslog/oslogstore/3204125-getentries</a></span></p>
</div>
y_f
ã¯ãã¯ãããã®æ¤ç´¢åæ æéã 1/288 ã«ããã·ã¹ãã æ¹ä¿®
hatenablog://entry/820878482973215525
2023-10-05T15:00:00+09:00
2023-10-05T15:00:00+09:00 ã¬ã·ããæ稿ãã¦ããæ¤ç´¢çµæã«åæ ãããã¾ã§ã®æéãã24 æéãã 5 åã«ã¾ã§ç縮ããã·ã¹ãã æ¹ä¿®ã«ã¤ãã¦ç´¹ä»ãã¾ãã
<p>ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ã®æ°äºï¼<a href="https://twitter.com/SpicyCoffee66">@SpicyCoffee</a>ï¼ã§ãã</p>
<p>ã¯ãã¯ãããã§ã¯ããã¾ã§ãã¬ã·ããæ稿ãã¦ããæ¤ç´¢çµæã«åæ ãããã¾ã§æé·ã§ 24 æéç¨åº¦ã®æéãããã£ã¦ãã¾ãããä»åããã®æéã 5 åç¨åº¦ãæé·ã§ã 10 åç¨åº¦ã«ç縮ãããã¨ã«æåãã¾ãããæ¬è¨äºã§ã¯ãããã¸ã§ã¯ããªã¼ãã¼ã®ç«å ´ã§é¢ãã£ãç§ã代表ãã¦ãã®éçºã«ã¤ãã¦ç´¹ä»ãã¾ãã</p>
<h1 id="ããã¸ã§ã¯ãã®ç®çã¨æ°å¤ç®æ¨">ããã¸ã§ã¯ãã®ç®çã¨æ°å¤ç®æ¨</h1>
<p>æ¬ããã¸ã§ã¯ãã§ã¯ä¸è¨ã®ãã¬ã·ããæ稿ãã¦ããæ¤ç´¢çµæã«åæ ãããã¾ã§ã®æéç縮ããç®çã¨ããã¾ãããããããæéç縮ã¨ãã£ã¦ãç¾ç¶ 24 æéã§ãããã®ã "1 æé" ã«ããã®ãã"1 å" ã«ããã®ãã"1 ç§" ã«ããã®ãã§ã¯è©±ãå
¨ç¶éãã¾ãããã®æ°å¤ç®æ¨ã¯è¨è¨ãå§ãã¨ããå¾ã®ææ決å®ã«å¤§ããå½±é¿ãä¸ããããããã£ããã¨ããæå³ãæã£ãç¶æ
ã§æ確ã«å®ãã¦ããå¿
è¦ãããã¾ããã</p>
<p>ããã§ãç§ã¨ãããã¯ããªã¼ãã¼<a href="#f-9b11b8ee" name="fn-9b11b8ee" title="ä»å㯠CEO ããã®å½¹å²ãæ
ã£ã¦ãã¾ããã社é·ã¨ç´æ¥ä»äºãããæ©ä¼ãéã£ã¦ãã¦ã©ããã¼ã">*1</a>ãè°è«ãéããã¾ã㯠âä»åã®ããã¸ã§ã¯ãã§å®ç¾ãããã¦ã¼ã¶ã¼ä½é¨" ãå®ãã¾ããããã®ä½é¨ããå¿
è¦ã¨ãªãæ¤ç´¢çµæã®åæ é »åº¦ãéç®ããæçµçãªæ°å¤ç®æ¨ããä¸å¤®å¤ 5 åç¨åº¦ãæ大ã§ã 10 å以å
ã®æ¤ç´¢çµæã¸ã®åæ ãã§ããã¨å®ãããã¨ã¨ãªãã¾ãããåæã«å®ããããã¸ã§ã¯ãã®ã¹ã±ã¸ã¥ã¼ã«ã¯ 6 é±éã§ãããè¦ç©ããã®ç¬¬ä¸å°è±¡ã¨ãã¦ã¯ããªãã®ãªã®ãªã®è¨å®ã§ããã</p>
<p>ãã®è¨äºã§ã¯ãä»å¾æ¬ããã¸ã§ã¯ãã§å®ç¾ããããæ¤ç´¢çµæãåæ ãããã¾ã§ã®æéã®ç縮ãã âshort-period indexingâ ã¨å¼ç§°ãããã¨ã«ãã¾ãã</p>
<h1 id="æ§ã·ã¹ãã ã®æ¦è¦">æ§ã·ã¹ãã ã®æ¦è¦</h1>
<p>ããã¸ã§ã¯ãçºè¶³æç¹ã§ã®æ¤ç´¢å¨ãã®ã·ã¹ãã (以ä¸æ§ã·ã¹ãã )ã以ä¸ã«ç¤ºãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="æ§ã·ã¹ãã ã®æ§æ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/spicycoffee/20231005/20231005140329.png" width="1200" height="665" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>æ§ã·ã¹ãã ã®æ§æ</figcaption></figure></p>
<p>æ§ã·ã¹ãã ã®èã¯ä»¥ä¸ã®ï¼ç¹ã§ãã</p>
<h2 id="æ¤ç´¢ã¤ã³ããã¯ã¹ãçæããæ¥æ¬¡ããã">æ¤ç´¢ã¤ã³ããã¯ã¹ãçæããæ¥æ¬¡ããã</h2>
<p>æ§ã·ã¹ãã ã§ã¯ãæ¤ç´¢çµæã®æ´æ°ã 24 æéã«ä¸åº¦ã§ããã¨å²ãåããæ¥æ¬¡ãããã§ã¤ã³ããã¯ã¹ã®æ´æ°ãè¡ã£ã¦ãã¾ãããã¬ã·ãã«é¢ããå種ã¡ã¿ãã¼ã¿ãéããå¿
è¦ã«å¿ãã¦å å·¥ãããã¨ã§ããã¥ã¡ã³ããçæãããã®ããã¥ã¡ã³ãã Solr ã«éä¿¡ãããã¨ã§ã¤ã³ããã¯ã¹ãçæãã¾ããçæãããã¤ã³ããã¯ã¹ã¯å¾ã»ã©èª¬æãã ECS ãå©ç¨ãããããã¤ã¡ã³ãã®ããã« S3 ã«é
ç½®ããã¾ãã</p>
<p>æ¥æ¬¡æ´æ°ã§ããã¨ããå²ãåãã®å
ã«ãããã 100 ãè¶
ãã field ã®æ
å ±ãæ°ç¾ä¸ã¬ã·ãã«ã¤ãã¦æ¯æ¥çæãã¦ãããä¸ã«ã¯æ©æ¢°å¦ç¿ãç¨ãã¦ã¬ã·ãã«ã¹ã³ã¢ãä»ä¸ãããããªå¦çãå«ã¾ãã¦ããããããã®å®è¡æé㯠90 åç¨åº¦ã«ãªã£ã¦ãã¾ããã</p>
<p>ã¡ãªã¿ã«ããã®ãããèªä½ã 5 å¹´ã»ã©åã«æ§ã·ã¹ãã ããåé¢ã»ãªãã¬ã¤ã¹ããããã®ã«ãªãã¾ããå½æã®æ§åã¯ä»¥ä¸ã®è¨äºã«è¨è¼ãã¦ããããããããããã°ãããã¦ã覧ãã ããã
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2019%2F06%2F17%2F123000" title="ã¬ã·ãæ¤ç´¢ãæ¯ããã¬ã¬ã·ã¼ã§ã¯ãªãã£ã«ã«ãªå¤§è¦æ¨¡ããããå·æ°ãã話 - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<h2 id="ECS-ãå©ç¨ãããããã¤ã¡ã³ã">ECS ãå©ç¨ãããããã¤ã¡ã³ã</h2>
<p>æ§ã·ã¹ãã ã§ã¯ãECS ã®ã¿ã¹ã¯ã¨ã㦠Solr ãèµ·åãã¦ãã¾ãããä¸è¬ã«ãæ¤ç´¢ã¨ã³ã¸ã³ã®ãããªã¹ãã¼ããã«ãªããã«ã¦ã§ã¢ã¨ ECS ã¯ç¸æ§ããããªãã¨ããã¦ãã¾ããããããæ§ã·ã¹ãã ã§ã¯ S3 ã«ã¤ã³ããã¯ã¹ãé
ç½®ãã¦ã¿ã¹ã¯ã®èµ·åæã«ããããã¦ã³ãã¼ããã¦ãããã¨ã§ã¹ãã¼ããã³ã³ããã®å¤ã«åºãããã®ç¸æ§ã®æªãã解æ¶ãã¦ãã¾ãã</p>
<p>ãã®è¨è¨ã¯ãã¹ãã¼ã(= ã¤ã³ããã¯ã¹)ã®æ´æ°é »åº¦ãååã«ä½ããã¨ããåæã«åºã¥ãã¦ãããã®ã§ãããæ¬ããã¸ã§ã¯ãã®ç®çãéæããããã«ã¯ãªãã¬ã¤ã¹ããå¿
è¦æ§ãåºã¦ããå¯è½æ§ãããç®æã§ããã</p>
<p>ãã®éçºã«ã¤ãã¦ã®è©³ç´°ã¯ä»¥ä¸ã®è¨äºã§è§£èª¬ããã¦ãã¾ãã®ã§ããããããã°ãã¡ããã覧ãã ããã
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2020%2F11%2F25%2F080000" title="人æ°é æ¤ç´¢ã®Solrã¯ã¹ã±ã¼ã«ã®ããã«ãã£ã¹ã¯ãæ¨ã¦ã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<h1 id="ç®æ¨éæã®ããã®èª²é¡">ç®æ¨éæã®ããã®èª²é¡</h1>
<p>æ§ã·ã¹ãã ãèå¯ãããã¨ã§ãç®æ¨ãéæããããã«ã¯ä»¥ä¸ã®ãããªèª²é¡ããããã¨ãããã£ã¦ãã¾ããã</p>
<h2 id="short-period-indexing-ã«é©ãã-Solr-ã®ä½¿ãæ¹ãåèãã">short-period indexing ã«é©ãã Solr ã®ä½¿ãæ¹ãåèãã</h2>
<p>æ§ã·ã¹ãã ã§ã¯ã常å¨çã«èµ·åãã¦ãã Solr 㯠"åç
§ç³»" ã®ã¿ã§ãããindex ã®æ´æ°æã«ã¯ spot instance ã¨ã㦠"æ´æ°ç³»" ã® Solr ãç«ã¡ä¸ã㦠index ãçæãã¦ãã¾ãããindex ã®æ´æ°ãæ¥æ¬¡ã§ããã°ãã®æ¹æ³ã§ãåé¡ããã¾ãããããããæ°å以ä¸ã®ãªã¼ãã¼ã«ãªããªã "æ´æ°ç³»" ã® Solr ã常ã«ç¨¼åãããã¤è¤æ°ã® "åç
§ç³»" Solr ã«æ´æ°ãåæããå¿
è¦ãããããã§ãã</p>
<p>æ´æ°ã®åææ¹æ³ã«ã¤ãã¦ã¯ããã¨ãã°ãSolr ã«ã¯ã¯ã©ã¹ã¿ãçµã㧠replication ãå®è¡ããããã®æ©è½ãããã¾ãããããããã®æ©è½ã ECS ã« Solr ãä¹ãã¦ããç¶æ
ã§ãåé¡ãªãåä½ãããã¯èªæã§ã¯ããã¾ãããECS ãæ´»ç¨ãããã¨ã«ãããããã¤ãã¹ã±ã¼ãªã³ã°ã®å®¹ææ§ã¨ãã£ãã¡ãªããã¯å¯è½ãªéãæ®ããã<a href="#f-f14419c3" name="fn-f14419c3" title="å½æã® ECS &amp; 社å
åºç¤ Hako ã¨ããæ§æã¯éç¨è² è·ãä½ãä¸ã«é常ã«å®å®ãã¦ãããSolr ãç´æ¥ã®çç±ã¨ãªã£ã¦é害ãèµ·ããã®ã¯å¹´ã« 1 度ããªãããã«è¨æ¶ãã¦ãã¾ãã">*2</a>ãã®ã®ããã®ããã«ã¯æ°ããè¦ä»¶ã«åããã調æ»ã工夫ãå¿
è¦ã«ãªãããã§ãã</p>
<p>ãããã S3 ãä»ãã¦ã¤ã³ããã¯ã¹ãé
å¸ããããæ¹ãé©ãã¦ããããå«ã Solr å¨ãã®æ§æã»è¨è¨ã¯å¤§å¹
ã«èãç´ãå¿
è¦ãããããã§ããã</p>
<h2 id="ã¤ã³ããã¯ã¹ããæ
å ±ãé¸å¥ãã">ã¤ã³ããã¯ã¹ããæ
å ±ãé¸å¥ãã</h2>
<p>åè¿°ããããã«ãã¬ã·ãã®ããã¥ã¡ã³ã㯠100 åå¾ã® field ãæã£ã¦ãããä¸ã«ã¯æ©æ¢°å¦ç¿ãç¨ãã¦ä»ä¸ãããã¹ã³ã¢ã®ãããªãã®ãå«ã¾ãã¾ãããããå
¨ã¦ã®æ
å ±ãã¤ã³ããã¯ã¹ãããã¨ããã¨ããããããã®å¦çã«æéããããå¯è½æ§ãé«ããshort-period indexing ã®ã¿ã¤ã ã¹ãã³ã§ãããå®è¡ãããã¨ã¯å°é£ã ã¨èãããã¾ãããããã£ã¦ãã¦ã¼ã¶ã¼ä½é¨ã«ç«ã¡è¿ã£ã¦ short-period indexing ã®ã¹ã³ã¼ãã«å«ãã field ãå®ç¾©ããå¿
è¦ãããã¾ããã</p>
<p>ã¾ããã¯ãã¯ãããã®ã¬ã·ãã¯ã¦ã¼ã¶ã¼æ稿ç©ã§ãããããã£ã¦ãä½ã®ãã§ãã¯ãããã«ã¬ã·ããã¤ã³ããã¯ã¹ãã¦ãã¾ãã¨ãæããã«æçã§ã¯ãªãåçãç¨ããã¬ã·ããªã©ã®ãä¸é©åãªæ稿ã®é²åºãå¢ãã¦ãã¾ãå¯è½æ§ãããã¾ãããã®ãã¨ãèããã¨ãã¤ã³ããã¯ã¹ããæ
å ±ã«å ãã¦ãã©ã®ã¬ã·ããã¤ã³ããã¯ã¹ããããã¨ããå¤å®ãå¿
è¦ã«ãªãã¨äºæ³ããã¾ãã<a href="#f-f1ecce9b" name="fn-f1ecce9b" title="人æã«ããã¬ã·ãã®å
¨ä»¶ãã§ãã¯ã¯ short-period indexing 以åãè¡ããã¦ãããããããªãã¬ã¼ã·ã§ã³ã®è¦ç´ããå«ããã¬ã·ããã§ãã¯å¨ãã§ãã·ã¹ãã å¤æ´ãå¿
è¦ã«ãªãã¨äºæ³ããããã¨ãã表ç¾ã®æ¹ãæ£ç¢ºããããã¾ããã">*3</a>ã</p>
<h2 id="æ¥æ¬¡ãããã«ããæ´æ°ã¨-short-period-indexing-ã«ããæ´æ°ãåå±
ããã">æ¥æ¬¡ãããã«ããæ´æ°ã¨ short-period indexing ã«ããæ´æ°ãåå±
ããã</h2>
<p>æ¥æ¬¡ãããã«ããã¤ã³ããã¯ã¹æ´æ°ã¯ãæ´æ°é »åº¦ã¨å¼ãæãã§ã¯ããã¾ããããç·æ¥æã«ãã¼ã«ããã¯ã容æã«ãªãã¨ãã£ãã¡ãªãããããã¾ãããæ¤ç´¢çµæã«ä¸å
·åãçããéãã¤ã³ããã¯ã¹ã®ãã¼ã¸ã§ã³ãå·»ãæ»ããã¨ã§åæ¥æç¹ã®ã¤ã³ããã¯ã¹ãç¨ãã¦æ¤ç´¢æ©è½ãæä¾ãããã¨ã容æã§ãããã¯æ¤ç´¢ã·ã¹ãã ãã®ãã®ã®é å¥æ§ãæ¯ããä¸ã¤ã®è¦ç´ ã«ãªã£ã¦ãã¾ãã</p>
<p>ãã®ãã»ã¼ããã¤ã³ããã¤ãããæ©è½ã¯æç¨ãªããå¯è½ã§ããã°æ®ãããããããªãã¨æ¥æ¬¡ãããã«ããæ´æ°ã¨ short-period indexing ã«ããæ´æ°ã並åãããã¨ã«ãªãã¾ãããããªãã¨ã¤ã³ããã¯ã¹ã®æ´æ°çµè·¯ãè¤æ°ã«ãªãããããã®éã«ã³ã³ããªã¯ããèµ·ãããªãããã«ã·ã¹ãã ãè¨è¨ããå¿
è¦ãããããã§ããã</p>
<h2 id="ãã£ãã·ã¥ãæ¤ç´¢çµæã®æ´æ°ãé»å®³ããªãããã«ãã">ãã£ãã·ã¥ãæ¤ç´¢çµæã®æ´æ°ãé»å®³ããªãããã«ãã</h2>
<p>åè¿°ããæ§æå³ã§ã¯è¡¨ç¾ããã¦ãã¾ããã§ããããæ¤ç´¢ã·ã¹ãã ã®å¨è¾ºã«ã¯å¤ç¨®å¤æ§ã®ãã£ãã·ã¥ãåå¨ãã¦ãã¾ããã¯ã©ã¤ã¢ã³ãã¢ããªããã®ãªã¯ã¨ã¹ããåãä»ãã API ããæ¤ç´¢ãµã¼ãã¼ããã®ãªã¯ã¨ã¹ããåãä»ãã Solr ã¨ãè¤æ°ç®æã«ãã£ãã·ã¥ãåå¨ãã¦ãããæ¤ç´¢ã¤ã³ããã¯ã¹ã®æ´æ°æã«ã¯ããããç ´æ£ããªããã°æ¤ç´¢çµæãå¤åãã¾ããã</p>
<p>åç´ã«ãã£ãã·ã¥ãå¥ããã°åãµã¼ãã¹ã¸ã®è² è·å¢å¤§ã¯é¿ãããããã¾ãã¯ç¾ç¶ã®ãããççã調æ»ãã¦å¥ããããªãå°ããã¤å¥ãããé£ããããªããµã¼ãã¼ãå¢ãããªã©ã®å¯¾å¿ãå¿
è¦ã«ãªãããã§ããã</p>
<h1 id="æ°ã·ã¹ãã ã®æ¦è¦">æ°ã·ã¹ãã ã®æ¦è¦</h1>
<p>以ä¸ã«æãã課é¡ã解決ããããã«ã以ä¸ã®å³ã«ç¤ºããããªå
¨ä½åã®ã·ã¹ãã ãè¨è¨ã»éçºãã¾ããã</p>
<p>éçºã®æµãã¨ãã¦ã¯ãå
¨ä½è¨è¨ã«ã¤ãã¦ã¯ããã¸ã§ã¯ãã¡ã³ãã¼ã® 4 åå
¨å¡ã§è°è«ããªããåºããå¿
è¦ãªéçºãããç¨åº¦ç¹å®ãããå¾ã«ãåä½ã®å°éé åã«åããã¦èª¿æ»ãå®è£
ãå²ãæ¯ãå½¢ã«ãã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="æ°ã·ã¹ãã ã®æ§æ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/spicycoffee/20231005/20231005140655.png" width="1200" height="856" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>æ°ã·ã¹ãã ã®æ§æ</figcaption></figure></p>
<p>æ°ã·ã¹ãã ã®ç¹å¾´ã以ä¸ã«ç¤ºãã¾ãã</p>
<h2 id="1-User-Managed-Index-Replication-ãå©ç¨ãã-Solr-cluster-ã®æ§ç¯">1. User-Managed Index Replication ãå©ç¨ãã Solr cluster ã®æ§ç¯</h2>
<p>æ°ã·ã¹ãã ã§ã¯ Solr ãæä¾ãã <a href="https://solr.apache.org/guide/solr/9_1/deployment-guide/user-managed-index-replication.html">User-Managed Index Replication</a> ã®ä»çµã¿ãå©ç¨ã㦠"æ´æ°ç³»" 㨠"åç
§ç³»" ãçµã¿åããã Solr cluster ãæ§ç¯ãã¾ããã</p>
<p>ãã®ã¢ã¼ãã§ã¯ Solr ã¤ã³ã¹ã¿ã³ã¹ã¯ update ãªã¯ã¨ã¹ããåãä»ãã 1 å°ã® leader ã¨ãæ¤ç´¢ãªã¯ã¨ã¹ããåãä»ããè¤æ°å°ã® follower ã«åããã¾ããfollower ã¯è¨å®ããæéãã¨ã« leader ã«å¯¾ãã¦ãã¼ãªã³ã°ãè¡ããå·®åããã¦ã³ãã¼ããã¾ã<a href="#f-5d18f9a0" name="fn-5d18f9a0" title="leader ãã follower ã«å¯¾ãã¦å¤æ´ãéç¥ããªãç¹ã¯ MySQL ã® replication ã¨ã®éãããããã¾ããã">*4</a>ãããããã® Solr ã¯æ§ã·ã¹ãã ã¨å¤ããã Hako ãç¨ã㦠ECS Task ã¨ãã¦èµ·åãã¦ãã¾ãã</p>
<p>ç´°ããªè¦ä»¶ã¨ãã¦ã¯ãæ´æ°ãã³ã³ããªã¯ãããªãããã«åæã«èµ·åãã¦ãã leader ã¯æ大 1 task ã«æããå¿
è¦ãããããã㯠ECS ã® <code>minimumHealthyPercent</code> ã <code>maximumPercent</code> ãè¨å®ãããã¨ã§ä¿è¨¼ãã¦ãã¾ãã</p>
<p>ã¾ããfollower ã¯èµ·åæã«æ¥æ¬¡ãããã§çæããã index ã S3 ãããã¦ã³ãã¼ããããã®å¾ leader ãä¿æãã¦ããæ´æ°åã replicate ãçµãã£ãã¿ã¤ãã³ã°ã§èªèº«ã® status ã healthy ã¨ãã¦ãµã¼ãã¹ã¤ã³ãã¾ãããããããã¨ã§ããã«ã¹ãã§ãã¯ãæåãããã¿ã¤ãã³ã°ãã³ã³ããã¼ã«ããèµ·åå¾ replication éä¸ã® follower ã«ã¢ã¯ã»ã¹ãéä¸ããã¨ãã¢ã¯ã»ã¹æ¯ã«æ¤ç´¢çµæãå¤ãã£ã¦ãã¾ãã¨ãã£ãåé¡ãé²ãã§ãã¾ãã</p>
<h2 id="2-EFS-ãå©ç¨ãã-index-ã®æ°¸ç¶å">2. EFS ãå©ç¨ãã index ã®æ°¸ç¶å</h2>
<p>æ°ã·ã¹ãã ã«ããã¦ã¯ãleader Solr ãåèµ·åã deploy ãããå ´åã«ããã¦ã index ã®ç¶æ
ãä¿ã¡ãupdate 㨠replication ãæ£ããåä½ããç¶æ
ãä¿è¨¼ããå¿
è¦ãããã¾ãã</p>
<p>ãããå®ç¾ããããã«ãAWS ã®ãããã¯ã¼ã¯ã¹ãã¬ã¼ã¸ãµã¼ãã¹ã§ãã EFS ãå©ç¨ã§ãã¾ããEFS ã ECS ã«ã¢ã¿ãããããã¨ã§ãæ°¸ç¶çãªã¹ãã¬ã¼ã¸ããã¦ã³ããããã¨ãã§ãã¾ããããããEFS ã¯ãããã¯ã¼ã¯è¶ãã«ã¢ã¯ã»ã¹ããã¹ãã¬ã¼ã¸ã§ãããããã¬ã¤ãã³ã·çã®æ§è½ã¯ ECS ã®ã¨ãã§ã¡ã©ã«ã¹ãã¬ã¼ã¸ã«å¯¾ãã¦å°ãå£ããã®ã¨ãªã£ã¦ãã¾ãã¾ãã</p>
<p>ããã§ãupdate ãªã¯ã¨ã¹ããåãä»ã㦠index ãæ°¸ç¶åããå¿
è¦ã®ãã leader ã®ã¹ãã¬ã¼ã¸ã«ã¯ EFS ã使ããã¦ã¼ã¶ã¼ããã®æ¤ç´¢ãªã¯ã¨ã¹ããåãä»ãã¦ç´ æ©ãå¿çããå¿
è¦ããã follower ã®ã¹ãã¬ã¼ã¸ã«ã¯ tmpfs ãå©ç¨ãããã¨ã¨ãã¦ãã¾ãã</p>
<p>ã¾ããæ°ã·ã¹ãã ã«ããã¦ããæ§ã·ã¹ãã ã¨åæ§ã«æ¥æ¬¡ã§è¨ç®ã»ä»ä¸ããã field ã¯åå¨ãããããæ¥æ¬¡ãããã§çæããã index 㧠EFS ã®ä¸èº«ãå·®ãæ¿ããå¦çãå®è¡ããã¦ãã¾ãã</p>
<p>ãã®ã¨ããindex ã®å·®ãæ¿ãã leader/follower ã®åèµ·åé åºã«ãã£ã¦ã¯ replication ã®æ´åæ§ãåããªããªãæ§ã
ãªåé¡ãçºçãããã¨ãããã£ããããä¾åé¢ä¿ãä¸å¯§ã«æ´çãã¦åå¦çã®å®è¡é åºãå¶å¾¡ãã¦ãã¾ã<a href="#f-7ea06241" name="fn-7ea06241" title="ç¾ç¶ã®å®è£
ã ã¨æ¤ç´¢çµæãæ°æéåã®ç¶æ
ã«ä¸ç¬ã ãå·»ãæ»ã£ã¦ãã¾ã£ããããã®ã§ãããå®è£
é£æ度ãèãã¦ãããä»æ§å´ã§è¨±å®¹ããã¨ãã£ãå¤æããããªã£ã¦ãã¾ãã">*5</a>ã</p>
<h2 id="3-index-update-batch-ã®å®æå®è¡">3. index update batch ã®å®æå®è¡</h2>
<p>index ã®æ´æ°ã¯ 5 åãã¨ã«å®æå®è¡ãããããã§å®ç¾ãã¦ãã¾ãããã®å®æå®è¡ãã¨ã«ãç´è¿ 1 æéã§æ´æ°ããã£ãã¬ã·ãã®æ
å ±ããåå¾ãããã®æ
å ±ãå
ã«å¿
è¦ãªå¦çãæ½ãã¦ããã¥ã¡ã³ããçæããleader ã« update ã®ãªã¯ã¨ã¹ããæããã¨ããæµãã§ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="5 åã«ä¸åº¦ update ããªã¯ã¨ã¹ãããã¦ããæ§å"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/spicycoffee/20231005/20231005141050.png" width="1200" height="322" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>5 åã«ä¸åº¦ update ããªã¯ã¨ã¹ãããã¦ããæ§å</figcaption></figure></p>
<p>ãã®ã¨ãããã®ã¬ã·ããä¸é©åãªæ稿ã§ãã確çã¯ã©ã®ãããããã ML ã«ãã£ã¦å¤å®ãã API ã¸ã®ãªã¯ã¨ã¹ããæããã¨ã§ãä¸é©åæ稿ã®é²åºãå¢ãããã¨ãé²ãã§ãã¾ã<a href="#f-68d5fb5c" name="fn-68d5fb5c" title="ãã® API ã®éçºã¯ãæ稿ç©ã®ãã§ãã¯ãè¡ã£ã¦ãããã¼ã ã¨æ©æ¢°å¦ç¿ãã¼ã ã®ååã«ãã£ã¦è¿
éã«éçºããã¾ããã">*6</a>ã</p>
<p>å®æå®è¡ãããã«ããã®ã§ã¯ãªããã¬ã·ãã®æ稿ã»æ´æ°ã«ããã¯ããã¦ã¤ãã³ããçºè¡ã»ãã¥ã¼ã¤ã³ã°ãã¦é½åº¦å¦çããæ¹éãèãã¾ãããã</p>
<ul>
<li>ã¤ãã³ãã®çºè¡æ°ãå¤ããªãæ¢åã®ç¤¾å
åºç¤ãå©ç¨ãããã¨ãã§ãããã©ãããæããã§ãªãã£ã</li>
<li>ãªãã©ã¤å¦çã®å®è£
ãè¤éã«ãªã</li>
<li>ããã¾ã§ã®ãªã¢ã«ã¿ã¤ã æ§ãæ±ãããã¦ããªã</li>
</ul>
<p>ãã¨ããæ¡ç¨ãè¦éã£ã¦ãã¾ãã</p>
<h1 id="æ¬çªç°å¢ã¸ã®å±é">æ¬çªç°å¢ã¸ã®å±é</h1>
<p>ããã¸ã§ã¯ãã®å®éã«ã¯ãã·ã¹ãã ã®æ§ç¯ã¨ã¯å¥ã«å±éã«åããå種ä½æ¥ãå¿
è¦ã§ããä»å㯠SRE ã®ã¡ã³ãã¼ã®ååã«ãã£ã¦ã以ä¸ã«æãããããªä½æ¥ãäºåã«ãã£ããã¢ããã»é²è¡ãã¦ããããã¨ãã§ããé常ã«ã¹ã ã¼ãºã«å±éãçµãããã¨ãã§ãã¾ããã</p>
<h2 id="ãã£ãã·ã¥ã®æ´ç">ãã£ãã·ã¥ã®æ´ç</h2>
<p>ã·ã¹ãã ãæ§ç¯ãã¦ããæ¢åã®ãã£ãã·ã¥æ§æã¯æ¥æ¬¡ã§ã®æ¤ç´¢çµææ´æ°ãåæã¨ãã¦ãããããTTL ãæ°æéåä½ã®ãã®ã«ãªã£ã¦ãã¾ããããã®ã¾ã¾ã§ã¯ããã£ãã·ã¥ã®æ´æ°ééãæ¤ç´¢çµæã®æ´æ°ééãããé·ããªã£ã¦ãã¾ãã¾ãã</p>
<p>æ¤ç´¢çµæã®æ´æ°é »åº¦ã«åããã¦ãã£ãã·ã¥ã® TTL ãç縮ãããã§ããã調æ»ãä¸ååã®ã¾ã¾é²ããã¨ãã£ãã·ã¥ã®è£å´ã«ãããµã¼ãã¹ã¸ã®è² è·ãå¢å¤§ããé害ãå¼ãèµ·ããã¦ãã¾ãå¯è½æ§ãããã¾ãã</p>
<p>ããã§ã¾ãã¯ãã£ãã·ã¥ã®è¨å®å¤æ´ãä¸ãã¦ããå½±é¿ã観測ã§ããããã«ãPrometheus + prometheus_exporter gem ãç¨ãããã£ãã·ã¥ã®ãããçãªã©ãè¨æ¸¬ããããã«ãã¾ãã<a href="#f-441c6e5a" name="fn-441c6e5a" title="ã¯ãã¯ããããæ¡ç¨ãã¦ãã Unicorn ã¯ãã«ãããã»ã¹ã§åãã¦ãããããprometheus_exporter ã® multi process modeãç¨ãã¾ããã">*7</a>ã次ã«ãããã®å¤åãåãµã¼ãã¹ã®è² è·ã確èªããªãããã£ãã·ã¥ã® TTL ãå¾ã
ã«çãããå¤æ´ãè¡ããæçµçã«ããµã¼ãã¹é害ãèµ·ãããã¨ãªã TTL ã 5 åã«ã¾ã§ç縮ã§ãã¾ããã</p>
<h2 id="è² è·è©¦é¨ã¨æ®µéãã¼ã«ã¢ã¦ã">è² è·è©¦é¨ã¨æ®µéãã¼ã«ã¢ã¦ã</h2>
<p>æ¤ç´¢æ©è½ã®å¤æ´ã¯ã¯ãã¯ãããã®ã»ã¼å
¨ã¦ã¼ã¶ã¼ã«å½±é¿ãä¸ãã大è¦æ¨¡ãªãã®ã«ãªãã¾ããæ¬çªå±éåã®è² è·è©¦é¨ã¯ãå±éå¾ã®é害çºççãæãããã¨ãã§ããã®ã¯ãã¡ãããéçºè
ãå®å¿ãã¦å±éãè¡ããããã«ãªãã¾ãã</p>
<p>ã¾ããå±éèªä½ãä¸åº¦ã«è¡ãã®ã§ã¯ãªããå¾ã
ã«ã¦ã¼ã¶ã¼ãªã¯ã¨ã¹ããæµããããªæ®µéãã¼ã«ã¢ã¦ãã®æé ãè¸ããã¨ã§ã大è¦æ¨¡é害ã®çºççãæãããã¨ãã§ãã¾ãã</p>
<p>ä»åã¯ä»¥ä¸ã®æé ã§è² è·è©¦é¨ã¨æ®µéãã¼ã«ã¢ã¦ããè¡ãã¾ããã</p>
<ol>
<li>æ¬çªã® Solr ã«å±ãã¦ãããªã¯ã¨ã¹ãããã©ã¼ãªã³ã°ããæ° Solr cluster ã§ããªã¯ã¨ã¹ããåé¡ãªãæãããã確èªãã(è² è·è©¦é¨)
<figure class="figure-image figure-image-fotolife" title="è² è·è©¦é¨ã®æ¦è¦"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/spicycoffee/20231005/20231005141214.png" width="1160" height="582" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>è² è·è©¦é¨ã®æ¦è¦</figcaption></figure></li>
<li>å®éã«ä¸é¨ã®ã¬ã¹ãã³ã¹ãæ°ã·ã¹ãã ããã®ãã®ã«å·®ãæ¿ããå¾ã
ã«ãã®å²åã大ãããã¦ããï¼æ®µéãã¼ã«ã¢ã¦ãï¼
<figure class="figure-image figure-image-fotolife" title="段éãã¼ã«ã¢ã¦ãã®æ¦è¦"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/spicycoffee/20231005/20231005141301.png" width="1149" height="572" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>段éãã¼ã«ã¢ã¦ãã®æ¦è¦</figcaption></figure></li>
<li>å
¨ã¦ã®ã¬ã¹ãã³ã¹ãæ°ã·ã¹ãã ããã®ãã®ã«ãªã£ãå¾ãshort-period indexing ãæå¹ã«ãã</li>
</ol>
<p>ãã®ãã¡ã1 ã®è² è·è©¦é¨ã¯ Envoy ã®<a href="https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-msg-config-route-v3-routeaction-requestmirrorpolicy">RequestMirrorPolicy</a>ãã 2 ã®æ®µéãã¼ã«ã¢ã¦ã㯠Envoy ã®<a href="https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#config-route-v3-weightedcluster">WeightedCluster</a>ã使ã£ã¦å®ç¾ãã¦ãã¾ãã</p>
<h1 id="ã¾ã¨ãã¨æ¯ãè¿ã">ã¾ã¨ãã¨æ¯ãè¿ã</h1>
<p>æ¬ããã¸ã§ã¯ãã§ã¯ãå¾æ¥ã®æ¤ç´¢ã·ã¹ãã ã§ã¯ã¬ã·ãæ稿ããçµæã¸ã®è¡¨ç¤ºã¾ã§ã«æé· 24 æéããã£ã¦ãããã®ãã5 åç¨åº¦ã«ã¾ã§ç縮ãããã¨ã«æåãã¾ããã課é¡ã®ç¹å®ãã解決ã¾ã§ã 6 é±éã§ãããªãã¨ããã¿ã¤ããªã¹ã±ã¸ã¥ã¼ã«ã§ã¯ããã¾ããããäºæ¥è¦ä»¶ã«éä¸è¶³ã®ãªãéçºãäºæ
ãªãå®éãããã¨ãã§ããã®ã§ã¯ãªããã¨æãã¾ãã</p>
<p>æ¯ãè¿ã£ã¦ã¿ãã¨æåã®è¦å ã¨ãã¦ã¯</p>
<ul>
<li>ããã¸ã§ã¯ãåé ã«ãããã¯ãã®å®ç¾ãã¹ãä½é¨ãããã¬ã¼ã¯ãã¦ã³ããå½¢ã§è¦ä»¶ããã£ããã¨å®ç¾©ãã
<ul>
<li>å¾ã®ææ決å®ã«è»¸ãéããææ»ããå°ãªããªã£ã</li>
</ul>
</li>
<li>ãããã¯ãããã¤ã³ãã©ãµã¤ãã¾ã§ãåé åã«ã¤ãã¦é«ãå°éæ§ãæã¤ã¡ã³ãã¼ãéã¾ã£ã
<ul>
<li>å
¨ä½ã®è¦ä»¶å®ç¾©ããã£ããã¨ããè¨è¨ã¯å
¨å¡ã§è¡ããããããå
ã®è©³ç´°éçºã¯åã¡ã³ãã¼ãæ
å½ãã</li>
<li>ããã¸ã§ã¯ãã®ããè¨æã«çµæããããã¼ã ã ã£ãããæéä¸ã¯é±ï¼åã® check-in MTG ãè¨å®ãã¦ã¹ã ã¼ãºã«åæã¨ç¸è«ããããªããããã«ãã</li>
</ul>
</li>
<li>ã¹ãããã§æ©æ¢°å¦ç¿ã¨ã³ã¸ãã¢ãªã©ãä»ãã¼ã ã®å©åãå¾ããã¨ãã§ãã</li>
</ul>
<p>ãã¨ã大ããã£ãã®ã§ã¯ãªããã¨æãã¾ãã</p>
<p>çµç¹ã¨ãã¦éæãããããã·ã§ã³ãããããã®ããã®äºæ¥ã»ãããã¯ããããããããå®ç¾ãããä½é¨ãé»ãã§ããéå£ãããã¨ããã«æè¡ãã¶ã¤ãã¦ãããåãé¤ãã¨ããä»äºã¯ããã¯ãã¨ã¦ãããããã®ãããã®ã ã¨æ¹ãã¦å®æãã¾ãããããããã«é«ãå°éæ§ãæã¤ã¡ã³ãã¼ããæããã¼ã ã§ä»äºãã§ãããã¨ãå«ãã¦ãå人çã«ã¯å
¥ç¤¾ä»¥æ¥ãã£ã¨ããããããä»äºã®ä¸ã¤ã§ãã£ãããã«æãã¾ãã</p>
<h1 id="Acknowledgements">Acknowledgements</h1>
<p>æ¬ããã¸ã§ã¯ã㯠4 åã®ã¡ã¤ã³ã¡ã³ãã¼ï¼å¨è¾ºé¨ç½²ã®ã¡ã³ãã¼ãé¢ãããããããåãçºæ®ãããã¨ã§å®éãããã¨ã®ã§ããããã¸ã§ã¯ãã§ããç§ä¸äººã®åã§ã¯å°åºå®ç¾ã§ããªãã£ãã§ããã課é¡è§£æ±ºãå
±ã«æ¨é²ãã¦ããããã¨ã«æ¹ãã¦æè¬ãã¾ãã</p>
<p>æå¾ã«ãã¡ã¤ã³ã¡ã³ãã¼ã® 4 åã«ã¤ãã¦ãåä½æ¥ãã©ã®ããã«æ
å½ããããæè¨ãã¾ãã</p>
<ul>
<li><a href="https://twitter.com/SpicyCoffee66">@SpicyCoffee</a>ï¼çè
ï¼
<ul>
<li>æ¤ç´¢ã¨ã³ã¸ãã¢</li>
<li>æ
å½ï¼ããã¸ã§ã¯ãå
¨ä½ã®çµ±æ¬ã»æçµæææ±ºå® / indexing application ã®å®è£
</li>
</ul>
</li>
<li><a href="https://twitter.com/osyoyu">@osyoyu</a>
<ul>
<li>æ¤ç´¢ã¨ã³ã¸ãã¢</li>
<li>æ
å½ï¼Solr Cluster 㨠Persistent Storage å¨ãã®è¨è¨ã»éçº</li>
</ul>
</li>
<li><a href="https://twitter.com/s4ichi">@s4ichi</a>
<ul>
<li>SRE</li>
<li>æ
å½ï¼Solr Cluster 㨠Persistent Storage å¨ãã®è¨è¨ã»éçº / è² è·è©¦é¨ã¨ãã¼ã«ã¢ã¦ã</li>
</ul>
</li>
<li><a href="https://twitter.com/eagletmt">@eagletmt</a>
<ul>
<li>SRE</li>
<li>æ
å½ï¼ãã£ãã·ã¥ã®èª¿æ»ã¨æé©å / indexing application ã®å®è£
</li>
</ul>
</li>
</ul>
<p>ãã®è¨äºããæ¥ã
æè¡ãç¨ãã¦ã¦ã¼ã¶ã¼èª²é¡ã解決ãã¦ããã¿ãªãã¾ã®ãå½¹ã«ç«ã¦ã°å¹¸ãã§ãã</p>
<div class="footnote">
<p class="footnote"><a href="#fn-9b11b8ee" name="f-9b11b8ee" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ä»å㯠CEO ããã®å½¹å²ãæ
ã£ã¦ãã¾ããã社é·ã¨ç´æ¥ä»äºãããæ©ä¼ãéã£ã¦ãã¦ã©ããã¼ã</span></p>
<p class="footnote"><a href="#fn-f14419c3" name="f-f14419c3" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">å½æã® ECS & 社å
åºç¤ <a href="https://github.com/eagletmt/hako">Hako</a> ã¨ããæ§æã¯éç¨è² è·ãä½ãä¸ã«é常ã«å®å®ãã¦ãããSolr ãç´æ¥ã®çç±ã¨ãªã£ã¦é害ãèµ·ããã®ã¯å¹´ã« 1 度ããªãããã«è¨æ¶ãã¦ãã¾ãã</span></p>
<p class="footnote"><a href="#fn-f1ecce9b" name="f-f1ecce9b" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">人æã«ããã¬ã·ãã®å
¨ä»¶ãã§ãã¯ã¯ short-period indexing 以åãè¡ããã¦ãããããããªãã¬ã¼ã·ã§ã³ã®è¦ç´ããå«ããã¬ã·ããã§ãã¯å¨ãã§ãã·ã¹ãã å¤æ´ãå¿
è¦ã«ãªãã¨äºæ³ããããã¨ãã表ç¾ã®æ¹ãæ£ç¢ºããããã¾ããã</span></p>
<p class="footnote"><a href="#fn-5d18f9a0" name="f-5d18f9a0" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">leader ãã follower ã«å¯¾ãã¦å¤æ´ãéç¥ããªãç¹ã¯ MySQL ã® replication ã¨ã®éãããããã¾ããã</span></p>
<p class="footnote"><a href="#fn-7ea06241" name="f-7ea06241" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">ç¾ç¶ã®å®è£
ã ã¨æ¤ç´¢çµæãæ°æéåã®ç¶æ
ã«ä¸ç¬ã ãå·»ãæ»ã£ã¦ãã¾ã£ããããã®ã§ãããå®è£
é£æ度ãèãã¦ãããä»æ§å´ã§è¨±å®¹ããã¨ãã£ãå¤æããããªã£ã¦ãã¾ãã</span></p>
<p class="footnote"><a href="#fn-68d5fb5c" name="f-68d5fb5c" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãã® API ã®éçºã¯ãæ稿ç©ã®ãã§ãã¯ãè¡ã£ã¦ãããã¼ã ã¨æ©æ¢°å¦ç¿ãã¼ã ã®ååã«ãã£ã¦è¿
éã«éçºããã¾ããã</span></p>
<p class="footnote"><a href="#fn-441c6e5a" name="f-441c6e5a" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text">ã¯ãã¯ããããæ¡ç¨ãã¦ãã Unicorn ã¯ãã«ãããã»ã¹ã§åãã¦ãããããprometheus_exporter ã® <a href="https://github.com/discourse/prometheus_exporter#multi-process-mode">multi process mode</a>ãç¨ãã¾ããã</span></p>
</div>
spicycoffee
ã¯ãã¯ãããã®ããã³ãã¨ã³ã CSS in JS ãã¼ãã©ã³ã¿ã¤ã ã«åãæ¿ãã¾ãã
hatenablog://entry/820878482972440079
2023-10-03T10:52:40+09:00
2023-10-03T18:18:21+09:00 ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ã®kaorun343ã§ããæã
ã®ãã¼ã ã§ã¯ã¬ã·ããµã¼ãã¹ã®ããã³ãã¨ã³ãã Next.js 㨠GraphQL ã®ã·ã¹ãã ã«ç½®ãæãã¦ãã話 - ã¯ãã¯ãããéçºè
ããã°ã«ã¦ç´¹ä»ããã¨ãããã¬ã·ããµã¼ãã¹ã Next.js ãã¼ã¹ã®æ°ã·ã¹ãã ã¸ã¨ç§»è¡ãã¦ãã¾ããä»åã¯ããã®æ°ã·ã¹ãã ã®CSS in JSãEmotionããã¼ãã©ã³ã¿ã¤ã ã®vanilla-extractã¸å¤æ´ãã話ã§ãã vanilla-extract.style èæ¯ ä»¥åæ¸ãã ã¬ã·ããµã¼ãã¹ã®ããã³ãã¨ã³ãã« CSS in JS ãæ¡ç¨ãã話 - ã¯ãã¯ãããéçºè
ããã°ã§ã¯ãCSS in JSã©ã¤â¦
<p>ããã«ã¡ã¯ãã¬ã·ãäºæ¥é¨ã®kaorun343ã§ããæã
ã®ãã¼ã ã§ã¯<a href="https://techlife.cookpad.com/entry/2020/12/01/093000">レシピサービスのフロントエンドを Next.js と GraphQL のシステムに置き換えている話 - クックパッド開発者ブログ</a>ã«ã¦ç´¹ä»ããã¨ãããã¬ã·ããµã¼ãã¹ã Next.js ãã¼ã¹ã®æ°ã·ã¹ãã ã¸ã¨ç§»è¡ãã¦ãã¾ããä»åã¯ããã®æ°ã·ã¹ãã ã®CSS in JSãEmotionããã¼ãã©ã³ã¿ã¤ã ã®vanilla-extractã¸å¤æ´ãã話ã§ãã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fvanilla-extract.style%2F" title="vanilla-extract â Zero-runtime Stylesheets-in-TypeScript." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://vanilla-extract.style/">vanilla-extract.style</a></cite></p>
<h3 id="èæ¯">èæ¯</h3>
<p>以åæ¸ãã <a href="https://techlife.cookpad.com/entry/2021/03/15/090000">レシピサービスのフロントエンドに CSS in JS を採用した話 - クックパッド開発者ブログ</a>ã§ã¯ãCSS in JSã©ã¤ãã©ãªã¨ã㦠Emotionï¼@emotion/reactï¼ãæ¡ç¨ããçµç·¯ã¨éçºç°å¢æ´åãç´¹ä»ãã¾ãããæ¡ç¨çç±ã¨ãã¦ã¯ä»¥ä¸ã®éãã§ããã</p>
<ul>
<li>ã»ã¬ã¯ã¿ã«ä¸æãªIDãå²ãæ¯ãããã®ã§ãã¹ã¿ã¤ã«ãé©ç¨ããè¦ç´ ã¨ã¯å¥ã®è¦ç´ ã¸ã®ãæå³ããªãã¹ã¿ã¤ã«é©ç¨ãé²ããã¨ãã§ããã</li>
<li>ESLintãTypeScriptã³ã³ãã¤ã©ã¨ãã£ãJavaScriptã®éç解æãã¼ã«ã®æ©æµãåãããã¨ãã§ããã¿ã¤ããæ©è½åé¤æã®åé¤æ¼ãã«æ°ã¥ãããããªãã</li>
<li>styled-componentsã®ãããªã¹ã¿ã¤ã«ã§ã¯JSXã®ããªã¼ãè¦ãã¨ãã«ãæ©è½ãæã¤ã³ã³ãã¼ãã³ããªã®ãè£
飾ãããã³ã³ãã¼ãã³ããªã®ããããããã³ã¼ãã¬ãã¥ã¼ããã«ããã</li>
<li>é常ã®CSSã®è¨æ³ã«æ
£ããã¡ã³ãã¼ãå¤ãã®ã§ãString Stylesãããªãã¡ã¿ã°ä»ããã³ãã¬ã¼ããªãã©ã«ãæ¡ç¨ããã</li>
</ul>
<p>ãã®ãããªæ¹éã§Emotionã®å°å
¥ã決ããstylelintãeslintãå°å
¥ããå¿
è¦ã«å¿ãã¦ã«ã¹ã¿ã ã«ã¼ã«ãä½æãã¦æ©è½éçºãé²ãã¾ããã</p>
<p>ããããªãããEmotionãå°å
¥ãã¦ãã2å¹´ã»ã©çµã£ãçµæã以ä¸ã®ãããªèª²é¡ãæ¸å¿µãæ±ããããã«ãªãã¾ããã</p>
<ol>
<li><strong>ãã¼ã¸ãµã¤ãº</strong>ï¼SSRæã«ã¯åæ表示ç¨ã®CSSãEmotionãä½ãããã§ããããã®CSS㯠.css ãã¡ã¤ã«ã¨ãã¦ãã©ã¦ã¶ã«å±ãã®ã§ã¯ãªã Next.jsããé
ä¿¡ãããHTMLã«åãè¾¼ã¾ããç¶æ
ã§ãã©ã¦ã¶ã«å±ãã¾ãããã®ããããã¼ããã©ã³ãµã¼ãééããHTMLã®ãµã¤ãºãå¢å ãã¦ãã¾ãã¾ããCSSã®ãã¼ã¿ãCDNãéããªããããããã©ã¼ãã³ã¹ã®é¢ã§ãã³ã¹ãã®é¢ã§åé¡ã§ããå®éãbackground-imageã«base64ã®ç»åURLãåãè¾¼ãã ã¨ãã«ã¯ããã®å½±é¿ãå¼·ãåºã¦ãã¾ãã¾ããã</li>
<li><strong>åççæã«ããè¥å¤§å</strong>ï¼Next.jsã®SSRæã«ãã£ããCSSã®ããªã¨ã¼ã·ã§ã³ãå¢ããã¦ãã¾ãã¨ãNext.jsããã»ã¹ã®ã¡ã¢ãªä½¿ç¨éãå¢å¤§ããã¢ããªã±ã¼ã·ã§ã³ãè½ã¡ã¦ãã¾ãã¾ããããã¯ãEmotionãã¤ã³ã¡ã¢ãªã®ãã£ãã·ã¥æ©æ§ãåãã¦ãããä¸åº¦çæããCSSãã¼ã¿ãä¿æãç¶ããããã§ããéå»ã®äºä¾ã§ã¯ãã¬ã·ããã¨ã«ç°ãªãbackground-imageãè¨å®ããCSSãEmotionã§æ¸ããã¨ãã«ãã®åé¡ãçãã¾ãã<a href="#f-77e91b7c" name="fn-77e91b7c" title="ãã®ã±ã¼ã¹ã§ã¯styleå±æ§ã«ç´æ¥background-imageãæå®ããCSSãä»ä¸ãã¦åé¡ãåé¿ãã¾ããããéçºè
ããã®ç¹æ§ãæ°ã«ãç¶ããã®ã¯é£ããã§ã">*1</a>ã</li>
<li><strong>ã¯ã©ã¤ã¢ã³ãå´ã®ãªã¼ãã¼ããã</strong>ï¼CSSçæã®ããã«ãã©ã¦ã¶ä¸ã§JavaScriptãå®è¡ãããããããã¼ã¸ã®ããã©ã¼ãã³ã¹ã¸å½±é¿ãããæ¸å¿µãããã¾ããEmotionã¯CSSã®è¨è¿°å
容ã®è§£æãå¤ããã©ã¦ã¶åãã®è¨è¿°ã®è¿½å ãCSSã®åæãããã¦ã¹ã¿ã¤ã«ã®DOMã¸ã®æ¿å
¥ããã©ã¦ã¶ä¸ã§å®è¡ãã¾ããEmotionã®<code>css</code>é¢æ°ã使ãã°ä½¿ãã»ã©CSSã«é¢ããå¦çã®å®è¡æéãå¢ãã¦ããã¾ããã¾ãããããã®å¦çããã©ã¦ã¶ä¸ã§å®è¡ããããã®JSã®ã³ã¼ããå¿
è¦ã¨ãªãããããã³ãã«ãµã¤ãºãå¢å ãã¦ãã¾ãã¾ããæ°ã·ã¹ãã ããã¹ããã¦ãããã¼ã¸ã¯ã¹ãã¼ããã©ã³åãã®ãã¼ã¸ã§ãããããã©ã¼ãã³ã¹ããã³ãã«ãµã¤ãºã¯ç¹ã«æ³¨è¦ãã¦ãã¾ãã</li>
</ol>
<p>ããã§ãEmotionããå¥ã® CSS ç°å¢ã¸ã®ç§»è¡ãæ¤è¨ãã¾ããã</p>
<h3 id="æè¡é¸å®">æè¡é¸å®</h3>
<p>ä¸è¨ã®èª²é¡ãè¸ã¾ãã以ä¸ã®è¦ä»¶ã§æ°ããCSS ç°å¢ãæ¤è¨ãã¾ããã</p>
<ul>
<li><strong>Emotionã«è¿ãéçºä½é¨</strong>ï¼Emotionã¨åæ§ã«ãCSS ã¯ã©ã¹åãèªåã§ã¤ããå¿
è¦ããªããã¨</li>
<li><strong>CDNã®æ´»ç¨</strong>ï¼ãã«ãæã« CSS ãã¡ã¤ã«ãçæãã㦠CDN ããéçã«é
ä¿¡ã§ãããã¨</li>
<li><strong>ä½ããªã¼ãã¼ããã</strong>ï¼ã¼ãã©ã³ã¿ã¤ã ã§ãããã¨ï¼ãã«ãæã«CSSãçæãããã©ã¦ã¶ã«éãããJavaScriptã«ã¯CSSãçæããã³ã¼ããå«ã¾ãªããã¨ï¼</li>
<li><strong>å°æ¥æ§</strong>ï¼Server Componentså°å
¥ãè¦æ®ãã¦ãServer Componentsã«å¯¾å¿ãã¦ããã¨å¬ãã</li>
</ul>
<p>æ¤è¨ããçµæããããã®è¦ä»¶ãæºããã©ã¤ãã©ãªã¨ãã¦vanilla-extractãæããã¾ããã
vanilla-extractã§ã¯CSSãJavaScriptã®ãªãã¸ã§ã¯ãã¨ã㦠.css.[jt]s ã¨ããæ¡å¼µåã®ãã¡ã¤ã«ã«è¨è¿°ãã¾ãããããvanilla-extractã®å種ãã³ãã©ã«å¯¾å¿ãããã©ã°ã¤ã³ãCSSã«å¤æããCSSãã¡ã¤ã«ãçæãã¾ããã¾ããããããã®ã¹ã¿ã¤ã«ã¯ä¸æãªã¯ã©ã¹åãã»ã¬ã¯ã¿ã¨ãã¦ãããEmotionã¨åãããã«æå³ããªãã¹ã¿ã¤ã«é©ç¨ãé²ããã¨ãã§ãã¾ãã</p>
<p>Emotionã¨vanilla-extractã®æ¯è¼ã表ã«ããã¨ä»¥ä¸ã®ããã«ãªããæè¡é¸å®ã§éè¦ããé
ç®ãæºããã¦ãã¾ãã</p>
<table>
<thead>
<tr>
<th> æ¯è¼é
ç® </th>
<th> Emotion </th>
<th> vanilla-extract </th>
</tr>
</thead>
<tbody>
<tr>
<td> ã¯ã©ã¹åã®èªåä»ä¸ </td>
<td> â </td>
<td> â </td>
</tr>
<tr>
<td> CDNããé
å¸å¯è½ </td>
<td> ï¼HTMLã«åãè¾¼ã¾ããï¼ </td>
<td> â </td>
</tr>
<tr>
<td> ã¼ãã©ã³ã¿ã¤ã </td>
<td> ï¼ãã©ã¦ã¶ï¼ </td>
<td> â </td>
</tr>
<tr>
<td> Server Componentså¯¾å¿ </td>
<td> ï¼CSRã®ã¿ï¼ </td>
<td> â </td>
</tr>
<tr>
<td> ã³ã³ãã¼ãã³ãã¨åããã¡ã¤ã«ã«æ¸ãã </td>
<td> â </td>
<td> ï¼.css.[jt]sã«æ¸ãå¿
è¦ãããï¼</td>
</tr>
<tr>
<td> CSSã®æ¸ãæ¹ </td>
<td> String StylesãObject Styles </td>
<td> Object Stylesã®ã¿ </td>
</tr>
<tr>
<td> Stylelint </td>
<td> â </td>
<td> ï¼æªå¯¾å¿ï¼</td>
</tr>
<tr>
<td> ã¹ãããã·ã§ãããã¹ã </td>
<td> â </td>
<td> ï¼ãªãï¼ </td>
</tr>
<tr>
<td> ãã³ãã¼ãã¬ãã£ã¯ã¹ã®èªåä»ä¸ </td>
<td> â </td>
<td>ï¼ãªãï¼</td>
</tr>
</tbody>
</table>
<p>ï¼æ¯è¼å½æã@emotion/reactã¯v11.10.0ã@vanilla-extract/cssã¯v1.9.1ã§ããï¼</p>
<p>çè
ãvanilla-extractãææ¡ããéã¯ãè¨è¿°æ¹æ³ã®éãããã«ãææç©ã®å·®ããããããã«ãå®éã®ãã¼ã¸ãæ¸ãæãããã«ãªã¯ã¨ã¹ããä¾ç¤ºãã¾ãããCSSã®ã³ã¼ãéãå°ãã<a href="https://techlife.cookpad.com/entry/dynamic-og-image">OGPç»åçæç¨ã®ãã¼ã¸</a>ã対象ã«ãã¾ããã</p>
<h4 id="vanilla-extractã®ãã¡ãªãã">vanilla-extractã®ãã¡ãªãã</h4>
<p>ä¸æ¹ã§ãvanilla-extractã«ã¯ãã¡ãªãããåå¨ãã¾ãã</p>
<h5 id="CSSã®æ¸ãæ¹">CSSã®æ¸ãæ¹</h5>
<p>ã¾ããããã¾ã§éãString Stylesã§è¨è¿°ãããã¨ãã§ããªããªãã¾ããããã®ç¹ã«ã¤ãã¦æ¸å¿µç¹ããªãããã¶ã¤ãã¼ã®æ¹ã«ä¼ºã£ãã¨ããããCSSãæ¸ããã°åé¡ãªããã¨ã®ãã¨ã§ãããvanilla-extract㯠.css.[jt]s ã«è¨è¿°ããå¿
è¦ãããã¾ããããã®ç¹ã«ã¤ãã¦ãããã¼ã ã¡ã³ãã¼ããåæããããã¾ããã</p>
<h5 id="Stylelint">Stylelint</h5>
<p>å ãã¦è¨æ³ãå¤ãã£ããã¨ã«ããStylelintã§æ¤æ»ã§ããªããªãã¾ãããããããªãããCSSã®ããããã£ãå¤ã®ã¿ã¤ãã»ããããã£ã®éè¤ã¯TypeScriptã§è¦ã¤ãããã¾ãããæã
ã®ã¢ããªã±ã¼ã·ã§ã³ã§ã¯è©³ç´°åº¦ã«é¢é£ãã¦å°ããããªæ¸ãæ¹ããã¦ããªãã®ã§ãStylelintãå»æ¢ãããã¡ãªããã¯å°ããã¨å¤æãã¾ããã</p>
<h5 id="ã¹ãããã·ã§ãããã¹ã">ã¹ãããã·ã§ãããã¹ã</h5>
<p>Emotionã§ã¯@emotion/jestãã¹ãããã·ã§ãããã¹ãã«CSSã®è¨è¿°ã表示ããä»çµã¿ãæä¾ãã¦ãã¾ããããããvanilla-extractã§ã¯æä¾ããã¦ããããã¹ãããã·ã§ãããã¹ãã§CSSã®è¨è¿°ã確èªãããã¨ãã§ããªããªãã¾ãããã¹ãããã·ã§ãããã¹ãã«ã¤ãã¦ã¯ãéè¯ããã°ãæ¤ç¥ã§ããã»ã©ã®ã¡ãªãããããªãã¨å¤æãã使ããªããªããã¡ãªããã¯å°ããã¨å¤æãã¾ããã</p>
<h5 id="ãã³ãã¼ãã¬ãã£ã¯ã¹ã®èªåä»ä¸">ãã³ãã¼ãã¬ãã£ã¯ã¹ã®èªåä»ä¸</h5>
<p>Emotionã¯ãã³ãã¼ãã¬ãã£ã¯ã¹ãèªåã§ä»ä¸ãã¦ãããã®ã§ãããEmotionã§ã¯ã©ã¤ãã©ãªå©ç¨è
ããã©ã¦ã¶ã®ãã¼ã¸ã§ã³ãæå®ã§ããªããããã¯ãã¯ãããã®æ¨å¥¨ç°å¢ããå¤ããã©ã¦ã¶ã対象ã¨ããããããã£ã追å ããã¦ãã¾ãããã¯ãã¯ãããã®æ¨å¥¨ç°å¢ãéã¿ãèªåä»ä¸ããªããªããã¡ãªããã¯å°ããã¨å¤æãã¾ããã</p>
<h3 id="vanilla-extractã¸ã®ç§»è¡">vanilla-extractã¸ã®ç§»è¡</h3>
<p>CSSã®è¨è¿°ãvanilla-extractã¸ç§»è¡ãããã¨ã決å®ããå¾ã移è¡ä½æ¥ã«ã¨ããããã¾ããã</p>
<p>æåã¯ãã¹ã¦æä½æ¥ã§æ¸ãæãã¦ããã®ã§ãããéä¸ããæ£è¦è¡¨ç¾ã使ã£ãç°¡ç´ ãªå¤æãã¼ã«ãå°å
¥ãã¦ç§»è¡ä½æ¥ãã¹ãã¼ãã¢ãããã¾ããã
Emotionã¨vanilla-extractã¯å
±åã§ãããããæã空ãã¦ããã¨ãã«æåãããã¦å°ããã¤ç§»è¡ãã¦ããã¾ãããã¾ããNext.jsã¢ããªã±ã¼ã·ã§ã³æ¬ä½ã ãã§ã¯ãªããå
±éã³ã³ãã¼ãã³ãããã±ã¼ã¸ãããã¦ç¤¾å
ã®ãã¶ã¤ã³ã·ã¹ãã ã®Reactã©ã¤ãã©ãªãvanilla-extractã«ç§»è¡ãã¾ããã</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// Emotion</span>
<span class="synStatement">import</span> <span class="synIdentifier">{</span> css <span class="synIdentifier">}</span> from <span class="synConstant">'@emotion/react'</span>
<span class="synStatement">const</span> linkStyle = css<span class="synConstant">`</span>
<span class="synConstant"> flex: 1;</span>
<span class="synConstant"> box-sizing: border-box;</span>
<span class="synConstant"> background-color: white;</span>
<span class="synConstant">`</span>
<span class="synStatement">const</span> linkDisableStyle = css<span class="synConstant">`</span>
<span class="synConstant"> </span><span class="synSpecial">${linkStyle}</span>
<span class="synConstant"> background-color: gray;</span>
<span class="synConstant">`</span>
<span class="synStatement">export</span> <span class="synStatement">const</span> MyComponent = () => <span class="synIdentifier">{</span>
<span class="synStatement">return</span> (
<section>
<a href=<span class="synConstant">"#"</span> css=<span class="synIdentifier">{</span>linkStyle<span class="synIdentifier">}</span>>
Link
</a>
<a href=<span class="synConstant">"#"</span> css=<span class="synIdentifier">{</span>linkDisableStyle<span class="synIdentifier">}</span>>
Disabled Link
</a>
</section>
)
<span class="synIdentifier">}</span>
</pre>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// vanilla-extract</span>
<span class="synComment">// MyComponent.css.js</span>
<span class="synStatement">export</span> <span class="synStatement">const</span> linkStyle = style(<span class="synIdentifier">{</span>
flex: 1,
boxSizing: <span class="synConstant">'border-box'</span>,
backgroundColor: <span class="synConstant">'white'</span>,
<span class="synIdentifier">}</span>)
<span class="synStatement">export</span> <span class="synStatement">const</span> linkDisabledStyle = style(<span class="synIdentifier">[</span>
linkStyle,
<span class="synIdentifier">{</span>
backgroundColor: <span class="synConstant">'gray'</span>,
<span class="synIdentifier">}</span>,
<span class="synIdentifier">]</span>)
<span class="synComment">// MyComponent.js</span>
<span class="synStatement">import</span> <span class="synIdentifier">{</span> linkDisabledStyle, linkStyle <span class="synIdentifier">}</span> from <span class="synConstant">'./MyComponent.css.js'</span>
<span class="synStatement">export</span> <span class="synStatement">const</span> MyComponent = () => <span class="synIdentifier">{</span>
<span class="synStatement">return</span> (
<section>
<a href=<span class="synConstant">"#"</span> className=<span class="synIdentifier">{</span>linkStyle<span class="synIdentifier">}</span>>
Link
</a>
<a href=<span class="synConstant">"#"</span> className=<span class="synIdentifier">{</span>linkDisabledStyle<span class="synIdentifier">}</span>>
Disabled Link
</a>
</section>
)
<span class="synIdentifier">}</span>
</pre>
<p>åçã«ã¹ã¿ã¤ã«ãçæãã¦ããç®æã«ã¤ãã¦ã¯ã @vanilla-extract/dynamic ã @vanilla-extract/recipesãå©ç¨ãã¦åé¡ãªãç½®ãæãããã¾ããã</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// Emotion</span>
<span class="synStatement">import</span> <span class="synIdentifier">{</span> css <span class="synIdentifier">}</span> from <span class="synConstant">'@emotion/react'</span>
<span class="synStatement">const</span> linkStyle = (size) => css<span class="synConstant">`</span>
<span class="synConstant"> width: </span><span class="synSpecial">${size}</span><span class="synConstant">;</span>
<span class="synConstant"> height: </span><span class="synSpecial">${size}</span><span class="synConstant">;</span>
<span class="synConstant">`</span>
<span class="synStatement">export</span> <span class="synStatement">const</span> MyComponent = () => <span class="synIdentifier">{</span>
<span class="synStatement">return</span> (
<section>
<a href=<span class="synConstant">"#"</span> css=<span class="synIdentifier">{</span>linkStyle(<span class="synConstant">'100px'</span>)<span class="synIdentifier">}</span>>
Link 1
</a>
<a href=<span class="synConstant">"#"</span> css=<span class="synIdentifier">{</span>linkStyle(<span class="synConstant">'200px'</span>)<span class="synIdentifier">}</span>>
Link 2
</a>
</section>
)
<span class="synIdentifier">}</span>
</pre>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// vanilla-extract</span>
<span class="synComment">// MyComponent.css.js</span>
<span class="synStatement">import</span> <span class="synIdentifier">{</span> createVar, style <span class="synIdentifier">}</span> from <span class="synConstant">'@vanilla-extract/css'</span>
<span class="synStatement">export</span> <span class="synStatement">const</span> sizeVar = createVar()
<span class="synStatement">export</span> <span class="synStatement">const</span> linkStyle = style(<span class="synIdentifier">{</span>
width: sizeVar,
height: sizeVar,
<span class="synIdentifier">}</span>)
<span class="synComment">// MyComponent.js</span>
<span class="synStatement">import</span> <span class="synIdentifier">{</span> assignInlineVars <span class="synIdentifier">}</span> from <span class="synConstant">'@vanilla-extract/dynamic'</span>
<span class="synStatement">import</span> <span class="synIdentifier">{</span> sizeVar, linkStyle <span class="synIdentifier">}</span> from <span class="synConstant">'./MyComponent.css.js'</span>
<span class="synStatement">export</span> <span class="synStatement">const</span> MyComponent = () => <span class="synIdentifier">{</span>
<span class="synStatement">return</span> (
<section>
<a
href=<span class="synConstant">"#"</span>
className=<span class="synIdentifier">{</span>linkStyle<span class="synIdentifier">}</span>
style=<span class="synIdentifier">{</span>assignInlineVars(<span class="synIdentifier">{</span> <span class="synIdentifier">[</span>sizeVar<span class="synIdentifier">]</span>: <span class="synConstant">'100px'</span> <span class="synIdentifier">}</span>)<span class="synIdentifier">}</span>
>
Link 1
</a>
<a
href=<span class="synConstant">"#"</span>
className=<span class="synIdentifier">{</span>linkStyle<span class="synIdentifier">}</span>
style=<span class="synIdentifier">{</span>assignInlineVars(<span class="synIdentifier">{</span> <span class="synIdentifier">[</span>sizeVar<span class="synIdentifier">]</span>: <span class="synConstant">'200px'</span> <span class="synIdentifier">}</span>)<span class="synIdentifier">}</span>
>
Link 2
</a>
</section>
)
<span class="synIdentifier">}</span>
</pre>
<p>移è¡ããçµæãEmotionã§èª²é¡ãæ¸å¿µã«æãã¦ãããã¨ã解æ¶ã§ãã¾ããã</p>
<ol>
<li><strong>ãã¼ã¸ãµã¤ãº</strong>ï¼CSSãã¡ã¤ã«ã«ãã¼ã¸å
¨ä½ã®CSSãå«ã¾ããããã«ãªããCDNããé
å¸ã§ããããã«ãªãã¾ãããbackground-imageã¨ãã¦base64ã®ç»åãã¡ã¤ã«ãåãè¾¼ãã å ´åã§ããã¼ããã©ã³ãµã¼ãéãHTMLã®ãµã¤ãºã大ãããªããã¨ã¯ããã¾ããã</li>
<li><strong>åççæã«ããè¥å¤§å</strong>ï¼ã¡ã¢ãªä½¿ç¨éãå¢å ãã¦Next.jsããã»ã¹ãè½ã¡ããã¨ã¯ãªããªãã¾ããã</li>
<li><strong>ã¯ã©ã¤ã¢ã³ãå´ã®ãªã¼ãã¼ããã</strong>ï¼CSSã®çæã¯ãã«ãæã«ã®ã¿ãããªãããããã«ãªããçæã®ããã®JavaScriptã¯@vanilla-extract/dynamicã@vanilla-extract/recipesã ãã«ãªãã¾ãããã¾ããEmotionã®ã©ã³ã¿ã¤ã åé¤ã«ãããã³ãã«ãµã¤ãºã¯gzipã§10kBå¼±æ¸å°ãã¾ããã</li>
</ol>
<p>ã¨ã³ã¸ãã¢ããã¶ã¤ãã¼ããããç¹ã«ãã¬ãã£ããªæè¦ã¯åºã¦ãã¾ãããåãã¦vanilla-extractã触ãã¡ã³ãã¼ããåé¡ãªãCSSãå¤æ´ã§ãã¦ãã¾ãã</p>
<h3 id="ãããã«">ãããã«</h3>
<p>ä»åã¯ã¬ã·ããµã¼ãã¹ã®æ°ã·ã¹ãã ã«ããã ã¼ãã©ã³ã¿ã¤ã CSS in JS ã®è©±ãç´¹ä»ãã¾ãããã¯ãã¯ãããã§ã¯ãããããã¢ãã³ãªæè¡ã«ããã¬ã·ããµã¼ãã¹ã®å·æ°ãé²ãã¦ããã¾ãã</p>
<div class="footnote">
<p class="footnote"><a href="#fn-77e91b7c" name="f-77e91b7c" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãã®ã±ã¼ã¹ã§ã¯styleå±æ§ã«ç´æ¥background-imageãæå®ããCSSãä»ä¸ãã¦åé¡ãåé¿ãã¾ããããéçºè
ããã®ç¹æ§ãæ°ã«ãç¶ããã®ã¯é£ããã§ã</span></p>
</div>
kaorun343
iOSã¢ããªã«å®è£
ãããUIè¦ç´ ã®ãã¬ã¼ã ããã¼ã¸ã³ãæ軽ã«ç¢ºèªã§ãããã¼ã«ãä½ã
hatenablog://entry/820878482967113840
2023-09-14T16:00:00+09:00
2023-09-20T23:24:21+09:00 ããã«ã¡ã¯ãã¯ãã¯ããããã¼ããããã¯ãéçºé¨ã®ä½è¤ï¼@n_atmarkï¼ã§ãã æ®æ®µã¯ã¯ãã¯ããããã¼ãã®ã¢ãã¤ã«ã¢ããªéçºã«å¾äºãã¦ãã¾ãã ä»åãiOSã¢ããªã«å®è£
ãããUIè¦ç´ ã®ãã¬ã¼ã ããã¼ã¸ã³ãæ軽ã«ç¢ºèªã§ãããã¼ã«ãä½ã£ã¦ã¿ãã®ã§ãã®ç´¹ä»ãè¡ãã¾ãã åä½ãã¦ããç©ãè¦ã¦ããã ãã®ãåãããããã¨æãã®ã§ãæ©éã§ããåä½ã¤ã¡ã¼ã¸ããã¡ãã«ãªãã¾ãã ãã¬ã¼ã ã¤ã³ã¹ãã¯ã¿ã®åä½ã®æ§å (gif) ã¢ããªã«å®è£
ãããUIè¦ç´ ãé·æ¼ãããã¨ãã¹ã¯ãªã¼ã³ã¨ã®è·é¢ãUIè¦ç´ ã®ãµã¤ãºãè§ä¸¸ã®åå¾ã表示ããããã«ãã¦ãã¾ãã ã¾ãã2æ¬æã§äºã¤ã®UIè¦ç´ ãé·æ¼ãããã¨ãé·æ¼ãããUIè¦ç´ éã®ãâ¦
<p>ããã«ã¡ã¯ãã¯ãã¯ããããã¼ããããã¯ãéçºé¨ã®ä½è¤ï¼<a href="https://twitter.com/n_atmark">@n_atmark</a>ï¼ã§ãã</p>
<p>æ®æ®µã¯ã¯ãã¯ããããã¼ãã®ã¢ãã¤ã«ã¢ããªéçºã«å¾äºãã¦ãã¾ãã
ä»åãiOSã¢ããªã«å®è£
ãããUIè¦ç´ ã®ãã¬ã¼ã ããã¼ã¸ã³ãæ軽ã«ç¢ºèªã§ãããã¼ã«ãä½ã£ã¦ã¿ãã®ã§ãã®ç´¹ä»ãè¡ãã¾ãã</p>
<p>åä½ãã¦ããç©ãè¦ã¦ããã ãã®ãåãããããã¨æãã®ã§ãæ©éã§ããåä½ã¤ã¡ã¼ã¸ããã¡ãã«ãªãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="ãã¬ã¼ã ã¤ã³ã¹ãã¯ã¿ã®åä½ã®æ§å"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/n_atmark/20230914/20230914162704.gif" alt="フレームインスペクタの動作の様子" width="577" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:320px" itemprop="image"></span><figcaption>ãã¬ã¼ã ã¤ã³ã¹ãã¯ã¿ã®åä½ã®æ§å (<a href="https://cdn-ak.f.st-hatena.com/images/fotolife/n/n_atmark/20230913/20230913154929.gif">gif</a>)</figcaption></figure></p>
<p>ã¢ããªã«å®è£
ãããUIè¦ç´ ãé·æ¼ãããã¨ãã¹ã¯ãªã¼ã³ã¨ã®è·é¢ãUIè¦ç´ ã®ãµã¤ãºãè§ä¸¸ã®åå¾ã表示ããããã«ãã¦ãã¾ãã
ã¾ãã2æ¬æã§äºã¤ã®UIè¦ç´ ãé·æ¼ãããã¨ãé·æ¼ãããUIè¦ç´ éã®ãã¼ã¸ã³ã表示ããããã«ãã¦ãã¾ãã</p>
<h2 id="éçºã®èæ¯">éçºã®èæ¯</h2>
<p>ç§ãæ®æ®µéçºã«å¾äºãã¦ããã¯ãã¯ããããã¼ãiOSã¢ããªã§ã¯å
ã
5ã®åæ°ãã¼ã¸ã³ãæ¡ç¨ãã¦ããã®ã§ãããããã4ã®åæ°ãã¼ã¸ã³ã«å¤ãããã¨ããèæ¯ãããã¾ããã</p>
<p>å¾çºã®ã¯ãã¯ããããã¼ãAndroidã¢ããªã§4ã®åæ°ãã¼ã¸ã³ãæ¡ç¨ãã¦ããããã¶ã¤ãã¼ãç»é¢ãã¶ã¤ã³ãä½æããããã«5ã®åæ°ãã¼ã¸ã³ / 4ã®åæ°ãã¼ã¸ã³ãåãæ¿ãã¦ãã¶ã¤ã³ãä½ããªãã¨ãããªãã¨ãã課é¡ããããã©ã¡ããã«çµ±ä¸ãããã¨ããè¦æãããã¾ãããã¯ãã¯ããã社å
ã®ä»ã®iOSã¢ããªã§ã4ã®åæ°ãã¼ã¸ã³ãæ¡ç¨ãã¦ãããã¨ããããã¯ãã¯ããããã¼ãiOSã¢ããªã4ã®åæ°ãã¼ã¸ã³ã«åããããã¨ã«ãªãã¾ããã</p>
<p>ãããããã¼ã¸ã³å¤ãæ©æ¢°çã«ç½®ãæããã®ã¯é£ãããç¾å¨ã¯æ°ã¥ããç®æããå¾ã
ã«ç½®ãæãã¦ããæ¹éã§é²ãã¦ãã¾ãã
ã¯ãã¯ããããã¼ãiOSã¢ããªã«ã¯UIKit (AutoLayout) ã§ä½ãããç»é¢ãããã°SwiftUIã§ä½ãããç»é¢ãããããã¼ã¸ã³ãç´æ¥è¨å®ãã¦ããç®æãå¤æ°ã«ç½®ãã¦ããç®æãã¢ãã¡ã¼ã·ã§ã³ã®ããã«ãã¼ã¸ã³å¤ãåãæ¿ãã¦ããç®æãªã©ããããããçµ±ä¸ããæ¹æ³ã§ç½®ãæããã§ããªãããã§ããã¾ãã10ptã®ç®æã8ptã«ç½®ãæããã¹ãã12ptã«ç½®ãæããã¹ããã¨ãã£ãåé¡ãããã¾ãã</p>
<p>ããã§ããã¼ã¸ã³ã®éãã«æ°ã¥ããããããã¨ããç®çã§ä»åã®ãã¼ã«ãéçºãã¾ããã
QAæ
å½è
ããã¶ã¤ãã¼ãå®éã®ã¢ããªã®ç»é¢ãè¦ã¦æ°ã«ãªã£ãããã¼ã¸ã³ã®éåæãããµã¯ãã¨ç¢ºããããããããªä»çµã¿ã¨ãã¦ç¨æãã¦ãã¾ãã</p>
<h2 id="å®è£
ã®ç´¹ä»">å®è£
ã®ç´¹ä»</h2>
<p>å®è£
ã®å
¨ä½ã¯ <a href="https://gist.github.com/natmark/ef27845aff19059e74916df421223b79">https://gist.github.com/natmark/ef27845aff19059e74916df421223b79</a> ã«ç½®ãã¦ããã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synStatement">final</span> <span class="synPreProc">class</span> <span class="synIdentifier">DebugFrameInspectorView</span><span class="synSpecial">:</span> <span class="synType">UIView</span> {
<span class="synIdentifier">init</span>() {
<span class="synIdentifier">super</span>.<span class="synIdentifier">init</span>(frame<span class="synSpecial">:</span> .zero)
backgroundColor <span class="synIdentifier">=</span> .clear
isUserInteractionEnabled <span class="synIdentifier">=</span> <span class="synConstant">false</span>
}
<span class="synComment">// ⦠ç¥</span>
}
</pre>
<p><code>DebugFrameInspectorView</code> ããã¼ã¸ã³ããã¬ã¼ã ãµã¤ãºã表示ãã¦ããViewã§ãã <code>backgroundColor = .clear</code> ã㤠<code>isUserInteractionEnabled = false</code> ãªViewã¨ãªã£ã¦ãã¦ãããã <code>keyWindow</code> ã«å¯¾ã㦠<code>addSubView(_:)</code> ãã¦å©ç¨ãã¦ãããæ³å®ã§ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">func</span> <span class="synIdentifier">setup</span>() {
<span class="synPreProc">let</span> <span class="synIdentifier">singleLongPressGestureRecognizer</span> <span class="synIdentifier">=</span> UILongPressGestureRecognizer(target<span class="synSpecial">:</span> <span class="synType">self</span>, action<span class="synSpecial">:</span> #selector(didSingleLongPress(_<span class="synSpecial">:</span>)))
singleLongPressGestureRecognizer.minimumPressDuration <span class="synIdentifier">=</span> <span class="synConstant">0.2</span>
singleLongPressGestureRecognizer.numberOfTouchesRequired <span class="synIdentifier">=</span> <span class="synConstant">1</span>
window?.addGestureRecognizer(singleLongPressGestureRecognizer)
<span class="synPreProc">let</span> <span class="synIdentifier">doubleLongPressGestureRecognizer</span> <span class="synIdentifier">=</span> UILongPressGestureRecognizer(target<span class="synSpecial">:</span> <span class="synType">self</span>, action<span class="synSpecial">:</span> #selector(didDoubleLongPress(_<span class="synSpecial">:</span>)))
doubleLongPressGestureRecognizer.minimumPressDuration <span class="synIdentifier">=</span> <span class="synConstant">0.2</span>
doubleLongPressGestureRecognizer.numberOfTouchesRequired <span class="synIdentifier">=</span> <span class="synConstant">2</span>
window?.addGestureRecognizer(doubleLongPressGestureRecognizer)
}
</pre>
<p><code>setup()</code> ã¡ã½ããã®ä¸ã§ <code>UILongPressGestureRecognizer</code> ã <code>UIWindow</code> ã«è¿½å ãã¦é·æ¼ãã®ã¸ã§ã¹ãã£ã¼ãè£è¶³ã§ããããã«ãã¦ãã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synType">@objc</span> <span class="synType">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">didSingleLongPress</span>(_ sender<span class="synSpecial">:</span> <span class="synType">UILongPressGestureRecognizer</span>) {
<span class="synStatement">if</span> sender.state <span class="synIdentifier">==</span> .began {
<span class="synPreProc">let</span> <span class="synIdentifier">positionInWindow</span> <span class="synIdentifier">=</span> sender.location(<span class="synStatement">in</span><span class="synSpecial">:</span> <span class="synType">window</span>)
<span class="synStatement">if</span> <span class="synPreProc">let</span> <span class="synIdentifier">hitView</span> <span class="synIdentifier">=</span> window?.hitTest(positionInWindow, with<span class="synSpecial">:</span> <span class="synType">nil</span>) {
<span class="synPreProc">let</span> <span class="synIdentifier">positionInHitView</span> <span class="synIdentifier">=</span> sender.location(<span class="synStatement">in</span><span class="synSpecial">:</span> <span class="synType">hitView</span>)
<span class="synPreProc">let</span> <span class="synIdentifier">globalRect</span> <span class="synIdentifier">=</span> CGRect(
x<span class="synSpecial">:</span> <span class="synType">positionInWindow.x</span> <span class="synIdentifier">-</span> positionInHitView.x,
y<span class="synSpecial">:</span> <span class="synType">positionInWindow.y</span> <span class="synIdentifier">-</span> positionInHitView.y,
width<span class="synSpecial">:</span> <span class="synType">hitView.frame.size.width</span>,
height<span class="synSpecial">:</span> <span class="synType">hitView.frame.size.height</span>
)
singlePressValue <span class="synIdentifier">=</span> SinglePressValue(
viewWireframe<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(
rect<span class="synSpecial">:</span> <span class="synType">globalRect</span>,
cornerRadius<span class="synSpecial">:</span> <span class="synType">hitView.layer.cornerRadius</span>,
maskedCorners<span class="synSpecial">:</span> <span class="synType">hitView.layer.maskedCorners</span>
)
)
setNeedsDisplay()
}
} <span class="synStatement">else</span> <span class="synStatement">if</span> sender.state <span class="synIdentifier">==</span> .ended {
singlePressValue <span class="synIdentifier">=</span> <span class="synConstant">nil</span>
setNeedsDisplay()
}
}
<span class="synType">@objc</span> <span class="synType">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">didDoubleLongPress</span>(_ sender<span class="synSpecial">:</span> <span class="synType">UILongPressGestureRecognizer</span>) {
<span class="synComment">// ç¥</span>
}
</pre>
<p>é·æ¼ãæã®å¦çããã¡ãã«ãªãã¾ãã1æ¬æã§é·æ¼ãããã2æ¬æã§é·æ¼ããããã«ãã£ã¦ <code>didSingleLongPress(_:)</code> <code>didDoubleLongPress(_:)</code> ã¨å®è£
ãåãã¦ãã¾ãããå
容ã¨ãã¦ã¯ã»ã¼åãå¦çã«ãªãã¾ãã</p>
<p><code>let positionInWindow = sender.location(in: window)</code> 㧠<code>keyWindow</code> å
ã«ãããã¿ããä½ç½®ãåå¾ããå¾ <code>hitTest(_:with:)</code> ãç¨ãã¦ã¿ãããããViewãç¹å®ãã¦ãã¾ãã
(<code>let hitView = window?.hitTest(positionInWindow, with: nil)</code> ã®ç®æ)</p>
<p>ãã®å¾ã¿ãããããViewã®frame (superviewãåºæºã«ããç¸å¯¾ä½ç½®) ã <code>keyWindow</code> å
ã«ããã座æ¨ã«å¤æãããã®ã§ã<code>hitView</code> å
ã§ã®ã¿ããä½ç½®ã®åº§æ¨ãåå¾ã ( <code>let positionInHitView = sender.location(in: hitView)</code> ã®ç®æ) ã <code>positionInWindow</code> ãã <code>positionInHitView</code> ã®åº§æ¨åããããã¨ã§ <code>keyWindow</code> å
ã«ããã座æ¨ãåå¾ãã¦ãã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">let</span> <span class="synIdentifier">globalRect</span> <span class="synIdentifier">=</span> CGRect(
x<span class="synSpecial">:</span> <span class="synType">positionInWindow.x</span> <span class="synIdentifier">-</span> positionInHitView.x,
y<span class="synSpecial">:</span> <span class="synType">positionInWindow.y</span> <span class="synIdentifier">-</span> positionInHitView.y,
width<span class="synSpecial">:</span> <span class="synType">hitView.frame.size.width</span>,
height<span class="synSpecial">:</span> <span class="synType">hitView.frame.size.height</span>
)
</pre>
<p><code>singlePressValue</code> ã<code>doublePressValue</code> ã¨ããå¤æ°ã«å¤ãä¿æãã¦ãã㦠<code>setNeedsDisplay()</code> ãå¼ã³åºããã¨ã§ <code>draw(_:)</code> ã¡ã½ãããå¼ã³åºãããã¬ã¼ã å¢çããã¼ã¸ã³ãªã©ã®ç·ã®æç»ãè¡ã£ã¦ãã¾ãã</p>
<p>ç·ã®æç»ã«é¢ãã¦ã¯è©³ãã触ãã¾ããããCoreGraphicsãç¨ãã¦ã´ãªã´ãªè¨è¿°ãã¦ãã¾ãã</p>
<h2 id="å©ç¨æ¹æ³">å©ç¨æ¹æ³</h2>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">/// SceneDelegate.swift</span>
<span class="synPreProc">func</span> <span class="synIdentifier">scene</span>(_ scene<span class="synSpecial">:</span> <span class="synType">UIScene</span>, willConnectTo session<span class="synSpecial">:</span> <span class="synType">UISceneSession</span>, options connectionOptions<span class="synSpecial">:</span> <span class="synType">UIScene.ConnectionOptions</span>) {
<span class="synStatement">guard</span> <span class="synPreProc">let</span> <span class="synIdentifier">windowScene</span> <span class="synIdentifier">=</span> (scene <span class="synStatement">as?</span> <span class="synType">UIWindowScene</span>) <span class="synStatement">else</span> { <span class="synStatement">return</span> }
<span class="synPreProc">let</span> <span class="synIdentifier">window</span><span class="synSpecial">:</span> <span class="synType">UIWindow</span>
<span class="synComment">// 以ä¸ã¯ã¢ããªã®æ§æã«ãã£ã¦å¤ããã®ã§å¿
è¦ç®æã ãå
¥ãã¦ãã ãã</span>
<span class="synStatement">if</span> <span class="synPreProc">let</span> <span class="synIdentifier">keyWindow</span> <span class="synIdentifier">=</span> windowScene.keyWindow {
<span class="synComment">// keyWindowãããå ´å (SwiftUI.Appå©ç¨æ)</span>
window <span class="synIdentifier">=</span> keyWindow
} <span class="synStatement">else</span> <span class="synStatement">if</span> <span class="synPreProc">let</span> <span class="synIdentifier">firstWindow</span> <span class="synIdentifier">=</span> windowScene.windows.first {
<span class="synComment">// keyWindowã¯ãªãããwindowãåå¨ããå ´å (Main Storyboardå©ç¨æ)</span>
window <span class="synIdentifier">=</span> firstWindow
window.makeKeyAndVisible()
} <span class="synStatement">else</span> {
<span class="synComment">// windowèªä½åå¨ããªãå ´å (Storyboardä¸ä½¿ç¨æ)</span>
window <span class="synIdentifier">=</span> UIWindow(windowScene<span class="synSpecial">:</span> <span class="synType">windowScene</span>)
window.makeKeyAndVisible()
window.rootViewController <span class="synIdentifier">=</span> MyRootViewController()
<span class="synIdentifier">self</span>.window <span class="synIdentifier">=</span> window
}
<span class="synPreProc"> #if</span> DEBUG
<span class="synPreProc">let</span> <span class="synIdentifier">debugFrameInspectorView</span> <span class="synIdentifier">=</span> DebugFrameInspectorView()
window.addSubview(debugFrameInspectorView)
debugFrameInspectorView.translatesAutoresizingMaskIntoConstraints <span class="synIdentifier">=</span> <span class="synConstant">false</span>
NSLayoutConstraint.activate([
window.leadingAnchor.constraint(equalTo<span class="synSpecial">:</span> <span class="synType">debugFrameInspectorView.leadingAnchor</span>),
window.trailingAnchor.constraint(equalTo<span class="synSpecial">:</span> <span class="synType">debugFrameInspectorView.trailingAnchor</span>),
window.topAnchor.constraint(equalTo<span class="synSpecial">:</span> <span class="synType">debugFrameInspectorView.topAnchor</span>),
window.bottomAnchor.constraint(equalTo<span class="synSpecial">:</span> <span class="synType">debugFrameInspectorView.bottomAnchor</span>),
])
debugFrameInspectorView.setup()
<span class="synPreProc"> #endif</span>
}
</pre>
<p><code>keyWindow</code> ã¨ãªãUIWindowã«å¯¾ã㦠<code>addSubView(_:)</code> ããå¾ã <code>setup()</code> ãå¼ã³åºããã¨ã§å©ç¨ã§ãã¾ãã</p>
<p>ãããã°ç¨ã®æ©è½ãªã®ã§Compilation conditionsã使ã£ã¦build configurationã <code>DEBUG</code> ã®æã®ã¿æå¹ã«ãããªã©ãã¦ãããã¨ããªã¹ã¹ã¡ãã¾ãã</p>
<h2 id="å®éã«éçºãããã¼ã«ã使ã£ã¦ããã£ã¦">å®éã«éçºãããã¼ã«ã使ã£ã¦ããã£ã¦</h2>
<p>éçºã®èæ¯ã®ç®æã«ãããéããä»åã®ãã¼ã«ã®ç®çã¨ãã¦ã¯QAæ
å½è
ããã¶ã¤ãã¼ãå®éã®ã¢ããªã®ç»é¢ãè¦ã¦æ°ã«ãªã£ãããã¼ã¸ã³ã®éåæãããµã¯ãã¨ç¢ºããããããããªä»çµã¿ãç¨æãããã¨ã§ããã</p>
<p>ãã¼ã ã¡ã³ãã¼ã«å®éã«ä½¿ã£ã¦ãããã¨ã以ä¸ã®ãããªã³ã¡ã³ãããããã¾ããã</p>
<h4 id="ãã¸ãã£ããªæè¦">ãã¸ãã£ããªæè¦</h4>
<ul>
<li>(ã¨ã³ã¸ãã¢) å®éã®ã¢ããªã®ãã¼ã¸ã³ãå®è£
ãè¦ã«è¡ããªãã¦ã確ãããããã®ã¯å¬ãã</li>
<li>(ãã¶ã¤ãã¼) Webã ã¨ã¤ã³ã¹ãã¯ã¿ã§ããè¦ç´ ã確èªã§ããã®ã«ãã¤ãã£ãã¢ããªã ã¨è¦ããã¨ãã§ããªãã®ã§ãã¢ããªã§ããã¬ã¼ã å¤ã確èªã§ããã®ãããããã</li>
</ul>
<h4 id="追å è¦æ">追å è¦æ</h4>
<ul>
<li>(ãã¶ã¤ãã¼) å®è£
ããã¦ãããã©ã³ããµã¤ãºãè²ã確èªã§ããã¨å¬ãã</li>
</ul>
<h4 id="ãã¬ãã£ããªæè¦">ãã¬ãã£ããªæè¦</h4>
<ul>
<li>(ã¨ã³ã¸ãã¢) ãã¿ã³ã®é åãé·æ¼ãããæã«ããã¿ã³å
ã®è¦ç´ ã®ãã¬ã¼ã ãµã¤ãºãè¦ããªã</li>
<li>(ã¨ã³ã¸ãã¢) ãã¬ã¼ã ãµã¤ãºãå®è£
ãè¡ã£ãéã«æå³ãããµã¤ãºã¨éã£ã¦è¡¨ç¤ºããããã¨ããã</li>
</ul>
<p>å®è£
ãããã¢ããªä¸ã§ãã¼ã¸ã³å¤ããµã¯ãã¨ç¢ºããããããã¨ã«å¯¾ãã¦ä¸å®å¹æããããããªãã¨ãåããã¾ããã
ã¾ãããã©ã³ããµã¤ãºãè²ã確èªã§ããã¨ã¢ããªã«å®è£
ããããã¶ã¤ã³ãæ£ãããã©ããã確èªããç¨éã§ããã便å©ã«ä½¿ãããã§ãã</p>
<p>ä¸æ¹ã§ãåå¾ã«éãªã£ãè¦ç´ ã«å¯¾ãã¦ã¯ãã¬ã¼ã ãµã¤ãºã®ç¢ºèªããã¾ãã§ããªãã¨ãã課é¡ããå®éã®å®è£
å¤ã¨è¡¨ç¤ºãããå¤ãéããã¨ãããã¨ãã課é¡ãæµ®ã彫ãã«ãªãã¾ããã</p>
<h2 id="å®è£
ãé£ããé¨åã®ç´¹ä»">å®è£
ãé£ããé¨åã®ç´¹ä»</h2>
<p>ããã¾ã§éçºãããã¬ã¼ã ã¤ã³ã¹ãã¯ã¿ã®ç´¹ä»ããã¾ããããã§ããªããã¨ãçµæ§ããã¾ãã
å
ã»ã©ãã¼ã ã¡ã³ãã¼ããè¦æã®ãã£ããå®è£
ããã¦ãããã©ã³ããµã¤ãºãè²ãã¨ãã£ãè¦ç´ ãããå®è£
å¤ã¨è¡¨ç¤ºå¤ãéããã¨ãã課é¡ã®è§£æ±ºãç¾ç¶é£ããã¨æãã¦ããç¹ã§ãã</p>
<p>ãã®é£ããã®å
ã«ãªãã®ãSwiftUIã§å®è£
ãããç»é¢ã§ãã</p>
<p>UIKitã¨SwiftUIã§ä»¥ä¸ã®ãããªç»é¢ãå®è£
ããã¨ãã¾ãã</p>
<h3 id="UIKit">UIKit</h3>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/n_atmark/20230913/20230913162229.png" width="1200" height="1016" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h3 id="SwiftUI">SwiftUI</h3>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/n_atmark/20230913/20230913162238.png" width="1200" height="868" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p><code>UIWindow</code> ã® <code>subViews</code> ã辿ã£ã¦é層æ§é ã表示ããã¨ããããä¸ã®ããã«ãªãã¾ãã</p>
<table>
<tr>
<th>UIKit</th>
<th>SwiftUI</th>
</tr>
<tr>
<td>
UIWindow<br>
â UITransitionView<br>
â UIDropShadowView<br>
â UIView<br>
â UIStackView<br>
â UIStackView<br>
â â UIImageView (<font color="red">â¡</font>)<br>
â â UILabel (<b>ã¿ã¤ãã«</b>)<br>
â UILabel (ããã¹ãããã¹ãããã¹ã)<br>
</td>
<td>
UIWindow<br>
â UITransitionView<br>
â UIDropShadowView<br>
â _UIHostingView<ModifiedContent<AnyView, RootModifier>><br>
â _UIGraphicsView (<font color="red">â¡</font>)<br>
â CGDrawingView (<b>ã¿ã¤ãã«</b>)<br>
â CGDrawingView (ããã¹ãããã¹ãããã¹ã)<br>
</td>
</tr>
</table>
<p>UIKitã®å ´å㯠<code>UIImageView</code> ã <code>UILabel</code> ã¨ãã£ãã馴æã¿ã®Viewã¯ã©ã¹ãªã®ã§ã <code>UIView</code> åã® <code>subview</code> ããã£ã¹ãããã°ç°¡åã«ããããã£ã確èªã§ãã¾ãããSwiftUIã®å ´åã¯SwiftUIã®Viewæ§é ã§ã¯ãªããæç»ç¨ã®ã¯ã©ã¹ã§ãã <code>SwiftUI._UIGraphicsView</code> ã <code>SwiftUI.DisplayList.ViewUpdater.Platform.CGDrawingView</code> ã¨ãã£ãprivateãªã¯ã©ã¹ãå©ç¨ããã¾ãã</p>
<p>ãããä¸ã¤ç®ã®é£ãããã¤ã³ãã§ãSwiftUIã§Viewãçµã¿ç«ã¦ãæã«ã©ããã£ãModifierãé©ç¨ããããã¨ãã£ãæ
å ±ãæããªããããããããããã©ã³ããµã¤ãºãè¨å®ãããè²ãåãåºããã¨ã¯å°é£ã§ãã</p>
<p>ã¾ããUIKitã§ã¯Viewã®é層æ§é ãä¿æãããã®ã«å¯¾ãã¦ãSwiftUIã§ã¯ <code>_UIHostingView</code> ã¨ããã¯ã©ã¹ã®é
ä¸ã«ãã©ããã«å±éããã¦ãã¾ãã¾ãã
SwiftUIã®Viewãçµã¿ç«ã¦ãæã«å©ç¨ãã <code>VStack</code> ã <code>HStack</code> ã¯ããã¬ã¼ã ã®æ±ºå®ã ãã«ç¨ãããUIViewã®ä¸çã«ããã¦ã¯ç¾ãã¾ããã
ãããäºã¤ç®ã®é£ãããã¤ã³ãã§ãã</p>
<p>ã<code>VStack</code> ã <code>HStack</code> ã¯ããã¬ã¼ã ã®æ±ºå®ã ãã«ç¨ãããUIViewã®ä¸çã«ããã¦ã¯ç¾ããªããã¨ããã®ãã©ãé£ããã«ã¤ãªãã£ã¦ããã®ã説æããããã«ä»¥ä¸ã®å³ãç¨æãã¦ã¿ã¾ããã</p>
<table>
<thead>
<tr>
<th style="text-align:center;">UIKit</th>
<th style="text-align:center;">SwiftUI</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center;"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/n_atmark/20230913/20230913163324.png" width="554" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:320px" itemprop="image"></span></td>
<td style="text-align:center;"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/n_atmark/20230913/20230913163305.png" width="554" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:320px" itemprop="image"></span></td>
</tr>
</tbody>
</table>
<p>ãã㯠<code>UIWindow</code> ã® <code>subviews</code> ã辿ã£ã¦ããããã® <code>CALayer</code> ã«å¯¾ãã¦æ ç·ã表示ãããã®ã§ãã</p>
<p>SwiftUIå´ã®å®è£
ã§ãã<b>ã¿ã¤ãã«</b>ãããã³ãããã¹ãããã¹ãããã¹ããã«ã¯ <code>.frame(maxWidth: .infinity, alignment: .leading)</code> ãã¤ãã¦ãããã®ã® <code>CGDrawingView</code> ã表ç¾ãããã¬ã¼ã ã¯ããã¹ããæåå¹
ã«ç¸®ãã§ãã¾ã£ã¦ãããã¨ããããã¨æãã¾ãã</p>
<p>å
ã
VStackã«è¨å®ãã¦ãã <code>.padding(.horizontal, 20)</code> ã®ãã¡ã<code>trailing</code> å´ã«é¢ãã¦ã¯æ£ããå¹ãã¦ãããã©ãããä»åã®ãã¬ã¼ã ã¤ã³ã¹ãã¯ã¿ã§ã¯ä¸æã確èªãããã¨ãã§ãã¾ããã</p>
<h3 id="è£è¶³">è£è¶³</h3>
<p>ã¡ãªã¿ã« <code>VStack</code> ããã³ <code>HStack</code> ã« <code>.background(Color.white)</code> ã追å ããã¨VStack/HStackã®é åãæç»ãããä¸ã®ããã«ç»é¢å¹
ã«ãã¬ã¼ã ãåºãã£ã¦ããã®ã確èªã§ãã¾ãã</p>
<table>
<tr>
<th>Viewæ§é </th>
<th>ãã¬ã¼ã ã¬ã¤ã¢ã¦ã</th>
</tr>
<tr>
<td>
UIWindow<br>
â UITransitionView<br>
â UIDropShadowView<br>
â _UIHostingView<ModifiedContent<AnyView, RootModifier>><br>
â _UIGraphicsView (HStackã®background)<br>
â _UIGraphicsView (VStackã®background)<br>
â _UIGraphicsView (<font color="red">â¡</font>)<br>
â CGDrawingView (<b>ã¿ã¤ãã«</b>)<br>
â CGDrawingView (ããã¹ãããã¹ãããã¹ã)<br>
</td>
<td>
<span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/n_atmark/20230913/20230913163313.png" width="554" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:320px" itemprop="image"></span>
</td>
</tr>
</table>
<h2 id="ä»å¾ã®å±æ">ä»å¾ã®å±æ</h2>
<p>ä»åä½ã£ããã¬ã¼ã ã¤ã³ã¹ãã¯ã¿ã§ã¯SwiftUIã§ä½ã£ãç»é¢è¡¨ç¤ºã«ã¾ã 課é¡ããããã¨ãç´¹ä»ãã¾ããã
ã¨ããã§Xcodeã«ã¯ <code>Debug View Hierarchy</code> ã¨ãããããã°æ©è½ããããã¨ã¯ããç¥ããã¦ããã¨æãã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/n_atmark/20230913/20230913163813.png" width="1200" height="651" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ãã® <code>Debug View Hierarchy</code> ãç¨ããã¨SwiftUIã§éçºãããç»é¢ã«é¢ãã¦ã <code>Horizontal Stack</code> ã <code>Text</code> ã¨ãã£ãSwiftUIã§çµã¿ç«ã¦ãViewã®æ§é ããããã¹ãã®ãã©ã³ãã¨ãã£ãModifierã確èªãããã¨ãã§ãã¾ãã</p>
<p>ã©ãã«ã <code>Debug View Hierarchy</code> ã§è¡¨ç¤ºãã¦ãããããªæ
å ±ãåå¾ã§ããã¨ãä»åéçºãããã¬ã¼ã ã¤ã³ã¹ãã¯ã¿ã«æ©è½è¿½å ãããã¨ãã§ãããã§ãã</p>
<h3 id="_UIHostingView_viewDebugData"><code>_UIHostingView._viewDebugData()</code></h3>
<p><code>SwiftUI._UIHostingView</code> ã«éå
¬éAPIã¨ã㦠<code>_viewDebugData()</code> ã¨ããã¡ã½ãããåå¨ãã¾ãããããç¨ããã¨ãããã°ç¨ã«SwiftUIã®Viewæ§é ã解æã§ãããã§ãã (<a href="https://apurin.me/articles/swiftui-secrets/">https://apurin.me/articles/swiftui-secrets/</a> ãåèã«ããã¦ããã ãã¾ãã)</p>
<p>SwiftUIã®Viewæ§é ãããä¸åº¦ç¤ºãã®ã§ãã</p>
<pre></code>UIWindow
â UITransitionView
â UIDropShadowView
â _UIHostingView<ModifiedContent<AnyView, RootModifier>>
â _UIGraphicsView (<font color="red">â¡</font>)
â CGDrawingView (<b>ã¿ã¤ãã«</b>)
â CGDrawingView (ããã¹ãããã¹ãããã¹ã)
</code></pre>
<p>é層ã辿ããã¨ã§ <code>_UIHostingView</code> ãåå¾ã§ãããã§ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">ViewDebugData</span> {
<span class="synPreProc">let</span> <span class="synIdentifier">data</span><span class="synSpecial">:</span> <span class="synSpecial">[</span><span class="synType">_ViewDebug.Property</span><span class="synSpecial">:</span><span class="synType"> Any</span><span class="synSpecial">]</span>
<span class="synPreProc">let</span> <span class="synIdentifier">childData</span><span class="synSpecial">:</span> <span class="synSpecial">[</span><span class="synType">ViewDebugData</span><span class="synSpecial">]</span>
}
<span class="synPreProc">protocol</span> <span class="synIdentifier">DebuggableSwiftUIView</span> {
<span class="synPreProc">func</span> <span class="synIdentifier">viewDebugData</span>() <span class="synSpecial">-></span> <span class="synSpecial">[</span><span class="synType">ViewDebugData</span><span class="synSpecial">]</span>
}
<span class="synPreProc">extension</span> <span class="synIdentifier">_UIHostingView</span><span class="synSpecial">:</span> <span class="synType">DebuggableSwiftUIView</span> {
<span class="synPreProc">func</span> <span class="synIdentifier">viewDebugData</span>() <span class="synSpecial">-></span> <span class="synSpecial">[</span><span class="synType">ViewDebugData</span><span class="synSpecial">]</span> {
<span class="synPreProc">let</span> <span class="synIdentifier">_viewDebugData</span> <span class="synIdentifier">=</span> _viewDebugData()
<span class="synStatement">return</span> unsafeBitCast(_viewDebugData, to<span class="synSpecial">:</span> <span class="synSpecial">[</span><span class="synType">ViewDebugData</span><span class="synSpecial">]</span>.<span class="synIdentifier">self</span>)
}
}
</pre>
<p>DebuggableSwiftUIView ã¨ãããããã³ã«ãç¨æã㦠<code>_UIHostingView</code> ã«æºæ ããã¦ãã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synStatement">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">digDebuggableSwiftUIView</span>(from view<span class="synSpecial">:</span> <span class="synType">UIView</span>) <span class="synSpecial">-> (</span><span class="synType">any DebuggableSwiftUIView</span><span class="synSpecial">)</span>? {
<span class="synStatement">if</span> <span class="synPreProc">let</span> <span class="synIdentifier">debuggableView</span> <span class="synIdentifier">=</span> view <span class="synStatement">as?</span> <span class="synType">DebuggableSwiftUIView</span> {
<span class="synStatement">return</span> debuggableView
} <span class="synStatement">else</span> {
<span class="synStatement">for</span> subView <span class="synStatement">in</span> view.subviews {
<span class="synStatement">if</span> <span class="synPreProc">let</span> <span class="synIdentifier">debuggableView</span> <span class="synIdentifier">=</span> debuggableView(from<span class="synSpecial">:</span> <span class="synType">subView</span>) {
<span class="synStatement">return</span> debuggableView
}
}
<span class="synStatement">return</span> <span class="synConstant">nil</span>
}
}
</pre>
<p>DebuggableSwiftUIViewã«æºæ ããViewãæ¢ç´¢ããã¡ã½ãããç¨æãã¦</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synStatement">guard</span> <span class="synPreProc">let</span> <span class="synIdentifier">window</span> <span class="synStatement">else</span> { <span class="synStatement">return</span> }
<span class="synPreProc">let</span> <span class="synIdentifier">viewDebugData</span> <span class="synIdentifier">=</span> digDebuggableSwiftUIView(from<span class="synSpecial">:</span> <span class="synType">window</span>)?.viewDebugData()
print(viewDebugData)
</pre>
<p><code>viewDebugData()</code> ãå¼ã³åºããã¨ã§ã<code>_UIHostingView._viewDebugData()</code> ã®çµæã確èªã§ãããã§ãã</p>
<p><code>_viewDebugData()</code> ã®åºåã¯ããªã大ããã®ã§åºåã®å
¨ä½ã¯ <a href="https://gist.github.com/natmark/0c13ded1ae0bf97f1f4bbd991f9e0118">https://gist.github.com/natmark/0c13ded1ae0bf97f1f4bbd991f9e0118</a> ã«ç½®ãã¦ããã¾ãã</p>
<p><code>SwiftUI._ViewDebug.Property.type</code> ã®é¨åã ãé層表示ããã¨</p>
<pre><code><font size="-3">_SafeAreaInsetsModifier
â ModifiedContent<ModifiedContent<ModifiedContent<AnyView, RootModifier>, EditModeScopeModifier>, HitTestBindingModifier>
â HitTestBindingModifier
â ModifiedContent<ModifiedContent<AnyView, RootModifier>, EditModeScopeModifier>
â EditModeScopeModifier
â ModifiedContent<_ViewModifier_Content<EditModeScopeModifier>, TransformModifier>
â TransformModifier
â _ViewModifier_Content<EditModeScopeModifier>
â ModifiedContent<AnyView, RootModifier>
â RootModifier
â ModifiedContent<ModifiedContent<_ViewModifier_Content<RootModifier>, RootEnvironmentModifier>, PresentedSceneValueInputModifier>
â PresentedSceneValueInputModifier
â ModifiedContent<_ViewModifier_Content<RootModifier>, RootEnvironmentModifier>
â RootEnvironmentModifier
â _ViewModifier_Content<RootModifier>
â AnyView
â ContentView
â ModifiedContent<VStack<TupleView<(HStack<TupleView<(ModifiedContent<ModifiedContent<Image, _EnvironmentKeyWritingModifier<Optional<Color>>>, _FrameLayout>, ModifiedContent<Text, _FlexFrameLayout>)>>, ModifiedContent<Text, _FlexFrameLayout>)>>, _PaddingLayout>
â _PaddingLayout
â VStack<TupleView<(HStack<TupleView<(ModifiedContent<ModifiedContent<Image, _EnvironmentKeyWritingModifier<Optional<Color>>>, _FrameLayout>, ModifiedContent<Text, _FlexFrameLayout>)>>, ModifiedContent<Text, _FlexFrameLayout>)>>
â Tree<_VStackLayout, TupleView<(HStack<TupleView<(ModifiedContent<ModifiedContent<Image, _EnvironmentKeyWritingModifier<Optional<Color>>>, _FrameLayout>, ModifiedContent<Text, _FlexFrameLayout>)>>, ModifiedContent<Text, _FlexFrameLayout>)>>
â _FlexFrameLayout
â â Text
â â AccessibilityStyledTextContentView
â â ModifiedContent<ModifiedContent<StyledTextContentView, AccessibilityAttachmentModifier>, AccessibilityLargeContentViewModifier<Text>>
â â AccessibilityLargeContentViewModifier<Text>
â â ModifiedContent<StyledTextContentView, AccessibilityAttachmentModifier>
â â AccessibilityAttachmentModifier
â â StyledTextContentView
â HStack<TupleView<(ModifiedContent<ModifiedContent<Image, _EnvironmentKeyWritingModifier<Optional<Color>>>, _FrameLayout>, ModifiedContent<Text, _FlexFrameLayout>)>>
â Tree<_HStackLayout, TupleView<(ModifiedContent<ModifiedContent<Image, _EnvironmentKeyWritingModifier<Optional<Color>>>, _FrameLayout>, ModifiedContent<Text, _FlexFrameLayout>)>>
â _FlexFrameLayout
â â Text
â â AccessibilityStyledTextContentView
â â ModifiedContent<ModifiedContent<StyledTextContentView, AccessibilityAttachmentModifier>, AccessibilityLargeContentViewModifier<Text>>
â â AccessibilityLargeContentViewModifier<Text>
â â ModifiedContent<StyledTextContentView, AccessibilityAttachmentModifier>
â â AccessibilityAttachmentModifier
â â StyledTextContentView
â _FrameLayout
â Image
â ModifiedContent<Resolved, AccessibilityAttachmentModifier>
â AccessibilityAttachmentModifier
â Resolved
</font></code></pre>
<p>ã®ããã«ãªã£ã¦ãããViewã®é層æ§é ãåãããã§ãã
ã¾ãã<code>SwiftUI._ViewDebug.Property.type: SwiftUI.Text</code> ã®ç®æã ãããã¯ã¢ãããã¦ã¿ãã¨</p>
<pre class="code" data-lang="" data-unlink>SampleSwiftUIView.ViewDebugData(
data: [
SwiftUI._ViewDebug.Property.value: SwiftUI.Text(
storage: SwiftUI.Text.Storage.anyTextStorage(<LocalizedTextStorage: 0x00006000017f54a0>: \"ããã¹ãããã¹ãããã¹ã\"),
modifiers: [SwiftUI.Text.Modifier.font(Optional(SwiftUI.Font(provider: SwiftUI.(unknown context at $105d9f910).FontBox<SwiftUI.Font.(unknown context at $105e5aae8).SystemProvider>)))]
),
SwiftUI._ViewDebug.Property.type: SwiftUI.Text,
SwiftUI._ViewDebug.Property.size: (191.0, 20.333333333333332),
SwiftUI._ViewDebug.Property.position: (20.0, 442.5)
],
childData: [] // ç¥
)</pre>
<p>Modifierã«é¢ãã¦ã確èªãããã¨ãã§ãã¾ããã</p>
<p><code>_viewDebugData</code> ã®å
容ãå®éã«ãããã°ãã¼ã«ã«æ´»ç¨ãããã¨æãã¨ããªã骨ãæããä½æ¥ã«ãªãããã§ãããSwiftUIã®Viewæ§é ãããã¾ã§è©³ç´°ã«åå¾ã§ããã°ãViewã®ãããã°ãã¼ã«ãä½ãã«ããã£ã¦ã§ãããã¨ã¯åºããããã§ãã</p>
<h2 id="ã¾ã¨ã">ã¾ã¨ã</h2>
<p>ä»åãã¢ããªã«å®è£
ãããUIè¦ç´ ã®ãã¬ã¼ã ãµã¤ãºããã¼ã¸ã³ãç°¡åã«ç¢ºèªã§ãããã¼ã«ãä½ã£ã¦ç´¹ä»ãã¾ããã</p>
<p>ãã¼ã ã¡ã³ãã¼ã«ä½¿ã£ã¦ããã£ã¦ããµã¯ãã¨ãã¬ã¼ã ãµã¤ãºããã¼ã¸ã³ã確èªããç¨éã§ããã°ä¾¿å©ã«ä½¿ããããªãã¨ãåãã£ãä¸æ¹ã§ãããå®æ度ã®é«ããã¼ã«ãç®æããã¨ããéã«ãSwiftUI製ã®Viewã®ã¤ã³ã¹ãã¯ã¿å®è£
ã¯é£ããé¨åãå¤ããã¨ãåããã¾ããã</p>
<p><code>_UIHostingView</code> ã®private APIã§ãã <code>_viewDebugData()</code> ã使ãã¨è©³ç´°ãªSwiftUIã®Viewæ§é ãå©ç¨ã§ããããªãã¨åãã£ãããã <code>_viewDebugData()</code> ã®ãã¼ã¿ãæ´»ç¨ãããããã°ãã¼ã«ã®æ¹åã«é¢ãã¦ãå¼ãç¶ãæ¤è¨ãã¦ã¿ããã¨èãã¦ãã¾ãã</p>
<p>ä»åã®è¨äºãå¿«é©ãªãããã°ç°å¢æ§ç¯ã®åèã«ãªãã°å¬ããã§ãã</p>
n_atmark
ã¯ãã¼ãºãããµã¼ãã¹ã®ç®¡çç»é¢ãéçãµã¤ãã«ãã
hatenablog://entry/820878482963625603
2023-09-01T10:04:02+09:00
2023-09-06T14:53:37+09:00 ããã«ã¡ã¯ãæè¡é¨ã®ç³å·ã§ãã ããæ¥ã社å
ã®å種ã¢ããªã±ã¼ã·ã§ã³ãçºãã¦ããä¸ã§ãã¨ããã¯ãã¼ãºãããµã¼ãã¹ã®ç®¡çç»é¢ãæ
ã£ã¦ããã¦ã§ãã¢ããªãä»ãåãã¦ããã¨æ°ä»ãã¾ãããç°¡åã«ãã¢ãªã³ã°ããã¨ããããµã¼ãã¹èªä½ã¯ã¯ãã¼ãºãããã®ã®ãä¿æãã¦ãããã¼ã¿ã次ã®ãã£ã¬ã³ã¸ã«çããããã管çç»é¢ã ãæ®ãã¦ããã¨ã®ãã¨ã§ããã ä¸æ¹ã§ããã®ç®¡çç»é¢ã¸ã®ã¢ã¯ã»ã¹ã¯ããå¤ãããã¾ããã§ãããæ¯æ¥ã¡ãã£ã¨ã ãã®ãªã¯ã¨ã¹ããå¦çããããã ãã«ãã¼ã¿ãã¼ã¹ã¨ãµã¼ãã¼ãåãã¦ãããå°ãç¡é§ãããç¶æ
ã«ãªã£ã¦ãã¾ããã ããæ°ã«ãªã£ãã®ã§æ¤è¨ããçµæãæçµçã«ãã®ç®¡çç»é¢ã¢ããªã Next.js 製ã®éçãªãã¼â¦
<p>ããã«ã¡ã¯ãæè¡é¨ã®ç³å·ã§ãã</p>
<p>ããæ¥ã社å
ã®å種ã¢ããªã±ã¼ã·ã§ã³ãçºãã¦ããä¸ã§ãã¨ããã¯ãã¼ãºãããµã¼ãã¹ã®ç®¡çç»é¢ãæ
ã£ã¦ããã¦ã§ãã¢ããªãä»ãåãã¦ããã¨æ°ä»ãã¾ãããç°¡åã«ãã¢ãªã³ã°ããã¨ããããµã¼ãã¹èªä½ã¯ã¯ãã¼ãºãããã®ã®ãä¿æãã¦ãããã¼ã¿ã次ã®ãã£ã¬ã³ã¸ã«çããããã管çç»é¢ã ãæ®ãã¦ããã¨ã®ãã¨ã§ããã</p>
<p>ä¸æ¹ã§ããã®ç®¡çç»é¢ã¸ã®ã¢ã¯ã»ã¹ã¯ããå¤ãããã¾ããã§ãããæ¯æ¥ã¡ãã£ã¨ã ãã®ãªã¯ã¨ã¹ããå¦çããããã ãã«ãã¼ã¿ãã¼ã¹ã¨ãµã¼ãã¼ãåãã¦ãããå°ãç¡é§ãããç¶æ
ã«ãªã£ã¦ãã¾ããã</p>
<p>ããæ°ã«ãªã£ãã®ã§æ¤è¨ããçµæãæçµçã«ãã®ç®¡çç»é¢ã¢ããªã Next.js 製ã®éçãªãã¼ã¿ãã¥ã¼ã¯ã¼ãµã¤ãã¨ãã¦ãªãã¥ã¼ã¢ã«ãã社å
åãã® GitHub Pages ã¨ãã¦æä¾ããã¦ããç¶æ
ã«ã§ãã¾ããããã®è¨äºã§ã¯ãã®é¡æ«ããç´¹ä»ãã¾ãã</p>
<h2 id="æè¡é¸å®">æè¡é¸å®</h2>
<p>ããã¤ãäºå調æ»ãããçµæãä»åã®ç®¡çç»é¢ã«ã¤ãã¦ä»¥ä¸ã®ãã¨ãåããã¾ããã</p>
<ul>
<li>Rails ãåãã¦ããããµã¼ãã¼å´ã§ã¯ graphql-ruby ãå©ç¨ãã GraphQL API ãåãã¦ãã¦ãã¦ã§ãããã³ãã¨ã³ãå´ã§ã¯ç´ ã® React ã API ã«ãªã¯ã¨ã¹ããè¡ããªãããã¼ã¸ãä½ã£ã¦ããããã¼ã¿ãã¼ã¹ã¯ PostgreSQL (Amazon RDS for PostgreSQL)ã</li>
<li>ãã¼ã¿éã¯ããã¾ã§å¤ããªãããç®ã§å
¨ä»¶ç¢ºèªã§ããã»ã©å°ãªãããªãã</li>
<li>ãã¼ã¸ã®ç¨®å¥ã¯ãRails ã® <code>app/javascript/pages</code> ä¸ã«ãã index.tsx ãæ°ãã¦ã¿ã㨠80 ç¨åº¦ã移æ¤ããªãã¦è¯ããã¼ã¸ããããªãã«ããããã</li>
<li>ç»åãæ åã表示ãã¦ãããã¼ã¸ãããã</li>
<li>ãã¼ã¿ã®è¿½å ãæ´æ°ã¯ããè¡ããªãã</li>
<li>èªè¨¼ã»èªå¯ã¯ä¸è¦ã«ãªããæ£ç¢ºã«ã¯ã社å
ãããã«éããç°å¢ã§ããã°å
¨å
¬éã§æ§ããªãã</li>
<li>äºå®ã¨ãã¦ã¯ä»å¾æ°å¹´ã¢ã¯ã»ã¹ããã¤ãããããã</li>
<li>ãã¼ã¿ã®ä¸è¦§ãã¼ã¸ã®ã¨ããã«ããæ¤ç´¢æ©è½ã¯æ®ãããã</li>
</ul>
<p>ãã®ç¶æ³ä¸ã§ãç¾ç¶ã®ç®¡çç»é¢ã¢ããªã«æ¿ããéç¨ã¨ãã¦ä»¥ä¸ã®é¸æè¢ãèãã¾ããã</p>
<ul>
<li>DWH ã¸ã®ã¯ã¨ãªã§æ¸ã¾ãã¦ãã¾ããã¯ãã¯ãããã§ã¯ Amazon Redshift ã«å種ãã¼ã¿ãéç©ããDWH ã¨ãã¦æ´»ç¨ãã¦ãã¾ãã<a href="#f-7d5b6688" name="fn-7d5b6688" title="https://techlife.cookpad.com/entry/2019/10/18/090000">*1</a>ä»åã®ã¢ããªãå©ç¨ãã¦ãããã¼ã¿ãã¼ã¹ä¸ã®æ
å ±ã¯ DWH ã«ãããããããã¬ã¤ãªç»é¢ã¯æ¶ãã¦ãã¾ã£ã¦ç´ æ´ãª SQL ã¯ã¨ãªã«ãã¦ãã¾ããã¨ã¯å¯è½ã§ããã¯ã¨ãªçµæãå
±æããã¢ããªã常ç¨ããã¦ãããããã¾ãã<a href="#f-f4daf0b7" name="fn-f4daf0b7" title="https://techlife.cookpad.com/entry/2021/06/11/120000">*2</a></li>
<li>BI ãã¼ã«ã®ããã·ã¥ãã¼ãã¨ãã¦åå®è£
ããã</li>
<li>ãã¼ã¿ãã¼ã¹ãã¢ããªããµã¼ãã¼ã¬ã¹ãªæ§æã«ç§»æ¤ããã</li>
<li>éçãµã¤ãã¸ã§ãã¬ã¼ã¿ã¼ã®ä½ãããã使ã£ã¦åå®è£
ããã</li>
<li>ä»ãã管çç»é¢ã«å¯¾ãã¦å¤å
¸çãªã¦ã§ãã¹ã¯ã¬ã¤ãã³ã°ãè¡ã£ã¦å
¨ãã¼ã¸ã®ãã¡ã¤ã«ãåå¾ãããããæç´ãããããã§éçãµã¤ãã¨ãã¦æä¾ãããæ¤ç´¢æ©è½ã¯è«¦ãã¦ããã©ã¦ã¶ã®ãã¼ã¸å
æ¤ç´¢ã使ãã</li>
</ul>
<p>ãã®ãã¡ãDWH æ¡ã¨ããã·ã¥ãã¼ãæ¡ã¯ããã«åãä¸ãã¾ãããç»åãæ åã¨ããã¹ãã横ã«ä¸¦ãã ç¶æ
ã§ããã¨ä¸è¦§ã§ããç¾ç¶ãä¿ã¡ããã¨ããå©ç¨è
ããã®è¦æããã£ãã®ã¨ãåå®è£
ããããã¼ã¸ç¨®å¥æ°ããããªãã«ãã£ã¦ã¯ã¨ãªãããã·ã¥ãã¼ãã¨ãã¦ä½ãã«ã¯æéããããããã ã£ãã®ãçç±ã§ãã</p>
<p>ãµã¼ãã¼ã¬ã¹åããã£ã¦ã¿ãã®ãé¢ç½ããã§ã¯ããã¾ããããã¨ãã°ãã¼ã¿ãã¼ã¹ã ã Amazon Aurora Serverless ã«ãã¦ã¢ããªã¯ãã®ã¾ã¾ãã¨ããã®ã¯ç¤¾å
ã«éå»äºä¾ãããã§ãããã§ãããä¸æ¹ã§ã¢ããªãæ®ã以ä¸ãã®ãã¡ã»ãã¥ãªãã£ã¢ãããã¼ãçã®å¯¾å¿ã¯å¿
è¦ã«ãªãããããã£ã¨ã©ã¯ãã§ãããªãå¬ããã¨èãã¾ãããã¯ãã¼ãºãããµã¼ãã¹ã®ã¢ããªã¯ãªã¼ãã¼ä¸å¨ã«ãªã確çãé«ããã§ã誰ãã¡ã³ããã³ã¹ããã®ãã¨ããåé¡ãããã¾ããã<a href="#f-4b212a06" name="fn-4b212a06" title="ãã¡ããéçãµã¤ãã«ããã¨ãã¦ã JavaScript ã©ã¤ãã©ãªã®æ´æ°ãå¿
è¦ã«ãªãå¯è½æ§ãç¡ãã¨ã¯è¨ããªãã®ã§ãããæ°å¹´ãããçµã£ã¦æ°ãããã©ã¦ã¶ã¼ã§ãã¾ãåããªããªã£ãã¨ããããã§ãããããã">*3</a></p>
<p>ã¨ããããã§éçãµã¤ãã¸ã§ãã¬ã¼ã¿ã¼ãã¹ã¯ã¬ã¤ãã³ã°ã® 2 æã«ãªãã¾ãããã©ã¯ããã ã£ãã¹ã¯ã¬ã¤ãã³ã°ãé¸ãã§ãè¯ãã£ãã®ã§ãããããã§å°ã欲ãåºãã¦ãéçãµã¤ãã¸ã§ãã¬ã¼ã¿ã¼ã試ãã¦ã¿ããã¨ããæ°åã«ãªãã¾ããã</p>
<p>éçãµã¤ãã¸ã§ãã¬ã¼ã¿ã¼ã使ãå ´åãå
ã
ã®ç»é¢ã React ã§å®è£
ããã¦ããã®ã§ã移æ¤ã®å®¹æããèãã㨠Next.js ã Gatsby ãªã©ã®é¸æè¢ãèãããã¾ããå®ã¯ Gatsby ã§éçãµã¤ãåããã®ã¯å¥ã®å°ããªç¤¾å
åãã¢ããªã§åä¾ãããã¾ããããã 2023 å¹´ç¾å¨ã®ç¤¾å
ã§ã¯ Next.js ãå©ç¨ãã¦ããã¢ããªãå¤ããããã¾ã Next.js ã®éçãµã¤ãåãæ©è½ã§ãã <a href="https://nextjs.org/docs/app/building-your-application/deploying/static-exports">Static Exports</a> ãèªåã§ä½¿ã£ã¦ã¿ããã¨ããªãã£ããããæè¡æ¤è¨¼ã®æå³ãè¾¼ã㦠Next.js ã使ã£ã¦ã¿ããã¨ã«ãã¾ããã<a href="#f-042b27a5" name="fn-042b27a5" title="ãã®ç®¡çç»é¢ãéçºãã¦ããã¨ã³ã¸ãã¢ã¯ãµã¼ãã¹ã¯ãã¼ãºå¾ã社å
ã«å¨ç±ãã¦ããã®ã§ããããã®ãããã®æè¡æ¤è¨¼ããã¦ã¿ããã¨ããèªåã®è¦æããèªåãå®è£
ãã¦ã¿ããã¨ã«ããã®ã§ããã">*4</a></p>
<p>React 以å¤ã®ä¾åé¢ä¿ã確èªãã¦ããã¾ããããå
ã
ã®ç®¡çç»é¢ã®ã³ã¼ãã§ä½¿ããã¦ãã package.json ãè¦ã¦ã¿ãã¨ãããUI ã©ã¤ãã©ãªã¨ã㦠Ant Design (antd)ãGraphQL ã¯ã©ã¤ã¢ã³ãã¨ã㦠Apollo ãå
¥ã£ã¦ããããã®ä»å°ããªã©ã¤ãã©ãªããããå
¥ã£ã¦ããã¨ããç¶æ³ã§ããããã®ãããã®è¤é度ãªã移æ¤ã§ãããã ãªã¨å¤æãã¾ããã</p>
<p>ãã¦ãNext.js ã® Static Exports ã§åå®è£
ããã®ã§ããã°ãPostgreSQL ã«å
¥ã£ã¦ãããã¼ã¿ãã©ããããèããªããã°ããã¾ãããAWS ä¸ã«ãã¼ã¿ãã¼ã¹ãæ®ã£ãã¾ã¾ã ã¨ã³ã¹ãã¯ãã¾ãæ¸ãã¾ãããããã¼ã«ã«ç°å¢ã®ãã¼ã¿ãã¼ã¹ã«ç§»ããªãã·ã³ãã«ãªå½¢ã«ãããã§ããèããçµæãä»åã®ãã¼ã¿ã¯ããã¾ã§å·¨å¤§ã§ã¯ç¡ãã£ããããSQLite ã¸ç§»æ¤ã㦠.sqlite3 ãã¡ã¤ã«ã¨ãã¦æã£ã¦ãã¾ããã¨ã«ãã¾ãããjsonb åãªã© PostgreSQL åºæã®æ©è½ã使ã£ã¦ããç®æããã£ãã®ã§ããæ°ç®æããç¡ãã£ããã SQLite åãã«æ¸ãç´ããSQLite ã®ã¿ã ã¨è¶³ããªãå¦çã¯ãã¼ã¿ãã¼ã¹ãããã¼ã¿ãåã£ã¦ããå¾ã«ã¢ããªå´ã§è¡ãããã«ãã¦ãã¾ãã¾ããã</p>
<p>ãã¼ã¿åå¾ã¾ããã«ã¤ãã¦ã¯ GraphQL ã使ããã¨ã«ãã¾ãããå
ã
ã® Rails 製 GraphQL API ãã³ãããã¦ããã° GraphQL ã¹ãã¼ãã¨ãµã¼ãã¼ãåºæ¥ã¾ãããã¯ã©ã¤ã¢ã³ãå´ã§ãåãèªåçæã§ãã¦ã©ã¯ãåºæ¥ããã¨ããç®è«è¦ãããã¾ãããSQLite ã«å¯¾ãã¦ç´æ¥ SQL ã¯ã¨ãªãèµ°ããã¦ãè¯ãã£ãã®ã§ãããèªåãå
ã®ç®¡çç»é¢ã®å®è£
ã«ããã¾ã§è©³ãããªãã£ãã¨ããã®ããããã©ãã¾ã§è¤éãªã¯ã¨ãªãå¿
è¦ã«ãªãã®ãå®è£
åã®æç¹ã§å¤æãã¥ããã£ãããã³ããã«ãã¦ãã¾ãã¾ããã</p>
<h2 id="å®è£
">å®è£
</h2>
<p>æ¹éã決ã¾ã£ã¦ãã¾ãã°å¾ã¯å®è£
ããã ãã§ããAPI ãµã¼ãã¼ã¯å
ã® Rails ã®ã³ã¼ãããã®ã¾ã¾ã³ãããã¦ãã¦åããããã«ããããã³ãã¨ã³ã㯠<code>create-next-app</code> ãã¾ãããNext.js 13 㧠App Router ã使ãã¤ã¤ãStatic Exports ã®ããã« next.config.js ã</p>
<pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> nextConfig = <span class="synIdentifier">{</span>
output: <span class="synConstant">'export'</span>,
<span class="synIdentifier">}</span>
module.exports = nextConfig
</pre>
<p>ã«ãªã£ã¦ãã¾ãã<a href="#f-de2afe0b" name="fn-de2afe0b" title="æçµç㪠next.config.js ã§ã¯æ´ã«ãnext/image ã®æé©å㯠Static Exports ã§ã¯æå³ãç¡ãã®ã§ unoptimized: true ã«ããããå®è£
ã®é½å㧠trailingSlash: true ã«ãããããã¦ãã¾ããGitHub Pages ã«ãããã¤ããåã«ã¯ basePath ã調æ´ããã®ããã£ã¦ãã¾ãã">*5</a></p>
<p><code>next build</code> ãããéã«ã¯æ¨ªã§ SQLite ã«ç¹ãã£ã Rails 製 GraphQL API ãåããã¦ãã¦ãå¿
è¦ã«å¿ã㦠API ã«ãªã¯ã¨ã¹ããããã¨ã§ãã¼ã¿ãåã£ã¦ãã¾ããStatic Exports ã®å ´åã¯ãã«ããçµãã㨠HTML çã®ãã¡ã¤ã«ç¾¤ãçæãããã®ã§ãéçºç°å¢ã§ã¯ <code>serve</code> ã使ããªã©ããã°é²è¦§ã§ãã¾ãã</p>
<p>å®è£
ã®ç§»æ¤ã«ã¤ãã¦ããã¼ã¸ã React Components ã®ç§»æ¤ã¯ã¾ãã¾ãã³ããã§çµããã¾ãããå
ã
ã®å®è£
ããããªãã«ã³ã³ãã¼ãã³ãåããã¦ãã¦å
¨å®¹ãææ¡ããããã£ãã®ã¨ãå
ã¨å¤ããã GraphQL ã使ã£ã¦ããããããå¹ãã¾ããã</p>
<p>ãã ã Server Components 㨠Client Componentsãã¤ã¾ãã©ã®ã³ã³ãã¼ãã³ãã®å¦çã¯ãµã¼ãã¼å´ã§è¡ããã©ã®å¦çã¯ãã©ã¦ã¶å´ã§è¡ãããã®ãã«ã¤ãã¦ã¯æ´çããå¿
è¦ãããã¾ãããStatic Exports ãè¡ãããã§ã¯ API ãããã¼ã¿ãåå¾ããé¨åã¯ãã¹ã¦ãµã¼ãã¼å´ã§è¡ããã¦ããªãã¨ããã¾ããããéã« <code>useState</code> ã使ããããªç®æã¯ãã©ã¦ã¶å´ã§è¡ãããªããã°ãªãã¾ãããå
ã
ã®ç®¡çç»é¢ã§ã¯ãã©ã¦ã¶ãã GraphQL ãªã¯ã¨ã¹ããè¡ã£ã¦ãããããè³ãã¨ããã«ãã API ãªã¯ã¨ã¹ãã¯ãµã¼ãã¼å´ã«éä¸ããããæ¸ãæããå¿
è¦ã§ããããã¼ã¿ã®æµããæ´çããçµæã¨ãã¦ã²ã¨ã¤ã®ã³ã³ãã¼ãã³ããåå²ã㦠Server Component 㨠Client Component ã«åãããããã¾ããã</p>
<p>ã¨ã¯ããããã¼ã¿ã®è¿½å ãæ´æ°ã¯ããè¡ããªããã¨ããå¶ç´ãã¨ã¦ãå¼·ãå¹ãã¦ãã¦ãå®è£
ã¯æ¯è¼çã·ã³ãã«ã«ãªãã¾ããããã¼ã¿ã®è¿½å ã»æ´æ°ã®ããã«åå¨ãã¦ãã React ã³ã¼ããã°ã£ããåé¤ãã¦ãã£ãçµæã大æµã®ãã¼ã¸ã¯ãServer Components ã§ãã¼ã¿ãåå¾ãã¦ãåã¨ãªã UI ã³ã³ãã¼ãã³ãã«ãã¼ã¿ã渡ããåã³ã³ãã¼ãã³ãã¯å¿
è¦ã§ããã° Client Components ã«ããããããã®åç´ãã«ãªãã¾ããã</p>
<p>Client Components ãç¨ããã®ã¯ä¸»ã«ãæ¤ç´¢ãã©ã¼ã ãè¨ç½®ãã¦ããã¨ãã㨠antd 5.8 ãå¿
è¦ã¨ããã¨ããã§ãã<a href="#f-0e92a6ba" name="fn-0e92a6ba" title="antd ã§å¿
è¦ã«ãªãã®ã¯ https://ant.design/docs/react/use-with-next#using-nextjs-app-router ã«æ¸ããã¦ãã "if you use the above sub-components in your page, you can add "use client" to the first line of the page component to avoid warnings" ã§ãã">*6</a></p>
<p>æ¤ç´¢æ©è½ã«ã¤ãã¦ã¯ãå
ã®ç®¡çç»é¢ã§ã¯ãµã¼ãã¼å´ã«æ¤ç´¢æ©è½ãå®è£
ãã¦ãã¾ãããéçãµã¤ãã§ã¯ä¸å¯è½ãªã®ã§ããã©ã¦ã¶å´ã® JavaScript ã§ç´ æ´ã« <code>String.prototype.includes</code> ã使ã£ã¦çµãè¾¼ããã¨ã§å®ç¾ãã¾ããããã¼ã¸ãã¼ã·ã§ã³ãã¦è¦ãç®ä¸ã®è¡¨ç¤ºä»¶æ°ãæ¸ããã¯ãã¾ãããããããªãã®æ°ã®ãã¼ã¿ããã£ã¦ãé«éã«åä½ããã®ã§ãã©ã¦ã¶ã¯åãã§ããâ¦â¦ãããè¤éãªå
¨ææ¤ç´¢ã欲ãããªãç®æãããã° <a href="https://github.com/olivernn/lunr.js">Lunr.js</a> çã使ã£ã¦ã¿ãã¤ããã§ããããä»åã¯ããã¾ã§è¤éãªæ¤ç´¢ã¯ç¡ãã£ããã使ããã«æ¸ãã§ãã¾ãã¾ããã</p>
<p>ãããªãããªã§è©³ç´°ãåºã¾ããã¾ãã¯è¤éãããªãã¼ã¸ããå®è£
ãã¦ã¿ãã¨ããä¸æãåããããå®è£
ãé²ããå¿
è¦ãªãã¼ã¸ãã¹ã¦ã«ã¤ãã¦å®è£
ããããã¨ãã§ãã¾ããã</p>
<h2 id="æ¯ãè¿ã">æ¯ãè¿ã</h2>
<p>å
¨é¨çµãã£ããã¨æ¯ãè¿ã£ã¦ã¿ãã¨ãNext.js ã® App Router 㨠GraphQL ã使ããã¨ã«ããã®ã¯æåã ã£ãã¨æãã¾ããã³ããã§ããã®ãããã§ãããå®è£
ãã¦ããæä¸ãApp Router ã®ãã£ã¬ã¯ããªæ§é ã使ã£ã¦ GraphQL ã¯ã¨ãªãå°ãã React Components ã®ãã¡ã¤ã«ãã¡ããã¼ã¸ã®å®è£
ã®è¿ãã«é
ç½®ã§ããã®ãã©ã¯ã§ããã</p>
<p>å
·ä½çã«ã¯ãã¨ãã° graphql-codegen 㧠<a href="https://the-guild.dev/graphql/codegen/plugins/presets/near-operation-file-preset"><code>near-operation-file</code></a> ã使ãããã«ããããã§ã以ä¸ã®ãããªãã¡ã¤ã«é
ç½®ã«ãªãããã§ãã</p>
<pre class="code" data-lang="" data-unlink>app
âââ _lib
â âââ types.generated.ts
âââ recipes
â âââ _lib
â â âââ Table.tsx
â â âââ query.generated.ts
â â âââ query.graphql
â âââ page.tsx
:
:</pre>
<p>å
ã
ã®å®è£
ããã¼ã¸ãã¨ã®ã³ã¼ãã¨ãã¼ã¸åºæã® React Componentsãããã¨ãã¾ã«å
¨ä½ã§å
±æã® React Components ã¨ããæãã ã£ãã®ã§ãããããã®ã¾ã¾æã£ã¦ãããã¨ãåºæ¥ã¾ããããã¼ã¿ã®æµããæ´çããéç¨ã§å°ãã Server Components ã Client Components ãçã¾ããã®ã§ããããã®ãã£ã¬ã¯ããªæ§é ã ã¨æ°ã«ãªãã¾ããã</p>
<p>Static Exports ã¯ä½ã®åé¡ããªãåãã¦ããã¾ããã移æ¤ã®éã« <code><a></code> ã¿ã°ããã¹ã¦ next/link ã® <code><Link></code> ã«æ¸ãæããããããã ã§ããéçãµã¤ãã§éãã®ã« prefetch ã®ãããã§æ´ã«éãæãããããµã¤ããåºæ¥ä¸ããã¾ããã</p>
<p>ãã¡ããå®è£
åã®ä¸å®ç¹ã¨ãã¦ãantd ãªã©ã®ä¾åã©ã¤ãã©ãªã¯å®è£
æç¹ã§ã®ææ°ãã¼ã¸ã§ã³ã¾ã§ä¸ããªã㨠App Router ããã³ React Server Components 対å¿ã®ä¸å
åãªç¹ãããããã ã¨ã¯åãã£ã¦ãã¾ããããã®é¢ä¿ã§å
ã®ç®¡çç»é¢ã§ä½¿ã£ã¦ãããã¼ã¸ã§ã³ããã¡ã¸ã£ã¼ãã¼ã¸ã§ã³ãä¸ããªãã¨ãããªãã©ã¤ãã©ãªãããã¾ãããã¨ã¯ããå®è£
åã«ã¶ã¼ãã¨åã©ã¤ãã©ãªã®å¤æ´å±¥æ´ãçºããçµæããã¾ã§å°ããªãããã¨å¤æããå®éãã¾ãå°ãã¾ããã§ããã</p>
<p>ã¾ã App Router ã®é¢ä¿ãªã®ã Static Exports ã®é¢ä¿ãªã®ãã¯ã¡ããã¨èª¿ã¹ã¦ãã¾ããããããã¤ãã®ã¨ã©ã¼ã§åå ã調ã¹ãã®ã«å°ãæéããããã¾ãããã¨ã©ã¼ã¡ãã»ã¼ã¸ã¨ã¹ã¿ãã¯ãã¬ã¼ã¹ãåããã¥ããã£ãã®ã§ãããâ¦â¦ã<code>"use client";</code> ãã¤ã㦠Client Components ã«ãããç´ãã®ã ãã©ã¨ã©ã¼ã¡ãã»ã¼ã¸ããç´æ¥ã¯åããã¥ããé¡ã®ã¨ã©ã¼ã«ã¯ããã¤ãééãã¾ããã</p>
<p>ã¨ã¯ããå§ã¾ãããçµããã¾ã§è¦ãã¨ãããå´åããããã«ç§»æ¤ã§ããã®ã§æºè¶³ã§ãããã®ç®¡çç»é¢ãæä¾ãã¦ãããã¼ã¿ã¯ç¤¾å
ãè¦æ¸¡ãã¦ããããªãã«ã¦ãã¼ã¯ãªæçãã¼ã¿ã§ãã¦ããããåç
§ããããå½¢ã§æ®ãç¶ãããããã¨ã«ã¯ä¾¡å¤ãããã¨ãå人çã«ãèãã¦ãã¾ãããããã£ãæ義ã®ã¤ãã§ã«æè¡æ¤è¨¼ãåºæ¥ããªãã¯ãªä»äºã§ããã</p>
<div class="footnote">
<p class="footnote"><a href="#fn-7d5b6688" name="f-7d5b6688" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://techlife.cookpad.com/entry/2019/10/18/090000">https://techlife.cookpad.com/entry/2019/10/18/090000</a></span></p>
<p class="footnote"><a href="#fn-f4daf0b7" name="f-f4daf0b7" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://techlife.cookpad.com/entry/2021/06/11/120000">https://techlife.cookpad.com/entry/2021/06/11/120000</a></span></p>
<p class="footnote"><a href="#fn-4b212a06" name="f-4b212a06" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãã¡ããéçãµã¤ãã«ããã¨ãã¦ã JavaScript ã©ã¤ãã©ãªã®æ´æ°ãå¿
è¦ã«ãªãå¯è½æ§ãç¡ãã¨ã¯è¨ããªãã®ã§ãããæ°å¹´ãããçµã£ã¦æ°ãããã©ã¦ã¶ã¼ã§ãã¾ãåããªããªã£ãã¨ããããã§ãããããã</span></p>
<p class="footnote"><a href="#fn-042b27a5" name="f-042b27a5" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãã®ç®¡çç»é¢ãéçºãã¦ããã¨ã³ã¸ãã¢ã¯ãµã¼ãã¹ã¯ãã¼ãºå¾ã社å
ã«å¨ç±ãã¦ããã®ã§ããããã®ãããã®æè¡æ¤è¨¼ããã¦ã¿ããã¨ããèªåã®è¦æããèªåãå®è£
ãã¦ã¿ããã¨ã«ããã®ã§ããã</span></p>
<p class="footnote"><a href="#fn-de2afe0b" name="f-de2afe0b" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">æçµç㪠next.config.js ã§ã¯æ´ã«ãnext/image ã®æé©å㯠Static Exports ã§ã¯æå³ãç¡ãã®ã§ unoptimized: true ã«ããããå®è£
ã®é½å㧠trailingSlash: true ã«ãããããã¦ãã¾ããGitHub Pages ã«ãããã¤ããåã«ã¯ basePath ã調æ´ããã®ããã£ã¦ãã¾ãã</span></p>
<p class="footnote"><a href="#fn-0e92a6ba" name="f-0e92a6ba" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">antd ã§å¿
è¦ã«ãªãã®ã¯ <a href="https://ant.design/docs/react/use-with-next#using-nextjs-app-router">https://ant.design/docs/react/use-with-next#using-nextjs-app-router</a> ã«æ¸ããã¦ãã "if you use the above sub-components in your page, you can add "use client" to the first line of the page component to avoid warnings" ã§ãã</span></p>
</div>
nekketsuuu
Rubyã®ä¸¦å並è¡å¦çã®ããã¾ã§ã¨ãããã
hatenablog://entry/820878482963206936
2023-08-31T15:25:11+09:00
2023-08-31T15:29:53+09:00 æ¬è¨äºã§ã¯ãRubyã®ä¸¦è¡ä¸¦åå¦çã®æ¹åã«ã¤ãã¦ã®ç§ã®åãçµã¿ã«ã¤ãã¦ãããã« RubyKaigi 2022 㨠2023 ã§çºè¡¨ããå
容ããã¨ã«ãç´¹ä»ãã¾ãã
Rubyï¼CRuby/MRIï¼ã¯å¤ãããThreadã«ãã並è¡å¦çã®ããã®ä»çµã¿ãæä¾ãã¦ããã並åå¦çã¯Unixãªã©ã®ããã»ã¹ãªã©ãç¨ãããã¤ã¾ãRubyã®å¤å´ï¼ï¼ï¼ã®æ©è½ã¨çµã¿åããã¦ä½¿ãå¿
è¦ãããã¾ãããããã¦ãRuby 3.0ããå°å
¥ããã Ractor 㧠Ruby ããã»ã¹å
ã§å©ç¨ã§ãã並åå¦çã®ä»çµã¿ãå°å
¥ããã¾ãããã¾ã ã¾ã ä¸ååãªã®ã§ããããããã£ã¨é å¼µã£ã¦ããããã£ã¦ããå
容ã®è¨äºã«ãªãã¾ãã
<p>æè¡é¨ã®ç¬¹ç°ã§ããä»æ¥ã§éè·ããã®ã§ããã¿ãã¿ã¨è¿å´ãªã©ã®æºåããã¦ãã¾ãã</p>
<p>æ¬è¨äºã§ã¯ãRubyã®ä¸¦è¡ä¸¦åå¦çã®æ¹åã«ã¤ãã¦ã®ç§ã®åãçµã¿ã«ã¤ãã¦ãããã« RubyKaigi 2022 㨠2023 ã§çºè¡¨ããå
容ããã¨ã«ãç´¹ä»ãã¾ãã</p>
<p>並è¡ã¨ä¸¦åã¯ããä¼¼ãè¨èã§ãããæ¬è¨äºã§ã¯æ¬¡ã®ãããªæå³ã§ä½¿ãã¾ãã</p>
<p>並è¡å¦çï¼concurrent processingï¼ã¯ããè¤æ°ã®ç¬ç«ããå®è¡åä½ããå¾
ã£ã¦ããã°ãã¤ãçµããï¼ãããã¯ãå¦çãé²ãï¼ãã¨ããè«ççãªæ¦å¿µã§ãå¤å
¸çã«ã¯ã¿ã¤ã ã·ã§ã¢ãªã³ã°ã·ã¹ãã ãªã©ãæãããã¾ãã</p>
<p>並åå¦çï¼parallel processingï¼ã¯ããè¤æ°ã®ç¬ç«ããå®è¡åä½ã®ãã¡ã®ããã¤ãããããã¿ã¤ãã³ã°ã§åæã«åãã¦ãããã¨ããç©ççãªæ¦å¿µã§ãå¤å
¸çã«ã¯è¤æ°ã®CPUä¸ã§åæã«å®è¡ããããã¨ãããã®ã§ããæè¿ã§ã¯ã1ã¤ã®CPUä¸ã§è¤æ°ã³ã¢ãåæã«åãã¦ãããã¨ããã®ãæ®éã«ãªã£ã¦ãã¾ãããã</p>
<p>Rubyï¼CRuby/MRIï¼ã¯å¤ãããThreadã«ãã並è¡å¦çã®ããã®ä»çµã¿ãæä¾ãã¦ããã並åå¦çã¯Unixãªã©ã®ããã»ã¹ãªã©ãç¨ãããã¤ã¾ãRubyã®å¤å´ï¼ï¼ï¼ã®æ©è½ã¨çµã¿åããã¦ä½¿ãå¿
è¦ãããã¾ãããããã¦ãRuby 3.0ããå°å
¥ããã Ractor 㧠Ruby ããã»ã¹å
ã§å©ç¨ã§ãã並åå¦çã®ä»çµã¿ãå°å
¥ããã¾ãããã¾ã ã¾ã ä¸ååãªã®ã§ããããããã£ã¨é å¼µã£ã¦ããããã£ã¦ããå
容ã®è¨äºã«ãªãã¾ãã</p>
<h2 id="ç°¡åãªæ´å²">ç°¡åãªæ´å²</h2>
<h3 id="Ruby-18-ã¾ã§">Ruby 1.8 ã¾ã§</h3>
<p>Ruby 1.8 ã¾ã§ã¯ãRubyã¯ã¦ã¼ã¶ã¼ã¬ãã«ã¹ã¬ããã¨å¼ã°ããä»çµã¿ã§ãOSãªã©ãæä¾ãããã¤ãã£ãã¹ã¬ããï¼ä¸è¨NTã¨ã表è¨ãPthreadãWindows APIã®ã¹ã¬ããï¼ã1ã¤ã¤ãã£ã¦ãè¤æ°ã®Rubyã¹ã¬ããï¼ä¸è¨ãRTã¨ã表è¨ã<code>Thread.new{}</code>ã§ä½ããã¤ï¼ã管çãã¦ãã¾ããã1ã¤ã®ãã¤ãã£ãã¹ã¬ããã§è¤æ°ï¼Måï¼ã® Rubyã¹ã¬ããã管çããã®ã§ãM:1 ã¢ãã«ã¨ãããã¨ãããã¾ãï¼ä¸éçã«ã¯ 1:N ã¹ã¬ããã¢ãã«ã¨ãããã¨ãå¤ãã®ã§ããè¨äºã®é½åä¸ãM:1 ã¨æ¸ãã¦ããã¾ãï¼ã</p>
<p><figure class="figure-image figure-image-fotolife" title="M:1 (N:1) model, Green threads, user level threads, quoted from my RubyKaigi2022 talk"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koichi-sasada/20230831/20230831113351.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>M:1 (N:1) model, Green threads, user level threads, quoted from my RubyKaigi2022 talk</figcaption></figure></p>
<p>è¤æ°ã®Rubyã¹ã¬ããã¯ã(1) I/Oã sleepãMutex ãªã©ã§å¾
ã¡ãå
¥ãã¿ã¤ãã³ã°ã(2) ä¸å®æéï¼ã¿ã¤ã ã¹ã©ã¤ã¹ï¼çµéããã¨ãã®ã¿ã¤ãã³ã°ã§åãæ¿ããã¾ããI/O ã«é¢ãã¦ã¯ãselectï¼ã«é¡ããï¼ã·ã¹ãã ã³ã¼ã«ã§æºåã§ããããå®æçã«ç¢ºèªããªãã管çãã¾ãã</p>
<p>ãã®ææ³ã®å©ç¹ã¨æ¬ ç¹ã¯æ¬¡ã®éãã§ãã</p>
<ul>
<li>å©ç¹:
<ul>
<li>ã¦ã¼ã¶ã¬ãã«ã¹ã¬ãããªã®ã§ãçæã¯éã</li>
</ul>
</li>
<li>æ¬ ç¹:
<ul>
<li>å½æã¯Rubyã¹ã¬ããã®åãæ¿ããã¹ã¿ãã¯ã丸ã
ã³ãã¼ããã¨ããææ³ã使ã£ã¦ããã®ã§ãçµæ§é
ããã®ã§ããã</li>
<li>select ã§å¾
ã¡ãå¶å¾¡ã§ããªãå¦çããã¨ãã° <code>waitpid()</code> ã <code>flock()</code>ããã¡ã¤ã³å解決ãå¾
ã¡ãå
¥ããããªã©ã¤ãã©ãªé¢æ°ãªã©ã§å¾
ã£ã¦ããã¨ä»ã®Rubyã¹ã¬ããã«åãæ¿ãããªã</li>
<li>ãã¼ãªã³ã°åºæ¥ãå¦çã¯é½åº¦ãã¼ãªã³ã°ã§å¯¾å¦ãã¦ããããã¹ã¬ããæ°ãå¢ããã¨ã¹ã±ã¼ã«ããªãå¯è½æ§ããã</li>
<li>ãã¤ãã£ãã¹ã¬ãããå æããã»ããé½åãããã©ã¤ãã©ãªï¼GUIç³»ãªã©ï¼ãç´ ç´ã«ä½¿ããªã</li>
<li>ä½ãã®ãï¼ã¡ã³ããç¶ããã®ãï¼çµæ§å¤§å¤</li>
</ul>
</li>
</ul>
<h3 id="Ruby-19-Thread">Ruby 1.9 Thread</h3>
<p>Ruby 1.9 ã§ã¯ãRubyã¹ã¬ããä¸ã¤ã«ã¤ããã¤ãã£ãã¹ã¬ãã1ã¤ç¨æãã 1:1 ã¢ãã«ã«å¤æ´ããã¾ãããã¨ããã®ããã¦ã¼ã¶ã¬ãã«ã¹ã¬ããã¯ãããã tricky ã§ãå®éã«å®è£
ãã¦ããç§ã«ã¯æããããªãã£ãããã§ããã¾ãã1:1 ã¢ãã«ã«ãããã¨ã§ã並è¡å¦çã ãã§ãªãã並åå¦çã¸ã®æ¡å¼µãè¦éã«å
¥ã£ã¦ããããã§ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="1:1 model, quoted from my RubyKaigi2022 talk"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koichi-sasada/20230831/20230831113458.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>1:1 model, quoted from my RubyKaigi2022 talk</figcaption></figure></p>
<p>Ruby 1.9 㧠1:1 ã¢ãã«ãå°å
¥ããéã«ã¯ãGVLï¼Global VM Lockï¼ã¨ããã®ãå°å
¥ãã¾ããããã ä¸ã¤ã®GVLãããã¯ãã¦ããRubyã¹ã¬ããããåããªããã¨ããã¢ãã«ã¨ãããã¨ã§ãåæã«Rubyã¹ã¬ããã¯ããã ã1ã¤ããåããªããã¨ããã®ãã®ã§ãããã®ãããRubyã¹ã¬ããã¯ç¸å¤ããã並è¡å¦çããµãã¼ããã¾ããã並åå¦çã¯ãµãã¼ããã¾ããããããããªçç±ã¯ããã®ã§ãããè¦ã¯ãå®è£
ãç°¡åã ãããã¨ãããã¨ã«å°½ãã¾ãã</p>
<p>ï¼ä½è«ï¼ã©ã¡ããç°¡åã«æã£ã¦ããã®ã¯ãææããããã¨ããã®ãæ¬é³ãªã®ã§ããã建åã§ããã¨ããã³ãã¯ã¼ã®ãªãç¶æ³ã§å®å®ããå¦çç³»ãæä¾ããããã«ã¯é©åãªãã¬ã¼ããªãã§ããã®ã§ã¯ãªãããªãã¨æã£ã¦ããããã®å¤æã¯ä»æ¯ãè¿ã£ã¦ã妥å½ã ã£ãããããªãããªã¨æãã¾ãã2006å¹´ããã§ãããï¼</p>
<ul>
<li>å©ç¹:
<ul>
<li>1:1 ã¹ã¬ããã¢ãã«ã¯çµæ§ç°¡å</li>
<li>GVL ãããã®ã§ä¸¦åã«å®è¡ãããªãããã«ããã«ç°¡å</li>
<li>GVL ã解æ¾ãããã¨ã§ï¼C-extensionï¼ããããã¯ããå¦çããã¦ããéãä»ã®Rubyã¹ã¬ããã«å¦çã移ããã¨ãã§ãã</li>
<li>GVL ã解æ¾ãããã¨ã§ï¼C-extensionï¼ãè¤æ°ã®å¦çï¼Rubyã§è¨è¿°ãã¦ããå¦çã§ã¯ãªãï¼ã並åã«å®è¡ãããã¨ãã§ãããä¾ãã°ãBignumã®æéããããè¨ç®ããI/O ã®å¦çï¼Ruby ã¤ã³ã¿ããªã¿ã¨ã¯ç¬ç«ããå¦çï¼ãªã©ã</li>
<li>Rubyã¹ã¬ããåãæ¿ãã¯ãã¤ãã£ãã¹ã¬ããã«ãã£ã¦å®è£
ãããã®ã§éã</li>
<li>ã¹ã±ã¸ã¥ã¼ãªã³ã°ããã¤ãã£ãã¹ã¬ããã«ä»»ãã¦ãã¾ãã®ã§æ¥½</li>
</ul>
</li>
<li>æ¬ ç¹:
<ul>
<li>1:1 ã¢ãã«ãªã®ã§ Ruby ã¹ã¬ããã1ã¤ä½ããã³ã«ãã¤ãã£ãã¹ã¬ãã 1 ã¤å¿
è¦ã«ãªããRubyã¹ã¬ããæ°ãã¹ã±ã¼ã«ããªãï¼ãã¤ãã£ãã¹ã¬ããã®å®è£
ã«ããï¼</li>
<li>GVL ãããã®ã§ä¸¦åå®è¡ã§ããªã</li>
<li>Rubyã¹ã¬ããéã§å¦çãåã渡ããããªå¦çãé
ãï¼CPU core éã§å¦çãé »ç¹ã«åãæ¿ããã¨ãã«é
ãï¼</li>
<li>ã¹ã±ã¸ã¥ã¼ãªã³ã°ããã¤ãã£ãã¹ã¬ããã«ä»»ãã¦ãã¾ãã®ã§ãRubyå´ããã®ç´°ããå¶å¾¡ãé£ãã</li>
</ul>
</li>
</ul>
<p>Ruby 1.8 ã§ãã£ãåé¡ãã ãã¶è§£æ±ºããã¦ããã®ããããã¨æãã¾ãããã®ã»ãã¯ãã¬ã¼ããªãã§ããã</p>
<h3 id="Ruby-19-Fiber">Ruby 1.9 Fiber</h3>
<p>Ruby 1.9 ã§ã¯ãã¦ã¼ã¶ã¬ãã«ã¹ã¬ããã®å©ç¹ãã¡ãã£ã¨ã®ããã¦ãããã¨ãããã¨ã§ Fiber ãå°å
¥ããã¾ãããFiber 㯠Ruby 1.8 ã®ã¦ã¼ã¶ã¬ãã«ã¹ã¬ããã¨ã»ã¼åæ§ã§ãããã¿ã¤ã ã¹ã©ã¤ã¹ããªããI/O ãªã©ã§èªåçã«ã¹ã¤ãããããã¨ã®ãªããã¨ããç¹ãç°ãªãã¾ãã</p>
<p>Fiber ã¯å½å㯠Ruby 1.8 ã®ã¹ã¬ããåãæ¿ãå¦çããã®ã¾ã¾è¸è¥²ãã¦ããã®ã§ãããã®ã¡ã®ãã¼ã¸ã§ã³ã§æ¹åãããä»ã§ã¯ CPU ãã¨ã«ã¢ã»ã³ãã©ãç¨ãã¦è¨è¿°ãããã¨ãããã®ã«ãªãã¾ããã</p>
<h3 id="Ruby-30-ã®-Fiber-scheduler">Ruby 3.0 ã® Fiber scheduler</h3>
<p>Ruby 3.0 ã§å°å
¥ããã Fiber scheduler ã¯ãI/O ãªã©ããããã¯ãããããªå¦çãå¥æ©ã«ããã¯ãå¼ã³åºãä»çµã¿ã§ãèªå㧠Fiber ãã¹ã±ã¸ã¥ã¼ã«ããå¦çï¼ãããç·ç§°ã㦠Fiber schedulerï¼ãè¨è¿°ããããã®ä»çµã¿ã§ããå®éã«ã¯ãèªåã§è¨è¿°ããã®ã§ã¯ãªãããã§ã«ãã gem ãå©ç¨ããã¨ããã§ãããã</p>
<ul>
<li>å©ç¹:
<ul>
<li>ã¦ã¼ã¶ã¬ãã«ã¹ã¬ããã®å©ç¹ï¼ä½ã³ã¹ããªçæï¼ãå¾ããã</li>
<li>èªåã§ã¹ã±ã¸ã¥ã¼ã©ã¼ãè¨è¿°ã§ãã</li>
<li>ã¿ã¤ã ã¹ã©ã¤ã¹ããªãããäºæ¸¬å¯è½æ§ãä¸ãã</li>
</ul>
</li>
<li>æ¬ ç¹:
<ul>
<li>Ruby 1.8 ã¹ã¬ããã¨åãï¼ç®¡çã§ããªããããã¯ããå¦çã§ã¯ä»ã®Fiberã«åãæ¿ããããªãï¼</li>
<li>Fiber ãæèããå¿
è¦ããã</li>
<li>ã¿ã¤ã ã¹ã©ã¤ã¹ããªã</li>
</ul>
</li>
</ul>
<p>ã¢ããªã±ã¼ã·ã§ã³ã«ç¹åããã¹ã±ã¸ã¥ã¼ã©ãè¨è¿°ã§ããã¨ããã®ã¯ãæé«æ§è½ãç®æãã¨ãã観ç¹ããã¯è¯ããã®ã§ãããå¤ãã®å ´å too much ãããªãããªããã¨ããã®ãç§ã®ææ³ã§ãã</p>
<h3 id="Ruby-30-ã®-Ractor">Ruby 3.0 ã® Ractor</h3>
<p>ãããã Ruby ã¹ã¬ãããã¨ãããããããã¹ã¬ããä¸è¬ã£ã¦ãå¤æ´å¯è½ï¼Mutableï¼ãã¼ã¿ã®å
±æã«ãããã¼ã¿ã¬ã¼ã¹ãã¬ã¼ã¹ã³ã³ãã£ã·ã§ã³ã®åé¡ããããMutexãªã©ã§æ£ããæä»å¶å¾¡ãå¿
è¦ã§ãããªã®ã§ãç§ã¯ãã¹ã¬ããé£ãããªããããã¾ã便å©ã«ããããªããªããã¨ããæè¦ãæã£ã¦ãã¾ããããã®åé¡ã«å¯¾å¦ããããã«ãä¾ãã°ä»ã®è¨èªã§ã¯æ¬¡ã®ãããªå·¥å¤«ããã¦ãã¾ããã</p>
<ul>
<li>é åãå®å
¨ã«åãã¦ãã¾ã: Unix ãªã©ã®ããã»ã¹ãRacketï¼ã®Placeï¼</li>
<li>ãã¹ã¦ã®ãã¼ã¿ã Immutable ã«ãã¦ãImmutable ãªãã¼ã¿ããå
±æã§ããªãããã«ãã: Erlang, Elixirï¼ã®Processï¼</li>
<li>åã§ããæãã«ãªãã¨ããã: Rust</li>
<li>ãã¼ã¿ãå
±æããæ¹æ³ãæ¨æºåãã: Java, Golangï¼Goroutineï¼</li>
<li>å®è¡æã«ãã°ãããªã¨ãããæ¤ç¥ãã¦ä¿®æ£ãã: Valgrind, Thread sanitiser, ...</li>
</ul>
<p>Rubyã§åããããªæ¦ç¥ã¯ä½ããªãã¨ãããã¨ãèãã¦ãã"Immutable ãªãã¼ã¿ããå
±æã§ããªãããã«ãã" ãªãããªãã¨ããªãããããªï¼ãã¨ããçºæ³ã§ Ractor ãè¨è¨ãã¾ãããã¤ã¡ã¼ã¸ã¨ãã¦ã¯ãUnixãªã©ã®ããã»ã¹ã«è¿ãã§ãããå
±æå¯è½ãªé¨åã¯å
±æããããMutable ã§ãããã¯ãå¿
é ã¨ãããªãå
±æå¯è½ã«ãããã¨ããã¨ãããè¿ãã§ãã</p>
<ul>
<li>å©ç¹:
<ul>
<li>並åå®è¡ãå¯è½</li>
<li>ãã¼ã¿ã¬ã¼ã¹ãªã©ã®åé¡ãåçä¸èµ·ãããªã</li>
</ul>
</li>
<li>æ¬ ç¹:
<ul>
<li>å
±æå¯è½ãªãã¸ã§ã¯ããå¶éããã®ã§ããµã¤ãã®Rubyããã°ã©ã ã multi-Ractor ã®ä¸ã§ã¯åããªã</li>
<li>å®è£
ãæªãã®ã§è²ã
é
ã</li>
</ul>
</li>
</ul>
<p>ãå®è£
ãæªããã¨ããé¨åã¯ãæ¹åããã°ããã®ã§ãããããã¤ã大ããªåé¡ãããã¾ããã</p>
<ul>
<li>1:1 ã¹ã¬ãããè¸è¥²ãã¦ããï¼Ractor å
ã« 1 å Ruby ã¹ã¬ãããä½ãããããã 1 ãã¤ãã£ãã¹ã¬ãããè¦æ±ããï¼</li>
<li>ãã¹ã¦ãã¨ã㦠GC ããªããã°ãããªãã®ã§ Ractor æ°ã«ã¹ã±ã¼ã«ããªã</li>
</ul>
<p>æ¬ ç¹ã®1ã¤ç®ã®åé¡ããããªããªãå©ç¨ããããã§ã¯ã¹ã¯ã©ãã㧠Ractor ãã¼ã¹ã«æ¸ãç´ãã°ãããªãããã¨ããã¨2ã¤ç®ã®æ¬ ç¹ã§ããå®è£
ãæªãã¨ããç¹ããå©ç¨ãé²ã¾ãªããã¨ãªãã°é å½ã«å®è£
ããããã¦ããããã¨ããã®ãä»å¹´ã® RubyKaigi 2023 ã§ã®ç§ã®çºè¡¨ã§ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="âRactorâ reconsidered, or 2nd progress report of MaNy projects"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koichi-sasada/20230831/20230831113943.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>âRactorâ reconsidered, or 2nd progress report of MaNy projects</figcaption></figure></p>
<p><a href="https://rubykaigi.org/2023/presentations/ko1.html#day1">"Ractor" reconsidered - RubyKaigi 2023</a></p>
<p>è¦ç¹ã¨ãã¦ã¯ã</p>
<ul>
<li>Ractor ã¯ãã£ããå
¥ã£ããã ãã©
<ul>
<li>æ¢åã®ã³ã¼ããããã«ã¯åããªããã使ããã¦ããªã</li>
<li>æ§è½ãæªããã¨ãå¤ãã¦ãªããªã使ããã¦ããªã</li>
</ul>
</li>
<li>ã¨ããããæ§è½ãããããã¨ã§ãå°è¦æ¨¡ãªã³ã¼ããã使ãããããããªãã ããã</li>
<li>ãã®ããã«ã¯ãããããã¨ããã£ããããããããããããã¨ãããã</li>
</ul>
<p><figure class="figure-image figure-image-fotolife" title="Future expected situation on Ractor, quoted from my RubyKaigi2023 talk"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koichi-sasada/20230831/20230831113133.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Future expected situation on Ractor, quoted from my RubyKaigi2023 talk</figcaption></figure></p>
<p>ã¨ãããã®ã§ããã</p>
<h2 id="MN-ã¹ã¬ããã®å°å
¥">M:N ã¹ã¬ããã®å°å
¥</h2>
<p>ã¨ããããã§ããããããã¨ãããã/ããããã¨ãã話ã§ãã</p>
<p>ã¾ããRactorãRubyã¹ã¬ããã¯ã1:1ã¢ãã«ã§ãããã¨ã§ãçæãé
ãã£ããçæã§ããæ°ãå°ãªãã£ãããã¾ãï¼1:1ã¢ãã«ã®æ¬ ç¹ï¼ãããã§ãM:N ã¹ã¬ããã¢ãã«ãå°å
¥ã§ããªãããä»ï¼ã¨ãããå»å¹´ããï¼éçºä¸ã§ãã</p>
<p>ã¨ããå
容ã RubyKaigi 2022 ã§ã®ç§ã®çºè¡¨ã§ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="Making *MaNy* threads on Ruby"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koichi-sasada/20230831/20230831113830.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Making <em>MaNy</em> threads on Ruby</figcaption></figure></p>
<p><a href="https://rubykaigi.org/2022/presentations/ko1.html">Making <em>MaNy</em> threads on Ruby - RubyKaigi 2022</a></p>
<p>ã¡ãªã¿ã«ãM:N ã¹ã¬ããã¢ãã«ã®å®è£
ãªã®ã§ MaNy ããã¸ã§ã¯ãã¨ããã³ã¼ããã¼ã ãä¸ç°ããã«ã¤ãã¦ãããã¾ããã</p>
<ul>
<li>M:1ã¹ã¬ããã¯ãRubyã¹ã¬ããMåã«ãããã¦ãã¤ãã£ãã¹ã¬ããã1åï¼Ruby 1.8 ã¾ã§ï¼</li>
<li>1:1ã¹ã¬ããã¯ãRubyã¹ã¬ãã1åã«ãããã¦ãã¤ãã£ãã¹ã¬ããã1åï¼Ruby 1.9ï½ï¼</li>
<li>M:Nã¹ã¬ããã¯ãRubyã¹ã¬ããMåã«ãããã¦ãã¤ãã£ãã¹ã¬ãããNå</li>
</ul>
<p>ã¨ããã¢ãã«ã§ããN ãã³ã¢æ°ã«ãããã¨ã§ãååå°ããæ°ã®Nåã®ãã¤ãã£ãã¹ã¬ããã§ãMåã®Rubyã¹ã¬ããï¼ä¾ãã°1000åï¼ãè³ããã¨ãããã®ã§ããããã§ãã1:1ã¹ã¬ããã¢ãã«ãã¹ã±ã¼ã«ããªããã¨ããåé¡ã解決ãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="Thread system implementation techniques, quoted from my RubyKaigi2022 talk"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koichi-sasada/20230831/20230831112246.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Thread system implementation techniques, quoted from my RubyKaigi2022 talk</figcaption></figure></p>
<p>æ¬ ç¹ã¨ãã¦ã¯ãå®è£
ãè¤éã§ãããã¨ã§ãããããã¯é å¼µãã¾ãã/é å¼µã£ã¦ãã¾ãã</p>
<p>åãRactorã«å±ããRubyã¹ã¬ããã¯åæ並åã«ã¯åããªãã®ã§ã1ã¤ããRactorãåãããªãå ´åã¯Nããããå¤ããã¦ã2å以ä¸ã®ãã¤ãã£ãã¹ã¬ããã使ããã¨ã¯ããã¾ããããªã®ã§ããã®ç¹ã¯ã¦ã¼ã¶ã¬ãã«ã¹ã¬ããã¨åãã§ãã
ãã¾ãRubyã¹ã¬ããããããããã¨ããåæ©ã¯ãªãã®ã§ããï¼Ractor 使ã£ã¦ã»ããï¼ãå¯æ¬¡çã«ç¾å¨ã®ãã«ã Ruby ã¹ã¬ããããã°ã©ã ãã¦ã¼ã¶ã¬ãã«ã¹ã¬ããã¢ãã«ãç¨ãããã¨ã§æ¹åããããã¨ãããããããã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="M:1 Thread level scheduling in a Ractor, quoted from my RubyKaigi2022 talk"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koichi-sasada/20230831/20230831112630.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>M:1 Thread level scheduling in a Ractor, quoted from my RubyKaigi2022 talk</figcaption></figure></p>
<p>ã¦ã¼ã¶ã¬ãã«ã¹ã¬ããã§åé¡ã¨ãªã£ã¦ãããã©ã¼ããã¼ããªããããã¯ãã¦ãã¾ãå¦çã¯ããã®ãããã¯ãã¦ãã¾ãï¼å¯è½æ§ã®ããï¼å¦çãå®è¡ä¸ã1:1ã¹ã¬ãããã¤ã¾ã1ã¤ã®Rubyã¹ã¬ããã1ã¤ã®ãã¤ãã£ãã¹ã¬ãããå æãããã¨ããç¶æ
ã«ãã¦ããã¾ããã¡ãªã¿ã«ãã®ç¶æ
ããæ»ããªãã¨ãä»ã®Rubyã¹ã¬ããã«å¦çããã¤ããªããããªæ°ããã¾ãããã¡ããã¨ç§»ãããã«ãæºåã®ã§ããRubyã¹ã¬ãããå®è¡ããããã®ãã¤ãã£ãã¹ã¬ããã1åä½åã«è¿½å ãã¾ããã¤ã¾ããN ã¯ãã®ãããªã¹ã±ã¸ã¥ã¼ã«å¯è½ãªãã¤ãã£ãã¹ã¬ããã®æ°ã§ãããå æããã¦ããç¶æ
ã®ãã¤ãã£ãã¹ã¬ããã¯æ°ã«å
¥ãã¾ããï¼ä¸éãªãã«ãã¤ãã£ãã¹ã¬ãããä½ãããå¯è½æ§ãããã¾ãããåããªãããã¾ããã¨ããã¹ã¿ã³ã¹ã§ãï¼ã</p>
<p><figure class="figure-image figure-image-fotolife" title="Handle unmanaged blocking operations, quoted from my RubyKaigi2022 talk"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koichi-sasada/20230831/20230831112416.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Handle unmanaged blocking operations, quoted from my RubyKaigi2022 talk</figcaption></figure></p>
<p>ãã®å·¥å¤«ã«ãããã¦ã¼ã¶ã¼ã¬ãã«ã¹ã¬ããã¢ãã«ã§åé¡ã§ãã£ããä½ãå¦çãæ¢ã¾ã£ã¦ãã¾ããã¨ãã«å¥ã®Rubyã¹ã¬ããã«åãæ¿ãããªããªããã¨ããåé¡ã解決ãã¾ãã
M:Nã¹ã±ã¸ã¥ã¼ã©ã¯ãåãæ¿ãããªããªããããããªããã¨ãã£ãå±éºã®ãªããªã£ããããã¦ã¿ã¤ã ã¹ã©ã¤ã¹ã§åãæ¿ããï¼ããªã¨ã³ãã·ã§ã³ãããï¼çµã¿è¾¼ã¿ã® Fiber scheduler ã¿ãããªãã®ãã¨ããã¨ããæ¹ãã§ããã¨æãã¾ãã</p>
<p>ãã®M:Nã¹ã¬ããã®å®è£
ã¯ãGo language ã®æ§æã«ããä¼¼ã¦ãã¾ããã¡ãã£ã¨ããéãã¨ãã¦ã¯ãgoroutine ã¯ã©ããé ä¸åã§å®è¡ã§ããã®ã§ãããRubyã®ã¹ã¬ããã¯åãRactorã«æå±ãã¦ããå ´åãåæã«åããã¨ã¯ã§ããªãï¼GVLããã¤Rubyã¹ã¬ããããåãããªãï¼ãã¨ããå¶ç´ã§ãããã®å¶ç´ãæºãããããRactorå
ã®Rubyã¹ã¬ããã«ã¤ãã¦ã®ã¹ã±ã¸ã¥ã¼ã©ã¨ãã©ã®Ractorãåãããé¸ã¶Ractorã«ã¤ãã¦ã®ã¹ã±ã¸ã¥ã¼ã©ã®2ã¤ã®ã¹ã±ã¸ã¥ã¼ã©ã«ããæ§æã¨ãªã£ã¦ãã¾ãï¼ãã¡ããç´°ããéãã¯ã»ãã«ãããããããã¾ãï¼ã</p>
<p>M:Nã¹ã¬ããã¯ãå¤ãã®å ´åã§ï¼ã¡ããã¨ä½ã£ã¦æãã°ï¼åé¡ãªãã¨æãããã®ã§ãããã©ããã¦ãä»çµã¿çã«ãã¤ãã£ãã¹ã¬ããã® Thread local storage ã«ä¾åããä½ãã«ãªã£ã¦ããã³ã¼ããå©ç¨ããã¨ç ´ç¶»ãããã¨ããåé¡ãããã¾ãï¼ããRubyã¹ã¬ãããç°ãªããã¤ãã£ãã¹ã¬ããã§å®è¡ããããã«ãªãããï¼ãããã§ãä»ã®ã¨ãã M:N ã¹ã¬ããã¢ãã«ã¯ããã©ã«ãã§ã¯ãªã³ã«ãªããªãããã«ãããã¨ãããã¨ã«ãã¦ãã¾ããããæ£ããã¯ãã¡ã¤ã³Ractorã®ä¸ã§Rubyã¹ã¬ãããä½ãå ´åï¼ã¤ã¾ãããµã¤ãã®Rubyã«ããã¹ã¬ããããã°ã©ã ã®å ´åï¼ã1:1 ã¹ã¬ããã¨ããå¾æ¥ã®ã¹ã¬ããã¢ãã«ã§å®è¡ããããã¨ã«ãªãã¾ããè¤æ°Ractorãå©ç¨ããå ´åã¯ãã¡ã¤ã³Ractor以å¤ã¯M:Nã¹ã¬ããã¢ãã«ã§å®è¡ããã¾ãã</p>
<p>ä»ã®ã¨ããã<code>RUBY_MN_THREADS=1</code> ã¨ããç°å¢å¤æ°ã§ã¡ã¤ã³Ractorã§ã®M:Nã¹ã¬ãã対å¿ãæå®åºæ¥ãããã«ããäºå®ã§ãããã M:N ã¹ã¬ããã®å®è£
ããã¼ã¸ãããã試ãã¦ã¿ã¦ãã ãããã¡ãªã¿ã«ããã¤ãã£ãã¹ã¬ããæ°ã®ä¸éNãæå®ããã«ã¯ãä»ã®ã¨ãã<code>RUBY_MAX_CPU=n</code>ã¨ããç°å¢å¤æ°ã§æå®ã§ããããã«ããäºå®ã§ãã</p>
<p>詳細ã¯å½è©²ãã±ãããã覧ãã ãã: <a href="https://bugs.ruby-lang.org/issues/19842">Feature #19842: Introduce M:N threads - Ruby master - Ruby Issue Tracking System</a></p>
<p>æ§è½æ¹åãªã©ã¯ãã¾ããã¡ãã¨ã§ãã¦ããªãã®ã§ãããå»å¹´ã® RubyKaigi 2022 ã®çºè¡¨ã§ãå°ãè¿°ã¹ã¦ãã¾ããå ´åã«ãã£ã¦ã¯ã ãã¶éããªã£ã¦ãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="Ring example, quoted from my RubyKaig 2022 talk"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koichi-sasada/20230831/20230831112739.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Ring example, quoted from my RubyKaig 2022 talk</figcaption></figure></p>
<p><figure class="figure-image figure-image-fotolife" title="Ring example, compare with Go/Loop time, quoted from my RubyKaigi 2022 talk"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koichi-sasada/20230831/20230831112809.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Ring example, compare with Go/Loop time, quoted from my RubyKaigi 2022 talk</figcaption></figure></p>
<h2 id="Ractor-local-GC-ã®å°å
¥">Ractor local GC ã®å°å
¥</h2>
<p>ç¾å¨ Ractor ãé
ããã£ã¨ã大ããªçç±ã GC ã§ãã並åã«å®è¡ããã Ractor ã§ãããGC ãããããã«ã¯ãã¹ã¦ãæ¢ããå¿
è¦ããããã¨ããç¶æ
ã§å¯ä¸ã®ãã¤ãã£ãã¹ã¬ããä¸ã§GCãå®è¡ãããããã«ãªã£ã¦ãã¾ããããã¯è²ã
é
ãã®ã§ãRactorãã¨ã«GCããããã並åã«å®è¡ãããã¨ããRactor local GCãã§ããªãã試è¡é¯èª¤ä¸ã§ãï¼ä»ã®æ¹ã試é¨å®è£
ä¸ï¼ã</p>
<p><figure class="figure-image figure-image-fotolife" title="Ractor local GC, quoted from my RubyKaigi2023 talk"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koichi-sasada/20230831/20230831113102.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Ractor local GC, quoted from my RubyKaigi2023 talk</figcaption></figure></p>
<p>ãããå®ç¾ããããã«ã¯ãRactorããªã¾ãã£ã Immutable ãªãã¸ã§ã¯ã㯠Ractor éã§å
±æã§ããã¨ãã£ãä»çµã¿ããããã¡ãã¨åããããã«ã¯åæ£GCãå¿
è¦ã«ãªãã¾ããç¾å¨ãå®è£
ããªããåé¡ãè¦ã¤ã解決ãã¦ãããããªææ¢ããªæãã§éçºãé²ãã¦ãã¾ããæ¥å¹´ãããã«ä½ããç´¹ä»ã§ããã¨ããã§ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="Ractor local GC needs distributed GC, quoted from my RubyKaigi2023 talk"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koichi-sasada/20230831/20230831113020.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Ractor local GC needs distributed GC, quoted from my RubyKaigi2023 talk</figcaption></figure></p>
<h2 id="ãããã«">ãããã«</h2>
<p>æ¬ç¨¿ã§ã¯ãRubyã®ä¸¦è¡ä¸¦åå¦çã«ã¤ãã¦å¤è¦³ããå©ç¹ã¨æ¬ ç¹ãã¾ã¨ãã¾ãããããã¦ããããã®æ¬ ç¹ã解æ¶ããããã« M:N ã¹ã¬ããã¢ãã«ã®å®è£
ãè¡ã£ã¦ãããç¾ç¶ããç´¹ä»ãã¾ãããã¾ããããã«ã¾ã¨ããªæ§è½ã«ããããã«ã¯Ractor local GCãå¿
è¦ã§ããã¨ãããã¨ããç´¹ä»ãã¾ããã</p>
<p>ãã¾ã®ã¨ãããM:Nã¹ã±ã¸ã¥ã¼ã©ã¯ã1:1ã¢ãã«ã«ãã¦ããã°ï¼ããã©ã«ãã§ãï¼ãã¹ãéã£ã¦ããã®ã§ãããM:Nã¹ã±ã¸ã¥ã¼ã©ãæå¹ã«ããã¨ãããã¼ï¼ãã¨ããã¨ããã§ãã°ãåºãã®ã§ãRuby 3.3 ã«ãã¼ã¸ã§ããã®ãäºæã許ããªãæãã§ããããã°ãã</p>
<p>ã¯ãã¯ãããã§ã¯ãå®ã¯ãã£ã¨ãã®è¾ºããã£ã¦ãã¾ããã</p>
<ul>
<li>2016: Guild ï¼ã®ã¡ã® Ractorï¼æ§æ³ã®çºè¡¨</li>
<li>2017: Ractor ã«ã¤ãªããããã®ãFiber å¨ãã®æ´çï¼ãã®å¹´ã«ã¯ãã¯ãããå
¥ç¤¾ï¼</li>
<li>2018: Ractor ã®å®è£
ã®æ¤è¨</li>
<li>2019: Ractor ã®å®è£
ï¼RubyKaigi 2019 ã§ã¯ Ruby 㧠MRI ãæ¸ãããã®è©±ããã¦ãããã©ï¼</li>
<li>2020: Ractor ã®å
¥ã£ãRubyã®ãªãªã¼ã¹ï¼Guild -> Ractor ã«ååãå¤ãã£ãã®ããã®å¹´ï¼</li>
<li>2021: Ractor ããããã°ã§ããããã«ããããã« debug.gem ã®éçºï¼ã¾ã Ractor ã§ã¯åããªããã ãã©ï¼</li>
<li>2022: M:N ã¹ã±ã¸ã¥ã¼ã©æ§æ³ã®çºè¡¨ã¨ãããã¿ã¤ã</li>
<li>2023: M:N ã¹ã±ã¸ã¥ã¼ã©ã§ Ractor ãåãããããã«</li>
</ul>
<p>ï¼ãã£ã¦ããã®ã¯ããã ããããªããã©ï¼åããã¼ãã§ä½å¹´ãã£ã¦ããã ãã¨ããæ°ããã¾ãããé·ãç®ã§éçºãæ¯ãã¦ãããã¯ãã¯ãããã«æ·±ãæè¬ãã¾ãã</p>
<p>ãããªæãã§ã¾ã ã¾ã ãããã¨ãã¤ããã¤ããã¾ãããã並å並è¡å¦çãæ¸ããªãRubyãããããã¨ãã£ã¦ããããããã«ããããããé å¼µã£ã¦ããããã¨æãã¾ãã</p>
<p>èªã¾ãªãã¦ãããä½è«ã§ããç§ã®åè«ï¼2002ï¼ã¯ã¹ã¬ããã©ã¤ãã©ãªï¼pthreadï¼ã®å®è£
ã§ãã¦ã20å¹´ãã£ã¦ãä¼¼ããããªãã¨ãããã£ã¦ãã¼ãªãã¨ããææ³ãããã¾ãï¼ã§ãã20å¹´ãã£ã¨æ¥½ããã®ã§å¹¸ããã¡ãªã¿ã«YARVéçºã¯2004å¹´ãããªã®ã§ãããã20å¹´ï¼ãM:N ã¹ã±ã¸ã¥ã¼ã©ã¯ãã®é ããèãã¦ã¯ãããã ãã©ãå½æã¯éã«é
ããªããããªãããªã©ã¨æã£ã¦ããã¨ããã§ãããGo ãã ãããåããããªãã¨ããã¦ãããã¨ãããã¨ã確èªã§ããã®ã§ãçµæ§èªä¿¡ããã£ã¦é²ãã¦ããã¨ãã次第ã§ããã¾ããGo ã¯ã»ãã®ã©ã³ã¿ã¤ã ãå
¨é¨èªåã§ããã¦ããã®ã§ãTLSã¿ãããªäºææ§åé¡ããã¾ãèµ·ããªãã¨ããã¨ããã¯ããã¨ã¯æããã§ããã2018å¹´ã®RubyConf ã§ãã¾ã¤ãã¨ããã®é¨å±ã§ãããªæ§æ³ã話ãã¦ããã§ã2020å¹´ã«ã¯éã«åããªãããªã¼ãã¨è¨ã£ã¦ããå
容ãããã£ã¨å½¢ã«ãªã£ã¦ãã¾ããããã¾ãã¾ã¨ããããªãåãéãããï¼</p>
<p>ã¨ããããã§ãã¾ãã©ããã§ææããã¿ãã§ãããã¨ã楽ãã¿ã«ãã¦ãã¾ãã</p>
<p>Happy hacking!</p>
koichi-sasada
TechMTGæåèµ·ããã¬ãã¼ãï¼ã¯ãã¯ããããã¼ãã®Androidã¢ããªã®UIéçºã®ããã¾ã§ã¨ãããã
hatenablog://entry/820878482954241916
2023-07-31T18:18:11+09:00
2023-07-31T18:18:11+09:00 ããã«ã¡ã¯ãCTO室ã®ç·å·ã§ããã¯ãã¯ãããã§ã¯éé±ã§å
¨ã¨ã³ã¸ãã¢ãéã¾ãTechMTGã¨ãããã¼ãã£ã³ã°ãè¡ã£ã¦ãã¾ããä»åã¯TechMTGã§è©±ããæè¡çãªåãçµã¿ã解説ãæåèµ·ããã¬ãã¼ãã¨ãã¦ãå±ããã¾ãã ä»åã¯4æ19æ¥ã«çºè¡¨ãããã¯ãã¯ããããã¼ãã§ã®Androidã¢ããªã®UIéçºã«ã¤ãã¦ã§ãã 以ä¸ãã¬ãã¼ãæ¬æã§ãã ã¯ãã¯ããããã¼ãã®Androidã¢ããªã®UIéçºã®ããã¾ã§ã¨ãããã ããã«ã¡ã¯ãéç°ã§ãã 2016å¹´ã«æ°åå
¥ç¤¾ããã¦2019å¹´ã«ã¯ãã¯ããããã¼ãã«ç§»åããä»ã¯è²·ç©ãããã¯ãéçºé¨ã¨ããã¨ããã§Androidã¨ã³ã¸ãã¢ããã¦ãã¾ãã ä»åã¯Androidã¢ãâ¦
<p>ããã«ã¡ã¯ãCTO室ã®ç·å·ã§ããã¯ãã¯ãããã§ã¯éé±ã§å
¨ã¨ã³ã¸ãã¢ãéã¾ãTechMTGã¨ãããã¼ãã£ã³ã°ãè¡ã£ã¦ãã¾ããä»åã¯TechMTGã§è©±ããæè¡çãªåãçµã¿ã解説ãæåèµ·ããã¬ãã¼ãã¨ãã¦ãå±ããã¾ãã</p>
<p>ä»åã¯4æ19æ¥ã«çºè¡¨ãããã¯ãã¯ããããã¼ãã§ã®Androidã¢ããªã®UIéçºã«ã¤ãã¦ã§ãã
以ä¸ãã¬ãã¼ãæ¬æã§ãã</p>
<h2 id="ã¯ãã¯ããããã¼ãã®Androidã¢ããªã®UIéçºã®ããã¾ã§ã¨ãããã">ã¯ãã¯ããããã¼ãã®Androidã¢ããªã®UIéçºã®ããã¾ã§ã¨ãããã</h2>
<p>ããã«ã¡ã¯ãéç°ã§ãã
2016å¹´ã«æ°åå
¥ç¤¾ããã¦2019å¹´ã«ã¯ãã¯ããããã¼ãã«ç§»åããä»ã¯è²·ç©ãããã¯ãéçºé¨ã¨ããã¨ããã§Androidã¨ã³ã¸ãã¢ããã¦ãã¾ãã</p>
<p>ä»åã¯Androidã¢ããªã®UIéçºã®è©±ããããã¨æãã¾ãã
Jetpack Composeã®è©±ãã¡ã¤ã³ã«ãªãã¨æã£ã¦ãããã§ããã©ãå»å¹´ã®11æãå
æã«ãJetpack Composeã®è©±ã¯ç¤¾å
ã§ãã¦ããã®ã§ãä»åã¯ãããªã«è©±ããã¨ããªããªã£ãããªã¨æã£ãã®ã§ããããã¯ãã¯ããããã¼ãã§Jetpack Composeãã©ããããµãã«ä½¿ã£ã¦ãããï¼ãã¨ãã話ã¯ã§ãã¦ããªãã£ãã¨æãã®ã§ãä»åã¯ããã話ããããªã¨æã£ã¦ãã¾ãã</p>
<h3 id="ã¢ããªã®å®è£
ã§æãæéããããé¨å">ã¢ããªã®å®è£
ã§æãæéããããé¨å</h3>
<p>æåã«åé¡å®ç¾©ããããã®ã§ãããã¢ããªã®å®è£
ãããã¨ãã«æãæéããããã¨ããã£ã¦çããã©ãã ã¨æãã¾ããï¼ åã®ä¸ã§ã¯ãªãã¨ãã£ã¦ãUIéçºãä¸çªå¤§ããã¨æã£ã¦ãã¾ããä»åã¯ãããåæã«è©±ãã¦ãããããªã¨æãã¾ãã
<span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20230731/20230731161556.png" width="1200" height="577" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ã¢ããªã®éçºããã¦ããä¸ã§ç´ æ©ãUIãæ§ç¯ãããã¨ããç´ æ©ãæ©è½ãéçºãã¦ãããã¨ã«æãå¯ä¸ããããããªãããªã¨æã£ã¦ãã¾ããããããããã©ãããã°å®ç¾ã§ããã®ã ããã¨ããã¨ãããèããã¨ãã«ããã¿ã¼ã³åãã¦ç°¡åã«èª°ã§ãæ©ãéçºã§ããã°ããããããªããã¨ãããã¨ãèãã¦ãã¯ãã¯ããããã¼ãã®éçºåæããçµæ§ãã®ãã¨ãèãã¦å®è£
ãã¦ãã¾ããã</p>
<p>2019å¹´ã«ã¯ãã¯ããããã¼ããä½ãå§ããã¨ãã¯Android ViewãããããXMLã使ã£ã¦æ§ç¯ãã¦ãã³ã¼ãã§å³ä»ããã¦ããã¿ãããªæ¹æ³ãä¸è¬çã ã£ãæ代ãªãã§ããã©ããã®æã«ã¯RecyclerViewã¨ããè¦ç´ ããã£ã±ã並ã¹ãã©ã¤ãã©ãªã¨ããã¨ãããç°¡åã«ä½¿ãããã®Groupieã£ã¦ããã©ã¤ãã©ãªã使ã£ã¦è¤éãªç»é¢ã®æ§ç¯ãç°¡åã«ãã¦ããã®ããã£ã¦ãã¾ãããããã®ã¡ãªããã¨ãã¦ã¯ã¬ã¤ã¢ã¦ããã¡ã¤ã«ããã¨ãã°åå詳細ã ã£ããã¨ããé·ãã¬ã¤ã¢ã¦ãã®ãã¡ã¤ã«ã§ãããç¨åº¦ã®ã¾ã¨ã¾ããã¨ã«åå²ãã¦çµã¿ç«ã¦ã¦ãããã¨ãã§ããã®ã§ãå
¨ä½ãã·ã³ãã«ã«æ§ç¯ã§ããã¨ãããã¨ã«ä¸ã¤å¤§ããªã¡ãªãããããã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20230731/20230731161622.png" width="1200" height="595" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ãã¨ã¯ä¸è¦§ç»é¢ã¨è©³ç´°ç»é¢ã¿ãããªã¨ããã§å®è£
ã®ãã¿ã¼ã³åãã§ãã¦ããããå®è£
é度ã®åä¸ã«å¤§ããå¯ä¸ãããããããªãããªã¨æã£ã¦ãã¾ããããã«é¢ãã¦ã¯å½æããã¯ãããã¯ã«ãæ¸ãã¦ãã®ã§ãããåç
§ãã¦ãã ããã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechlife.cookpad.com%2Fentry%2F2019%2F04%2F11%2F130000" title="ã¯ãã¯ããããã¼ãAndroidã¢ããªã®ç»é¢å®è£
ãæé«ã«ãã話ãé£è¼:ã¯ãã¯ããããã¼ãéçºã®è£å´ vol.4ã - ã¯ãã¯ãããéçºè
ããã°" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techlife.cookpad.com/entry/2019/04/11/130000">techlife.cookpad.com</a></cite></p>
<p>å
·ä½çã«ã¯ã©ããªãµãã«ãªã£ã¦ãããã¨ããã¨ãä¸ã®æ¹ããSectionã£ã¦ããç»é¢ã®åä½ãåãã¦ãã£ã¦ããã横å´ã«æ¸ãã¦ããã¨ãç»é¢å
¨ä½ã®æ§ç¯ãå²ã¨è¦ãããç¶æ
ã«ãªã£ã¦ããããä¸ã¤ä¸ã¤ã®ã¬ã¤ã¢ã¦ãã®ãã¡ã¤ã«ã®åä½ãå·¦å´ã®layout XMLãæ§ç¯ããã¦ãããå³å´ã§ããã«å¯¾ãã¦ååãå
¥ãããã¨ãç»åãè¨å®ãããã¨ããã¨ãããµãã«è¨å®ãã¦ããã¨çµæ§ããããããã·ã³ãã«ãªã¬ã¤ã¢ã¦ããçµããããããªãããªã¨æã£ã¦ãã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20230731/20230731161649.png" width="1200" height="695" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span>
<span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20230731/20230731162435.png" width="1200" height="657" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ãã ããã®è¾ºã¯éçºä¸ã®åé¡ãçµæ§ãã£ã¦ããã¨ãã°åçãªUIã®æ´æ°ã¨ããã®ãå°å³ã«é£ããã£ãããããã§ãããããã¨ãã°ãè¦ç´ ã追å ãããã¨ãåé¤ãããã¨ããããã¿ãããªUIè¦ç´ ãå
¥ãããæã¨ãã«Viewã追å åé¤ããã®ã¯çµæ§åæã«ãªããã¡ã§é£ããã£ãããè§ä¸¸ã¨ããã¼ãã¼ã¨ãã®è¡¨ç¾ãAndroidã ã¨å°å³ã«é¢åãããã¨ããããã£ã¦ãããé£ããã¨ããã§ããã</p>
<p>ãã¨ã¯è¤éãªUIç¶æ
ã¨ããã®ãLayout XMLã®Previewã£ã¦ä¸ã¤ãããªãã®ã§ãå®éã«ãã¼ã¿ãå½ã¦ã¯ãã¦ç¢ºèªããããéããªãã®ã§çµæ§é£ããã¨ããã®ãããã¾ããã¯ãã¯ããããã¼ãã ã¨ç¹ã«ååã®åãåãã®ç¶æ
ã£ã¦è¤æ°ãã£ã¦ã6ã»7種é¡ããããããã§ããã©ã¹ãã¼ã¸ã³ã°ç°å¢ã ã¨ãã®ç¶æ
ãåç¾ããã®ã«ãä¸è¦å´ããã®ã§ããã®åä½ç¢ºèªãããã大å¤ã ã£ãã£ã¦ããã®ãããã¾ãããã¨ã¯å
¨ä½ãã³ã¼ãã§çµã¿ç«ã¦ã¦ããã®ã§ç»é¢å
¨ä½ã®æ§ç¯ã£ã¦ããã¨ããã確èªã§ããªãã®ãçµæ§é£ããã¨ããã§ããã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20230731/20230731161732.png" width="1200" height="616" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ãã¨ãã°UIã追å ã¨ããåé¤ã¨ãããã¿ãããªè©±ãªãã§ããã©ãjQueryã¨ããè¦ã¦ããããã¨åããã¨æããã§ããã©ãç¹å®ã®divã¿ã°ã«åè¦ç´ ã®ãã¥ã¼ãå
¨é¨æ¶ãã¦ããä¸åå
¥ãç´ãã¿ãããªãã¨ãåæã§å
¥ãã¦ããæ°ããã¦ãå®è¡ãã¦ã¿ãªãã¨ãã¾ãã§ãããããããªãã¨ããå
¥ããã¨ããã®ãã¥ã¼ã®å¤§ããã ã£ããè¦ç´ éã®ãã¼ã¸ã³ã ã£ããã¨ããçµæ§èª¿æ´ãã¥ããã£ããã¨ããããããåé¡ããã£ã±ãèµ·ãã¾ããããã¨è§ä¸¸ãã¤ãããã ãã§ããã ãæ¸ããªãããããªãã¦ããããbackgroundã«è¨å®ããã¿ãããªãã¨ããããªãããããªãã¦ãããé¢åãããã£ãã£ã¦ããã®ãããã¾ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20230731/20230731161757.png" width="1200" height="660" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ãã¨ã¯ãã¿ã¼ã³åãèããã¨ãã«ããã¨ãã°ãã®ããããä¾¡æ ¼ã£ã¦ããã¨ããã¯ãå½æ¥é©ç¨ããããã¿ã¼ã³ã¨ãããªããã¿ã¼ã³ããããã§ããã©ããããªããã¿ã¼ã³ãã©ããããµãã«ç¢ºèªããã®ãã¨ããã¨ããããªããã¿ã¼ã³ãå¿ã®ç®ã§æãåã£ã¦å®è£
ãããããªãã¨ããã®ãçµæ§ãã¤ãã§ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20230731/20230731161820.png" width="665" height="613" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Androidã¨ãiOSã®éçºã£ã¦çµæ§ã¬ã¤ã¢ã¦ããå´©ãããã¨ãããã¨æããã§ããã©ããããã¨ããã大ããããããªãããªã¨åã¯æã£ã¦ã¾ãã</p>
<h3 id="Jetpack-Composeã¯UIéçºã®ä½ãå¤ããã">Jetpack Composeã¯UIéçºã®ä½ãå¤ãããï¼</h3>
<p>2022å¹´ã¯çµæ§Jetpack Composeã®å¹´ã«ãªã£ããã¨æããã§ããã©ãJetpack Composeã£ã¦UIéçºã®ä½ãå¤ãããã£ã¦ããã¨ãããããåçãªViewã®è¿½å åé¤ã ã£ããã¨ãè§ä¸¸ã ã£ããã¨ããã¼ãã¼ã ã£ããã¨ãããããä»ã¾ã§ã¡ãã£ã¨ããã¥ããã£ãUIã®è¡¨ç¾ã ã£ããã¨ããç°¡ç¥åãã¦ãããã®ãçµæ§å¤§ããããªã¨æããã§ãã</p>
<p>ä»ã«ã¯é«æ§è½ãªPreviewãä½ã£ã¦ãããã£ã¦ããã®ãåã®ä¸ã§ã¯ä¸çª
Jetpack Composeã®ä¸ã§ããã¨æã£ã¦ãã¦ããããå¼·åããã¦ãããæ±ãããã®éçºã¹ã¿ã¤ã«ã®è¨è¨ãå»å¹´ã¯ãã¦ãã¾ãããJetpack Composeã¯ç´æçã«ifæãæ¸ãããã¨ããã¦æ¡ä»¶å¼ã«ãã£ã¦ãã®Viewã表示ããããããªãã¿ãããªãã®ãå¶å¾¡ã§ãããã¨ãããã¨è§ä¸¸ã表示ããã¨ãã«.clipã£ã¦æ¸ãã ãã§ä¸è¡ã§è¡¨ç¾ã§ãããã¨ãããããã®ãæ¬å½ã«å©ããã¾ããã</p>
<h3 id="Previewã®ãããããã">Previewã®ãããããã</h3>
<p>ãã¨ãä»æ¥ã¯Previewã®è©±ããããããããã§ããã©ãPreviewã¯ãã¡ããã¡ãè¯ãã¦ãPreviewã®ãããããã¨ããã¯Previewãèªåã§æ¸ããã¨ãã§ããã¨ããã§ããéã«è¨ãã¨èªåã§æ¸ããªãã¨ãããªããã§ããã©ãä»»æã®è¦ç´ ã«å¯¾ãã¦Previewãèªåã§æ¸ãããã§ãããªã®ã§ãã©ããªPreviewã§ãèªåã§ä½ããã¨ãã§ãã¦ãã¤ã¾ãåãUIã«å¯¾ãã¦ãè¤æ°ã®ãã¼ã¿ãå
¥ãã¦Previewãæ¸ããã¨ããã¡ããã§ããã¨ããã®ãããªãå¼·ãã¨ããããªã¨æãã¾ãããã¨ã¯Live Editã£ã¦ããããªãã¼ãã¿ãããªãã¤ã§ãããComposeã®ã³ã¼ããç·¨éãã¦ããã¨æ¨ªã§èªåã§ã³ã³ãã¤ã«ãèµ°ã£ã¦Previewãã©ãã©ãæ´æ°ããã¦ãã£ã¦ãã®ç¶æ
ãè¦ããã¨ããæãã«ãªã£ã¦ã¾ãã
<span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20230731/20230731161856.png" width="1200" height="589" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Previewãåºãã°ã£ããã®ã¨ãã¯Android Studioãçµæ§ä¸å®å®ã§ãã¾ãåããªãã£ãã¨ãããã£ããã§ããã©ãæè¿ã¯ããªãå®å®ãã¦ä½¿ããããã«ãªã£ã¦ããã®ã§ããããªãã§ã¯UIéçºãé²ã¾ãªãã¨ãããããã«æé«ã«ãªã£ã¦ãã¾ããã</p>
<p>ãã¨Interactive Modeã ã£ããã¨ãDeploy Previewã£ã¦ããã®ããã£ã¦ãä½ã£ãPreviewãå®æ©ã¨ãã¨ãã¥ã¬ã¼ã¿ã¼ã«ãã¤ã³ã¹ãã¼ã«ã§ãããã§ãããã¨ãã°ãã¿ã³ãæ¼ããã¨ãã«ãã¼ã¹ãéç¥ã表示ãã¦ãã ããã¨ãããã¿ã³ãæ¼ããã¨ãã«ãã®è¦ç´ ãé ãã¦ãã ããã¨ãããããã¤ãã³ãã¨ããå
¨é¨åºã¦ãã¦ããã®ã§ãå®ã¯Previewã ãã§ã¢ããªãä½ãã¦è»½ããããã¿ã¤ãã³ã°ã¿ãããªã¨ãããã§ããã¨ããã®ãçµæ§å¤§ãããªã¨å人çã«ã¯æã£ã¦ãã¾ãã</p>
<p>ã§ããããã©ããããµãã«æ±ã£ã¦ããããã¨ããã¨ãããèããã¨ãã«Live Editã«ããã注ç®ãã¦ãã¦ãLive Editãæå¹ã«æ±ãããã«ã¯ã¢ããªã®ã³ã³ãã¤ã«æéãçãããå¿
è¦ããããã§ãã
ã
ãªãã§ãã¨è¨ãã¨ããã£ãè¨ã£ãã¨ããLive Editããã¨ãã¯æ¨ªã§ã³ã³ãã¤ã«ãèµ°ã£ã¦èªåã§Previewãæ´æ°ãããã¨ãã話ããã¦ãã®ã§ãéã«è¨ãã¨ã³ã³ãã¤ã«æéãé·ãã£ããç·¨éãã¦ããéã«ã³ã³ãã¤ã«ããããæéãããã£ã¦ãPreviewã表示ãããã¾ã§30ç§ã¨ã1åã¨ããããã£ã¦ãããããªç¶æ
ã«ãªãã¨çµæ§ãã¤ãã¨æãã®ã§ããã®æéãçãããå¿
è¦ãããã¾ããComposeã®é¢æ°ãæ¸ãããã ãã®ã¢ã¸ã¥ã¼ã«ãåãã£ã¼ãã£ã¼ãã¨ã«åé¢ãããããªéçºææ³ãã¯ãã¯ããã ãã¼ãã§ã¯åãçµã¿ã¾ãããã§ãããã§Previewãããã¢ã¸ã¥ã¼ã«ã ããã«ããããã°ãããªãã®ã§ãããªãã³ã³ãã¤ã«ã楽ã«ãªã£ãã¨ããæãã§ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20230731/20230731161926.png" width="1200" height="573" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ãã¨ã¯è¡¨ç¤ºãã¿ã¼ã³ãPreviewã§ç¶²ç¾
ã§ããããã«ãªã£ãã¨ãããã¨ã§ãã¡ããã¨ç¶²ç¾
ãããã¨ããããã¤ãã®å®è£
ã«ã¼ã«ã ã£ããã¨ãã決ããããã¾ããã
å®éã«Previewãæ¸ãã¨@Previewã£ã¦ä»ãã¦ããã¢ããã¼ã·ã§ã³ãæ¸ãã¦ããé¢æ°ãPreviewç¨ã®Composeã®é¢æ°ã§ãå®éã®Composeã®é¢æ°ã«å¯¾ãã¦å¼ãæ°ã«ãã¼ã¿ã渡ãã¦ãããã°å³å´ã«ãã¼ã¿ãåºã¦ãã¾ããä¸ã«æ¸ãã¦ããProviderãå®éã«ãã®Previewã«æ¸¡ããããã¼ã¿ã®ä¸è¦§ã¿ãããªæãã«ãªã£ã¦ãã¦ããããå¢ããã¦ãããã¨ã§ããããªç¶æ
ã®Previewããã£ã±ãä½ããã¨ãã§ããããã¾ãã</p>
<p>ã§ãããååä¸è¦§ç»é¢ã®ååã®ãã¼ã¿ã®ç¨®é¡ããã¼ãã¨Previewã§ä½ã£ã¦ã¿ãããã¤ãªãã§ããã©ããããã便å©ã§ã¢ããªã§å®æ©ã§å®éã«ç¢ºèªãããã¨ããã¨ããå
¨é¨åç¾ããã®ãã¡ããã¡ã大å¤ãªãã§ããã©ãPreviewã§ç¢ºèªããã ãã§ãããªã«ç°¡åã«ä½ããã£ã¦ããã®ã¯ãããè¯ãã£ããªã£ã¦ããæãã§ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20230731/20230731161956.png" width="1200" height="674" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>ãã¨ã¯ãã£ãã®ã¢ã¸ã¥ã¼ã«ã®åé¢ã®è©±ãããã¨ãã¢ã¸ã¥ã¼ã«ã¯ãã£ã±ãããã¦ãã¾ããã§ãPreviewã«å¿
è¦ãªãã«ãç¯å²ãæå°éã«ãã¦ãã¾ããã¢ããªã®ãã«ãã«ãã£ã¦çµæ§æéããã£ã¦ãã¦ã¯ãã¯ããããã¼ãã®ã¢ããªã ã¨5åã¨ããææª7åãããããã£ãããããã§ããã©ããã£ã¼ãã£ã¢ã¸ã¥ã¼ã«ã®ä¸ã¤ã ãã®ãã«ãã¨ãã ã¨æ°åç§ç¨åº¦ã§çµãã£ããããã®ã§ãããã§éçºé度ãå¤ãã£ã¦ãããªã¨ããå°è±¡ãããã¾ããã</p>
<p>ãã¨ã¯éçºæ¹éã®è©±ã§ããã¤ãã®å®è£
ã«ã¼ã«ãåãã話ã軽ãããã¨æããã§ããã©ãç»é¢å
¨ä½ã®ãã¨ãScreenã¨ãããµãã«ã¯ãã¯ããããã¼ãã§ã¯å¼ãã§ãã£ã¦ãåºåãç·ã§å²ã¾ããé¨åãSectionã¨ãããµãã«å¼ãã§ãã¾ãããã¨ã¯Sectionãããã¤ãã®åä½ã§ã¾ã¨ããã®ãContentã¨ãããµãã«å¼ãã§ãããã§ããã©ãå®è£
ã«è½ã¨ãã¦ããã¨Contentã並ãã§ããã¿ãããªå®è£
ã ã£ããããã¡ã¤ã«ãåãã¦ãã®Contentã®ä¸èº«ã«ã¯ã©ãã«ã«ããããã¹ãã¨Sectionã¨ã¿ãããªæãã§ããããåãã¦ããã¨ãããµãã«ãã¦ããããããã«å¯¾ãã¦ããããã§Previewãä½ããã®ã§ç»é¢å
¨ä½ã®æ§ç¯ãããã ãç»é¢ã®ç´°ããã¨ããã«å¯¾ãã¦ã®UIã®Previewãããªãç°¡åã«ä½ããããã«ãªã£ãã£ã¦ããã®ãããã大ããå¤åã ã£ããªã¨ãããµãã«æãã¾ãã</p>
<h3 id="ããããã®è©±">ããããã®è©±</h3>
<p>ããã¾ã§ã2022å¹´ãããã¾ã§ãåããä»ã¾ã§éçºãã¦ããUIéçºã®è©±ã ã£ããã§ããã©2023å¹´ã ããããããã®è©±ã§ã¡ãã£ã¨ãããããªã¨æãã¾ããæè¿Relayã£ã¦ãããã¼ã«ãæ°ã«ãªã£ã¦ãã¦ããããä½ãã¨è¨ãã¨figmaã®ãã¼ã¿ãComposeã®ã³ã¼ãã«å¤æãã¦ããããã¼ã«ãªãã§ãããã§ããã®è¾ºå·éã«èãã¦ã¿ãã¨çµæ§é¢åãããä½æ¥ã§ããã¶ã¤ãã¼ãããfigmaã§ãã¶ã¤ã³ä½ã£ã¦ããã¦ãæã
ã¢ããªã¨ã³ã¸ãã¢ãSwift UIã ã£ããã¨ãJetpack Composeã§ãããå®ç¾ããã£ã¦ããéçºããã¼ã«ãªã£ã¦ããã§ããã©ãããã£ã¦ããã®ã¯figmaã¨Swift UIã¨Jetpack Composeã§åããã¶ã¤ã³ã§3åä½ã£ã¦ããã£ã¦ãããã¨ã«ãªã£ã¦ãã¦ããããã ãããªããã£ã¦ãããµãã«æè¿ã¯èãã¦ãã¾ããéã«è¨ãã¨ãããããªãããã°UIã®éçºã£ã¦æé«ã«ãªãããããã£ã¦æè¿ã¯ã¡ãã£ã¨èããããã¦ãã¾ãã</p>
<p>ã³ã¼ãå¤æã ã£ããã³ã¼ãçæã ã£ãããè¡ã£ã¦ããããã¼ã«ã£ã¦ããããããã¨æããã§ããã©ããã®Relayã£ã¦ãã¼ã«ã¯ä½ããããã£ã¦ããã¨Android Developersãå
¬å¼ã§åºãã¦ããã¼ã«ãªãã§ããããã ããã¡ãã£ã¨èå³ããã£ã¦ãã¡ãã£ã¨è¦ã¦ããã§ããã©ã¾ã ã¾ã ãªæ®µéã¨ãããããªäºæã¯ãã¦ãã¾ãã</p>
<p>ã¡ãã£ã¨ä½¿ã£ã¦ã¿ããã§ããã©ãåå詳細ç»é¢ã®figmaãComponentã¨ãã¦Relayã«å°ãããã¨Composeã®é¢æ°ãåæã«ä½ã£ã¦ããã¾ããä»ã¯ã¬ã¤ã¢ã¦ãã¨ãå´©ãã¡ãã£ã¦ãããã§ããã©ããã©ã³ãã調æ´ãã¦ä¸æãããã¨ã¨ããã¨ãããã«æç»ããããã¨ããã¦ãçµæ§ç°¡åã«ä½ãã¦UIéçºãããã¨ã¹ã ã¼ãºã«ãªãããããªãããªã¨ãããµãã«å¦æ³ãã¦ãã¨ããã§ãã</p>
<p>Relayãç°¡åã«è§¦ãã¦ã¿ãææã¨ãã¦ã¯figmaã®ãªã³ã¯ãè²¼ãã ãã§ã³ã¼ããçæãããã®ã§ç°¡åã«ä½¿ããã£ã¦ããã®ã¯ãããããã¦ããã¶ã¤ã³ã®ã¢ãããã¼ããfigmaã«ãã¦ãå®è£
ãå³ã¯ãªãã¯ã§ç°¡åã«ã¢ãããã¼ãã§ããã®ã§ããããããªãããªã¨æã£ã¦ããã§ããã©ãAndroid Studioä¸ããã¢ãããã¼ããããããªãã¦CIã¨ãã§æ´æ°ããã®ãã¡ãã£ã¨é£ãããã£ã¦ããã¨ããã¯ã²ã¨ã¤ããã¯ããªã¨æã£ã¦ã¾ãããã¨ã¯figmaã®ãã¶ã¤ã³ãAuto Layoutã£ã¦ããçµæ§ãããã«ãããªãããããªãã¨ããä»çµã¿ã使ããªãã¨ãããªãã¦ããããã¡ãã£ã¨é¢åããããªã£ã¦ããã¨ãããããã¾ãã</p>
<p>ãã¨ç´°ããå¶å¾¡ãé£ããã£ã¦ããã¨ããããããã§ããã©ãå²ãåã£ã¦ä½¿ããå ´é¢ã¯ããããããªã¨æã£ã¦ãã¦ãå
±éComponentã«ãªããã®ã ãåãåºãã¨ããããããã¨ããã¯ãã¾ã使ããã¨ããããªãããªã£ã¦æ¢ããªããå¦æ³ãã¦ããã¨ããã§ããã¯ãã¯ããããã¼ãã§ã¯ç´°ããå®ç¾©ããã¦ããã®ãããã®ã§ãå°ããªComponentãããã ã£ãããã¾ã使ããªãããªã£ã¦ããã®ã¯ã¡ãã£ã¨å¦æ³ãã¦ãããã¾ãã</p>
<p>ã¯ãã¯ããããã¼ãã®Androidã®UIéçºã«ã¤ãã¦ãããã話ãã¦ãããã§ããã©ãJetpack Composeã¯ãããã¨ä»ã¾ã§çãããèãã¦ããã¨æããã§ããã©ãJetpack Composeã¯ãããã§ãããç¹ã«Live Previewãæé«ã«ããã®ã§ãçããããã²ä½¿ã£ã¦ã¿ã¦ãã ããããã¨ã¯æå¾ã«è¨ã£ã¦ãéããã¶ã¤ã³ãã³ã¼ãã«åãè¾¼ãåçµã¿ãããªä½æ¥ããã¤ãçµããããããããããªã¨æã£ã¦ããã¨ããã§ãã</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20230731/20230731162022.png" width="1200" height="603" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
cookpadtech
iOSç»åéåæåå¾
hatenablog://entry/820878482952300372
2023-07-25T09:00:00+09:00
2023-07-25T09:00:07+09:00 ããã«ã¡ã¯ãã¢ãã¤ã«åºç¤ã®ã´ã¡ã³ãµã³(@vincentisambart)ã§ãã åå¹´ãããåã«ãiOSã¯ãã¯ãããã¢ããªã§ç»åéåæåå¾ãèªä½ãããã¨ã«ãªãã¾ãããå°å
¥ãã¦ããä½ã¶æãåé¡ãªãåãã¦ããã®ã§ãã©ãåãã¦ããã®ãç´¹ä»ãããã¨æãã¾ããã§ããã®åã«èªä½ãããã¨ã«ãªã£ãçµç·¯ã説æãã¾ãããã èªä½çµç·¯ é·å¹´ç»åéåæåå¾ã«æ¢åã®ã©ã¤ãã©ãªã使ã£ã¦ãã¾ããããæ¨å¹´ã©ã¤ãã©ãªã®ä¸å
·åã§ç»åã®åå¾ãç¨ã«å¤±æãã¦ãããã°ãããã¤ãããã¾ããããã°ãä¿®æ£ããã¦ããã®æ°ã¶æå¾ã«ã¾ãä¼¼ãåé¡ã ãã®ç¶æ
ã好ã¾ãããªãã£ãã®ã§ã以ä¸ã®é¸æè¢ã®ã©ããã«ãããã¨è°è«ãã¾ããã 使ã£ã¦ããã©ã¤ãã©ãªã®ã¡ã³â¦
<p>ããã«ã¡ã¯ãã¢ãã¤ã«åºç¤ã®ã´ã¡ã³ãµã³(<a href="https://twitter.com/vincentisambart">@vincentisambart</a>)ã§ãã</p>
<p>åå¹´ãããåã«ãiOSã¯ãã¯ãããã¢ããªã§ç»åéåæåå¾ãèªä½ãããã¨ã«ãªãã¾ãããå°å
¥ãã¦ããä½ã¶æãåé¡ãªãåãã¦ããã®ã§ãã©ãåãã¦ããã®ãç´¹ä»ãããã¨æãã¾ããã§ããã®åã«èªä½ãããã¨ã«ãªã£ãçµç·¯ã説æãã¾ãããã</p>
<h2 id="èªä½çµç·¯">èªä½çµç·¯</h2>
<p>é·å¹´ç»åéåæåå¾ã«æ¢åã®ã©ã¤ãã©ãªã使ã£ã¦ãã¾ããããæ¨å¹´ã©ã¤ãã©ãªã®ä¸å
·åã§ç»åã®åå¾ãç¨ã«å¤±æãã¦ãããã°ãããã¤ãããã¾ããããã°ãä¿®æ£ããã¦ããã®æ°ã¶æå¾ã«ã¾ãä¼¼ãåé¡ã</p>
<p>ãã®ç¶æ
ã好ã¾ãããªãã£ãã®ã§ã以ä¸ã®é¸æè¢ã®ã©ããã«ãããã¨è°è«ãã¾ããã</p>
<ul>
<li>使ã£ã¦ããã©ã¤ãã©ãªã®ã¡ã³ããã³ã¹ã«ãã£ã¨ç´æ¥åå ãã
<ul>
<li>ã³ã¼ããå¤ãã¡ã³ããã³ã¹ãããããªãããã§ããã</li>
</ul>
</li>
<li>使ã£ã¦ããã©ã¤ãã©ãªã®ãã¼ã¸ã§ã³ãåºå®ãã
<ul>
<li>èªåçã«æ´æ°ãããã¦ãããã°ä¿®æ£ãææ°ã®OSã®å¯¾å¿ã®ããã«å®æçã«æ´æ°ããæ¹ãè¯ãã§ãããã</li>
</ul>
</li>
<li>å¥ã®ã©ã¤ãã©ãªã«ããã
<ul>
<li>é¸å®ãé£ããã§ããããä¾ãã°ã¢ããªããªãªã¼ã¹ããªãã¨ã©ã¤ãã©ãªã®å®å®æ§ãå¤æãã¥ããã§ãã</li>
</ul>
</li>
<li>èªä½ããã</li>
</ul>
<p>ã©ã¤ãã©ãªã使ã£ã¦ãããã®ã®ãè¤éãªæ©è½ã¯ä½¿ã£ã¦ãã¾ããã§ãããå¿
è¦ã ã£ãã®ã¯ç»åã®ãã¦ã³ãã¼ãããã£ãã·ã¥ã<a href="https://ja.wikipedia.org/wiki/WebP">WebP</a>対å¿ããããã§ãã</p>
<p>ã¯ãã¯ãããã§ã¢ãã¤ã«ã¢ããªã®ç»åã¯WebPã使ã£ã¦ããã®ã§ãç»ååå¾ãèªä½ãããã¨ã«ãªã£ã¦ããWebPèªã¿è¾¼ã¿ã«ã©ã¤ãã©ãªãå¿
è¦ã ã¨æã£ã¦ãã¾ãããã§ãè°è«ä¸ã«ãiOS 14以é<code>UIImage</code>ãWebPãèªã¿è¾¼ããã®ãçºè¦ãã¾ããããã®æç¹ã§ææ°ã®iOSã¯ãã¯ãããã¢ããªã®æå°ãµãã¼ããã¼ã¸ã§ã³ã¯ãã§ã«iOS 15ã§ããã</p>
<p>ç»åãã¦ã³ãã¼ãã¨ãã£ãã·ã¥ã ããå¿
è¦ãªãèªä½ãã¦ã¿ã¦ãè¯ãããã¨ããçµè«ã«ãªãã¾ããã</p>
<h2 id="å®è£
">å®è£
</h2>
<p>çµç·¯ã®æ¬¡ã¯å®è£
ã®è©³ç´°ã説æãããã¨æãã¾ãããã®ä¸ã§ãã¾ãã¯ä¸çªè¤éãããªãã£ãã·ã¥ã®å®è£
ã¯ã©ããã¾ããããã</p>
<h3 id="ãã£ãã·ã¥">ãã£ãã·ã¥</h3>
<p>å¤ãã®ç»åéåæåå¾ã©ã¤ãã©ãªããã£ãã·ã¥ã2段éã§è¡ãã¾ãï¼</p>
<ul>
<li>åå¾ãããç»åãã¡ã¤ã«ããã£ã¹ã¯ã«ãã£ãã·ã¥ãã¾ããã¢ããªãåèµ·åãã¦ããã¼ã¿ã¯æ®ãã¾ãï¼ãã ã端æ«ã«ç©ºã容éã足ããªããªã£ãå ´åãOSãä¸é¨ãæ¶ããã¨ãããã¾ãï¼ã</li>
<li>ç»åãã¡ã¤ã«ãèªã¿è¾¼ã¾ãã<code>UIImage</code>ãã¡ã¢ãªä¸ã«ãã£ãã·ã¥ãã¾ãããã¡ããã¢ããªãåèµ·åãããä¸æããã¾ãããã£ã¹ã¯ããã®ãã¡ã¤ã«èªã¿è¾¼ã¿ããç»åãã¼ã¿ã®ãã³ã¼ãããã¡ã¤ã³ã¹ã¬ããã§ãããªãæ¹ãè¯ããã¨ã§ããã<code>UIImage</code>ãã¡ã¢ãªä¸ã§ãã£ãã·ã¥ããã¨ã©ã£ã¡ãå¿
è¦ãªãã®ã§ãã®ãã£ãã·ã¥ã¯ç´æ¥ã¡ã¤ã³ã¹ã¬ããã§æ±ãã¾ãã</li>
</ul>
<p>ã©ã¡ãã®ãã£ãã·ã¥ã®ç¨®é¡ãã§ããã°èªåã§å®è£
ãããããã¾ããããã£ã¹ã¯ãã¡ã¢ãªã®ç©ºã容éããã£ãã·ã¥ã®ä½¿ã£ã¦ãã容éããæ°ã«ããå¿
è¦ãããã®ã¯ããããããã§ããã§ãå®ã¯Foundationã«ãã®ããã®ãã¼ã«ãããã¾ãã</p>
<p>ãã¦ã³ãã¼ãããããã¼ã¿ããã£ãã·ã¥ããããã«<a href="https://developer.apple.com/documentation/foundation/urlcache"><code>URLCache</code></a>ãããã¾ãããã¦ã³ãã¼ãã«ä½¿ã<code>URLSession</code>ã®<code>configuration</code>ã«ä»£å
¥ããã ãã§ãã¦ã³ãã¼ããããã¼ã¿ããã£ãã·ã¥ãããããã«ãªãã¾ãã</p>
<p>ã¡ã¢ãªä¸ã§ãã¼ã¿ããã£ãã·ã¥ããããã«<a href="https://developer.apple.com/documentation/foundation/nscache"><code>NSCache</code></a>ãããã¾ãã使ãæ¹ã<code>Dictionary</code>ã«è¿ãã§ãããã¼ã¨å¤ã<code>AnyObject</code>ã§ããã¹ããªã®ã§Swiftãã使ãå ´åå°ãä¸ä¾¿ãªå ´é¢ãããã¾ãããä»åãã¼(<code>URL</code>)ã<code>NSURL</code>ã«ç°¡åã«ãã£ã¹ãã§ããã®ã§ãå¥ã®ãã®ã«ã©ããããã«<code>AnyOject</code>ã«ã§ãã¾ãã</p>
<h3 id="API">API</h3>
<p>ãã£ãã·ã¥ã®ããã®ãã¼ã«ãæã£ãã®ã§ãç»ååå¾APIãè¦ã¦ã¿ã¾ãããã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">enum</span> <span class="synIdentifier">LoadingImage</span> {
<span class="synStatement">case</span> cached(UIImage)
<span class="synStatement">case</span> inProgress(Task<span class="synIdentifier"><</span>UIImage, any Error<span class="synIdentifier">></span>)
}
<span class="synStatement">final</span> <span class="synPreProc">class</span> <span class="synIdentifier">ImageLoader</span> {
<span class="synPreProc">func</span> <span class="synIdentifier">loadImage</span>(from imageURL<span class="synSpecial">:</span> <span class="synType">URL</span>) <span class="synSpecial">-></span> <span class="synType">LoadingImage</span> {
</pre>
<p><code>Task</code>ãè¦ãããã®ã§Swift Concurrencyã使ããã¦ããã®ã§ãããasyncã¡ã½ããã§ã¯ããã¾ããã</p>
<p>ãã¥ã¼ã®èªã¿è¾¼ã¿ã表示æã«OSããå¼ã°ããã¡ã½ãã(<code>collectionView(_:cellForItemAt:)</code>ã<code>viewDidLoad</code>ããªã©)ã¯åºæ¬çã«asyncã§ã¯ããã¾ããã</p>
<p><code>loadImage</code>ã®å®ç¾©ã<code>func loadImage(from imageURL: URL) async throws -> UIImage</code>ã§ããããasyncã§ãªãã¡ã½ããããå¼ã¶ã¨æ°è¦ã¿ã¹ã¯ãä½æããå¿
è¦ãããã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">func</span> <span class="synIdentifier">viewDidLoad</span>() {
Task { <span class="synType">@MainActor</span> <span class="synType">in</span>
<span class="synComment">// ãã®ã³ã¼ãã`viewDidLoad`ã®ã¿ã¤ãã³ã°ã§å®è¡ãããã®ã§ã¯ãªãã</span>
<span class="synComment">// `MainActor`ã次åå®è¡ããããã«ãã¥ã¼ããã¾ãã</span>
<span class="synPreProc">let</span> <span class="synIdentifier">image</span> <span class="synIdentifier">=</span> await imageLoader.loadImage(from<span class="synSpecial">:</span> <span class="synType">imageURL</span>)
imageView.image <span class="synIdentifier">=</span> image
}
}
</pre>
<p><code>loadImage</code>å
ã§<code>await</code>ãããã«<code>return</code>ããã¨ãã¦ããåã<code>MainActor</code>ã§å®è¡ãããå¥ã®ã¿ã¹ã¯å
ãªã®ã§ã<code>viewDidLoad</code>ã®å¾ã«å®è¡ããã¦ãã¾ãã¾ããç»åãã¡ã¢ãªä¸ã®ãã£ãã·ã¥ã«ãã£ãã¨ãã¦ãããã¥ã¼ã®æåã«æåã§ç»åã表示ãããªãå¯è½æ§ãããã¾ããæåã®æåã§ç»åãªãã次ã®æåã§ç»åãããã¯ãã«ãã«ãã¦éã«è¦ãã¾ãããã§ã¼ãã¤ã³ã使ãã°å°ããã·ã§ããããã表示ã§ããã°ãããã§ãã</p>
<h2 id="loadImage"><code>loadImage</code></h2>
<p>æ¬æ¥ã®<code>loadImage</code>ã®ã³ã¼ããè¦ã¦ã¿ã¾ãããã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synStatement">final</span> <span class="synPreProc">class</span> <span class="synIdentifier">ImageLoader</span> {
<span class="synPreProc">func</span> <span class="synIdentifier">loadImage</span>(from imageURL<span class="synSpecial">:</span> <span class="synType">URL</span>) <span class="synSpecial">-></span> <span class="synType">LoadingImage</span> {
<span class="synComment">// ç»åãã¡ã¢ãªä¸ãã£ãã·ã¥ã«å
¥ã£ã¦ããã°ãããè¿ãã¾ãã</span>
<span class="synStatement">if</span> <span class="synPreProc">let</span> <span class="synIdentifier">image</span> <span class="synIdentifier">=</span> cachedImage(<span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">imageURL</span>) {
<span class="synStatement">return</span> .cached(image)
}
<span class="synComment">// ããã¯ã°ã©ã¦ã³ãã¿ã¹ã¯ã§åå¾ã¨ãã³ã¼ããè¡ãã¾ãã</span>
<span class="synComment">// `detached`ã使ãã®ã¯actorãå¼ãç¶ããªãããã§ãã</span>
<span class="synStatement">return</span> .inProgress(Task.detached {
<span class="synPreProc">let</span> <span class="synIdentifier">request</span> <span class="synIdentifier">=</span> URLRequest(url<span class="synSpecial">:</span> <span class="synType">imageURL</span>)
<span class="synComment">// ã³ã¼ãã®åãããããã®ããã«ç»åã`URLCache`ã«å
¥ã£ã¦ããã®ãã©ããåºå¥ãã¦ãã¾ãããã</span>
<span class="synComment">// å¿
è¦ã§ããã°`urlCache.cachedResponse(for: request)`ã§å
¥ã£ã¦ãããã©ãã確èªã§ãã¾ãã</span>
<span class="synPreProc">let</span> <span class="synIdentifier">data</span> <span class="synIdentifier">=</span> <span class="synStatement">try</span> await <span class="synIdentifier">self</span>.loadData(<span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">request</span>)
<span class="synStatement">return</span> <span class="synStatement">try</span> <span class="synIdentifier">self</span>.decode(data, <span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">imageURL</span>)
})
}
</pre>
<p>ãã¦ã³ãã¼ãããã£ã³ã»ã«ããããã°ã<code>inProgress()</code>ã«å
¥ã£ãã¿ã¹ã¯ã®<code>cancel()</code>ã¡ã½ãããå¼ã³ã¾ãã<code>Task.init()</code>ãä»åã®ããã«<code>Task.detached()</code>ã使ãã¨structured concurrencyã§ã¯ãªãã®ã§ã<code>loadImage(from:)</code>ã®æ»ãå¤ãæ¾ç½®ãã¦ãã¿ã¹ã¯ããã£ã³ã»ã«ããããã¨ã¯ããã¾ããã</p>
<p>ä¸è¨ã«ä½¿ããã¦ãã<code>cachedImage(for:)</code>ã<code>NSCache</code>ã®ã¡ã½ãããå¼ã¶ã ãã§ãã<code>URL</code>ã<code>AnyObject</code>ã§ã¯ãªãã®ã§ã<code>NSCache</code>ã®ãã¼ã«ä½¿ãã«ã¯<code>NSURL</code>ã«ãã£ã¹ãããå¿
è¦ãããã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synStatement">final</span> <span class="synPreProc">class</span> <span class="synIdentifier">ImageLoader</span> {
<span class="synStatement">private</span> <span class="synPreProc">let</span> <span class="synIdentifier">memoryCache</span> <span class="synIdentifier">=</span> NSCache<span class="synIdentifier"><</span>NSURL, UIImage<span class="synIdentifier">></span>()
<span class="synPreProc">func</span> <span class="synIdentifier">cachedImage</span>(<span class="synStatement">for</span> imageURL<span class="synSpecial">:</span> <span class="synType">URL</span>) <span class="synSpecial">-></span> <span class="synType">UIImage?</span> {
memoryCache.object(forKey<span class="synSpecial">:</span> <span class="synType">imageURL</span> <span class="synStatement">as</span> <span class="synType">NSURL</span>)
}
</pre>
<p><code>loadData(for:)</code>ã«é¢ãã¦ã¯ã<code>URLSession.data(for:)</code>ãããå°ã使ããããããã ãã§ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synStatement">final</span> <span class="synPreProc">class</span> <span class="synIdentifier">ImageLoader</span> {
<span class="synStatement">private</span> <span class="synPreProc">let</span> <span class="synIdentifier">session</span><span class="synSpecial">:</span> <span class="synType">URLSession</span>
<span class="synStatement">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">loadData</span>(<span class="synStatement">for</span> request<span class="synSpecial">:</span> <span class="synType">URLRequest</span>) async <span class="synStatement">throws</span> <span class="synSpecial">-></span> <span class="synType">Data</span> {
<span class="synStatement">do</span> {
<span class="synStatement">return</span> <span class="synStatement">try</span> await session.data(<span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">request</span>).<span class="synConstant">0</span>
} <span class="synStatement">catch</span> {
<span class="synComment">// ã¿ã¹ã¯ããã£ã³ã»ã«ãããæã`session.data(for:)`ãCocoaã®ã¨ã©ã¼ãçºçãããã®ã§ããã</span>
<span class="synComment">// Swiftã§ã¯`CancellationError`ããã£ã¨èªç¶ã ã¨æãã¾ãã</span>
<span class="synComment">// ã¿ã¹ã¯ããã£ã³ã»ã«ããã¦ããå ´å`CancellationError`ãçºçããã`try Task.checkCancellation()`ãã¡ããã©è¯ãã§ãã</span>
<span class="synStatement">try</span> Task.checkCancellation()
<span class="synStatement">throw</span> error
}
}
</pre>
<p><code>decode(_:for:)</code>ã<code>UIImage(data:)</code>ãã©ãããã¦ããã³ã¼ããããç»åã<code>NSCache</code>ã«å
¥ãã¦ãããã ãã§ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synStatement">final</span> <span class="synPreProc">class</span> <span class="synIdentifier">ImageLoader</span> {
<span class="synStatement">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">decode</span>(_ data<span class="synSpecial">:</span> <span class="synType">Data</span>, <span class="synStatement">for</span> imageURL<span class="synSpecial">:</span> <span class="synType">URL</span>) <span class="synStatement">throws</span> <span class="synSpecial">-></span> <span class="synType">UIImage</span> {
<span class="synStatement">if</span> <span class="synPreProc">let</span> <span class="synIdentifier">image</span> <span class="synIdentifier">=</span> UIImage(data<span class="synSpecial">:</span> <span class="synType">data</span>) {
memoryCache.setObject(image, forKey<span class="synSpecial">:</span> <span class="synType">imageURL</span> <span class="synStatement">as</span> <span class="synType">NSURL</span>)
<span class="synStatement">return</span> image
} <span class="synStatement">else</span> {
<span class="synStatement">throw</span> InvalidImageDataError(url<span class="synSpecial">:</span> <span class="synType">imageURL</span>)
}
}
</pre>
<p><code>ImageLoader</code>ã®å
¨ã³ã¼ãããã®è¨äºã®ä¸çªä¸ã«ã¾ã¨ãã¾ãããä¸è¨ã«ãªãã£ã<code>init</code>ãå«ã¾ãã¦ãã¾ãã</p>
<h2 id="使ãæ¹">使ãæ¹</h2>
<p>ä¸è¨ã«å®è£
ããAPIã¯åºæ¬çã«ä»¥ä¸ã®ããã«ä½¿ãã°è¯ãã®ã§ã¯ãªãã§ããããã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">// 以åè¡ãããåå¾ãçµãã£ã¦ããªããã°ãã£ã³ã»ã«ãã¾ãã</span>
<span class="synComment">// ãã£ã³ã»ã«ãããå¯è½æ§ãªããªãã`loadTask`ã¤ã³ã¹ã¿ã³ã¹å¤æ°ã¯è¦ããªãã§ãããã</span>
<span class="synStatement">if</span> <span class="synPreProc">let</span> <span class="synIdentifier">loadTask</span> {
loadTask.cancel()
<span class="synIdentifier">self</span>.loadTask <span class="synIdentifier">=</span> <span class="synConstant">nil</span>
}
<span class="synComment">// åå¾ãå§ãã¾ãã</span>
<span class="synStatement">switch</span> imageLoader.loadImage(from<span class="synSpecial">:</span> <span class="synType">imageURL</span>) {
<span class="synStatement">case</span> <span class="synPreProc">let</span> .cached(image)<span class="synSpecial">:</span>
<span class="synComment">// `image`ããã®ã¾ã¾è¡¨ç¤ºã§ãã¾ãã</span>
<span class="synStatement">case</span> <span class="synPreProc">let</span> .inProgress(loadTask)<span class="synSpecial">:</span>
<span class="synComment">// ãã£ã³ã»ã«ã§ããããã«`loadTask`ãã¨ã£ã¦ããã¾ãã</span>
<span class="synIdentifier">self</span>.loadTask <span class="synIdentifier">=</span> loadTask
Task { <span class="synType">@MainActor</span> <span class="synSpecial">[</span><span class="synType">weak self</span><span class="synSpecial">]</span> <span class="synStatement">in</span>
<span class="synStatement">do</span> {
<span class="synPreProc">let</span> <span class="synIdentifier">image</span> <span class="synIdentifier">=</span> <span class="synStatement">try</span> await loadTask.value
<span class="synComment">// ç¡äºã«ç»åãåå¾ã§ããã®ã§è¡¨ç¤ºã§ãã¾ãã</span>
} <span class="synStatement">catch</span> <span class="synStatement">is</span> <span class="synType">CancellationError</span> {
<span class="synComment">// å¾
ã¡åããã¦ãã`loadTask`ããã£ã³ã»ã«ãããã®ã§ä½ãããã¹ãã§ã¯ããã¾ããã</span>
} <span class="synStatement">catch</span> {
<span class="synComment">// åå¾ã失æããã®ã§ãplaceholderã表示ãããã¨ãå¤ãã§ãã</span>
}
}
}
</pre>
<h2 id="移è¡">移è¡</h2>
<p>iOSã¯ãã¯ãããã¢ããªã§ä»¥å使ã£ã¦ããã©ã¤ãã©ãªããèªä½<code>ImageLoader</code>ã¸ã®ç§»è¡ãå²ã¨ã¹ã ã¼ãºã§ããã<a href="https://techlife.cookpad.com/entry/2021/06/16/110000">ã¢ã¸ã¥ã¼ã«å</a>ã«ãã£ã¦ç»åèªã¿è¾¼ã¿ãæ½è±¡åããã¦ããå ´é¢å¤ãã£ãã§ãããã»ã¨ãã©ã®ç»åãéãããæ°ã®ãã¥ã¼ã«è¡¨ç¤ºããã¦ãã¾ãã</p>
<p>ç»ååå¾ãã¾ã æ½è±¡åããã¦ããªãã£ãããã¾ãæ½è±¡åããããæ°ããå®è£
ã®ä¸ã«ä»¥åã®ã«è¿ãAPIãç¨æããããã®2æã§ããããåè
ã¯æ½è±¡åãçµãã£ãã移è¡ãããã©ãå¾è
ã¯æ°ããã³ã¼ãã«ç§»è¡ãã¦ãã以åã®APIã®å©ç¨ãå°ããã¤æ¸ããã¾ãã</p>
<h2 id="æå¾ã«">æå¾ã«</h2>
<p>iOSã®æ¢åã®ãã¼ã«ã使ãã°ãã·ã³ãã«ãªç»ååå¾ã¯å²ã¨ã·ã¥ãã¨å®è£
ã§ãã¾ãããã ããã¨ãã£ã¦ç»ååå¾ãèªä½ããæ¹ãè¯ãããã§ãããã¾ãããã¢ããªã«ãã£ã¦ç¶æ³ãéãã¾ããèªä½ãããèªåã§ã¡ã³ããã³ã¹ããå¿
è¦ãããã¾ãããå¿
è¦ã«ãªã£ãæ©è½ãèªåã§å®è£
ãã¾ãã</p>
<p>èªä½ãããã©ããé¢ä¿ãªããç»ååå¾ãæ½è±¡åããã¦ããã¨ãå¥ã®ã©ã¤ãã©ãªã«ç§»è¡ããããã®ã§ããã¾ç´æ¥ã©ã¤ãã©ãªã使ã£ã¦ãã¦ãã¦å¤ããå¯è½æ§ãããã°ãã¨ããããæ½è±¡åãã¦ãè¯ãããããã¾ããã</p>
<p>ãã®è¨äºã®ã³ã¼ããå
ã«èªä½ããã®ã§ããããæ©è½ã®è¿½å ãå¿
è¦ããããã¾ãããããã§ç´¹ä»ããã¦ãã¾ããããiOSã¯ãã¯ãããã¢ããªã§ã¯ãç»åãprefetchããä»çµã¿ããã¡ã¢ãªä¸ãã£ãã·ã¥ã«ãªãã£ãç»åã表示ã«ãã§ã¼ãã¤ã³ã§è¡¨ç¤ºããã<code>FadeInImageView</code>(UIKitç)ã¨<code>FadeInImage</code>(SwiftUIç)ãããã¾ãã</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">// This project is licensed under the MIT No Attribution license.</span>
<span class="synComment">// </span>
<span class="synComment">// Copyright (c) 2023 Cookpad Inc.</span>
<span class="synComment">// </span>
<span class="synComment">// Permission is hereby granted, free of charge, to any person obtaining a copy</span>
<span class="synComment">// of this software and associated documentation files (the "Software"), to deal</span>
<span class="synComment">// in the Software without restriction, including without limitation the rights</span>
<span class="synComment">// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell</span>
<span class="synComment">// copies of the Software, and to permit persons to whom the Software is</span>
<span class="synComment">// furnished to do so.</span>
<span class="synComment">//</span>
<span class="synComment">// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR</span>
<span class="synComment">// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,</span>
<span class="synComment">// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE</span>
<span class="synComment">// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER</span>
<span class="synComment">// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,</span>
<span class="synComment">// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE</span>
<span class="synComment">// SOFTWARE.</span>
<span class="synPreProc">import</span> Foundation
<span class="synPreProc">import</span> UIKit
<span class="synStatement">public</span> <span class="synPreProc">enum</span> <span class="synIdentifier">LoadingImage</span> {
<span class="synStatement">case</span> cached(UIImage)
<span class="synStatement">case</span> inProgress(Task<span class="synIdentifier"><</span>UIImage, any Error<span class="synIdentifier">></span>)
}
<span class="synStatement">public</span> <span class="synPreProc">struct</span> <span class="synIdentifier">InvalidImageDataError</span><span class="synSpecial">:</span> <span class="synType">Error</span> {
<span class="synStatement">public</span> <span class="synPreProc">var</span> <span class="synIdentifier">url</span><span class="synSpecial">:</span> <span class="synType">URL</span>
<span class="synStatement">public</span> <span class="synIdentifier">init</span>(url<span class="synSpecial">:</span> <span class="synType">URL</span>) {
<span class="synIdentifier">self</span>.url <span class="synIdentifier">=</span> url
}
}
<span class="synStatement">public</span> <span class="synStatement">final</span> <span class="synPreProc">class</span> <span class="synIdentifier">ImageLoader</span> {
<span class="synStatement">private</span> <span class="synPreProc">let</span> <span class="synIdentifier">session</span><span class="synSpecial">:</span> <span class="synType">URLSession</span>
<span class="synStatement">private</span> <span class="synPreProc">let</span> <span class="synIdentifier">memoryCache</span> <span class="synIdentifier">=</span> NSCache<span class="synIdentifier"><</span>NSURL, UIImage<span class="synIdentifier">></span>()
<span class="synStatement">public</span> <span class="synIdentifier">init</span>() {
<span class="synPreProc">let</span> <span class="synIdentifier">configuration</span> <span class="synIdentifier">=</span> URLSessionConfiguration.<span class="synStatement">default</span>
<span class="synPreProc">let</span> <span class="synIdentifier">cacheDirectoryURL</span><span class="synSpecial">:</span> <span class="synType">URL?</span>
<span class="synStatement">do</span> {
<span class="synPreProc">let</span> <span class="synIdentifier">systemCacheURL</span> <span class="synIdentifier">=</span> <span class="synStatement">try</span> FileManager.<span class="synStatement">default</span>.url(<span class="synStatement">for</span><span class="synSpecial">:</span> .cachesDirectory, <span class="synStatement">in</span><span class="synSpecial">:</span> .userDomainMask, appropriateFor<span class="synSpecial">:</span> <span class="synType">nil</span>, create<span class="synSpecial">:</span> <span class="synType">true</span>)
cacheDirectoryURL <span class="synIdentifier">=</span> systemCacheURL.appendingPathComponent(<span class="synConstant">"CookpadImageLoader"</span>, isDirectory<span class="synSpecial">:</span> <span class="synType">true</span>)
} <span class="synStatement">catch</span> {
assertionFailure(<span class="synConstant">"Could not create cache path: </span><span class="synSpecial">\(</span>error<span class="synSpecial">)</span><span class="synConstant">"</span>)
cacheDirectoryURL <span class="synIdentifier">=</span> <span class="synConstant">nil</span>
}
<span class="synComment">// ããã©ã«ãã§ã¯`URLCache.shared`ã使ããã¾ãããããå°ããã£ã¹ã¯å®¹éã使ããç»åå°ç¨ã®ã使ãã¾ãã</span>
configuration.urlCache <span class="synIdentifier">=</span> URLCache(
<span class="synComment">// `memoryCapacity`ã¯è©¦ããéã0ã§ãåé¡ãªãåãããã§ãããä¸å¿å¿µã®çºå°ãã®ã¡ã¢ãªãå²ãå½ã¦ã¾ãã</span>
memoryCapacity<span class="synSpecial">:</span> <span class="synType">URLCache.shared.memoryCapacity</span>,
diskCapacity<span class="synSpecial">:</span> <span class="synType">URLCache.shared.diskCapacity</span> <span class="synIdentifier">*</span> <span class="synConstant">4</span>,
directory<span class="synSpecial">:</span> <span class="synType">cacheDirectoryURL</span>
)
session <span class="synIdentifier">=</span> .<span class="synIdentifier">init</span>(configuration<span class="synSpecial">:</span> <span class="synType">configuration</span>)
}
<span class="synStatement">public</span> <span class="synPreProc">func</span> <span class="synIdentifier">cachedImage</span>(<span class="synStatement">for</span> imageURL<span class="synSpecial">:</span> <span class="synType">URL</span>) <span class="synSpecial">-></span> <span class="synType">UIImage?</span> {
memoryCache.object(forKey<span class="synSpecial">:</span> <span class="synType">imageURL</span> <span class="synStatement">as</span> <span class="synType">NSURL</span>)
}
<span class="synStatement">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">decode</span>(_ data<span class="synSpecial">:</span> <span class="synType">Data</span>, <span class="synStatement">for</span> imageURL<span class="synSpecial">:</span> <span class="synType">URL</span>) <span class="synStatement">throws</span> <span class="synSpecial">-></span> <span class="synType">UIImage</span> {
<span class="synStatement">if</span> <span class="synPreProc">let</span> <span class="synIdentifier">image</span> <span class="synIdentifier">=</span> UIImage(data<span class="synSpecial">:</span> <span class="synType">data</span>) {
memoryCache.setObject(image, forKey<span class="synSpecial">:</span> <span class="synType">imageURL</span> <span class="synStatement">as</span> <span class="synType">NSURL</span>)
<span class="synStatement">return</span> image
} <span class="synStatement">else</span> {
<span class="synStatement">throw</span> InvalidImageDataError(url<span class="synSpecial">:</span> <span class="synType">imageURL</span>)
}
}
<span class="synStatement">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">loadData</span>(<span class="synStatement">for</span> request<span class="synSpecial">:</span> <span class="synType">URLRequest</span>) async <span class="synStatement">throws</span> <span class="synSpecial">-></span> <span class="synType">Data</span> {
<span class="synStatement">do</span> {
<span class="synStatement">return</span> <span class="synStatement">try</span> await session.data(<span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">request</span>).<span class="synConstant">0</span>
} <span class="synStatement">catch</span> {
<span class="synComment">// ã¿ã¹ã¯ããã£ã³ã»ã«ãããæã`session.data(for:)`ãCocoaã®ã¨ã©ã¼ãçºçãããã®ã§ããã</span>
<span class="synComment">// Swiftã§ã¯`CancellationError`ããã£ã¨èªç¶ã ã¨æãã¾ãã</span>
<span class="synComment">// ã¿ã¹ã¯ããã£ã³ã»ã«ããã¦ããå ´å`CancellationError`ãçºçããã`try Task.checkCancellation()`ãã¡ããã©è¯ãã§ãã</span>
<span class="synStatement">try</span> Task.checkCancellation()
<span class="synStatement">throw</span> error
}
}
<span class="synStatement">public</span> <span class="synPreProc">func</span> <span class="synIdentifier">loadImage</span>(from imageURL<span class="synSpecial">:</span> <span class="synType">URL</span>) <span class="synSpecial">-></span> <span class="synType">LoadingImage</span> {
<span class="synStatement">if</span> <span class="synPreProc">let</span> <span class="synIdentifier">image</span> <span class="synIdentifier">=</span> cachedImage(<span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">imageURL</span>) {
<span class="synStatement">return</span> .cached(image)
}
<span class="synStatement">return</span> .inProgress(Task.detached {
<span class="synPreProc">let</span> <span class="synIdentifier">request</span> <span class="synIdentifier">=</span> URLRequest(url<span class="synSpecial">:</span> <span class="synType">imageURL</span>)
<span class="synComment">// ã³ã¼ãã®åãããããã®ããã«ç»åã`URLCache`ã«å
¥ã£ã¦ããã®ãã©ããåºå¥ãã¦ãã¾ãããã</span>
<span class="synComment">// å¿
è¦ã§ããã°`urlCache.cachedResponse(for: request)`ã§å
¥ã£ã¦ãããã©ãã確èªã§ãã¾ãã</span>
<span class="synPreProc">let</span> <span class="synIdentifier">data</span> <span class="synIdentifier">=</span> <span class="synStatement">try</span> await <span class="synIdentifier">self</span>.loadData(<span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">request</span>)
<span class="synStatement">return</span> <span class="synStatement">try</span> <span class="synIdentifier">self</span>.decode(data, <span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">imageURL</span>)
})
}
}
</pre>
vincentisambart
Project Googrename: Google Workspace 㧠14 å¹´éç¨ããããã¡ã¤ã³ã¨ã¤ãªã¢ã¹ããã©ã¤ããªãã¡ã¤ã³ã«å¤æ´ & å
¨ã¦ã¼ã¶ã¼ãå®å
¨ã«ãªãã¼ã ãã
hatenablog://entry/820878482945343579
2023-06-28T17:04:51+09:00
2023-06-28T17:04:51+09:00 id:sora_h ãã¯ãã¯ãããã® Google Workspace ã§ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ã¨ãã¦éç¨ããã¦ãããã®ããã©ã¤ããªãã¡ã¤ã³ã¸å¤æ´ãå
¨ã¦ã¼ã¶ã¼ã®ãã¡ã¤ã³ãåããã¦å¤§è¦æ¨¡ãªãªãã¼ã ãå®å
¨ã«å®æ½ããéã®ãã解説ãã¾ãã
<p><figure class="figure-image figure-image-fotolife" title="Google Workspace ã® Primary Domain Changed ç»é¢ã®ã¹ã¯ãªã¼ã³ã·ã§ãã"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sora_h/20230628/20230628160304.png" width="1200" height="501" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure></p>
<p>ã³ã¼ãã¬ã¼ãã¨ã³ã¸ãã¢ãªã³ã°é¨ã® <a href="http://blog.hatena.ne.jp/sora_h/">id:sora_h</a> ã§ã <a href="#f-55f9a260" name="fn-55f9a260" title="æè¡é¨ SRE ã°ã«ã¼ãã主åã§ããä¸å¿â¦">*1</a>ãä»å㯠3 ãµæã»ã©åã«å®æ½ãããGoogle Workspace ããã³ãã®ãã©ã¤ããªãã¡ã¤ã³å¤æ´ã«ã¤ãã¦ãè¨é²ãå
¼ãã¦èª¬æãã¾ãã</p>
<p>ã¯ãã¯ããã㯠2009 å¹´é <a href="#f-d60ff08e" name="fn-d60ff08e" title="ããã³ãèªä½ã¯ 2007 å¹´ãããæ¥åã«æ¬æ ¼çã«ä½¿ãããã®ã¯ 2009 å¹´ããããããã§ãã詳細ã¯ä¸æã§ã">*2</a> ãã Google Workspace <a href="#f-9eed22d0" name="fn-9eed22d0" title="å½æ㯠Google Apps">*3</a> ãå©ç¨ãã¦ãã¾ããå½ç¤¾ã®å¯¾å¤çãªã¡ã¼ã«ã¢ãã¬ã¹ã¯ cookpad.com ã§ãããGoogle ã§ã¯ãã©ã¤ããªãã¡ã¤ã³ã¨ã㦠cookpad.jp ãè¨å®ããã¦ãã¾ããåã¦ã¼ã¶ã¼ã«ã¯ cookpad.com ã®ã¢ãã¬ã¹ãå¥å (ã¨ã¤ãªã¢ã¹) ã¨ãã¦ç»é²ããã¦ãã¦ãã¡ã¼ã«ã¢ãã¬ã¹ã¨ãã¦ã¯ cookpad.com ãå©ç¨ããã Google ã¸ãã°ã¤ã³ããæã ã cookpad.jp ãå©ç¨ããéç¨ã«ãªã£ã¦ãã¾ãããæ³åãåºæ¥ãã¨æãã¾ããããããæ§ã
ãªé¢ã§ä¸ä¾¿ã»æ··ä¹±ãçºçããã¦ãã¾ãããã©ããã¦ãããªã£ã⦠<a href="#f-6766843a" name="fn-6766843a" title="ãããå°å
¥ææã¨åæ§ã«çµç·¯ã¯å®å
¨ã«æããã¦ãã¦ä¸æãå©ç¨ãã¦ããªããã¡ã¤ã³ã§ãä¸æ¦ãè¨å®ãã¦ã¿ã¦ããã®ã¾ã¾æ¬çªå©ç¨ããã¡ãã£ããã¿ã¼ã³ã¨æ³åãã¦ãã¾ã">*4</a>ã</p>
<p>ãã®è² åµã解決ãã¹ãã2022/8 é ããç·©ããã«æºåãå§ãã2023/3 ä¸æ¬ã«å
¨ã¦ã¼ã¶ã¼ã®ãã¡ã¤ã³ã¨ãã©ã¤ããªãã¡ã¤ã³ã cookpad.com ã«å¤æ´ãã¾ãããæ¬ç¨¿ã§ã¯å¤æ´ã«è¸ã¿åã£ãçç±ããä¸æºåãå½æ¥ï½äºå¾ã®ä½æ¥ã«ã¤ãã¦è§£èª¬ãã¾ããããããé·ãæ§ã
ãªã¿ã¹ã¯ããã£ãããä¹±éãªè¨äºã¨ãªã£ã¦ãã¾ãããä½ãã®å½¹ã«ç«ã¦ã°å¹¸ãã§ãã</p>
<ul class="table-of-contents">
<li><a href="#å¤æ´ã®ã¢ããã¼ã·ã§ã³">å¤æ´ã®ã¢ããã¼ã·ã§ã³</a></li>
<li><a href="#Google-Workspace-ã«ããããã¡ã¤ã³ã¨ã¯">Google Workspace ã«ããããã¡ã¤ã³ã¨ã¯</a><ul>
<li><a href="#ã¨ã¤ãªã¢ã¹">ã¨ã¤ãªã¢ã¹</a></li>
<li><a href="#Cookpad-ã«ããã-Google-Workspace-ãã¡ã¤ã³è¨å®">Cookpad ã«ããã Google Workspace ãã¡ã¤ã³è¨å®</a></li>
<li><a href="#ãã©ã¤ããªãã¡ã¤ã³ã¾ã§å¤æ´ãããã©ãã">ãã©ã¤ããªãã¡ã¤ã³ã¾ã§å¤æ´ãããã©ãã</a></li>
</ul>
</li>
<li><a href="#ç®æ¨-å½±é¿ãæå°ã«æãã">ç®æ¨: å½±é¿ãæå°ã«æãã</a></li>
<li><a href="#ããã¸ã§ã¯ãã®æµã">ããã¸ã§ã¯ãã®æµã</a></li>
<li><a href="#äºåæºå-å種ãµã¼ãã¹-SaaS-ã®å½±é¿ç¢ºèª">äºåæºå: å種ãµã¼ãã¹ (SaaS) ã®å½±é¿ç¢ºèª</a></li>
<li><a href="#äºåæºå-å
製ã·ã¹ãã ã®æºå">äºåæºå: å
製ã·ã¹ãã ã®æºå</a></li>
<li><a href="#äºåæºå-ã¡ã¼ã«åä¿¡ãã¦ã³ã¿ã¤ã ã®å½±é¿ãæå°åãã">äºåæºå: ã¡ã¼ã«åä¿¡ãã¦ã³ã¿ã¤ã ã®å½±é¿ãæå°åãã</a></li>
<li><a href="#äºåæºå-ä½æ¥ç¨ãã¼ã¿ã®æºå">äºåæºå: ä½æ¥ç¨ãã¼ã¿ã®æºå</a></li>
<li><a href="#ä½æ¥å½æ¥-ãã©ã¤ããªãã¡ã¤ã³å¤æ´ä½æ¥">ä½æ¥å½æ¥: ãã©ã¤ããªãã¡ã¤ã³å¤æ´ä½æ¥</a><ul>
<li><a href="#ç´åä½æ¥-MXã¬ã³ã¼ãã®åãæ¿ã">ç´åä½æ¥: MXã¬ã³ã¼ãã®åãæ¿ã</a></li>
<li><a href="#ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ã¨ãã¦ã®-cookpadcom-ãåé¤">ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ã¨ãã¦ã® cookpad.com ãåé¤</a></li>
<li><a href="#ã»ã«ã³ããªãã¡ã¤ã³ã¨ãã¦-cookpadcom-ãå追å ">ã»ã«ã³ããªãã¡ã¤ã³ã¨ã㦠cookpad.com ãå追å </a></li>
<li><a href="#å
¨ã¦ã¼ã¶ã¼ã°ã«ã¼ãã®ãã¡ã¤ã³ã-cookpadcom-ã«å¤æ´">å
¨ã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã®ãã¡ã¤ã³ã cookpad.com ã«å¤æ´</a></li>
<li><a href="#MXã¬ã³ã¼ãã®å¾©æ§">MXã¬ã³ã¼ãã®å¾©æ§</a></li>
<li><a href="#ãã©ã¤ããªãã¡ã¤ã³ã®å¤æ´">ãã©ã¤ããªãã¡ã¤ã³ã®å¤æ´</a></li>
<li><a href="#é害-ã¡ã¼ã«ã¨ã¤ãªã¢ã¹ã®èæ
®ä¸è¶³">é害: ã¡ã¼ã«ã¨ã¤ãªã¢ã¹ã®èæ
®ä¸è¶³</a></li>
</ul>
</li>
<li><a href="#äºå¾ä½æ¥">äºå¾ä½æ¥</a><ul>
<li><a href="#å種SaaSã¸ã®åæ 確èªä½æ¥">å種SaaSã¸ã®åæ ã»ç¢ºèªä½æ¥</a></li>
<li><a href="#Asana">Asana</a></li>
<li><a href="#Figma">Figma</a></li>
<li><a href="#Zoom">Zoom</a></li>
<li><a href="#Slack">Slack</a></li>
<li><a href="#ã¡ã¼ã«ãã¦ã³ã¿ã¤ã ã®äºå¾å ±å">ã¡ã¼ã«ãã¦ã³ã¿ã¤ã ã®äºå¾å ±å</a></li>
<li><a href="#DKIM-ã®åè¨å®">DKIM ã®åè¨å®</a></li>
</ul>
</li>
<li><a href="#社å
ã¢ãã¦ã³ã¹ã«ã¤ãã¦">社å
ã¢ãã¦ã³ã¹ã«ã¤ãã¦</a></li>
<li><a href="#æ¯ãè¿ã">æ¯ãè¿ã</a></li>
<li><a href="#Acknowledgements">Acknowledgements</a></li>
</ul>
<h3 id="å¤æ´ã®ã¢ããã¼ã·ã§ã³">å¤æ´ã®ã¢ããã¼ã·ã§ã³</h3>
<p><figure class="figure-image figure-image-fotolife" title="å³: ä½æ¥åã«æ®å½±ããçè
ã® Google ã¢ã«ã¦ã³ãã®æ§å"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sora_h/20230628/20230628160106.png" width="404" height="245" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>å³: ä½æ¥åã«æ®å½±ããçè
ã® Google ã¢ã«ã¦ã³ãã®æ§å</figcaption></figure></p>
<p>ããã¯å¤å²ã«ãããããã代表çãªãã®ãç®æ¡æ¸ãã§èª¬æãã¾ã:</p>
<ul>
<li>ã¦ã¼ã¶ã¼ããè¦ã¦ã¡ã¼ã«ã¢ãã¬ã¹ãã¢ã«ã¦ã³ãåã cookpad.com 㨠cookpad.jp ã® 2 種é¡ãæã¤ãã¨ã«ãªãæ··ä¹±ãã
<ul>
<li>対å¤çãªã¡ã¼ã«ã¢ãã¬ã¹ã¯ cookpad.com ã§ããã¹ãã ã cookpad.jp ãå©ç¨ãã¦ãã¾ã</li>
<li>éã« Google ã¢ã«ã¦ã³ã㯠cookpad.jp ãªã®ã« cookpad.com ãå
¥åãã¦ã¨ã©ã¼ã«ãªã£ã¦ãã¾ã</li>
</ul>
</li>
<li>ã¯ãã¯ããã㯠Azure AD ãä½µç¨ãã¦ã㦠<a href="#f-f7cd5bb7" name="fn-f7cd5bb7" title="Intune ã®åå¨ãSAMLæ©è½ã®æè»æ§ãªã©è«¸ã
ã§ä½µç¨ã«ãªã£ã¦ãã¾ãããæ¬ããã¸ã§ã¯ãã§ãã£ã¨ã¢ãã¬ã¹ãçµ±ä¸ããæ··ä¹±ãé²ããããã«ãªã£ãã®ã§ Google Workspace 㯠Azure AD ããã®ãã§ãã¬ã¼ã·ã§ã³ã«åãæ¿ãããâ¦!">*5</a>ããã¡ã㯠cookpad.com ã§ãããããåæ§ã«æ··ä¹±ãã
<ul>
<li>Google ãã°ã¤ã³ãã Azure AD ã® SAML ã«åãæ¿ããå ´å SAML ä¸ã¯ cookpad.jp ãåä¹ãããå¿
è¦ããã (ãããã¯å¾è¿°ã®ããã«é å¼µã£ã¦å¤æ´ä½æ¥ãè¡ã)ã</li>
</ul>
</li>
<li>Slack ãå½å Google ãã°ã¤ã³ã«ãã£ã¦ cookpad.jp ã®å©ç¨ã§ãã£ããã <a href="#f-0a97cccc" name="fn-0a97cccc" title="Slack ãã¾ãã« Enterprise Grid 㧠SAML ã«å¤æ´ã«ãªã£ãæãcookpad.jp ã SAML 㧠Azure AD ã«åä¹ããã¦ããä¾">*6</a>ãSlack Connect ã®æå¾
ã cookpad.com ã®ã¢ãã¬ã¹ã¸éä¿¡ããã㨠Slack ã¢ããªä¸ã§ãããå諾ãããã¨ãã§ããªãããªã©ãªã©å種ã¢ããªã§ã®ä¸é½å</li>
<li>cookpad.com (ã¡ã¼ã«ã¢ãã¬ã¹, Azure AD) 㨠cookpad.jp (Google) ã®æå³ã«éããããããã社å
ã·ã¹ãã åæã®å®è£
ã§èªã¿æ¿ããªã©ã®èæ
®ãå¿
è¦
<ul>
<li>åä½ã®èªèã¨ãã®å®è£
ãã¾ã¡ã¾ã¡ã§ä¸å
·åãâ¦</li>
</ul>
</li>
<li>Google Workspace ã®ãã©ã¤ããªãã¡ã¤ã³ãå¤æ´ã§ããªããªãæ¡ä»¶ãæºããã¦ãã¾ããµã¼ãã¹ãå©ç¨ã§ããªã (Chrome OS ã® MDM ãªã©)</li>
</ul>
<p>ãã ããæ¬ç¨¿ãé常ã«é·ããã¨ããåããããã«ãä¸å¯§ãªèª¿æ»ã¨ä½æ¥ãè¡ããªããã°æ§ã
ãªåé¡ãçºçãã¦ãã¾ããããè
°ãããã¸ãéããã®ã§ãããæ¥å㧠Chrome OS ãä¸é¨ã§å©ç¨ãã¦ãããã¨ãã Chrome OS ã® MDM ãæ¤è¨ããã®ãåæ¤è¨ã®ããã«ã±ã¨ãã¦å¤§ããªè¦å ã§ããã</p>
<h3 id="Google-Workspace-ã«ããããã¡ã¤ã³ã¨ã¯">Google Workspace ã«ããããã¡ã¤ã³ã¨ã¯</h3>
<p>åæã¨ãã¦ãGoogle Workspace ã«ããããã¡ã¤ã³ã«ã¤ãã¦è»½ã解説ãã¾ãã</p>
<p>Google Workspace é
ä¸ã®ã¦ã¼ã¶ã¼ã¯ããã³ãã«ç»é²ããã¦ãããã¡ã¤ã³ãå©ç¨ãã¾ããå Google Workspace ããã³ãã¯è¤æ°ã®ãã¡ã¤ã³ãç»é²ã§ãããããé
ä¸ã®ã¦ã¼ã¶ã¼ã«è¨å®ãã¾ãã</p>
<p>ç»é²ããã¦ãããã¡ã¤ã³ã® 1 ã¤ããã©ã¤ããªãã¡ã¤ã³ã«æå®ããããã©ã«ããã¤ããã³ãã示ããã¡ã¤ã³ã¨ãã¦åæã§å©ç¨ããã¾ãããã®ä¸ã§ããã©ã¤ããªãã¡ã¤ã³ä»¥å¤ã«ã»ã«ã³ããªãã¡ã¤ã³ãè¨å®ãã¦ã¦ã¼ã¶ã¼ãã¨ã«ä½¿ãåãããã¨ãå¯è½ã«ãªã£ã¦ãã¾ãã</p>
<h4 id="ã¨ã¤ãªã¢ã¹">ã¨ã¤ãªã¢ã¹</h4>
<p>Google Workspace ã¦ã¼ã¶ã¼/ã°ã«ã¼ãã«ã¯ã¨ã¤ãªã¢ã¹ãè¨å®ã§ãã¾ããã¨ã¤ãªã¢ã¹ã¨ãã¦(ç°ãªãã¦ã¼ã¶ã¼åã»ãã¡ã¤ã³ãæã¤)å¥ã®ã¡ã¼ã«ã¢ãã¬ã¹ã追å ãããã®ã¡ã¼ã«ã¢ãã¬ã¹ã§ãã¡ã¼ã«ãéåä¿¡ã§ããããã«ãªãã¾ã <a href="#f-2c3d8465" name="fn-2c3d8465" title="人ã®ååã¯å§åããããå¤ãã£ããããã¸ãã¹ãã¼ã ãç°ãªã£ãããè¡çªããããããããã¦ã¼ã¶ã¼åã«å½åè¦åãè¨ããã®ã¯é常ã«ãããããã¾ãããã²ã©ãä¾ã§ã¯ãã¤ã¬ã®ã¥ã©ã¼ãããã®ã«ã¦ã¼ã¶ã¼åã®å½åè¦åãéµåã¿ã«ãã¦ã¡ã¼ã«ã¢ãã¬ã¹ã®æ¨æ¸¬ãè¡ããããªå®è£
ãçºçãã¦ãã¾ãã¾ããå½ç¤¾ã§ã¯è¨å·ã®å¶ç´ã¯ããã¾ãããä»»æã®ã¦ã¼ã¶ã¼åãå
¥ç¤¾æã«ãªã¯ã¨ã¹ããããã¨ãåºæ¥ãããã«ãªã£ã¦ãã¾ã">*7</a>ã</p>
<p>ããã¦ãã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãåä½ã«è¨å®ããã¨ã¤ãªã¢ã¹ã¨ã¯å¥ã«ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ãå©ç¨ã§ãã¾ãããã¡ã¤ã³ã¨ã¤ãªã¢ã¹ã¯ãã®åã®éããã¡ã¤ã³å
¨ä½ã«å¯¾ãã¦ä»ã®ãã¡ã¤ã³ãå¥åã¨ãã¦ç»é²ãããã®ã§ãã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã«ã¨ã¤ãªã¢ã¹ã¨ãã¦ç»é²ããã¨ããã¨ã¤ãªã¢ã¹ãåå¨ãããã¡ã¤ã³ã§ã¢ãã¬ã¹ãåå¨ãã¦ããã°ããã®ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ã§ãã¡ã¼ã«ã®éåä¿¡ãå¯è½ã«ãªãã¾ãã</p>
<p>ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ã¯ãã©ã¤ããªãã¡ã¤ã³ãå«ãããã³ãã®ãã¡ã¤ã³ã«å¯¾ãã¦è¿½å ãããã®ã§ãããã®ãããã¡ã¤ã³ã¨ã¤ãªã¢ã¹ã¯ã¦ã¼ã¶ã¼ã®(ãã©ã¤ããªã®)ã¡ã¼ã«ã¢ãã¬ã¹ã¨ãã¦ç´æ¥å²ãå½ã¦ããã¨ãä¸å¯è½ã«ãªã£ã¦ãã¾ãã</p>
<h4 id="Cookpad-ã«ããã-Google-Workspace-ãã¡ã¤ã³è¨å®">Cookpad ã«ããã Google Workspace ãã¡ã¤ã³è¨å®</h4>
<p>æ¬ããã¸ã§ã¯ã以åã® Google Workspace ã®ãã¡ã¤ã³ã¯ããã£ãã以ä¸ã®ããã«ãªã£ã¦ãã¾ããã</p>
<ul>
<li>cookpad.jp (ãã©ã¤ããªãã¡ã¤ã³)
<ul>
<li>cookpad.com (ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹)</li>
</ul>
</li>
<li>{ãã®ä»åå¥ã®ã¨ã¤ãªã¢ã¹ã§å©ç¨ããåæã®ç´°ã
ã¨ãããã¡ã¤ã³} <a href="#f-3576f14d" name="fn-3576f14d" title="æ㯠hostmaster@ ã§ã¡ã¼ã«ãåã㦠SSL 証ææ¸çºè¡ã®æ¿èªã¨ãããã¦ããã®ã§ããããããã¤ã®ããã«è²ã
追å ããã¦ãã¾ãã">*8</a></li>
</ul>
<p>åé ã§æ¸ããããã«ãcookpad.jp ããã©ã¤ããªãã¡ã¤ã³ã»å
¨ã¦ã¼ã¶ã¼/ã°ã«ã¼ãã®ãã¡ã¤ã³ã«è¨å®ããã¦ãã¾ããcookpad.com ãã¡ã¼ã«ã¢ãã¬ã¹ã¨ãã¦å©ç¨ãããããcookpad.com 㯠cookpad.jp ã«å¯¾ãããã¡ã¤ã³ã¨ã¤ãªã¢ã¹ã«ãªã£ã¦ãã¾ããã</p>
<p>æ¬ããã¸ã§ã¯ãã§ã¯ cookpad.com ããã©ã¤ããªãã¡ã¤ã³ã«å¤æ´ãããã¨ãæçµç®çã§ããå¯ãã®è¯ãæ¹ã¯æ°ä»ãã¨æãã¾ãããå¤æ´å
ã®ãã¡ã¤ã³ããã¡ã¤ã³ã¨ã¤ãªã¢ã¹ã¨ãã¦æ¢ã«è¨å®ããã¦ãããã¨ã§ãã¾ãã¾ãè¦å´ãã¾ãã</p>
<h4 id="ãã©ã¤ããªãã¡ã¤ã³ã¾ã§å¤æ´ãããã©ãã">ãã©ã¤ããªãã¡ã¤ã³ã¾ã§å¤æ´ãããã©ãã</h4>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsupport.google.com%2Fa%2Fanswer%2F7009324%3Fhl%3Den" title="Change your primary domain for Google Workspace - Google Workspace Admin Help" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>ã¦ã¼ã¶ã¼ãå©ç¨ãã = ã¦ã¼ã¶ã¼ã® Google ã¢ã«ã¦ã³ãã¨ãªããã¡ã¤ã³ãå¤æ´ããã ãã§ããã°ãã©ã¤ããªãã¡ã¤ã³ã®å¤æ´ã¯å¿
é ã§ã¯ããã¾ããããã ãGoogle Cloud Platform ã®çµç¹åãªã©åæã§æ··ä¹±ãæ®ãã¦ãã¾ããã¨ãå¤æ´ããªãçç±ããªããã¨ãGoogle ã® OAuth2/OIDC ã® hd ãªãã·ã§ã³ã®æåãã¤ãã¤ãã«ãªã <a href="#f-5ad8fcdd" name="fn-5ad8fcdd" title="å¾è¿°ãã¾ãããGoogle Workspace ã Cloud Identity ã®ãã¡ã¤ã³ãå
ã«ã¢ã«ã¦ã³ãé¸æç»é¢ã®è¡¨ç¤ºããã£ã«ã¿ãããã¹ããããã¾ãæ°è¦ãã°ã¤ã³æã«ãã©ã¼ã ã«ãã¡ã¤ã³ãããããã表示ããæ©è½ãçµç¹ã®ãã©ã¤ããªãã¡ã¤ã³ != ã¦ã¼ã¶ã¼ã®ãã¡ã¤ã³æã®æåãä¸å®">*9</a>ããã¨ããä»åã¯ã¦ã¼ã¶ã¼ã®ãã¡ã¤ã³ã«å ãããã©ã¤ããªãã¡ã¤ã³ã¾ã§å¤æ´ãè¡ãã¾ããã</p>
<p>ãã©ã¤ããªãã¡ã¤ã³ã«ã¤ãã¦ã¯ä¸è¨ãã«ãã«è¨è¼ããã¦ããããã«ãå¤æ´ã§ããªããæ¡ä»¶ãåå¨ãã¾ãããã®ãã¡ã®å¤§ããªç©ã Chrome Enterprise ã Chrome OS 端æ«ãã¨ã³ãã¼ã«ãã¦ããã¨ããç¹ã§ãããã¯ã¢ããã¼ã·ã§ã³ã® 1 ã¤ã¨ãªã£ã¦ãããããå¾æ»ãåºæ¥ãªããªãåã«æ¸ã¾ãããã£ãããè¸ã¿åãã¾ããã</p>
<h3 id="ç®æ¨-å½±é¿ãæå°ã«æãã">ç®æ¨: å½±é¿ãæå°ã«æãã</h3>
<p>ã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã®ãã¡ã¤ã³ãå¤æ´ããã ããªã Google Workspace ã®ããã¥ã¡ã³ãéãã«ä½æ¥ããã ããªã®ã§ç°¡åã§ãããããã ãã§æ¸ãã¯ããããã¾ãããæ¬ããã¸ã§ã¯ãã«ã¯å種ãµã¼ãã¹æãªã©å½±é¿ãæå°éã«æãããã¨ã大ããªç®æ¨ã¨ãã¦ããã¾ãããããã¯ä¸è¨ãå«ã¾ãã¾ã:</p>
<ul>
<li><strong>ã¡ã¼ã«:</strong> åä¿¡ãåºæ¥ãªãæéãçãæãããåºæ¥ãªãå ´åã§ããã®å½±é¿ãæå°ã«ãã</li>
<li><strong>ãã°ã¤ã³é£æºã®ç¶ç¶:</strong> Google ã¢ã«ã¦ã³ãã§ãã°ã¤ã³ãã¦ããå種ãµã¼ãã¹ã¸ã®ã¢ã¯ã»ã¹ãæããªãããã«ããããã°ã¤ã³ãããæ°è¦ã¦ã¼ã¶ã¼ã«ãªã£ã¦ãã¾ããã¨ãã£ããã©ãã«ãåé¿ãã</li>
<li><strong>æ··ä¹±ãé¿ãã:</strong> å種ã¢ãã¦ã³ã¹ããã£ã¡ãè¡ãã¦ã¼ã¶ã¼ã®æ··ä¹±ãé¿ãããç¹ã«å¼·å¶ãã°ã¢ã¦ããçºçããå ´åã«åãã</li>
</ul>
<p>ç¹ã« Google ã® OAuth2/OIDC ãå©ç¨ãã¦ãã°ã¤ã³ãè¡ã£ã¦ããå種 SaaS ã¸ã®å½±é¿ãå¿é
ã§ãããçã£å½ã«å®è£
ãã¦ããã° sub ã¯ã¬ã¼ã ãå©ç¨ãã¦ã¦ã¼ã¶ã¼æ¤ç´¢ãè¡ãå®è£
ã«ãªãã¯ã <a href="#f-0dca8a1f" name="fn-0dca8a1f" title="Google ã sub claim ãå©ç¨ãããemail ã¯é©ããªãã¾ã¨ ããã¥ã¡ã³ãã§æè¨ãã¦ãã¾ããOIDC ä»æ§ä¸ã§ã sub ã¯ã¬ã¼ã 以å¤ã§ã®ç
§å㯠non-conformant ã§ã https://openid.net/specs/openid-connect-core-1_0.html#ClaimStability ã">*10</a> ã§ãã</p>
<p>ãã ãOIDC ã OAuth2 + userinfo API ã§å¾ã email ãå
ã«ã¦ã¼ã¶ã¼ãæ¤ç´¢ãã¦ããä¸å±ããªãµã¼ãã¹ã¯ç¢ºå®ã«åå¨ãã¾ã (ãã¾ãã)ãä¸ãä¸æ¥åã§ãã®ãããªãµã¼ãã¹ãå©ç¨ããã¦ããå ´å (ãã¾ãã)ãæ¥åã«å½±é¿ãçãã¦ãã¾ãããããããã«ã¤ãã¦ã¯äºåã«å¯¾çãè¡ãå¿
è¦ãããã¾ãã</p>
<h3 id="ããã¸ã§ã¯ãã®æµã">ããã¸ã§ã¯ãã®æµã</h3>
<p>æ¬ããã¸ã§ã¯ãã¯ä¸è¨ã®æé ã§é²è¡ãã¾ãããåé ã«è¨è¼ããéã 2022/8 ï½ 2023/2 é ã¾ã§ç·©ããã«æºåã2023/3 ä¸æ¬ã«ä¸æ°ã«çä»ãããããªã¹ã±ã¸ã¥ã¼ã«ã§ãã</p>
<ol>
<li>äºåæºå: å種ãµã¼ãã¹ (SaaS)ã社å
(å
製)ã·ã¹ãã ã¸ã®å½±é¿ã確èªãå¿
è¦ãªä½æ¥ã«ã¤ãã¦ã¢ãã¦ã³ã¹ãæºå</li>
<li>äºåæºå: æ¤è¨¼ç°å¢ (Google Workspace ããã³ããªã©) ãç¨æãã¦å種æåã®ãã§ãã¯ãã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãæ
å ±å¤æ´ã«å©ç¨ããã¹ã¯ãªããã®æºå</li>
<li>åèªä½æ¥: äºåã«ã¢ãã¦ã³ã¹ããå種ãµã¼ãã¹ã§å¿
è¦ãªå¯¾å¿ãåèªã§å®æ½</li>
<li>ç´åä½æ¥: å種ãµã¼ãã¹ã§å¿
è¦ãªå¯¾å¿ãå®æ½</li>
<li>æ¬çªä½æ¥: æ¬çª Google Workspace ããã³ãã®å¤æ´ä½æ¥</li>
<li>äºå¾ä½æ¥: å種ãµã¼ãã¹ã§å¿
è¦ãªå¯¾å¿ãå®æ½</li>
<li>äºåæºå: å種ãµã¼ãã¹ (SaaS) ã®å½±é¿ç¢ºèª</li>
</ol>
<h3 id="äºåæºå-å種ãµã¼ãã¹-SaaS-ã®å½±é¿ç¢ºèª">äºåæºå: å種ãµã¼ãã¹ (SaaS) ã®å½±é¿ç¢ºèª</h3>
<p>ã¨ããããã§ãæ¬ããã¸ã§ã¯ãã§ã¾ãæåã«è¡ã£ãã®ã¯å種ãµã¼ãã¹ã§ã®å½±é¿ã»å¿
è¦ãªã¢ã¯ã·ã§ã³ã確èªãããã¨ã§ããããã¸ã§ã¯ãã®å¤§åã®æéã¯å°éã«é 次ãã®ç¹æ¤ãè¡ã£ã¦ããæ°ããã¾ãã</p>
<p>Google Admin ããå©ç¨å±¥æ´ã®ãã OAuth2 Client ã®ãªã¹ããåå¾ã§ãããããåå¾ããä¸ã§æ¥åã§å©ç¨ãã¦ããã»ããã¦ããã㪠<a href="#f-d41066f8" name="fn-d41066f8" title="ã·ã£ãã¼ITã£ã½ãã¨ãããããã¯ãåä¼ç¤¾ã§å©ç¨ãã¦ãã¦æ¬ä½ã§é¢ç¥ãã¦ããªããµã¼ãã¹çããããã">*11</a> ãµã¼ãã¹ã®ãªã¹ããä½æãã¾ãããããã 30 ãµã¼ãã¹ããªã¹ãã¢ãããããé 次ãµãã¼ãã«èããæ¤è¨¼ç°å¢ã§ã¡ãã£ã¨ã¢ã«ã¦ã³ããªãã¼ã ãã¦ã¿ã¦åä½ã試ãã¦ã¿ãâ¦ã¨ããã®ãå°éã«è¡ãã¾ãã</p>
<p>ããã¨ããã¯ãä¸è¨ã®ããã«ãã¾ãã¾ãªãã¿ã¼ã³ã§å¯¾å¿ãæ±ãããããã¨ãåãã£ã¦ãã¾ããã</p>
<ul>
<li>ä½ã対å¿ããå¿
è¦ã¯ãªã (ãã°ã¤ã³ããªããã°ã¡ã¼ã«ã¢ãã¬ã¹ã®å¤æ´ãèªåã§åæ ããã)</li>
<li>äºåã«åèªã§ã¡ã¼ã«ã¢ãã¬ã¹ãå¤æ´ãã¦ããå¿
è¦ããã</li>
<li>äºåã«ç®¡çè
ãæåã§ã¡ã¼ã«ã¢ãã¬ã¹ã 1 ã¦ã¼ã¶ã¼ãã¤å¤æ´ãã¦åãå¿
è¦ããã</li>
<li>äºåã«ç®¡çè
ãæ°ãããã¡ã¤ã³ã allowlist ã«è¿½å ãããææ権ã®æ¤è¨¼ãæ¸ã¾ãã¦ããå¿
è¦ããã</li>
<li>管çè
ãããã¡ã¤ã³å¤æ´ãä¼ãããã¾ã¨ãã¦ãµãã¼ãã«å¤æ´ãã¦ãããã</li>
<li>管çè
ã§æ°æ§ãåãã csv ããµãã¼ãã«æåºãããå¤æ´ãã¦ãããã</li>
<li>管çè
ã§ã¦ã¼ã¶ã¼ãä¸åº¦åé¤ãã¦åæå¾
ããªããã°ãããªã</li>
<li>ãã°ã¤ã³èªä½ã¯ãªãã¼ã å¾ã§ãç¶ç¶ãã¦è¡ããããã¡ã¼ã«ã¢ãã¬ã¹å¤æ´ã¯æåã§åèªãè¡ãå¿
è¦ããã</li>
</ul>
<p>ã¯ããçé¢ç®ã« OIDC ä»æ§éãã«ãã°ã¤ã³ãå®è£
ãã¦ãªãã¨ããããã¾ãã«ãå¤ããã¦æ¬å½ã«å°ãã¾ããOIDC ãããã¤ãããå¾ãããã¡ã¼ã«ã¢ãã¬ã¹ãä¿¡ç¨ãã¦ããã§ãããããã³ã°ãããªãã®ã¯ãã¾ãã«ãéå®è£
ããã¾ã <a href="#f-5fe8f86d" name="fn-5fe8f86d" title="ããããã°æè¿ https://www.descope.com/blog/post/noauth ã¨ãããã¾ããããGoogle ã®å ´å㯠email_verified claim ãè¦ã¦ããã°ãããã©ãè¦ã¦ããªãã¨ãããå®ã¯ãã£ãããããã ããã?">*12</a> ã</p>
<p>ä»åã®ãã©ã¤ããªãã¡ã¤ã³å¤æ´ã§ã¯æ°è¦ã«ãã¡ã¤ã³ãåå¾ãã¦ãããå¤æ´ããããã§ã¯ãªãã10 å¹´ã»ã©å¹³è¡ãã¦ä¸¡ãã¡ã¤ã³ã¨ãã¡ã¼ã«ã¢ãã¬ã¹ã¨ãã¦ä½¿ã£ã¦ãã <a href="#f-1bd2ca63" name="fn-1bd2ca63" title="å¤æ´å
ã® cookpad.jp ã¯æå³ãã使ããã¦ãã¦ãã¾ã£ããã¨è¨ãã®ãæ£ãããã§ãã">*13</a> é¢ä¿ã§ã人ã«ãã£ã¦ã¯ cookpad.jp â cookpad.com ã«ç®¡çè
ããµãã¼ãã§ãªãã¼ã ãããã¨ãããã³ã³ããªã¯ããã¦ãã¾ã£ã! ã¨ããäºä¾ãããªãçºçããããç¶æ³ã§ã <a href="#f-f9abd0ca" name="fn-f9abd0ca" title="ãã¨ãã° Google ãã°ã¤ã³ãå¼·å¶ã§ããããã¹ã¯ã¼ãã§ãµã¤ã³ã¢ãããã¢ã«ã¦ã³ãæå¾
ãå諾ãã¦ãã¾ãã¨ãããªã£ã¦ãã¾ã">*14</a>ãããã«ã¤ãã¦ãååããè¡ããä¸è¨ã®ãããªãã¿ã¼ã³ãåãã£ã¦ãã¾ããã</p>
<ul>
<li>æ¬äººã«ã©ã¡ããã®ã¢ã«ã¦ã³ããåé¤ãã¦ãããå¿
è¦ããã <a href="#f-2b9c1d76" name="fn-2b9c1d76" title="Google ãã°ã¤ã³ã§ããªãå ´åã§ããã¹ã¯ã¼ããªã»ããã®æé ã§ãªãã¨ããªã£ããããäºãå¤ãã£ã">*15</a></li>
<li>ãµãã¼ããã¦ã¼ã¶ã¼ãã¼ã¸ãè¡ã£ã¦ããã <a href="#f-03a6a6da" name="fn-03a6a6da" title="ãã¡ã¤ã³ææ権ã®æ¤è¨¼ããããµã¼ãã¹ã ã¨ãé¡ãã§ããããä¸åº¦ããã³ãã« .jp, .com 両æ¹ã®ã¦ã¼ã¶ã¼ãå
¥ãã¦ããã ã£ãã">*16</a></li>
</ul>
<p>ããããä¼æ¥ãå©ç¨ãããã¨ãåæã¨ãã¦ãããµã¼ãã¹ã§ããã¡ã¤ã³ã®ãªãã¼ã ãã¦ã¼ã¶ã¼åã®ãªãã¼ã ãããæ¨ããµãã¼ãã«ä¼ãã¦ããªããªãç解ãã¦ããããªããã¨ãå¤ãã¦çµæ§å°ã£ã¦ãã¾ãã¨ããã§ãããã¡ã¤ã³ã ããããªãã¦ã¦ã¼ã¶ã¼åãããã¨å¤ãããã¨ãããã®ã§â¦ãSAML ãªãã¨ããã OAuth2/OIDC ãå©ç¨ãã¦ããªã sub claim ãé©åã«å©ç¨ãã¦ã»ãã (éè¦ãªäºãªã®ã§ä½åº¦ã§ãæ¸ãã¾ã)ã</p>
<p>ã¾ããäºåã«åèªã§ä½æ¥ãå¿
è¦ãªãµã¼ãã¹ã«ã¤ãã¦ã¯äºåã«ãªã¹ãã¢ããã»ä½æ¥å
容ã Wiki ã«è¨è¼ãã¦å
¨ç¤¾ã¢ãã¦ã³ã¹ã§åèªã®ç¹æ¤ãä¾é ¼ãã¾ããåèªã§ä½æ¥ãå¿
è¦ãªããµã¼ãã¹ã§ããå¤æ´ä½æ¥ä¸ã¯æ°è¦ãã°ã¤ã³ãã§ããªãå¯è½æ§ããããããã«ã¤ãã¦ã¯å
¨ã¦æå
ã®ç«¯æ«ã§ãã°ã¤ã³ãã§ãã¦ããã確èªããããã«è¨è¿°ãã¾ããã</p>
<p>å½æ¥ã«è¤éãªå¯¾å¿ãããªããã°ãªããªãã£ãããã¤ãã®ãµã¼ãã¹ã«ã¤ãã¦ã¯ãå¾è¿°ããå¤æ´ä½æ¥ã®ç¯ã§è§£èª¬ãã¾ãã</p>
<p>ãªããGoogle Workspace ã³ã¢ãµã¼ãã¹ä»¥å¤ã® Google ã®ãµã¼ãã¹ã«ã¤ãã¦ã¯ GCP ãå«ãã¦ç¾å¨ã¾ã§å¤§ããªåé¡ã¯å ±åããã¦ãã¾ããããã¡ãã®äºå調æ»ããµãã¼ããéãã¦è¡ãããã£ãã¨ãããã³ã¢ãµã¼ãã¹ã§ãªããã¨ãã Google Workspace ã®ãµãã¼ãããã¯è¿çãè²°ããåãµã¼ãã¹ã®ãµãã¼ãã¸èªå°ãããåãµã¼ãã¹ã®ãµãã¼ããã㯠Google Workspace ã®ãµãã¼ãã¸èªå°ããã¾ããããã®ãããä¸é¨ã®éã³ã¢ãµã¼ãã¹ã§éè¦ãª Google Analytics ã Google Cloud Platform ã«ã¤ãã¦ã¯æ¤è¨¼ç°å¢ã§æ¨©éãªã©ãç¶æããããã¨ã確èªãã¦æ¸ã¾ãã¾ããã</p>
<p>⻠大ããªåé¡ã¯ããã¾ããã§ããããçè
ã®ã¢ã«ã¦ã³ãã§ã¯ Google ã«ã¬ã³ãã¼ãä¸åèªã¿è¾¼ã¾ããªã (Web ã ã¨ç½ç´ã«ãªã) ã¨ããäºè±¡ãçºçãã¾ããããµãã¼ãæ°ã 24 æé㯠<em>propagation</em> ã«ããããããã£ãã®ã§ããã¨ãªããå¾
ã£ããèªç¶ã«è§£æ¶ãã¦ããã¾ããã</p>
<h3 id="äºåæºå-å
製ã·ã¹ãã ã®æºå">äºåæºå: å
製ã·ã¹ãã ã®æºå</h3>
<p>社å
ã§å
製ãã¦ããã·ã¹ãã ã¯ã»ã¨ãã© <a href="#f-a40f5a87" name="fn-a40f5a87" title="æ¥æ¬å´ã®å ´åãUK ãªãã£ã¹ãä¸å¿ã¨ãªã£ã¦ããã°ãã¼ãã«äºæ¥ã¯ ALB + Azure AD OIDC ã ã£ãããã¾ã">*17</a> ã <a href="https://techlife.cookpad.com/entry/2018/04/02/140846">Hako 㧠ECS ã«ãããã¤</a> ããã¦ãã¦ãã¾ãã¦ã¼ã¶ã¼èªè¨¼ã¯ã ããã omniauth-google_oauth2 gem ãå©ç¨ãã¦ãã¾ãããã©ã¤ããªãã¡ã¤ã³å¤æ´æã®è¿½å¾ãã¹ã ã¼ãºã«è¡ãããã大åã®ãã¿ã¼ã³ã§ãã Hako + Rails + omniauth-google_oauth2 ã«ã¤ãã¦å¯¾å¿ãæ¤è¨ãã¾ããã</p>
<p>ã¾ããomniauth-google_oauth2 gem ã§ã¯ hd ãªãã·ã§ã³ã« Google Workspace ã®ãã¡ã¤ã³ãæå®ãããã¨ã§ omniauth strategy ã¬ãã«ã§ãã°ã¤ã³å¯è½ãªãã¡ã¤ã³ãå¶éã§ãã¾ã (hosted domain, hd claim ã®æ¤è¨¼)ãomniauth-google_oauth2 ã® hd ãªãã·ã§ã³èªä½ã¯è¤æ°ã®ãã¡ã¤ã³ãæå®å¯è½ã§ãããhd ãªãã·ã§ã³ã¯ Google ã® OAuth2 èªå¯ã¨ã³ããã¤ã³ãã¸ãå¼ãç¶ããã¦ãã¦ããã¡ãã¯è¤æ°ã®æå®ãåãä»ãã¾ãããhd ãªãã·ã§ã³ãå©ç¨ããã¨ã¢ã«ã¦ã³ãé¸æç»é¢ã§è¡¨ç¤ºããã¢ã«ã¦ã³ããçµã£ããã1ã¤ã«çµãããçç¥ããããã¾ãæ°è¦ãã°ã¤ã³ã®éããã¡ã¤ã³åãè£å®ãããããã¦ã¼ã¶ã¼ä½é¨ä¸æçã§ãããã®å©ç¹ã失ãããã¯ããã¾ããã§ãã <a href="#f-9ce5d767" name="fn-9ce5d767" title="ãã¡ãããä¸æçã« hd ã«ä¸¡æ¹æå®ãã¦ããã£ã¦å¾ã§æ»ãã¨ããã®ãã¢ãªã§ãããå¾ã§æ»ãã¦ããããã¨ã¯å¿
é ã§ã¯ãªããããæ»ãã¦ããããªãã£ãã¨ããã ãä¸ä¾¿ãªã¾ã¾ã¨ããç¶æ³ãäºæ³ã§ãã¾ã">*18</a>ãã¾ããhd ãªãã·ã§ã³ä»¥å¤ã§ããã¡ã¤ã³ãæ¤è¨¼ãã¦ããç®æãã¡ãã»ã確èªã§ãã¾ããã</p>
<p>ãã©ã¤ããªãã¡ã¤ã³ã®å¤æ´ä½æ¥ã¯é±æ«ãæ¥æ¬æéã®åææã«è¡ãã¾ãããä½ãæãæããªãã®ã§ããã°åèªã§é±æãã« cookpad.jp ã cookpad.com ã«æ¸ãæãã¦ãããã¤ããããDB ä¸ã®ã¬ã³ã¼ããæ¸ãæãã¦ãããã¨ããã¨ããã§ããã社å
ã·ã¹ãã ã«é±æãã«åèªã§å¤æ´ããããã¤ããã¾ã§ãæ°è¦ãã°ã¤ã³ã§ããã«é±æ«ä¸ã®æ¥åãåæ¢ããäºæ
ã¯é¿ããããã®ã§ãã</p>
<p>æ¤è¨ããçµæãAWS Systems Manager Parameter Store ã«ç¾å¨ã® Google Workspace ãã©ã¤ããªãã¡ã¤ã³ã示ãå¤ãä¿åãã¾ãã (ä½æ¥å㯠cookpad.jp)ããããç°å¢å¤æ°ã¨ã㦠ECS ã¿ã¹ã¯ã«äºåã«å
¥ãã¦ãããã¤ããã®ç°å¢å¤æ°ãåå¨ããã° hd ãªãã·ã§ã³ãå«ããã©ã¤ããªãã¡ã¤ã³ã示ãå¤ã¨ãã¦å©ç¨ããããã«ã¨ã³ã¸ãã¢å
¨å¡ã«ä¾é ¼ãããã¾ããããã©ã¤ããªãã¡ã¤ã³å¤æ´å¾ãParameter Store ä¸ã®æ´æ°ãã¦å ECS ãµã¼ãã¹ã®ã¿ã¹ã¯ãåèµ·åããã°ãã©ã¤ããªãã¡ã¤ã³å¤æ´ã®åæ ãå®äºããã¨ããä»æãã§ãã</p>
<p>ããã«å ãã¦ãDB ãªã©ã«ä¿åããã¦ãã Google ã¢ã«ã¦ã³ãåãããã° cookpad.jp â cookpad.com ã«ãã¦ãããå¿
è¦ãããã¾ã <a href="#f-99f459b1" name="fn-99f459b1" title="ãã¡ããèªè¨¼ã¯ sub claim ãå©ç¨ãã¦èªè¨¼ãè¡ãããã®ãæã¾ããã§ãããå種ãã©ã¼ã ãªã©ã¡ã¼ã«ã¢ãã¬ã¹ããå¼ããã¨ãå½ç¶ãããã">*19</a>ãã¢ãã¦ã³ã¹ããä½æ¥ç´å¾ã®ç§»è¡æéä¸ã¯ã©ã¡ããæ¥ã¦ãããããã« cookpad.jp ã .com ã«èªã¿æ¿ãã¦ãããã¨ãã£ãåºæ¬çãªäºãå«ãã¦ç¢ºèªããé¡ããã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="ã¨ã³ã¸ãã¢çµç¹å
¨ä½ã¸ã®ã¢ãã¦ã³ã¹æé¢ (GitHub Issue)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sora_h/20230628/20230628160028.png" width="1103" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ã¨ã³ã¸ãã¢çµç¹å
¨ä½ã¸ã®ã¢ãã¦ã³ã¹æé¢ (GitHub Issue)</figcaption></figure></p>
<p>ãã©ã¤ããªãã¡ã¤ã³å¤æ´ä½æ¥å¾ã¯ç¨æãã Parameter Store ã®å¤ãæ´æ°ããECS ã¿ã¹ã¯ãåèµ·åãããã¨ã§åæ ããã¾ããå½ç¤¾ã¯ã»ã¨ãã© Fargate ã®å©ç¨ããªã EC2 ãã³ã³ããã¤ã³ã¹ã¿ã³ã¹ã¨ãã¦å©ç¨ãã¦ããã®ã§ã社å
åããµã¼ãã¹ç¨ã® ECS ã¯ã©ã¹ã¿ã«å¯¾å¿ãã Auto scaling group ã® instance refresh ãå®è¡ãã¾ããã</p>
<p>å¤æ°ã® ECS ãµã¼ãã¹ã« force new update ãè¡ãã¨ã¯ã©ã¹ã¿å
¨ä½ãä¹±ãã¦å¿
è¦ä»¥ä¸ã®ã³ã³ããã¤ã³ã¹ã¿ã³ã¹æ°ã«ã«ãªããéç©çãä¸æçã«ä¸ãããã³ã³ããã¤ã³ã¹ã¿ã³ã¹ã®èµ·åã®å¾
ã¡ãé次çºçãã¦æéãããããã¨ãã£ããã¡ãªããããããããinstance refresh ã§ã³ã³ããã¤ã³ã¹ã¿ã³ã¹ãå
¨ã¦å
¥ãæ¿ãã¦ã¿ã¹ã¯ã®åèµ·åã«æ¿ããã®ãããããã§ãã</p>
<h3 id="äºåæºå-ã¡ã¼ã«åä¿¡ãã¦ã³ã¿ã¤ã ã®å½±é¿ãæå°åãã">äºåæºå: ã¡ã¼ã«åä¿¡ãã¦ã³ã¿ã¤ã ã®å½±é¿ãæå°åãã</h3>
<p>次ã¯ã¡ã¼ã«åä¿¡ã«ã¤ãã¦ã§ããåè¿°ããããã«ãå¤æ´åã® Google Workspace ããã³ãã¯ä¸è¨ã®ãããªãã¡ã¤ã³æ§æã«ãªã£ã¦ãã¾ãã:</p>
<ul>
<li>cookpad.jp (ãã©ã¤ããªãã¡ã¤ã³)
<ul>
<li>cookpad.com (ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹)</li>
</ul>
</li>
</ul>
<p>ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ãå©ç¨ãã¦ããã®ã§ãã¦ã¼ã¶ã¼ã cookpad.jp ãã¡ã¤ã³ã§ä½æããã¨ãããªã cookpad.com ã®ã¡ã¼ã«ã¢ãã¬ã¹ãã¨ã¤ãªã¢ã¹ã¨ãã¦ä»ãã¦ããç¶æ
ã§ãã</p>
<p>ããã¦å¤æ´ä½æ¥å¾ã¯ãä¸è¨ãæºããã¦ããå¿
è¦ãããã¾ãã</p>
<ul>
<li>æ¢åã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã®ãã¡ã¤ã³ã cookpad.com ã«åãæ¿ãã£ã¦ãã</li>
<li>æ¢åã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã¯å¼ãç¶ã cookpad.jp ãã¡ã¤ã³ã§ãã¡ã¼ã«ãåãåããã¨ãã§ãã</li>
</ul>
<p>ãããéæããããã«ã¯ã2ãã¿ã¼ã³ã®æ¡ãããã¾ãã:</p>
<ul>
<li>Aæ¡: ä¸è¨æ§æã«å¤æ´ãã
<ul>
<li>cookpad.com (ãã©ã¤ããªãã¡ã¤ã³)
<ul>
<li>cookpad.jp (ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹)</li>
</ul>
</li>
</ul>
</li>
<li>Bæ¡: ä¸è¨æ§æã«ããä¸ã§ãå
¨ã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã«ã¨ã¤ãªã¢ã¹ãåå¥ã«è¿½å ãã
<ul>
<li>cookpad.com (ãã©ã¤ããªãã¡ã¤ã³)</li>
<li>cookpad.jp (ã»ã«ã³ããªãã¡ã¤ã³)</li>
</ul>
</li>
</ul>
<p>é·æçã«ã¯ãæ°è¦ã¦ã¼ã¶ã¼ã¯ cookpad.jp ã®ãã¡ã¤ã³ã§ã¡ã¼ã«ãåä¿¡ããå¿
è¦ããªããããå¨è¾ºãµã¼ãã¹å«ãã¦å¤æ´ãå®äºããã¿ã¤ãã³ã°ã§ cookpad.jp ãã¡ã¤ã³ã§ã®ã¡ã¼ã«ã¢ãã¬ã¹ä»ä¸ãåæ¢ãããã¨èãã¦ãã¾ããããã®å ´å A æ¡ã ã¨ãã¤ã¾ã§ãå¼ããããæ°è¦å©ç¨ãå®å
¨ã«æ¢ãããã¨ãã§ãã¾ããããããã£ã¦ä»å㯠B æ¡ãæ¡ç¨ãã¾ããã</p>
<p>ãã ããA æ¡ B æ¡ã©ã¡ãã«ãåé¡ããããGoogle Workspace ã¯ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ãã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã«ç´æ¥å²ãå½ã¦å¯è½ãªããã¡ã¤ã³ãã«å¤æ´ããæä½ãåå¨ãã¾ãããã©ã¡ãã®å ´åããä¸åº¦ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ãåé¤ããã¡ã¤ã³ã¨ãã¦å追å ãã¦ææ権ã®æ¤è¨¼ãããç´ããªããã°ããã¾ãããã¾ããåé¤â追å ã¯å³åº§ã«è¡ãããåé¤å®äºããã°ããå¾
ã¤å¿
è¦ãããã¾ãã</p>
<p>ã¾ããA æ¡ã 㨠cookpad.com ã¸å¤æ´ãçµããå¾ã« cookpad.jp ãåé¤ããã®ä¸ã§ã¨ã¤ãªã¢ã¹ã¨ãã¦å追å ããæé ãå¿
è¦ãªããä½æ¥æéã伸ã³ã¦ãã¾ãã®ãåé¡ã¨èãã¾ããã</p>
<p>ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ãåé¤ããã¨å½ç¶ãªããã¨ã¤ãªã¢ã¹ã«ãã£ã¦çºçãã¦ããã¡ã¼ã«ã¢ãã¬ã¹ã¯åå¨ããªããªãã¾ãããããã£ã¦ãå¤æ´å
ãã¤ã¡ã¼ã«ã¢ãã¬ã¹ã¨ãã¦ã¯ãã©ã¤ããªã§ãã cookpad.com ãã¡ã¤ã³ã§ã¡ã¼ã«ãä¸å®æéåä¿¡ã§ããªããã¦ã³ã¿ã¤ã ãçºçãã¾ããåé¤å®äºï½ãã¡ã¤ã³å追å ï½ã¨ã¤ãªã¢ã¹å追å ãã©ããããã®æéã«ãªããã¯äºæ¸¬ãã§ãã¾ãã <a href="#f-ec846153" name="fn-ec846153" title="äºåã«æ¤è¨¼ç¨ã® Google Workspace ä¸ã§åãæ°ã®ã¦ã¼ã¶ã¼ãä½æãã¦ã¿ãã°åããããããã¾ããããã¢ãã«ãªããªãä¸ã«è²»ç¨ãç¡é§ã«ããã£ã¦ãã¾ãâ¦">*20</a>ããã®ãã¦ã³ã¿ã¤ã ã«ã¤ãã¦ã©ãããããèãã¾ããã</p>
<p>人éå士ã®ã¡ã¼ã«éåä¿¡ã§ããã°ãã¾ãåé¡ã«ã¯ãªãã¾ãããããã®ãã¦ã³ã¿ã¤ã ä¸ã¯ Google ã® MTA ãã¡ã¼ã«ãåä¿¡ããéãéä¿¡å
ã®ãµã¼ãã«ã¡ã¼ã«ããã¯ã¹ãåå¨ããªã hard bounce (5.2.1 NoSuchUser) ãè¿ããã¨ã«ãªãã¾ããéä¿¡å
ã supression list ãæã¤ãããªã·ã¹ãã ããã®ã¡ã¼ã«ããã¦ã³ã¿ã¤ã ä¸ã«åä¿¡ã㦠bounce ããéããã®å¯¾å¿ã¨ãã¦èªå㧠supression list ã«å
¥ã£ã¦ä»¥å¾ã®ã¡ã¼ã«ãéä¿¡ãããªããªãå¯è½æ§ãããã¾ãã</p>
<p>Supression list ã«å
¥ã£ã¦ãããã¨ãèªåã§ç¢ºèªã§ããªããµã¼ãã¹ã¯å¤ãããµãã¼ãã«ç¢ºèªããªããã°ãããªããµã¼ãã¹ãå¤æ°åå¨ãã¾ãããããåä½ã§æãåºãã¦ããã£ã¦ãµãã¼ãã«åãåãããªããã°ãªããªãâ¦ã¨ããç¶æ³ã«ããã®ã¯é¿ããããã®ã§ããåºæ¥ããã¨ãªã soft bounce ã«ãã¦ãªãã©ã¤ãä¿ãããã¨èãã¾ããã</p>
<p>æ¤è¨ããçµæãä½æ¥æ¥åã«ãã¦ã³ã¿ã¤ã ãçºçãã cookpad.com ã® MX ã¬ã³ã¼ãã® TTL ãçããã¦ããã¦ã³ã¿ã¤ã ãçºçããåã«ä¸æçã«ãããã¤ãã Postfix ãµã¼ãã¼ã¸åãããã¨ã«ãã¾ããã以ä¸ã®ãããªè¨å®ã§ cookpad.com, cookpad.jp å®ã®ã¡ã¼ã«ã«ã¤ãã¦ã¯ 4.3.2 ã® soft bounce ã«ãªããéä¿¡è
ã«ãªãã©ã¤ãä¿ãã¾ãã</p>
<pre class="code" data-lang="" data-unlink># main.cf
maillog_file = /dev/stdout
debug_peer_level = 1
compatibility_level = 2
myhostname = inboundmx.googrename.cookpad.com
mydomain = cookpad.com
myorigin = googrename.cookpad.com
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain, cookpad.jp
local_recipient_maps =
# 4xx
unknown_local_recipient_reject_code = 450
smtpd_recipient_restrictions = reject_unauth_destination, defer
inet_interfaces = all
smtp_tls_security_level = may
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_key_file = /etc/postfix/cert/key.pem
smtpd_tls_cert_file = /etc/postfix/cert/cert.pem
smtpd_tls_CAfile = /etc/postfix/cert/chain.pem
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
# tls cipher list ã mandatory protocols ã¯çç¥; Mozilla SSL Configuration Generator ãªã©ãå©ç¨ããã®ããªã¹ã¹ã¡ https://ssl-config.mozilla.org/#server=postfix&version=3.4.8&config=intermediate&openssl=1.1.1k&guideline=5.7</pre>
<p>ãããå©ç¨ããã°ã¡ã¼ã«ãåä¿¡ãããã¨ã«ãããã¼ã¿ãä¿åãããã¨ããªãã®ã§ãå®å
¨ã¹ãã¼ãã¬ã¹ã§éç¨å¯è½ã§ããæ¤è¨¼ç°å¢ã§åä½ç¢ºèªãããã¨ãããåé¡ãªãã¡ã¼ã«åä¿¡ããªãã©ã¤ããã¦é
延ãã¦åä¿¡ã§ãã¾ããã</p>
<p>ããã¯å½ç¤¾ã®æ¨æºçãªãã©ãããã©ã¼ã ã§ãã <a href="https://techlife.cookpad.com/entry/2018/04/02/140846">Amazon ECS 㸠Hako ãå©ç¨</a> ãã¦ä½æ¥æ°æ¥åã«ãããã¤ãã¾ãããStartTLS ãããé½åãNLB ã® TLS çµç«¯æ©è½ã¯å©ç¨ã§ããªããã ECS ã¿ã¹ã¯ã« TLS 証ææ¸ã渡ãå¿
è¦ãããã¾ããTLS 証ææ¸ã¯å½ç¤¾ã®å ´å ECS ä¸ã®ã·ã¹ãã ã§ã¯ <a href="https://github.com/sorah/acmesmith">sorah/acmesmith</a> ãå©ç¨ãã¦çºè¡ã»ã³ã³ããèµ·åæã« S3 ããåå¾ããã®ãä¸è¬çãªããããã®ä»çµã¿ãå©ç¨ãã¾ãããã¾ãããã®è¨è¨ã§åä¿¡ããã ãã§ããã°å¤é¨ã«ã¡ã¼ã«ãéä¿¡ãããã¨ã¯ãªããããAWS ã® OP25B 解é¤ãªã©ãè¡ã£ã¦ãã¾ããã</p>
<p>ãªãã代æ¡ã¨ãã¦ã¯ MX ã¬ã³ã¼ãã®åé¤ã MX ã¬ã³ã¼ãä¸ã§å·¥å¤«ãããã¨ã§ãªãã©ã¤ãããããªããæ¤è¨ãã¾ãããã <a href="https://datatracker.ietf.org/doc/html/rfc5321#section-5.1">RFC 5321 § 5.1. </a> ãå®éã®æåãããã¤ã確èªããéãã§ã確å®ã«ãªãã©ã¤ããããããã®ã§ã¯ãªãã¨å¤æããPostfix ã®è¨å®ã«è³ã£ã¦ãã¾ãã</p>
<p>ããã§ã¡ã¼ã«ã«é¢ããæºåãçµããã¾ããã</p>
<h3 id="äºåæºå-ä½æ¥ç¨ãã¼ã¿ã®æºå">äºåæºå: ä½æ¥ç¨ãã¼ã¿ã®æºå</h3>
<p>ä½æ¥æ°æ¥åã«ãããã³ãå
ã®ã¦ã¼ã¶ã¼ãªã¹ããå
ã«å®ä½æ¥ã§å©ç¨ããå種ãã¼ã¿ãä½æãã¾ããããã®ãããå®ä½æ¥ä¸é±éåã« IT ãã«ããã¹ã¯ã HR ãã¼ã ã¨é£æºã®å
ãããã³ãå
ã®æ°è¦ã¦ã¼ã¶ã¼ä½æãä¸æçã«ä¿çã»æ°è¦ä½æãæ¢ãã¦ãããããã«ãé¡ããã¦ãã¾ãã</p>
<p>ä½æ¥ã§å©ç¨ãããã¼ã¿ã¯ä¸é¨ SaaS ã®ãµãã¼ãã¸æåºãããªãã¼ã 対å¿è¡¨ãã¾ã Google Admin ã¸ã¤ã³ãã¼ããã CSV ãªã©ãããã¾ãããdry-run ãå
¼ãããã¾ã terraform plan file ã®ããã«åæ å
容ãæããã«ãããããå¯è½ã§ããã°äºåã«ãã¼ã¿ãèªåä½æã§ããããã«ãã¦ç¢ºèªããªããä½æ¥ããæå³ã§è¡ã£ã¦ãã¾ãã</p>
<h3 id="ä½æ¥å½æ¥-ãã©ã¤ããªãã¡ã¤ã³å¤æ´ä½æ¥">ä½æ¥å½æ¥: ãã©ã¤ããªãã¡ã¤ã³å¤æ´ä½æ¥</h3>
<p>æ¥æ¬æéã®åææããä½æ¥ãéå§ããã£ããä¸è¨ã®æé ã§é²è¡ãã¾ããã以ä¸ã«è§£èª¬ãã¾ãã</p>
<ol>
<li>ç´åä½æ¥: ä¸é¨ç¤¾å
ãµã¼ãã¹ãã¡ã³ãã«å
¥ããããMXã¬ã³ã¼ããåè¿°ã® Postfix ã«å
¥ãæ¿ãã</li>
<li>ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ cookpad.com ã®åé¤ (ããã§ã¡ã¼ã«åä¿¡ã®ãã¦ã³ã¿ã¤ã ãå§ã¾ã)</li>
<li>cookpad.com ããã¡ã¤ã³ã¨ãã¦å追å </li>
<li>å
¨ã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã®ãã¡ã¤ã³ã cookpad.com ã«å¤æ´</li>
<li>å
¨ã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã®ã¨ã¤ãªã¢ã¹ã« cookpad.jp ã追å </li>
<li>ã¡ã¼ã«åä¿¡ãå¯è½ã§ãããã¨ã確èªã㦠MX ã¬ã³ã¼ãã®å¾©æ§</li>
<li>社å
ãµã¼ãã¹åãã® Parameter Store ä¸ã®å¤ãå¤æ´ãã¦ä¸æåæ </li>
<li>ãã©ã¤ããªãã¡ã¤ã³ã cookpad.com ã¸å¤æ´</li>
<li>å種 SaaS ã¸å¤æ´ãåæ </li>
</ol>
<p>ãããã®æé ã¯äºåã«ä½æ¥æé æ¸ãç¨æãã¦ãã°ãè¨å
¥ããªããé²è¡ãã¾ãããã¾ããä½æ¥éå§åã« Google ã®ã¦ã¼ã¶ã¼ãªã¹ããªã©ãä¸éããã¦ã³ãã¼ããã¦ããã¯ã¢ãããã¦ããã¾ãã</p>
<h4 id="ç´åä½æ¥-MXã¬ã³ã¼ãã®åãæ¿ã">ç´åä½æ¥: MXã¬ã³ã¼ãã®åãæ¿ã</h4>
<p>ã¾ãã¯ã¡ã¼ã«åä¿¡ã®ãã¦ã³ã¿ã¤ã ã«åã㦠MX ã¬ã³ã¼ããåè¿°ã® Postfix ã¸å¤æ´ãã¾ããä½æ¥é±ã®æ©ããã¡ã«äºã TTL ãç縮ãã¦ããè¿
éãªåæ ãçãã¾ãããããã£ããåé¡ãªãåãæããè¡ããã¾ããã</p>
<p>SMTP ã®å種確èªã§ã¯ swaks ã便å©ã§ããå©ç¨ãã¦ãã¾ããäºåã« OP25B ã解é¤ããã IP ã¢ãã¬ã¹ãæã¤ãã·ã³ããµã¼ããç¨æãã¦å¯¾å¿ãã SPF ã¬ã³ã¼ããä½æãã¦ãããswaks ã§ã¡ã¼ã«ãéä¿¡ãã¦åä½ãã§ãã¯ãé²ãã¾ããã</p>
<pre class="code" data-lang="" data-unlink>swaks --from 'sorah@[REDACTED]' --to '[email protected]' --tls-optional-strict --tls-verify</pre>
<h4 id="ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ã¨ãã¦ã®-cookpadcom-ãåé¤">ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ã¨ãã¦ã® cookpad.com ãåé¤</h4>
<p><figure class="figure-image figure-image-fotolife" title="Google Workspace Admin ã®ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹åé¤ç¢ºèªç»é¢"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sora_h/20230628/20230628160004.png" width="1015" height="614" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure></p>
<p>次㯠cookpad.com ã Google Workspace ã®ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ããåé¤ãã¾ããæåã«æ¸ããããã«åé¤ããªããã°é常ã®ãã¡ã¤ã³ã¨ãã¦å追å ãã§ããªãããè¡ãã¾ãããããããã¡ã¼ã«éåä¿¡ã®ãã¦ã³ã¿ã¤ã ãçºçãã¾ãã</p>
<p>ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹åé¤å®è¡å¾ã¯å³åº§ã«æ¶ããããã§ã¯ãªããã°ããæéããããã¾ãã(è¨æ¶ãæ£ãããã°) ãã¡ã¤ã³ä¸è¦§ããã¯å³åº§ã«æ¶å¤±ãã¾ãããã¦ã¼ã¶ã¼è©³ç´°ãã¼ã¸ã確èªããã¨ã¦ã¼ã¶ã¼ããé 次ã¨ã¤ãªã¢ã¹ãåé¤ããã¦ããæ§åã確èªã§ãã¾ãã</p>
<p>ãã¡ãã swaks ã§ç¶æ³ã確èªã§ãã¾ããMX ã¬ã³ã¼ãã«æå®ããã SMTP ãµã¼ãã¸ã¡ã¼ã«ãç´éããã¨ãNoSuchUser ã¨ã©ã¼ãè¿ã£ã¦ãããã¨ã確èªãã¾ããã</p>
<pre class="code" data-lang="" data-unlink>swaks --from 'sorah@[REDACTED]' --to '[email protected]' --server aspmx.l.google.com:25 --tls-optional-strict --tls-verify</pre>
<h4 id="ã»ã«ã³ããªãã¡ã¤ã³ã¨ãã¦-cookpadcom-ãå追å ">ã»ã«ã³ããªãã¡ã¤ã³ã¨ã㦠cookpad.com ãå追å </h4>
<p>å½ç¤¾ã®ç°å¢ã§ã¯ããã 1 æéãããã¦ãã¡ã¤ã³ã¨ã¤ãªã¢ã¹ã®åé¤ãå®å
¨ã«å®äºãã¾ãããè¾æ¸é ã§ã¨ã¤ãªã¢ã¹ãã¦ã¼ã¶ã¼ããåé¤ããã¦ãã£ã¦ããããã ã£ãã®ã§ãè¾æ¸é ã§æå¾ã®ã¦ã¼ã¶ã¼ãããæ¶ãããã¨ã確èªãã¦ããå追å ãå®è¡ãã¾ãã</p>
<p>ãã¡ã¤ã³ãä¸åº¦åé¤ããã¦ããé½åããã¡ã¤ã³ææ権ã®å確èªãå¿
è¦ã§ãããã¡ã㯠DNS ã¬ã³ã¼ããç¨ãã¦éããã«å®äºããã¾ããã</p>
<h4 id="å
¨ã¦ã¼ã¶ã¼ã°ã«ã¼ãã®ãã¡ã¤ã³ã-cookpadcom-ã«å¤æ´">å
¨ã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã®ãã¡ã¤ã³ã cookpad.com ã«å¤æ´</h4>
<p>ãã¡ã¤ã³ãããã³ãã«å追å ããã ãã§ã¯ãã¨ã¤ãªã¢ã¹ã§ã¯ãªãããåæã§å©ç¨ããå§ãããã¨ã¯ããã¾ãããä»åã®ç®çã¯ãã©ã¤ããªãã¡ã¤ã³ã«å ãå
¨ã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã cookpad.com ã主ããã¢ãã¬ã¹ã¨ãã¦å©ç¨ããç¶æ³ãä½ããã¨ãªã®ã§ã次ã¯ãã®å¤æ´ä½æ¥ãè¡ãã¾ãã</p>
<p>ä»åã®ä½æ¥ã§ã¯ãã¦ã¼ã¶ã¼ã¯ Google Admin ã§ã¨ãã¹ãã¼ããã CSV ã® New Primary Email å±æ§ãåã㦠CSV ã¤ã³ãã¼ããããã¨ã§è¡ãã¾ãããæ§ Primary Email ã¯ã¨ã¤ãªã¢ã¹ã¨ãã¦èªåã§æ®ãããããã®å¯¾å¿ã§åé¡ãªãå¤æ´ãè¡ããã¨ãã§ãã¾ãã <strong>ãã ãæ¢åã®ã¨ã¤ãªã¢ã¹ãé¤ããããå®éã«ã¯å
¨ã¦ API ã§å®æ½ããã®ãç¡é£ã§ãã</strong> ãã®ç¹ã«ã¤ãã¦ã¯å¾è¿°ãã¾ãã</p>
<p>ã°ã«ã¼ã㯠Google Admin API ãå©ç¨ãã¦ä¸æ¬æ´æ°ãè¡ãã¾ããã</p>
<p>ä½æ¥ãã¼ã ã®ã¢ã«ã¦ã³ãã«ã¤ãã¦ã¯å¾åãã«ãã¦æåã§å®è¡ãã¾ããããªãã¼ã ã«ãã£ã¦éå»ã«ã¯åãã°ã¤ã³ãå¿
è¦ã«ãªã£ããããè¨æ¶ãããã¾ããã2023/3 ã«ç¢ºèªããéãã§ã¯ Android ã iOS <a href="#f-f20f6dab" name="fn-f20f6dab" title="ç´æ£ã¡ã¼ã«ã»ã«ã¬ã³ãã¼ã§å©ç¨ãã iOS èªä½ã«ç»é²ããã¦ããã¢ã«ã¦ã³ãæ
å ±ãGoogle 製ã¢ããªã§ã¯ä¸è¦ã ã£ã">*21</a> 以å¤ã§åãã°ã¤ã³ãæ±ãããã¾ããã§ãããå¼·å¶ãã°ã¢ã¦ãã«ãã£ã¦å
¨ã¦ã®ç«¯æ«ã§ãã°ã¤ã³ä¸å¯è½ã«ãªããªã© (人ã¯ãã¹ã¯ã¼ããå¿ããã®ã§) 大ããªãã©ãã«ã¯åé¿ã§ãããããããã¯å¬ããæåã§ããã</p>
<h4 id="MXã¬ã³ã¼ãã®å¾©æ§">MXã¬ã³ã¼ãã®å¾©æ§</h4>
<p>cookpad.com ãã¡ã¤ã³ã§å度ã¡ã¼ã«ãåä¿¡ã§ããããã«ãªã£ããã¨ã確èªãã¦ãMXã¬ã³ã¼ããå
ã«æ»ãã¾ããä¸æ¸¬ã®äºæ
ã«åãããã®ã¿ã¤ãã³ã°ã§ã¯ TTL ã¯çãã¾ã¾ç¶æãã¦ããã¾ãã</p>
<p>ã¡ã¼ã«ã®åä¿¡ã¯ã¬ã³ã¼ããå¤æ´ããã¨ããã¡ã¤ã³ã¨ã¤ãªã¢ã¹åé¤æã®å®è¡ä¾ã¨åæ§ã« swaks ã§ç¢ºèªå¯è½ã§ãã</p>
<h4 id="ãã©ã¤ããªãã¡ã¤ã³ã®å¤æ´">ãã©ã¤ããªãã¡ã¤ã³ã®å¤æ´</h4>
<p>ããã¾ã§åé¡ãªãæ¥ããå¾ã¯ããã³ãã®ãã©ã¤ããªãã¡ã¤ã³ãå¤æ´ããã ãã§ãã</p>
<p>ãã ãããã©ã¤ããªãã¡ã¤ã³å¤æ´ä¸å¯ã®æ¡ä»¶ã«å¼ã£æããããã«å¤æ´ãããã¨ã¯ã§ãã¾ããã§ããã <a href="https://support.google.com/a/answer/7009324?hl=en">https://support.google.com/a/answer/7009324?hl=en</a> ã«è¨è¼ããã¦ããããã«æ§ã
ãªæ¡ä»¶ãããã®ã§ãããå¤æ´æã®ã¨ã©ã¼ã§ã¯ä½ããã¡ãã¯æãã¦ãããã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="ãã©ã¤ããªãã¡ã¤ã³å¤æ´æã®ã¨ã©ã¼ç»é¢"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sora_h/20230628/20230628160011.png" width="1040" height="695" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ãã©ã¤ããªãã¡ã¤ã³å¤æ´æã®ã¨ã©ã¼ç»é¢</figcaption></figure></p>
<p>ãã©ã¤ããªãã¡ã¤ã³å¤æ´ãè¡ãããè¡ããªããã¯ã¦ã¼ã¶ã¼ã«(ã»ã¼)å½±é¿ãã話ã§ã¯ãªããããã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã® primary email address ã®ãã¡ã¤ã³ãå¤æ´ããã¦ããã°åé¡ã¯ããã¾ããããã å
è¿°ã®éãå®éã¯ãããããã¨ããã¨ããã§å¯¾å¿ãæ¤è¨ãã¾ããã</p>
<p>äºå®ããã¦ããä½æ¥æéä¸ã®åæ ã¯è«¦ãã¾ãããã幸ãã«ãã¦ãã£ãããµãã¼ãã«åãåãããã¨ããè¿
éã«åçãã¦ãããã¾ãã <a href="#f-5d6bda02" name="fn-5d6bda02" title="ãã£ãããµãã¼ãã¯ãã°ãæ®ããªãä¸ãã®å ´ã§è§£æ±ºã¾ã§è³ããªãã¤ã¡ã¼ã¸ãå¼·ãã£ãã®ã§ãããGoogle Workspace ã®è±èªãµãã¼ãã¯å²ã¨ãã®æå¾
ã¯ä¸åã£ãæ°ããã¾ã">*22</a>ãäºåã«ç®è¦ãã¦åé¡ãªãã¨æã£ã¦ãã Chrome Enterprise ã®è©¦ç¨ã©ã¤ã»ã³ã¹ <a href="#f-1ea41bf8" name="fn-1ea41bf8" title="ãªã»ã©ã¼ãããã©ã¤ã¢ã«ç®çã§æ¸¡ããããã®ã¨è¨æ¶ãã¦ãã¾ããããã詳細ãä¸æâ¦">*23</a> ãå®ã¯ã¢ã¯ãã£ãã§å¼ã£æãã£ã¦ãã¾ããããã©ã¤ããªãã¡ã¤ã³å¤æ´ã®ããã« hold ãä¾é ¼ãã¦ããã®æ¥ã®æ·±å¤ã«æ¹ãã¦å¤æ´ãå®äºãã¾ããã</p>
<h4 id="é害-ã¡ã¼ã«ã¨ã¤ãªã¢ã¹ã®èæ
®ä¸è¶³">é害: ã¡ã¼ã«ã¨ã¤ãªã¢ã¹ã®èæ
®ä¸è¶³</h4>
<p>ä½æ¥ãä¸æ¯ã¤ããã¨ããã§èæ
®ä¸è¶³ãçºè¦ãã¾ãããã¦ã¼ã¶ã¼ã»ã°ã«ã¼ãã®primary email ã«ã¤ãã¦ã¯ cookpad.jp ãã .com ã¸å¤æ´ããã¾ããããsecondary email - ã¤ã¾ãåå¥ã«ä»ä¸ãã¦ããã¨ã¤ãªã¢ã¹ã«ã¤ã㦠cookpad.com ã¸ã®å¤æ´ãå¿
è¦ã ã£ãã¨ããèæ
®ããæ¼ãã¦ãã¾ããããã®ç¶æ
ã 㨠cookpad.com ã® secondary email address ã¸ã¡ã¼ã«ãçä¿¡ããéã« NoSuchUser 㧠hard bounce ãè¿ã£ã¦ãã¾ãã¾ãã</p>
<p>æ¥ã MX ã¬ã³ã¼ããå度 Postfix ã¸æ»ããã¹ã¯ãªãããæ¸ãã¦å¾©æ§ä½æ¥ã¸åãæããã¾ãããããã«ã¤ãã¦ã¯æ¢åã®ã¨ã¤ãªã¢ã¹ã§ cookpad.jp ãã¡ã¤ã³ã®ã¿åå¨ããã¢ãã¬ã¹ãæ½åºããcookpad.com ãã¡ã¤ã³ã®ã¨ã¤ãªã¢ã¹ã¨ãã¦è¿½å ãã¾ããã</p>
<p>Email ã®å¤æ´ã¯æ§åã® alias ãèªåçæããããã CSV ã§è¯ãã®ã§ã¯? ã¨ãªã£ã¦ã¤ã³ãã¼ãç¨ CSV ã®çæé¨ã®ã³ã¼ãã¬ãã¥ã¼ã¾ã§ããã¨ããããäºãã§å®å
¨ã«åå¨ãå¿ãã大ãããªãã¹ã¨ãªã£ã¦ãã¾ãã¾ããããã®ç¹ã®ãªã«ããªã«ã¤ãã¦ã¯å¾è¿°ãã¾ãã</p>
<h3 id="äºå¾ä½æ¥">äºå¾ä½æ¥</h3>
<p>Google Workspace ããã³ãèªä½ã®ä½æ¥ã¯ããã§å®äºã§ãããããããã¯äºå¾ã«è¡ã£ãä½æ¥ã«ã¤ãã¦è§£èª¬ãã¾ãã</p>
<p><figure class="figure-image figure-image-fotolife" title="å³: æ¬å½ã«ã§ããã®ãã¨é©ã社å¡ã®æ§å"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sora_h/20230628/20230628155957.png" width="840" height="679" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>å³: æ¬å½ã«ã§ããã®ãã¨é©ã社å¡ã®æ§å</figcaption></figure></p>
<h4 id="å種SaaSã¸ã®åæ 確èªä½æ¥">å種SaaSã¸ã®åæ ã»ç¢ºèªä½æ¥</h4>
<p>å種 SaaS ã®ç®¡çè
å´ä½æ¥ãä¸è¨ã®éãåå¨ãã¾ããã</p>
<ul>
<li>管çã³ã³ã½ã¼ã«ããã¦ã¼ã¶ã¼æ
å ±ãæ´æ°ãã</li>
<li>ãµãã¼ãå´ãªãã¼ã å®äºãå¾
㤠(ããã㯠Google å´ã®å®äºãä¼ãã¦å®æ½ãã¦ãããããããã«ããæ¥ç¨ã¯äºåã«ç¸è«ãã¦ãã)</li>
<li>ãµãã¼ãã« Google å´ã®ãªãã¼ã ãå®äºããæ¨ä¼éãã</li>
</ul>
<p>管çã³ã³ã½ã¼ã«ããã¦ã¼ã¶ã¼æ
å ±ãæ´æ°ããå ´åãä¸æ¬ã§åºæ¥ãªãä¾ãã»ã¨ãã©ã§ãããè¤æ°äººã§åæ
ãã¦ã²ãããã¡ã¼ã«ã¢ãã¬ã¹ã®æ´æ°ãå®æ½ãã¾ããã</p>
<p>ãã以å¤ã®ããã¤ãã® SaaS ã§ã¯äºåã«åæãã¦ããæéã¾ã§ã«ãªãã¼ã ãå®äºãã¦ããªããã¾ãã¯æ°ãã¡ã¤ã³ã§ãã cookpad.com ã®ã¢ãã¬ã¹ã«æ¢ã«ã¢ã«ã¦ã³ããåå¨ãã¦ãªãã¼ã ã§ããªãã£ã (ã³ã³ããªã¯ã) ã¨ãã£ããã©ãã«ãããã¾ããã</p>
<p>æ¬ç¨¿ã§ã¯å¯¾å¿ãé·å¼ãã SaaS ãä¸å¿ã«ä»£è¡¨ä¾ã解説ãã¾ãããªããããæ¬ç¨¿ããã©ã¤ããªãã¡ã¤ã³å¤æ´ã®ããã«åç
§ãã¦ããæ¹ãããã°ããµã¼ãã¹å´ã®å®è£
ãç¶æ³ã¯å¤åããå¯è½æ§ããããããæ¬ç¨¿ã®å
容ã¯åèç¨åº¦ã«çãã¦é½åº¦ååããæ¤è¨¼ã¯è¡ãããã«ãã¦ãã ããã</p>
<h4 id="Asana">Asana</h4>
<p>Asana ã¯ããã³ã(çµç¹) ãã¨ã«è¤æ°ã®ãã¡ã¤ã³ãç»é²å¯è½ã§ãããcookpad.jp 㨠cookpad.com ãããããç¬ç«ããçµç¹ã§åå¨ãã¦ããç¶æ
ã§ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="Asana ã®çµç¹åãæ¿ãã¡ãã¥ã¼"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sora_h/20230628/20230628155919.png" width="570" height="440" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Asana ã®çµç¹åãæ¿ãã¡ãã¥ã¼</figcaption></figure></p>
<p>社å
横æçã«åãã¦ããä¸é¨ã®ãã¼ã ã 2 ã¤ã®çµç¹ãé »ç¹ã«åãæ¿ããªããã°ãªããªãã¨ãããã£ã¼ãããã¯ããã£ãã®ãåãããã¡ãã«ã¤ãã¦ã¯æºåãå
¼ãã¦ãAsana ã«ã¤ãã¦ã¯ 2023/1 é ã«æºåãå§ãã¾ãããAsana å´ã«çµç¹ã®ç§»è¡ã¬ã¤ããç¨æããã¦ãããããããã«å¾ã£ã¦çµç¹ã 1 ã¤ã«ãã¼ã¸ <a href="#f-2a1bf5cc" name="fn-2a1bf5cc" title="å®éã«ã¯ãã¼ã¸ã§ã¯ãªããçµ±åå
ã®çµç¹ã«ãã¼ã¿ãã³ãã¼ãããå®è£
ã®ããã§ã">*24</a> ãã¾ããã</p>
<p><a href="https://asana.com/guide/help/organizations/data-migration">https://asana.com/guide/help/organizations/data-migration</a></p>
<p>ã¦ã¼ã¶ã¼ã®ãã¼ã¸ã«ã¤ãã¦ã¯ãã¦ã¼ã¶ã¼ã« cookpad.jp, cookpad.com 両æ¹ã®ã¡ã¼ã«ã¢ãã¬ã¹ãç»é²ãã¦æ¤è¨¼ãéãã¦ããããã¨ã§èªåã§æãããããã§ããããã¼ã¸ãå¿
è¦ãªã¦ã¼ã¶ã¼ã¯åå¥ã«é£çµ¡ããã¦å¯¾å¿ãä¾é ¼ãã¾ããã</p>
<h4 id="Figma">Figma</h4>
<p>Figma 㯠Google ãã°ã¤ã³ã«ã¤ãã¦ã¡ã¼ã«ã¢ãã¬ã¹ããã¼ã¹ã«å®è£
ããã¦ããããã§ãã幸ãã«ãã¦ãµãã¼ãã CSV ãå
ã«ããä¸æ¬ã®ãªãã¼ã ãè¡ã£ã¦ãããã¾ãããã¾ãããµãã¼ãã«ä¾é ¼ãããã¡ã¤ã³è¿½å ã®æé ãå¿
è¦ã ã£ããããããã¯äºåã«æ¸ã¾ãã¦ããã¾ãã</p>
<p>ã¾ããåæ¥ã§ã®ä½æ¥ã«ã¤ãã¦ã¯åæãå¾ãããªãã£ããããéæã®ãã¡ã«ãªãã¼ã ãå®æ½ãã¦ãããã¾ããããã®å¯¾å¿ã¨ãã¦ãäºåã®ã¢ãã¦ã³ã¹ã§æ¥åã§éè¦ãªå ´å㯠Figma ã®ãã°ã¤ã³ç¶æ
ã確èªãã¦ããã°ã¤ã³ã§ãã¦ããªããã°ãã°ã¤ã³ãããããã«å¨ç¥ãã¾ããã</p>
<p>å®æ½ããçµæãã³ã³ããªã¯ããçãã¦ããã¦ã¼ã¶ã¼ããã¾ãããããã以å¤ã¯ç¡äºã«å®äºãã¾ãããã³ã³ããªã¯ãããã¦ã¼ã¶ã¼ã«ã¤ãã¦ã¯ Figma ãµãã¼ãã«ç©ºãã©ãã確èªãã¦ãããã空ã§ããã°åé¤ãã¦ãªãã¼ã ãç¶è¡ãã¦ããããã¨ãã§ããã®ã§ããã®ããã«ä¾é ¼ãã¾ããã</p>
<h4 id="Zoom">Zoom</h4>
<p>Zoom ã«é¢ãã¦ãåæ§ã®å¯¾å¿ã§ CSV ãå
ã«ãªãã¼ã ã¨ãªãã¾ããããã¡ã¤ã³è¿½å ã»æ¤è¨¼ãäºåã«è¡ãç¹ãåæ§ã§ãã</p>
<p>ãã¡ãã¯äºåã« CSV ãéä»ãä½æ¥æ¥ç¨ã¾ã§åæãã¦ããã¨ãããäºåã«å
容ãç²¾æ»ãã¦ãããããä½æ¥æéã«ãªã£ã¦ã³ã³ããªã¯ããçºçããããå
¨ä½ã®ä½æ¥ãä¸æããã¨å ±åããã¦ãã¾ãã¾ãããããã«å ããäºåã«ä½æããå
¨ Google ã¦ã¼ã¶ã¼ã®æ°æ§å¯¾ç
§ CSV ãéã£ã¦ããã®ã§ãããå®å¨ãã Zoom ã¦ã¼ã¶ã¼ã«çµã£ã¦ã»ããã¨ã®ãªã¯ã¨ã¹ããããã¾ãããäºåã«éã£ã¦ããã®ã ããäºåã«ãã§ãã¯ãã¦ããã¦ã»ããâ¦â¦â¦ã</p>
<p>ã³ã³ããªã¯ãã«ã¤ãã¦ã¯ Google ãã°ã¤ã³ã§ã¯ãªããã以å¤ã®æ段 (ã¡ã¼ã«ã¢ãã¬ã¹+ãã¹ã¯ã¼ã) ã§ãµã¤ã³ã¢ããã»ãã°ã¤ã³ãã¦ãã¾ã£ãå ´åã«çºçãã¾ãããã®ãããªåé¡ãçãã¦ãã¾ãã®ãè¤æ°ã®ãã¡ã¤ã³ãããæ
ã¨è¨ãã¾ããä»®ã«ã¦ã¼ã¶ã¼æ¬äººã誤ãã«æ°ä»ãã¦ãã°ã¤ã³ãç´ããã¨ãã¦ããä½æããã¦ãã¾ã£ãã¢ã«ã¦ã³ãã¨ã¦ã¼ã¶ã¼ã¯ãã®ã¾ã¾æ®ç½®ããããããã³ã³ããªã¯ãã¸ç¹ããã¨ãã訳ã§ããã</p>
<p>次ã«ãæ®å¿µãªãããã¨ãã¨ä¼æ¥ã«å®æ½ãã¦ããé¢ä¿ã§ãµãã¼ããã追å ã®ã¬ã¹ãè¿ã£ã¦ãããã¨ã¯ãªãã大å¤å°ã£ããã¨ã«ãªãã¾ããã
ãã®å ´ã§æ¤è¨¼ãè¡ã£ãã¨ããããªããªãåä»ãªãã¨ãåããã¾ãããç¹ã«ã³ã³ããªã¯ãããªããã° Google ãã°ã¤ã³å¾ã«ã¡ã¼ã«ã¢ãã¬ã¹ãæ´æ°ãããèãããæ´æ°ããã° Zoom ã¦ã¼ã¶ã¼ã¯ãªãã¼ã ã«åé¡ãªã追å¾ã§ãã¾ãã</p>
<p>ããããã³ã³ããªã¯ããããå ´åã®æåãé常ã«å°ãã¾ãããã¾ãã cookpad.com ã¢ãã¬ã¹ã§ Zoom ã¦ã¼ã¶ã¼ãæ¢ã«åå¨ããå ´åã¯å®¹èµ¦ãªããã¡ãã¸ãã°ã¤ã³ããäºã«ãªããä¸è¨ã®é¸æè¢ãæ示ããã¾ã:</p>
<ol>
<li>ã¡ã¼ã«ã¢ãã¬ã¹ãå¤æ´ãã</li>
<li>ãã¡ã¤ã³ã«å¯¾å¿ããã¢ã«ã¦ã³ãã¸åå ãã</li>
<li>ç¡è¦ãã¦ç¶è¡ãã</li>
</ol>
<p><a href="https://support.zoom.us/hc/en-us/articles/4405656980109-Advanced-Associated-Domain-configurations">https://support.zoom.us/hc/en-us/articles/4405656980109-Advanced-Associated-Domain-configurations</a>
(å®æ½å½æããã¦ã¼ã¶ã¼ãç®ã«ããç»é¢ã®ä¾ãæ²è¼ããã¦ãã¦ããã¥ã¡ã³ããæ¡å
ããã¦ãã!)</p>
<p>ãããã®é¸æè¢ã¯ã¢ã«ã¦ã³ãã«è¿½å ãããã¡ã¤ã³ãã¨ã«ã«ã¹ã¿ãã¤ãºå¯è½ã§ããæ¬ããã¸ã§ã¯ãã®ç®æ¨ã¨ãã¦ãå½±é¿ãæå°éã«æããããæ²ãã¦ãããããæ¥åãæ¯éãªãç¶ç¶ã§ããå¿
è¦ãããã¾ããå¾ã£ã¦ãããã¾ã§å©ç¨ãã¦ãã cookpad.jp ã® Zoom ã¦ã¼ã¶ã¼ã¸å¾©å¸°ã§ããããæ´ããå¿
è¦ãçãã¾ããã</p>
<p>æ¤è¨¼ã®æ«ã(3) ç¡è¦ãã¦ç¶è¡ãã â ç¡æãã©ã³ (Basic) ã®ã¢ã«ã¦ã³ããåé¤ãã¨ããæé ã辿ã cookpad.com å義ã®(1ã¦ã¼ã¶ã¼ããããªã)ã¢ã«ã¦ã³ãã¨ã¦ã¼ã¶ã¼ãåé¤ãããã¨ã§ cookpad.jp ã¦ã¼ã¶ã¼ã¸ãã°ã¤ã³ããªãã¼ã ã¸è¿½å¾ã§ãããã¨ãå¤æãã¾ããã</p>
<p>(2) ã®ãªãã·ã§ã³ (consolidation) ã«ã¤ãã¦ã¯å®æ½ããã㨠IT ã§ç®¡çããã¡ã¤ã³ã® Zoom ã¢ã«ã¦ã³ãã¸åå ããcookpad.jp 㨠cookpad.com å義ã§éè¤ããã¦ã¼ã¶ã¼ãåå¨ãã¦ãã¾ãä¸ãèªåã§ã®å¾©å¸°ãä¸å¯è½ã«ãªãããç¡å¹åãã¾ããã(1) ã®ãªãã·ã§ã³ã®ã¿æ示ããã«ã¹ã¿ãã¤ãºãå¯è½ã§ãããcookpad.jp ã® Zoom ã¦ã¼ã¶ã¼ãæ¢ã«åå¨ãã¦ãã®ã¡ã¼ã«ã¢ãã¬ã¹ã«ã¯å¤æ´ã§ããªãä¸ãcookpad.com ã¯èª¤ã£ã¦ä½æãããã¦ã¼ã¶ã¼ãã®ãã®ã§ä¸å¯è½ãªããè¦éãã¾ããã</p>
<p>ã¢ã«ã¦ã³ãåé¤ã®æä½ã¯ã¯ãªãã¯æ°ãå¤ãããä¸å®ã ã£ããããæ¥è±ä¸¡æ¹ã§ã¹ã¯ãªã¼ã³ã·ã§ãããæ·»ããä½æ¥æé ã®ã¢ãã¦ã³ã¹ãã¼ã¸ã追å ã§å¨ç¥ãã¾ããã</p>
<p>調æ»ã®çµæãã¹ã¯ã¼ããã°ã¤ã³ â Google ãã°ã¤ã³ã¸åãæ¿ããéã®ç¢ºèªç»é¢ãåå¨ããäºãå¤æããããã¼å«ãã¦ããè¤éãªæç« ã«ãªãã¾ãããããã®åä¸å¯§ã«ä½æããããæ··ä¹±ãªãä¹ãè¶ãããã¾ãããé±æããã IT ãã«ããã¹ã¯ã«å¯¾å¿ãã¦ããã£ããã©ãã«ã·ã¥ã¼ãã§ãGoogle/Zoom ã®ã¯ããã¼ãå
¨åé¤ããªã㨠cookpad.com ã¸æ´æ°ãããªãä¾ãªã©ããã¤ã追å ã§ã±ã¼ã¹ãè¦ã¤ããã¾ããããæ¦ãåé¡ãªãã£ãããã§ãã</p>
<p>è¨å®å¤æ´ã¾ã§ã«èª¤ã£ã¦ã¢ã«ã¦ã³ãã consolidate ããã¦ãã¾ã£ããã¿ã¼ã³ãªã©ãã¡ã¤ã³ã® Zoom ã¢ã«ã¦ã³ãã« cookpad.jp, cookpad.com 両ã¦ã¼ã¶ã¼ãåå¨ãã¦ãã¾ãã±ã¼ã¹ãçºçãã¾ããããããã«ã¤ãã¦ã¯ Zoom ãµãã¼ãã«é±æãã«ãã«ã¯ã§ã®ãªãã¼ã ã¨åããã¦ãã¼ã¸å¯¾å¿ããé¡ãã§ãã¾ããã</p>
<h4 id="Slack">Slack</h4>
<p>Slack ã«é¢ãã¦ã Google ãã°ã¤ã³ã§éç¨ãéå§ãããããç¾ç¶ã¯ SAML ãã°ã¤ã³ã§ãã cookpad.jp ãã cookpad.com ã«å¤æ´ããå¿
è¦ãããã¾ã <a href="#f-71aa549b" name="fn-71aa549b" title="ããªãåã« Enterprise Grid ãã©ã³ã«ç§»è¡ãã¦ããã®ã§ç¾å¨ã¯ Azure AD ãã SAML ãã°ã¤ã³ãcookpad.jp ãåä¹ããã¦ãã¾ããEnterprise Grid 移è¡æã«ãªãã¼ã ãæ¤è¨ããã¨ããå½æã¯ä¸å¯è½ã ã£ãè¦ã">*25</a>ãä»åãµãã¼ãã«èããã¨ãã SCIM ãå©ç¨ããã®ãæ©ããããã¨ããææ¡ãè²°ãã¾ããã</p>
<p>ã¹ã¯ãªããã¬ã¹ã§æ¸ãã° & ãã®ã¿ã¤ãã³ã°ã§ SCIM å°å
¥ãã§ããã°å¬ããã¨ãããã¨ã§ SCIM ãå°å
¥ããæ¹åã§æ±ºãã¾ããããæ¤è¨¼ããå°å
¥ã¾ã§ã¾ãã¾ãæéãããã£ã¦ãã¾ããå®ã¯ Slack ã«ã¤ãã¦å®äºããã®ã¯ã¤ãå
æ¥ã®äºã§ãããã®ä¸ã§å°å
¥ç´å¾ã« SCIM v2 ã®ãªãªã¼ã¹ãããã¦ãããã¿ã¤ãã³ã°ãæªãã£ãã§ããâ¦ã</p>
<p>Azure AD ãã SCIM ãè¡ã£ã¦ãã¦ãcookpad.jp ã§æåã«ãããã³ã°ãã cookpad.com ã«å¤æ´ãè¡ãã¾ãããSlack 㯠user name ãã display name ã«æ¹éãåãæ¿ãã¦ã ãã¶çµã¡ã¾ãããuser name 㯠API ä¸ã®åæã«æ®ã£ã¦ãã¦ãSCIM ãä¾å¤ã§ã¯ããã¾ãããSCIM ã®ããã¥ã¡ã³ãã«ãè¨è¼ããã¦ããããã«æå種ã»æåé·å¶éãåé¿ãã¦æå®ããå¿
è¦ãããã¾ãã</p>
<p>SCIM åã«ããã£ã¦åé¡ã ã£ãã®ã¯ãã® userName ãéè¤ããã«è¨å®ãããã¨ã§ããDisplay name ã¯ã¦ã¼ã¶ã¼ãèªç±ã«è¨å®ã§ããããã«ããããã SCIM ã§ã¯æ¸¡ããªããã¨ã«ãããããæ°è¦ã«ãããã¸ã§ã³ãããã¦ã¼ã¶ã¼ã¯ userName ãç®ã«ãã¾ãããããã£ã¦ãããã¯æ©æ¢°çãªå¤ã«è¨å®ã§ãã¾ãããAzure AD ãã³ãã¬ã¼ãã®ããã©ã«ãã§ãã email ã®ãã¼ã«ã«ãã¼ããæ¡ç¨ãããã¨ã«ãã¾ããããsingle/multi channel guest ã® userName ã¨ãè¡çªãã¦ã¯ãªããªãã¨ããæã§ä½ä»¶ãè¡çªãçºçãã¾ãããããã«ã¤ãã¦ã¯åå¥ã«ã¨ã©ã¼ã確èªã㦠guest å´ãå¥åã«å¤æ´ããã¨ããæªç½®ãè¡ãã¾ããã</p>
<h4 id="ã¡ã¼ã«ãã¦ã³ã¿ã¤ã ã®äºå¾å ±å">ã¡ã¼ã«ãã¦ã³ã¿ã¤ã ã®äºå¾å ±å</h4>
<p>é±æãã¾ã§ã«å¾©æ§ã§ãã¦ããªãã¨å°ã SaaS é¡ã®å¯¾å¿ã¾ã§ä¸æ®µè½ããã¨ããã§ãã¡ã¼ã«ãã¦ã³ã¿ã¤ã ã®äºå¾å ±åã¨ãã¡ã¼ã«ã¨ã¤ãªã¢ã¹é害ã®äºå¾å¯¾å¿ãè¡ãã¾ãã</p>
<p>ã¾ãã¯ãã¦ã³ã¿ã¤ã ä¸ã«çä¿¡ããã¡ã¼ã«ãã°ãéè¨ãã¦ã¦ã¼ã¶ã¼ã»ã°ã«ã¼ããã¨ã«çä¿¡ãã envelope from ã®ãªã¹ããä½æãã¾ãããCloudWatch Logs Insights 㧠Postfix ã®ãã°ãä¸è¨ã¯ã¨ãªã§æ½åºãã¾ã:</p>
<pre><code>fields @timestamp, @message, @logStream, @log
| filter @message like /NOQUEUE/
| parse @message /RCPT from (?<remote_mta>.+?)\[(?<remote_ip>.+?)\]: (?<response_code>[0-9. ]+?) <(?<recipient_email>.+?)>: (?<response_message>.+?);/
| parse @message /; from=<(?<from_email>.*?)> to=<(?<to_email>.+?)> proto=(?<proto>.+?) helo=<(?<helo>.+?)>/
| filter from_email not like /bounces.google.com/
| stats count(*) as cnt by from_email, to_email
| sort to_email asc, cnt desc
| limit 10000
</code></pre>
<p>ããã§å¾ããã from_email 㯠envelope from ã§ãããããGoogle Sheets ã«ã¤ã³ãã¼ããã¦å種é
ä¿¡ãµã¼ãã¹ã®åçã«ä½æããã envelope from ãæ£è¦åãã¨ã¤ãªã¢ã¹ã primary email address ã«å¤æãåéè¨ã®ã¡ CSV ã§åºåãã¾ããã</p>
<p>é害èªä½ã¯ wiki ã§å¨ç¥ã®ä¸ããã®ãã¼ã¿ãæ·»ãã¦é害ã®å¯¾è±¡ã ã£ãã¦ã¼ã¶ã¼ã»ãã以å¤ã§æé¢ãåãã¦ã¡ã¼ã«ã§åå¥ã«ãç¥ãããéä¿¡ãã¾ãããéä¿¡ãããã¼ã¿ãã©ããããå½¹ã«ç«ã¤ãã¯åããã¾ããããå°ãªãã¨ãé害ããªãã£ãå ´åã«ã¤ãã¦ã¯ãªãã©ã¤ã§å¾ããå°çãã¹ãã¡ã¼ã«ãæ¥ã¦ãããã©ããã¯å¤æã§ããã¨ãããã¨ã§å®æ½ãã¦ãã¾ãã</p>
<p>ã¾ããã¨ã¤ãªã¢ã¹ãåé¤ããããã¨ã«ãã Gmail ã® Send mail as è¨å®ãèªåã§å¥å¥ªããããã¨ã確èªãããããåè¨å®ã«ã¤ãã¦ãæ¡å
ãã¾ããã</p>
<h4 id="DKIM-ã®åè¨å®">DKIM ã®åè¨å®</h4>
<p>ãããã¡ã¼ã«é¢é£ã§ç²ç¹ã®1ã¤ã§ãããããã¡ã¤ã³ã¨ã¤ãªã¢ã¹ãåé¤ããæç¹ã§ DKIM ã®è¨å®ãæ¶å¤±ãã¾ããå®æ½å¾ãã°ãããã¦ã»ãã¥ãªãã£ãã¼ã ã®ã¡ã³ãã¼ãã DKIM å¿ãã¦ãªã? ã¨è¨ããã¦æ
ã¦ã¦è¨å®ãã¾ãã <a href="#f-4b6482da" name="fn-4b6482da" title="DMARC ã® reject ããªã·ã¼æå¹åã®ããã«ã¬ãã¼ããç£è¦ãã¦ããããã§ã">*26</a>ãåã»ããã¢ããã«ããéµé·ãç¾ä»£çã«ãªã£ãã®ã§ãå¾ã ã£ãã¨æããã¨ã«ãã¾ãã</p>
<h3 id="社å
ã¢ãã¦ã³ã¹ã«ã¤ãã¦">社å
ã¢ãã¦ã³ã¹ã«ã¤ãã¦</h3>
<p>ãã¦é·ã
ã¨æ¸ãã¦ã¾ããã¾ããããæå¾ã«ä½æ¥ä¸ããã³ä½æ¥åå¾ã«å®æ½ãã社å
ã¸ã®ã¢ãã¦ã³ã¹ã«ã¤ãã¦è¨åãããã¨æãã¾ããä»åã¯ã¢ã«ã¦ã³ãã®ãªãã¼ã ã«ãã£ã¦ç¤¾å
ã·ã¹ãã ã SaaS ã«ã¢ã¯ã»ã¹ä¸å¯è½ã«ãªããªã¹ã¯ããäºåäºå¾ã«åèªã®ä½æ¥ãåããã¾ãæ¢ã«æ¸ãã¦ããããã«ãã®ä»ä¸æ¸¬ã®äºæ
ãçºçããå¯è½æ§ããã£ããããæ¯è¼çä¸å¯§ã«ã¢ãã¦ã³ã¹ãå·çãã¾ããã</p>
<p>Slack ã§ã®ç¤¾å
ã¢ãã¦ã³ã¹ã¯ãã¡ãããäºå¾ Zoom ã Figma ãªã©ãªãã¼ã ã»ãã¼ã¸ã®ç©ã¿æ®ããçºçããããã常ã«ç¶æ³ãåãããã¼ã¸ãæ´æ°ãç¶ãã¦ãã¾ããã</p>
<p><figure class="figure-image figure-image-fotolife" title="å種ã¢ãã¦ã³ã¹æé¢"><div class="images-row mceNonEditable"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sora_h/20230628/20230628160036.png" width="1200" height="768" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sora_h/20230628/20230628155947.png" width="1200" height="780" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sora_h/20230628/20230628160101.png" width="1200" height="158" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></div><figcaption>å種ã¢ãã¦ã³ã¹æé¢</figcaption></figure></p>
<p>ä»åããªãã¼ã å¾ã«ããã¤ããã©ãã«ããã£ãããæ³å®ã¹ã±ã¸ã¥ã¼ã«éãã« SaaS å«ããªãã¼ã ãå®äºããªãã¨ãã£ãè¨ç»å¤ã®åºæ¥äºãããã¾ããããå®ã¯æ¥æ¬ã»ã°ãã¼ãã«éãã¦ã»ã¼åãåãããããã¾ããã§ãããä¸å¯§ãªããã¥ã¡ã³ããç°¡æ½ãªã¢ãã¦ã³ã¹ãæ··ä¹±ãé²ãã»ã«ããµã¼ãã¹ã§è§£æ±ºãåºæ¥ãã¨ãããã¨ãæ¹ãã¦å®æã§ããããã¸ã§ã¯ãã¨ãªãã¾ããã</p>
<h3 id="æ¯ãè¿ã">æ¯ãè¿ã</h3>
<p>ãã¦ã以ä¸ã Google Workspace ãã©ã¤ããªãã¡ã¤ã³å¤æ´ããã¸ã§ã¯ãã®è§£èª¬ã¨ãªãã¾ããã¾ã¨ãã¨æ¯ãè¿ããã¦ã¯ä¸è¨ã§ããããã</p>
<ul>
<li><strong>Google Workspace å¤ã®ãµã¼ãã¹ã¸ã®å½±é¿ã調æ»ããã®ãã¨ã«ãã大å¤</strong>
<ul>
<li>Relying Party åä½ã«ã¯ OIDC ã®é©åãªå®è£
ããé¡ããããããã¡ã¤ã³ä»¥å¤ã§ãã¡ã¼ã«ã¢ãã¬ã¹ã¯æ®éã«å¤ãããããã®ã</li>
<li>æè¿ <a href="https://techcommunity.microsoft.com/t5/microsoft-entra-azure-ad-blog/the-false-identifier-anti-pattern/ba-p/3846013">The False Identifier Anti-pattern</a> ã¨ããããè¨äºã Microsoft Entra ãã¼ã ããåºã¦ããã®ã§ãªã¹ã¹ã¡ã§ãã</li>
<li>SAML ãã°ã¤ã³ã«å¯¾å¿ããã®ã¯å¿
é ã§ã¯ãªã <a href="#f-b01be3ce" name="fn-b01be3ce" title="ããã SAML ãå«ãªãä»»æã® OIDC ã¯ã©ã¤ã¢ã³ããè¨å®ããã¦ã»ããâ¦">*27</a> ã¨ãã¦ãGoogle ãã°ã¤ã³ãªã©ãå¼·å¶ãããªãã·ã§ã³ã¯æ¬²ãããããã¯ããã¨ãã¦ãµãã¼ãã³ã¹ããæããã®ã¯åããã <a href="https://sso.tax">SSO ãæåãªãã·ã§ã³ã«ããªãã§ã»ãã</a>ã</li>
</ul>
</li>
<li><strong>ä½æ¥æ¥ç¨ã JST åææã«ããããéææ·±å¤ã«ããã¹ãã ã£ã</strong>
<ul>
<li>å種ãµã¼ãã¹ã®ãµãã¼ããç±³å½è¥¿æµ·å²¸æé (PT) ã§ç¨¼åãã¦ãããã</li>
<li>ããããæ¬ä»¶ãã¾ã£ããç解ãã¦ããããªã <a href="#f-8aafcc3a" name="fn-8aafcc3a" title="å®é説æã«è¦å´ãããµã¼ãã¹ã¯ãããããã£ã">*28</a> ã対å¿ãè¤éãã¨ãã£ãçç±ã§è±èªè©±è
ã¸ã¨ã¹ã«ã¬ããããã¨ãæ³å®ããæ¥æ¬å½å¤ã§éçºããã¦ãããµã¼ãã¹ã¸ã®ååãã¯æ¥æ¬èªãµãã¼ãããããã¨ãå
¨ã¦è±èªã§è¡ã£ã¦ããã®ãããããã¡ããæ¥æ¬èªãµãã¼ããç¡ãå ´åãå¤æ°ã ã£ãã</li>
<li>å½ç¤¾ã§ç¤¾å
ã·ã¹ãã ã¡ã³ãæã«èæ
®ãã¹ãã¿ã¤ã ã¾ã¼ã³ã¯ UK æéã¨æ¥æ¬æéããããã«ã被ã£ã¦ããªã PTããã¤å¹³æ¥ã«å®æ½ããã°ã¹ã ã¼ãºã«ãµãã¼ãã«ä½æ¥ãè¡ã£ã¦ãããããããªã¢ã«ã¿ã¤ã ã«é£æºãã§ããã¨æããã</li>
<li>ã¾ããåæä½æ¥ã§æ°´æã« Google Workspace ãå«ãã IdP ã®ããªã¼ãºã宣è¨ãããããµãã¼ãã¨ã® TZ å·®ã«ãã RTT ãèæ
®ãã¦ææã«ã¯ããªã¼ãºãã¦ããã¹ãã ã£ã</li>
</ul>
</li>
<li><strong>ãã©ã¤ããªãã¡ã¤ã³ã®å¤æ´å¯ã»ä¸å¯ã¯äºåã«ç¢ºèªãã¦ããã¹ãã ã£ã</strong>
<ul>
<li>æ£ç¢ºã«ã¯éä¸ã§(諸äºæ
ã§)ã¿ã¹ã¯ããã¼ãã¦ãã¾ã£ã⦠ããã¦ããã«ã¤ãã¦ã¯ãµãã¼ãã«åãåããããããªãã®ã大å¤ãªã¨ãã</li>
</ul>
</li>
<li><strong>ã»ã¨ãã©ã®ã¡ã¼ã«ã«ã¤ãã¦ãã¹ããç°¡åãªä»æãã§é²ããã®ã¯è¯ãã£ã</strong>
<ul>
<li>ãã ãã¨ã¤ãªã¢ã¹ãè¦è½ã¨ãã¦ã¡ã¼ã«ã®ã¨ã¤ãªã¢ã¹ã®ã¿é害ãèµ·ããã¦ãã¾ã£ã</li>
</ul>
</li>
<li><strong>æ¥æ¬ã»ã°ãã¼ãã«ãã¼ã 両æ¹ã§å®æ½å¾ãé±æãããã®ãã©ãã«ãã¦ã¼ã¶ã¼ååãã¯ã»ã¼ç¡ããéæã§ãã¦è¯ãã£ã</strong>
<ul>
<li>ä¸å¯§ãªããã¥ã¡ã³ãã»ç°¡æ½ãªã¢ãã¦ã³ã¹ãå©ããã¨æããã</li>
<li>è±æ¥ä¸¡æ¹ã§ããã¥ã¡ã³ããç¨æããããã¹ã¯ãªã¼ã³ã·ã§ããã¾ã§è±æ¥ã§åããã®ã¯ä½æ¥è² è·ã¨ãã¦ã¯å²ã¨å¤§å¤ã§ã¯ãã</li>
<li>ã¾ããå¤æ´ã«ãã£ã¦å¼·å¶ãã°ã¢ã¦ããçºçããªãã®ãè¯ãã£ããã¢ãã¤ã«ã§ã iOS/iPadOS ã§ã¯ Google 製ã¢ããªã¯ä½ããªããAndroid ã§ããã¹ã¯ã¼ãã®ã¿ã§ã¡ã¼ã«ã¢ãã¬ã¹ã¾ã§æ±ãããããã¨ããªãã£ã</li>
</ul>
</li>
</ul>
<p>ç·ä½æ¥æéã¨ãã¦ã¯ããã¸ã§ã¯ãçµç¤ã«éä¸ãã¦ããã¨ã¯ããã2022/8 ï½ 2023/3ãSlack ãªã©ãå«ããã° 2023/6 ã¾ã§ã¨é·æéã«ãããæ¬å½ã«å¤§å¤ã ã£ãã®ã§ä»®ãã¡ã¤ã³åã§éç¨ãéå§ããã®ã¯æ¬å½ã«ãããããã¾ãã <a href="#f-3eff14a5" name="fn-3eff14a5" title="é¢ä¿ãªãã§ãã Active Directory (Windows Server) ã®ãã¡ã¤ã³ãã¡ããã¨ãããã¡ã¤ã³ã使ãã®ãè¯ãã§ããpublic TLD ã®ãµããã¡ã¤ã³ããªã«ããè¨å®ããä¸ã§(ãããKerberos Realmã«ãªãã®ã§ã¦ã¼ã¶ã¼ã«åããããããã¡ã¤ã³ã®ä¸ãè¯ãã¨æãã¾ã)ãUPN suffix ãã¡ã¼ã«ã¢ãã¬ã¹ã«åããã¾ãããã">*29</a>ã</p>
<h3 id="Acknowledgements">Acknowledgements</h3>
<p>æ¬ããã¸ã§ã¯ãã¯çè
(sorah) ã ãã§ã¯æãå°åºè¶³ããªãã£ãããã<a href="https://www.i-style.jp/">æ ªå¼ä¼ç¤¾I-Style</a> ã®æ¿å£ å´å¸ãã¾, è
äº ç¥å¤ªæ (<a href="http://blog.hatena.ne.jp/hokkai7go/">id:hokkai7go</a>) ãã¾, ä¸ç° å¥å²ãã¾ã«äºå調æ»ãã¹ã¯ãªããã®ä½æã»æ¤è¨¼ã®ãååãããã ãã¾ããã</p>
<p>ã¾ãä¸è¨ã®è¨äºãå
è¡äºä¾ã¨ãã¦åèã«ããã¦ããã ãã¾ãããããã°åºæ¥ããã â¦! ã¨ããã¨ããã§è¸ã¿åãããã«ã±ã«ããªãã¾ããã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.pepabo.com%2F2022%2F01%2F19%2Fprimary-domain-change%2F" title="Google Workspaceã®ãã©ã¤ããªãã¡ã¤ã³å¤æ´ãå®æ½ãã¾ãã - Pepabo Tech Portal" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>ã¾ãããã¡ãã¯åæ§ã®æ¸å¿µã§å¥ããã³ãã¸ã®å¼ã£è¶ããè¡ã£ãäºä¾ã ããã§ããæ¬ç¨¿ã®ããã«é å¼µãã°åãã¢ã«ã¦ã³ããç¶æãã¦ãªãã¼ã ãããã¨ãã§ãã¾ãããèå³æ·±ãäºä¾ã¨ãã¦ç´¹ä»ãã¾ãã</p>
<p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fproduct.st.inc%2Fentry%2F2023%2F04%2F03%2F123846" title="社åå¤æ´ã«ä¼´ãGoogle Workspaceã¢ã«ã¦ã³ãã®å¼è¶ãæé ã解説ãã¾ã - STORES Product Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p>
<p>ã¿ãªãã¾ããã®è¨äºãå½¹ã«ç«ã¦ãªããã¨ãé¡ãã¤ã¤çµããããã¨æãã¾ãããä½ãã®å½¹ã«ç«ã¦ã°å¹¸ãã§ãã</p>
<div class="footnote">
<p class="footnote"><a href="#fn-55f9a260" name="f-55f9a260" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">æè¡é¨ SRE ã°ã«ã¼ãã主åã§ããä¸å¿â¦</span></p>
<p class="footnote"><a href="#fn-d60ff08e" name="f-d60ff08e" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">ããã³ãèªä½ã¯ 2007 å¹´ãããæ¥åã«æ¬æ ¼çã«ä½¿ãããã®ã¯ 2009 å¹´ããããããã§ãã詳細ã¯ä¸æã§ã</span></p>
<p class="footnote"><a href="#fn-9eed22d0" name="f-9eed22d0" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">å½æ㯠Google Apps</span></p>
<p class="footnote"><a href="#fn-6766843a" name="f-6766843a" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãããå°å
¥ææã¨åæ§ã«çµç·¯ã¯å®å
¨ã«æããã¦ãã¦ä¸æãå©ç¨ãã¦ããªããã¡ã¤ã³ã§ãä¸æ¦ãè¨å®ãã¦ã¿ã¦ããã®ã¾ã¾æ¬çªå©ç¨ããã¡ãã£ããã¿ã¼ã³ã¨æ³åãã¦ãã¾ã</span></p>
<p class="footnote"><a href="#fn-f7cd5bb7" name="f-f7cd5bb7" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">Intune ã®åå¨ãSAMLæ©è½ã®æè»æ§ãªã©è«¸ã
ã§ä½µç¨ã«ãªã£ã¦ãã¾ãããæ¬ããã¸ã§ã¯ãã§ãã£ã¨ã¢ãã¬ã¹ãçµ±ä¸ããæ··ä¹±ãé²ããããã«ãªã£ãã®ã§ Google Workspace 㯠Azure AD ããã®ãã§ãã¬ã¼ã·ã§ã³ã«åãæ¿ãããâ¦!</span></p>
<p class="footnote"><a href="#fn-0a97cccc" name="f-0a97cccc" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">Slack ãã¾ãã« Enterprise Grid 㧠SAML ã«å¤æ´ã«ãªã£ãæãcookpad.jp ã SAML 㧠Azure AD ã«åä¹ããã¦ããä¾</span></p>
<p class="footnote"><a href="#fn-2c3d8465" name="f-2c3d8465" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text">人ã®ååã¯å§åããããå¤ãã£ããããã¸ãã¹ãã¼ã ãç°ãªã£ãããè¡çªããããããããã¦ã¼ã¶ã¼åã«å½åè¦åãè¨ããã®ã¯é常ã«ãããããã¾ãããã²ã©ãä¾ã§ã¯ãã¤ã¬ã®ã¥ã©ã¼ãããã®ã«ã¦ã¼ã¶ã¼åã®å½åè¦åãéµåã¿ã«ãã¦ã¡ã¼ã«ã¢ãã¬ã¹ã®æ¨æ¸¬ãè¡ããããªå®è£
ãçºçãã¦ãã¾ãã¾ããå½ç¤¾ã§ã¯è¨å·ã®å¶ç´ã¯ããã¾ãããä»»æã®ã¦ã¼ã¶ã¼åãå
¥ç¤¾æã«ãªã¯ã¨ã¹ããããã¨ãåºæ¥ãããã«ãªã£ã¦ãã¾ã</span></p>
<p class="footnote"><a href="#fn-3576f14d" name="f-3576f14d" class="footnote-number">*8</a><span class="footnote-delimiter">:</span><span class="footnote-text">æ㯠hostmaster@ ã§ã¡ã¼ã«ãåã㦠SSL 証ææ¸çºè¡ã®æ¿èªã¨ãããã¦ããã®ã§ããããããã¤ã®ããã«è²ã
追å ããã¦ãã¾ãã</span></p>
<p class="footnote"><a href="#fn-5ad8fcdd" name="f-5ad8fcdd" class="footnote-number">*9</a><span class="footnote-delimiter">:</span><span class="footnote-text">å¾è¿°ãã¾ãããGoogle Workspace ã Cloud Identity ã®ãã¡ã¤ã³ãå
ã«ã¢ã«ã¦ã³ãé¸æç»é¢ã®è¡¨ç¤ºããã£ã«ã¿ãããã¹ããããã¾ãæ°è¦ãã°ã¤ã³æã«ãã©ã¼ã ã«ãã¡ã¤ã³ãããããã表示ããæ©è½ãçµç¹ã®ãã©ã¤ããªãã¡ã¤ã³ != ã¦ã¼ã¶ã¼ã®ãã¡ã¤ã³æã®æåãä¸å®</span></p>
<p class="footnote"><a href="#fn-0dca8a1f" name="f-0dca8a1f" class="footnote-number">*10</a><span class="footnote-delimiter">:</span><span class="footnote-text">Google ã sub claim ãå©ç¨ãããemail ã¯é©ããªãã¾ã¨ <a href="https://developers.google.com/identity/openid-connect/openid-connect#obtainuserinfo">ããã¥ã¡ã³ãã§æè¨ãã¦ãã¾ã</a>ãOIDC ä»æ§ä¸ã§ã sub ã¯ã¬ã¼ã 以å¤ã§ã®ç
§å㯠non-conformant ã§ã <a href="https://openid.net/specs/openid-connect-core-1_0.html#ClaimStability">https://openid.net/specs/openid-connect-core-1_0.html#ClaimStability</a> ã</span></p>
<p class="footnote"><a href="#fn-d41066f8" name="f-d41066f8" class="footnote-number">*11</a><span class="footnote-delimiter">:</span><span class="footnote-text">ã·ã£ãã¼ITã£ã½ãã¨ãããããã¯ãåä¼ç¤¾ã§å©ç¨ãã¦ãã¦æ¬ä½ã§é¢ç¥ãã¦ããªããµã¼ãã¹çããããã</span></p>
<p class="footnote"><a href="#fn-5fe8f86d" name="f-5fe8f86d" class="footnote-number">*12</a><span class="footnote-delimiter">:</span><span class="footnote-text">ããããã°æè¿ <a href="https://www.descope.com/blog/post/noauth">https://www.descope.com/blog/post/noauth</a> ã¨ãããã¾ããããGoogle ã®å ´å㯠email_verified claim ãè¦ã¦ããã°ãããã©ãè¦ã¦ããªãã¨ãããå®ã¯ãã£ãããããã ããã?</span></p>
<p class="footnote"><a href="#fn-1bd2ca63" name="f-1bd2ca63" class="footnote-number">*13</a><span class="footnote-delimiter">:</span><span class="footnote-text">å¤æ´å
ã® cookpad.jp ã¯æå³ãã使ããã¦ãã¦ãã¾ã£ããã¨è¨ãã®ãæ£ãããã§ãã</span></p>
<p class="footnote"><a href="#fn-f9abd0ca" name="f-f9abd0ca" class="footnote-number">*14</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãã¨ãã° Google ãã°ã¤ã³ãå¼·å¶ã§ããããã¹ã¯ã¼ãã§ãµã¤ã³ã¢ãããã¢ã«ã¦ã³ãæå¾
ãå諾ãã¦ãã¾ãã¨ãããªã£ã¦ãã¾ã</span></p>
<p class="footnote"><a href="#fn-2b9c1d76" name="f-2b9c1d76" class="footnote-number">*15</a><span class="footnote-delimiter">:</span><span class="footnote-text">Google ãã°ã¤ã³ã§ããªãå ´åã§ããã¹ã¯ã¼ããªã»ããã®æé ã§ãªãã¨ããªã£ããããäºãå¤ãã£ã</span></p>
<p class="footnote"><a href="#fn-03a6a6da" name="f-03a6a6da" class="footnote-number">*16</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãã¡ã¤ã³ææ権ã®æ¤è¨¼ããããµã¼ãã¹ã ã¨ãé¡ãã§ããããä¸åº¦ããã³ãã« .jp, .com 両æ¹ã®ã¦ã¼ã¶ã¼ãå
¥ãã¦ããã ã£ãã</span></p>
<p class="footnote"><a href="#fn-a40f5a87" name="f-a40f5a87" class="footnote-number">*17</a><span class="footnote-delimiter">:</span><span class="footnote-text">æ¥æ¬å´ã®å ´åãUK ãªãã£ã¹ãä¸å¿ã¨ãªã£ã¦ããã°ãã¼ãã«äºæ¥ã¯ ALB + Azure AD OIDC ã ã£ãããã¾ã</span></p>
<p class="footnote"><a href="#fn-9ce5d767" name="f-9ce5d767" class="footnote-number">*18</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãã¡ãããä¸æçã« hd ã«ä¸¡æ¹æå®ãã¦ããã£ã¦å¾ã§æ»ãã¨ããã®ãã¢ãªã§ãããå¾ã§æ»ãã¦ããããã¨ã¯å¿
é ã§ã¯ãªããããæ»ãã¦ããããªãã£ãã¨ããã ãä¸ä¾¿ãªã¾ã¾ã¨ããç¶æ³ãäºæ³ã§ãã¾ã</span></p>
<p class="footnote"><a href="#fn-99f459b1" name="f-99f459b1" class="footnote-number">*19</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãã¡ããèªè¨¼ã¯ sub claim ãå©ç¨ãã¦èªè¨¼ãè¡ãããã®ãæã¾ããã§ãããå種ãã©ã¼ã ãªã©ã¡ã¼ã«ã¢ãã¬ã¹ããå¼ããã¨ãå½ç¶ãããã</span></p>
<p class="footnote"><a href="#fn-ec846153" name="f-ec846153" class="footnote-number">*20</a><span class="footnote-delimiter">:</span><span class="footnote-text">äºåã«æ¤è¨¼ç¨ã® Google Workspace ä¸ã§åãæ°ã®ã¦ã¼ã¶ã¼ãä½æãã¦ã¿ãã°åããããããã¾ããããã¢ãã«ãªããªãä¸ã«è²»ç¨ãç¡é§ã«ããã£ã¦ãã¾ãâ¦</span></p>
<p class="footnote"><a href="#fn-f20f6dab" name="f-f20f6dab" class="footnote-number">*21</a><span class="footnote-delimiter">:</span><span class="footnote-text">ç´æ£ã¡ã¼ã«ã»ã«ã¬ã³ãã¼ã§å©ç¨ãã iOS èªä½ã«ç»é²ããã¦ããã¢ã«ã¦ã³ãæ
å ±ãGoogle 製ã¢ããªã§ã¯ä¸è¦ã ã£ã</span></p>
<p class="footnote"><a href="#fn-5d6bda02" name="f-5d6bda02" class="footnote-number">*22</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãã£ãããµãã¼ãã¯ãã°ãæ®ããªãä¸ãã®å ´ã§è§£æ±ºã¾ã§è³ããªãã¤ã¡ã¼ã¸ãå¼·ãã£ãã®ã§ãããGoogle Workspace ã®è±èªãµãã¼ãã¯å²ã¨ãã®æå¾
ã¯ä¸åã£ãæ°ããã¾ã</span></p>
<p class="footnote"><a href="#fn-1ea41bf8" name="f-1ea41bf8" class="footnote-number">*23</a><span class="footnote-delimiter">:</span><span class="footnote-text">ãªã»ã©ã¼ãããã©ã¤ã¢ã«ç®çã§æ¸¡ããããã®ã¨è¨æ¶ãã¦ãã¾ããããã詳細ãä¸æâ¦</span></p>
<p class="footnote"><a href="#fn-2a1bf5cc" name="f-2a1bf5cc" class="footnote-number">*24</a><span class="footnote-delimiter">:</span><span class="footnote-text">å®éã«ã¯ãã¼ã¸ã§ã¯ãªããçµ±åå
ã®çµç¹ã«ãã¼ã¿ãã³ãã¼ãããå®è£
ã®ããã§ã</span></p>
<p class="footnote"><a href="#fn-71aa549b" name="f-71aa549b" class="footnote-number">*25</a><span class="footnote-delimiter">:</span><span class="footnote-text">ããªãåã« Enterprise Grid ãã©ã³ã«ç§»è¡ãã¦ããã®ã§ç¾å¨ã¯ Azure AD ãã SAML ãã°ã¤ã³ãcookpad.jp ãåä¹ããã¦ãã¾ããEnterprise Grid 移è¡æã«ãªãã¼ã ãæ¤è¨ããã¨ããå½æã¯ä¸å¯è½ã ã£ãè¦ã</span></p>
<p class="footnote"><a href="#fn-4b6482da" name="f-4b6482da" class="footnote-number">*26</a><span class="footnote-delimiter">:</span><span class="footnote-text">DMARC ã® reject ããªã·ã¼æå¹åã®ããã«ã¬ãã¼ããç£è¦ãã¦ããããã§ã</span></p>
<p class="footnote"><a href="#fn-b01be3ce" name="f-b01be3ce" class="footnote-number">*27</a><span class="footnote-delimiter">:</span><span class="footnote-text">ããã SAML ãå«ãªãä»»æã® OIDC ã¯ã©ã¤ã¢ã³ããè¨å®ããã¦ã»ããâ¦</span></p>
<p class="footnote"><a href="#fn-8aafcc3a" name="f-8aafcc3a" class="footnote-number">*28</a><span class="footnote-delimiter">:</span><span class="footnote-text">å®é説æã«è¦å´ãããµã¼ãã¹ã¯ãããããã£ã</span></p>
<p class="footnote"><a href="#fn-3eff14a5" name="f-3eff14a5" class="footnote-number">*29</a><span class="footnote-delimiter">:</span><span class="footnote-text">é¢ä¿ãªãã§ãã Active Directory (Windows Server) ã®ãã¡ã¤ã³ãã¡ããã¨ãããã¡ã¤ã³ã使ãã®ãè¯ãã§ããpublic TLD ã®ãµããã¡ã¤ã³ããªã«ããè¨å®ããä¸ã§(ãããKerberos Realmã«ãªãã®ã§ã¦ã¼ã¶ã¼ã«åããããããã¡ã¤ã³ã®ä¸ãè¯ãã¨æãã¾ã)ãUPN suffix ãã¡ã¼ã«ã¢ãã¬ã¹ã«åããã¾ãããã</span></p>
</div>
sora_h
Path Drawing in SwiftUI
hatenablog://entry/820878482943197956
2023-06-21T16:25:23+09:00
2023-06-21T16:25:23+09:00 How to draw shapes using paths in SwiftUI, starting from the basics.
<p>Hi, this is Chris Trott (<a href="https://twitter.com/twocentstudios">@twocentstudios</a>) from Cookpad Mart's iOS team.</p>
<p>In this post I want to share a few tips for how to draw shapes using paths in SwiftUI, starting from the basics. The code in this post targets iOS 16 and Xcode 14, but is low-level enough that it should be relatively forward and backward compatible.</p>
<p>Drawing paths manually is not a common task in day-to-day app work. It can be especially tedious for complex shapes. However, it can sometimes be a most prudent choice over bitmaps or 3rd party rendering libraries.</p>
<p>You can view the complete code from this post from <a href="https://gist.github.com/twocentstudios/6deb870942ce0b69816c7550c73a3a14">this gist</a>.</p>
<h1 id="Contents">Contents</h1>
<ul>
<li>Basic shapes</li>
<li>Styling</li>
<li>Drawing line-by-line</li>
<li>How to use arcs</li>
<li>How to use quadratic bezier curves</li>
<li>Path operations</li>
<li>Creating a chat bubble shape</li>
<li>Trimming a path</li>
<li>Transition animations</li>
</ul>
<h1 id="Basic-shapes">Basic shapes</h1>
<p>SwiftUI has a protocol <code>Shape</code> â both conforming to, and conceptually similar to <code>View</code> â that we can use to draw custom shapes.</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">protocol</span> <span class="synIdentifier">Shape</span> <span class="synSpecial">:</span> <span class="synType">Animatable</span>, View
</pre>
<p>It has one requirement: a function that takes a <code>CGRect</code> and returns a <code>Path</code>.</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">import</span> SwiftUI
<span class="synPreProc">struct</span> <span class="synIdentifier">MyCustomShape</span><span class="synSpecial">:</span> <span class="synType">Shape</span> {
<span class="synPreProc">func</span> <span class="synIdentifier">path</span>(<span class="synStatement">in</span> rect<span class="synSpecial">:</span> <span class="synType">CGRect</span>) <span class="synSpecial">-></span> <span class="synType">Path</span> {
<span class="synComment">/// </span><span class="synTodo">TODO</span><span class="synComment">: return a `Path`</span>
}
}
</pre>
<p>As we'll see later, the input <code>rect</code> is determined by SwiftUI's <a href="https://kean.blog/post/swiftui-layout-system#layout-process">layout system rules</a>, but the path we return can draw anywhere, including outside the bounds of <code>rect</code>.</p>
<p><code>Path</code> is SwiftUI's drawing command primitive, while UIKit has <code>UIBezierPath</code> and CoreGraphics has <code>CGPath</code>. All are similar, but not quite the same.</p>
<p>Let's use SwiftUI's <code>Path</code> primitives to make a simple rounded rectangle.</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">RoundedRectShape</span><span class="synSpecial">:</span> <span class="synType">Shape</span> {
<span class="synPreProc">func</span> <span class="synIdentifier">path</span>(<span class="synStatement">in</span> rect<span class="synSpecial">:</span> <span class="synType">CGRect</span>) <span class="synSpecial">-></span> <span class="synType">Path</span> {
Path(roundedRect<span class="synSpecial">:</span> <span class="synType">rect</span>, cornerRadius<span class="synSpecial">:</span> <span class="synConstant">20</span>)
}
}
</pre>
<p>Of course this is the same as SwiftUI's built-in shape:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink>RoundedRectangle(cornerRadius<span class="synSpecial">:</span> <span class="synConstant">20</span>)
</pre>
<p>A <code>Shape</code> has only a "<a href="https://developer.apple.com/documentation/swiftui/shape">default fill</a> based on the foreground color", so let's add a SwiftUI Preview for it.</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">RoundedRectView_Previews</span><span class="synSpecial">:</span> <span class="synType">PreviewProvider</span> {
<span class="synStatement">static</span> <span class="synPreProc">var</span> <span class="synIdentifier">previews</span><span class="synSpecial">:</span> <span class="synType">some</span> View {
RoundedRectShape()
.fill(.gray)
.frame(width<span class="synSpecial">:</span> <span class="synConstant">200</span>, height<span class="synSpecial">:</span> <span class="synConstant">150</span>)
.padding(<span class="synConstant">50</span>)
.previewLayout(.sizeThatFits)
}
}
</pre>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620182416.png" width="654" height="554" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Why not make a custom view modifier for making previewing more convenient:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">extension</span> <span class="synIdentifier">View</span> {
<span class="synType">@ViewBuilder</span> <span class="synType">func</span> previewingShape() <span class="synSpecial">-></span> <span class="synType">some</span> View {
frame(width<span class="synSpecial">:</span> <span class="synConstant">200</span>, height<span class="synSpecial">:</span> <span class="synConstant">150</span>)
.padding(<span class="synConstant">50</span>)
.previewLayout(.sizeThatFits)
}
}
<span class="synPreProc">struct</span> <span class="synIdentifier">RoundedRectView_Previews</span><span class="synSpecial">:</span> <span class="synType">PreviewProvider</span> {
<span class="synStatement">static</span> <span class="synPreProc">var</span> <span class="synIdentifier">previews</span><span class="synSpecial">:</span> <span class="synType">some</span> View {
RoundedRectShape()
.fill(.gray)
.previewingShape()
}
}
</pre>
<h1 id="Styling">Styling</h1>
<p>Before we get too far into the weeds with <code>Path</code>, we should take a look at basic <code>Shape</code> styling. Otherwise, how will we be able to see what we're drawing?</p>
<p>We can either stroke <em>or</em> fill a shape instance, but not both. This is because <code>.stroke</code> and <code>.fill</code> are both defined on <code>Shape</code> but return a <code>View</code>.</p>
<pre class="code lang-swift" data-lang="swift" data-unlink>RoundedRectShape()
.fill(.gray)
</pre>
<pre class="code lang-swift" data-lang="swift" data-unlink>RoundedRectShape()
.stroke(.gray)
</pre>
<pre class="code lang-swift" data-lang="swift" data-unlink>RoundedRectShape()
.stroke(.gray)
.fill(.gray) <span class="synComment">// Error: Value of type 'some View' has no member 'fill'</span>
</pre>
<table>
<thead>
<tr>
<th>Fill</th>
<th>Stroke</th>
</tr>
</thead>
<tbody>
<tr>
<td><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620182416.png" width="654" height="554" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></td>
<td><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620182842.png" width="658" height="552" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></td>
</tr>
</tbody>
</table>
<p><a href="https://www.hackingwithswift.com/quick-start/swiftui/how-to-fill-and-stroke-shapes-at-the-same-time">To do both</a>, we need to layer two separate instances of the shape:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink>ZStack {
RoundedRectShape()
.fill(.gray)
RoundedRectShape()
.stroke(Color.black, lineWidth<span class="synSpecial">:</span> <span class="synConstant">4</span>)
}
</pre>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620182909.png" width="652" height="554" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h1 id="Drawing-line-by-line">Drawing line-by-line</h1>
<p>We draw a path line-by-line or curve-by-curve as if we were describing pen strokes to a friend.</p>
<ul>
<li><code>move(to:)</code> moves the "cursor" without drawing.</li>
<li><code>addLine(to:)</code> draws a line from current "cursor" to the the <code>to</code> point.</li>
<li><code>closeSubpath()</code> marks the subpath as closed by drawing a line from the "cursor" back to the start point if necessary.</li>
</ul>
<blockquote><p><strong>Note</strong>: it's required to call <code>move(to:)</code> before adding a line or curve. Otherwise the path will not appear. When adding a complete subpath like <code>addEllipse(in:)</code>, <code>move(to:)</code> is <em>not</em> required.</p></blockquote>
<p>Let's draw a banner shape, starting from the bottom left corner:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">BannerShape</span><span class="synSpecial">:</span> <span class="synType">Shape</span> {
<span class="synPreProc">func</span> <span class="synIdentifier">path</span>(<span class="synStatement">in</span> rect<span class="synSpecial">:</span> <span class="synType">CGRect</span>) <span class="synSpecial">-></span> <span class="synType">Path</span> {
<span class="synStatement">return</span> Path { p <span class="synStatement">in</span>
p.move(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.minX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span>))
p.addLine(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.minX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.minY</span>))
p.addLine(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.maxX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.midY</span>))
p.closeSubpath()
}
}
}
</pre>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620182953.png" width="658" height="462" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Since we're using the <code>rect</code> parameter to specify our drawing points, the shape will always be <em>relative</em> to the size of the view.</p>
<p>We could also specify <code>absolute</code> coordinates:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">BannerAbsoluteShape</span><span class="synSpecial">:</span> <span class="synType">Shape</span> {
<span class="synPreProc">func</span> <span class="synIdentifier">path</span>(<span class="synStatement">in</span> rect<span class="synSpecial">:</span> <span class="synType">CGRect</span>) <span class="synSpecial">-></span> <span class="synType">Path</span> {
<span class="synStatement">return</span> Path { p <span class="synStatement">in</span>
p.move(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synConstant">10</span>, y<span class="synSpecial">:</span> <span class="synConstant">50</span>))
p.addLine(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synConstant">10</span>, y<span class="synSpecial">:</span> <span class="synConstant">10</span>))
p.addLine(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synConstant">100</span>, y<span class="synSpecial">:</span> <span class="synConstant">30</span>))
p.closeSubpath()
}
}
}
</pre>
<p>And you can see from the lighter gray background color I've added to the view that the path that defines the shape no longer fills it.</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183015.png" width="662" height="456" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h1 id="How-to-use-arcs">How to use arcs</h1>
<p>There are three APIs for drawing an arc:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">/// Adds an arc of a circle to the path, specified with a radius and a</span>
<span class="synComment">/// difference in angle.</span>
<span class="synStatement">public</span> <span class="synStatement">mutating</span> <span class="synPreProc">func</span> <span class="synIdentifier">addRelativeArc</span>(center<span class="synSpecial">:</span> <span class="synType">CGPoint</span>, radius<span class="synSpecial">:</span> <span class="synType">CGFloat</span>, startAngle<span class="synSpecial">:</span> <span class="synType">Angle</span>, delta<span class="synSpecial">:</span> <span class="synType">Angle</span>, transform<span class="synSpecial">:</span> <span class="synType">CGAffineTransform</span> <span class="synIdentifier">=</span> .identity)
<span class="synComment">/// Adds an arc of a circle to the path, specified with a radius and angles.</span>
<span class="synStatement">public</span> <span class="synStatement">mutating</span> <span class="synPreProc">func</span> <span class="synIdentifier">addArc</span>(center<span class="synSpecial">:</span> <span class="synType">CGPoint</span>, radius<span class="synSpecial">:</span> <span class="synType">CGFloat</span>, startAngle<span class="synSpecial">:</span> <span class="synType">Angle</span>, endAngle<span class="synSpecial">:</span> <span class="synType">Angle</span>, clockwise<span class="synSpecial">:</span> <span class="synType">Bool</span>, transform<span class="synSpecial">:</span> <span class="synType">CGAffineTransform</span> <span class="synIdentifier">=</span> .identity)
<span class="synComment">/// Adds an arc of a circle to the path, specified with a radius and two</span>
<span class="synComment">/// tangent lines.</span>
<span class="synStatement">public</span> <span class="synStatement">mutating</span> <span class="synPreProc">func</span> <span class="synIdentifier">addArc</span>(tangent1End p1<span class="synSpecial">:</span> <span class="synType">CGPoint</span>, tangent2End p2<span class="synSpecial">:</span> <span class="synType">CGPoint</span>, radius<span class="synSpecial">:</span> <span class="synType">CGFloat</span>, transform<span class="synSpecial">:</span> <span class="synType">CGAffineTransform</span> <span class="synIdentifier">=</span> .identity)
</pre>
<p>The first two add a new subpath disconnected from the current path.</p>
<p>The last one â using tangents â adds an arc connected to the current subpath. We can use this API to add an arc to a line-by-line drawing session like the banner above.</p>
<p>Let's create the rounded rectangle shape with only the <code>addLine</code> and <code>addArc</code> primitives. It should take a corner radius as a parameter and draw inside the provided bounds rectangle.</p>
<p>First, we'll visualize what we want to draw. The black-outlined rectangle is a representation of our input rectangle and the gray-filled shape is the target shape we want to draw.</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183045.png" width="1200" height="697" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>The corner radius <code>r</code> can be visualized as a square, situated at each corner of the bounds rectangle, with side <code>r</code>.</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183107.png" width="321" height="303" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Looking at the <a href="https://developer.apple.com/documentation/swiftui/path/addarc(tangent1end:tangent2end:radius:transform:"><code>addArc</code></a>) function again:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">func</span> <span class="synIdentifier">addArc</span>(tangent1End p1<span class="synSpecial">:</span> <span class="synType">CGPoint</span>, tangent2End p2<span class="synSpecial">:</span> <span class="synType">CGPoint</span>, radius<span class="synSpecial">:</span> <span class="synType">CGFloat</span>)
</pre>
<p>We need to assemble 4 parameters:</p>
<ol>
<li><code>startPoint</code> (implicit; this is where the "cursor" is)</li>
<li><code>tangent1End</code></li>
<li><code>tangent2End</code></li>
<li><code>radius</code></li>
</ol>
<p>We only know (4) <code>radius</code>.</p>
<p>Despite the potentially ð¤ names, the tangents correspond to the following points on the aforementioned square:</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183123.png" width="321" height="318" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Zooming out to the whole rectangle, if we decide to draw clockwise, that means we'll have 4 arcs with the following points:</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183134.png" width="1200" height="671" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Let's alternate drawing lines and arcs, clockwise, and in the following order:</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183147.png" width="1200" height="671" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>We want the points to be drawn relative to the bounds rectangle. We can use the following helper functions on <code>CGRect</code> to derive the corner points we need:</p>
<ul>
<li><code>CGRect.minX</code></li>
<li><code>CGRect.maxX</code></li>
<li><code>CGRect.minY</code></li>
<li><code>CGRect.maxY</code></li>
<li><code>CGRect.midX</code> (also useful)</li>
<li><code>CGRect.midY</code> (also useful)</li>
</ul>
<blockquote><p>If you mix up these helpers while writing drawing code, you're in good company.</p></blockquote>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183201.png" width="1200" height="671" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>We derive the non-corner points by adding or subtracting the corner radius.</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183216.png" width="1200" height="294" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>With all the details worked out, all we have to do is arrange the code:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">RoundedRectArcUnsafeShape</span><span class="synSpecial">:</span> <span class="synType">Shape</span> {
<span class="synPreProc">let</span> <span class="synIdentifier">cornerRadius</span><span class="synSpecial">:</span> <span class="synType">CGFloat</span>
<span class="synPreProc">func</span> <span class="synIdentifier">path</span>(<span class="synStatement">in</span> rect<span class="synSpecial">:</span> <span class="synType">CGRect</span>) <span class="synSpecial">-></span> <span class="synType">Path</span> {
Path { p <span class="synStatement">in</span>
p.move(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.minX</span> <span class="synIdentifier">+</span> cornerRadius, y<span class="synSpecial">:</span> <span class="synType">rect.minY</span>))
p.addLine(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.maxX</span> <span class="synIdentifier">-</span> cornerRadius, y<span class="synSpecial">:</span> <span class="synType">rect.minY</span>))
p.addArc(
tangent1End<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.maxX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.minY</span>),
tangent2End<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.maxX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.minY</span> <span class="synIdentifier">+</span> cornerRadius),
radius<span class="synSpecial">:</span> <span class="synType">cornerRadius</span>
)
p.addLine(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.maxX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span> <span class="synIdentifier">-</span> cornerRadius))
p.addArc(
tangent1End<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.maxX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span>),
tangent2End<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.maxX</span> <span class="synIdentifier">-</span> cornerRadius, y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span>),
radius<span class="synSpecial">:</span> <span class="synType">cornerRadius</span>
)
p.addLine(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.minX</span> <span class="synIdentifier">+</span> cornerRadius, y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span>))
p.addArc(
tangent1End<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.minX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span>),
tangent2End<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.minX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span> <span class="synIdentifier">-</span> cornerRadius),
radius<span class="synSpecial">:</span> <span class="synType">cornerRadius</span>
)
p.addLine(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.minX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.minY</span> <span class="synIdentifier">+</span> cornerRadius))
p.addArc(
tangent1End<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.minX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.minY</span>),
tangent2End<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.minX</span> <span class="synIdentifier">+</span> cornerRadius, y<span class="synSpecial">:</span> <span class="synType">rect.minY</span>),
radius<span class="synSpecial">:</span> <span class="synType">cornerRadius</span>
)
p.closeSubpath()
}
}
}
</pre>
<p>If we overlay SwiftUI's build-in <code>RoundedRectangle</code> shape, ours looks pretty good:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">RoundedRectArcUnsafeView_Previews</span><span class="synSpecial">:</span> <span class="synType">PreviewProvider</span> {
<span class="synStatement">static</span> <span class="synPreProc">var</span> <span class="synIdentifier">previews</span><span class="synSpecial">:</span> <span class="synType">some</span> View {
<span class="synPreProc">let</span> <span class="synIdentifier">cornerRadius</span><span class="synSpecial">:</span> <span class="synType">CGFloat</span> <span class="synIdentifier">=</span> <span class="synConstant">20</span>
ZStack {
RoundedRectArcUnsafeShape(cornerRadius<span class="synSpecial">:</span> <span class="synType">cornerRadius</span>)
.stroke(.gray, lineWidth<span class="synSpecial">:</span> <span class="synConstant">9</span>)
RoundedRectangle(cornerRadius<span class="synSpecial">:</span> <span class="synType">cornerRadius</span>, style<span class="synSpecial">:</span> .circular)
.stroke(.red, lineWidth<span class="synSpecial">:</span> <span class="synConstant">1</span>)
}
.previewingShape()
}
}
</pre>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183240.png" width="888" height="608" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>But what happens if we make <code>cornerRadius</code> something like <code>100</code> (when our shape height is <code>100</code>)?</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183254.png" width="884" height="608" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Looks like SwiftUI's version does some bounds checking so the shape becomes a capsule or circle. Let's fix our implementation:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">RoundedRectArcShape</span><span class="synSpecial">:</span> <span class="synType">Shape</span> {
<span class="synPreProc">let</span> <span class="synIdentifier">cornerRadius</span><span class="synSpecial">:</span> <span class="synType">CGFloat</span>
<span class="synPreProc">func</span> <span class="synIdentifier">path</span>(<span class="synStatement">in</span> rect<span class="synSpecial">:</span> <span class="synType">CGRect</span>) <span class="synSpecial">-></span> <span class="synType">Path</span> {
<span class="synPreProc">let</span> <span class="synIdentifier">maxBoundedCornerRadius</span> <span class="synIdentifier">=</span> min(min(cornerRadius, rect.width <span class="synIdentifier">/</span> <span class="synConstant">2.0</span>), rect.height <span class="synIdentifier">/</span> <span class="synConstant">2.0</span>)
<span class="synPreProc">let</span> <span class="synIdentifier">minBoundedCornerRadius</span> <span class="synIdentifier">=</span> max(maxBoundedCornerRadius, <span class="synConstant">0.0</span>)
<span class="synPreProc">let</span> <span class="synIdentifier">boundedCornerRadius</span> <span class="synIdentifier">=</span> minBoundedCornerRadius
<span class="synStatement">return</span> Path { p <span class="synStatement">in</span>
p.move(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.minX</span> <span class="synIdentifier">+</span> boundedCornerRadius, y<span class="synSpecial">:</span> <span class="synType">rect.minY</span>))
<span class="synComment">// ...</span>
}
}
}
</pre>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183308.png" width="890" height="616" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>That's better. As a bonus, I'm showing SwiftUI's <code>.continuous</code> corner style in blue over the <code>.circular</code> style in red.</p>
<h1 id="How-to-use-quadratic-bezier-curves">How to use quadratic bezier curves</h1>
<p>We often want to avoid the kinds of sharp corners that appear when connecting <code>line</code>s, but don't necessarily want to use circular <code>arc</code>s.</p>
<p>For smoother lines, the <code>Path</code> API gives us:</p>
<ul>
<li><code>addCurve</code> for cubic Bézier curves</li>
<li><code>addQuadCurve</code> for quadratic Bézier curves</li>
</ul>
<p>Cubic Bézier curves give us a lot of flexibility. They can also be a <a href="https://pomax.github.io/bezierinfo/">weighty</a> <a href="https://en.wikipedia.org/wiki/B%C3%A9zier_curve">topic</a>. I recommend <a href="https://www.youtube.com/watch?v=aVwxzDHniEw">this YouTube video</a> by Freya Holmér.</p>
<p>I've found quadratic Bézier curves as a nice compromise between flexibility and complexity, so let's try to quickly build some intuition on how to use them.</p>
<p>Let's start by looking at the <a href="https://developer.apple.com/documentation/swiftui/path/addquadcurve(to:control:"><code>addQuadCurve</code></a>) function:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">func</span> <span class="synIdentifier">addQuadCurve</span>(to p<span class="synSpecial">:</span> <span class="synType">CGPoint</span>, control cp<span class="synSpecial">:</span> <span class="synType">CGPoint</span>)
</pre>
<p>We need to assemble 3 parameters:</p>
<ol>
<li><code>startPoint</code> (implicit; this is where the "cursor" is)</li>
<li><code>endPoint</code> (<code>p</code>)</li>
<li><code>controlPoint</code> (<code>cp</code>)</li>
</ol>
<p>When we set up the three points as various triangles, we can see that the curve is stretched towards the control point.</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183332.png" width="1200" height="579" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Calculating the actual positions of the three points will depend on our use case.</p>
<p>Let's say we want to draw a simple quad curve as a "scoop" with the control point at the bottom. But we'll allow the caller to specify a relative position on the x-axis for the control point.</p>
<p>Add the input rectangle to our planning diagram will help us determine how to calculate each of the three points:</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183344.png" width="1200" height="579" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>With that, here's the code:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">QuadCurveScoop</span><span class="synSpecial">:</span> <span class="synType">Shape</span> {
<span class="synComment">/// 0...1</span>
<span class="synPreProc">var</span> <span class="synIdentifier">pointOffsetFraction</span><span class="synSpecial">:</span> <span class="synType">CGFloat</span> <span class="synIdentifier">=</span> <span class="synConstant">0.0</span>
<span class="synPreProc">func</span> <span class="synIdentifier">path</span>(<span class="synStatement">in</span> rect<span class="synSpecial">:</span> <span class="synType">CGRect</span>) <span class="synSpecial">-></span> <span class="synType">Path</span> {
Path { p <span class="synStatement">in</span>
p.move(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.minX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.minY</span>))
p.addQuadCurve(
to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.maxX</span>, y<span class="synSpecial">:</span> <span class="synType">rect.minY</span>),
control<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">rect.maxX</span> <span class="synIdentifier">*</span> pointOffsetFraction, y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span>)
)
}
}
}
</pre>
<blockquote><p>If we don't explicitly close the subpath, SwiftUI presumably closes it for us when drawing.</p></blockquote>
<p>I've set up the preview to mimic the figure above, and I've added an overlay to show the input rectangle and approximate control point for each curve.</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183358.png" width="882" height="884" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h1 id="Path-operations">Path operations</h1>
<p>Path operations look like set operations: <code>union</code>, <code>intersection</code>, <code>subtracting</code>, etc.</p>
<p>These operations allow us to combine subpaths in unique ways, without necessarily needing to draw line-by-line or arc-by-arc.</p>
<p>Let's try making a cloud shape by adding together 3 ellipses:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">Cloud1Shape</span><span class="synSpecial">:</span> <span class="synType">Shape</span> {
<span class="synPreProc">func</span> <span class="synIdentifier">path</span>(<span class="synStatement">in</span> rect<span class="synSpecial">:</span> <span class="synType">CGRect</span>) <span class="synSpecial">-></span> <span class="synType">Path</span> {
<span class="synPreProc">let</span> <span class="synIdentifier">inset</span> <span class="synIdentifier">=</span> rect.width <span class="synIdentifier">/</span> <span class="synConstant">2.0</span>
<span class="synStatement">return</span> Path { p <span class="synStatement">in</span>
p.addEllipse(<span class="synStatement">in</span><span class="synSpecial">:</span> <span class="synType">rect.inset</span>(by<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(top<span class="synSpecial">:</span> <span class="synConstant">0</span>, left<span class="synSpecial">:</span> <span class="synConstant">0</span>, bottom<span class="synSpecial">:</span> <span class="synConstant">0</span>, right<span class="synSpecial">:</span> <span class="synType">inset</span>)))
p.addEllipse(<span class="synStatement">in</span><span class="synSpecial">:</span> <span class="synType">rect.inset</span>(by<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(top<span class="synSpecial">:</span> <span class="synConstant">0</span>, left<span class="synSpecial">:</span> <span class="synType">inset</span> <span class="synIdentifier">/</span> <span class="synConstant">2.0</span>, bottom<span class="synSpecial">:</span> <span class="synConstant">0</span>, right<span class="synSpecial">:</span> <span class="synType">inset</span> <span class="synIdentifier">/</span> <span class="synConstant">2.0</span>)))
p.addEllipse(<span class="synStatement">in</span><span class="synSpecial">:</span> <span class="synType">rect.inset</span>(by<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(top<span class="synSpecial">:</span> <span class="synConstant">0</span>, left<span class="synSpecial">:</span> <span class="synType">inset</span>, bottom<span class="synSpecial">:</span> <span class="synConstant">0</span>, right<span class="synSpecial">:</span> <span class="synConstant">0</span>)))
}
}
}
</pre>
<p>When we fill it, it looks fine:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">Cloud1View_Previews</span><span class="synSpecial">:</span> <span class="synType">PreviewProvider</span> {
<span class="synStatement">static</span> <span class="synPreProc">var</span> <span class="synIdentifier">previews</span><span class="synSpecial">:</span> <span class="synType">some</span> View {
Cloud1Shape()
.fill(.gray)
.previewingShape()
}
}
</pre>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183416.png" width="658" height="456" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>But if we decide to draw an outline instead, it looks like 3 ellipses:</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183426.png" width="656" height="454" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>We can fix this by joining the shapes together using the <a href="https://developer.apple.com/documentation/coregraphics/cgpath/3994971-union"><code>union</code></a> path operation.</p>
<p>The path operation APIs are available on iOS 16+. Unfortunately, they're defined on <code>CGPath</code> and not <code>Path</code>. It's simple to convert between them, but we'll have to rewrite our path drawing code.</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">Cloud2Shape</span><span class="synSpecial">:</span> <span class="synType">Shape</span> {
<span class="synPreProc">func</span> <span class="synIdentifier">path</span>(<span class="synStatement">in</span> rect<span class="synSpecial">:</span> <span class="synType">CGRect</span>) <span class="synSpecial">-></span> <span class="synType">Path</span> {
<span class="synPreProc">let</span> <span class="synIdentifier">inset</span> <span class="synIdentifier">=</span> rect.width <span class="synIdentifier">/</span> <span class="synConstant">2.0</span>
<span class="synPreProc">let</span> <span class="synIdentifier">leftEllipse</span> <span class="synIdentifier">=</span> Path(ellipseIn<span class="synSpecial">:</span> <span class="synType">rect.inset</span>(by<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(top<span class="synSpecial">:</span> <span class="synConstant">0</span>, left<span class="synSpecial">:</span> <span class="synConstant">0</span>, bottom<span class="synSpecial">:</span> <span class="synConstant">0</span>, right<span class="synSpecial">:</span> <span class="synType">inset</span>)))
<span class="synPreProc">let</span> <span class="synIdentifier">centerEllipse</span> <span class="synIdentifier">=</span> Path(ellipseIn<span class="synSpecial">:</span> <span class="synType">rect.inset</span>(by<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(top<span class="synSpecial">:</span> <span class="synConstant">0</span>, left<span class="synSpecial">:</span> <span class="synType">inset</span> <span class="synIdentifier">/</span> <span class="synConstant">2.0</span>, bottom<span class="synSpecial">:</span> <span class="synConstant">0</span>, right<span class="synSpecial">:</span> <span class="synType">inset</span> <span class="synIdentifier">/</span> <span class="synConstant">2.0</span>)))
<span class="synPreProc">let</span> <span class="synIdentifier">rightEllipse</span> <span class="synIdentifier">=</span> Path(ellipseIn<span class="synSpecial">:</span> <span class="synType">rect.inset</span>(by<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(top<span class="synSpecial">:</span> <span class="synConstant">0</span>, left<span class="synSpecial">:</span> <span class="synType">inset</span>, bottom<span class="synSpecial">:</span> <span class="synConstant">0</span>, right<span class="synSpecial">:</span> <span class="synConstant">0</span>)))
<span class="synPreProc">let</span> <span class="synIdentifier">combinedCGPath</span> <span class="synIdentifier">=</span> leftEllipse.cgPath
.union(centerEllipse.cgPath)
.union(rightEllipse.cgPath)
<span class="synStatement">return</span> Path(combinedCGPath)
}
}
</pre>
<p>Now when we outline the shape, we get a cloud again.</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183437.png" width="648" height="446" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h1 id="Creating-a-chat-bubble-shape">Creating a chat bubble shape</h1>
<p>I used the above techniques to create a chat bubble shape for the onboarding section of the recently decommissioned <a href="https://note.com/tabedori/">Tabedori ãã¹ããª</a> app.</p>
<p>The arrow position on the bottom can be adjusted by providing <code>arrowOffsetFraction</code>.</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">MapOnboardingBubbleShape</span><span class="synSpecial">:</span> <span class="synType">Shape</span> {
<span class="synPreProc">var</span> <span class="synIdentifier">cornerRadius</span><span class="synSpecial">:</span> <span class="synType">CGFloat</span> <span class="synIdentifier">=</span> <span class="synConstant">12</span>
<span class="synPreProc">var</span> <span class="synIdentifier">arrowRectSize</span><span class="synSpecial">:</span> <span class="synType">CGFloat</span> <span class="synIdentifier">=</span> <span class="synConstant">20</span>
<span class="synPreProc">var</span> <span class="synIdentifier">arcLength</span><span class="synSpecial">:</span> <span class="synType">CGFloat</span> <span class="synIdentifier">=</span> <span class="synConstant">12</span>
<span class="synComment">/// 0.0 = left, 0.5 = center, 1.0 = right</span>
<span class="synPreProc">var</span> <span class="synIdentifier">arrowOffsetFraction</span><span class="synSpecial">:</span> <span class="synType">CGFloat</span> <span class="synIdentifier">=</span> <span class="synConstant">0.5</span>
<span class="synPreProc">func</span> <span class="synIdentifier">baseXPos</span>(<span class="synStatement">for</span> rect<span class="synSpecial">:</span> <span class="synType">CGRect</span>) <span class="synSpecial">-></span> <span class="synType">CGFloat</span> {
(rect.maxX <span class="synIdentifier">-</span> cornerRadius <span class="synIdentifier">-</span> cornerRadius <span class="synIdentifier">-</span> arrowRectSize) <span class="synIdentifier">*</span> arrowOffsetFraction <span class="synIdentifier">+</span> cornerRadius
}
<span class="synPreProc">func</span> <span class="synIdentifier">path</span>(<span class="synStatement">in</span> rect<span class="synSpecial">:</span> <span class="synType">CGRect</span>) <span class="synSpecial">-></span> <span class="synType">Path</span> {
<span class="synPreProc">let</span> <span class="synIdentifier">roundedRect</span> <span class="synIdentifier">=</span> Path(roundedRect<span class="synSpecial">:</span> <span class="synType">rect</span>, cornerRadius<span class="synSpecial">:</span> <span class="synType">cornerRadius</span>)
<span class="synPreProc">let</span> <span class="synIdentifier">arrowPath</span> <span class="synIdentifier">=</span> Path { p <span class="synStatement">in</span>
p.move(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">baseXPos</span>(<span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">rect</span>), y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span>))
p.addLine(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(
x<span class="synSpecial">:</span> <span class="synType">baseXPos</span>(<span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">rect</span>) <span class="synIdentifier">+</span> arrowRectSize <span class="synIdentifier">-</span> arcLength,
y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span> <span class="synIdentifier">+</span> arrowRectSize <span class="synIdentifier">-</span> arcLength
))
p.addQuadCurve(
to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(
x<span class="synSpecial">:</span> <span class="synType">baseXPos</span>(<span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">rect</span>) <span class="synIdentifier">+</span> arrowRectSize,
y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span> <span class="synIdentifier">+</span> arrowRectSize <span class="synIdentifier">-</span> arcLength
),
control<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(
x<span class="synSpecial">:</span> <span class="synType">baseXPos</span>(<span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">rect</span>) <span class="synIdentifier">+</span> arrowRectSize,
y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span> <span class="synIdentifier">+</span> arrowRectSize
)
)
p.addLine(to<span class="synSpecial">:</span> .<span class="synIdentifier">init</span>(x<span class="synSpecial">:</span> <span class="synType">baseXPos</span>(<span class="synStatement">for</span><span class="synSpecial">:</span> <span class="synType">rect</span>) <span class="synIdentifier">+</span> arrowRectSize, y<span class="synSpecial">:</span> <span class="synType">rect.maxY</span>))
p.closeSubpath()
}
<span class="synPreProc">let</span> <span class="synIdentifier">combinedCGPath</span> <span class="synIdentifier">=</span> roundedRect.cgPath.union(arrowPath.cgPath)
<span class="synPreProc">let</span> <span class="synIdentifier">combinedPath</span> <span class="synIdentifier">=</span> Path(combinedCGPath)
<span class="synStatement">return</span> combinedPath
}
}
</pre>
<p>The <code>arrowOffsetFraction</code> is the text inside the bubble.</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183452.png" width="676" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<p>Here's a screenshot of it in context:</p>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183506.jpg" width="553" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h1 id="Trimming-a-path">Trimming a path</h1>
<p>Animating the path is something that can't be done (easily) with a single static image, but is easy to do with a <code>Shape</code>.</p>
<p>The <code>trim</code> modifier on <code>Shape</code> allows you to draw only a variable fraction of the path.</p>
<p>Since SwiftUI is adept at many kinds of animations, we can use it to animate the path being drawn:</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">DrawBubbleView</span><span class="synSpecial">:</span> <span class="synType">View</span> {
<span class="synType">@State</span> <span class="synType">var</span> drawFraction<span class="synSpecial">:</span> <span class="synType">CGFloat</span> <span class="synIdentifier">=</span> <span class="synConstant">0</span>
<span class="synPreProc">var</span> <span class="synIdentifier">body</span><span class="synSpecial">:</span> <span class="synType">some</span> View {
VStack {
MapOnboardingBubbleShape()
.trim(from<span class="synSpecial">:</span> <span class="synConstant">0</span>, to<span class="synSpecial">:</span> <span class="synType">drawFraction</span>)
.stroke(.gray, lineWidth<span class="synSpecial">:</span> <span class="synConstant">3</span>)
.animation(.spring(), value<span class="synSpecial">:</span> <span class="synType">drawFraction</span>)
.frame(width<span class="synSpecial">:</span> <span class="synConstant">150</span>, height<span class="synSpecial">:</span> <span class="synConstant">100</span>)
.padding(.bottom, <span class="synConstant">50</span>)
Button(drawFraction <span class="synIdentifier">></span> <span class="synConstant">0.0</span> ? <span class="synConstant">"Hide"</span> <span class="synSpecial">:</span> <span class="synConstant">"Show"</span>) {
drawFraction <span class="synIdentifier">=</span> drawFraction <span class="synIdentifier">></span> <span class="synConstant">0.0</span> ? <span class="synConstant">0.0</span> <span class="synSpecial">:</span> <span class="synConstant">1.0</span>
}
.tint(Color.gray)
}
}
}
</pre>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183524.gif" width="628" height="776" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h1 id="Transition-animations">Transition animations</h1>
<p>And finally, since <code>Shape</code>s have appearance/disappearance transitions like any other <code>View</code>, we can add a fun springy insertion animation.</p>
<pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">BubbleTransitionView</span><span class="synSpecial">:</span> <span class="synType">View</span> {
<span class="synType">@State</span> <span class="synType">var</span> isVisible<span class="synSpecial">:</span> <span class="synType">Bool</span> <span class="synIdentifier">=</span> <span class="synConstant">false</span>
<span class="synPreProc">var</span> <span class="synIdentifier">body</span><span class="synSpecial">:</span> <span class="synType">some</span> View {
VStack {
ZStack {
<span class="synStatement">if</span> isVisible {
Text(<span class="synConstant">"Hello!"</span>)
.padding(<span class="synConstant">30</span>)
.background {
MapOnboardingBubbleShape().fill(Color(.systemGray5))
}
.transition(.opacity.combined(with<span class="synSpecial">:</span> .scale).animation(.spring(response<span class="synSpecial">:</span> <span class="synConstant">0.25</span>, dampingFraction<span class="synSpecial">:</span> <span class="synConstant">0.7</span>)))
}
}
.frame(width<span class="synSpecial">:</span> <span class="synConstant">200</span>, height<span class="synSpecial">:</span> <span class="synConstant">100</span>)
.padding(.bottom, <span class="synConstant">50</span>)
Button(isVisible ? <span class="synConstant">"Hide"</span> <span class="synSpecial">:</span> <span class="synConstant">"Show"</span>) {
isVisible.toggle()
}
}
}
}
</pre>
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/christopher-trott/20230620/20230620183543.gif" width="628" height="776" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p>
<h1 id="Conclusion">Conclusion</h1>
<p>Thanks for reading! I hope this post has led you on a <em>path</em> of enlightenment.</p>
christopher-trott