<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kan Ouivirach</title>
    <description>The latest articles on DEV Community by Kan Ouivirach (@zkan).</description>
    <link>https://dev.to/zkan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F56459%2F8d14a162-b9ca-4d7e-b0d4-5e413d486802.jpg</url>
      <title>DEV Community: Kan Ouivirach</title>
      <link>https://dev.to/zkan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zkan"/>
    <language>en</language>
    <item>
      <title>ใช้ Papermill ทำ Automation กับ Jupyter Notebooks</title>
      <dc:creator>Kan Ouivirach</dc:creator>
      <pubDate>Mon, 07 Sep 2020 08:53:40 +0000</pubDate>
      <link>https://dev.to/dataength/papermill-automation-jupyter-notebooks-n91</link>
      <guid>https://dev.to/dataength/papermill-automation-jupyter-notebooks-n91</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;ในยุคนี้เวลาที่ชาว Data Scientist อยากจะสื่อสารผลที่ได้จากการวิเคราะห์ข้อมูลกับฝั่ง Business หรืออยากที่จะสร้างโมเดลทำนายผลทางธุรกิจสักอย่างหนึ่ง &lt;a href="https://jupyter.org/"&gt;Jupyter Notebooks&lt;/a&gt; จะเป็นอาวุธหรือเครื่องมือสำคัญที่ส่วนใหญ่เราจะหยิบเอามาใช้กัน ซึ่งข้อดีของเครื่องมือตัวนี้ก็คือมันสามารถเป็นทั้ง document เป็น live code รวมไปถึง visualization ภายในตัว&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;แต่อย่างไรก็ดี เครื่องมือตัวนี้ยังมีข้อจำกัดอยู่ที่ว่าเวลาที่เรา&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;อยากจะเปลี่ยนแปลงค่า input บางค่า หรือว่ามีส่วนของโค้ดที่เราอยากไปดึงข้อมูลหลังจากจบเดือนนี้ หรือดึงโค้ดในวันสิ้นปี เราต้องเปิด notebook นั้นๆ ขึ้นมาในวันนั้นๆ แล้วสั่ง execute แต่ละ cell เอง&lt;/li&gt;
&lt;li&gt;อยากจะใช้ค่าจากผลการทดลองที่อยู่ใน notebook ของเพื่อนร่วมทีม สิ่งที่เราต้องทำก็คือไปเปิด notebook ของเพื่อน นั่งไล่ execute แต่ละ cell แล้วก็อปปี้ผลที่ได้มาแปะใน notebook ของเราเพื่อนำค่านั้นๆ ไปใช้งานต่อ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ทำแค่ครั้งสองครั้งน่าจะยังสบายๆ อยู่ แต่เมื่อไหร่ก็ตามที่เราต้องทำแบบนี้ไปเรื่อยๆ ทุกๆ อาทิตย์ หรือบ่อยกว่านั้น คงจะไม่สนุกแน่ เราควรเอาเวลาไปใช้กับสิ่งที่มีคุณค่ามากกว่านี้ดีกว่า&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Papermill
&lt;/h2&gt;

&lt;p&gt;บทความนี้เลยอยากจะมาแนะนำเครื่องมือ open source ตัวหนึ่งที่ชื่อ &lt;a href="https://papermill.readthedocs.io/"&gt;Papermill&lt;/a&gt; ครับ เค้าว่ามาแบบนี้&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Papermill is a tool for parameterizing and executing Jupyter Notebooks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;หมายความว่าเราสามารถที่จะ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;กำหนด parameters และส่งเข้า notebook ของเราได้&lt;/li&gt;
&lt;li&gt;สั่ง execute ตัว notebook ของเราได้&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;แน่นอนครับ เมื่อเรามีความสามารถแบบนี้ เราจึงสามารถทำ automation หรือจับเอา notebook ของเราเข้าไปรวมอยู่ใน automated workflow ได้แล้วน่ะสิ! ปัญหาที่เอ่ยถึงด้านบนก็ถูกแก้ไขไปเรียบร้อยแล้วด้วยเครื่องมือตัวนี้ครับ&lt;/p&gt;

&lt;p&gt;สิ่งที่ Papermill ทำมีอยู่ 3 ขั้นตอนสั้นๆ คือ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;รับ notebook มาพร้อมกับ parameters ที่เรากำหนด&lt;/li&gt;
&lt;li&gt;ทำ execute ตัว notebook นั้นๆ ให้&lt;/li&gt;
&lt;li&gt;บันทึกผลลัพธ์ไว้ใน notebook ไฟล์ใหม่&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;อยากลองเล่น? ไปดูหัวข้อถัดไปกันเลย~&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Papermill
&lt;/h2&gt;

&lt;p&gt;ติดตั้ง Papermill กับ Jupyter Notebook ก่อนครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;papermill jupyter
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;เสร็จแล้วก็ให้เปิด server ของ Jupyter Notebook ขึ้นมา&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;jupyter notebook
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;เสร็จแล้วก็เขียนโค้ดตามปกติประมาณนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--afl9cyca--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sj6fi64ofhwm534gn9zd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--afl9cyca--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sj6fi64ofhwm534gn9zd.png" alt="Write some code in a Jupyter notebook"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ต่อไปให้เรากำหนด Tags ให้กับ cell ที่เราอยากให้เป็น parameters ครับ วิธีกำหนดก็ตามรูปด้านล่างนี้เลย กดที่ cell นั้นๆ ก่อน&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AeLDF40h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/naxmsxdniwestzu6v49p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AeLDF40h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/naxmsxdniwestzu6v49p.png" alt="Set up tags for a cell"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ให้เราใส่ tag เป็นคำว่า &lt;code&gt;parameters&lt;/code&gt; ครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Teip9rsX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uwcugwgq3zzdxd7bw7b1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Teip9rsX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uwcugwgq3zzdxd7bw7b1.png" alt='Name the tag "parameters"'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เสร็จแล้วให้เราไปที่ terminal แล้วสั่ง&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;papermill &lt;span class="nt"&gt;-p&lt;/span&gt; name &lt;span class="s1"&gt;'Kan'&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; x 9 &lt;span class="nt"&gt;-p&lt;/span&gt; y 8 main.ipynb output.ipynb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;คำสั่งด้านบนนี้แปลว่าเรากำหนด parameters ตามนี้&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; ให้มีค่าเป็น string มีค่า &lt;code&gt;'Kan'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x&lt;/code&gt; ให้เป็น integer มีค่า 9&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;y&lt;/code&gt; ให้เป็น integer มีค่า 8&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;พอรันเสร็จให้เราเปิดไฟล์ notebook ที่เราได้ออกมาใหม่ที่ชื่อ &lt;code&gt;output.ipynb&lt;/code&gt; ดูครับ จะเป็นประมาณนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wLgiLN6P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qevk79a622t01dsxo50o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wLgiLN6P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qevk79a622t01dsxo50o.png" alt="Output from the execution using Papermill"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เห็นได้ว่า Papermill มาแทรก cell ที่มี tag ชื่อ &lt;code&gt;injected-parameters&lt;/code&gt; ไว้ข้างใต้ cell ของเราที่ tag ไว้ก่อนหน้านี้ ซึ่งทำให้ cell ถัดๆ ไปได้ใช้ค่า parameters ที่โดน inject เข้าไปแทนที่จะใช้ parameters ที่เรากำหนดไว้ตอนแรก&lt;/p&gt;

&lt;p&gt;ดูเป็นวิธีที่ simple มากๆ แต่แก้ปัญหาได้เยอะเลยนะ งดงามมาก&lt;/p&gt;

&lt;p&gt;ใครอยากเห็นโค้ดที่ใช้ในบทความนี้ ตามไปดูกันได้ที่ &lt;a href="https://github.com/zkan/hello-papermill"&gt;hello-papermill&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ใครที่ใช้ Papermill กันอยู่ อยากรู้จังว่าเอาไปใช้ใน use case ไหนกันบ้าง แชร์กันมาได้นะครับ ^^&lt;/p&gt;

&lt;p&gt;ปล. ถ้าเราไม่ tag คำว่า &lt;code&gt;parameters&lt;/code&gt; ที่ cell ของเราไว้ Papermill จะใส่ &lt;code&gt;injected-parameters&lt;/code&gt; ไว้ที่ cell ด้านบนสุดให้&lt;/p&gt;

</description>
      <category>jupyter</category>
      <category>papermill</category>
      <category>automation</category>
    </item>
    <item>
      <title>9 นิสัยเล็กๆ ที่จะเปลี่ยนชีวิต</title>
      <dc:creator>Kan Ouivirach</dc:creator>
      <pubDate>Sun, 30 Aug 2020 01:09:27 +0000</pubDate>
      <link>https://dev.to/zkan/9-1eng</link>
      <guid>https://dev.to/zkan/9-1eng</guid>
      <description>&lt;p&gt;ฟังจาก Podcast ของ Mission to the Moon มาตอน &lt;a href="https://open.spotify.com/episode/1B6o6dldtjgIHVLZ7Tqdgh?si=T2zrXPSJSy6-8YzY7YzwLA&amp;amp;context=spotify%3Ashow%3A4y8NZOg9W9IiR4Xh6YZWzX"&gt;นิสัยเล็กๆ มากๆ ที่จะเปลี่ยนชีวิตคุณในหนึ่งปี&lt;/a&gt; ขอมาสรุปจดไว้ 😊&lt;/p&gt;

