<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Ascend Developers - Medium]]></title>
        <description><![CDATA[Creating opportunities for all through world-class digital platforms &amp; services - Medium]]></description>
        <link>https://medium.com/ascend-developers?source=rss----98c9c9d1ba0c---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Ascend Developers - Medium</title>
            <link>https://medium.com/ascend-developers?source=rss----98c9c9d1ba0c---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 21:31:41 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/ascend-developers" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[From Code Ownership to Team Ownership วันที่ผมเริ่มวัดงานจากทีม ไม่ใช่ Pull Request]]></title>
            <link>https://medium.com/ascend-developers/from-code-ownership-to-team-ownership-%E0%B8%A7%E0%B8%B1%E0%B8%99%E0%B8%97%E0%B8%B5%E0%B9%88%E0%B8%9C%E0%B8%A1%E0%B9%80%E0%B8%A3%E0%B8%B4%E0%B9%88%E0%B8%A1%E0%B8%A7%E0%B8%B1%E0%B8%94%E0%B8%87%E0%B8%B2%E0%B8%99%E0%B8%88%E0%B8%B2%E0%B8%81%E0%B8%97%E0%B8%B5%E0%B8%A1-%E0%B9%84%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%8A%E0%B9%88-pull-request-744db07cdba4?source=rss----98c9c9d1ba0c---4</link>
            <guid isPermaLink="false">https://medium.com/p/744db07cdba4</guid>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[tech-lead]]></category>
            <category><![CDATA[engineering-mangement]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Visarut Sae-Pueng]]></dc:creator>
            <pubDate>Thu, 27 Aug 2026 06:56:30 GMT</pubDate>
            <atom:updated>2026-08-27T06:56:29.150Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*H1UL55ToqwbVHVCTumGwNg.png" /></figure><p>ตอนเป็น Engineer ผมชอบความรู้สึกตอนงานจบ</p><blockquote>รับ requirement → เขียน code → ทำ test → เปิด PR → แก้ comment → แล้ว merge</blockquote><p>ถึงระหว่างทางจะมี requirement เปลี่ยน มี bug แทรก หรือ production issue เข้ามา งานก็ยังมองเห็นได้เป็นชิ้นๆ มี branch มี commit และมีจุดที่บอกไดชัดเจนว่าเสร็จแล้ว</p><p>พอขยับมาเป็น Development Lead ความชัดแบบนั้นค่อยๆ หายไป เวลาส่วนหนึ่งเปลี่ยนไปอยู่กับ requirement ที่ยังไม่ลงตัว การช่วยแตก task ให้ทีมเริ่มทำงานได้ การเคลียร์ dependency กับทีมอื่น รวมถึง decision ที่อาจกระทบทั้ง sprint</p><p>หลายวันจบลงโดยไม่มี commit หรือ PR ของตัวเอง ช่วงแรกผมรู้สึกเหมือนวันนั้นไม่ได้ส่งมอบอะไร แต่พออยู่กับบทบาทนี้นานขึ้นก็เริ่มเห็นว่างานไม่ได้หายไปไหน เพียงแต่มันย้ายจากการปิดงานของตัวเอง ไปเป็นการช่วยให้ทีมส่งมอบงานได้ดีขึ้น</p><h3>ช่วงที่ Vega ยังเป็นทีมใหม่</h3><p>สมาชิกแต่ละคนมาจากคนละ background และมีความถนัดไม่เหมือนกัน บางคนแข็งแรงเรื่อง code บางคนเข้าใจ business flow มากกว่า ขณะที่ payment domain ก็ยังเป็นเรื่องใหม่สำหรับหลายคน หลายอย่างเลยเป็นสิ่งที่พวกเราเรียนรู้ไปพร้อมกัน</p><p>ช่วงนั้นผมใช้เวลาอธิบายค่อนข้างเยอะ ทั้งเรื่อง TrueMoney domain, product, engineering standard และความเสี่ยงของระบบที่ทีมต้องมองให้เห็น</p><p>ในระบบ payment gateway เรื่องเล็กๆ ใน code มักโยงไปไกลกว่าที่เห็นใน backlog ปลายทาง timeout แล้ว transaction ควรอยู่สถานะอะไร การ retry จะทำให้เกิดรายการซ้ำหรือไม่ ทีม Operation จะตามรายการจากข้อมูลไหน และเมื่อ reconciliation ไม่ตรง ใครจะเป็นคนเห็นก่อน</p><p>บางเรื่องผมทำให้ดู บางเรื่องนั่งคิดไปพร้อมกับทีม ส่วนงานที่มีความเสี่ยงสูงก็ review ละเอียดกว่าปกติ คำถามจำนวนมากเลยกลับมาที่ผมในช่วงแรก ซึ่งไม่ได้แปลกอะไร เพราะทุกคนยังอยู่ระหว่างสร้างความเข้าใจร่วมกัน</p><p>พอเวลาผ่านไป สิ่งที่ผมเริ่มสังเกตไม่ใช่แค่ว่าทีมถามน้อยลงหรือไม่ แต่เป็นลักษณะของคำถามที่เปลี่ยนไป ทีมเริ่มเห็น risk ก่อนที่ผมจะถาม เริ่มเสนอทางเลือกมาก่อน และเรื่องที่เคยต้องให้ผมอธิบายก็เริ่มมีคนอื่นช่วยอธิบายแทนได้</p><p>พอเริ่มเห็นสิ่งเหล่านี้ ผมรู้สึกได้ว่าความรู้เริ่มกระจายออกจาก Lead ไปอยู่กับทีมแล้วจริงๆ</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NYQzs1NdYe3VriFHf3Enfg.png" /></figure><h3>จากการส่งต่องาน ไปสู่การส่งต่อ Decision</h3><p>ผมเปิดให้ทีมมีส่วนร่วมกับการตัดสินใจมาตั้งแต่แรก เวลาเจอปัญหา แต่ละคนจะได้เสนอทางเลือก คุยข้อดีข้อเสีย และ challenge ideas หรือ proposed solutions กันก่อน แทนที่จะรอให้ผมเลือกแล้วทุกคนทำตาม</p><p>วิธีนี้ช่วยให้ทีมเห็นมุมที่กว้างขึ้น แต่สิ่งที่ผมประเมินต่ำไปคือ การเปิดพื้นที่ให้คุยไม่ได้แปลว่า discussion จะจบได้เองเสมอไป หลายครั้งทีมเลือกแนวทางแล้ว แต่เรื่องเดิมยังถูกนำกลับมาถก จนการ challenge solution ค่อยๆ กลายเป็นการพยายามพิสูจน์ว่าทางของใครดีกว่า</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*z-OwjSzGK_rb6eyJ0t-2OA.png" /></figure><p>แม้ตัวเรื่องจะไม่ได้ใหญ่เมื่อเทียบกับผลกระทบต่อระบบ แต่กลับใช้เวลาของทีมมาก และบางครั้งก็เริ่มกระทบทั้งคนในวงสนทนาและการทำงานร่วมกัน ตรงนี้ทำให้ผมเริ่มมองการปิด decision มากกว่าแค่เรื่องเวลา เพราะเรื่องที่จบใน meeting อาจยังค้างอยู่กับบางคน และเมื่อเกิดซ้ำๆ ก็อาจกระทบทั้ง team health และ well-being ได้ การคุยแบบ 1-on-1, Retro และ team health check ช่วยให้ผมเห็นสิ่งที่อาจไม่ได้ถูกพูดออกมาในวงใหญ่</p><p>ช่วงนี้ผมลองใช้ 1–2–4-All เพื่อให้แต่ละคนได้คิดก่อน แล้วค่อยแลกเปลี่ยนเป็นคู่ เป็นกลุ่มเล็ก และกลับมาคุยรวมกัน วิธีนี้ช่วยเปิดพื้นที่ให้หลายเสียงมากขึ้น โดยไม่ปล่อยให้ discussion ถูกนำไปโดยคนเพียงไม่กี่คน</p><p>เมื่อถึงจุดที่ต้องเลือก ผมพยายามทำให้ชัดว่าใครเป็น decision owner เรากำลังตัดสินใจจากข้อมูลอะไร และประเด็นไหนควรแยกไปติดตามต่อ บางครั้งผมก็ยังต้องเข้าไปช่วยสรุปหรือปิดเรื่องเอง ตอนนี้ผมยังอยู่ระหว่างลองผิดลองถูก สิ่งที่ยากไม่ใช่แค่การเปิดพื้นที่ให้ทุกคนมีส่วนร่วม แต่คือทำให้ discussion จบลงด้วย decision ที่ทีมเดินหน้าต่อได้ โดยไม่ทิ้งผลกระทบไว้กับคนในทีมในภายหลัง</p><h3>หลายเรื่องเริ่มมี owner ช่วยพาไปต่อ</h3><p>เมื่อทีมทำงานร่วมกันมาระยะหนึ่ง หลายเรื่องก็ไม่จำเป็นต้องผ่านผมเหมือนช่วงแรก คนในทีมเริ่มอธิบาย flow ให้กันได้ มองเห็น risk ก่อนที่ผมจะถาม และเสนอทางเลือกพร้อมข้อดีข้อเสีย แทนที่จะรอคำตอบจาก Lead</p><p>สำหรับ Senior ผมเริ่มมอบบทบาทที่กว้างกว่าการรับงาน technical ที่ยากขึ้น สิ่งที่ผมเริ่มเห็นคือการช่วย shape งานตั้งแต่ต้น ทั้งการมองว่า requirement ยังขาดตรงไหน ควรดึง QA หรือทีมอื่นเข้ามาเมื่อไร และ decision ที่กำลังเลือกจะกระทบ delivery หรือ production อย่างไร</p><p>การเพิ่มความรับผิดชอบให้ใครสักคนไม่ได้ตรงไปตรงมาอย่างที่คิด การเพิ่มแต่งานโดยไม่ได้ส่งต่อข้อมูลหรือสิทธิ์ในการตัดสินใจไปด้วย อาจทำให้คนรับรู้สึกว่าแค่ต้องแบกมากขึ้น</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*50uobjqeBxdRAy3GTKH0iw.png" /></figure><p>อีกด้านหนึ่ง ต่อให้หลายเรื่องไม่ต้องผ่าน Lead แล้ว ทีมก็อาจเพียงเปลี่ยนไปพึ่ง Senior คนเดียวแทน ซึ่งสุดท้ายก็กลายเป็น bottleneck จุดใหม่</p><p>ตอนนี้สิ่งที่ผมกำลังลองทำเลยไม่ใช่แค่ส่งต่องาน แต่พยายามส่งต่อทั้งบริบท สิทธิ์ในการตัดสินใจ และพื้นที่ให้ owner ของเรื่องนั้นช่วยพา discussion ไปข้างหน้าด้วย</p><h3>Technical Detail ยังสำคัญ แต่เหตุผลเปลี่ยนไป</h3><p>ผมยังคงให้เวลากับ technical detail อยู่มาก เพียงแต่เหตุผลไม่เหมือนตอนที่ผมโฟกัสกับ implementation ของตัวเอง</p><p>หลายครั้งปัญหาไม่ได้อยู่ที่ใครเข้าใจผิด แต่อยู่ที่แต่ละฝ่ายเห็นระบบคนละส่วน Business เห็น customer flow, QA เห็น test scenario, Operation เห็นสิ่งที่จะเกิดหลัง release ส่วน Engineer เห็น implementation ตรงหน้า แม้แต่ละฝ่ายจะเข้าใจถูกในส่วนของตัวเอง แต่เมื่อนำทุกส่วนมาต่อกันก็ยังมีช่องว่างได้</p><p>ผมเคยเจอทั้ง API ที่ทำงานได้แต่ตามรายการไม่ได้ Test ที่ผ่านแต่ยังไม่ชัดว่าควร alert เมื่อไร และ error code ที่ถูกต้องในเชิง technical แต่คนรับช่วงต่อไม่รู้ว่าต้องทำอะไร สถานการณ์เหล่านี้ทำให้ผมยังต้องลงไปดูรายละเอียดทางเทคนิค ไม่ใช่เพื่อเป็นคนที่ตอบได้ทุกเรื่อง แต่เพื่อช่วยเชื่อมมุมมองของแต่ละฝ่าย และมองหาส่วนที่อาจไม่ได้เขียนไว้ใน backlog</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oApnlToZdHqllFiUb4R-vQ.png" /></figure><p>บาง solution ดูถูกต้องเมื่อมองเฉพาะ code แต่ยังไม่ตอบปัญหาของระบบจริง งาน review ของผมจึงไม่ได้หยุดอยู่แค่ว่า code เขียนถูกหรือไม่ แต่ต้องย้อนกลับไปดูด้วยว่า สิ่งที่ทีมกำลังทำตอบโจทย์ของทั้ง flow จริงหรือเปล่า</p><h3>จากสิ่งที่ผมทำ ไปสู่สิ่งที่ทีมทำได้</h3><p>เมื่อก่อนผมมองงานจากสิ่งที่ทำเสร็จด้วยตัวเอง แต่หลังจากขยับมาเป็น Lead ผมต้องมองสิ่งที่เกิดขึ้นกับทีมมากขึ้น ทั้งความชัดเจนก่อนเริ่มงาน การมองเห็นความเสี่ยงได้เร็วขึ้น คุณภาพของการตัดสินใจ และความสามารถของทีมในการเดินหน้าต่อได้ด้วยตัวเอง ช่วงหลังผมเริ่มมีทั้ง OKRs และ engineering metrics เช่น planning lead time, PR cycle time และระยะจาก development ไปถึง production มาช่วยดูภาพเหล่านี้มากขึ้น ไม่ใช่เพื่อแทนสิ่งที่เห็นจากการทำงานร่วมกัน แต่เพื่อไม่ให้ผมต้องประเมินทุกอย่างจากความรู้สึกเพียงอย่างเดียว</p><p>วันนี้ผมยังชอบเขียน code และยังรู้สึกดีกับ PR ที่ทำออกมาเรียบร้อย เพียงแต่ PR ของตัวเองไม่ใช่ตัวชี้วัดหลักอีกต่อไป</p><p>การเปลี่ยนจาก Code Ownership ไปสู่ Team Ownership ไม่ได้หมายความว่าผมเขียน code น้อยลง แต่มันคือการที่ความรู้ การตัดสินใจ และความรับผิดชอบค่อยๆ กระจายออกไปอยู่กับทีมมากขึ้น</p><p>บางทีการเติบโตของ Lead อาจไม่ได้วัดจากจำนวนงานที่ตัวเองทำได้มากขึ้น แต่อยู่ที่การสร้างสภาพแวดล้อมที่ทำให้คนอื่นสามารถเติบโต ตัดสินใจ และพาทีมเดินหน้าต่อได้ด้วยตัวเอง</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=744db07cdba4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/from-code-ownership-to-team-ownership-%E0%B8%A7%E0%B8%B1%E0%B8%99%E0%B8%97%E0%B8%B5%E0%B9%88%E0%B8%9C%E0%B8%A1%E0%B9%80%E0%B8%A3%E0%B8%B4%E0%B9%88%E0%B8%A1%E0%B8%A7%E0%B8%B1%E0%B8%94%E0%B8%87%E0%B8%B2%E0%B8%99%E0%B8%88%E0%B8%B2%E0%B8%81%E0%B8%97%E0%B8%B5%E0%B8%A1-%E0%B9%84%E0%B8%A1%E0%B9%88%E0%B9%83%E0%B8%8A%E0%B9%88-pull-request-744db07cdba4">From Code Ownership to Team Ownership วันที่ผมเริ่มวัดงานจากทีม ไม่ใช่ Pull Request</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[สอน Copilot ให้เข้าใจงานด้วย Skills]]></title>
            <link>https://medium.com/ascend-developers/%E0%B8%AA%E0%B8%AD%E0%B8%99-copilot-%E0%B9%83%E0%B8%AB%E0%B9%89%E0%B9%80%E0%B8%82%E0%B9%89%E0%B8%B2%E0%B9%83%E0%B8%88%E0%B8%87%E0%B8%B2%E0%B8%99%E0%B8%94%E0%B9%89%E0%B8%A7%E0%B8%A2-skills-75089a1df120?source=rss----98c9c9d1ba0c---4</link>
            <guid isPermaLink="false">https://medium.com/p/75089a1df120</guid>
            <category><![CDATA[qa]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[claude-skills]]></category>
            <category><![CDATA[github-copilot]]></category>
            <dc:creator><![CDATA[Jittapat Aus]]></dc:creator>
            <pubDate>Tue, 21 Jul 2026 04:40:49 GMT</pubDate>
            <atom:updated>2026-07-21T04:40:48.283Z</atom:updated>
            <content:encoded><![CDATA[<h4>คู่มือสร้าง Custom Skills สำหรับ AI เบื้องต้นฉบับ QA/Tester</h4><blockquote>สวัสดีครับ สำหรับใครที่ยังไม่รู้จักผม จิตพัฒน์ หรือจะเรียกว่า ไอเดียก็ได้ครับ วันนี้ผมอยากจะพาทุกคนมาเจาะลึกเรื่องการสร้าง Skills สำหรับงาน QA/Tester กันครับ!!</blockquote><h4>อย่างแรกมารู้จัก Skills กันก่อน?</h4><p>ให้สมมุติว่า เรามี “<strong>น้อง Junior ที่เก่งมาก” </strong>อยู่คนหนึ่งที่สามารถช่วยงานเราได้ทุกอย่างแต่ทุกวันที่เริ่มทำงาน น้องดันลืมทุกอย่างที่เคยเรียนรู้มาหมดเลย เราเลยต้องนั่งอธิบายวิธีทำงานให้เขาใหม่ทุกวัน มันน่าเบื่อใช่มั้ยล่ะ นี่แหละคือปัญหาของการใช้ AI แบบเดิมๆ ที่เราต้องเขียน prompt ใหม่ทุกครั้ง</p><p>Skill จึงถือกำเนิดขึ้นมา โดยมันเป็นโครงสร้าง Standard ของระบบ AI Agent ยุคใหม่ (เช่น Claude Skills ของ Claude เป็นต้น) ที่ช่วยจัดระเบียบ/เพิ่มทักษะให้ AI โดย Skill หรือ Agent Skills คือ “ชุดความสามารถเฉพาะทาง” ที่เราสร้างขึ้นเพื่อสอนให้ AI ทำงานตามที่เรากำหนด (Task) ได้อย่างแม่นยำและตรงกับบริบทของทีมมากขึ้น เหมือนเรามี Guideline สำหรับการทำงานให้ <strong>Junior </strong>คนนั้นอ่าน แล้วเขาก็จะทำตามได้เลยโดยไม่ต้องอธิบายซ้ำทุกครั้งที่เราต้องการนั้นเอง</p><h4>โครงสร้างของ Skill</h4><p>Skill หนึ่งตัวจะประกอบไปด้วย 1 Folder ที่มีส่วนประกอบดังนี้</p><pre>my-skill/<br>├── SKILL.md          ← ไฟล์หลักที่มีคำสั่งอยู่ (บังคับต้องมี)<br>├── scripts/          ← สามารถเพิ่มโค้ดที่จะให้รันเมื่อใช้ skill ได้ (ถ้ามี)<br>├── references/       ← สามารถเพิ่มแหล่งข้อมูล/เอกสารอ้างอิงได้ (ถ้ามี)<br>└── assets/           ← สามารถเพิ่ม template, รูปภาพ ฯลฯได้ (ถ้ามี)</pre><p>โดย ไฟล์ SKILL.md จะเป็นไฟล์ Markdown ที่ประกอบด้วย <br>- <strong>YAML frontmatter</strong> เป็น Metadata ที่ระบบใช้ตัดสินใจว่าควรเรียกใช้ Skill นี้ตอนไหน <br>- <strong>Instructions </strong>คือชุดคำสั่ง (Prompt) ที่ระบุขั้นตอนการทำงานอย่างละเอียด รวมถึงการกำหนดคำสั่ง (Scripts) และตัวแปร (Parameters) ต่าง ๆ ที่จำเป็น<br>- <strong>Additional files</strong> เช่น scripts ที่จะให้รัน, เทมเพลต หรือรูปภาพ, แหล่งอ้างอิงหรือ เอกสารอ้างอิงที่ใช้เป็น ข้อมูล, ซึ่งจะช่วยให้ AI ของเราเข้าใจงานได้มากขึ้น</p><h4><strong>ทำไมต้องใช้ MCP ควบคู่กับ Skills?</strong></h4><p>ลองจินตนาการว่า AI ของเราเป็นช่างซ่อมบ้าน 🏠</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3n-xkYJQGmE1MamcNrlvKQ.png" /></figure><p>MCP คือตัวเชื่อมต่อกับระบบภายนอก เปรียบเสมือน <strong>“กล่องเครื่องมือ”</strong> ที่ช่างพกไปด้วย (ข้างในก็จะมี ไขควง, ค้อน, มิเตอร์วัดไฟ) ถ้าไม่มี MCP ช่างก็จะได้แต่ยืนมอง เพราะไม่มีอุปกรณ์ชิ้นไหนเข้าถึงหน้างานได้เลย เปรียบ MCP เหมือนเป็นมือของ Copilot ที่ไว้จับเครื่องมือ</p><p>ส่วน Skills คือ “<strong>ทักษะและประสบการณ์”</strong> ในหัวของช่าง ที่บอกว่าเมื่อหยิบไขควงมาแล้ว ต้องไขน็อตตัวไหนก่อนหลังตามลำดับ</p><p>ทั้งสองอย่างนี้ทำงานเสริมกัน MCP ให้ความสามารถในการเข้าถึง ส่วน Skills ให้ความรู้ในการใช้งาน เวิร์กโฟลว์ที่ดีคือการใช้ทั้งสองอย่างร่วมกัน เช่น มี MCP เชื่อมต่อกับ Jira แล้วมี Skill ที่สอน Copilot ว่าเมื่อเจอข้อนี้จากใน Jira แล้วต้องวิเคราะห์และสรุปตอบมายังไง</p><h4>มาลองดูตัวอย่างการใช้งานจริงกัน เพื่อให้เห็นภาพว่า Skills สามารถช่วยอะไรเราได้บ้าง ?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JfdTVuEHOcZP4hpx5TdbFg.png" /></figure><p>จากตัวอย่างด้านล่างนี้คือ Skill ที่ชื่อว่า <strong>“testcase-generator”</strong> ซึ่งถูกสร้างขึ้นมาเพื่อให้ สร้าง Test Case ตาม requirements ที่มาจาก Jira cards หรือ Confluence specs</p><p>ซึ่งนี่คือตัวอย่างการทำงานร่วมกันระหว่าง <strong>MCP และ Skills</strong> ที่ชัดเจนมาก โดยใน skill นี้เราจะใช้ “<strong>mcp-atlassian”</strong> จะทำหน้าที่เป็นสะพานเชื่อมเข้าไปยัง Jira หรือ Confluence ส่วน <strong>Skill</strong> จะเป็นตัวออกคำสั่งและไกด์ทางให้ Copilot รู้ว่าต้องไป Analysis และออกแบบ Testcase อย่างไงบ้าง?</p><pre>---<br>name: &quot;testcase-generator&quot;<br>description: &gt;<br>  Generate comprehensive backend API test cases from Jira cards and Confluence specs.<br>  Fetches requirements via mcp-atlassian, analyzes API specifications, and produces<br>  structured Markdown test case tables covering happy paths, edge cases, and negative<br>  testing scenarios. Invoke when the user asks to &quot;generate test cases&quot;, &quot;create test<br>  cases from Jira&quot;, &quot;write API tests&quot;, or provides a Jira/Confluence link and asks<br>  for test coverage.<br>capabilities:<br>  - Fetch and parse API specifications from Jira issues and Confluence pages<br>  - Identify RESTful endpoints, request/response schemas, status codes, and business rules<br>  - Generate test cases for happy paths, edge cases, and error/negative scenarios<br>  - Output results as a structured Markdown table<br>---<br><br># Test Case Generator<br><br>You are a **Senior QA Engineer** specializing in backend API testing. Your job is to<br>produce comprehensive, implementation-ready test cases from API specifications stored<br>in Jira and Confluence.<br><br>---<br><br>## 1. Input Acquisition<br><br>When the user provides a **Jira issue key** (e.g., `PROJ-1234`) or a **Confluence page<br>URL/title**, use the **mcp-atlassian** MCP server to retrieve the content:<br><br>### Jira<br><br>Use the `jira_get_issue` tool to fetch the issue. Extract:<br><br>- **Summary** and **Description** (may contain API spec or acceptance criteria).<br>- **Acceptance Criteria** or **Definition of Done** sections.<br>- **Attachments** or **linked Confluence pages** that contain detailed API specs.<br>- **Labels, Components, Fix Version** for context on scope.<br><br>If the Jira issue references a Confluence page, follow the link.<br><br>### Confluence<br><br>Use the `confluence_get_page` or `confluence_search` tool to fetch the page content.<br>Extract:<br><br>- Endpoint definitions: **method**, **path**, **query/path parameters**.<br>- **Request body schema** (field names, types, required/optional, constraints).<br>- **Response body schema** and **HTTP status codes**.<br>- **Business rules**, validation logic, and authorization requirements.<br>- **Data models** and enum/allowed values.<br><br>---<br><br>## 2. Specification Analysis<br><br>Before generating test cases, perform a structured analysis of the extracted spec.<br>Identify and mentally catalog:<br><br>### 2.1 Endpoints<br><br>For each endpoint, record:<br><br>| Attribute         | Details                                              |<br>| ----------------- | ---------------------------------------------------- |<br>| Method &amp; Path     | e.g., `POST /api/v1/users`                           |<br>| Authentication    | Bearer token, API key, none, etc.                    |<br>| Authorization     | Required roles/permissions                           |<br>| Path Parameters   | Name, type, constraints                              |<br>| Query Parameters  | Name, type, required/optional, defaults, constraints |<br>| Request Body      | Schema with field types, required flags, constraints |<br>| Response Codes    | 200, 201, 400, 401, 403, 404, 409, 422, 500, etc.   |<br>| Response Body     | Schema per status code                               |<br><br>### 2.2 Business Rules<br><br>- Field validation rules (min/max length, regex patterns, allowed values).<br>- Cross-field dependencies (e.g., `endDate` must be after `startDate`).<br>- State-machine constraints (e.g., cannot cancel an already-completed order).<br>- Rate limits, pagination rules, idempotency requirements.<br><br>### 2.3 Edge-Case Indicators<br><br>Flag anything that commonly produces edge cases:<br><br>- Nullable vs. required fields.<br>- Boundary values (0, 1, max, max+1).<br>- Empty collections vs. missing fields.<br>- Unicode / special characters in string fields.<br>- Concurrent access or duplicate submission scenarios.<br><br>---<br><br>## 3. Test Case Generation Rules<br><br>Generate test cases organized into **three categories**. Use the following ID<br>convention:<br><br>| Category    | ID Prefix | Description                                           |<br>| ----------- | --------- | ----------------------------------------------------- |<br>| Happy Path  | `HP-XXX`  | Valid inputs producing expected successful outcomes    |<br>| Edge Case   | `EC-XXX`  | Boundary, unusual-but-valid, or tricky-but-valid cases|<br>| Error Case  | `ERR-XXX` | Invalid inputs, unauthorized access, constraint violations |<br><br>### 3.1 Happy Path (`HP-XXX`)<br><br>Generate test cases for:<br><br>- **Basic CRUD** success for each endpoint.<br>- All **optional fields** present vs. only required fields.<br>- Each valid **enum/status value**.<br>- Successful **authentication and authorization** with correct credentials/roles.<br>- Pagination with default and custom page sizes (if applicable).<br>- Filtering/sorting with valid parameters (if applicable).<br><br>### 3.2 Edge Cases (`EC-XXX`)<br><br>Generate test cases for:<br><br>- **Boundary values**: minimum, maximum, and just-within-range values for numeric<br>  fields, string lengths, array sizes, and date ranges.<br>- **Empty vs. null**: empty string `&quot;&quot;` vs. `null` vs. field omitted entirely.<br>- **Special characters**: Unicode, emojis, SQL-injection-like strings (as data, not<br>  as attacks), HTML entities in string fields.<br>- **Large payloads**: maximum allowed collection sizes, deeply nested objects.<br>- **Concurrent operations**: duplicate submissions, race conditions (describe<br>  scenario; actual concurrency testing is out of scope for the table).<br>- **Idempotency**: repeated identical requests where the API should be idempotent.<br>- **Default values**: verify server-assigned defaults when optional fields are<br>  omitted.<br><br>### 3.3 Error / Negative Cases (`ERR-XXX`)<br><br>Generate test cases for:<br><br>- **Missing required fields** (one at a time, then all at once).<br>- **Invalid data types** (string where number expected, number where boolean, etc.).<br>- **Constraint violations**: too short, too long, out of range, invalid format<br>  (e.g., bad email, invalid date).<br>- **Invalid enum values**.<br>- **Unauthorized access**: missing token, expired token, invalid token.<br>- **Forbidden access**: valid token but insufficient role/permissions.<br>- **Resource not found**: valid format ID that doesn&#39;t exist.<br>- **Conflict / duplicate**: creating a resource that already exists (409).<br>- **Method not allowed**: using wrong HTTP method on an endpoint.<br>- **Unsupported media type**: wrong `Content-Type` header.<br><br>---<br><br>## 4. Output Format<br><br>Present **all** generated test cases in a single Markdown table with these columns:<br><br>```<br>| ID       | Description                                         | Input                                                    | Expected Result                                    |<br>| -------- | --------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------- |<br>| HP-001   | Create user with all valid fields                   | POST /api/v1/users { &quot;name&quot;: &quot;John&quot;, &quot;email&quot;: &quot;...&quot; }   | 201 Created; response body contains user ID        |<br>| EC-001   | Create user with name at max length (255 chars)     | POST /api/v1/users { &quot;name&quot;: &quot;&lt;255 chars&gt;&quot;, ... }        | 201 Created; name stored and returned in full      |<br>| ERR-001  | Create user without required email field             | POST /api/v1/users { &quot;name&quot;: &quot;John&quot; }                    | 400 Bad Request; error message indicates email required |<br>```<br><br>### Column Guidelines<br><br>| Column          | Content Rules                                                        |<br>| --------------- | -------------------------------------------------------------------- |<br>| **ID**          | Prefixed category + sequential number: `HP-001`, `EC-001`, `ERR-001`|<br>| **Description** | One-line summary of what is being tested and why                     |<br>| **Input**       | HTTP method, path, and relevant request body / query params (use     |<br>|                 | concise JSON; abbreviate large payloads with `&lt;description&gt;`)        |<br>| **Expected Result** | Expected HTTP status code + key assertions on the response body  |<br>|                 | or headers. Include error message patterns where applicable.         |<br><br>---<br><br>## 5. Execution Workflow<br><br>Follow these steps in order:<br><br>1. **Receive input** — The user provides a Jira key, Confluence link, or raw API spec.<br>2. **Fetch spec** — Use `mcp-atlassian` tools to retrieve the full specification.<br>3. **Analyze** — Parse endpoints, schemas, rules, and constraints (Section 2).<br>4. **Generate** — Produce test cases per the rules in Section 3.<br>5. **Format** — Output the complete Markdown table (Section 4).<br>6. **Summarize** — After the table, provide a brief summary:<br>   - Total test case count broken down by category.<br>   - Any assumptions made due to ambiguous or missing spec details.<br>   - Suggested areas that need clarification from the product owner or developer.<br><br>---<br><br>## 6. Quality Checklist<br><br>Before presenting the final output, verify:<br><br>- [ ] Every endpoint in the spec has at least one happy-path test.<br>- [ ] Every required field has a missing-field error test.<br>- [ ] Every field with constraints has boundary/edge-case tests.<br>- [ ] Authentication and authorization are covered (if applicable).<br>- [ ] Error response structures match the documented API error format.<br>- [ ] IDs are sequential within each category and do not repeat.<br>- [ ] Descriptions are specific enough that another QA engineer can implement<br>      the test without referring back to the spec.<br>- [ ] Input column contains enough detail to reproduce the request.<br><br>---<br><br>## 7. Assumptions &amp; Conventions<br><br>- If the spec is ambiguous about a constraint, **generate the test case anyway**<br>  and note the assumption in the summary.<br>- Use realistic but fictional data in examples (e.g., `john.doe@example.com`).<br>- Do not generate security penetration tests (SQLi, XSS payloads as attacks);<br>  only test that the API properly rejects malformed input.<br>- When the spec mentions pagination, always include tests for first page, last<br>  page, page beyond range, and invalid page number.<br>- For date/time fields, test ISO 8601 format compliance and timezone handling<br>  unless the spec says otherwise.</pre><p>โอเคมาถึงตรงนี้ หลายท่านคงบอกว่า <em>‘โอ้โห ทำไมมันดูวุ่นวายจัง ดูเหมือนต้องไปใส่นู่นนี่นั่นเยอะแยะเต็มไปหมด’. </em>ข่าวดีคือเราไม่ต้องเขียน SKILL.md จากศูนย์เอง — เราบอก Copilot ได้เลยว่าอยากได้ Skill แบบไหน แล้วให้มันช่วยร่างให้</p><h4>แล้วสำหรับ QA/Tester เราควรสร้าง Skill อะไร ? 🧐</h4><p>ก่อนจะเริ่มทำกัน ผมเชื่อว่าในบริษัทที่มีทีมอยู่มากมาย ก็คงมีความต้องการ Skill ที่เเตกต่างกันไปตามแต่ละทีม ดังนั้น Skill ที่ดีคือ skill ที่ตอบโจทย์บริบท หรือความต้องการของผู้ใช้ได้ โดยเราควรเน้นให้แต่ละ skill มีความเฉพาะเจาะจง โดย 1 skill ควรทำหน้าที่เดียว (Single Responsibility) และกำหนด คำอธิบาย (Description) ให้แม่นยำเพื่อช่วยให้ AI ตัดสินใจเรียกใช้ได้อย่างถูกต้อง</p><h4>เราลองมาสร้าง Skill เเรก ด้วย Copilot กันดีกว่า 😙</h4><p>ก่อนที่เราจะ prompt ให้ Copilot สร้าง skill ให้เรา ผมอยากจะเเนะนำหนึ่งในวิธีที่จะทำให้ Skill ที่ Copilot สร้างดียิ่งขึ้น นั้นก็คือ การใช้ Skill สำหรับสร้าง Skill นั้นเอง โดยเราสามารถหา Skill เหล่านี้มาได้จาก Web อย่าง <a href="https://www.skills.sh/">https://www.skills.sh/</a> ซึ่งเป็นแหล่งรวบรวม Skill มากมาย โดยเราสามารถ Search หา Skill ที่ต้องการเเล้ว ดาวน์โหลดมาติดตั้งใช้งานใน Copilot ของเราได้นั้นเอง</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zHQZFaLebGYyPhlL5fc-0A.png" /><figcaption><a href="https://www.skills.sh/anthropics/skills/skill-creator">https://www.skills.sh/anthropics/skills/skill-creator</a></figcaption></figure><p>หลักการเขียน prompt สำหรับสร้าง Skill มี 3 ส่วนสำคัญ<br>(ทุกคนสามารถเอาไปปรับต่อได้)</p><ol><li>บอกว่าคุณ หรือ AI เป็นใครเเล้วทำงานอะไร? (Role)<br>“ฉันเป็น QA Engineer ในทีม backend เราทำงานกับ REST API และ ใช้ Jira เป็น Tool สำหรับแบ่งงาน มี Confluence ใช้อธิบายรายละเอียดของ API เส้นต่างๆ ว่ามันมีไว้ทำอะไร? ”</li><li>บอกว่าอยากให้ skill นี้ทำอะไร? (Task)<br>“อยากได้ skill ที่ ช่วยดู API spec จาก Jira และ Confluence แล้ว Suggest test case ให้อัตโนมัติ โดยครอบคลุม happy case, error case และ edge case”</li><li>บอก output ที่ต้องการ? (Format)<br>“output ควรอยู่ในรูปของ markdown table ที่มี คอลัมน์ Features, Testcase ID, Name, Objective, Test Data, …. ,Expected Result”</li></ol><p>โดยเราจะเรียก prompt framework แบบนี้ว่า RTF ย่อมาจาก Role + Task + Format (นอกจาก framework นี้ยังมี framework อื่นๆอีกซึ่งทุกท่านสามารถไปหาเพิ่มเติมได้)</p><h4>ตัวอย่าง Prompt</h4><p>จำไว้ว่าสิ่งสำคัญคือ ยิ่งบอก Context มากเท่าไร skill ที่ได้ก็จะ fit กับการทำงานจริงมากขึ้นเท่านั้น</p><pre>Act as an AI Assistant expert in creating MCP (Model Context Protocol) skills. <br>Please create a skill.md file for a custom GitHub Copilot skill designed for a QA Engineer focusing on backend systems.<br><br>Goal:<br>Automate test case generation from API specifications found in Jira and Confluence.<br><br>Requirements:<br>1. Integration: The skill must be configured to use the mcp-atlassian server to fetch requirements from Jira and Confluence links provided as input.<br>2. Logic: Analyze the API specs and generate comprehensive test cases covering: Happy paths,Edge cases, Error cases (negative testing)<br>3. Output Format: Present the output as a Markdown table with the following columns: ID, Description, Input, and Expected Result.<br><br>Deliverables:<br>- Please provide the full skill.md content, including the necessary frontmatter (name, description, capabilities) and detailed instructions for the LLM.<br>- Ensure the instructions are clear, technical, and ready to be implemented.<br>- Output the final response as a downloadable Markdown file.</pre><pre> Act as an AI Assistant expert in creating MCP (Model Context Protocol) skills.<br> Please create a skill.md file for a custom GitHub Copilot skill designed for a QA Engineer focusing on backend systems.<br> <br> Goal:<br> Automate test case generation from API specifications found in Jira and Confluence,<br> following the team&#39;s established conventions defined in the reference files below.<br> <br> Requirements:<br> 1. Integration: The skill must use the mcp-atlassian MCP server to fetch requirements<br>    from Jira (via `jira_get_issue`) and Confluence (via `confluence_get_page` or<br>    `confluence_search`) when the user provides a Jira issue key or Confluence URL/title.<br> <br> 2. Logic: Analyze the fetched API specs and generate test cases covering:<br>    - Happy paths / success scenarios<br>    - Input validation / edge cases<br>    - Error / negative testing scenarios<br>    - Business rule violations<br>    - Service unavailability scenarios<br> <br> 3. Output Format: Generate test cases in an Excel-compatible table structure following<br>    the 16-column format defined in the Test Case Structure reference. Each column must<br>    be populated according to the rules in that reference.<br> <br> 4. Naming Conventions: Follow the Test Case ID format and Test Case Name patterns<br>    exactly as defined in the Naming Patterns reference.<br> <br> 5. Expected Results Format: All expected results must use the [CHK##] checkpoint format<br>    defined in the Expected Results reference. Each checkpoint must have a descriptive<br>    header and bullet-point assertions.<br> <br> 6. Tagging &amp; Priority: Apply tags and priority following the taxonomy in the Tags and<br>    Priorities reference. Every test case must have test type, service, feature, and<br>    priority tags.<br> <br> 7. Scenario Coverage: Use the Scenario Coverage Checklist from the Test Scenarios<br>    reference to ensure completeness before finalizing output.<br> <br> Reference Files (include these as-is in the skill&#39;s references/ folder):<br> <br> --- references/test-case-structure.md ---<br> {paste content of test-case-structure.md here}<br> <br> --- references/naming-patterns.md ---<br> {paste content of naming-patterns.md here}<br> <br> --- references/expected-results.md ---<br> {paste content of expected-results.md here}<br> <br> --- references/tags-priorities.md ---<br> {paste content of tags-priorities.md here}<br> <br> --- references/test-scenarios.md ---<br> {paste content of test-scenarios.md here}<br> <br> Deliverables:<br> - Provide the full skill.md content including frontmatter (name, description, capabilities).<br> - The skill instructions must reference the above files using relative paths<br>   (e.g., [Test Case Structure](references/test-case-structure.md)) so the LLM can<br>   load them at runtime.<br> - Include a Workflow Decision Tree so the LLM knows when to fetch from Jira/Confluence<br>   vs. when to proceed from a raw spec.<br> - Include a Quality Checklist the LLM must self-verify before presenting output.<br> - Ensure instructions are clear, technical, and implementation-ready.</pre><h4>วิธีนำ Skill ของเราไปติดตั้งเข้ากับ Copilot CLI</h4><p>เมื่อเราได้ Folder Skill ของเรามาเเล้วเราสามารถเอาไปใส่ใน copilot CLI ของเราได้</p><pre>#1 ไปเอาไฟล์มาใส่ตรงๆ<br>~/.copilot/skills/&lt;ชื่อ skill ของเรา&gt;/</pre><p>วิธีเรียกใช้ Skill ในการทำงานจริง สามารถทำได้ 2 แบบครับ:</p><ol><li><strong>เรียกใช้งานโดยตรง (Explicit Intent):</strong> บน Copilot CLI เราสามารถพิมพ์ชื่อ Skill ได้ทันที เช่น code-review หรือ create-report วิธีนี้ Copilot จะเข้าใจทันทีว่าเราต้องการเจาะจงใช้ทักษะไหนโดยไม่ต้องเดา</li><li><strong>เรียกใช้ผ่านภาษาธรรมชาติ (Natural Language):</strong> วิธีนี้ง่ายและเป็นธรรมชาติที่สุด เพราะเราแค่พิมพ์สั่งงานตามปกติ แล้วระบบจะวิเคราะห์ให้เองว่าคำสั่งของเราตรงกับ Skill ไหนที่มีอยู่ จากนั้นจะโหลดมาทำงานให้อัตโนมัติ เช่น หากเราพิมพ์ว่า <em>“ช่วยรีวิวโค้ดนี้หน่อย”</em> และในเครื่องเราติดตั้ง Skill code-review เอาไว้ Copilot ก็จะหยิบมาใช้จัดการให้เราทันทีครับ</li></ol><h4>Tips/ข้อผิดพลาดที่มักเจอ 😣</h4><p>สิ่งที่คนมักสะดุดตอนสร้าง skill ครั้งแรก</p><ol><li>Frontmatter ไม่ชัดทำให้ AI ไม่ trigger skill เลย<br>ปัญหาที่เจอบ่อยที่สุด คือเขียน description กว้างเกินไป เช่น “ช่วยเรื่อง QA” แบบนี้ Copilot ไม่รู้ว่าจะเรียก skill นี้ตอนไหน ควรระบุให้ชัดว่า trigger เมื่อไหร่ เช่น “ใช้เมื่อผู้ใช้ส่ง API endpoint มาและขอให้ช่วยเขียน test case”</li><li>Instructions ยาวเกินไปโดยไม่มีโครงสร้าง<br>เขียน instructions แบบ paragraph ยาวๆ ทำให้ AI งง ควรแบ่งเป็น step หรือ bullet ชัดๆ ว่าให้ทำอะไรก่อนหลัง</li><li>ไม่ได้ทดสอบหลังสร้าง<br>เมื่อสร้างเสร็จแล้วต้องลองจริงๆ ด้วยการส่ง input ที่หลากหลาย ดูว่า output ตรงกับที่ต้องการไหม แล้วค่อยปรับ instructions ให้แม่นขึ้น</li><li>สร้าง skill เดียวให้ทำทุกอย่าง<br>อย่างที่กล่าวไปข้างต้น skill ที่ดีควรมีความเฉพาะเจาะจง เราไม่ควรสร้าง skill ตัวเดียวและให้มันช่วยทำทุกๆ อย่างเพราะมันอาจทำให้ ผลลัพธ์ที่ได้ออกมาไม่ดีนั้นเอง</li></ol><h4>สรุป</h4><p>ถ้าอ่านมาถึงตรงนี้ คิดว่าทุกคน น่าจะพอเห็นภาพรวมของการสร้าง skill ด้วย Copilot กันเเล้วสิ่งที่ควรทำต่อจากนี้คือ ลองเอา skill ไปใช้งานจริงในงาน QA ของคุณสัก 1 สัปดาห์ แล้วสังเกตว่ามันช่วยตรงไหนได้บ้าง ตรงไหนยังขาด แล้วค่อยกลับมาปรับ instructions ให้ดีขึ้น Skill ที่ดีไม่ได้เกิดจากการเขียนครั้งเดียวแล้วสมบูรณ์ แต่เกิดจากการทำ<strong>ซ้ำ</strong>ไปเรื่อยๆ นั่นเองครับ</p><p>ขอบคุณที่อ่านจนจบครับ 🙏🏻</p><p>แหล่งอ้างอิง</p><ul><li><a href="https://www.grandlinux.com/blogs/claude-skill.html">Claude Skills คืออะไร? คู่มือ Agent Skills ฉบับเจาะลึก | Saeree ERP</a></li><li><a href="https://www.thadaw.com/posts/agent-skill-teach-ai-to-do-what-it-cant?id=fose7zs">Agent Skill: สอน AI Agent ให้ทำงานที่มันทำไม่ได้ | Thada W.</a></li><li><a href="https://ai-rockstars.com/prompt-engineering/">Prompt Engineering: Complete Guide (2026)</a></li><li><a href="https://www.skills.sh/anthropics/skills/skill-creator">skill-creator - anthropics/skills</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=75089a1df120" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/%E0%B8%AA%E0%B8%AD%E0%B8%99-copilot-%E0%B9%83%E0%B8%AB%E0%B9%89%E0%B9%80%E0%B8%82%E0%B9%89%E0%B8%B2%E0%B9%83%E0%B8%88%E0%B8%87%E0%B8%B2%E0%B8%99%E0%B8%94%E0%B9%89%E0%B8%A7%E0%B8%A2-skills-75089a1df120">สอน Copilot ให้เข้าใจงานด้วย Skills</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ถอดรหัส AWS Summit ทำไม QA ต้องรู้เรื่อง Infrastructure?]]></title>
            <link>https://medium.com/ascend-developers/%E0%B8%96%E0%B8%AD%E0%B8%94%E0%B8%A3%E0%B8%AB%E0%B8%B1%E0%B8%AA-aws-summit-%E0%B8%97%E0%B8%B3%E0%B9%84%E0%B8%A1-qa-%E0%B8%95%E0%B9%89%E0%B8%AD%E0%B8%87%E0%B8%A3%E0%B8%B9%E0%B9%89%E0%B9%80%E0%B8%A3%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%87-infrastructure-4b8dd6d24024?source=rss----98c9c9d1ba0c---4</link>
            <guid isPermaLink="false">https://medium.com/p/4b8dd6d24024</guid>
            <category><![CDATA[infrastructure]]></category>
            <category><![CDATA[qa]]></category>
            <category><![CDATA[aws-event]]></category>
            <dc:creator><![CDATA[Poopae Paisingkhon]]></dc:creator>
            <pubDate>Mon, 29 Jun 2026 08:08:15 GMT</pubDate>
            <atom:updated>2026-06-29T08:08:14.283Z</atom:updated>
            <content:encoded><![CDATA[<p>หลังจากบทความที่แล้วที่เป้ได้แชร์เรื่องการขยับบทบาทสู่การเป็น <strong>QA Orchestrator</strong> ในยุค AI-DLC ไป วันนี้เป้มีอีกหนึ่งแง่คิดสำคัญจากงาน AWS Summit Bangkok 2026 ที่อยากมาชวนทุกคนพูดคุยกันค่ะ นั่นคือคำถามที่ว่า:</p><blockquote><strong><em>“ทำไมพวกเราชาว QA ถึงต้องรู้เรื่อง Infrastructure บ้างล่ะ? ทำไมไม่ปล่อยให้คนอื่นเค้าทำไป?”</em></strong></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QI1zraUW262T-_iQRafEBw.jpeg" /></figure><p>ยอมรับตรง ๆ เลยค่ะว่าเป้เองก็เคยโดนคำถามนี้มาเหมือนกัน (หัวเราะ) และสารภาพตามตรงว่าคำตอบในใจ (หรือเปล่านะ) ตอนนั้นคือ <strong>“เดี๋ยวเถียงกับคนอื่นไม่ได้ค่ะ! 😂”</strong></p><p>แต่ถ้าตอบแบบจริงจังและดูดีขึ้นมาหน่อย เหตุผลจริง ๆ คือ ถ้าเราพอรู้เรื่องโครงสร้างหลังบ้านบ้าง มันจะช่วยให้เราเข้าใจภาพรวมของระบบทั้งหมด และมีข้อกังวลต่อระบบที่คมขึ้นเวลาเจอปัญหาค่ะ</p><p>จากเดิมที่หลายคนมองว่าเรื่อง Infrastructure, การตั้งค่าคลาวด์ (Cloud Config) หรือระบบเน็ตเวิร์กหลังบ้าน เป็นหน้าที่ของฝั่ง CloudOps, Network, System, DevOps หรือ Site Reliability Engineer (SRE) ฝ่ายเดียว (ที่เยอะอยู่) แต่จากเซสชันในงาน AWS รอบนี้ และจากสิ่งที่เป้ได้เรียนรู้จากการทำงานที่ Ascend หลายปี มันทำให้เป้ตกผลึกเลยว่า:</p><blockquote><strong><em>“หาก QA ไม่รู้เรื่องพวกนี้เลย เวลาเจอ Performance Drop หรือแอปสตาร์ทไม่ขึ้น เราก็อาจจะหลงทางและมุ่งเป้าไปงมแก้แค่ที่ตัวโค้ดแอปพลิเคชันอย่างเดียว ทั้งที่จริง ๆ มันอาจจะเป็นเรื่องของการปรับจูนระหว่างตัวแอปกับ Infrastructure ให้สอดคล้องกัน ซึ่งการที่เราพอมีความรู้ในส่วนนี้บ้าง จะทำให้เราดีไซน์แผนการเทสได้แม่นยำขึ้น คุยภาษาเดียวกับทีมอื่นได้เข้าใจ และช่วยกันอุดรอยรั่วเพื่อหาส่วนผสมที่ลงตัวที่สุดให้กับระบบ โดยไม่ต้องปล่อยให้เป็นหน้าที่ของฝั่งใดฝั่งหนึ่งฝ่ายเดียวค่ะ”</em></strong></blockquote><p>และในฐานะคนที่คุมภาพรวมด้านคุณภาพของระบบ การเทสในยุคนี้ไม่ได้จบลงแค่การตรวจสอบว่าฟังก์ชันทำงานถูกหรือยิง API ผ่าน แต่คือการการันตีว่าระบบจะยังคงเสถียร ทนทาน และสามารถรองรับผู้ใช้งานในพฤติกรรมต่าง ๆ ได้ดีด้วย</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E7REwOAUUkneLiH5anHesw.jpeg" /></figure><p>ในงาน AWS รอบนี้เป้ได้เข้าไปฟังอีกหนึ่งเซสชันที่น่าสนใจมาก ๆ คือ <strong>“Developer Considerations for Overcoming Multi-Region Deployment Challenges”</strong> ซึ่งเป็นเรื่องการจัดการระบบแบบ Multi-region</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*L1ceAqN4oEo0l1EmW6frPg.jpeg" /></figure><p>เรื่องนี้ประจวบเหมาะกับที่ปัจจุบันทีมของเป้ที่ Ascend ก็มีงานเปลี่ยนจากการใช้ RDS เป็น Aurora database เพื่อรองรับการทำระบบ Multi-region นี้อยู่เหมือนกัน ที่ช่วยลดผลกระทบเมื่อเกิดกรณีที่ Site ใด Site หนึ่งล่มหรือใช้งานไม่ได้ ระบบของเราจะได้ยังมีอีก Site คอยสแตนด์บายทำงานต่อได้ทันที มันก็ทำให้เข้าใจเบื้องลึกมากขึ้นว่า ทำไมทีมถึงเปลี่ยนจาก RDS มาใช้ตัว Aurora DB รวมไปถึงดีเทลการตั้งค่า Config โหมดต่าง ๆ ของ Aurora เพื่อรองรับการสเกลข้าม region รวมทั้งได้เห็นตัวอย่างการเซตดีไซน์ระบบการทำ Multi-region ที่ทาง AWS แนะนำเป็นอย่างไร</p><p>และจริง ๆ แล้วทาง Ascend เองมีการวางระบบ Infrastructure หลังบ้านที่ดีมาก ๆ อยู่แล้ว ซึ่งพี่เต้ Head of platform Engineering ที่ Ascend money ได้มาแชร์ในงาน AWS ปีนี้เช่นกันในหัวข้อ <em>“4 AWS EKS Configurations That Unlock Scale”</em> ทุกคนสามารถตามไปอ่านแบบ Full Detail ได้จากโพสต์ของ <strong>พี่เต้</strong> ตามลิงก์นี้ได้เลยนะคะ 👉 <a href="https://www.facebook.com/share/p/1Lm2ycMRdF/">https://www.facebook.com/share/p/1Lm2ycMRdF/</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bTpxCmRPDnd5dgzpO_vVxA.png" /><figcaption><em>“4 AWS EKS Configurations That Unlock Scale” Sharing Session</em></figcaption></figure><p>การที่ทีม Infra ของเราวางโครงสร้างที่ยอดเยี่ยมไว้ให้ตั้งแต่แรก ส่งผลให้ทั้ง Developer และ QA สามารถนำมาใช้ต่อยอดในการสร้างและส่งมอบ Application ได้อย่างมีประสิทธิภาพ</p><p>แต่สิ่งสำคัญคือ ในฐานะ QA เราจะมั่นใจได้อย่างไรว่าค่าที่เรา Setting ไว้นั้นทำงานได้ถูกต้องตรงตามทฤษฎี?</p><p>เป้ขอยกตัวอย่างเทส Test Scenario: <strong>Probe Behavior Testing</strong> ที่ได้มีทำเพื่อพิสูจน์และตรวจสอบการตั้งค่าระบบ healthcheck ของ Kubernetes ทั้งในส่วนของความถี่ (periodSeconds) และเกณฑ์ขั้นต่ำที่ยอมให้ล้มเหลว (failureThreshold) ว่ามีความเหมาะสมกับ Service ของเราหรือไม่ โดยเราสามารถดูข้อมูลผ่าน Kube log ได้ เพื่อเช็กพฤติกรรมหลังบ้านได้ค่ะ จริง ๆ อันนี้มี work กับ Devopsให้ช่วยเอา Kubernetes log จาก Cloud Watch ออกมาให้ดู</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RVrhS4JO5tAULo3tRZ8VUQ.png" /><figcaption>Kubernetes log from <strong>Probe Behavior Testing</strong></figcaption></figure><p>เป้าหมายสำคัญของการทำ Probe Behavior Testing ในสเตปนี้คือ:</p><p><strong>1.เช็กความถูกต้องของการตั้งค่า (Probe Configurations):</strong> เมื่อระบบทำงานภายใต้ภาวะไม่ปกติ<strong> </strong>เช่น กรณี Database ล่ม ระบบต้องมีการจัดการทราฟฟิกและฟื้นฟูระบบได้อย่างถูกต้องและเหมาะสมตามเงื่อนไขที่กำหนด โดยจะตรวจสอบดังนี้</p><ul><li><strong>Readiness Probe:</strong> ในช่วงที่ Container ยังไม่พร้อมใช้งาน ระบบจะส่ง Probe ไปตรวจสอบตามความถี่ (periodSeconds) และจำนวนครั้ง (failureThreshold) ที่ตั้งค่าไว้</li></ul><p>หากผลการตรวจสอบเป็น <strong>Fail</strong> ระบบจะต้อง<strong>หยุดจ่ายทราฟฟิก</strong>ไปยัง Pod นั้นทันที</p><p><em>ผลลัพธ์ที่คาดหวัง:</em> หากมีการส่ง Traffic เข้ามาในช่วงที่ Pod ไม่พร้อมใช้งาน ระบบจะต้องตอบกลับด้วย HTTP Status <strong>503 Service Unavailable</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/748/1*euMl9QjBCS2OIi6jbIYXvw.png" /></figure><ul><li><strong>Liveness Probe:</strong> ตรวจสอบว่าระบบ<strong>มีการสั่งรีสตาร์ท (Restart) Pod</strong> เพื่อฟื้นฟูการทำงานโดยอัตโนมัติทันทีเมื่อ Probe เกิดสถานะ <strong>Fail</strong> ครบตามเงื่อนไข</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7NXKonQyytJm-iJTPY6F9A.png" /><figcaption>Verify application restart when Liveness Probe fails</figcaption></figure><p><strong>2.วัดเวลาการ Startup ของแอปพลิเคชัน:</strong> เช็กว่าเวลาที่แอปใช้ในการเปิดระบบขึ้นมา (Application Startup Time) มันช้าเกินกว่าค่า startupProbe ที่เรา Setting ไว้หรือไม่ เพราะถ้าแอปใช้เวลาโหลดชิ้นส่วนภายในช้าเกินไป หรือตั้งค่าตัวเลขที่ไม่เหมาะสม ตัว Kubernetes จะมองว่าแอปตาย แล้วสั่งรีสตาร์ทวนไปรัว ๆ ตามที่พี่เต้ได้เตือนไว้ในบทความค่ะ</p><p>ซึ่งหากเราทำการทดสอบแล้วพบว่าแอปพลิเคชันของเราใช้เวลาสตาร์ทช้าเกินไปจริง ๆ สิ่งนี้จะเป็นสัญญาณเตือนให้ทีมต้องมาเช็กหาสาเหตุ และทำ Tuning เพื่อให้แอปพลิเคชันทำงานได้ดีขึ้นค่ะ</p><p>นอกจากนี้ บางทีในการทำ Performance Test แล้วแอปเกิดอาการ <strong>OOM</strong> (Out of Memory) หรือใช้ Memory น้อยหรือมากเกินกว่าที่ขอ หากพวกเรามีความรู้เรื่องการเซตค่า <strong>JVM Heap</strong> ต่าง ๆ บ้าง ก็จะทำให้เรามีข้อกังวลในเรื่องที่ตรงจุดมากขึ้นเวลาไปพูดคุยหรือช่วยกันวิเคราะห์ปัญหาร่วมกับเดฟและทีม Infra ค่ะ</p><h3>🎯 สรุป</h3><p>ยาวเลยใช่ไหมคะ? 😂 แต่เป้พยายามเล่าเพื่อให้ทุกคนเห็นภาพตรงกันค่ะว่า ทำไมเราถึงต้องรู้เรื่องเหล่านี้</p><p>การมางาน AWS ในครั้งนี้มันทำให้เราได้เห็นมุมมองหรือเรื่อง Infrastructure มากขึ้นเยอะ และย้ำว่าเราไม่จำเป็นต้องปล่อยให้มันเป็นเรื่องของคนอื่นอย่างเดียว การที่ QA มีความรู้ความเข้าใจในส่วนนี้ติดตัวไว้บ้าง จะทำให้เราเกิด Concern ที่คมขึ้น คุยกับทีมอื่นรู้เรื่อง (แถมแอบเอาไว้ใช้ Challenge คัดค้านได้อย่างมีเหตุมีผลด้วยนะ! 😉) เพื่อร่วมกันทำให้แอปพลิเคชันของเรามีคุณภาพและสเกลได้อย่างปลอดภัยที่สุดค่ะ! ✌️😊</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4b8dd6d24024" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/%E0%B8%96%E0%B8%AD%E0%B8%94%E0%B8%A3%E0%B8%AB%E0%B8%B1%E0%B8%AA-aws-summit-%E0%B8%97%E0%B8%B3%E0%B9%84%E0%B8%A1-qa-%E0%B8%95%E0%B9%89%E0%B8%AD%E0%B8%87%E0%B8%A3%E0%B8%B9%E0%B9%89%E0%B9%80%E0%B8%A3%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%87-infrastructure-4b8dd6d24024">ถอดรหัส AWS Summit ทำไม QA ต้องรู้เรื่อง Infrastructure?</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ถอดรหัส AWS Summit เมื่อเราต้องขยับสู่บทบาท ‘QA Orchestrator’]]></title>
            <link>https://medium.com/ascend-developers/%E0%B8%96%E0%B8%AD%E0%B8%94%E0%B8%A3%E0%B8%AB%E0%B8%B1%E0%B8%AA-aws-summit-%E0%B9%80%E0%B8%A1%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B9%80%E0%B8%A3%E0%B8%B2%E0%B8%95%E0%B9%89%E0%B8%AD%E0%B8%87%E0%B8%82%E0%B8%A2%E0%B8%B1%E0%B8%9A%E0%B8%AA%E0%B8%B9%E0%B9%88%E0%B8%9A%E0%B8%97%E0%B8%9A%E0%B8%B2%E0%B8%97-qa-orchestrator-c65de0bf0ae1?source=rss----98c9c9d1ba0c---4</link>
            <guid isPermaLink="false">https://medium.com/p/c65de0bf0ae1</guid>
            <category><![CDATA[qa-automation]]></category>
            <category><![CDATA[aws-summit]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[aidlc]]></category>
            <dc:creator><![CDATA[Poopae Paisingkhon]]></dc:creator>
            <pubDate>Thu, 11 Jun 2026 03:23:41 GMT</pubDate>
            <atom:updated>2026-06-11T15:13:13.155Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0MS_3OtD4l6PkNcE_Wy2rg.png" /></figure><p>ในปี 2026 นี้ คงไม่มีใครไม่ใช้ AI ในการทำงานแล้วใช่ไหมคะ? ปีนี้ก็เป็นอีกครั้งที่เป้ (ผู้เขียน) ได้มีโอกาสมาร่วมงาน AWS Summit Bangkok และแน่นอนว่าเป้าหมายหลักในใจรอบนี้ คือการมาพยายามมองหามุมมองและไอเดียการทำงานคู่กับ AI จากในงานเพื่อเอามาประยุกต์ใช้กับงาน QA ที่ทำอยู่</p><p>ซึ่งเทรนด์ AI ในปีนี้จะรู้เลยว่ามันขยับไปไกลกว่าปีที่ผ่าน ๆ มามาก จากที่เราเคยมองมันเป็นแค่ผู้ช่วยคิด ช่วยเจนเทสเคส หรือเทสสคริปต์สั้น ๆ หยิบมาช่วยเป็นครั้งคราว แต่ในงาน AWS ปีนี้ ชัดเจนมากว่ามันได้เข้ามาแทรกซึมในทุก ๆ กระบวนการพัฒนาซอฟต์แวร์ (SDLC) ตั้งแต่ต้นน้ำจนถึงปลายน้ำ จนกลายเป็น <strong>AI-Driven Development Lifecycle (AI-DLC)</strong> เต็มรูปแบบ</p><p>สิ่งนี้ทำให้เห็นภาพชัดขึ้นเลยค่ะว่า งานของพวกเราชาว QA ขยับขึ้นมาเป็น <strong>“QA Orchestrator”</strong> หรือผู้ควบคุม วางทิศทาง และตรวจรับงานจาก AI อีกทีหนึ่ง ลองนึกภาพตัวเราเป็น “วาทยกร (Conductor)” ที่ยืนคุมอยู่หน้าเวที เราไม่ได้ลงไปเล่นเครื่องดนตรีเองทุกชิ้น (หน้าที่ทำของถึก ๆ ปล่อยให้ AI จัดการ) แต่หน้าที่ของเราคือคอยโบกไม้คุมจังหวะ สั่งงาน และตรวจสอบว่าผลลัพธ์ที่ออกมาถูกต้อง ไพเราะ และไม่มีจุดไหนหลุดคีย์</p><p>ในบทความนี้ เป้เลยตั้งใจจะพาทุกคนมาส่องแนวคิด AI-DLC ไปด้วยกัน มาดูกันว่าเราจะทำ <strong>“Left-shifting QA”</strong> (คำฮิตในวงการช่วงนี้ ที่หมายถึงการย้ายกระบวนการเทสและตรวจสอบคุณภาพขึ้นไปไว้ตั้งแต่ต้นน้ำ) ด้วยพลังของ AI ได้ยังไงบ้าง รวมทั้ง Real world use cases จาก 2 sessions ในงานมารีวิวให้ฟังกัน ดังนี้ค่ะ:</p><ul><li><strong>Use cases 1:</strong> จาก <em>Tackling Anti-patterns with AI-Driven Development Lifecycle (AI-DLC)</em> Session โดย คุณ Anan Phupengjai, Expert QA Engineer, Axons / CP Foods: ที่เขาหยิบเอาแนวคิด AI-DLC ไปประยุกต์ใช้กับงาน QA จนเกิดเป็น Autonomous Testing ในสเกลงานจริง</li><li><strong>Use cases 2:</strong> จาก <em>Effectively using AI Assistants to Accelerate Development</em> Session โดย คุณ Phuwadon Potithong, CTO, WiPay: มาแชร์การ workflow การทำงานกับ AI เจาะลึกเทคนิคการสร้าง AI Agent Instruction พร้อมเคล็ดลับการประหยัด Token ไม่ให้บอท Burn เงินโดยเปล่าประโยชน์</li></ul><p>ก่อนที่เราจะเข้าเรื่อง AI-DLC อยากจะมาแชร์เรื่อง Anti-pattern กันก่อน มันเป็นพฤติกรรมที่คนทำงานมักจะพลาดเวลาใช้ AI (ซึ่งสารภาพว่าเป้อาจจะจำได้ไม่ครบร้อยเปอร์เซ็นต์นะคะ 😅) ที่ทาง AWS รวบรวมมา และตัวโมเดล AI-DLC นี้ทาง AWS ก็พัฒนามาเพื่อแก้ไขปัญหาพวกนี้ด้วยค่ะ แต่จะมีอะไรบ้างมาดูกันเลยค่ะ…</p><h3>AI Anti-pattern</h3><ol><li>Avoid Vibe Code: ให้ <strong>‘พยายามเลี่ยง Vibe Code’</strong> อ้าว… ฟังตอนแรกแอบงงใช่ไหมคะ? เป้ก็งงค่ะ! แต่ที่งงไม่ใช่อะไรนะ… คือไม่รู้ว่ามันคืออะไร ฮ่า ๆ ๆ</li></ol><p>สำหรับใครที่ไม่รู้เหมือนกัน ไปหาข้อมูลมาให้ละค่ะ มันคือสไตล์การพัฒนาซอฟต์แวร์ที่ปล่อยให้ AI เขียนโค้ดรัว ๆ จนได้แอปขึ้นมา โดยที่เราแค่นั่งบอกไอเดียชิล ๆ สั่งงานตามมู้ด (Vibes) แบบที่ไม่ต้องลงไปเข้าใจโค้ดอะไรขนาดนั้น… สบายไปเลยดิ!</p><p>แต่พออ่านความหมายลึก ๆ แล้วแอบกลัวเหมือนกันนะคะ เพราะถ้าเราปล่อยให้ AI เขียนโค้ดไปเรื่อย ๆ โดยที่เราไม่รู้จักหรือไม่ได้เข้าใจการทำงานของมันจริง ๆ น่าจะเจอบั๊กเยอะแน่ค่ะ! แล้วความน่ากลัวนี้ไม่ได้อยู่แค่ฝั่งเดฟนะคะ ในมุมของ QA อย่างเรา ๆ ถ้ายืมมือ AI มาช่วยเขียนเทสสคริปต์แบบปล่อยใจตาม Vibe โดยไม่ตรวจเช็กตรรกะ (Logic) ให้ดี บอกเลยว่ามีหลุดแน่นอน! เพราะ AI มันก็เนียนเจนสคริปต์หลอกตาเราได้เก่งเหมือนกัน ดังนั้นงานจะรีบแค่ไหนก็อย่ารีบค่ะ 😂</p><p>2. Ask AI to do small, simple tasks: ก็ตามชื่อเลยค่ะ เวลาสั่งงาน AI อย่าโยนงานใหญ่ ๆ หรือโปรเจกต์ยักษ์ให้มันทำทีเดียวทั้งหมด ไม่งั้นตอนมันแก้กลับมาทีเราต้องมานั่งรีวิวกันหนักหน่วงมากเลยนะ ให้พยายามแตกเป็น Task ย่อย ๆ ง่าย ๆ ให้มันทำทีละสเตปดีกว่าค่ะ อย่าไปคิดเอาเองว่ามันเก่ง เดี๋ยวโยนไปตู้มเดียวมันจะทำสำเร็จแน่ ๆ</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BR-5NuZx8c_8WZQ_9nHL3Q.jpeg" /></figure><p>3. Manage Context window careful: ข้อนี้อยากชวนทุกคนมาลองสังเกตตัวเองกันดูค่ะว่า เวลาที่เราคุยงานกับ AI เนี่ย… เราคุยสลับไปสลับมาหลาย ๆ เรื่องในห้องแชทเดิมกันอยู่หรือเปล่า? แล้วเคยได้กด Reset หรือเริ่ม New Chat ใหม่กันบ้างไหมคะ? ซึ่งการที่ไม่ทำมันทำให้ AI เริ่มสับสน ถือบริบทเก่ามาปนเรื่องใหม่ และทำให้เราได้คำตอบที่ไม่ถูกต้องเยอะขึ้นเรื่อย ๆ เป็นสิ่งที่ต้องระวัง</p><p>4. Don’t trust AI as you trust a Sr. Engineer: ให้เรามอง AI เป็นแค่ <strong>“Intern ที่เก่งมาก”</strong> ก็พอ แต่อย่าเพิ่งไปไว้ใจมันระดับ Senior Engineer สิ่งที่เราต้องทำคือ <strong>‘ต้องระบุเงื่อนไขให้ชัดเจน’</strong> ไปเลยค่ะว่าอยากให้ทำแบบไหน ผลลัพธ์ต้องออกมาหน้าตาเป็นยังไง มีข้อห้ามอะไรบ้าง อย่าปล่อยให้ AI มันเดาหรือแอบคิดไปเองเด็ดขาดค่ะ! เพราะถ้าเราสั่งงานแบบกว้าง ๆ หรือกำกวมเมื่อไหร่ AI มันจะเริ่มใช้จินตนาการตรรกะของมันเองทันที</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IzAixALhLgdaZkwCqDpMeA.png" /></figure><p>อ้อ! แล้วในเซสชันเค้าแชร์เคล็ดลับอีกอันที่ดีมาก ๆ คือ <strong>ให้สั่งกำชับมันไว้เลยว่า “อันไหนไม่แน่ใจให้มาถามเราก่อนทุกครั้ง ห้ามคิดไปเอง”</strong> ซึ่งในมุมของงาน QA ถ้าปล่อยให้ AI เดาสเปกหรือปล่อยมันคิด logic การเทสแบบที่ไม่ถูกต้องขึ้นมา… บอกเลยว่าบันเทิงแน่นอนค่ะ ฮ่า ๆ</p><p>สุดท้ายแล้ว <strong>“เราคือเจ้าของงาน”</strong> ดังนั้นเราต้องทำการรีวิวทุกครั้ง ย้ำ! อย่าลืมรีวิวนาจา และที่สำคัญคืออย่าปล่อยโค้ดหรือสคริปต์ที่ AI เจนออกมาดุ่ย ๆ แล้วโยนให้คนอื่น (Team Reviewer) รีวิวให้อย่างเดียวด้วยนะคะ ไม่งั้นจะกลายเป็นสร้างงานหยาบและเพิ่มภาระให้ PR Reviewer แทนค่ะแถมอาจจะโดนบ่นด้วยนะ 🤣</p><p>ซึ่งจาก Anti-pattern ข้างต้น ทาง AWS เลยพัฒนามาเป็น AI-DLC คอนเซปต์หลัก ๆ ของมันคือ อยากให้ AI ทำอะไรเราบอกมันไปได้เลย แต่กระบวนการทำงานจะเปลี่ยนไป คือ <strong>เราจะขยับมาเป็นคนคุมและคอย ‘Validate’ (ตรวจสอบ) สิ่งที่ AI ทำในทุก ๆ สเตป</strong> ไม่ปล่อยให้มันคิดเองเออเอง</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RBiBt-Y9bfT7u98hKCifwQ.jpeg" /></figure><p>ในเซสชันเค้าได้โชว์ภาพ The Operating Model for AI</p><p>ถ้าดูจากแผนภาพวงกลมในรูป จะเห็นเลยว่าลูปการทำงานร่วมกับ AI จะเริ่มจาก <em>AI Creates Plan ➡ Humans Verify the Plan ➡ AI Refines the Plan </em>ขั้นตอนนี้สะท้อนเรื่องห้ามปล่อยใจไปกับ Vibe สุด ๆ ค่ะ เพราะเริ่มแรก AI จะยังไม่เขียนโค้ดทันที แต่มันจะไปคิดและเขียนแผนงาน (Plan) มาให้เราดูก่อน จากนั้น มนุษย์ (ซึ่งก็คือพวกเรา) ต้องเป็นคน Verify ตรวจสอบแผนงานเสมอ ถ้าตรงไหนยังไม่เคลียร์หรือไม่ตรงสเปก เราก็บอกมันได้ ให้มันไปเขียนแผนมาใหม่ วนลูปแบบนี้ไปค่ะจนกว่าจะได้ Final Plan ที่สมบูรณ์ร่วมกัน</p><p>หลังจากนั้น <em>AI Executes the Plan ➡ Humans Verify the Outcome </em>พอแผนนิ่งปุ๊บ AI มันก็เริ่มรันงานตามแผนทันที และเมื่อมันทำเสร็จ หน้าที่สำคัญที่สุดของพวกเราก็คือการกลับมา Verify ผลลัพธ์สุดท้าย ว่าทำงานได้ถูกต้อง ปลอดภัย และไม่มีอะไรหลุดค่ะ</p><p>ย้ำกันอีกทีจากสไลด์ของ AWS เลยค่ะว่า ไม่ว่า AI จะเก่งและ Autonomous แค่ไหน <strong>‘มนุษย์ก็ยังต้องอยู่เป็นศูนย์กลางในฐานะ Verifier เสมอ’</strong> ยิ่งในมุมของ QA หน้าที่สแกนความถูกต้องตรงนี้ยิ่งทวีความสำคัญขึ้นไปอีกในยุค AI-DLC เพื่อขยับตัวไปเป็น QA Orchestrator ที่คุมภาพรวมได้อย่างมั่นใจค่ะ</p><p>หลังจากที่เราเข้าใจกติกาการอยู่ร่วมกันใน Operating Model แล้ว ทีนี้เรามาดูภาพกว้างของขั้นตอนการทำงานจริงที่ทาง AWS นำเสนอในสไลด์ AI-Driven Development Lifecycle (AI-DLC) นี้กันค่ะ</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qX6uPM5mQm1N5FP_Mz03ow.png" /></figure><p>วงจรนี้ถูกแบ่งออกเป็น 3 ช่วงหลัก (Inception, Construction, Operation) ดังนี้ค่ะ:</p><p><strong>1.Inception:</strong> จุดนี้คือที่มาของคำว่า ‘Context’ กระบวนการจะเริ่มจากการป้อน Input ต่าง ๆ ไม่ว่าจะเป็นไฟล์โจทย์ ความต้องการของระบบ หรือถ้าเป็นการแก้ไขระบบเดิม AI ก็จะเข้าไปศึกษาทำความเข้าใจกับซอร์สโค้ดเดิมที่มีก่อนหน้า จากนั้นหน้าที่สำคัญของพวกเราคือการเข้าไป Clarify สิ่งต่าง ๆ ร่วมกับ AI ค่ะ พอคุยกันจนเคลียร์ปุ๊บ AI มันก็จะเจน User Stories ออกมา ที่เหมือนเป็นชุด Requirement ชุดนึงและแตกเป็น Units of Work เล็ก ๆ ออกมา</p><p>ซึ่งเฟสนี้แหละค่ะที่เป้มองว่า ถ้าทีมเราเอาแนวคิดนี้มาใช้ มันจะเป็นจุดที่ QA อย่างเราต้องรีบกระโดดเข้าไปเช็กความถูกต้องตั้งแต่ต้นน้ำ เป็นการทำ Left-shifting QA ของจริง เพื่อให้แน่ใจว่า Tasks ย่อย ๆ ที่ได้ออกมานั้น มันถูกต้อง ตรงสเปก และมีคุณภาพตั้งแต่วันแรกที่ยังไม่ได้เริ่มเขียนโค้ดเลยค่ะ ทั้งนี้ก็เพื่อป้องกันและสกัดกั้นให้บั๊คเกิดน้อยที่สุดเท่าที่จะเป็นไปได้นั่นเองค่ะ</p><p><strong>2. Construction:</strong> เมื่อแผนนิ่ง AI ก็จะเริ่มสร้างแล้ว ตั้งแต่การ เขียนโค้ด, การ Generate Tests รวมไปถึงการวางโครงสร้างระบบ (Infra) เพื่อเตรียม Deploy จุดนี้งานเราคือการรีวิวสิ่งที่มันเจนออกมาว่า ‘เทสที่ AI เขียนให้เนี่ย มันครอบคลุมจริงไหม?’ หรือมันแอบข้ามขั้นตอนอะไรไปหรือเปล่า</p><p><strong>3. Operation:</strong> สุดท้ายคือการนำงานที่สร้างเสร็จไปรันบนโปรดักชันจริงด้วยและมีระบบ AI คอยช่วยจัดการเวลาเกิดปัญหา (Incidents) ให้ด้วยค่ะ</p><p>เอาล่ะค่ะ ทีนี้เราลองมาดูของจริงในชีวิตจริงกันบ้างดีกว่าค่ะ ว่าในชีวิตการทำงานจริง เค้าหยิบเอาแนวคิด AI-DLC ไปประยุกต์ใช้กับหน้างาน QA จนเกิดเป็น <strong>Autonomous Testing</strong> ได้อย่างไร… มาดูกันเลยค่ะ! 👇</p><h3>🚀<strong>มาเริ่มกันที่ Use Case แรก กันเลยค่ะ</strong></h3><p>โดยเซสชันนี้คุณ Anan Phupengjai, Expert QA Engineer, Axons / CP Foods ได้เริ่มต้นด้วยการลิสต์ปัญหาคลาสสิกที่เจอจากการทำงานในชีวิตประจำวัน มันเป็นปัญหาที่พวกเราชาว QA น่าจะเจอคล้าย ๆ กันหมด นั่นคือ</p><p><strong>ปัญหาที่ 1: การเขียน Test Case ที่ใช้เวลานาน</strong> ปกติหลังจากเปิด Sprint มา เรามักจะต้องใช้เวลาปั้นเทสเคสกันไปแล้ว 1–2 วัน</p><p>หรือขอยกตัวอย่างตัวเองบ้างที่เป้พยายามทำในหลาย ๆ สปริ้นเลยคือ การเร่งดราฟต์เทสเคสให้เสร็จเท่าที่จะทำได้ให้ทันก่อนเริ่ม Sprint Planning ซึ่งเอาจริงๆ ในทีมก็ไม่ได้มีใครมาบังคับนะคะ แต่คือพยายามอยากให้งานเทสมันวิ่งควบคู่ไปพร้อม ๆ กับงานเดฟให้ได้ และที่สำคัญคือ การที่เราคุยเรื่องเทสเคสทั้ง Unit test และเคสที่เอาไปเขียน Automate script กันตั้งแต่เนิ่น ๆ มันช่วยทำให้เดฟและเราเองด้วยไม่ตกหล่นเคสสำคัญตอนเขียนโค้ด แถมยังเปิดโอกาสให้ทีมได้เข้ามา discuss สเปกและข้อกังวลต่าง ๆ ร่วมกันตั้งแต่งานยังไม่ทันเริ่ม Dev เลย มันคือการทำ Shift-Left QA เพื่อคุมคุณภาพตั้งแต่ต้นน้ำ …แต่ก็นั่นแหละค่ะ กว่าจะเค้นดราฟต์ออกมาให้ทันก่อนแพลนนิ่งได้ในแต่ละรอบ บอกเลยเหนื่อยเหมือนกันค่ะ 😭</p><p><strong>ปัญหาที่ 2: การทำ Automate test script ที่เขียนออกมาได้ช้าถ้าประสบการณ์ยังไม่เยอะ</strong></p><p><strong>ปัญหาที่ 3: UI change</strong> ที่ต้องใช้เวลาแก้ไข script หรือใช้เวลา maintain มาก</p><p>ซึ่งจากปัญหาทั้งหมดนี้ทางทีม Axons เค้าได้เซ็ตลูปการทำงานกับ KIRO + MCP ออกมาเป็นสเตปตามนี้เลยค่ะ:</p><ul><li><strong>Step 1: </strong>เริ่มจากการให้บอทเข้าไปอ่านตัวงานโดยตรงจาก PBI (Product Backlog Item) Board ผ่าน MCP</li><li><strong>Step 2: </strong>จากนั้นให้ AI ปั้นและเขียน Test Case ออกมาให้ โดยมันจะเขียนตามโครงสร้างที่เราได้สั่งและล็อกสเปกกำชับเอาไว้ใน Instruction File</li><li><strong>Step 3: </strong>เมื่อ AI เจนเสร็จ หน้าที่ของ QA คือเข้ามาตรวจสอบความถูกต้อง</li></ul><p>ซึ่งวิธีนี้ทำให้ QA เขาไม่ต้องเริ่มสร้างเทสเคสจากหน้าเปล่า เปลี่ยนเป็นเริ่มจากเทสเคสที่ไปใช้งานได้ตั้งแต่วันแรกของ sprint กันเลยค่ะ</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*J4Y1iprVl31cq7sakyAQag.png" /></figure><p>หลังจากนั้นการสร้าง automate test script ก็ใช้ Doc ต่าง ๆ ป้อนให้ AI เขียนเทสสคริปต์ให้และจบด้วยให้ QA รีวิวตัวสคริปต์</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*09tUfIFxilIEqlux4cXZbQ.png" /></figure><p>จากตัวอย่าง Use Case แรกนี้ ทุกคนจะเห็นภาพได้ชัดเจนมากเลยใช่ไหมคะว่า <strong>เราได้ทำการลดบทบาทจากการเป็น ‘ผู้เขียนสคริปต์เองทั้งหมด’ ขยับขึ้นมาเป็น ‘ผู้ควบคุมและดูแลภาพรวมแทน (QA Orchestrator)’</strong> งานถึก ๆ งานเขียนสคริปต์ยาว ๆ ที่เคยสูบพลังชีวิตเราไป ปล่อยให้เป็นหน้าที่ของ AI-DLC ลุยไปเลยค่ะ ส่วนเราใช้ประสบการณ์และ Logic ในการคุมทิศทางเพื่อการันตีคุณภาพแทน!</p><p>แต่ใด ๆ ก็ตาม… ท่ามกลางความสะดวกสบายของ Use Case แรกนี้ มันก็แอบมีเรื่องน่ากังวลแฝงอยู่เหมือนกันนะ ‘อย่าปล่อยให้ความสบายนี้ มาทำให้ Base Skills ที่เราควรจะมีในฐานะ QA หายไปเด็ดขาดนะคะ’ เพราะอย่าลืมว่า ถ้าเราไม่มีทักษะพื้นฐานในการคิด Logic ไม่มีสกิลการออกแบบ Test Case หรืออ่านสคริปต์ไม่แตกด้วยตัวเองตั้งแต่แรก… เราจะขยับขึ้นมาเป็น <strong>QA Orchestrator</strong> ที่คอยคุมบอทและคอยตรวจรับงานให้ถูกต้องได้อย่างมั่นใจได้ยังไงจริงไหมคะ? บอทเจนไม่ถูกต้องมาเราก็ดูไม่ออกพอดี ดังนั้น ใช้ AI เป็นผู้ช่วยได้ แต่อย่าทิ้งสกิลติดตัวของเราเด็ดขาดค่ะ!</p><h3>🚀 ไปต่อกันที่ Use Case ที่สอง!</h3><p>Session นี้ทางคุณ Phuwadon Potithong, CTO, WiPayได้มาร่วมแชร์แนวคิดและเทคนิคการสร้าง AI Agent Instruction ที่ดีค่ะ</p><p>ก่อนที่จะไปดูแนวทางการเขียนคำสั่งคุมบอท ทาง WiPay ได้แชร์ให้เห็นถึง Developer Bottleneck (คอขวดในฝั่งพัฒนาซอฟต์แวร์) ในยุคที่ทีมยังไม่ได้ใช้ AI โดยสไลด์นี้ได้สรุปความท้าทายหลักออกเป็น 3 ด้าน</p><p>อันแรกคือการรอคิว Developer ไม่ว่าจะเป็นการขอฟีเจอร์ใหม่ การทำระบบออโตเมชัน หรือการเขียนสคริปต์จัดการข้อมูล ต่อมาคือปัญหาเรื่องช่องโหว่ด้านความปลอดภัยต่าง ๆ ที่หลุดรอดไป และสุดท้ายการปล่อยของที่ล่าช้า</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bDTlW-W8ad1XtSUi13lDYw.jpeg" /></figure><p>เพื่อแก้ปัญหาคอขวดดังกล่าว WiPay ได้นำเสนอโมเดลการทำงานรูปแบบใหม่ที่เรียกว่า <strong>‘Codified Standards + AI’</strong> ซึ่งเป็นการเปลี่ยนจากเดิมมาเป็นการบันทึกและแปลงให้อยู่ในรูปแบบของ <strong>Instruction Files</strong> เพื่อให้ AI สามารถนำมาตรฐานเหล่านี้ไปปรับใช้ในสเกลใหญ่ได้กับทุกทีม โดยแบ่งหน้าที่ความรับผิดชอบออกเป็น 3 ส่วนสำคัญตามสไลด์ดังนี้ค่ะ:</p><ol><li><strong>Developer </strong>กำหนดกฎเหล็กและมาตรฐานระบบ</li><li><strong>QA / Analyst </strong>แปลงมาตรฐานเหล่านั้นให้เป็นไฟล์ <strong>Structured Markdown</strong> เพื่อให้ AI สามารถอ่านและนำไปทำตามได้อย่างถูกต้องแม่นยำ</li><li><strong>AI Agent </strong>อ่านไฟล์คำสั่ง (Instruction File) แล้วรับหน้าที่เจนโค้ดที่ถูกต้องตามมาตรฐานและเงื่อนไขความปลอดภัย</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5libN34OYZuuwl1Lbd4yCA.jpeg" /></figure><p>ทาง WiPay ได้แบ่ง Instuction file ออกเป็นหลาย ๆ ไฟล์โดยครอบคลุมทุก ๆ งาน</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*84pRy3n6szqM6o-UYxybMA.jpeg" /></figure><p>มาถึงตรงนี้แล้วไม่แน่ใจว่ามีใครเคยสงสัยคล้าย ๆ กับเป้ไหมคะว่า แนวทางการเขียนและจัดการไฟล์ Instruction นั้น เราควรจะเขียนกฎทุกอย่างรวมกันไว้ในไฟล์เดียว หรือควรจะแยกไฟล์ดี? แล้วถ้าแยก… ควรมีเกณฑ์ในการแยกอย่างไร? ซึ่งทาง WiPay เลือกที่จะ <strong>‘แยกไฟล์ Instruction ออกตามโมดูลงาน และตั้งเงื่อนไขให้ AI เลือกอ่านเฉพาะเท่าที่จำเป็น’</strong> ซึ่งวิธีนี้นอกจากจะช่วยให้ AI ทำงานได้โฟกัสและมีประสิทธิภาพแล้ว ยังเป็นคีย์สำคัญในการช่วยประหยัด Token (ลดค่าใช้จ่าย) ได้ดีเยี่ยมอีกด้วยค่ะ</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P8k5bYm8cHZpdet2-YBElQ.jpeg" /></figure><p>นอกจากเทคนิคการจัดโครงสร้างไฟล์แล้ว ทาง WiPay ยังแชร์แนวทางปฏิบัติที่ดีในการเขียน Instruction สำหรับควบคุม AI ออกเป็น 4 ข้อสำคัญ โดยเปรียบเทียบสิ่งทีควรทำและสิ่งที่ควรหลีกเลี่ยงไว้ดังนี้ค่ะ:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1rOL-uGl2Xzo9Imu17pcDg.jpeg" /></figure><p><strong>1. Be Specific, Not Vague</strong></p><p>✅ <strong>Do:</strong> ระบุความต้องการและรูปแบบที่ชัดเจน</p><p>❌ <strong>Avoid:</strong> ใช้คำกว้าง ๆ ที่ไม่มีทิศทางชัดเจน</p><p>ตัวอย่างถ้าเราสั่ง AI แค่ว่า ‘อยากได้ระบบที่ปลอดภัย’ สิ่งที่เกิดขึ้นคือ AI จะพยายามประโคมดึงฟีเจอร์ความปลอดภัยทุกระบบเท่าที่มันคิดได้ใส่เข้ามาแบบจัดเต็ม ซึ่งนอกจากจะเกินความจำเป็นแล้ว <strong>ผลลัพธ์คือมันจะแอบ Burn Tokenโดยเปล่าประโยชน์ </strong>ดังนั้นเราควรระบุไปเลยว่าต้องการระบบความปลอดภัยและการทำ Encryption แบบไหนที่เฉพาะเจาะจงไปเลย</p><p><strong>2. State Non-Negotiables Explicitly</strong></p><p>✅ <strong>Do:</strong> ประกาศกฎห้ามฝ่าฝืนเป็นข้อ ๆ อย่างตรงไปตรงมา เช่น <em>“กฎเหล็กที่ยอมความไม่ได้: 1. ห้ามทำ… 2. ต้องทำ… เสมอ” </em>โดยเฉพาะเรื่องของมาตรฐานระบบ ความปลอดภัย หรือเงื่อนไขสำคัญ อะไรที่ไม่ควร Compromise ก็ต้องระบุห้ามเด็ดขาดตั้งแต่แรก เพื่อไม่ให้ AI มีช่องว่างในการคิดยืดหยุ่นเอง</p><p>❌ <strong>Avoid:</strong> ใช้คำขอร้องหรือคำที่ดูลังเล เช่น <em>“พยายามหลีกเลี่ยงการส่งข้อมูล PII ไปยังบราวเซอร์”</em> เพราะเมื่อไหร่ที่เราใช้คำที่ดูอ่อนข้อ AI จะตีความว่าเป็นเพียงคำแนะนำสไตล์ Option เสริม ไม่ใช่ข้อบังคับที่ต้องทำตาม 100% ค่ะ</p><p><strong>3. Show Code Examples</strong></p><p>✅ <strong>Do:</strong> แนบตัวอย่างแพทเทิร์นโค้ดที่ถูกต้องเวลาเรารีวิวโค้ดกันอันไหนดีไม่ดีเขียนไปเลยให้ AI มันเห็นว่าแบบนี้เรียกว่าดีนะให้ AI เห็นภาพชัดที่สุด</p><p>❌ <strong>Avoid:</strong> อธิบายเป็นความเรียงหรือตัวอักษรยาว ๆ เพียงอย่างเดียว เพราะ AI อาจจะตีความข้อความเหล่านั้นคลาดเคลื่อนได้</p><p><strong>4. Version and Maintain</strong></p><p>✅ <strong>Do:</strong> คอยอัปเดตไฟล์ Instruction ทุกครั้งเมื่อมาตรฐานระบบมีการเปลี่ยนแปลง และ ให้ QA ทำหน้าที่เป็นเจ้าของในการควบคุมเวอร์ชัน เพื่อรักษามาตรฐานให้ใหม่อยู่เสมอ</p><p>❌ <strong>Avoid:</strong> เขียนครั้งเดียวแล้วปล่อยทิ้งไว้ (Write once and forget) เพราะไฟล์คำสั่งที่ล้าสมัย จะส่งผลให้ AI เจนโค้ดที่ตกรุ่นและสร้างปัญหาตามมาในภายหลัง</p><h3>🎯 สรุป</h3><p>ภาพรวมทั้งหมดจากทั้ง 2 Use Cases ในงาน AWS Summit รอบนี้ ทำให้เราได้เห็นปลายทางของการทำงานร่วมกับ AI ในยุค <strong>AI-DLC</strong> อย่างชัดเจนค่ะ สังเกตได้ว่าหน้าที่ของ QA ไม่ได้ถูกลดทอนลงไปเลย เพียงแต่เปลี่ยนรูปแบบจากการลงแรงทำงานซ้ำ ๆ มาเป็นการใช้ทักษะและประสบการณ์ในฐานะ <strong>QA Orchestrator</strong> เพื่อกำหนดทิศทาง ควบคุมมาตรฐาน และตรวจรับงานแทน</p><p>และสิ่งสำคัญที่สุดที่จะทำให้เราก้าวขึ้นมาทำหน้าที่นี้ได้อย่างมั่นใจ คือการไม่ละทิ้งทักษะพื้นฐานของตัวเอง ควบคู่ไปกับการฝึกฝนทักษะใหม่อย่างการออกแบบ Instuction file เพื่อควบคุม AI ให้ทำงานได้อย่างมีประสิทธิภาพและคุ้มค่าที่สุดนั่นเองค่ะ! 😊</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c65de0bf0ae1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/%E0%B8%96%E0%B8%AD%E0%B8%94%E0%B8%A3%E0%B8%AB%E0%B8%B1%E0%B8%AA-aws-summit-%E0%B9%80%E0%B8%A1%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B9%80%E0%B8%A3%E0%B8%B2%E0%B8%95%E0%B9%89%E0%B8%AD%E0%B8%87%E0%B8%82%E0%B8%A2%E0%B8%B1%E0%B8%9A%E0%B8%AA%E0%B8%B9%E0%B9%88%E0%B8%9A%E0%B8%97%E0%B8%9A%E0%B8%B2%E0%B8%97-qa-orchestrator-c65de0bf0ae1">ถอดรหัส AWS Summit เมื่อเราต้องขยับสู่บทบาท ‘QA Orchestrator’</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My Notes from AWS Summit Bangkok 2026]]></title>
            <link>https://medium.com/ascend-developers/my-notes-from-aws-summit-bangkok-2026-63119e1b8c84?source=rss----98c9c9d1ba0c---4</link>
            <guid isPermaLink="false">https://medium.com/p/63119e1b8c84</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[backend-development]]></category>
            <category><![CDATA[ai-agent]]></category>
            <dc:creator><![CDATA[Radit P.]]></dc:creator>
            <pubDate>Tue, 02 Jun 2026 10:52:07 GMT</pubDate>
            <atom:updated>2026-06-03T00:45:16.011Z</atom:updated>
            <content:encoded><![CDATA[<p>Last week, I had a chance to join AWS Summit Bangkok 2026 at QSNCC. This was my first time going to this big AWS event. And here I would like to summarize some of the topics that I have learned, for anyone interested in what AWS has to offer this year. And though I did not take notes during the event, I will try to make sure all details are as accurate as possible.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mbRqhEjR6OIFoYGJbaM83g.jpeg" /><figcaption>So many people</figcaption></figure><h4>Design infrastructure for multi-region deployment</h4><p>There were 2 sessions I joined about this topic, which I was interested in because my company is also starting to do multi-region deployment. The first one in the morning explained how creating multi-AZ or multi-region workload to support high availability is not as simple as copying your workload to another AZ. For Active-standby setup, you need to have lots of manual work to switch over if your data is not synced between 2 zones. It is better to aim for active-active setup where you can switch over with no hassles.</p><p>First, we need a database that can replicate between 2 sites automatically, and Aurora is the suggested solution. Aurora have a shared storage that copies data between each AZ automatically and can sync data to other region too. The session goes further to explain the difference between the eventual consistency and strong consistency mode in Aurora, which you can pick according to your needs. For example, in Social Network applications, it is still acceptable to not see the latest updates in your news feed instantly. This means eventual consistency in data is ok in this case. The higher consistency comes with a higher cost of the time required to replicate the data.</p><p>For managing session, it is suggested to use stateless session management like JWT. And for caching, when you use ElastiCache (which do async replication) and you want to ensure strong consistency, you can attach metadata, like a timestamp or a flag, that indicate whether the local read has the latest data you want or not. If not, then you can direct to read directly from the actual DB or primary ElastiCache node.</p><p>A simple example for one-time data update:</p><blockquote>At Region A, app update the data in DB and then update the cache of key A in ElastiCache with flag is_data_replicated=true.</blockquote><blockquote>Then there 2 scenarios that can happen:</blockquote><blockquote>Lagging: At Region B, app read data of key A from local ElastiCache, but the flag is not available or is_data_replicated=false, then it knows it does not have the latest data yet, then it can go read data from DB directly.</blockquote><blockquote>Caught Up: At Region B, app read data of key A from local ElastiCache and found flag is_data_replicated=true then it knows the data is latest.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oaT1Ll5ipuVkyuQQJl3vsg.jpeg" /><figcaption>The Developer Chats zone with Kiro Haunted House behind.</figcaption></figure><p>In the second session in the afternoon, I learned about Aurora and Aurora DSQL which is a serverless solution that is highly-scalable and has even better support for active-active multi-region. We have in-depth details about how Aurora DSQL storage replication works with a special clock that help keeps time highly synchronized between regions, a write/read timestamp technique that helps ensure your read session get the consistent data at the time you read, and the 3 regions quorum setup that help failover the master node to another region correctly. And since it’s a managed solution, you don’t need to be concerned about any maintenance works.</p><h4>Architecture patterns and AI-DLC</h4><p>First part is about all the architecture patterns like microservices, event driven, SAGA, orchestrator.</p><p>Then comes to the highlight. AI-DLC which stands for AI development life cycle, from the classic software development life cycle (SDLC), is the new process that involves AI agent in every steps (Inception — Construction — Operation). In the demo, you start with a requirement document. The agent will read it and generate some questions with predefined choices to clarify the design, for example: what is the tech stack you’d like to use? which architecture patterns? You answer the questions and then it will generate a design document for you to review. After that you approve the design, it will generate the task plans for implementation. You review it again and proceed to the code generation steps, then review again. And so on, until the deployment and operate steps.</p><p>By this approach, AI can have precise context on the requirements and design by involving human as a reviewer in each steps of the life cycle. It helps prevent some anti-pattern when using AI agents which is talked about in another session. Some of examples : giving AI agent too much or too less context, agent got misled by not starting with new context, trusting the agent too much, etc. By using AI-DLC, this should lead to the output that is more accurate to what you have in mind when asking AI agents to generate codes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wDP9jMLb-P8hvIaAQlmPog.jpeg" /><figcaption>AI-DLC operating model</figcaption></figure><p>I have already joined a one-day workshop about AI-DLC at my company 2 months ago. And we’re testing this approach on a real project with my team. Still waiting to see how it can help us deliver faster. The sessions here give me some confirmation on how big a change this will be on how we develop software.</p><p>And I should note that you don’t even need Kiro to use AI-DLC. You can try AI-DLC on any AI coding platforms by following the instructions here: <a href="https://github.com/awslabs/aidlc-workflows#ai-dlc-ai-driven-development-life-cycle">https://github.com/awslabs/aidlc-workflows#ai-dlc-ai-driven-development-life-cycle</a></p><h4>Modernize Legacy apps with Kiro and AWS Transform</h4><p>This session began with some outlines on how legacy apps can be modernized in the scale from the basic moving to the overhaul: rehost, replatform, refactor and reimagine. Then it went on to demonstrate the approach of using Kiro, the AI-driven IDE and AWS Transform which is a CLI tool that use AI agent to analyze your legacy codes, create documents and upgrade your service from old version of language and frameworks to the modern one. For example, this can help ease the burden of migrating your old services from Java7/Spring Boot 2 to Java 21/Spring Boot 3.</p><p>Then this is backed up by the sharing from the architect at Gosoft. They shared their approach for modernize legacy services by first using AI tools to generate more documents and unit tests for the service first. After the service is modernized, they can run the unit tests to ensure nothing breaks.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DVyDyN9rEmZsTDK1Uebtvw.jpeg" /><figcaption>Let’s delete .. sorry, modernize legacy codes.</figcaption></figure><h4>Overall</h4><p>There are many more interesting sessions. And these are just some glimpse from the one that I have a chance to attend with my limited time. Some might think AWS is more about infra team than developers. But that gap should have already been less and less since the DevOps culture is promoted. And now with the new era of AI, there are even more powerful tool and services catering to devs than ever. The event is full of topics about new developer tools that can empower you to build things faster and better.</p><p>In all, it is a joy to be able to participate in this AWS Summit. Many fun activity throughout the day. The catering also provides great food, snack and drinks. The venue is convenient and clean. The topics shared are useful and presented very well.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=63119e1b8c84" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/my-notes-from-aws-summit-bangkok-2026-63119e1b8c84">My Notes from AWS Summit Bangkok 2026</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Manage Database Concurrency With Pessimistic and Optimistic Locking]]></title>
            <link>https://medium.com/ascend-developers/manage-database-concurrency-with-pessimistic-and-optimistic-locking-c5913b9b72a6?source=rss----98c9c9d1ba0c---4</link>
            <guid isPermaLink="false">https://medium.com/p/c5913b9b72a6</guid>
            <category><![CDATA[pessimistic-locking]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[concurrency]]></category>
            <category><![CDATA[optimistic-locking]]></category>
            <dc:creator><![CDATA[Napas Vinitnantharat]]></dc:creator>
            <pubDate>Mon, 11 May 2026 03:21:56 GMT</pubDate>
            <atom:updated>2026-05-11T03:21:54.616Z</atom:updated>
            <content:encoded><![CDATA[<p>In high-concurrency applications that interact with a database, it is essential to ensure that simultaneous transactions do not result in data loss or duplication. Proper concurrency control mechanisms must be implemented to maintain data consistency and integrity when multiple transactions attempt to update the same data concurrently. To ensure data consistency and integrity, an appropriate locking mechanism is required. This article explores the concepts of pessimistic locking and optimistic locking, how they work, how to use them, and also gives you some Java code to explore.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/742/1*MqHbv6ZuCTgr_9Q9RYtnJA.png" /></figure><p>Consider the situation when transaction 1 (T1) is trying to update the account balance from the database, while transaction 2 (T2) is also trying to update the balance at the same time. Without a locking mechanism after T1 and T2 updates, the account balance might be incorrect. This is because both Transactions have queried the same account balance. So both transactions are updated based on the same initial balance, which will result in the incorrect update of the account balance</p><p>Let’s write some code to see the behavior of the account balance without locking</p><p>Account Entity</p><pre>@Entity<br>@Table(name = &quot;accounts&quot;)<br>@Getter<br>@Setter<br>@AllArgsConstructor<br>@NoArgsConstructor<br>public class Account {<br><br>  @Id<br>  @GeneratedValue(strategy = GenerationType.IDENTITY)<br>  private Long id;<br><br>  @Column(nullable = false)<br>  private BigDecimal balance;<br>  <br>}</pre><p>Service layer code implementation of no lock deductBalance</p><pre>@Transactional<br>public Account deductBalance(Long accountId, BigDecimal amount) {<br>  String ThreadName = Thread.currentThread().getName();<br><br>  log.info(&quot;Thread={} | Fetching account id ={}&quot;, ThreadName, accountId));<br>  Account account = accountRepository.findById(accountId)<br>                    .orElseThrow(() -&gt; new IllegalArguementException(<br>                            &quot;Acount not found: &quot; + accountId));<br><br>  log.info(&quot;Thread={} | Read Balance ={}&quot;, ThreadName, account.getBalance()));<br><br>  // Simulate processing delay - makes the race window wide enogh to observe<br>  simulateDelay(100);<br><br>  if (account.getBalance().compareTo(amount) &lt; 0) {<br>    throw new IllegalStateException(&quot;Insufficient balance: &quot;, account.getBalance());<br>  }<br><br>  BigDecimal newBalance = account.getBalance().subtract(amount);<br>  account.setBalance(newBalance)<br>  <br>  Account saved = accountRepository.save(account);<br><br>  log.info(&quot;Thread={} | Updated balance ={}&quot;, ThreadName, saved.getBalance()));<br><br>}<br><br>private void simulateDelay(long millis) {<br>  try {<br>    Thread.sleep(millis);<br>  } catch(InterruptedException e) {<br>    Thread.currentThread().interrupt();<br>  }<br>}</pre><p>To properly test for a race condition in this code, we need to simulate concurrent execution by using a thread pool and submitting multiple tasks that operate on shared resources simultaneously, with the following code</p><pre>private List&lt;String&gt; runConcurrently(int threadCount, Callable&lt;String&gt; task) {<br>  ExecutorService executor = Executors.newFixedThreadPool(threadCount);<br>  List&lt;future&lt;String&gt;&gt; futures = new ArrayList&lt;&gt;();<br><br>  for (int i = 0; i &lt; threadCount; i++) {<br>    futures.add(executor.submit(task));<br>  }<br>  <br>  executor.shutdown();<br>  <br>  List&lt;String&gt; results = new ArrayList&lt;&gt;();<br>  for (Future&lt;String&gt; f: futures) {<br>    try{<br>      results.add(f.get());<br>    } catch(Exception e) {<br>      results.add(&quot;Error: &quot; + e.getMessage());<br>    }<br>  }<br>  <br>  return results;<br>}</pre><p>Let’s run with 5 threads, trying to deduct balance from the account with ID equal to 1, and each thread deducts $100 from the account. With the initial account balance of $1,000</p><pre>List&lt;String&gt; results = runConcurrently(5, () -&gt; {<br>  Account a = noLockService.deductBalance(1, 100);<br>  return &quot;OK -&gt; balance=&quot; + a.getBalance();<br>})</pre><p>After running the following code, let&#39;s see the result</p><pre>{<br>  &quot;results&quot; : [<br>    &quot;OK -&gt; balance=900.00&quot;,<br>    &quot;OK -&gt; balance=900.00&quot;,<br>    &quot;OK -&gt; balance=900.00&quot;,<br>    &quot;OK -&gt; balance=900.00&quot;,<br>    &quot;OK -&gt; balance=900.00&quot;<br>  ]<br>}<br><br>// SELECT id, balance FROM accounts<br>-------------------<br>|  id  |  balance |<br>-------------------<br>|  1   |   900    |<br>-------------------</pre><p>Even though five threads attempted to deduct $100 simultaneously, the final persisted balance is $900 instead of the expected $500. This happens because each thread fetches the account state concurrently and observes the same balance value of $1000. Without any locking strategy, every thread computes the updated balance as $900 and writes it back to the database, causing later writes to overwrite earlier ones. This demonstrates the <strong>lost update problem</strong></p><h3>Pessimistic Locking</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/878/1*0eOY7XAwma8u4mYe9IKwIA.png" /><figcaption>Two Transaction try to update account balance with pessimistic lock mechanism</figcaption></figure><p>When using pessimistic locking, we prevent another transaction from updating data by locking the related rows or table. This operation will be done at the database level. So, in case another transaction tries to update the data, it must wait until the lock is released.</p><h4><strong>Pessimistic Locking Flow</strong></h4><ol><li>T1 select data from the database to update the new balance</li><li>T1 acquires the database lock to prevent other transactions from updating the account balance data</li><li>T2 selects data from the database to update the new balance</li><li>T1 commits the updated balance to the database and releases the lock</li><li>T2, continue to update the new balance after T1 releases the lock</li></ol><p>Let’s write some code to see the behavior of the account balance with pessimistic locking</p><p>Repository layer code implementation with pessimistic locking</p><pre>@Repository<br>public interface AccountRepository extends JpaRepository&lt;Account, Long&gt; {<br><br>  // Generates: SELECT ... FOR UPDATE <br>  // The DB places an exclusive row lock; all other writers <br>  // on the same row block<br>  @Lock(LockModeType.PESSIMISTIC_WRITE)<br>  @Query(&quot;SELECT a FROM Account a WHERE a.id = :id&quot;)<br>  Optional&lt;Account&gt; findByIdWithPessimisticLock(@Param(&quot;id&quot;) Long id);<br>}</pre><p>Service layer code implementation of no lock deductBalance</p><pre>@Transactional<br>public Account deductBalance(long accountId, BigDecimal amount) {<br>  log.info(&quot;Thread={} | Acquiring FOR UPDATE lock on account id={}&quot;,<br>    Thread.currentThread().getName(), accountId);<br><br>  // Acquire the Lock, Block another thread to execute the code<br>  Account account = accountRepository.findByIdWithPessimisticLock(accountId)<br>      .orElseThrow(() -&gt; <br>          new IllegalArguementException(&quot;Account not found: &quot;, accountId));<br>  <br>  log.info(&quot;Thread ={} | Lock acquired. balance={}&quot;,<br>        Thread.currentThread().getName(), account.getBalance());<br><br>  // Simulate processing delay - other threads are BLOCKED at the SELECT above<br>  simulateDelay(100);<br>  <br>  if (account.getBalance().compareTo(amount) &lt; 0) {<br>    throw new IllegalStateException(&quot;Insufficient balance: &quot;, account.getBalance());<br>  }<br><br>  BigDecimal newBalance = account.getBalance().subtract(amount);<br>  account.setBalance(newBalance)<br>  <br>  Account saved = accountRepository.save(account);<br><br>  log.info(&quot;Thread={} | Updated balance ={}&quot;, ThreadName, saved.getBalance()));<br>}<br><br>private void simulateDelay(long millis) {<br>  try {<br>    Thread.sleep(millis);<br>  } catch(InterruptedException e) {<br>    Thread.currentThread().interrupt();<br>  }<br>}<br></pre><p>Let’s run with 5 threads, trying to deduct balance from the account with ID equal to 1, and each thread deducts $100 from the account. With the initial account balance of $1,000</p><pre>List&lt;String&gt; results = runConcurrently(5, () -&gt; {<br>  Account a = pessimisticLockService.deductBalance(1, 100);<br>  return &quot;OK -&gt; balance=&quot; + a.getBalance();<br>})</pre><p>After running the following code, let’s see the result</p><pre>{<br>  &quot;results&quot; : [<br>    &quot;OK -&gt; balance=800.00&quot;,<br>    &quot;OK -&gt; balance=600.00&quot;,<br>    &quot;OK -&gt; balance=700.00&quot;,<br>    &quot;OK -&gt; balance=900.00&quot;,<br>    &quot;OK -&gt; balance=500.00&quot;<br>  ]<br>}<br>// SELECT id, balance FROM accounts<br>-------------------<br>|  id  |  balance |<br>-------------------<br>|  1   |   500    |<br>-------------------</pre><p>As you can see, we have an initial account balance of $1,000 with a pessimistic locking strategy, which allows only one thread to access the resources at a time; these threads are deducting the balance sequentially. So no race condition occurs in this case. The balance is deducted correctly with the final balance of 500$.</p><h4>Type of Locks</h4><ul><li><strong>Shared Lock</strong></li></ul><p>With a shared lock, we allow multiple transactions to read the same data at the same time, but do not allow transactions to update, modify, or delete it until the shared lock is released.</p><pre>BEGIN;<br>-- lock row update, delete operation <br>SELECT * FROM account WHERE account_id = 1 FOR SHARE;</pre><ul><li><strong>Exclusive lock</strong></li></ul><p>On the other hand, an exclusive lock prevents other transactions from reading or modifying the data; if another transaction tries to acquire a shared lock or exclusive lock, it will wait until the lock is released.</p><pre>BEGIN;<br><br>SELECT * FROM account WHERE id = 1 <br>FOR UPDATE;  -- locks row not allow read, update, delete<br><br>-- do business logic<br>UPDATE account <br>SET balance = balance - 100 <br>WHERE id = 1;<br><br>COMMIT;</pre><h4>When to use Pessimistic Locking</h4><ul><li>Critical financial operations</li><li>High chance of data conflict during update, write data to the database</li><li>Data consistency is more important than performance</li></ul><h3>Optimistic Locking</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/878/1*HuOuURBhAQe8Ex4fMBf3pg.png" /><figcaption>Two Transaction try to update account balance with optimistic lock mechanism — No retry</figcaption></figure><p>Optimistic locking uses a technique that allows multiple transactions to read and modify the same data concurrently. Instead of locking transactions via the database, the Optimistic Locking approach detects conflicts at commit time by checking a <strong>version column</strong>. If the version has changed since it was read, the update will affect zero rows, indicating a conflict.</p><pre>UPDATE account <br>SET balance = balance - 70, version = version + 1 <br>WHERE id = 1 AND version = 1</pre><h4>Optimistic Locking Flow</h4><ol><li>T1 selects data from the database with version V1</li><li>T2 also selects data from that database with version V1</li><li>T1 updates the new account balance and updates the account version from V1 to V2</li><li>T2 updated the new account balance with version V1 and got no row affected because T1 has updated the version V1 to V2</li></ol><p>Let’s write some code to see the behavior of the account balance with optimistic locking</p><p>Repository Layer Code</p><pre>@Entity<br>@Table(name = &quot;accounts&quot;)<br>@Getter<br>@Setter<br>@AllArgsConstructor<br>@NoArgsConstructor<br>public class Account {<br><br>  @Id<br>  @GeneratedValue(strategy = GenerationType.IDENTITY)<br>  private Long id;<br><br>  @Column(nullable = false)<br>  private BigDecimal balance;<br><br>  /**<br>   * Used by JPA optimistic lockig.<br>   * Hibernate reads this value before UPDATE and adds a<br>   * {@code WHERE version = ?} clause - causing a <br>   * {@link jararta.persistence.OptimisticLockException} when two<br>   * concurrent transactions try update the same row.<br>   */<br>  @Version<br>  @Column(nullable = false)<br>  private Long version;<br>  <br>}</pre><p>Service Layer Code</p><pre><br>@Transactional<br>public Account deductBalance(Long accountId, BigDecimal amount) {<br>  String ThreadName = Thread.currentThread().getName();<br><br>  log.info(&quot;Thread={} | Fetching account id ={}&quot;, ThreadName, accountId));<br>  Account account = accountRepository.findById(accountId)<br>                    .orElseThrow(() -&gt; new IllegalArguementException(<br>                            &quot;Acount not found: &quot; + accountId));<br><br>  log.info(&quot;Thread={} | Read Balance ={}&quot;, ThreadName, account.getBalance()));<br><br>  // Simulate processing delay - makes the race window wide enogh to observe<br>  simulateDelay(100);<br><br>  if (account.getBalance().compareTo(amount) &lt; 0) {<br>    throw new IllegalStateException(&quot;Insufficient balance: &quot;, account.getBalance());<br>  }<br><br>  BigDecimal newBalance = account.getBalance().subtract(amount);<br>  account.setBalance(newBalance)<br>  <br>  Account saved = accountRepository.save(account);<br><br>  log.info(&quot;Thread={} | Updated balance ={}&quot;, ThreadName, saved.getBalance()));<br><br>}<br><br>private void simulateDelay(long millis) {<br>  try {<br>    Thread.sleep(millis);<br>  } catch(InterruptedException e) {<br>    Thread.currentThread().interrupt();<br>  }<br>}</pre><p>Adding the version annotation to the account entity will generate a where clause `<strong>WHERE version = ?` </strong>So when two concurrent transactions try to update the same row, it will cause an <strong>OptimisticLockException</strong>.</p><p>Let’s run with 5 threads, trying to deduct balance from the account with ID equal to 1, and each thread deducts $100 from the account. With the initial account balance of $1,000</p><pre>List&lt;String&gt; results = runConcurrently(5, () -&gt; {<br>  Account a = optimisticLockService.deductBalance(1, 100);<br>  return &quot;OK -&gt; balance=&quot; + a.getBalance();<br>})</pre><p>After running the following code, let’s see the result</p><pre><br>{<br>  &quot;results&quot;: [<br>    &quot;OK -&gt; balance=900.00&quot;,<br>    &quot;ERROR: org.springframework.orm.ObjectOptimisticLockingFailureException: Unexpected row count&quot;,<br>    &quot;ERROR: org.springframework.orm.ObjectOptimisticLockingFailureException: Unexpected row count&quot;,<br>    &quot;ERROR: org.springframework.orm.ObjectOptimisticLockingFailureException: Unexpected row count&quot;,<br>    &quot;ERROR: org.springframework.orm.ObjectOptimisticLockingFailureException: Unexpected row count&quot;<br>  ]<br>}<br><br>// SELECT id, balance, version FROM accounts<br>-----------------------------<br>|  id  |  balance | version |<br>-----------------------------<br>|  1   |   900    |    2    |<br>-----------------------------</pre><p>The result shows that with an optimistic locking mechanism, only one thread can update the account balance. While the other 4 threads are failing to update the account balance.</p><h4><strong>Retry Strategy with Optimistic Locking</strong></h4><p>With Optimistic Locking, the conflict is checked at commit time, causing the concurrent transaction to fail. In a real-life scenario, we might not want the transaction to fail during a concurrent update. So, to solve this problem, we need a retry strategy to retry updating the transaction, as shown in the following figure.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/752/1*ufikmrbK62IkwxTwzLFsZQ.png" /><figcaption>Two Transaction try to update account balance with optimistic lock mechanism — with retry</figcaption></figure><p>Instead of letting T2 fail when T1 has already updated the account, we add <strong>Step 5: Retry the account balance update multiple times</strong> to ensure the transaction completes.</p><p>In a high-contention scenario, multiple transactions attempt to update the data concurrently, which can lead to many failed updates and retries. This can cause a database performance problem. So, Using Optimistic locking might not be a good idea in a high contention environment</p><pre>private static final int MAX_RETRIES = 5;<br><br>public Account deductBalance(Long accountId, BigDecimal amount) {<br>  int attempt = 0;<br>  <br>  while (attempt &lt; MAX_RETRIES) {<br>    attempt++;<br>    log.info(&quot;Thread={} | Attempt={} for account id ={}&quot;, <br>        Thread.currentThread().getName(), attempt, accountId);<br>    try {<br>      return tryDeductBalance(accountId, amount);<br>    } catch(ObjectOptimisticLockingFailureException ex) {<br>      log.warning(&quot;Thread={} | Version conflict on attempt {}. Retrying...&quot;,<br>        Thread.currentThread().getName(), attempt)<br>      if (attempt &gt;= MAX_RETRIES) {<br>        throw new IllegalStateException(&quot;Could not deduct account balance for&quot; <br>          + MAX_RETRIES + &quot;attempts due to concurrent update&quot;)<br>      }<br>    }<br>  // unreachable, but satisfies the complier<br>  throw new IllegalStateException(&quot;Unexpected state in deducting balance&quot;);<br>  } <br>}<br><br>@Transactional<br>public Account tryDeductBalance(Long accountId, BigDecimal amount) {<br>  String ThreadName = Thread.currentThread().getName();<br><br>  log.info(&quot;Thread={} | Fetching account id ={}&quot;, ThreadName, accountId));<br>  Account account = accountRepository.findById(accountId)<br>                    .orElseThrow(() -&gt; new IllegalArguementException(<br>                            &quot;Acount not found: &quot; + accountId));<br><br>  log.info(&quot;Thread={} | Read Balance ={}&quot;, ThreadName, account.getBalance()));<br><br>  // Simulate processing delay - makes the race window wide enogh to observe<br>  simulateDelay(100);<br><br>  if (account.getBalance().compareTo(amount) &lt; 0) {<br>    throw new IllegalStateException(&quot;Insufficient balance: &quot;, account.getBalance());<br>  }<br><br>  BigDecimal newBalance = account.getBalance().subtract(amount);<br>  account.setBalance(newBalance)<br>  <br>  Account saved = accountRepository.save(account);<br><br>  log.info(&quot;Thread={} | Updated balance ={}&quot;, ThreadName, saved.getBalance()));<br><br>}<br><br>private void simulateDelay(long millis) {<br>  try {<br>    Thread.sleep(millis);<br>  } catch(InterruptedException e) {<br>    Thread.currentThread().interrupt();<br>  }<br>}</pre><p>Let’s try to run it again with a retry strategy, so we will test with MAX_RETRIES equal to 5, and try to run 5 concurrent threads to see the result</p><pre>{<br>  &quot;results&quot;: [<br>    &quot;OK -&gt; balance=700.00&quot;,<br>    &quot;OK -&gt; balance=900.00&quot;,<br>    &quot;OK -&gt; balance=500.00&quot;,<br>    &quot;OK -&gt; balance=800.00&quot;,<br>    &quot;OK -&gt; balance=600.00&quot;,<br>  ]<br>}<br><br>// SELECT id, balance, version FROM accounts<br>-----------------------------<br>|  id  |  balance | version |<br>-----------------------------<br>|  1   |   500    |    6   |<br>-----------------------------</pre><h4>When to use Optimistic Locking</h4><ul><li>Low chance of data conflict during the update of data</li><li>The application has more read operations than write operations</li><li>Read latency is important</li><li>Transactions take a long time to complete, and holding locks during the entire process isn’t feasible.</li></ul><h4><strong>Reference</strong></h4><ul><li><a href="https://dev.to/jszutkowski/pessimistic-vs-optimistic-locking-in-mysql-3hgc">https://dev.to/jszutkowski/pessimistic-vs-optimistic-locking-in-mysql-3hgc</a></li><li><a href="https://newsletter.systemdesigncodex.com/p/pessimistic-vs-optimistic-locking">https://newsletter.systemdesigncodex.com/p/pessimistic-vs-optimistic-locking</a></li><li><a href="https://www.abstractalgorithms.dev/types-of-locks-explained-tips-for-maintaining-consistent-systems-and-avoiding-write-conflicts">https://www.abstractalgorithms.dev/types-of-locks-explained-tips-for-maintaining-consistent-systems-and-avoiding-write-conflicts</a></li><li><a href="https://raigardastautkus.medium.com/optimistic-vs-pessimistic-locking-basics-of-concurrency-in-databases-2f3adb290b2f">https://raigardastautkus.medium.com/optimistic-vs-pessimistic-locking-basics-of-concurrency-in-databases-2f3adb290b2f</a></li><li><a href="https://www.youtube.com/watch?v=lR6y-0RC5cc&amp;t=2075s">https://www.youtube.com/watch?v=lR6y-0RC5cc</a></li><li><a href="https://medium.com/@felipe.ascari_49171/concurrent-transactions-in-go-pessimistic-vs-optimistic-locks-part-1-2-33f866561feb">https://medium.com/@felipe.ascari_49171/concurrent-transactions-in-go-pessimistic-vs-optimistic-locks-part-1-2-33f866561feb</a></li><li><a href="https://medium.com/engineering-housing/optimistic-locking-overview-6b30315e759b">https://medium.com/engineering-housing/optimistic-locking-overview-6b30315e759b</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c5913b9b72a6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/manage-database-concurrency-with-pessimistic-and-optimistic-locking-c5913b9b72a6">Manage Database Concurrency With Pessimistic and Optimistic Locking</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Terminal tools for boosting productivity]]></title>
            <link>https://medium.com/ascend-developers/terminal-tools-for-boosting-productivity-894bd272fccd?source=rss----98c9c9d1ba0c---4</link>
            <guid isPermaLink="false">https://medium.com/p/894bd272fccd</guid>
            <category><![CDATA[terminal]]></category>
            <category><![CDATA[terminal-commands]]></category>
            <dc:creator><![CDATA[Napas Vinitnantharat]]></dc:creator>
            <pubDate>Mon, 02 Mar 2026 04:42:06 GMT</pubDate>
            <atom:updated>2026-03-02T04:42:05.135Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-b9k4lYuZJLY0Xy-Ole-IQ.png" /></figure><p>As a software developer, we use the terminal a lot of the time, whether it is about running a backend service on a local machine, connecting to a remote server, or even finding the right file in some directory. So having a good terminal setup helps improve a lot of productivity. This article will walk through the tools and recommend setups that will help your software development productivity</p><h3><strong>Select the terminal you like</strong></h3><p>Nowadays, there are a lot of terminals tool in the market such as <a href="https://terminaltrove.com/terminals/iterm2/">iTerm2</a>, <a href="https://terminaltrove.com/terminals/alacritty/">Alacritty</a>, <a href="https://terminaltrove.com/terminals/wezterm/">Wezterm</a>, <a href="https://www.warp.dev/">Warp</a>, etc. So the question is “ Which terminal should we use?” Based on my experience, I found that you have to try it to find what terminal is suitable for you. If you like a terminal that enriches AI agents&#39; tools, you might try <strong>Warp</strong>, But If you focus on simplicity and speed, you might want to try <strong>Alacritty</strong>. I recommend not using the default terminal app that comes with macOS because it offers little functionality.</p><h3>Decorate your terminal</h3><p>Decorating your terminal is not just about having a fancy terminal look, but need to give us information about the context of the directory you are working in. For example, when you are developing software, you want to know what git branch you are working with. So it will be great if you have a terminal plugin that tells you what branch you are on. There are a lot of ways to decorate your terminal; you can use tools like Oh my zsh, Oh my posh, and Starship.</p><h3>Add an alias to your terminal</h3><p>Sometimes when you has long command to type on the terminal, and you have to use it a lot of the time, it might be great if you have a way to make a shortcut of this long command using an alias</p><p>To add an alias to the terminal, open the file .bashrc ( if you use zsh, open.zshrc file instead) add alias by adding the following command</p><pre>alias la=&#39;ls -a&#39;</pre><p>By adding the following command, this creates a shortcut <strong>la, </strong>for the shell command ls -a, which lists all files in the directory</p><p>To list alias that you have created, use the command alias</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7w1SpVmQO1ezqehmQrIGAg.png" /></figure><h3><strong>Recommend CLI tools</strong></h3><h4><strong>zsh-autosuggestion</strong></h4><p><a href="https://github.com/zsh-users/zsh-autosuggestions">zsh autosuggestion</a> is one of the CLI tools I really like. This plugin auto- suggestion command you type in gray text, and if you press → on the keyboard, it means you accept the suggestion. This improves the speed a lot when you have to type a long command. By using <strong>zsh-autosuggestion,</strong> you just type a few letters on the terminal, then press →</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1021/1*xGpA3qFXxAC6GZulQCHMyg.png" /></figure><p>To install zsh-autosuggestion in your terminal, you can use Homebrew with the following command,</p><pre>brew install zsh-autosuggestions</pre><p>then activate autosuggestion by adding the following at the end of the file <strong>.zshrc</strong></p><pre>source $(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh</pre><h4>fd</h4><p><a href="https://github.com/sharkdp/fd">fd</a> is a program used for finding file, directory in your filesystem. It is similar to the command <a href="https://linux.die.net/man/1/find">find</a>, but much faster and simpler than the <a href="https://linux.die.net/man/1/find">find</a> command that comes up by default on Mac. In case you want to find a file in your system with fd, you can use the following command</p><pre>fd &quot;auth&quot; -t f # finding a file that contain auth </pre><p>compared to the find command</p><pre>find . -type f -name &quot;*auth*&quot;</pre><p>If you are using the find command in a large project, this might take 2 minutes to list all of the files, while using fd might take less than a second</p><h4><strong>fzf</strong></h4><p><a href="https://github.com/junegunn/fzf">fzf (fuzzy finder)</a> is a command line tool for filtering search list such as finding files in a directory, looking for git commits, and finding history commands that you been using. I use fzf for finding files and finding history commands. We can install Fzf using Homebrew with the following command</p><pre>brew install fzf</pre><p>To find a directory, I create a custom command, so when I use this command, it will change the directory automatically after completing the find from fzf. To achieve this functionality, we add the following command in file <strong>.zshrc</strong></p><pre>cdf() {<br>  local dir<br>  dir=$(fd --type d . | fzf) &amp;&amp; cd &quot;$dir&quot;<br>}</pre><h4>ripgrep</h4><p><a href="https://github.com/BurntSushi/ripgrep">ripgrep</a> is a tool used for searching text similar to grep, but the difference is that it recursively searches in the current directory by default. To install ripgrep, use the following command</p><pre>brew install ripgrep</pre><p>One of my use cases is to identify the configuration in the project. For Example If you want to find which microservice is using this API. You can use it ripgrep with the following command.</p><pre>rg --hidden /api/v1/user  # finding word /api/v1/user include hidden file</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/763/1*SnJHi_loG443T3vufH4XRA.png" /><figcaption>example of using riggrep (rg command) for search /api/v1/user</figcaption></figure><p>After running the ripgrep command, It show that 3 places contain the word /api/v1/user</p><h4>jq</h4><p><a href="https://github.com/jqlang/jq">jq</a> is a tool used for processing JSON in the terminal. One of the common usecase to use jq is to prettify the JSON payload. For example,</p><pre>echo &#39;{&quot;fruit&quot;:{&quot;name&quot;:&quot;apple&quot;,&quot;color&quot;:&quot;green&quot;,&quot;price&quot;:1.20}}&#39; | jq &#39;.&#39;</pre><p>with this command, the output will be the prettification of the JSON payload</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1015/1*sJ_GsyC2ne34FcnmmuVDyw.png" /></figure><h4><strong>bat</strong></h4><p><a href="https://github.com/sharkdp/bat">bat</a> is a command-line tool that is enhanced from the cat command. It adds feature syntax highlight to the cat command. Install bat using the following command</p><pre>brew install bat</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/656/0*1MOXMLNWmN7FQ5z8" /><figcaption>Syntax highlight from using bat reference: <a href="https://github.com/sharkdp/bat">https://github.com/sharkdp/bat</a></figcaption></figure><h4>lsd</h4><p><a href="https://github.com/lsd-rs/lsd">lsd</a> is similar to the lscommand, but lsd with more colorful and file, directory icons for a better experience. To Install lsd using the following command</p><pre>brew install lsd</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/812/1*10-iY9AMV7MDMvXOBrRpLg.png" /><figcaption>reference: <a href="https://github.com/lsd-rs/lsd?tab=readme-ov-file">https://github.com/lsd-rs/lsd?tab=readme-ov-file</a></figcaption></figure><p>When installing lsd, I typically configure the following alias to facilitate more convenient usage.</p><pre>alias ls=&#39;lsd&#39; # use ls command as lsd <br><br>alias l=&#39;ls -l&#39;<br>alias la=&#39;ls -a&#39;<br>alias lla=&#39;ls -la&#39;<br>alias lt=&#39;ls --tree&#39;</pre><h4><strong>tmux</strong></h4><p>tmux is a program that runs on a terminal. It allow user to create multiple terminal session with in single window. This tool is very handy when we open a lot of terminals. For example In the software development process, you might have one terminal for running Docker, one terminal for running remote to the server, and one terminal for running the application. This might be chaos when we have a lot of terminals opening. So using tmux, we can combine those terminals in a single window.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/1*66mF3jfdXpavbve0dY33fQ.png" /><figcaption>reference: <a href="https://www.tecmint.com/tmux-to-access-multiple-linux-terminals-inside-a-single-console/">https://www.tecmint.com/tmux-to-access-multiple-linux-terminals-inside-a-single-console/</a></figcaption></figure><p>To install tmux, we can use the following command</p><pre>brew install tmux</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=894bd272fccd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/terminal-tools-for-boosting-productivity-894bd272fccd">Terminal tools for boosting productivity</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[แนะนำการสร้าง Image Slip ด้วย java.awt]]></title>
            <link>https://medium.com/ascend-developers/%E0%B9%81%E0%B8%99%E0%B8%B0%E0%B8%99%E0%B8%B3%E0%B8%81%E0%B8%B2%E0%B8%A3%E0%B8%AA%E0%B8%A3%E0%B9%89%E0%B8%B2%E0%B8%87-image-slip-%E0%B8%94%E0%B9%89%E0%B8%A7%E0%B8%A2-java-awt-ea407cecca07?source=rss----98c9c9d1ba0c---4</link>
            <guid isPermaLink="false">https://medium.com/p/ea407cecca07</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[slip]]></category>
            <category><![CDATA[abstract-window-toolkit]]></category>
            <category><![CDATA[awt]]></category>
            <category><![CDATA[image]]></category>
            <dc:creator><![CDATA[Poobet Kan]]></dc:creator>
            <pubDate>Tue, 06 Jan 2026 06:24:58 GMT</pubDate>
            <atom:updated>2026-01-06T06:24:51.729Z</atom:updated>
            <content:encoded><![CDATA[<p>ผมได้รับโจทย์หนึ่งของงานคือการสร้าง slip image ที่มี details ของ slip ไม่มากและไม่ซับซ้อน ทำให้นึกถึง java.awt library ที่มาพร้อมกับ java ที่เคยใช้เมื่อนานมาแล้ว จึงได้นำมาเปรียบเทียบกับ tool หรือ library อื่นๆ เช่น jasper report เป็นต้น แต่เนื่องจากงานไม่ได้มีความซับซ้อนจึงได้ทดลองใช้งาน java.awt ก่อน และผมว่ามันค่อยข้างเหมาะสมกับงานชิ้นนี้จึงได้นำมาแชร์ และแนะนำ</p><p>สาเหตุที่ได้เลือกใช้ java.awt (Abstract Window Toolkit) ที่เป็น java library ทางด้าน GUI ของจาวา เพราะความสามารถหลากหลายทั้งสร้าง User interface หรือ การ planting image นอกจากนี้ ยังเลือกใช้งานตามข้อดีด้านล่างต่อไปนี้อีกด้วย</p><p><strong>ข้อดีของการใช้ java.awt</strong> ยกตัวอย่าง เช่น</p><p><strong>Lightweight</strong></p><ul><li>ไม่ต้องพึ่งพา Library ภายนอก เพราะใช้เพียง library ของจาวาเองเลย ยกตัวอย่าง เช่น หากสร้าง image ด้วย jasper report ก็จำเป็นต้องมี library ของ jasper report เพิ่ม และจำเป็นต้อง manage library ที่เกี่ยวข้องให้ดีให้ไม่มี hotspot code จากการใช้งาน library ภายนอก ถ้าใครเคยใช้งาน jasper report จะทราบดีว่าหากใช้งาน library ของบาง version จะไม่ผ่าน sonar scan ต้องมี manage การ exclude และ include library</li></ul><p><strong>การระบุขนาดและตำแหน่งละเอียด</strong></p><ul><li>เนื่องจากใน code สามารถระบุตำแหน่ง กำหนดขนาดของ image ได้อย่างแม่นยำเพราะสามารถระบุเป็นตัวเลขของตำแหน่ง และจุด pixel ได้เลย ยกตัวอย่าง เช่น สามารถกำหนดตำแหน่งของข้อความ รูปภาพ หรือเส้นแบ่งต่างๆ ได้ในระดับ pixel ผ่านคลาส Graphics2D ซึ่งเหมาะมากสำหรับสลิปที่ต้องมีหน้าตาคงที่</li></ul><p><strong>มี Performance ที่ดี</strong></p><ul><li>เพราะการทำงานของ java.awt จัดอยู่ในระดับการทำงานที่เป็น low level Application Programming Interface ซึ่งใกล้ชิดกับฮาร์ดแวร์จึงทำงานได้ไว</li></ul><p><strong>Headless</strong></p><ul><li>สามารถประมวลผล code ให้กลายเป็นรูปภาพโดยที่ไม่สนฮาร์ดแวร์ หรือคำนึงถึง os ที่ run เช่น run บน cloud หรือเครื่อง linux เป็นต้น</li></ul><p><strong>แต่ java.awt ก็มีข้อเสีย</strong> เช่น</p><p><strong>ความยากในการเขียน Code</strong></p><ul><li>คุณต้องคำนวณตำแหน่ง (x, y) เองทั้งหมด การขยับฟอร์มเล็กน้อยแต่ล่ะครั้งอาจจะหมายถึงแก้ไขโปรแกรมหลายบรรทัด ดังนั้นจึงไม่เหมาะกับรูปหรือเอกสารที่ซับซ้อน</li><li>การใช้งานภาษาไทยจำเป็นต้องเขียน code เพื่ออ่าน font เอง เพื่อความถูกต้องของการแสดงผล</li></ul><p><strong>มาดูตัวอย่างของ code กัน</strong></p><pre>import java.awt.*;<br>import java.awt.image.BufferedImage;<br>import java.io.File;<br>import java.io.IOException;<br>import javax.imageio.ImageIO;<br><br>public class SimpleSlipGenerator {<br>    public static void main(String[] args) throws IOException {<br>        int width = 400;<br>        int height = 500;<br><br>        // 1. สร้าง Canvas ของ slip ที่เป็น BufferedImage<br>        //    กำหนดขนาดและประเภทของภาพ<br>        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);<br><br>        // 2. รับ Graphics2D จาก BufferedImage เพื่อวาดรูป<br>        Graphics2D g2d = bufferedImage.createGraphics();<br><br>        // 3. set ให้ตัวอักษรและเส้นเนียนสวยไม่ดูหยาบเป็นพิกเซล<br>        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);<br>        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);<br><br>        // 4. พื้นหลัง เติมพื้นหลังสีขาวแบบเต็มพื้นที่<br>        g2d.setColor(Color.WHITE);<br>        g2d.fillRect(0, 0, width, height);<br><br>        // 5. ใส่ background สีส้มด้านบน<br>        g2d.setColor(new Color(255, 100, 0));<br>        g2d.fillRect(0, 0, width, 80);<br><br>        // 6. set ฟอนต์สำหรับหัวข้อ<br>        g2d.setColor(Color.WHITE);<br>        g2d.setFont(new Font(&quot;Tahoma&quot;, Font.BOLD, 22));<br>        g2d.drawString(&quot;Transfer Slip&quot;, 20, 50);<br>        int startY = 150;<br><br>        // 7. วาดเส้นแบ่ง<br>        g2d.setColor(Color.LIGHT_GRAY);<br>        g2d.drawLine(30, 100, width - 30, 100);<br><br>        // 8. เขียนข้อมูลการโอนเงิน<br>        g2d.setColor(Color.BLACK);<br>        g2d.drawString(&quot;From: นายร่ำรวย จ่ายเงินเยอะ&quot;, 30, startY);<br>        g2d.drawString(&quot;To: นายรวยกว่า นายร่ำรวย&quot;, 30, startY + 40);<br><br>        //9. วาดเส้นแบ่ง<br>        g2d.setColor(Color.LIGHT_GRAY);<br>        g2d.drawLine(30, startY + 70, width - 30, startY + 70);<br><br>        //10. เขียนจำนวนเงิน transaction Amount<br>        g2d.setColor(new Color(255, 100, 0));<br>        g2d.setFont(new Font(&quot;Tahoma&quot;, Font.BOLD, 15));<br>        g2d.drawString(&quot;transaction Amount 1,500.00 THB&quot;, 30, startY + 120);<br><br>        //11. เขียนวันที่และเลขที่อ้างอิง<br>        g2d.setColor(Color.GRAY);<br>        g2d.setFont(new Font(&quot;Tahoma&quot;, Font.PLAIN, 12));<br>        g2d.drawString(&quot;Ref: 20240327ABC123&quot;, 30, height - 50);<br>        g2d.drawString(&quot;Date: 27 Mar 2024, 14:30&quot;, 30, height - 30);<br><br>        //12. ปิด Graphics2D<br>        g2d.dispose();<br><br>        //13. สร้างรูปภาพ<br>        File file = new File(&quot;slip_output.png&quot;);<br>        ImageIO.write(bufferedImage, &quot;png&quot;, file);<br><br>    }<br>}</pre><p>ผลลัพธ์ของ code</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*jKPsFNs93_ed7RrDBeceqQ.png" /></figure><p>จากตัวอย่างจะเห็นว่าในการใช้งานต้องมีการประกาศค่า BufferedImage และ Graphics2D มีการ set Font และสีสลับกันไปมาได้อย่างอิสระ แต่เนื่องจากการ generate slip image อาศัย codebase เพียงอย่างเดียวและเรียงข้อความจากบนลงล่างเพราะแบบนี้ผู้เขียนจึงนำ awt มาใช้เขียน slip image เพราะไม่ได้มีความซับซ้อนเพื่อให้ code มี performance ที่ดี</p><p>ข้อควรพิจารณาว่าควรใช้ awt ในการสร้างรูปนั้นๆ หรืองานนั้นๆหรือไม่ ต้องดูความซับซ้อนของรูปหรือเอกสาร เพราะยิ่งรูปมีความซับซ้อน นั้นหมายถึงการเขียน code ที่ซับซ้อนขึ้น และต้องมีการทำความเข้าใจการจดจำตำแหน่ง pixel จุด start ในการ planting image ของข้อมูลเป็นอย่างมาก อาจจะต้องพิจารณา tool หรือ libraty อื่นๆในการสร้าง image หรือไฟล์นั้นแทน</p><p>อ้างอิง</p><p><a href="https://www.oracle.com/java/technologies/painting.html">https://www.oracle.com/java/technologies/painting.html</a></p><p><a href="https://www.geeksforgeeks.org/java/java-awt-tutorial/">Java AWT Tutorial - GeeksforGeeks</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ea407cecca07" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/%E0%B9%81%E0%B8%99%E0%B8%B0%E0%B8%99%E0%B8%B3%E0%B8%81%E0%B8%B2%E0%B8%A3%E0%B8%AA%E0%B8%A3%E0%B9%89%E0%B8%B2%E0%B8%87-image-slip-%E0%B8%94%E0%B9%89%E0%B8%A7%E0%B8%A2-java-awt-ea407cecca07">แนะนำการสร้าง Image Slip ด้วย java.awt</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[HNY ปีม้าไฟ 2026 ~ 6 กับดักชีวิต ที่อยากให้คุณเลี่ยง]]></title>
            <link>https://medium.com/ascend-developers/hny-%E0%B8%9B%E0%B8%B5%E0%B8%A1%E0%B9%89%E0%B8%B2%E0%B9%84%E0%B8%9F-2026-6-%E0%B8%81%E0%B8%B1%E0%B8%9A%E0%B8%94%E0%B8%B1%E0%B8%81%E0%B8%8A%E0%B8%B5%E0%B8%A7%E0%B8%B4%E0%B8%95-%E0%B8%97%E0%B8%B5%E0%B9%88%E0%B8%AD%E0%B8%A2%E0%B8%B2%E0%B8%81%E0%B9%83%E0%B8%AB%E0%B9%89%E0%B8%84%E0%B8%B8%E0%B8%93%E0%B9%80%E0%B8%A5%E0%B8%B5%E0%B9%88%E0%B8%A2%E0%B8%87-5b22e2e7e943?source=rss----98c9c9d1ba0c---4</link>
            <guid isPermaLink="false">https://medium.com/p/5b22e2e7e943</guid>
            <category><![CDATA[hny2026]]></category>
            <category><![CDATA[lifestyle]]></category>
            <category><![CDATA[work-life-balance]]></category>
            <dc:creator><![CDATA[Numpol Poldongnok]]></dc:creator>
            <pubDate>Fri, 02 Jan 2026 09:16:47 GMT</pubDate>
            <atom:updated>2026-01-02T09:16:46.568Z</atom:updated>
            <content:encoded><![CDATA[<p>สวัสดีปีใหม่ 2026 ครับสหาย Dev ทุกท่าน… สหายโทนี่ เองครับ</p><p>ปีนี้ปีม้าไฟ พลังงานมันพลุ่งพล่านจนหลายคนอยากจะรีบควบม้าออกไปสร้างเนื้อสร้างตัว แต่ในฐานะที่ผมควบม้า (และตกม้า) ในวงการ Dev มาเกือบ 10 ปี ผมเห็นน้องๆ สหายรุ่นใหม่หลายคนกำลังวิ่งเข้าหา “กับดัก” เดิมๆ ที่ผมเคยพลาดมาแล้ว วันนี้ผมไม่ได้จะมาชวน Code แต่จะมาทำ Life Review 6 ข้อที่ผมอยากให้คุณระวังก่อนจะแบกหนี้จนวิ่งไม่ออกในปีนี้</p><p>1. รถป้ายแดง: กับดักความโก้ที่แลกด้วยอิสรภาพ</p><p>ตอนผมทำงานได้แค่ 2 ปี ผมจัดรถมือหนึ่งป้ายแดงทันทีครับ เพราะคิดว่ามันคือสัญลักษณ์ของความสำเร็จ ผมยอมจ่ายเงินเดือนส่วนใหญ่ไปกับค่างวดรถ และหลอกตัวเองว่า “ผ่อนรถก็คือการออมรูปแบบหนึ่ง” สุดท้ายเป็นไง? รถเก่าลงทุกวัน ค่าเสื่อมราคาฮวบๆ แถมเงินออมจริงๆ เป็นศูนย์ รถคือสินทรัพย์ที่ลดค่า แต่หนี้คือของจริงที่อยู่ครบครับ</p><p>2. คอนโด: เมื่อสินทรัพย์กลายเป็นภาระผูกพันถึงอายุ 60</p><p>พอเงินเดือนเริ่มขยับ ผมก็หาเรื่อง “ลงทุน” ด้วยการซื้อคอนโดใกล้ที่ทำงาน ผ่อนไป 30% ของเงินเดือนยาวๆ ยันเกษียณ โดยหวังว่ามันจะเป็นสินทรัพย์ชั้นดี แต่ความจริงที่ผมลืมคิดคือ “อาชีพเราย้ายงานบ่อย” พอผมเปลี่ยนที่ทำงาน คอนโดที่เคยว่าใกล้ก็กลายเป็นไกลโพ้น จะขายหรือปล่อยเช่าก็ยาก เพราะมีตึกใหม่ๆ ใหญ่กว่า ดีกว่า เปิดแข่งตลอดเวลา สุดท้ายผมต้องแบกค่าจิปาถะและหนี้ก้อนโตไปอีกหลายสิบปี</p><p>3. การลงทุน: อย่าหาทางลัดในโลก Crypto/Forex</p><p>ผมเคยเป็นคนหนึ่งที่อยากรวยทางลัด กระโดดเข้าใส่คริปโตและฟอเร็กซ์แบบไม่คิดชีวิต ผลคือ “ได้ไวแต่เสียไวกว่า” หัวใจจะวายทุกครั้งที่กราฟดิ่ง บทเรียนหลายปีสอนผมว่า สำหรับคนทำงานประจำอย่างเรา การ DCA ในหุ้นที่มั่นคง หรือกองทุนที่มีพื้นฐานดี คือการลงทุนที่ทำให้เรานอนหลับได้สนิท หรือการใส่เงินเข้า provident fund ก็เป็นอีกตัวเลือกที่มั่นคงให้ผลตอบแทนยั่งยืนในระยะยาวครับ</p><p>4. ประกัน: อย่าให้คำว่า “เงินออม” มาหลอกคุณ</p><p>ระวังกับดักประกันออมทรัพย์ควบประกันชีวิตให้ดีครับสหาย ผลตอบแทนมันน้อยจนน่าใจหาย แถมสภาพคล่องก็ต่ำ สิ่งที่คนขายไม่ได้บอกคุณคือ งวดแรกที่สหายจ่ายไป ตัวแทนกวาดค่าคอมมิชชั่นไปเกือบ 50%</p><p>คำแนะนำจากสหายโทนี่: ซื้อแค่ประกันชีวิตแบบชั่วระยะเวลา (Term Life Insurance) ในช่วงที่จำเป็นเพื่อกันความเสี่ยงพอครับ ส่วนเงินที่จะออม เอาไปลงทุนอย่างอื่นที่คุณคุมเองได้และผลตอบแทนสูงกว่า อย่าเอาเงินไปจมให้คนอื่นรวย</p><p>5. สุขภาพ: Hardware ชิ้นเดียวที่ไม่มี Git Revert</p><p>ในวันที่งานล้นมือ เรามักจะคิดว่าร่างกายเราคือเครื่องจักรที่ไม่มีวันพัง แต่เชื่อผมเถอะครับ 10 ปีผ่านไป หลังที่ปวด ตาที่เบลอ มันไม่มีอะไหล่เปลี่ยนนะครับ ต่อให้สหายเขียน Code เก่งแค่ไหน แต่ถ้าต้องเอาเงินทั้งหมดไปจ่ายค่าหมอ มันคือความพ่ายแพ้ในเกมชีวิตครับ เริ่มดูแลตัวเองตั้งแต่วันนี้ แบ่งเวลามาออกกำลังกาย เข้ายิม เดิน-วิ่ง วันละ 20–30 นาที หรือการจัดโต๊ะทำงานให้ถูกหลัก Ergonomics ก็ช่วยให้ห่างไกลจาก Office syndrome ได้ครับ</p><p>6. ครอบครัว: Commit ที่สำคัญที่สุดในชีวิต</p><p>สุดท้ายอย่าละเลยคนข้างหลังครับ เวลาเป็น Data Type ที่ “Write Only” เขียนลงไปแล้วลบไม่ได้ และไม่มีทาง Rollback กลับไปแก้ไขได้ ใช้เวลากับครอบครัวให้คุ้มค่าตั้งแต่วันนี้ อย่ารอจนวันที่ม้าไฟของคุณวิ่งจนหมดแรงแล้วถึงค่อยหันกลับไปมอง</p><p>บทสรุปจากสหายโทนี่: ปี 2026 นี้ ม้าไฟอาจจะร้อนแรง แต่อย่าควบจนลืมเช็กอานม้าและทางข้างหน้านะครับ หวังว่าบทเรียนราคาแพงของผม จะช่วยให้สหายทุกคนวิ่งไปสู่เป้าหมายได้อย่างมั่นคงและมีความสุข</p><p>ขอให้เป็นการวิ่งที่ยั่งยืนครับสหาย!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CQG4_cldQ9v0nCX75x1l8w.png" /><figcaption>HNY 2026 — enjoy coding</figcaption></figure><p><em>*** การลงทุนมีความเสี่ยงโปรดใช้วิจารณญาณในการรับชม ไม่ต้องเชื่อผมเพราะผมไม่ได้เรียนมา เป็นการแชร์ประสบการณ์ตรง ขอให้โชคดีและสนุกกับชีวิตครับ สวัสดีปีใหม่ ๒๕๖๙</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5b22e2e7e943" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/hny-%E0%B8%9B%E0%B8%B5%E0%B8%A1%E0%B9%89%E0%B8%B2%E0%B9%84%E0%B8%9F-2026-6-%E0%B8%81%E0%B8%B1%E0%B8%9A%E0%B8%94%E0%B8%B1%E0%B8%81%E0%B8%8A%E0%B8%B5%E0%B8%A7%E0%B8%B4%E0%B8%95-%E0%B8%97%E0%B8%B5%E0%B9%88%E0%B8%AD%E0%B8%A2%E0%B8%B2%E0%B8%81%E0%B9%83%E0%B8%AB%E0%B9%89%E0%B8%84%E0%B8%B8%E0%B8%93%E0%B9%80%E0%B8%A5%E0%B8%B5%E0%B9%88%E0%B8%A2%E0%B8%87-5b22e2e7e943">HNY ปีม้าไฟ 2026 ~ 6 กับดักชีวิต ที่อยากให้คุณเลี่ยง</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Debug Reactive Streams ใน Spring WebFlux ด้วย Reactor Debug Agent]]></title>
            <link>https://medium.com/ascend-developers/debug-reactive-streams-%E0%B9%83%E0%B8%99-spring-webflux-%E0%B8%94%E0%B9%89%E0%B8%A7%E0%B8%A2-reactor-debug-agent-6de6373954fa?source=rss----98c9c9d1ba0c---4</link>
            <guid isPermaLink="false">https://medium.com/p/6de6373954fa</guid>
            <category><![CDATA[reactive-programming]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[backend-development]]></category>
            <category><![CDATA[javascript-development]]></category>
            <category><![CDATA[backend]]></category>
            <dc:creator><![CDATA[Ekarin Prasoppol]]></dc:creator>
            <pubDate>Fri, 26 Dec 2025 08:27:57 GMT</pubDate>
            <atom:updated>2025-12-26T08:27:56.133Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OwxKh_yqj26oITdt1BN6vA.png" /></figure><p>Reactive Programming ด้วย <strong>Spring WebFlux</strong> และ <strong>Project Reactor</strong> ช่วยให้เราสร้างระบบที่รองรับ concurrent workload ได้ดี ใช้ resource คุ้ม และ scale ได้ง่าย แต่ที่หลายคนบ่นเหมือนกันคือ</p><blockquote>“ตอน Error มัน Debug ยากมาก”</blockquote><p>Stacktrace ของ reactive pipeline มักไม่บอกเราตรง ๆ ว่า</p><ul><li>error เกิดจาก business logic จุดไหน</li><li>operator ไหนเป็นต้นเหตุ</li><li>หรือ reactive chain วิ่งมาถึงตรงนี้ได้อย่างไร</li></ul><p>ผลลัพธ์คือ developer ต้องไล่เดา flow จาก .map() .flatMap() ที่ต่อกันยาว ๆ ซึ่งเสียเวลาและทำให้การแก้ปัญหาช้าลงอย่างมาก</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tRcZKX22A8WHFx6pc78aZg.png" /></figure><p>บทความนี้จะพาไปทำความรู้จัก <strong>Reactor Debug Agent</strong> พร้อมเทคนิคสำคัญอย่าง .checkpoint() และ Hooks.onOperatorDebug()ผ่านตัวอย่างโค้ด และการอธิบายทีละบรรทัด เพื่อให้คุณสามารถ</p><ul><li>อ่าน stacktrace ของ reactive ได้เหมือน synchronous code</li><li>pinpoint ตำแหน่ง error ได้เร็วขึ้น</li><li>และ debug WebFlux application ได้อย่างมั่นใจมากขึ้น</li></ul><blockquote><em>เป้าหมายไม่ใช่แค่ “รู้ว่า error คืออะไร”<br> แต่คือ “รู้ว่ามันเกิดขึ้นได้อย่างไร และควรแก้ตรงไหน”</em></blockquote><h3>Step 1: เตรียมโปรเจกต์</h3><h3>เพิ่ม dependency ใน pom.xml</h3><pre>&lt;dependency&gt;<br>  &lt;groupId&gt;io.projectreactor.tools&lt;/groupId&gt;<br>  &lt;artifactId&gt;reactor-tools&lt;/artifactId&gt;<br>  &lt;version&gt;3.5.6&lt;/version&gt;<br>&lt;/dependency&gt;</pre><h3>Step 2: เปิด Debug Mode ตั้งแต่แอปเริ่มรัน</h3><pre>@SpringBootApplication<br>public class DemoApplication {<br>public static void main(String[] args) {<br>      Hooks.onOperatorDebug();<br>      ReactorDebugAgent.init();<br>      ReactorDebugAgent.processExistingClasses();</pre><pre>      SpringApplication.run(DemoApplication.class, args);<br>  }<br>}</pre><ul><li>เรียกใช้ Hooks.onOperatorDebug() เพื่อเปิดโหมด debug ของ reactive operator ทุกตัวในแอปฯ สำหรับเก็บข้อมูล stacktrace</li><li>เริ่มต้น Reactor Debug Agent ด้วย ReactorDebugAgent.init() เพื่อช่วยเพิ่มข้อมูล stacktrace ที่ชัดเจนและเชื่อมโยงข้อผิดพลาดกลับไปยังโค้ดต้นเหตุ</li><li>สั่งให้ agent สแกน class ที่โหลดแล้วด้วย ReactorDebugAgent.processExistingClasses() เพื่อ instrument operator ที่ถูกโหลดก่อนหน้านี้ (ถ้าไม่เรียก อาจมี operator บางตัวไม่ถูก instrument)</li></ul><h3>Step 3: ตัวอย่าง Reactive Flow ที่จะเกิด Exception</h3><pre>@GetMapping(&quot;/calculate&quot;)<br>public Mono&lt;String&gt; calculate() {<br>    return Mono.just(10)<br>               .map(i -&gt; i - 10)<br>               .map(i -&gt; 10 / i)<br>               .checkpoint(&quot;Divide calculation&quot;)<br>               .map(String::valueOf);<br>}</pre><h4>อธิบายทีล่ะบรรทัด</h4><pre>Mono.just(10)</pre><p>สร้าง Mono ที่ปล่อยค่าเดียวคือ 10</p><pre>.map(i -&gt; i - 10)</pre><p>แปลงค่า 10 → 0<br> ตอนนี้ยังไม่มี error</p><pre>.map(i -&gt; 10 / i)</pre><p>เกิดการคำนวณ 10 / 0<br> 💥 เกิด ArithmeticException ที่บรรทัดนี้</p><pre>.checkpoint(&quot;Divide calculation&quot;)</pre><p>ใส่ป้ายชื่อให้ pipeline<br> ถ้าเกิด error Stacktrace จะบอกว่า<br> 👉 “พังที่ checkpoint: Divide calculation”</p><pre>.map(String::valueOf)</pre><p>บรรทัดนี้ <strong>จะไม่ถูกเรียก</strong> เพราะ error เกิดก่อนหน้าแล้ว</p><h3>Step 4: ดู Stacktrace แบบ “อ่านรู้เรื่อง”</h3><h3>❌ ถ้าไม่ใช้ Debug Agent</h3><pre>java.lang.ArithmeticException: / by zero<br>    at reactor.core.publisher.FluxMap$MapSubscriber.onNext<br>    ...</pre><ul><li>ไม่รู้ว่า error มาจาก map ไหน</li><li>ไม่รู้ logic ธุรกิจ</li><li>แก้ยากมาก</li></ul><h3>✅ เมื่อใช้ Debug Agent + checkpoint</h3><pre>java.lang.ArithmeticException: / by zero<br>	at com.example.DemoController.lambda$calculate$1(DemoController.java:18)</pre><pre>Reactor operator stacktrace:<br>--- Operator checkpoint: Divide calculation</pre><p>🎯 <strong>รู้ทันที</strong></p><ul><li>พังที่ method ไหน</li><li>พังใน business logic จุดไหน</li><li>ไม่ต้องไล่เดา reactive chain</li></ul><h3>Step 6: ตัวอย่าง เพิ่มอีกหนึ่งเคส (DB / Service)</h3><pre>public Mono&lt;User&gt; findUser(String id) {<br>    return userRepository.findById(id)<br>        .checkpoint(&quot;DB: findUserById&quot;)<br>        .switchIfEmpty(Mono.error(new RuntimeException(&quot;User not found&quot;)));<br>}</pre><h3>ถ้าเกิด error จะเห็นว่า</h3><pre>--- Operator checkpoint: DB: findUserById</pre><p>📍 แปลว่า error นี้มาจาก database layer<br> ไม่ใช่ controller<br> ไม่ใช่ downstream service</p><h3>ปิดท้าย</h3><p>Reactive Programming ไม่ได้ทำให้การ debug เป็นไปไม่ได้ แต่ต้องใช้วิธีคิดและเครื่องมือที่เหมาะสม</p><p>ปัญหาที่หลายทีมเจอไม่ใช่ Reactive เอง แต่คือ <strong>stacktrace ที่ขาด context</strong> ทำให้ไม่รู้ว่า error เกิดจาก business logic ตรงไหนของ pipeline</p><p><strong>Reactor Debug Agent</strong> ช่วยเติมช่องว่างนี้ ด้วยการเชื่อม reactive operator กลับไปยังโค้ดจริงที่เขียนไว้<br> เมื่อใช้งานร่วมกับ .checkpoint() และ Hooks.onOperatorDebug() เราจะสามารถ</p><ul><li>pinpoint ตำแหน่งที่ error เกิดขึ้นได้เร็วขึ้น</li><li>อ่าน stacktrace ของ reactive code ได้เหมือน synchronous code มากขึ้น</li><li>ลดเวลาในการไล่ debug และการเดา flow ของระบบ</li></ul><p>อย่างไรก็ตาม เครื่องมือเหล่านี้มี overhead และควรใช้งานเฉพาะใน <strong>development หรือ troubleshooting environment</strong><br>การเลือกใช้ให้เหมาะสมในแต่ละสถานการณ์ จะช่วยให้ทีมได้ประโยชน์จาก Reactive Programming โดยไม่เพิ่มความซับซ้อนเกินจำเป็น</p><blockquote><em>Reactive ไม่ได้ยากขึ้น<br> แค่ต้อง debug ให้ถูกวิธี</em></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6de6373954fa" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ascend-developers/debug-reactive-streams-%E0%B9%83%E0%B8%99-spring-webflux-%E0%B8%94%E0%B9%89%E0%B8%A7%E0%B8%A2-reactor-debug-agent-6de6373954fa">Debug Reactive Streams ใน Spring WebFlux ด้วย Reactor Debug Agent</a> was originally published in <a href="https://medium.com/ascend-developers">Ascend Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>