&lt;p&gt;นิสัยเล็กๆ เมื่อทำๆ ไปจะเป็นผลดีในระยะยาว และจะเปลี่ยนชีวิตเรา มี 9 ข้อ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;อย่าตอบสนอง ตอบโต้เร็วเกินไป&lt;/li&gt;
&lt;li&gt;ทำงานให้เสร็จในตอนที่ไม่รู้สึกอยากทำ เช่น ล้างจาน ก็ล้างให้เสร็จๆ ไป&lt;/li&gt;
&lt;li&gt;ใช้ชีวิตสัก 1 วันโดยไม่เข้า social media&lt;/li&gt;
&lt;li&gt;เตรียมตัว เตรียมของ เตรียมงานในวันถัดไป ก่อนวันรุ่งขึ้นตอนกลางคืน จะทำให้วันรุ่งขึ้นมีพลังมากขึ้น&lt;/li&gt;
&lt;li&gt;กินอย่างมีสติ เวลากินก็ให้คิดถึงแต่เรื่องกิน&lt;/li&gt;
&lt;li&gt;กำหนดเวลาทำ task แบบ time boxing ต้องมีการจับเวลาเกิดขึ้น&lt;/li&gt;
&lt;li&gt;วางมือถือวางไว้ไกลๆ จากตัวที่สุด โดยเฉพาะเวลานอน&lt;/li&gt;
&lt;li&gt;เวลาอยากได้อะไร ให้รอก่อน ถ้าต้องการจริงๆ ค่อยกลับมา หรือเวลาเจอป้าย sale ให้ถามตัวเองว่า ถ้าเป็นราคาเต็มยังจะซื้ออีกไหม จะได้รู้ว่าเราต้องการจริงๆ หรือเปล่า&lt;/li&gt;
&lt;li&gt;เขียนทุกไอเดียที่เกิดขึ้น&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>habit</category>
      <category>productivity</category>
    </item>
    <item>
      <title>สรุปบทความ Quantity Always Trumps Quality (ในมุมของ Software Development)</title>
      <dc:creator>Kan Ouivirach</dc:creator>
      <pubDate>Sun, 12 Jul 2020 13:49:30 +0000</pubDate>
      <link>https://dev.to/zkan/quantity-always-trumps-quality-2an</link>
      <guid>https://dev.to/zkan/quantity-always-trumps-quality-2an</guid>
      <description>&lt;p&gt;จากบทความ &lt;a href="https://blog.codinghorror.com/quantity-always-trumps-quality/"&gt;Quantity Always Trumps Quality&lt;/a&gt; เค้าบอกว่า quantity (จำนวน) เหนือกว่า quality (คุณภาพ) เคยมี อ. ลองแบ่งกลุ่มนักเรียนเป็น 2 กลุ่ม โดยกลุ่มหนึ่งจะตัดเกรดจาก quantity อีกกลุ่มตัดเกรดจาก quality ปรากฎว่า กลุ่มที่เป็น quantity ทำได้ดีกว่า และที่แปลกก็คือกลุ่ม quantity มีงานที่มี quality สูงกว่า&lt;/p&gt;

&lt;p&gt;ในการพัฒนา Software ที่มีมัก deadline เข้ามาเอี่ยวด้วยเสมอ การที่เราเน้นจำนวนเป็นการที่เราทำซ้ำๆ ทำเยอะๆ และเราจะเรียนรู้จากตรงนั้น ถ้ามันไม่ work เราก็สร้างใหม่เรื่อยๆ จนกว่าเราจะเจอที่มัน work ดังนั้นแทนที่จะมานั่งเสียเวลาไปกับการคิดว่าจะทำดีหรือไม่ดี ให้หยุดคิดซะ แล้วลงมือทำ ซึ่งส่วนใหญ่แล้วคนที่ลงมือทำจะผลิตผลงานออกมาได้ดีกว่า (แต่ๆๆ ก็ให้คำนึงถึงเวลาที่เราลองผิดลองถูกกับเวลาที่ทำเป็นงานจริงๆ ด้วยนะ balance กันให้ดีครับ)&lt;/p&gt;

&lt;p&gt;Jeff (ผู้เขียน) ได้ให้คำแนะนำไว้ 3 ข้อตามนี้ ลองตามไปอ่านกันดู&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="http://www.codinghorror.com/blog/archives/000165.html"&gt;Stop theorizing&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://www.codinghorror.com/blog/archives/000684.html"&gt;Write lots of software&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://www.codinghorror.com/blog/archives/000300.html"&gt;Learn from your mistakes&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;เลิกเพ้อฝันแล้วลงมือทำกัน 😉&lt;/p&gt;

&lt;p&gt;ปล. บทความนี้พูดถึงมุมของ Software Development เท่านั้นนะ&lt;/p&gt;

</description>
    </item>
    <item>
      <title>สรุปบทความ Why You Should Have (at Least) Two Careers</title>
      <dc:creator>Kan Ouivirach</dc:creator>
      <pubDate>Fri, 10 Jul 2020 11:47:38 +0000</pubDate>
      <link>https://dev.to/zkan/why-you-should-have-at-least-two-careers-4496</link>
      <guid>https://dev.to/zkan/why-you-should-have-at-least-two-careers-4496</guid>
      <description>&lt;p&gt;เห็น &lt;a class="comment-mentioned-user" href="https://dev.to/gatukgl"&gt;@gatukgl&lt;/a&gt;
 แชร์ไว้ในเฟสบุค อ่านแล้วชอบมาก และรู้สึกว่าเป็นอย่างงั้นจริงๆ กับตัวเอง ขอเขียนสรุปไว้ประมาณนี้&lt;/p&gt;

&lt;p&gt;คือจากประสบการณ์ของผู้เขียน การเปลี่ยนงานมี cost ค่อนข้างสูง แล้วก็มีน้อยคนที่จะประสบความสำเร็จในชีวิต ซึ่งเค้าบอกว่าแทนที่เราจะมองหางานใหม่ ให้ลองมองหางานที่ 2 แทนอาจจะดีกว่า และทำให้เราได้ประโยชน์จากทั้ง 2 งาน (ผู้เขียนทำงานอยู่ 4 ที่..)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;งานหลักช่วย subsidize ทำให้เราสามารถพัฒนาสกิลจากงานที่ 2 ได้ และงานที่ 2 ก็ช่วยเติมเต็ม passion ของเรา&lt;/li&gt;
&lt;li&gt;เราได้เจอเพื่อนใหม่ในสังคมที่ต่างออกไป เราจะได้ perspective ที่ต่างออกไป และเรายังมี connection ที่เพิ่มมากขึ้นอีกด้วย&lt;/li&gt;
&lt;li&gt;เราจะได้พบกับ innovations จริงๆ เราจะมองเห็นการเชื่อมโยงต่างๆ การผสมผสานกันจนเกิดสิ่งใหม่ๆ ขึ้นมา&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ไปอ่านเต็มๆ ได้ที่ลิ้งค์ข้างล่างนี้จ้า 👇 &lt;/p&gt;

&lt;p&gt;&lt;a href="https://hbr.org/2017/04/why-you-should-have-at-least-two-careers"&gt;https://hbr.org/2017/04/why-you-should-have-at-least-two-careers&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>5 Challenges ในการสร้าง Production-Grade Data Pipeline</title>
      <dc:creator>Kan Ouivirach</dc:creator>
      <pubDate>Sat, 20 Jun 2020 01:58:51 +0000</pubDate>
      <link>https://dev.to/dataength/5-challenges-production-grade-data-pipeline-1p06</link>
      <guid>https://dev.to/dataength/5-challenges-production-grade-data-pipeline-1p06</guid>
      <description>&lt;p&gt;เวลาที่มีคนมาปรึกษาว่าอยากจะลงมือสร้าง data pipeline จะเริ่มสร้างอย่างไร ส่วนใหญ่ผมมักจะชวนให้ลองทำ data pipeline แบบ minimal ขึ้นมาก่อน โดยการเขียน script ประมาณนี้&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ดึงข้อมูลอะไรก็ได้ที่ดูน่าสนใจจากอินเตอร์เนท หรือจากเว็บ &lt;a href="https://data.go.th/"&gt;Open Government Data of Thailand&lt;/a&gt; ออกมาเก็บอยู่ในฟอร์แมตสักฟอร์แมตหนึ่ง เช่น CSV&lt;/li&gt;
&lt;li&gt;จัดการทำความสะอาดข้อมูลสักเล็กน้อย เช่น คอลัมน์ปีเกิด อาจจะเก็บค่าปีเป็น พ.ศ. บ้าง ค.ศ. บ้าง เราก็แปลงให้เป็นปีแบบเดียวกัน หรือพวก timestamp ก็จัดฟอร์แมตให้เราเอาไปใช้ต่อได้ง่าย&lt;/li&gt;
&lt;li&gt;สร้างฐานข้อมูลขึ้นมาสัก 1 เครื่อง แล้วโหลดข้อมูลเข้าไป&lt;/li&gt;
&lt;li&gt;กำหนดให้ script ถูกรันทุกๆ วันตอนเที่ยงคืน หรือทุกๆ 5 นาทีก็ได้นะ&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ที่ว่ามาด้านบนนี้คืออยากให้คนที่เพิ่งเริ่มต้นได้ feeling เบื้องต้นของสายงานด้าน data engineering เรียกได้ว่ามันคือการสร้าง data pipeline แบบ happy path เฉยๆ ทีนี้ถ้าเราจะต่อยอดไปจากนี้ แล้วสร้างให้ดีๆ เราจะเจอความท้าทาย 5 อย่างประมาณนี้ลองไปดูกัน&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Schema เปลี่ยนแปลงอยู่ตลอด
&lt;/h2&gt;

&lt;p&gt;อันนี้น่าจะเป็นปัญหาอันดับ 1 ที่ทุกคนต้องเจอ ยิ่งในยุคปัจจุบันที่โลกของซอฟต์แวร์นั้นเปลี่ยนแปลงไปเร็วมาก disrupt กันเป็นว่าเล่น ธุรกิจเราก็ evolve ตามไป และแน่นอนว่าจะส่งผลให้ schema ของข้อมูลนั้นเปลี่ยนแปลงไป เราก็ต้องปรับ data pipeline ของเราตาม&lt;/p&gt;

&lt;p&gt;วิธีการก็มีอยู่หลายวิธีขึ้นอยู่กับสถานการณ์ เช่น ถ้าข้อมูลเราไม่เยอะเท่าไหร่ แล้วการวิเคราะห์ข้อมูลก็ไม่จำเป็นต้องเป็น real-time เราอาจจะ drop table ทิ้ง สร้างใหม่ แล้วโหลดข้อมูลตามไปก็ได้อยู่นะ แต่ถ้าข้อมูลเราเยอะมากขึ้น การทำแบบนี้ก็อาจจะเสียเวลาไปเป็นวันๆ เราก็ต้องหาวิธีอื่นมาแก้ปัญหา เป็นต้น&lt;/p&gt;

&lt;p&gt;ระบบ monitoring กับ logging ดีๆ จะช่วยให้เรารู้ตัวได้ไว และการทำ schema version management ก็สามารถช่วยได้เช่นกัน 💪&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Machine Failure เป็นเรื่องปกติ
&lt;/h2&gt;

&lt;p&gt;งาน data pipeline ไม่ได้มีแค่ส่วนโค้ดที่เราต้องดูแล ยังมีเรื่องของ infrastructure ที่เราใช้อยู่ด้วย ระบบหรือตัวเครื่องเซิฟเวอร์ก็จะมีปัญหาประมาณว่า disk เต็ม เขียนไฟล์ไม่ได้บ้าง เครื่องค้างต้องรีสตาร์ท ระบบ network หรือ DNS ล่ม แล้วยังต้องอัพเกรด patch อีก สิ่งเหล่านี้เกิดขึ้นเป็นเรื่องปกติ 🤣 หาวิธีรับมือไว้เลยแต่เนิ่นๆ&lt;/p&gt;

&lt;h2&gt;
  
  
  3. การ Scale เพื่อรองรับข้อมูลที่มีขนาดใหญ่ขึ้นเรื่อยๆ
&lt;/h2&gt;

&lt;p&gt;ช่วงแรกๆ ตอนที่มีข้อมูลน้อยๆ เราก็ happy ดีแหละ data pipeline อาจจะใช้เวลาไม่ถึง 10 นาทีก็ทำงานเสร็จ 😊 แต่หลายๆ คน รวมถึงตัว business เอง เรื่องการ scale ตัว data pipeline อาจจะไม่ใช่ priority ขององค์กร เลยไม่ได้นึกถึงเรื่องการ scale เท่าไหร่ ตรงนี้ผมมองว่ามันจะเป็นหลุมพลาง (pitfall) เนื่องจากข้อมูลที่ไหลเข้ามา และเพิ่มขึ้นเรื่อยๆ การทำงานของ data pipeline ก็จะใช้เวลานานขึ้นเรื่อยๆ เช่นกัน ยังไม่จบแค่นั้น.. แต่ละองค์กรก็คงไม่ได้มีแค่ pipeline เดียวแน่ ยิ่งปล่อยทิ้งไว้มันก็ยิ่งเหมือน technical debt ที่สะสม ไปจนวันหนึ่งเราจะไม่สามารถแก้มันได้อีกแล้ว เพราะ cost ของ effort ที่จะลงแรงไปปรับปรุงให้ดีขึ้นมันสูงเกินไป&lt;/p&gt;

&lt;p&gt;ดังนั้นให้นึกถึงการ scale เอาไว้ด้วยเลย ยิ่งองค์กรไหนมีข้อมูลเยอะอยู่แล้ว เรื่องการ scale เป็นเรื่องที่สำคัญมาก ตรงนี้จะรวมไปถึงการเลือก technology ที่เหมาะสมมาใช้ด้วยเช่นกัน ใช้แค่ script อย่างที่กล่าวไว้ตอนต้นอย่างเดียวคงจะไม่พอล่ะ&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Batch vs. Real-Time
&lt;/h2&gt;

&lt;p&gt;เรื่องนี้ก็ตาม business เลย ขึ้นอยู่กับ context ของแต่ละที่ และแต่ละงาน ถึงแม้ว่าตลาดส่วนใหญ่จะเป็นเรื่อง batch processing แต่ก็อยากให้ระลึกไว้เสมอไว้ว่างานในหลายๆ ที่มีแบบ real-time processing เข้ามาแล้ว การทำ data pipeline แบบ batch กับแบบ real-time ก็มีการพัฒนาและการดูแลที่แตกต่างกัน พวกเราชาว data engineer ควรที่จะศึกษาและลองเล่นไว้ทั้ง 2 แบบนะ 🤓&lt;/p&gt;

&lt;h2&gt;
  
  
  5. การทำ Data Catalog และ Data Lineage
&lt;/h2&gt;

&lt;p&gt;หัวข้อนี้มีความเกี่ยวข้องกับการทำ data lake ด้วยนะ ซึ่งหลายคนมักจะมองข้าม เอาไว้ทำทีหลังก็ได้ แล้วสุดท้ายก็จะลืม.. หรือไม่ก็เกิดอาการ curse of knowledge ของคนทำข้อมูล ที่ว่ามองแว๊บเดียวก็รู้ว่าอะไรคืออะไร อย่าไปตกหลุมพลางเข้าล่ะ ระลึกไว้เสมอเลยว่าเราไม่ได้ทำงานคนเดียว 🙂&lt;/p&gt;

&lt;p&gt;ก็อยากจะมาเขียนย้ำครับว่าให้นึกถึงการทำ data catalog (เก็บ metadata ไว้เพื่อให้ค้นหาข้อมูลได้สะดวกและรวดเร็ว) กับ data lineage (รู้ที่มาที่ไปของข้อมูลว่ามาจากไหน ได้มาได้อย่างไร โดน transform มาแบบไหน) ด้วย &lt;/p&gt;

&lt;h2&gt;
  
  
  สรุปช่วงท้าย
&lt;/h2&gt;

&lt;p&gt;ที่เขียนไว้ด้านบนน่าจะเป็นความท้าทายที่คนทำงานทางด้านข้อมูล โดยเฉพาะสายงาน data engineer 👷🏻‍♀️👷🏻‍♂️ น่าจะต้องเจอกัน งานสร้าง data pipeline ให้ดีๆ ก็จะมีหลายอย่างที่ต้องคิด แล้วก็จะมีอีกหลายอย่างที่เราอาจจะต้องไปเจอหน้างาน แล้วแก้ไปตาม context ณ ตอนนั้นด้วย 😅&lt;/p&gt;

&lt;p&gt;คนที่แวะเข้ามาได้เจอความท้าทายอะไรกันบ้างเอ่ย? เล่าให้อ่านกันได้นะ 😘&lt;/p&gt;

&lt;p&gt;ปล. ขอบคุณรูป cover สวยๆ จาก &lt;a href="https://unsplash.com/@jjying?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;JJ Ying&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>datapipeline</category>
    </item>
    <item>
      <title>ใช้ Terraform ทำ Blue-Green Deployment แบบง่ายๆ</title>
      <dc:creator>Kan Ouivirach</dc:creator>
      <pubDate>Sat, 25 Apr 2020 06:50:23 +0000</pubDate>
      <link>https://dev.to/zkan/terraform-blue-green-deployment-18b9</link>
      <guid>https://dev.to/zkan/terraform-blue-green-deployment-18b9</guid>
      <description>&lt;p&gt;&lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt; มันคืออาวุธที่ใช้ในการทำ infrastructure as code เพื่อจัดการการวาง infrastructure ต่างๆ เราสามารถสร้างเครื่อง จัดการเครื่อง หรือลบเครื่องได้โดยไม่ต้องไปกดๆ ใน cloud console เลย และที่สำคัญคือมันทำ immutable infrastructure ลองดูวีดีโอด้านล่างนี้ 👇 เค้าอธิบาย mutable กับ immutable infrastructure ว่าต่างกันอย่างไร&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/II4PFe9BbmE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;ส่วน blue-green deployment มันคืออะไร? ถ้าใครไม่รู้จัก ผมอยากให้ตามไปอ่านโพสต์ &lt;a href="https://martinfowler.com/bliki/BlueGreenDeployment.html"&gt;BlueGreenDeployment&lt;/a&gt; ของ Martin Fowler กันก่อนครับ 😆 ด้านล่างนี่เป็นรูป blue-green deployment ที่ Martin เค้าวาดไว้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wyeVCIg6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bokl4hh1w5fcbt0mid18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wyeVCIg6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bokl4hh1w5fcbt0mid18.png" alt="รูป blue-green deployment จากโพสต์ BlueGreenDeployment ของ Martin Flower"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;มันก็ประมาณว่า เวลาที่เรา deploy server เราจะมอง server ตัวเก่าเป็น blue และตัวที่กำลังจะ deploy เป็น green โดยที่ตัว infrastructure ของเราจะรอให้ green ทำงานได้ก่อน รับ request ได้ก่อน แล้วค่อยฆ่าตัว blue ทิ้ง (ซึ่งแน่นอนครับ ต้องอาศัย load balancer สักตัวหนึ่งมาช่วย)&lt;/p&gt;

&lt;p&gt;ซึ่งตัว Terraform ที่เกริ่นมาข้างต้นนี่แหละ สามารถเอามาทำ blue-green deployment ได้นะ โดยหลักการที่เราจะทำเนี่ย เนื่องจาก Terraform ออกแบบมาเพื่อ immutable infrastructure ซึ่งการที่เราจะสร้าง infrastructure stack ทั้งหมด ตั้งแต่ DNS ยัน database เลยเนี่ย มันจะทำให้เราต้องมาทำ load balancer ครอบ stack ของเราอีกรอบ (ย้อนกลับไปดูรูปด้านบนครับ ตัว load balancer ก็คือ Router ในรูปนั่นเอง)&lt;/p&gt;

&lt;p&gt;ดังนั้นเราจะสร้างโครงขึ้นมาก่อน แล้วส่วนที่เราต้องการให้มีการเปลี่ยนแปลงอยู่ตลอดเวลา เช่น เครื่อง server เราก็จะแยกออกมาจากโครงนั้นเอามาทำ blue-green deployment ดูโค้ดกันเลย (ในทีนี้ใช้ AWS เป็น cloud provider นะ)&lt;/p&gt;

&lt;p&gt;เบื้องต้นผมจะทำไว้ 2 โฟลเดอร์มีหน้าตาประมาณนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  terraform tree -L 2
.
├── app
│   ├── bootstrap.sh
│   └── main.tf
└── base
    ├── bootstrap.sh
    └── main.tf

2 directories, 4 files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;โฟลเดอร์ base จะเป็น folder ที่ผมจะเอาไว้สร้างโครงครับ ซึ่งตรงนี้จะเป็น Terraform ก็ได้ หรือจะสร้างผ่าน CLI ก็ได้ หรือจะไป manual กดๆ ในหน้า AWS console เลยก็ได้ครับ ส่วนโฟลเดอร์ app ผมก็จะมีแค่การสร้าง EC2 instance แล้วก็การเอา instance นั้นไป register เข้ากับ load balancer (ในที่นี้ใช้ ALB)&lt;/p&gt;

&lt;p&gt;ส่วนไฟล์ &lt;code&gt;bootstrap.sh&lt;/code&gt; ทั้ง 2 ไฟล์ มีหน้าตาเหมือนกันครับ จะเป็นสคริปสำหรับติดตั้งอะไรก็ได้ตอนที่เครื่อง EC2 ถูก provision ขึ้นมา ในที่นี้ผมจะเอาไว้ลง Docker เฉยๆ เนื้อหาในไฟล์จะประมาณนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/sh

rm -rf /var/lib/cloud/*
sudo apt-get update -y
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
sudo usermod -aG docker ubuntu
sudo curl -L "https://github.com/docker/compose/releases/download/1.25.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;มาดู &lt;code&gt;base/main.tf&lt;/code&gt; กัน จะยาวๆ หน่อย&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# base/main.tf&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;access_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_key_id&lt;/span&gt;
  &lt;span class="nx"&gt;secret_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret_access_id&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"access_key_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS Access Key ID"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"secret_access_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS Secret"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ap-southeast-1"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS Region"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"product_area"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lost-in-space"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Product Area"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Product Environment"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"lost_in_space"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;assign_generated_ipv6_cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;                       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.30.0.0/21"&lt;/span&gt;
  &lt;span class="nx"&gt;enable_dns_hostnames&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_dns_support&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_internet_gateway"&lt;/span&gt; &lt;span class="s2"&gt;"lost_in_space_internet_gateway"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-internet-gateway"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"lost_in_space_public_subnet_zone_a"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;assign_ipv6_address_on_creation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;a"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.30.0.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv6_cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-public-subnet-zone-a"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"lost_in_space_public_route_table"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_block&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
    &lt;span class="nx"&gt;gateway_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-public-route-table"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"lost_in_space_public_route_table_association_zone_a"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space_public_subnet_zone_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space_public_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"lost_in_space"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lost-in-space-dev"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Security group for Lost In Space (dev)"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow SSH inbound traffic"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow HTTP inbound traffic"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow HTTPS inbound traffic"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow PostgreSQL inbound traffic"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;egress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow Internet Outbound"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lost-in-space-dev"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_alb"&lt;/span&gt; &lt;span class="s2"&gt;"lost_in_space"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lost-in-space-dev"&lt;/span&gt;
  &lt;span class="nx"&gt;security_groups&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;subnets&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space_public_subnet_zone_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space_public_subnet_zone_b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;enable_deletion_protection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;idle_timeout&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lost-in-space-dev"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_alb_target_group"&lt;/span&gt; &lt;span class="s2"&gt;"lost_in_space"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lost-in-space-alb-target"&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;stickiness&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lb_cookie"&lt;/span&gt;
    &lt;span class="nx"&gt;cookie_duration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;health_check&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;
    &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_alb_listener"&lt;/span&gt; &lt;span class="s2"&gt;"listener_http"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;load_balancer_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_alb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"80"&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;

  &lt;span class="nx"&gt;default_action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_alb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"forward"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ก็จะมีการสร้าง VPC, Internet Gateway, Subnet, Route Table, Security Group, Application Load Balancer (ALB) ซึ่งของพวกนี้เราไม่จำเป็นต้อง deploy ใหม่ทุกครั้งครับ เรา define ไว้ได้เลย&lt;/p&gt;

&lt;p&gt;ต่อไปมาดู &lt;code&gt;app/main.tf&lt;/code&gt; กัน ซึ่งไฟล์นี้แหละ เราจะเอาไว้ทำ blue-green deployment&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/main.tf&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;access_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_key_id&lt;/span&gt;
  &lt;span class="nx"&gt;secret_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret_access_id&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"access_key_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS Access Key ID"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"secret_access_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS Secret"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ap-southeast-1"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS Region"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"product_area"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lost-in-space"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Product Area"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Product Environment"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"infrastructure_version"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Infrastructure Version"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_alb_target_group"&lt;/span&gt; &lt;span class="s2"&gt;"lost_in_space"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lost-in-space-alb-target"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"selected"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tag:Name"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-public-subnet-zone-a"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"selected"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tag:Name"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"lost-in-space-dev"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_alb_target_group_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"lost_in_space_target_group_attachment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_alb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;target_id&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lost_in_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"lost_in_space"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;associate_public_ip_address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;security_groups&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;                         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-09a4a9ce71ff3f20b"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.medium"&lt;/span&gt;
  &lt;span class="nx"&gt;key_name&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rocket-dev"&lt;/span&gt;
  &lt;span class="nx"&gt;user_data&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"bootstrap.sh"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;root_block_device&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;volume_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gp2"&lt;/span&gt;
    &lt;span class="nx"&gt;volume_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;volume_tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lost-in-space-dev"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lost-in-space-dev"&lt;/span&gt;
    &lt;span class="nx"&gt;InfrastructureVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infrastructure_version&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product_area&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;create_before_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ไฟล์ &lt;code&gt;app/main.tf&lt;/code&gt; จะมีจุดสำคัญอยู่ 3 จุดที่เราจำเป็นต้องรู้คือ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;การใช้ &lt;a href="https://www.terraform.io/docs/configuration/data-sources.html"&gt;Data Source&lt;/a&gt; สาเหตุที่เราต้องใช้เพราะว่าเราจำเป็นต้องรู้ว่าตอนที่เราจะ deploy เราจะเอา EC2 instance ของเราไปผูกกับ ALB target group อะไร Subnet อะไร และ Security Group อะไร ที่เราสร้างไว้ตอนแรกใน &lt;code&gt;base/main.tf&lt;/code&gt; ซึ่งตัว Data Source นี่แหละ เหมือนให้เราสามารถไปดึงค่า AWS service ที่เราเคยสร้างเอาไว้แล้วมาใช้ในสคริปนี้ต่อได้&lt;/li&gt;
&lt;li&gt;การใช้ variable ที่ชื่อ &lt;code&gt;InfrastructureVersion&lt;/code&gt; ซึ่งตรงนี้ จะเป็นจุดที่ผมใช้เพื่อให้ Terraform ตรวจจับการเปลี่ยนแปลงเพื่อที่มันจะสร้าง stack ใหม่ให้ผมได้ ซึ่งตรงนี้ก็มีหลากหลายเทคนิคนะครับ จะแก้ตัวไฟล์ &lt;code&gt;app/main.tf&lt;/code&gt; เลย หรือจะมีอีกสคริปหนึ่งมา search &amp;amp; replace ค่าอะไรสักอย่างใน &lt;code&gt;app/main.tf&lt;/code&gt; ก็ได้ ใครชอบแบบไหนก็เลือกเอาได้เลย&lt;/li&gt;
&lt;li&gt;จุดสำคัญที่สุดที่จะทำให้เราลด downtime เวลาที่ deploy ได้คือตรง &lt;code&gt;lifecycle&lt;/code&gt; ครับ ในที่นี้ผมเซต &lt;code&gt;create_before_destroy = true&lt;/code&gt; จะหมายความว่าให้สร้าง stack ใหม่ให้เสร็จก่อน แล้วค่อยลบ stack เก่า ซึ่งถ้าไม่ได้เซตตรงนี้ไว้ stack เก่าจะโดนลบทันทีที่เรากำลังสร้าง stack ใหม่ครับ แนะนำให้อ่าน &lt;a href="https://www.hashicorp.com/blog/zero-downtime-updates-with-terraform/"&gt;Zero Downtime Updates with HashiCorp Terraform&lt;/a&gt; ต่อ เค้าจะบอกวิธีแก้ในกรณีที่ application ของเรายังไม่ได้รันขึ้นมาจริงๆ (รัน instance ขึ้นมาได้ ไม่ได้หมายความว่า application ของเราจะรันเสร็จ)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;จบ! 😎&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>deployment</category>
      <category>devops</category>
    </item>
    <item>
      <title>การเริ่มใช้ Mockito (@Mock กับ @InjectMocks) ใน Spring Boot</title>
      <dc:creator>Kan Ouivirach</dc:creator>
      <pubDate>Sun, 19 Apr 2020 04:56:13 +0000</pubDate>
      <link>https://dev.to/zkan/mockito-mock-injectmocks-spring-boot-5284</link>
      <guid>https://dev.to/zkan/mockito-mock-injectmocks-spring-boot-5284</guid>
      <description>&lt;p&gt;ลอง search ใน Google แล้วพบว่ามีหลายบทความที่เขียนเกี่ยวกับเรื่องนี้เยอะอยู่เหมือนกัน ไล่ตามอ่านหลายบทความอยู่จนพอที่จะทำ minimal working example เกี่ยวกับการ Mock ใน Spring Boot โดยใช้ Mockito (แค่ @Mock กับ @InjectMocks) ได้แล้ว ดีใจ~ 🤩🎉&lt;/p&gt;

&lt;p&gt;เผื่อใครอยากลองทำตามก็ไปใช้ &lt;a href="https://start.spring.io/" rel="noopener noreferrer"&gt;Spring Initializr&lt;/a&gt; สร้างโปรเจคมาก่อน เลือกเป็น Maven หรือ Gradle ก็ได้นะ ส่วนภาษาก็จริงๆ เลือกอะไรก็ได้ ไม่ว่าจะเป็น Java หรือ Kotlin หรือ Groovy แต่ในบทความจะเป็น Java นะครับ (เหตุผล? ตอนที่เขียนบทความนี้ผมรู้จัก syntax ของ Java อยู่ภาษาเดียวครับ 😂) สร้างเสร็จแล้วก็น่าจะได้ ZIP ไฟล์มา เราก็เอาไป import เข้า IDE ตัวที่ถนัดของเรา ไปเริ่มเขียนโค้ดกันเลย&lt;/p&gt;

&lt;h3&gt;
  
  
  แบบยังไม่ได้ใช้ Mockito
&lt;/h3&gt;

&lt;p&gt;สมมุติว่าเรามีคลาสแบบง่ายๆ อยู่ 2 คลาส&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;team.bars.mockito&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bear&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;roar&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BearService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Bear&lt;/span&gt; &lt;span class="n"&gt;bear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Bear&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;roar&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ตัว &lt;code&gt;BearService&lt;/code&gt; แค่สร้าง &lt;code&gt;bear&lt;/code&gt; ขึ้นมาแล้วส่งค่าจาก method ที่ชื่อ &lt;code&gt;roar&lt;/code&gt; ออกไป เวลาที่เราเขียนเทสก็จะประมาณนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;team.bears.mockito&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;junit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jupiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Assertions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BearServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;testItShouldReturnHelloFromBear&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;BearService&lt;/span&gt; &lt;span class="n"&gt;bearService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BearService&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bearService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;say&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ก็ดูปกติไม่มีอะไรเนอะ แต่ว่าแบบนี้มีจุดที่เราสามารถปรับปรุงให้ดีขึ้นได้คือ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;คลาส &lt;code&gt;BearService&lt;/code&gt; มีการเรียก &lt;code&gt;Bear&lt;/code&gt; ที่เป็น dependency ข้างใน เสมือนกับว่าเทสนี้ได้ไปทดสอบตัว &lt;code&gt;Bear&lt;/code&gt; ไปด้วยเลยโดยปริยาย ซึ่งความต้องการจริงๆ แล้วเราอาจจะอยากทดสอบแค่ตัว &lt;code&gt;BearService&lt;/code&gt; พอ&lt;/li&gt;
&lt;li&gt;เรามองไม่เห็นว่า &lt;code&gt;BearService&lt;/code&gt; ได้ไปเรียก &lt;code&gt;Bear&lt;/code&gt; จริงๆ หรือเปล่าตามที่เราตั้งใจไว้&lt;/li&gt;
&lt;li&gt;ถ้าเป็นกรณีที่ &lt;code&gt;say&lt;/code&gt; ของ &lt;code&gt;BearService&lt;/code&gt; ไปเรียก API เวลาที่เรารันเทสแล้ว มันก็จะไปยิง API จริงๆ ซึ่งเราคงไม่อยากให้เป็นแบบนั้น&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  มาลองใช้ Mockito (@Mock กับ @InjectMocks) กัน
&lt;/h3&gt;

&lt;p&gt;ก่อนอื่นเราจะต้องไปเพิ่ม Mockito ให้เป็น dependency ก่อน ถ้าใครใช้ &lt;a href="https://gradle.org/" rel="noopener noreferrer"&gt;Gradle&lt;/a&gt; ก็ให้ไปเพิ่ม dependency ที่ใช้สำหรับตอน compile ตัวเทส (ไม่เอาไปใช้บน production) ใน &lt;code&gt;dependencies&lt;/code&gt; ที่ไฟล์ &lt;code&gt;build.gradle&lt;/code&gt; ประมาณนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;testCompile&lt;/span&gt; &lt;span class="nl"&gt;group:&lt;/span&gt; &lt;span class="s1"&gt;'org.mockito'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s1"&gt;'mockito-core'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;version:&lt;/span&gt; &lt;span class="s1"&gt;'3.3.3'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ถ้าใครใช้ &lt;a href="http://maven.apache.org/" rel="noopener noreferrer"&gt;Maven&lt;/a&gt; ก็ให้เพิ่ม dependency ใน &lt;code&gt;dependencies&lt;/code&gt; ที่ไฟล์ &lt;code&gt;pom.xml&lt;/code&gt; ตามนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.mockito&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mockito-core&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.3.3&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;test&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;รู้ได้อย่างไรว่าต้องเขียน dependency แบบนี้? ดูจาก &lt;a href="https://mvnrepository.com/artifact/org.mockito/mockito-core/3.3.3" rel="noopener noreferrer"&gt;Maven Repository&lt;/a&gt; จ้า 😆&lt;/p&gt;

&lt;p&gt;ต่อไปเราจะไปแก้ที่ &lt;code&gt;BearService&lt;/code&gt; ก่อน โดยแทนที่เราจะ instantiate ตัว &lt;code&gt;bear&lt;/code&gt; ขึ้นมาเอง เราจะใช้เทคนิค dependency injection เพื่อที่เราจะได้ไม่ต้องมา instantiate ใน &lt;code&gt;BearService&lt;/code&gt; เอง ซึ่งใน Spring มี annotation ที่ชื่อ &lt;a href="https://www.baeldung.com/spring-autowire" rel="noopener noreferrer"&gt;@Autowired&lt;/a&gt; มาช่วยให้ชีวีตเราง่ายขึ้น โค้ดของ &lt;code&gt;BearService&lt;/code&gt; จะได้ตามนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;team.bears.mockito&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.beans.factory.annotation.Autowired&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BearService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="nc"&gt;Bear&lt;/span&gt; &lt;span class="n"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;roar&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จากนั้นเราก็จะไปแก้เทส &lt;code&gt;BearServiceTest&lt;/code&gt; กัน เราอยากจะ mock ตัว &lt;code&gt;Bear&lt;/code&gt; เพื่อที่เราจะได้ทดสอบแค่ส่วนของ &lt;code&gt;BearService&lt;/code&gt; เราก็จะแก้โค้ดตามนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;team.bears.mockito&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.extension.ExtendWith&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.mockito.InjectMocks&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.mockito.Mock&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.mockito.junit.jupiter.MockitoExtension&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;junit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jupiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Assertions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@ExtendWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MockitoExtension&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BearServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Mock&lt;/span&gt;
    &lt;span class="nc"&gt;Bear&lt;/span&gt; &lt;span class="n"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@InjectMocks&lt;/span&gt;
    &lt;span class="nc"&gt;BearService&lt;/span&gt; &lt;span class="n"&gt;bearService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;testItShouldReturnHelloFromBear&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bearService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;say&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ถ้าเราใช้ JUnit 5 (มีคำว่า Jupiter) เวลาเราจะใช้ Mockito เราก็ใส่ &lt;code&gt;@ExtendWith(MockitoExtension.class)&lt;/code&gt; ไว้บนคลาส ถ้าใครใช้ JUnit เวอร์ชั่นต่ำกว่านี้ ก็ให้ลง JUnit 5 ครับ อ่านไปอ่านบทความ &lt;a href="https://medium.com/@phayao/%E0%B9%83%E0%B8%8A%E0%B9%89-junit-5-mockito-%E0%B8%9A%E0%B8%99-spring-boot-6877b5b1b226" rel="noopener noreferrer"&gt;ใช้ JUnit 5 + Mockito บน Spring Boot&lt;/a&gt; กันต่อได้&lt;/p&gt;

&lt;p&gt;ทีนี้ &lt;code&gt;@Mock&lt;/code&gt; กับ &lt;code&gt;@InjectMock&lt;/code&gt; เนี่ยมันคืออะไรนะ? ผมขอใช้ประโยคง่ายๆ ละกัน&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ตัว &lt;code&gt;@Mock&lt;/code&gt; เป็นการบอกว่ามันคือ object ที่เราจะ mock นะ&lt;/li&gt;
&lt;li&gt;ตัว &lt;code&gt;@InjectMocks&lt;/code&gt; จะเป็นบอกว่า object ที่เราแปะหัวเนี่ย จะมีการ inject mock เข้าไปนะ&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;เสร็จแล้วก็ให้ลองรันเทสดูครับ มันควรจะ fail! 💥 เราจะเห็น error ประมาณนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expected: &amp;lt;Hello&amp;gt; but was: &amp;lt;null&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ดีใจได้เลยครับ มันแปลว่าเรา mock สำเร็จแล้ว 🎉 ทีนี้เราอยากแค่จะทดสอบนะ ว่า &lt;code&gt;bear&lt;/code&gt; ที่เรา mock ไว้มันจะโดยเรียกจริงเปล่า? ต้องเขียนโค้ดอย่างไรนะ? จัดไปตามนี้ครับ เราจะใช้ &lt;code&gt;verify&lt;/code&gt; กับ &lt;code&gt;times&lt;/code&gt; จาก Mockito&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;team.bears.mockito&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.extension.ExtendWith&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.mockito.InjectMocks&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.mockito.Mock&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.mockito.junit.jupiter.MockitoExtension&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mockito&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Mockito&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mockito&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Mockito&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;times&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@ExtendWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MockitoExtension&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BearServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Mock&lt;/span&gt;
    &lt;span class="nc"&gt;Bear&lt;/span&gt; &lt;span class="n"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@InjectMocks&lt;/span&gt;
    &lt;span class="nc"&gt;BearService&lt;/span&gt; &lt;span class="n"&gt;bearService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;testItShouldReturnHelloFromBear&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;bearService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;say&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;roar&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;แล้วถ้าเราอยากจะ Stub ตัว &lt;code&gt;bear&lt;/code&gt; สามารถทำได้ด้วยหรือเปล่า? ทำได้ครับ จัดไปตามนี้ เราจะใช้ &lt;code&gt;when&lt;/code&gt; จาก Mockito&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;team.bears.mockito&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.extension.ExtendWith&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.mockito.InjectMocks&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.mockito.Mock&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.mockito.junit.jupiter.MockitoExtension&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;junit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jupiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Assertions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mockito&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Mockito&lt;/span&gt;&lt;span class="o"&gt;.*;&lt;/span&gt;

&lt;span class="nd"&gt;@ExtendWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MockitoExtension&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BearServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Mock&lt;/span&gt;
    &lt;span class="nc"&gt;Bear&lt;/span&gt; &lt;span class="n"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@InjectMocks&lt;/span&gt;
    &lt;span class="nc"&gt;BearService&lt;/span&gt; &lt;span class="n"&gt;bearService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;testItShouldReturnHelloFromBear&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;roar&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;thenReturn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Grrrrr!"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bearService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;say&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Grrrrr!"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;roar&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;โค้ดทั้งหมดที่ใช้ในบทความนี้อยู่ที่ &lt;a href="https://github.com/zkan/hello-springboot/tree/master/mockito" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; นะครับ ลองเอาไปเล่นกันดู ตรงไหนปรับให้ดีขึ้นได้ ช่วยเปิด pull request มาให้ด้วยนะ 🤣&lt;/p&gt;

&lt;p&gt;หลังจาก&lt;a href="https://www.facebook.com/zkan.cs/posts/3162545270443957" rel="noopener noreferrer"&gt;โพสต์ลง Facebook&lt;/a&gt; ไป &lt;a href="http://www.somkiat.cc/" rel="noopener noreferrer"&gt;พี่ปุ๋ย&lt;/a&gt;มาให้คำแนะนำเกี่ยวกับ &lt;code&gt;@InjectMocks&lt;/code&gt; ว่า&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftuc837q99vinc7wbqy20.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftuc837q99vinc7wbqy20.png" alt="พี่ปุ๋ยให้คำแนะนำเรื่องการใช้ @InjectMocks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🙏 กราบขอบคุณพี่ปุ๋ยมา ณ ที่นี้ด้วยครับ ถ้าใครสนใจรายละเอียดเพิ่มเติมก็ลองไปอ่าน &lt;a href="https://javadoc.io/static/org.mockito/mockito-core/3.3.3/org/mockito/InjectMocks.html" rel="noopener noreferrer"&gt;InjectMocks doc&lt;/a&gt; กันดูนะ&lt;/p&gt;

&lt;p&gt;ทีนี้ผมขอมาแก้คลาส &lt;code&gt;BearService&lt;/code&gt; สักเล็กน้อย สุดท้ายแล้วจะได้ตามนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;team.bears.mockito&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BearService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Bear&lt;/span&gt; &lt;span class="n"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;BearService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Bear&lt;/span&gt; &lt;span class="n"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bear&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;roar&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ที่นี้ก็น่าจะชัดเจนแล้วว่าตัว mock จะถูก inject เข้ามาที่ default constructor ของ &lt;code&gt;BearService&lt;/code&gt; 🤓&lt;/p&gt;

</description>
      <category>mock</category>
      <category>mockito</category>
      <category>springboot</category>
      <category>testdouble</category>
    </item>
    <item>
      <title>We may be looking for "Work-Life Integration"</title>
      <dc:creator>Kan Ouivirach</dc:creator>
      <pubDate>Mon, 06 Apr 2020 04:21:00 +0000</pubDate>
      <link>https://dev.to/zkan/we-may-be-looking-for-work-life-integration-2apk</link>
      <guid>https://dev.to/zkan/we-may-be-looking-for-work-life-integration-2apk</guid>
      <description>&lt;p&gt;I found out that there is a term called "Work-Life Integration". I think it's quite interesting for all of us and I'd recommend everyone to check &lt;a href="https://www.forbes.com/sites/forbescoachescouncil/2018/04/18/achieving-work-life-integration-in-this-new-world-of-work/#19afb88bfd9e"&gt;this article&lt;/a&gt; out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.forbes.com/sites/forbescoachescouncil/2018/04/18/achieving-work-life-integration-in-this-new-world-of-work/#19afb88bfd9e"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YxyEuIR0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rftdvdnxioifm7u28wz5.png" alt="Achieving Work-Life Integration In This New World Of Work"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the definition of the term Work/Life Integration by &lt;a href="https://www.haas.berkeley.edu/human-resources/life-integration/"&gt;BerkeleyHaas&lt;/a&gt; below:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Work/Life Integration instead is an approach that creates more synergies between all areas that define "life": work, home/family, community, personal well-being, and health.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I really like it.&lt;/p&gt;

</description>
      <category>worklifeintegration</category>
    </item>
    <item>
      <title>สรุป Make learning part of the culture @ Google</title>
      <dc:creator>Kan Ouivirach</dc:creator>
      <pubDate>Sun, 05 Apr 2020 00:45:59 +0000</pubDate>
      <link>https://dev.to/zkan/make-learning-part-of-the-culture-google-3011</link>
      <guid>https://dev.to/zkan/make-learning-part-of-the-culture-google-3011</guid>
      <description>&lt;p&gt;เคยอ่านบทความ &lt;a href="https://rework.withgoogle.com/guides/learning-development-employee-to-employee/steps/make-learning-part-of-the-culture/" rel="noopener noreferrer"&gt;Make learning part of the culture&lt;/a&gt; เมื่อนานมาแล้ว กลับมาเขียนสรุปไว้เผื่อแวะมาอ่านกัน&lt;/p&gt;

&lt;p&gt;คือที่ Google เค้ามีโปรแกรมภายในที่ชื่อ g2g (“Googlers-to-Googlers”) พูดง่ายๆ ว่ามันคือโปรแกรมสอนกันเองนี่แหละ และเค้ามี core belief ที่ทำให้ learning process ของเค้าเป็น "the best place to learn on the planet"&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Learning is a process.&lt;/strong&gt; - มันคือการมี motivation และการที่เราได้โอกาสฝึกฝนตัวเองรัวๆ และได้ feedback กับสิ่งที่เราทำ&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning happens in real life.&lt;/strong&gt; - เราเรียนรู้ และเจอสิ่งที่ท้าทาย ตลอดเวลาในชีวิตจริง&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning is personal.&lt;/strong&gt; - แต่ละคนมี learning style ที่แตกต่างกัน ผมเคยฟังโน้สอุดม เค้าพูดเปรียบว่าเด็กแต่ละคนเหมือนผลไม้คนละชนิดกัน บางคนเป็นมะนาว บางคนเป็นแตงโม ซึ่งผลไม้แต่ละชนิดนั้นมีช่วงเวลาที่ออกดอกออกผลที่แตกต่างกัน เราจะไปยัดเยียดวิธีเรียนรู้สำหรับคนที่เป็นมะนาว ให้กับคนที่แตงโม ไม่ได้.. โคตรจะจริง&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning is social.&lt;/strong&gt; - ที่ Google เค้าสนับสนุนสภาพแวดล้อมที่ทำให้ Googlers ทุกคนสามารถช่วยเหลือ และสนับสนุนกันได้ง่าย&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;พารากราฟสุดท้ายในบทความเป็นพารากราฟที่ผมชอบมาก ขอยกมาแปะไว้ตรงนี้ด้วย&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The g2g team is often asked: “How do you motivate people to do something outside their core job?” The answer is actually pretty simple; trust people to do great work, give them tools and feedback, show them how it connects to the big picture, and then step aside. When the team focused their strategy around trust and support, participants have consistently exceeded expectations.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>google</category>
      <category>g2g</category>
    </item>
    <item>
      <title>My Notes from the Book, "Read This Before Our Next Meeting", by Al Pittampalli</title>
      <dc:creator>Kan Ouivirach</dc:creator>
      <pubDate>Fri, 14 Feb 2020 00:39:25 +0000</pubDate>
      <link>https://dev.to/zkan/my-notes-from-the-book-read-this-before-our-next-meeting-7j0</link>
      <guid>https://dev.to/zkan/my-notes-from-the-book-read-this-before-our-next-meeting-7j0</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G0n7txS5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/58h1ix1wffx4tbew3i11.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G0n7txS5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/58h1ix1wffx4tbew3i11.jpg" alt="Read This Before Our Next Meeting"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Meetings are expensive. They break workdays into a series of work moments. Interruptions force us to start over each time.&lt;/p&gt;

&lt;h2&gt;
  
  
  8 Principles of Modern Meetings
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The modern meeting doesn't make decisions. Leaders do. -- A meeting might be helpful as part of decision-making process, but it isn't the decision-making process itself. Consult the team without calling a meeting. If the decision is of no consequence, don't call a meeting.&lt;/li&gt;
&lt;li&gt;The modern meeting has 2 primary functions: conflict and coordination. -- For low consequence, the meeting is about  coordination. Get the team on the same page. Allow the team to ask questions, voice concerns, and propose modifications. For high consequence, the meeting is about conflict. Encourage a robust, honest debate. Have the team submit their own thoughts about the issues before the meeting. Ask pros and cons. Let the best decision prevail, even if it's not yours.&lt;/li&gt;
&lt;li&gt;The modern meeting moves fast and ends on schedule. -- Spend the time wisely since every minute costs us a fortune.&lt;/li&gt;
&lt;li&gt;The modern meeting limits the number of attendees. -- Invite only people who are absolutely necessary. Don't waste the others' time.&lt;/li&gt;
&lt;li&gt;The modern meeting rejects the unprepared. -- Prepare the agenda and a set of background materials. Every meeting should require pre-meeting work. If someone comes unprepared, cancel the meeting or hold it without him.&lt;/li&gt;
&lt;li&gt;The modern meeting produces committed action plans. -- Actions should be clear for all. If no action plan is necessary, neither is a meeting.&lt;/li&gt;
&lt;li&gt;The modern meeting refuses to be informational. Reading memos is mandatory. -- We must commit to reading the memos. Treat this agreement very seriously.&lt;/li&gt;
&lt;li&gt;The modern meeting works only alongside a culture of brainstorming. -- Brainstorm is so crucial to the success of meeting. Modern meeting is focused in decision.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Initiate a Meeting?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Determine that the meeting is necessary?&lt;/li&gt;
&lt;li&gt;Make a list of people to invite.&lt;/li&gt;
&lt;li&gt;Create a detailed agenda.&lt;/li&gt;
&lt;li&gt;Ask yourself again. Is the meeting really necessary?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to Decide When We Have a Decision to Make?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Can I make this decision myself?&lt;/li&gt;
&lt;li&gt;Is this a decision of high, low, or no consequence?&lt;/li&gt;
&lt;li&gt;If a group is necessary, how and when should I involve them?&lt;/li&gt;
&lt;li&gt;Does the opinion of someone else matter? Or are facts sufficient?&lt;/li&gt;
&lt;li&gt;Can I do this with a series of one-on-one conversations instead of a meeting?&lt;/li&gt;
&lt;li&gt;How much time should this decision take?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This book helps me a lot and I hope it does the same for you. 😃&lt;/p&gt;

</description>
      <category>meeting</category>
    </item>
    <item>
      <title>Mutation Testing อีกทางหนึ่งในการวัดคุณภาพของการทดสอบ</title>
      <dc:creator>Kan Ouivirach</dc:creator>
      <pubDate>Sat, 28 Dec 2019 06:08:57 +0000</pubDate>
      <link>https://dev.to/zkan/mutation-testing-402e</link>
      <guid>https://dev.to/zkan/mutation-testing-402e</guid>
      <description>&lt;p&gt;เพิ่งรู้จัก Mutation Testing จากบทความ &lt;a href="https://gerg.dev/2019/09/mutation-testing-demo/"&gt;A demonstration of Mutation Testing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เค้าบอกว่าปกติแล้วเราจะใช้ test coverage เป็น metric ในการวัด quality ของเทสของเรา แต่ทีนี้มันเหมือนโกงได้ คือจริงๆ มันประมาณว่าถ้าเทสเรารันผ่านโค้ดทุกส่วน โดยเทสไม่ต้อง pass หรือ fail ก็ได้ มันก็ได้ 100% coverage แล้ว (เช่น เขียนเทส แต่ไม่เขียน assertion เลย) ทีนี้ก็เลยมีอีกทางหนึ่งนอกจาก coverage แล้วเนี่ย เรายังทำ mutation testing เพิ่มได้อีก&lt;/p&gt;

&lt;p&gt;ไอเดียคือ… เราจะใช้ tool เพื่อใส่บั๊ก 🐛 หรือ mutants เข้าไปในโค้ดของเรา เพื่อให้เทส fail&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ถ้าเทสเรา fail แปลว่าเราฆ่า mutant นั้นได้&lt;/li&gt;
&lt;li&gt;ถ้าเทสเรา pass แปลว่า mutant นั้นยังมีชีวิตอยู่&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;เป้าหมายของเราคือฆ่า mutant ให้ได้เยอะที่สุด % ที่ mutant ตายยิ่งมาก ก็หมายความว่าเทสของเราก็ยิ่งมีประสิทธิภาพมากขึ้นด้วย 💪&lt;/p&gt;

&lt;p&gt;ลองดูตัวอย่างการที่ tool ใส่ mutant เข้าไปในโค้ดเราได้ที่ &lt;a href="https://stryker-mutator.io/#an-example"&gt;Stryker Mutator: An example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;หมายเหตุ&lt;/em&gt;&lt;/strong&gt; ข้อเสียมันคือใช้เวลารันนาน… แหงล่ะ เล่นใส่ mutant มาในโค้ดแล้วก็รันเทสทดสอบทีละ mutant แบบนั้น ดังนั้นเราก็ไม่ควรใส่ไว้ใน build pipeline นะ เอาไว้เป็น tool สำหรับ review เทสของเราก็น่าจะพอ&lt;/p&gt;

</description>
      <category>mutationtesting</category>
      <category>automatedtesting</category>
    </item>
    <item>
      <title>Setting Up a Minimal Amazon ECS Cluster to Manage Multiple Applications</title>
      <dc:creator>Kan Ouivirach</dc:creator>
      <pubDate>Thu, 26 Dec 2019 02:09:29 +0000</pubDate>
      <link>https://dev.to/zkan/setting-up-a-minimal-amazon-ecs-cluster-to-manage-multiple-applications-39cl</link>
      <guid>https://dev.to/zkan/setting-up-a-minimal-amazon-ecs-cluster-to-manage-multiple-applications-39cl</guid>
      <description>&lt;h2&gt;
  
  
  Outline
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What is Amazon ECS?&lt;/li&gt;
&lt;li&gt;Creating an ECS Cluster&lt;/li&gt;
&lt;li&gt;Creating an Application Load Balancer (ALB)&lt;/li&gt;
&lt;li&gt;Setting up an Application in the ECS cluster&lt;/li&gt;
&lt;li&gt;Adding a Domain to ALB&lt;/li&gt;
&lt;li&gt;Testing the Application&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id="heading--what-is-amazon-ecs"&gt;What is Amazon ECS?&lt;/h2&gt;

&lt;p&gt;I will not explain too much about it here. It's not the main point of this tutorial. Please read the quote below to get some idea what it is. 😆&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Amazon Elastic Container Service (Amazon ECS) is a fully managed container orchestration service. Customers such as Duolingo, Samsung, GE, and Cook Pad use ECS to run their most sensitive and mission critical applications because of its security, reliability, and scalability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Reference:&lt;/strong&gt; &lt;a href="https://aws.amazon.com/ecs/" rel="noopener noreferrer"&gt;Amazon ECS&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="heading--creating-an-ecs-cluster"&gt;Creating an ECS Cluster&lt;/h2&gt;

&lt;p&gt;Go to the ECS service and click "Create Cluster".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fs5nnnnnrtqbqomu5k05q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fs5nnnnnrtqbqomu5k05q.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the "EC2 Linux + Networking" template. Don't worry if we don't see AWS Fargate here, we can set it later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F62gg0xoxsh5psz1punoo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F62gg0xoxsh5psz1punoo.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill in the information below. If we want to SSH into the EC2 instance, we need to specify the key pair. In this tutorial, we don't need it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fapfdumn9i4sdn9ckn2yv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fapfdumn9i4sdn9ckn2yv.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For networking, let's create a new VPC.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fari7mni7qy3vm6drscfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fari7mni7qy3vm6drscfg.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use the default IAM role &lt;code&gt;ecsInstanceRole&lt;/code&gt;. Specify the tags, so we can track the bill and keep our resources organized.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F41nfgxcispg3y5gs5p3l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F41nfgxcispg3y5gs5p3l.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we finish, we should see the launch status as shown in the screenshot below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fkzzlt2kcslbkhetpvzph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fkzzlt2kcslbkhetpvzph.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can then view the newly created cluster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0onrme4juuimxtn0kkah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0onrme4juuimxtn0kkah.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="heading--creating-an-application-load-balancer-alb"&gt;Creating an Application Load Balancer (ALB)&lt;/h2&gt;

&lt;p&gt;We need a load balancer to route requests to the destination. Let's create one. Go to the EC2 service then find the "Load Balancer" on the menu on the left. Click "Create Load Balancer".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8f3bxijxne6qjpgki0cp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8f3bxijxne6qjpgki0cp.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose the Application Load Balancer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6k3t3r4ln1ulwx5zksit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6k3t3r4ln1ulwx5zksit.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Configure the ALB and choose the VPC we created from above in the Availability Zone section. Again, don't forget to add the tags.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fr8xiiyvq3mfxlila00zh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fr8xiiyvq3mfxlila00zh.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the step 2, we need a SSL certificate for it. We can set it later and can skip this for now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa4rb8sxmrjt14npfucvl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa4rb8sxmrjt14npfucvl.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the security group, just create a new one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Flrrewo657jxo7pbsx8x9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Flrrewo657jxo7pbsx8x9.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the routing configuration, create a new target group.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8sb6orncfv5ffijje5tv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8sb6orncfv5ffijje5tv.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can skip the register targets step. ECS will do it for us when we create a new service in ECS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F15e9a1n53q29uuu08824.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F15e9a1n53q29uuu08824.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wait until the ALB is provisioned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F94se0qng158w1zkd6953.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F94se0qng158w1zkd6953.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the provision is finished, what we need to do is to update the security group rule on the EC2 instances where containers will run in the ECS cluster, so the ALB can access.&lt;/p&gt;

&lt;p&gt;Go back to the ECS console, select the cluster, click on the tab "ECS Instances". We will see an instance is running. Click the ECS instance ID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fei2ma00fpzrxhnzegk6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fei2ma00fpzrxhnzegk6m.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the security group name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffrrzmmobrn0v0ohz3nma.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffrrzmmobrn0v0ohz3nma.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Edit the inbound rule.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fm1rnu2c635e8osmt2qt9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fm1rnu2c635e8osmt2qt9.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the security group previously created for the ALB. We're allowing all the traffic from the ALB to the instances since when working with ECS we can use a dynamic port mapping feature in order to run more containers with the same image in the same EC2 instances, so when starting our task, we won't specify any port to run applications and the ECS will do it for us.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fto4ca3k8nixwvjsr0hbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fto4ca3k8nixwvjsr0hbf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="heading--setting-up-an-application-in-the-ecs-cluster"&gt;Setting up an Application in the ECS cluster&lt;/h2&gt;

&lt;p&gt;In order to set up an application in the ECS cluster, we need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a task definition;&lt;/li&gt;
&lt;li&gt;Create a service.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating a Task Definition
&lt;/h3&gt;

&lt;p&gt;Go to the ECS console and click the "Task Definitions" on the left menu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Futn7t7dxuex8gwamxkj4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Futn7t7dxuex8gwamxkj4.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose the EC2 launch type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdmvify1x5t4fkh5l85nn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdmvify1x5t4fkh5l85nn.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Configure the task and container definitions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbm9mahcxtqsqqc746j5d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbm9mahcxtqsqqc746j5d.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F90433ljl0bfvtleqdg6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F90433ljl0bfvtleqdg6l.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Add container" then.&lt;/p&gt;

&lt;p&gt;To make it simple, we're using &lt;a href="https://hub.docker.com/repository/docker/zkan/bangkok" rel="noopener noreferrer"&gt;this public Docker image&lt;/a&gt; in this tutorial. Note that we can actually use any Docker image from either a public or private Docker image repository here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fes7z3h7ddajx2kmjvfc4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fes7z3h7ddajx2kmjvfc4.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We intentionally set the host port to 0 since it will be used to facilitate dynamic port allocation. The ALB dynamically allocate a port during the task placement.&lt;/p&gt;

&lt;p&gt;Add the tags.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0tkx7qadcpov34m060j7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0tkx7qadcpov34m060j7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check the "Auto-configure CloudWatch Logs", so we can view the log in CloudWatch. Just keep the default settings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fe4ietz4in9kyxk1m2kwm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fe4ietz4in9kyxk1m2kwm.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that, we add the container and create the task definition.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fve0u55js0kji2r9me94n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fve0u55js0kji2r9me94n.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Service
&lt;/h3&gt;

&lt;p&gt;Let's create a service. This allow us to run and maintain a specified number of instances of a task definition simultaneously in the ECS cluster. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fs9f870ejfqbmc75ir82h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fs9f870ejfqbmc75ir82h.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Configure the service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9xmt7xt9ei02pkz3o7aa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9xmt7xt9ei02pkz3o7aa.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Load balancing, choose the Application Load Balancer. Click "Add to load balancer" to add the container to the load balancer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F98lac9xgv5n2simk1qil.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F98lac9xgv5n2simk1qil.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Configure the container settings as follows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F110moeyodbi64xv97adl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F110moeyodbi64xv97adl.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Uncheck the "Enable service discovery integration" option. This is not necessary for now since we won't do anything in the Route 53 now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fsyut1h7vdah1i7f8suq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fsyut1h7vdah1i7f8suq8.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that, create the service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Farg9m9101595bks3vnx7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Farg9m9101595bks3vnx7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our application should be up and running soon.&lt;/p&gt;

&lt;h2 id="heading--adding-a-domain-to-alb"&gt;Adding a Domain to ALB&lt;/h2&gt;

&lt;p&gt;This is the last step before we can access our application from the Internet. Go to our ALB and edit rules.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8mz1smijxcoiwfef2nt0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8mz1smijxcoiwfef2nt0.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new rule or edit the existing one like this. In this tutorial, choose the host header for the condition, then we set the domain &lt;code&gt;my-test-zkan-bkk.prontotools.io&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fj9lcw6ipjpqotsi8zxo3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fj9lcw6ipjpqotsi8zxo3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Update". &lt;/p&gt;

&lt;h2 id="heading--testing-the-application"&gt;Testing the Application&lt;/h2&gt;

&lt;p&gt;If we have our own domain, we can point it to the ALB's DNS name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa53secrru1por4l8kdwh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa53secrru1por4l8kdwh.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If not, we can modify our hosts file. Get the ALB's IP with &lt;code&gt;nslookup&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fwvvu4jr6zmt7a81lbbk6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fwvvu4jr6zmt7a81lbbk6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Modify the hosts file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuyjvnqe1u53o002y6d8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuyjvnqe1u53o002y6d8e.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Done! 🍻🎄🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fwvs86kcxr3p5frb99cin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fwvs86kcxr3p5frb99cin.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we want to add a new application, just repeat the step 3 to 5 again. 😉&lt;/p&gt;

&lt;p&gt;I've drawn a simple diagram to show how it will look like after complete those steps above. 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpjuklfa51pv4qud3db0z.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpjuklfa51pv4qud3db0z.PNG" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hope this helps! 😇&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ecs</category>
      <category>alb</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
