<?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[Stories by Somprasong Damyos on Medium]]></title>
        <description><![CDATA[Stories by Somprasong Damyos on Medium]]></description>
        <link>https://medium.com/@somprasongd?source=rss-eb4e76569e6e------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*ZsjeFBV2WPBfu1SuKQbJiQ.jpeg</url>
            <title>Stories by Somprasong Damyos on Medium</title>
            <link>https://medium.com/@somprasongd?source=rss-eb4e76569e6e------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 01:01:16 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@somprasongd/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[การสร้าง Docker Image สำหรับ Go ให้เหมาะกับ Production]]></title>
            <link>https://medium.com/@somprasongd/%E0%B8%81%E0%B8%B2%E0%B8%A3%E0%B8%AA%E0%B8%A3%E0%B9%89%E0%B8%B2%E0%B8%87-docker-image-%E0%B8%AA%E0%B8%B3%E0%B8%AB%E0%B8%A3%E0%B8%B1%E0%B8%9A-go-%E0%B9%83%E0%B8%AB%E0%B9%89%E0%B9%80%E0%B8%AB%E0%B8%A1%E0%B8%B2%E0%B8%B0%E0%B8%81%E0%B8%B1%E0%B8%9A-production-e07455cb7fee?source=rss-eb4e76569e6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/e07455cb7fee</guid>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[go]]></category>
            <dc:creator><![CDATA[Somprasong Damyos]]></dc:creator>
            <pubDate>Mon, 30 Jun 2025 04:17:45 GMT</pubDate>
            <atom:updated>2025-06-30T04:17:45.571Z</atom:updated>
            <content:encoded><![CDATA[<blockquote><em>แนวทางสร้าง Go container image ที่ เล็ก ปลอดภัย deploy ง่าย และ maintain ได้ระยะยาว</em></blockquote><p>Originally published at <a href="https://somprasongd.work/blog/docker/docker-image-for-go">https://somprasongd.work/blog/docker/docker-image-for-go</a></p><p>เมื่อพัฒนาแอปพลิเคชันด้วยภาษา Go หนึ่งในจุดเด่นที่สำคัญคือการสร้างไฟล์ไบนารีแบบ static ทำให้เหมาะอย่างยิ่งสำหรับนำไปบรรจุใน Docker container ที่มีขนาดเล็กและปลอดภัย ซึ่ง Alpine Linux เป็นฐานที่คนนิยมใช้เพราะขนาดเล็กและมีแพ็กเกจพื้นฐานเพียงพอสำหรับงานส่วนใหญ่</p><p>แต่การจะทำให้ container ขนาดเล็กและพร้อมใช้จริงใน production ต้องเข้าใจประเด็นสำคัญทั้งในแง่ของ build, dependency, และการจัดการ image ให้มีความเสถียรและปลอดภัย</p><h3>ปิด cgo (CGO_ENABLED=0) เพื่อสร้าง static binary</h3><p>โดยปกติ Go จะเปิดใช้ cgo เป็นค่าเริ่มต้นหากโค้ดมีการเรียกไลบรารี C หรือใช้ฟังก์ชันมาตรฐานบางส่วนที่อ้างอิง libc ซึ่งอาจทำให้ไฟล์ไบนารีต้องพึ่งพา shared library ภายนอก เช่น glibc หรือ musl</p><p>เมื่อใช้ Alpine ซึ่งใช้ musl libc แทน glibc ปัญหาที่พบบ่อยคือไฟล์ไบนารีที่พึ่ง glibc จะรันไม่สำเร็จ หากต้องการความแน่นอนว่ารันได้ทุกที่บน Linux base image ควรตั้ง CGO_ENABLED=0 เพื่อให้ Go สร้างไฟล์ไบนารีที่ลิงก์แบบ static ทั้งหมด ลดปัญหา dependency ภายนอก</p><p>ตัวอย่างที่นิยมคือ</p><pre>ENV CGO_ENABLED=0</pre><h3>ลดขนาดไฟล์ด้วย ldflags “-s -w”</h3><p>ตัวเลือก -ldflags=&quot;-s -w&quot; เป็นอีกเทคนิคที่ใช้กันทั่วไปเพื่อลดขนาดไฟล์ที่ได้จาก go build</p><ul><li>s จะตัด symbol table ซึ่งไม่จำเป็นต่อการรันจริง</li><li>w จะตัดข้อมูลสำหรับการ debug (DWARF)</li></ul><p>ผลคือขนาดไฟล์จะลดลงได้หลาย MB แต่ข้อควรระวังคือ หากต้องใช้เครื่องมือ debug เช่น Delve ข้อมูลเหล่านี้จะหายไป ทำให้ debug ได้ลำบากขึ้น เทคนิคนี้จึงเหมาะสำหรับ build ไบนารีที่ใช้จริงใน production เท่านั้น</p><h3>ทำไมไม่ควรใช้ alpine:latest</h3><p>หลายคนมักเขียน Dockerfile ว่า FROM alpine:latest เพราะง่าย แต่ในทางปฏิบัติ นี่เป็นสิ่งที่ควรหลีกเลี่ยง เนื่องจาก tag latest ไม่ได้ผูกกับ version ใด ๆ แบบตายตัว ภายใน repository อาจอัปเดตเมื่อใดก็ได้โดยไม่ประกาศล่วงหน้า ซึ่งทำให้ build ครั้งถัดไปอาจได้ base image ที่ไม่เหมือนเดิมและอาจเกิดปัญหาใหม่โดยไม่รู้ตัว</p><p>แนวทางที่ควรทำคือระบุเวอร์ชันให้ชัดเจน เช่น FROM alpine:3.22 เพื่อให้แน่ใจว่าผลลัพธ์ reproducible และ rollback ได้ง่ายหากเกิดปัญหา</p><h3>ใช้ Multi-Stage Build เพื่อลดขนาดและจัดการได้ง่าย</h3><p>Dockerfile สำหรับ Go ที่ดีควรแยกขั้นตอน build ออกจากขั้นตอน runtime โดยใช้ multi-stage build ขั้นแรกใช้ image golang:&lt;version&gt;-alpine สำหรับ compile โค้ด ขั้นถัดไปใช้ alpine:&lt;version&gt; หรือแม้แต่ scratch เพื่อลดขนาด image</p><p>ตัวอย่างโครงสร้าง</p><pre># Build Stage<br>FROM golang:1.24-alpine AS builder<br>WORKDIR /app<br>COPY go.mod go.sum ./<br>RUN go mod download<br>COPY . .<br>ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64<br>ARG VERSION=latest<br>RUN go build -ldflags=&quot;-s -w -X &#39;main.Version=${VERSION}&#39;&quot; -o /app/app ./cmd/api/main.go<br># Final Stage<br>FROM alpine:3.22<br>RUN apk add --no-cache ca-certificates tzdata \<br>    &amp;&amp; addgroup -S appgroup \<br>    &amp;&amp; adduser -S appuser -G appgroup<br>WORKDIR /app<br>COPY --from=builder /app/app .<br>USER appuser<br>EXPOSE 8080<br>ENTRYPOINT [&quot;./app&quot;]</pre><h3>ใช้ Non-Root User</h3><p>อีกหนึ่งจุดที่หลายคนมองข้ามคือ user ที่ container ใช้รันโปรเซส เริ่มต้น container จะรันด้วย root ซึ่งถ้าเกิดช่องโหว่ ผู้โจมตีอาจใช้สิทธิ root ภายใน container เพื่อโจมตีต่อได้ง่าย</p><p>วิธีแก้คือสร้าง user สิทธิจำกัด แล้วสั่งให้ container รันด้วย user นี้แทน การเพิ่มบรรทัด adduser และ USER ใน Dockerfile เป็นวิธีปฏิบัติมาตรฐานที่ช่วยปิดความเสี่ยงนี้ได้ดี</p><h3>ENTRYPOINT vs CMD</h3><p>หลายคนสับสนระหว่าง ENTRYPOINT และ CMD ว่าควรใช้แบบไหน ต่างกันอย่างไร</p><ul><li>ENTRYPOINT ใช้กำหนด <strong>คำสั่งหลัก</strong> ที่ container ต้องรันเสมอ ไม่ว่าผู้ใช้จะสั่ง docker run พร้อม argument อะไร คำสั่งนี้จะถูกเรียกเสมอ โดย argument ที่ตามมาจะถูกต่อท้าย</li><li>CMD ใช้กำหนด <strong>default arguments</strong> ถ้า docker run ไม่ได้ระบุ argument ใหม่ ระบบจะใช้ค่าใน CMD แทน แต่ถ้าผู้ใช้ระบุ argument ใหม่ทั้งหมด CMD จะถูกแทนที่ทันที</li></ul><p>ตัวอย่างเช่น</p><pre>ENTRYPOINT [&quot;./app&quot;]<br>CMD [&quot;--port=8080&quot;]</pre><p>กรณีนี้ หากรัน docker run myapp จะได้ ./app --port=8080 ถ้ารัน docker run myapp --help จะได้ ./app --help</p><p>การใช้ ENTRYPOINT แบบ exec form ([&quot;./app&quot;]) ยังช่วยให้โปรเซสของเราทำงานเป็น PID 1 โดยตรง ทำให้จัดการ signal ได้ถูกต้อง โดยเฉพาะ SIGTERM ซึ่งสำคัญต่อการทำ graceful shutdown ใน production</p><h3>สรุปแนวทาง Production Docker Image สำหรับ Go</h3><ul><li>ปิด cgo (CGO_ENABLED=0) เพื่อสร้างไฟล์ static</li><li>ใช้ ldflags=&quot;-s -w&quot; เพื่อลดขนาดไฟล์</li><li>ระบุ base image version ชัดเจน เช่น alpine:3.18 อย่าใช้ latest</li><li>แยกขั้น build ออกจากขั้น runtime ด้วย multi-stage build</li><li>สร้าง non-root user เพื่อลดความเสี่ยง</li><li>ใช้ ENTRYPOINT เพื่อกำหนด command หลัก และ CMD เพื่อกำหนด default arguments</li></ul><p>แนวทางทั้งหมดนี้จะช่วยให้ได้ Docker Image ที่ขนาดเล็ก เสถียร ปลอดภัย และจัดการได้ง่ายจริงในงาน production</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e07455cb7fee" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ทำไมต้องใช้ Database Transaction?]]></title>
            <link>https://medium.com/@somprasongd/%E0%B8%97%E0%B8%B3%E0%B9%84%E0%B8%A1%E0%B8%95%E0%B9%89%E0%B8%AD%E0%B8%87%E0%B9%83%E0%B8%8A%E0%B9%89-database-transaction-9f4ecc895a1f?source=rss-eb4e76569e6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/9f4ecc895a1f</guid>
            <category><![CDATA[database]]></category>
            <dc:creator><![CDATA[Somprasong Damyos]]></dc:creator>
            <pubDate>Mon, 30 Jun 2025 04:15:44 GMT</pubDate>
            <atom:updated>2025-06-30T04:15:44.616Z</atom:updated>
            <content:encoded><![CDATA[<p>Originally published at <a href="https://somprasongd.work/blog/db/dbtx">https://somprasongd.work/blog/db/dbtx</a></p><p>ในการพัฒนาระบบที่เกี่ยวข้องกับข้อมูลสำคัญ เช่น การเงิน หนึ่งในสิ่งที่ขาดไม่ได้คือการใช้ <strong>Database Transaction</strong> เพื่อให้มั่นใจว่าข้อมูลจะถูกจัดการอย่างถูกต้องและปลอดภัย โดยเฉพาะในสถานการณ์ที่มีหลายคำสั่ง SQL ทำงานร่วมกัน เช่น “การโอนเงิน” ที่ต้องอัปเดตยอดเงินสองบัญชีพร้อมกัน</p><p>ในบทความนี้ เราจะพาคุณเข้าใจว่าเหตุใดการใช้ <strong>Transaction</strong> จึงสำคัญ ผ่านการเรียนรู้แนวคิด <strong>ACID</strong>, <strong>Lock</strong>, และ <strong>Deadlock</strong> ด้วยตัวอย่างจากการโอนเงินระหว่างบัญชีด้วย PostgreSQL</p><h3>โครงสร้างฐานข้อมูล: accounts และ transactions</h3><pre>CREATE TABLE public.accounts (<br>    id serial4 NOT NULL,<br>    &quot;name&quot; text NOT NULL,<br>    balance numeric NOT NULL,<br>    CONSTRAINT accounts_pkey PRIMARY KEY (id)<br>);<br><br>CREATE TABLE public.transactions (<br>    id serial4 NOT NULL,<br>    from_account_id int4 NULL,<br>    to_account_id int4 NULL,<br>    amount numeric NOT NULL,<br>    created_at timestamp DEFAULT now() NULL,<br>    CONSTRAINT transactions_pkey PRIMARY KEY (id),<br>    CONSTRAINT transactions_from_account_id_fkey FOREIGN KEY (from_account_id) REFERENCES public.accounts(id),<br>    CONSTRAINT transactions_to_account_id_fkey FOREIGN KEY (to_account_id) REFERENCES public.accounts(id)<br>);</pre><h3>ปัญหา: การโอนเงินโดยไม่ใช้ Transaction</h3><p>สมมุติว่าเราต้องโอนเงิน 500 บาทจากบัญชี A ไปยังบัญชี B โดยใช้คำสั่ง SQL ต่อไปน</p><pre>UPDATE accounts SET balance = balance - 500 WHERE id = 1;<br>UPDATE accounts SET balance = balance + 500 WHERE id = 2;<br>INSERT INTO transactions (from_account_id, to_account_id, amount)<br>VALUES (1, 2, 500);</pre><p>ถ้าเกิดเหตุขัดข้องหลังอัปเดตบัญชี A แล้ว (เช่น network error หรือ crash) แต่ยังไม่ทันเพิ่มเงินให้บัญชี B — เงิน 500 บาทจะ “หายไปจากระบบ” ซึ่งเป็นปัญหาหนักมากในระบบการเงิน</p><h3>วิธีแก้: ใช้ Database Transaction</h3><pre>BEGIN;<br><br>UPDATE accounts SET balance = balance - 500 WHERE id = 1;<br>UPDATE accounts SET balance = balance + 500 WHERE id = 2;<br>INSERT INTO transactions (from_account_id, to_account_id, amount)<br>VALUES (1, 2, 500);<br><br>COMMIT;</pre><p>หากคำสั่งใดล้มเหลว PostgreSQL จะไม่ COMMIT และเราสามารถ ROLLBACK เพื่อยกเลิกทุกคำสั่งที่ทำไปแล้ว ทำให้ข้อมูลไม่เสียหาย</p><h3>เข้าใจ ACID: หัวใจของ Transaction</h3><p>ACID คือคุณสมบัติหลักของ Database Transaction ที่ทำให้ข้อมูลเชื่อถือได้ ซึ่งย่อมาจาก</p><ul><li><strong>Atomicity</strong> — ทุกคำสั่งใน transaction ต้องสำเร็จทั้งหมด หรือไม่สำเร็จเลย</li><li><strong>Consistency</strong> — หลัง transaction สำเร็จ ฐานข้อมูลยังคงถูกต้องตามกฎ เช่น balance ≥ 0</li><li><strong>Isolation</strong> — แต่ละ transaction ไม่รบกวนกันระหว่างทำงานพร้อมกัน</li><li><strong>Durability</strong> — เมื่อ COMMIT แล้ว ข้อมูลจะถูกบันทึกถาวร แม้ระบบดับ</li></ul><p><strong>ตัวอย่าง:</strong> หากมีคนโอนเงินจากบัญชีเดียวกันสองคนพร้อมกัน PostgreSQL ต้องมั่นใจว่าไม่มีการขัดแย้ง หรือข้อมูลผิดเพี้ยน เช่นเงินติดลบ</p><h3>การล็อกข้อมูล (Database Lock)</h3><p>PostgreSQL จะล็อก <strong>row-level</strong> อัตโนมัติเมื่อมี UPDATE:</p><pre>UPDATE accounts SET balance = balance - 500 WHERE id = 1;</pre><p>แถวของบัญชี id = 1 จะถูกล็อกไว้จนกว่า transaction จะจบ เพื่อป้องกันไม่ให้ transaction อื่นเข้ามาแก้ไขบัญชีเดียวกันพร้อมกัน</p><h3>สถานการณ์: select ก่อน update</h3><p>แต่ในกรณีที่ใน application logic มีการดึงข้อมูล (select) ขึ้นมาก่อน แล้วทำการแก้ไขค่า เสร็จแล้วบันทึกกลับไปในฐานข้อมูล (update) แล้วเกิด transaction เข้ามาแก้ไขบัญชีเดียวกันพร้อมกัน เช่น</p><pre>-- Transaction A<br>BEGIN;<br>SELECT id, &quot;name&quot;, balance FROM accounts WHERE id = 1;<br>id|name|balance|<br>--+----+-------+<br> 1|A   |   1000|<br><br>UPDATE accounts SET balance = 1000 - 500 WHERE id = 1;<br>COMMIT;<br>-- Transaction B (เกิดขึ้นพร้อมกัน)<br>BEGIN;<br>SELECT id, &quot;name&quot;, balance FROM accounts WHERE id = 1;<br>id|name|balance|<br>--+----+-------+<br> 1|A   |   1000|<br>UPDATE accounts SET balance = 1000 - 500 WHERE id = 1;<br>COMMIT;</pre><p>ในสถานการณ์นี้จะทำให้ค่า balance ผิด แทนที่จะเหลือ 0 แต่กลับเหลือ 500 แทน</p><p><strong>วิธีแก้:</strong> ใช้ SELECT FOR UPDATE เพื่อล็อกก่อน</p><pre>-- Transaction A<br>BEGIN;<br>SELECT id, &quot;name&quot;, balance FROM accounts WHERE id = 1 FOR UPDATE;<br>id|name|balance|<br>--+----+-------+<br> 1|A   |   1000|<br><br>UPDATE accounts SET balance = 1000 - 500 WHERE id = 1;<br>COMMIT;<br>-- Transaction B (เกิดขึ้นพร้อมกัน)<br>BEGIN;<br>SELECT id, &quot;name&quot;, balance FROM accounts WHERE id = 1 FOR UPDATE;<br>-- จะหยุดรอจนกว่า Transaction A จะ COMMIT หรือ ROLLBACK<br>id|name|balance|<br>--+----+-------+<br> 1|A   |   500|<br>UPDATE accounts SET balance = 500 - 500 WHERE id = 1;<br>COMMIT;</pre><h3>Deadlock: ล็อกขัดกันเอง</h3><h3>สถานการณ์ที่ 1:</h3><ul><li><strong>Transaction A</strong>: โอน A ➜ B</li><li><strong>Transaction B</strong>: โอน B ➜ A</li></ul><pre>-- Transaction A<br>BEGIN;<br>UPDATE accounts SET balance = balance - 500 WHERE id = 1;<br>UPDATE accounts SET balance = balance + 500 WHERE id = 2;<br>-- รอ เพราะ lock จาก Transaction B<br><br>-- Transaction B (เกิดขึ้นพร้อมกัน)<br>BEGIN;<br>UPDATE accounts SET balance = balance - 300 WHERE id = 2;<br>UPDATE accounts SET balance = balance + 300 WHERE id = 1;<br>-- ERROR: deadlock detected</pre><h3>สถานการณ์ที่ 2:</h3><p>ถ้า INSERT ไปยัง transactions ซึ่งมี Foreign Key อ้างถึง accounts.id และหากมีอีก transaction กำลัง UPDATE accounts อยู่ อาจเกิด Deadlock ได้</p><p>เช่น สมมุติให้เปลี่ยนลำดับให้บันทึก transactions ก่อน แล้วมาดึงข้อมูลข้อมูล accounts ก่อน update</p><pre>-- Transaction A<br>BEGIN;<br><br>INSERT INTO transactions (from_account_id, to_account_id, amount)<br>VALUES (1, 2, 500);<br>SELECT id, &quot;name&quot;, balance FROM accounts WHERE id = 1 FOR UPDATE;<br>-- หยุดรอ เพราะ INSERT INTO transactions จาก Transaction B<br><br><br>-- Transaction B (เกิดขึ้นพร้อมกัน)<br>BEGIN;<br>INSERT INTO transactions (from_account_id, to_account_id, amount)<br>VALUES (1, 2, 500);<br>SELECT id, &quot;name&quot;, balance FROM accounts WHERE id = 1 FOR UPDATE;<br>-- ERROR: deadlock detected</pre><p>จากสถานการณ์ที่ยกตัวอย่างมา ทั้งสอง transaction จะล็อกบัญชีฝั่งของตนเองก่อน แล้วรอกันข้าม — ทำให้เกิด <strong>Deadlock</strong> PostgreSQL จะเลือก kill transaction ใด transaction หนึ่งเพื่อยกเลิก</p><h3>วิธีป้องกัน Deadlock</h3><ul><li>ล็อก row ตามลำดับเสมอ<br>ใช้ ORDER BY id เพื่อให้ทุก transaction ล็อกบัญชีตามลำดับเดียวกัน เช่น id จากน้อยไปมาก</li><li>ล็อก row ก่อนอัปเดตด้วย FOR NO KEY UPDATE<br>หากไม่ล็อก row ล่วงหน้า PostgreSQL จะพยายามล็อกเองในตอนที่ INSERT ไปยัง transactions ซึ่งมี Foreign Key อ้างถึง accounts.id<br>หากอีก transaction กำลัง UPDATE accounts อยู่ อาจเกิด Deadlock ได้<br><strong>FOR NO KEY UPDATE</strong> คือระดับ lock ที่<br>- ป้องกันการ update, delete หรือ insert ที่อ้าง foreign key<br>- เบากว่า FOR UPDATE แต่ปลอดภัยสำหรับกรณีแบบนี้<br>- เหมาะสำหรับ row ที่เราจะอัปเดต (แต่ไม่เปลี่ยนค่า primary key)</li><li>ควร UPDATE accounts ก่อน แล้วค่อย INSERT transactions<br>ช่วยลดความเสี่ยงในการเกิด Deadlock เพราะว่าหากคุณ INSERT ก่อน และมี Foreign Key ไปที่ accounts → PostgreSQL จะพยายามล็อก row เหล่านั้นก่อน ถ้าคำสั่ง UPDATE accounts มาทีหลัง อาจเกิด <strong>Deadlock</strong> ได้ (โดยเฉพาะถ้าอีก transaction ล็อกกลับกัน)</li></ul><p><strong>ตัวอย่าง</strong></p><pre>-- Transaction A<br>BEGIN;<br><br>-- 1. ล็อกบัญชีล่วงหน้าเพื่อลดโอกาส deadlock<br>SELECT id FROM accounts<br>WHERE id IN (1, 2)<br>ORDER BY id<br>FOR NO KEY UPDATE;<br>-- 2. อัปเดตยอดเงิน<br>UPDATE accounts SET balance = balance - 500 WHERE id = 1;<br>UPDATE accounts SET balance = balance + 500 WHERE id = 2;<br>-- 3. บันทึกธุรกรรม<br>INSERT INTO transactions (from_account_id, to_account_id, amount)<br>VALUES (1, 2, 500);<br>COMMIT;<br>-- Transaction B (เกิดขึ้นพร้อมกัน)<br>BEGIN;<br>SELECT id FROM accounts<br>WHERE id IN (1, 2)<br>ORDER BY id<br>FOR NO KEY UPDATE;<br>-- รอจนกว่า Transaction A จะถูก COMMIT หรือ ROLLBACK<br>UPDATE accounts SET balance = balance - 300 WHERE id = 2;<br>UPDATE accounts SET balance = balance + 300 WHERE id = 1;<br>INSERT INTO transactions (from_account_id, to_account_id, amount)<br>VALUES (2, 1, 300);<br>COMMIT;</pre><h3>เปรียบเทียบระดับ Lock (ใน SELECT … FOR …)</h3><ul><li>FOR UPDATEอัปเดต row ห้ามใคร update/delete แถวนี้</li><li>FOR NO KEY UPDATEอัปเดต row (ไม่แตะ key)ห้าม insert ที่อ้างถึง row นี้ (FK)</li><li>FOR SHAREอ่านอย่างเดียวห้ามใคร delete หรือ update แถวนี้</li><li>FOR KEY SHAREอ่านพร้อมอ้าง FKห้ามใครเปลี่ยน primary key หรือ delete</li></ul><h3>สรุป</h3><p>การใช้ <strong>Database Transaction</strong> เป็นสิ่งสำคัญอย่างยิ่งในระบบที่ต้องการความถูกต้องของข้อมูล โดยเฉพาะระบบการเงิน โดย PostgreSQL มีคุณสมบัติรองรับ ACID อย่างครบถ้วน พร้อมระบบ Lock และกลไกจัดการ Deadlock อัตโนมัติ</p><p><strong>หากละเลยการใช้ Transaction อาจนำไปสู่ความเสียหายทางข้อมูล และความไม่ถูกต้องที่ยากจะแก้ไขภายหลัง</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9f4ecc895a1f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ลด Coupling และแยก Concern ด้วย Domain Event และ Integration Event]]></title>
            <link>https://medium.com/@somprasongd/%E0%B8%A5%E0%B8%94-coupling-%E0%B9%81%E0%B8%A5%E0%B8%B0%E0%B9%81%E0%B8%A2%E0%B8%81-concern-%E0%B8%94%E0%B9%89%E0%B8%A7%E0%B8%A2-domain-event-%E0%B9%81%E0%B8%A5%E0%B8%B0-integration-event-be26a5b916f1?source=rss-eb4e76569e6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/be26a5b916f1</guid>
            <dc:creator><![CDATA[Somprasong Damyos]]></dc:creator>
            <pubDate>Fri, 06 Jun 2025 11:26:06 GMT</pubDate>
            <atom:updated>2025-06-06T11:30:19.113Z</atom:updated>
            <content:encoded><![CDATA[<p>Originally published at <a href="https://somprasongd.work/blog/architecture/domain-event-vs-integration-event">https://somprasongd.work/blog/architecture/domain-event-vs-integration-event</a></p><p>ในระบบที่ออกแบบแบบ DDD (Domain-Driven Design) หรือ Modular Monolith หลายคนมักเริ่มต้นด้วยการเขียนโค้ดในรูปแบบ <strong>Imperative Style</strong> — ซึ่งตรงไปตรงมาแต่ coupling สูง และแยก concern ได้ไม่ชัดเจน</p><p>ยกตัวอย่างเช่น ใน CustomerService ที่รับผิดชอบสร้างลูกค้าใหม่ เราอาจเขียน logic ประมาณนี้</p><pre>if err := h.custRepo.Create(ctx, customer); err != nil {<br> return err<br>}<br><br>// ส่งอีเมลต้อนรับทันที<br>if err := h.notiSvc.SendEmail(customer.Email, &quot;Welcome!&quot;, ...); err != nil {<br> return err<br>}</pre><p>แม้โค้ดนี้จะดูเรียบง่าย แต่จริงๆ แล้วมันแฝงปัญหาหลายอย่างไว้ เช่น</p><ul><li><strong>Coupling สูง</strong>: Command Handler รู้จัก Service สำหรับส่งอีเมลโดยตรง</li><li><strong>ยากต่อการขยาย/ปรับปรุง</strong>: หากในอนาคตอยากส่ง SMS หรือบันทึก event log เพิ่มเติม ต้องแก้ handler ตรงนี้</li><li><strong>ยากต่อการทดสอบ</strong>: การ mocking พฤติกรรมที่เกิด “หลัง” การสร้าง customer ทำได้ยาก</li></ul><h3>ย้าย Logic ที่ไม่ใช่ Core Concern ออกด้วย Event</h3><p>ทางออกที่ดีกว่า คือการใช้แนวคิด <strong>Domain Events</strong> และ <strong>Integration Events</strong> เพื่อแยก concern ต่างๆ ออกจาก core business logic</p><h3>Domain Event คืออะไร?</h3><p>Domain Event คือสิ่งที่เกิดขึ้นใน domain ของเรา เช่น CustomerCreated, OrderCancelled เป็นต้น - โดย publish และ handle อยู่ภายใน module เดียวกัน</p><p>เช่น เมื่อสร้าง Customer สำเร็จ ก็สามารถเพิ่ม logic เช่นนี้</p><pre>customer.AddDomainEvent(CustomerCreated{<br> ID:    customer.ID,<br> Email: customer.Email,<br>})</pre><p>จากนั้น ระบบจะมี <strong>Domain Event Dispatcher</strong> ที่ทำหน้าที่ตามหา handler ที่เหมาะสมมาทำงานต่อ — โดยไม่กระทบ core logic</p><h3>Integration Event คืออะไร?</h3><p>เมื่อมีบาง domain event ที่เราต้อง “แจ้ง” ไปยัง <strong>ระบบอื่น</strong> หรือ <strong>module อื่น</strong> เช่นการส่งอีเมล, เขียน log audit, หรือแจ้งผ่าน Kafka — เราจะ <strong>แปลง Domain Event เป็น Integration Event</strong> แล้วค่อยส่งออกแบบ <strong>async</strong></p><p>ตัวอย่าง</p><pre>type CustomerCreatedDomainEventHandler struct {<br> eventBus EventBus<br>}<br><br>func (h *CustomerCreatedDomainEventHandler) Handle(ctx context.Context, evt CustomerCreated) error {<br> // แปลงเป็น Integration Event<br> return h.eventBus.Publish(ctx, CustomerCreatedIntegrationEvent{<br>  CustomerID: evt.ID,<br>  Email:      evt.Email,<br> })<br>}</pre><h3>สรุปข้อดีของการใช้ Event-Based Approach</h3><ul><li>แยก business logic ออกจาก infrastructure concern</li><li>ลด coupling ระหว่าง module และ service ต่างๆ</li><li>เปิดทางสู่ scalability และ async processing</li><li>ง่ายต่อการทดสอบ และรองรับ use-case ใหม่ๆ โดยไม่แก้ core logic</li></ul><h3>สุดท้าย</h3><p>การเปลี่ยนจากแนวทาง imperative ที่ทำทุกอย่างตรงๆ ไปสู่ event-driven ไม่ได้หมายความว่าคุณต้องเปลี่ยนทั้งหมดในคราวเดียว — แต่การเริ่ม “แยก concern” ให้ชัดเจนมากขึ้น จะช่วยให้ระบบของคุณ flexible, maintainable และรองรับการเติบโตได้ดียิ่งขึ้น</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=be26a5b916f1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ทำความรู้จัก Modular Monolith: สถาปัตยกรรมที่สมดุลระหว่าง Monolith และ Microservices]]></title>
            <link>https://medium.com/@somprasongd/%E0%B8%97%E0%B8%B3%E0%B8%84%E0%B8%A7%E0%B8%B2%E0%B8%A1%E0%B8%A3%E0%B8%B9%E0%B9%89%E0%B8%88%E0%B8%B1%E0%B8%81-modular-monolith-%E0%B8%AA%E0%B8%96%E0%B8%B2%E0%B8%9B%E0%B8%B1%E0%B8%95%E0%B8%A2%E0%B8%81%E0%B8%A3%E0%B8%A3%E0%B8%A1%E0%B8%97%E0%B8%B5%E0%B9%88%E0%B8%AA%E0%B8%A1%E0%B8%94%E0%B8%B8%E0%B8%A5%E0%B8%A3%E0%B8%B0%E0%B8%AB%E0%B8%A7%E0%B9%88%E0%B8%B2%E0%B8%87-monolith-%E0%B9%81%E0%B8%A5%E0%B8%B0-microservices-4a5b6e99eaf8?source=rss-eb4e76569e6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/4a5b6e99eaf8</guid>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[modular-monolith]]></category>
            <dc:creator><![CDATA[Somprasong Damyos]]></dc:creator>
            <pubDate>Fri, 28 Mar 2025 14:27:55 GMT</pubDate>
            <atom:updated>2025-03-28T14:27:55.130Z</atom:updated>
            <content:encoded><![CDATA[<p>Originally published at <a href="https://somprasongd.work/blog/architecture/modular-monolith">https://somprasongd.work/blog/architecture/modular-monolith</a></p><p>ในการออกแบบซอฟต์แวร์ สถาปัตยกรรมเป็นหนึ่งในปัจจัยสำคัญที่มีผลต่อความสามารถในการขยายตัว (scalability) การดูแลรักษา (maintainability) และประสิทธิภาพ (performance) ของระบบ แนวทางหลัก ๆ ในการออกแบบระบบ ได้แก่ <strong>Monolith</strong>, <strong>Modular Monolith</strong>, <strong>Microservices</strong>, และ <strong>Distributed Monolith</strong> ในบทความนี้ เราจะมุ่งเน้นไปที่ <strong>Modular Monolith</strong> ซึ่งเป็นทางเลือกที่สมดุลระหว่างความง่ายของ Monolith และความยืดหยุ่นของ Microservices</p><h3>ทำความเข้าใจภาพรวมของสถาปัตยกรรมต่าง ๆ</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*Kv9dEAgAGSQUdyIxCTw9Cw.png" /><figcaption><a href="https://shopify.engineering/deconstructing-monolith-designing-software-maximizes-developer-productivity">https://shopify.engineering/deconstructing-monolith-designing-software-maximizes-developer-productivity</a></figcaption></figure><p>ก่อนที่เราจะเจาะลึกถึง Modular Monolith มาดูภาพรวมของแต่ละสถาปัตยกรรมที่แสดงในรูป:</p><ul><li><strong>Monolith</strong>: ระบบทั้งหมดรวมอยู่ในโค้ดเบสเดียวและ deploy เป็นหน่วยเดียวกัน (single deployment unit) ทำให้พัฒนาง่ายแต่ขยายระบบลำบาก</li><li><strong>Microservices</strong>: แต่ละโมดูลแยกออกเป็นบริการอิสระ (independent services) สามารถพัฒนา ทดสอบ และ deploy แยกจากกันได้</li><li><strong>Distributed Monolith</strong>: ระบบถูก deploy ออกเป็นหลายหน่วยแต่ยังคงมีการพึ่งพากันอย่างแน่นหนา ทำให้ขยายตัวยาก</li><li><strong>Modular Monolith</strong>: โค้ดถูกแยกเป็นโมดูลภายในระบบเดียวกัน มีขอบเขตที่ชัดเจนแต่ยังคง deploy เป็นหน่วยเดียว</li></ul><h3>Modular Monolith คืออะไร?</h3><p><strong>Modular Monolith</strong> เป็นสถาปัตยกรรมที่พยายามนำข้อดีของ Monolith และ Microservices มารวมกัน โดยมีลักษณะดังนี้:</p><ol><li><strong>การแยกโมดูลภายในระบบเดียวกัน</strong>: ระบบถูกแบ่งเป็นโมดูลแยกจากกัน (เช่น Payments, Bookings, Reviews) โดยแต่ละโมดูลมีขอบเขตที่ชัดเจนและสื่อสารกันผ่าน API ภายใน</li><li><strong>Deployment เป็นหน่วยเดียว</strong>: แม้ว่าโมดูลจะแยกกันชัดเจน แต่การ deploy ยังคงอยู่ในรูปแบบของแอปพลิเคชันเดียว</li><li><strong>การรักษาความสมบูรณ์ของข้อมูล (Data Integrity)</strong>: ต่างจาก Microservices ที่แต่ละบริการมีฐานข้อมูลของตัวเอง Modular Monolith มักใช้ฐานข้อมูลเดียวกัน แต่แบ่ง schema หรือใช้แนวทาง Database per Module</li><li><strong>การสื่อสารภายในที่มีประสิทธิภาพ</strong>: โมดูลสามารถเรียกใช้กันโดยตรงผ่าน method calls หรือภายใน memory แทนที่จะต้องใช้ HTTP/gRPC เหมือนใน Microservices ทำให้ลด latency ลง</li><li><strong>ง่ายต่อการเปลี่ยนไปใช้ Microservices</strong>: Modular Monolith ช่วยให้ทีมพัฒนาออกแบบระบบเป็นโมดูลตั้งแต่แรก ทำให้สามารถแยกโมดูลออกมาเป็น Microservices ได้ง่ายขึ้นในอนาคต</li></ol><h3>ข้อดีของ Modular Monolith</h3><ul><li><strong>ลดความซับซ้อนของการ Deploy</strong>: เนื่องจากทุกโมดูลอยู่ในระบบเดียว การจัดการ deployment ง่ายกว่าการต้อง deploy หลาย services</li><li><strong>พัฒนาง่ายและมีประสิทธิภาพสูง</strong>: นักพัฒนาสามารถทำงานใน codebase เดียวกัน โดยไม่ต้องเสียเวลาในการตั้งค่า service-to-service communication เหมือนใน Microservices</li><li><strong>รักษาความสม่ำเสมอของข้อมูลได้ง่าย</strong>: ใช้ฐานข้อมูลเดียวกัน ทำให้ลดปัญหา data inconsistency ที่มักเกิดขึ้นใน Microservices</li><li><strong>ลด Overhead ของ Infrastructure</strong>: ไม่ต้องจัดการระบบ orchestration ซับซ้อนเช่น Kubernetes หรือ API Gateway เหมือนใน Microservices</li><li><strong>รองรับการขยายในอนาคต</strong>: ถ้าจำเป็น สามารถแยกโมดูลออกมาเป็น Microservices ได้โดยไม่ต้องรื้อโครงสร้างระบบทั้งหมด</li></ul><h3>ข้อเสียของ Modular Monolith</h3><ul><li><strong>การ Deploy ยังต้องทำทั้งระบบ</strong>: แม้จะมีโมดูลแยกกัน แต่เมื่อมีการเปลี่ยนแปลงในโค้ด ระบบทั้งหมดต้องถูก deploy พร้อมกัน</li><li><strong>อาจนำไปสู่ Distributed Monolith ถ้าออกแบบไม่ดี</strong>: หากโมดูลต่าง ๆ ถูก deploy แยกกันโดยที่ยังคงมีการพึ่งพากันสูง อาจทำให้เกิด Distributed Monolith ซึ่งมีปัญหาที่ยากต่อการจัดการ</li><li><strong>ข้อจำกัดของ Scaling</strong>: ไม่สามารถ scale แต่ละโมดูลแยกจากกันได้ง่ายเหมือนใน Microservices</li></ul><h3>Modular Monolith เหมาะกับใคร?</h3><ul><li>ทีมที่ต้องการสร้างระบบที่มี <strong>ขอบเขตโมดูลชัดเจน</strong> แต่ยังไม่ต้องการความซับซ้อนของ Microservices</li><li>องค์กรที่ต้องการลด <strong>Overhead ของ Infrastructure</strong> และมีทีมขนาดเล็กถึงกลาง</li><li>ระบบที่ยังไม่ต้องการ scale แบบอิสระในแต่ละโมดูล แต่ต้องการเตรียมความพร้อมสำหรับอนาคต</li></ul><h3>สรุป</h3><p>Modular Monolith เป็นแนวทางที่อยู่ระหว่าง Monolith และ Microservices ซึ่งให้ความสมดุลระหว่างความง่ายในการพัฒนาและการแยกโมดูลที่ชัดเจน เป็นตัวเลือกที่เหมาะสมสำหรับทีมที่ต้องการแยกโมดูลแต่ยังไม่ต้องการความซับซ้อนของ Microservices อย่างไรก็ตาม การออกแบบให้โมดูลมีขอบเขตที่ชัดเจนและลดการ coupling ระหว่างโมดูลเป็นสิ่งสำคัญ เพื่อป้องกันการกลายเป็น Distributed Monolith โดยไม่ตั้งใจ</p><p>หากระบบของคุณเติบโตขึ้นและต้องการขยายตัว Modular Monolith จะช่วยให้สามารถเปลี่ยนไปใช้ Microservices ได้ง่ายขึ้น ทำให้เป็นแนวทางที่ได้รับความนิยมในการออกแบบระบบยุคใหม่</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4a5b6e99eaf8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Go Fundamentals]]></title>
            <link>https://medium.com/@somprasongd/go-fundamentals-749103ad3435?source=rss-eb4e76569e6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/749103ad3435</guid>
            <category><![CDATA[go]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Somprasong Damyos]]></dc:creator>
            <pubDate>Fri, 21 Mar 2025 07:27:57 GMT</pubDate>
            <atom:updated>2025-03-21T07:29:36.664Z</atom:updated>
            <content:encoded><![CDATA[<p>ภาษา Go (หรือ Golang) เป็นภาษาโปรแกรมระดับสูงที่พัฒนาโดย Google โดยเน้นความเรียบง่าย ประสิทธิภาพสูง และรองรับการประมวลผลพร้อมกัน (Concurrency) ได้อย่างยอดเยี่ยม ด้วยความสามารถเหล่านี้ทำให้ Go ได้รับความนิยมอย่างรวดเร็วในหมู่นักพัฒนาซอฟต์แวร์ โดยเฉพาะอย่างยิ่งในระบบที่ต้องการความเร็วและความเสถียรสูง เช่น ระบบเครือข่าย บริการแบบกระจาย (Distributed Systems) และแอปพลิเคชันที่มีการประมวลผลพร้อมกัน</p><p>บทความนี้จะพาคุณไปรู้จักกับภาษา Go ตั้งแต่ขั้นพื้นฐานจนถึงแนวคิดขั้นสูง โดยครอบคลุมหัวข้อสำคัญ ได้แก่</p><ul><li>เริ่มต้นใช้งาน</li><li>พื้นฐานภาษา Go</li><li>ชนิดของข้อมูลใน Go</li><li>ออกแบบโค้ดให้ยืดหยุ่นด้วย Interface</li><li>ทำความรู้จักกับ Generic ในภาษา Go</li><li>การทำงานแบบ Concurrency</li><li>การจัดการข้อผิดพลาด (Error Handling)</li><li>Unit Testing</li><li>การใช้งานฟังก์ชันขั้นสูง</li><li>การใช้ Context ใน Go</li><li>การทำงานกับ JSON</li></ul><p>อ่านเพิ่มเติมได้ที่ <a href="https://somprasongd.work/blog/go/go-fundamentals">https://somprasongd.work/blog/go/go-fundamentals</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=749103ad3435" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[เจ้าหนูอัลตร้า]]></title>
            <link>https://medium.com/ultra-journey/%E0%B9%80%E0%B8%88%E0%B9%89%E0%B8%B2%E0%B8%AB%E0%B8%99%E0%B8%B9%E0%B8%AD%E0%B8%B1%E0%B8%A5%E0%B8%95%E0%B8%A3%E0%B9%89%E0%B8%B2-50ba347e5b9?source=rss-eb4e76569e6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/50ba347e5b9</guid>
            <dc:creator><![CDATA[Somprasong Damyos]]></dc:creator>
            <pubDate>Mon, 07 Oct 2019 15:01:00 GMT</pubDate>
            <atom:updated>2019-10-07T15:01:00.792Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eWhLMdQWEUYrR4woJdFeXQ.jpeg" /></figure><h4>#2 มาฝึกเข้าเต้ากันเถอะ</h4><h3>โอบกอดแรก</h3><p>สัมผัสแรกระหว่างแม่ลูก คือการที่ พยบ. พาอัลตร้ามาเข้าเต้าแม่ปุ๊ เนื่องจากแม่ต้องนอนนิ่งๆ พยบ. จึงต้องค่อยช่วยให้อัลตร้าเข้าเต้าเพื่อกระตุ้นน้ำนม วันแรกพยายามกันอยู่ 5 รอบ แต่ยังถือว่าไม่ผ่่่านคะแนนแต่ละรอบได้ไม่เคยเกิน 5/10 พยบ.จึงขอให้แม่พักผ่อนตอน 3 ทุ่ม และพาอัลตร้าไปดูแลต่อเอง</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XnwxoVA8wARysalM9-B2pA.jpeg" /></figure><blockquote>โดยพยาบาลขอเสริมนมผงถ้าอัลตร้าหิวมาก ทั้งคืนได้ไป 3 ครั้ง ครั้งละ 10 ml</blockquote><h3>ฟ้าหลังฝนสวยงามเสมอ</h3><p>วันที่ 2 พยบ. นัดไว้ 7 โมงเช้า แต่รอแล้วยังไม่มา แม่ปุ๊ร้อนใจให้พ่อโทรตาม กว่าจะมาก็ 7:30 น. แล้ว แถมได้นมเสริมมาแล้ว 10 ml หลับปุ๋ยมาเลย แม่ปุ๊ถึงกับร้องไห้ที่ไม่ได้ให้นมอัลตร้า</p><p>เริ่มต้นใหม่อีกครั้ง คราวนี้ พยบ. จัดท่าอุ้มลูกฟุตบอลให้แม่ปุ๊ลองให้นม พอปากถึงเต้านมอัลตร้างับดูดทันที แม่ปุ๊ร้องไห้อีกครั้ง เพราะรอบนี้ได้คะแนน 10/10 🎉 ส่วนพ่อก็รับหน้าที่อุ้มเรอไป</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Pe0z5q25sO0DxeoZX5Uumg.jpeg" /></figure><p>คุณป้าหมอมาเยี่ยม พร้อมตรวจเต้านม คุณป้าหมอบอกมีน้ำนมแล้วดีมาก แถมอัลตร้าร้องลั่นอยากโชว์ ป้าหมอไม่รอช้าอุ้มจับเข้าเต้าทันทีในท่ามาตราฐาน เรียบร้อย perfect!!! พร้อมคำชมมากมายจากป้าหมอ พร้อมฉายา “จิ๋วของป้าหมอ”</p><p>วันนี้ทั้งวันเข้าเต้ามาตลอดจนถึงมื้อค่ำ จิ๋วเริ่มไม่ไหวร้องลั่นจนต้องเสริมนมผงไป 2 รอบ นอนสบายเลย จนเที่ยงคืนครึ่ง ตื่นมาเข้าเต้่ารอบนี้รู้สึกอัลตร้าตัวร้อนๆ เลยโทรตาม พยบ. มาวัดไข้ ถึงจะปกติแต่แม่ปุ๊กลัวไม่สบายเลยฝากพี่พยบ. ช่วยดูแลอีกคืน</p><blockquote>วันนี้อยู่ด้วยกันถึงตี 1 อยู่กับพี่พยบ. พี่ๆ บอกก็กินเก่งขึ้น และน้องไม่กวนเลย</blockquote><h3>อัลตร้าน้อยผู้หิวโหย</h3><p>วันที่ 3 เข้าเต้าครั้งนี้ พ่อไม่ต้องช่วยอัลตร้าเล็งแล้ว หนูหิวหนูหาเองได้พ่อ แต่ก็ยังลำบากทุกครั้งที่เข้าเต้าเนื่องจากแม่ปุ๊ยังจับจังหวะไม่ได้ พอเข้าถูกจังวะปุ๊บอัลตร้าก็ดูดยาวๆ เลย</p><p>แต่น้ำนมแม่คงยังมาไม่เยอะพอที่จะทำให้อัลตร้าอิ่มจนหลับได้ยาวๆ ตั้งแต่บ่าย 2 จนถึง 1 ทุ่ม อัลตร้าไม่เคยนอนเกิน 40 นาที เลย ร้องกินนมตลอด จนแม่ต้องโทรขอ พยบ. มาช่วยดูให้ ซึ่งแน่นอนอัลตร้าไม่อิ่ม เลยต้องเสริมนมผงไปบ้าง</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gGvABZw7mDZ1o-grfa6b_A.jpeg" /></figure><h3>คืนที่ท้าทาย</h3><p>คืนนี้คืนสุดท้ายก่อนกลับบ้าน พยบ. พาไปสอนอาบน้ำ และเมื่อกลับมาถึงห้อง พยบ. ก็ให้พ่อถามคำถามมากมาย และทิ้งท้ายว่าคืนนี้อยากให้แม่ให้นมแม่เองทั้งคืน ให้คิดว่าไม่มีนมผงในโลก แม่ลองสู้หน่อยสักคืน หิวปุ๊บเข้าเต้าปั๊บ ถี่หน่อยนะคืนนี้ เพื่อคืนต่อๆ ไปจะได้สบาย (แต่ถ้าไม่ไหวโทรตามได้ตลอดเวลา) ไม่รู้จะเป็นยังไงบ้างคืนนี้ ตอนนี้ได้ให้แม่ปุ๊นอนพักไปแล้ว</p><p>สู้ๆ นะแม่ปุ๊ พ่อจะค่อยนอน(รออุ้มเรอ)อยู่ข้างๆ 😜</p><h3>ไม่ไหวอย่าฝืน</h3><p>คุณแม่สำคัญที่สุด ถ้าเครียดไปก็ส่งผลต่อน้ำนม ฝืนให้นมจนไม่ได้พักผ่อน ไม่สบายขึ้นมาลูกจะกินอะไร คุณป้าหมอ คุณพี่พยบ. ก็ส่งเสริมให้กินนมจากเต้าทุกคน แต่ถ้าคุณแม่ไหวแล้ว จะเสริมนมผงบ้่างก็ไม่ได้ผิดอะไร เอาที่พอดี ทุกอย่างจะดีเอง</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1Acw9rzScjgod0ybfhWZPQ.jpeg" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=50ba347e5b9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ultra-journey/%E0%B9%80%E0%B8%88%E0%B9%89%E0%B8%B2%E0%B8%AB%E0%B8%99%E0%B8%B9%E0%B8%AD%E0%B8%B1%E0%B8%A5%E0%B8%95%E0%B8%A3%E0%B9%89%E0%B8%B2-50ba347e5b9">เจ้าหนูอัลตร้า</a> was originally published in <a href="https://medium.com/ultra-journey">ultra-journey</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[เจ้าหนูอัลตร้า]]></title>
            <link>https://medium.com/ultra-journey/%E0%B9%80%E0%B8%88%E0%B9%89%E0%B8%B2%E0%B8%AB%E0%B8%99%E0%B8%B9%E0%B8%AD%E0%B8%B1%E0%B8%A5%E0%B8%95%E0%B8%A3%E0%B9%89%E0%B8%B2-ab53d8d77b9c?source=rss-eb4e76569e6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/ab53d8d77b9c</guid>
            <dc:creator><![CDATA[Somprasong Damyos]]></dc:creator>
            <pubDate>Sat, 05 Oct 2019 08:42:07 GMT</pubDate>
            <atom:updated>2019-10-05T09:34:41.235Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oq_MMNm2u7BxYqzyyXPFDg.jpeg" /></figure><h4>#1 พบกันครั้งแรก</h4><p>วันนี้คุณหมอนัดให้มาผ่าคลอด โดยให้มาเตรียมตัวตั้งแต่ตี 4:30 น. คุณแม่ก็ไปวัด v/s ส่วนพ่อไปจ่ายเงินมัดจำ เป็นไปตามที่แม่คิดไว้บัตรเครดิตรูดไม่ได้ บอกกำลังปรับปรุงข้อมูลอยู่ ไปกดเงินสด กดอยู่หลายรอบกว่าจะได้บอกการสื่อการผิดพลาด อัลตร้าเกือบไม่ได้คลอดซะแล้ว</p><p>หลังจากจ่ายมัดจำเรียบร้อย ก็เข้ามานอนเตรียมตัวในห้องพัก วัดไข้ เจาะเลือด ตรวจน้ำตาล สรุปน้ำตาลต่ำ โดนน้ำเกลือไป 2 เท่า + น้ำตาลไปอีกขวด</p><p>ถึงเวลานัด พยาบาลมารับแม่ไปห้องผ่าตัด ส่วนพ่อต้องยืนรอพยาบาลเด็กพาไปเปลี่ยนชุดตามไปทีหลัง รออยู่นานมากจนนึกว่าเค้าลืมไปซะแล้ว พอเปลี่ยนเสื้อผ้าเสร็จก็ย้ายไปนั่งรอหน้าห้องผ่าตัด ดูเวลาครั้งสุดท้าย 9:40 น. สักพักพยาบาลก็เดินมาตาม</p><p>เข้าห้องผ่าตัด เห็นหมอน่าจะเริ่มผ่าไปแล้ว พยาบาลรีบตามให้มานั่งด้านบนหัวปุ๊ เก้าอี้สูงเกินฉากกั้น พยาบาลรีบมาเอาตัวใหม่เปลี่ยน นั่งลูบหัวปุ๊ แต่ปุ๊ดันไม่รู้ว่าใคร ฮาๆ คนกำลังมึน เห็นปุ๊บีบตา 2–3 ครั้ง น่าจะเจ็บ อยู่ๆ หมอก็สะดุ้ง ก่อนเสียงหัวเราะก็ตามมา ปุ๊ตกใจมาก นึกว่าอัลตร้าเป็นอะไร ที่แท้อัลตร้าเกือบเตะเข้าหน้าหมอ หมอบอกอย่ารีบลูก แปปเดียวออกมาทั้งตัว ประกาศเวลาคลอด 9:53 น. เสียงร้องลั่นห้องผ่าตัด ปุ๊ค่อยถามตลอดว่าลูกโอเคมั้ย ทั้งหมอทั้งพยาบาลบอกร้องขนาดนี้แข็งแรงแน่นอน พยาบาลก็มาขอโทรศัพท์จะถ่ายรูปให้ แต่พ่อนึกว่าเอาเข้าไม่ได้ ก็เลยใช้เครื่องของพยาบาลห้องเด็กแทน</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NJB0a31l0kNG9zJs1-3j6A.jpeg" /></figure><p>หลังทำความสะอาด ชั่งน.น. ตรวจเสร็จ คุณหมอเด็กก็ย้ายอัลตร้าไปอีกห้อง พ่อก็ต้องออกไปพร้อมกัน พยาบาลจับตัวพาเดินออกไป โดยไม่ยอมให้หันไปดูบนเตียง สงสัยกลัวพ่อจะเป็นลม 😅 เปลี่ยนชุดเสร็จรีบวิ่งไปหยิบโทรศัพท์ แล้วตามไปห้องเด็กอ่อนทันที</p><p>รอคุณหมอเด็กตรวจเสร็จ พยาบาลให้เปลี่ยนชุดอีกรอบ คุณหมอก็อธิบายหลายๆ อย่าง สรุป ok แต่น.น. เฉียดไปนิดเดียว บวกกับมีประวัติตัวเล็กในครรภ์ เลยขอติดตามอาการอีก 24 ช.ม.</p><p>เหมือนต้องมนต์สะกด… ยืนดูอัลตร้านอนอยู่ในตู้ปรับอุณหภูมิ เวลาผ่านไปเร็วมาก 1 ช.ม. จนพยาบาลต้องบอกให้ไปพักก่อน เพราะคุณแม่ใกล้จะออกจากห้องพักฟื้นแล้ว เดี๋ยวอาบน้ำ ฉีดวัคซีนเสร็จจะพาอัลตร้ามาเข้าเต้า</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JRJaIkkimvFZ6B33EOxH9A.jpeg" /></figure><p>กลับมานั่งรอในห้องไปแปปเดียวปุ๊ก็เข้ามา พุงหายไปเยอะเลย พ่ออยากพุงหายบ้าง😭 รออยู่เกือบ 2 ช.ม. อัลตร้าก็มาหาแม่แล้ว 🥰</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i9criI0q-eJFFtOf4_Z3iw.jpeg" /></figure><p>ลองเข้าเต้ากันอยู่นาน แต่อัลตร้าไม่ยอมตื่น งับทีสองที ก็นอนต่อ จนต้องปล่อยให้นอนต่อไป รอให้หิวแล้วตื่นเอง (ต้องขอบคุณพยาบาล ก้มๆ เงยๆ ช่วยกันอยู่นานหลายสิบนาที)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XnwxoVA8wARysalM9-B2pA.jpeg" /></figure><p>สุดท้ายก็หลับไปทั้งแม่ และลูก หลับกันแบบนี้คืนนี้ยาวแน่นอน 😱</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2ROzsRsGzkPkji0I9SCqSQ.jpeg" /></figure><p>ปล. หล่อได้พ่อจริงๆ</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ab53d8d77b9c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ultra-journey/%E0%B9%80%E0%B8%88%E0%B9%89%E0%B8%B2%E0%B8%AB%E0%B8%99%E0%B8%B9%E0%B8%AD%E0%B8%B1%E0%B8%A5%E0%B8%95%E0%B8%A3%E0%B9%89%E0%B8%B2-ab53d8d77b9c">เจ้าหนูอัลตร้า</a> was originally published in <a href="https://medium.com/ultra-journey">ultra-journey</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[วิธีการใช้ import/export ใน Node.js 10]]></title>
            <link>https://medium.com/@somprasongd/%E0%B8%A7%E0%B8%B4%E0%B8%98%E0%B8%B5%E0%B8%81%E0%B8%B2%E0%B8%A3%E0%B9%83%E0%B8%8A%E0%B9%89-import-export-%E0%B9%83%E0%B8%99-node-js-10-56feadef9277?source=rss-eb4e76569e6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/56feadef9277</guid>
            <dc:creator><![CDATA[Somprasong Damyos]]></dc:creator>
            <pubDate>Mon, 15 Oct 2018 14:41:19 GMT</pubDate>
            <atom:updated>2018-10-16T13:56:27.963Z</atom:updated>
            <content:encoded><![CDATA[<p>สามารถทำได้หลายวิธี แต่ในตัวอย่างนี้จะใช้ 2 วิธี คือ</p><ol><li>ใช้ <a href="https://babeljs.io/">babel</a></li><li>ใช้โมดูล <a href="https://www.npmjs.com/package/esm">esm</a></li></ol><h3>แบบใช้ Babel</h3><ul><li>เริ่มจากสร้างโปรเจค mkdir node-es6-babel &amp;&amp; cd $_</li><li>สร้างไฟล์ package.json npm init -y</li><li>ติดตั้ง babel npm i -D @babel/cli @babel/core @babel/node @babel/preset-env</li><li>สร้างไฟล์ .babelrc (touch .babelrc) แล้วตั้งค่าตามนี้</li></ul><pre>{<br>  &quot;presets&quot;: [<br>    [<br>      &quot;@babel/preset-env&quot;,<br>      {<br>        &quot;targets&quot;: {<br>          &quot;node&quot;: &quot;current&quot;<br>        }<br>      }<br>    ]<br>  ]<br>}</pre><blockquote>&quot;node&quot;: &quot;current&quot; คือ ระบุ version ของ nodejs ที่ใช้รัน babel อยู่ (เหมือน &quot;node&quot;: process.versions.node)</blockquote><ul><li>สร้างไฟล์ src/index.js และไฟล์ src/my-module.js</li></ul><pre>// src/my-module.js<br>// Named export - Has a name. Have as many as needed.<br>export const message = &#39;Some message from my-module.js&#39;;</pre><pre>// Default export - Has no name. You can only have one.<br>export default (name) =&gt; `Hello ${name}`;</pre><pre>// or<br>// const message = &#39;Some message from my-module.js&#39;;<br>// const getGreeting = (name) =&gt; `Hello ${name}`;<br>// export { message, getGreeting as default }</pre><pre>// src/index.js<br>import greetingFn, { message } from &#39;./my-module&#39;;</pre><pre>console.log(message);</pre><pre>console.log(greetingFn(&#39;Ball&#39;));</pre><ul><li>แก้ไข script ในไฟล์ package.json ตามนี้</li></ul><pre>{<br>  &quot;scripts&quot;: {<br>    &quot;start&quot;: &quot;babel-node src/index.js&quot;<br>  },<br>}</pre><p>หรือถ้าใช้ร่วมกับ nodemon</p><pre>{<br>  &quot;scripts&quot;: {<br>    &quot;start&quot;: &quot;nodemon src/index.js --exec babel-node -e js&quot;<br>  },<br>}</pre><ul><li>ทดสอบรันดู npm run start</li></ul><pre>Some message from my-module.js<br>Hello Ball</pre><ul><li>เมื่อจะเอาโค้ดไป deploy ต้อง build ก่อน ทำได้โดยเพิ่ม scripts ใน package.json ดังนี้</li></ul><pre>{<br>  &quot;scripts&quot;: {<br>    &quot;start&quot;: &quot;babel-node src/index.js&quot;,<br>    &quot;clean&quot;: &quot;rm -rf dist&quot;,<br>    &quot;build&quot;: &quot;npm run clean &amp;&amp; mkdir dist &amp;&amp; babel src -s -d dist --copy-files&quot;,<br>    &quot;prod&quot;: &quot;export NODE_ENV=production||set NODE_ENV=production&amp;&amp;node dist/index.js&quot;,<br>  },<br>}</pre><ul><li>build โดยคำสั่ง npm run build จะได้ folder dist ออกมา</li><li>รันโดยใช้คำสั่ง npm run prod</li></ul><h3>แบบใช้ NPM module ESM</h3><ul><li>เริ่มจากสร้างโปรเจค mkdir node-es6-esm &amp;&amp; cd $_</li><li>สร้างไฟล์ package.json npm init -y</li><li>ติดตั้ง esm npm i esm</li><li>สร้างไฟล์ src/index.js, src/main.js และไฟล์ src/my-module.js</li></ul><pre>// src/my-module.js<br>// Named export - Has a name. Have as many as needed.<br>export const message = &#39;Some message from my-module.js&#39;;</pre><pre>// Default export - Has no name. You can only have one.<br>export default (name) =&gt; `Hello ${name}`;</pre><pre>// or<br>// const message = &#39;Some message from my-module.js&#39;;<br>// const getGreeting = (name) =&gt; `Hello ${name}`;<br>// export { message, getGreeting as default }</pre><pre>// src/main.js<br>import greetingFn, { message } from &#39;./my-module&#39;;</pre><pre>console.log(message);</pre><pre>console.log(greetingFn(&#39;Ball&#39;));</pre><pre>// src/index.js<br>require = require(&#39;esm&#39;)(module);<br>module.exports = require(&#39;./main.js&#39;);</pre><ul><li>แก้ไข script ในไฟล์ package.json ตามนี้</li></ul><pre>{<br>  &quot;scripts&quot;: {<br>    &quot;start&quot;: &quot;node ./src/index.js&quot;,<br>    &quot;dev&quot;: &quot;nodemon ./src/index.js&quot;,<br>    &quot;prod&quot;: &quot;export NODE_ENV=production||set NODE_ENV=production&amp;&amp;npm start&quot;<br>  },<br>}</pre><p>หรือถ้าใช้ร่วมกับ nodemon</p><pre>{<br>  &quot;scripts&quot;: {<br>    &quot;start&quot;: &quot;node ./src/index.js&quot;,<br>    &quot;dev&quot;: &quot;nodemon ./src/index.js&quot;,<br>    &quot;prod&quot;: &quot;export NODE_ENV=production||set NODE_ENV=production&amp;&amp;npm start&quot;<br>  },<br>}</pre><ul><li>ทดสอบรันดู npm run start หรือ npm run dev</li></ul><pre>Some message from my-module.js<br>Hello Ball</pre><ul><li>เมื่อจะเอาโค้ดไป deploy แบบนี้ไม่ต้อง build สามารถรันได้เลยโดยใช้คำสั่ง npm run prod</li></ul><p>สามารถดูตัวอย่างโค้ดทั้งหมดได้ใน <a href="https://github.com/somprasongd/example-nodejs-es6-import-export">github</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=56feadef9277" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jasper Report: วิธีการตรวจสอบ $P{date} is null ในคิวรี่]]></title>
            <link>https://medium.com/@somprasongd/jasper-report-%E0%B8%A7%E0%B8%B4%E0%B8%98%E0%B8%B5%E0%B8%81%E0%B8%B2%E0%B8%A3%E0%B8%95%E0%B8%A3%E0%B8%A7%E0%B8%88%E0%B8%AA%E0%B8%AD%E0%B8%9A-p-date-is-null-%E0%B9%83%E0%B8%99%E0%B8%84%E0%B8%B4%E0%B8%A7%E0%B8%A3%E0%B8%B5%E0%B9%88-71cd22de31ea?source=rss-eb4e76569e6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/71cd22de31ea</guid>
            <category><![CDATA[ireport]]></category>
            <category><![CDATA[jasper-reports]]></category>
            <dc:creator><![CDATA[Somprasong Damyos]]></dc:creator>
            <pubDate>Thu, 11 Oct 2018 09:43:47 GMT</pubDate>
            <atom:updated>2018-10-11T16:31:56.000Z</atom:updated>
            <content:encoded><![CDATA[<p>โดยปกติการคิวรี่ข้อมูลจากวันที่ เราสามารถเขียน SQL ใส่เงื่อนไขจากวันที่ได้ตรงๆ อยู่แล้ว เช่น</p><pre>select * from tasks where due_date = $P{date}</pre><pre>-- or</pre><pre>select * from tasks where due_date between $P{start_date} and $P{end_date}</pre><p>แต่ถ้าต้องการเพิ่มเงื่อนไขว่าถ้า $P{date} มีค่าเท่ากับ null ให้แสดงค่าทั้งหมดออกมา เราจะไม่สามารถใช้ $P{date} is null ได้เหมือนกับพารามิเตอร์แบบ String</p><pre>select * from tasks where $P{date} is null or due_date = $P{date}</pre><pre><br>Error filling print... Error executing SQL statement for : sample-report<br>net.sf.jasperreports.engine.JRException: Error executing SQL statement for : sample-report</pre><blockquote><strong>หมายเหตุ </strong>ถ้า $P{date} มีค่าเป็น null จะสามารถทำงานได้ แต่ถ้ามีค่าจะไม่สามารถทำงานได้</blockquote><p>วิธีการที่ถูกต้อง คือ ต้องใส่ $X{} expression มาช่วยในการตรวจสอบค่า null ดังนี้</p><pre>select * from tasks where $X{EQUAL, NULL, date} or due_date = $P{date}</pre><p>นอกจากนี้ยังสามารถใช้ $X{} expression กับ Date ได้อีก ดังนี้</p><ul><li>$X{LESS, column, date} คือ column value &lt; $P{date}</li><li>$X{LESS], column, date} คือ column value&lt;= $P{date}</li><li>$X{GREATER, column, date} คือ column value&gt; $P{date}</li><li>$X{[GREATER, column, date} คือ column value&gt;= $P{date}</li><li>$X{BETWEEN, column, start_date, end_date} คือ column value &gt;= $P{start_date} AND column value &lt;= $P{end_date}</li></ul><p><strong>หมายเหตุ </strong>ส่วนตัวลอง $X{BETWEEN, column, start_date, end_date} แล้วไม่ได้ผล เพราะมันดันเอาเวลาไปใช้ด้วย</p><h3>ตัวอย่าง</h3><p>ถ้าต้องสร้างรายงานแสดงรายการงาน พร้อมแสดงวันที่ส่งงาน โดยสามารถค้นหาได้จากช่วงวันที่ หรือแสดงทั้งหมดเมื่อวันที่เป็น null ตามนี้</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/808/1*5Xj7WLcgwIypGCux5kj9Ww.png" /><figcaption>แสดงรายการตามช่วงเวลา</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/813/1*uyWljE6fQNUEP00XanUV6Q.png" /><figcaption>แสดงผลทั้งหมด เมื่อวันที่เป็น null</figcaption></figure><h4>Step 1: เตรียมฐานข้อมูล</h4><p>ในตัวอย่างนี้ ผมได้เตรียมข้อมูลเอาไว้แล้วเป็น PostgreSQL ซึ่งสามารถรันได้จาก Docker ตามนี้</p><pre>docker container run --rm -p 5433:5432 somprasongd/pg10-sample-db</pre><p>แต่ถ้าต้องการสร้างตารางเองก็สามารถใช้คำสั่งด้านล่างนี้ได้</p><pre>CREATE TABLE public.tasks (</pre><pre>id serial NOT NULL,</pre><pre>name varchar(100) NOT NULL,</pre><pre>due_date date NOT NULL,</pre><pre>created_at timestamptz NOT NULL DEFAULT now(),</pre><pre>CONSTRAINT tasks_pk PRIMARY KEY (id)</pre><pre>);</pre><pre>CREATE INDEX tasks_due_date_idx ON tasks USING btree (due_date);</pre><pre>INSERT INTO public.tasks (name, due_date) VALUES</pre><pre>(&#39;Task #1&#39;, now())</pre><pre>, (&#39;Task #2&#39;, now() + interval &#39;2 day&#39;)</pre><pre>, (&#39;Task #3&#39;, now() + interval &#39;4 day&#39;)</pre><pre>, (&#39;Task #4&#39;, now() + interval &#39;6 day&#39;)</pre><pre>, (&#39;Task #5&#39;, now() + interval &#39;8 day&#39;)</pre><pre>, (&#39;Task #6&#39;, now() + interval &#39;10 day&#39;)</pre><pre>, (&#39;Task #7&#39;, now() + interval &#39;20 day&#39;)</pre><pre>, (&#39;Task #8&#39;, now() + interval &#39;22 day&#39;)</pre><pre>, (&#39;Task #9&#39;, now() + interval &#39;30 day&#39;)</pre><pre>, (&#39;Task #10&#39;, now() + interval &#39;60 day&#39;);</pre><pre>-- ข้อมูลจะเริ่มจากวันที่ที่นำข้อมูลเข้าครั้งแรก เช่น วันนี้วันที่ 11 Oct 2018 Task #1 จะมี due_date เป็นวันที่ 11 Oct 2018</pre><h4>Step 2: เชื่อมต่อฐานข้อมูลกับโปรแกรม iReport</h4><p>เนื่องจากผมใช้ฐานข้อมูลจาก docker โดยกำหนด</p><ul><li><strong>port:</strong> 5433</li><li><strong>database name:</strong> sample-db</li><li><strong>username &amp; password:</strong> postgres</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/442/1*Hn_-VbHCmyxEKnOlsWUEGw.png" /></figure><h4>Step 3: สร้างไฟล์ sample-report.jrxml</h4><p>เมื่อสร้างไฟล์ใหม่จะได้ออกมาแบบนี้</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/914/1*AsfCCwWaG0dJHE41DYM-Cg.png" /></figure><p>เริ่มจากสร้าง parameter 2 ตัว คือ<br>- $P{start_date} เป็น java.util.Date ค่าเริ่มต้นเป็น null<br>- $P{end_date} เป็น java.util.Date ค่าเริ่มต้นเป็น null</p><p>เสร็จแล้วเขียน SQL ตามนี้</p><pre>SELECT name, due_date<br>FROM tasks<br>WHERE ($X{EQUAL, NULL, start_date} or $X{EQUAL, NULL, end_date} or<br>due_date between $P{start_date} and $P{end_date})<br>ORDER BY due_date</pre><p>จัดหน้าจอตามตัวอย่าง</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/603/1*GPfMyPSOklv5kj6AoeG1Xw.png" /><figcaption>$P{start_date} == null || $P{end_date} == null<br>? “Show All”<br>: ($P{start_date} != null ? “From: “ + $P{start_date} + “ “: “”)<br>+ ($P{end_date} != null ? “To: “ + $P{end_date} : “”)</figcaption></figure><p>หรือถ้าขี้เกียจ ให้เลือกที่ XML แล้ว copy โค้ดด้านล่างนี้ไปวางทับทั้งหมด</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3993aff8031714c3764d57d42b079054/href">https://medium.com/media/3993aff8031714c3764d57d42b079054/href</a></iframe><h4>Final Step: ทดสอบ Preview</h4><p>กดที่ปุ่ม Preview จะมีหน้าต่าง Parameter prompt ขึ้นมาให้เลือกวันที่ start_date และ end_date</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/807/1*rUZewGy_UkTVuk9zGrO3gQ.png" /><figcaption>แสดงตามช่วงวันที่เลือก</figcaption></figure><p>ถ้าเลือก start_date และ end_date เป็น Use default</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/461/1*BiI256lL6lFOdHGyyn0HfA.png" /></figure><p>รายงานก็จะแสดงข้อมูลทั้งหมดออกมาแทน</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/807/1*ON2QOmQpHcNaFk8xFnfhLw.png" /><figcaption>แสดงทั้งหมด</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=71cd22de31ea" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[แก้ไข Build Script ของ Netbeans Platform]]></title>
            <link>https://medium.com/@somprasongd/%E0%B9%81%E0%B8%81%E0%B9%89%E0%B9%84%E0%B8%82-build-script-%E0%B8%82%E0%B8%AD%E0%B8%87-netbeans-platform-85adc9525f2c?source=rss-eb4e76569e6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/85adc9525f2c</guid>
            <category><![CDATA[netbeans-platform]]></category>
            <dc:creator><![CDATA[Somprasong Damyos]]></dc:creator>
            <pubDate>Wed, 07 Feb 2018 10:18:17 GMT</pubDate>
            <atom:updated>2018-02-07T10:18:17.911Z</atom:updated>
            <content:encoded><![CDATA[<p>Netbeans Platform คือ Java framework ที่ใช้พัฒนา desktop applications โดยจะใช้โครงของตัว Netbeans ซึ่งเมื่อพัฒนาออกมาแล้วหน้าตาของโปรแกรมของเราจะออกมาคล้ายๆ กับ Netbeans [<a href="https://netbeans.org/kb/trails/platform.html">ดูเพิ่มเติม</a>]</p><p>ซึ่งเมื่อพัฒนาโปรแกรมเสร็จ และเราต้องการจะสร้างตัวติดตั้ง Netbeans ก็ได้เตรียม build script ไว้ให้แล้ว แต่ติดปัญหาอยู่ 2 ข้อคือ การกำหนดค่า Java heap size เริ่มต้นที่ 24Mb และสูงสุดที่ 64Mb อาจทำให้เกิดปัญหา Out of Memory ได้ กับ icon ของโปรแกรมจะได้เป็น icon Java สีดำๆ ออกมาเสมอ โดยเราสามารถแก้ไขปัญหาทั้ง 2 ข้อนี้ ด้วยไปแก้ไขไฟล์ Build Script ของ Netbeans</p><h4>การกำหนดค่า Java heap size</h4><ul><li>เปิดไฟล์ suite.xml ถ้าเป็น Windows จะอยู่ที่ C:\Program Files\NetBeans 8.2\harness\suite.xml</li><li>เลื่อนไปหา target ที่ชื่อว่า “build-launchers”</li><li>แทรกข้อความ &lt;replace file=&quot;${app.conf}&quot; token=&quot;-J-Xms24m -J-Xmx64m&quot; value=&quot;-J-Xms256m -J-Xmx512m&quot; /&gt; โดยที่ value คือค่าที่เราต้องการไปแทนที่ค่าเดิม ตำแหน่งที่ต้องแทรกโค้ดดูตามรูปด้านล่าง</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/657/1*l3uTR4QChFHRHXavWIyJ1A.png" /></figure><p>แต่วิธีข้างต้นจะเป็นการแก้ไขที่ส่งผลต่อทุกโปรเจค ถ้าหากต้องการแก้ไขให้แต่ละโปรเจคมีการกำหนดค่า Java heap size แตกต่างกัน สามารถทำได้ ดังนี้</p><ul><li>เปิดไฟล์ project.properties โดยเลือกที่ชื่อโปรเจค &gt; Important Files &gt; Project Properties</li><li>เพิ่ม run.args.extra=-J-Mmx256m -J-Xmx512m แล้วบันทึก</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/721/1*g2sfBv9k2pvj980VVfK-Fw.png" /><figcaption>แก้ไขไฟล์ project.properties</figcaption></figure><ul><li>เปิดไฟล์ suite.xml ถ้าเป็น Windows จะอยู่ที่ C:\Program Files\NetBeans 8.2\harness\suite.xml</li><li>เลื่อนไปหา target ที่ชื่อว่า “build-launchers”</li><li>แทรกข้อความ &lt;replace file=&quot;${app.conf}&quot; token=&quot;-J-Xms24m -J-Xmx64m&quot; value=&quot;${run.args.extra}&quot; /&gt; ซึ่งในขั้นตอนการ build โปรแกรมจะสามารถเข้าถึงค่าที่กำหนดไว้ใน project.properties ได้โดยใช้ ${ชื่อ}</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/620/1*aI2ahWnvK31FxTVIWqVPTA.png" /></figure><blockquote>วิธีนี้นอกจากจะกำหนดแยกรายโปรเจคได้แล้ว ทุกครั้งที่สั่ง Run โปรแกรมเพื่อทดสอบ Netbeans จะใช้ค่า Java heap size ตามที่กำหนดไว้ใน run.args.extra ด้วย</blockquote><h4>การเปลี่ยน Executable Icon บน Windows</h4><p>ขั้นตอนปกติในการสร้าง app.exe นั้น Netbeans จะทำการ copy ไฟล์ C:\Program Files\NetBeans 8.2\harness\launchers\app.exe มาไว้ที่ [yourproject]\build\launcher\bin\</p><p>จากนั้นจะเปลี่ยนชื่อโดยใช้ชื่อจาก app.name ที่กำหนดไว้ใน project.properties แต่ไม่ทำการเปลี่ยน icon ให้ ดังนั้นเราจะต้องใช้ third-party utility program มาช่วยในการเปลี่ยน icon แทน เช่นใช้โปรแกรม <a href="http://www.rw-designer.com/compile-vista-icon">ReplaceVistaIcon.exe</a> รันผ่าน command line</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/837/1*DxRlaph3Myn1P_fMrUu1Hw.png" /><figcaption>ตัวอย่างการใช้งาน ReplaceVistaIcon.exe</figcaption></figure><p>วิธีการแก้ไขไฟล์ Build Script เพิ่มเรียกใช้ ReplaceVistaIcon.exe ทำได้ดังนี้</p><ul><li>เปิดไฟล์ suite.xml ถ้าเป็น Windows จะอยู่ที่ C:\Program Files\NetBeans 8.2\harness\suite.xml</li><li>เพิ่มโค้ดด้านล่างไว้ใน &lt;project&gt;...&lt;/project&gt;</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/917490a3343b3215afac587ebc186912/href">https://medium.com/media/917490a3343b3215afac587ebc186912/href</a></iframe><blockquote><strong>อธิบายโค้ด</strong><br>condition เอาไว้ตรวจสอบว่าใช้ OS Windows หรือไม่<br>replaceWindowsLauncherIcon เป็น target ใหม่ที่สร้าง เพื่อจะเรียกใช้ในขั้นตอนการสร้างไฟล์ app.exe โดยตัวมันเองจะไปเรียกใช้ ReplaceVistaIcon.exe เพื่อเปลี่ยน icon ของ app.exe ซึ่งโดยปกติจะมีไฟล์ .exe อยู่ 3 ตัวคือ app.exe, app64.exe และ app_w.exe</blockquote><ul><li>เรียกใช้ target=&quot;replaceWindowsLauncherIcon&quot; ใน target=&quot;build-lanchers&quot;แทรกโค้ด &lt;!--Replace the icon for the Windows launcher exe.--&gt;&lt;antcall target=&quot;replaceWindowsLauncherIcon&quot;/&gt; หลังการ copy และ rename app.exe เสร็จแล้ว ดังนี้</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8cb1293bd948b29acc9096474db727d1/href">https://medium.com/media/8cb1293bd948b29acc9096474db727d1/href</a></iframe><p>เมื่อแก้ไขเสร็จทั้งหมดแล้วลองสร้างตัวติดตั้งดู</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/762/1*JSsWnc-A_kTZlWm2PF6YkA.png" /><figcaption>เลือก Installers จะสร้างไฟล์ติดตั้ง .exe ออกมาให้</figcaption></figure><p>ก็จะได้โปรแกรมที่มี icon และการกำหนดค่า Java heap size ตามที่เรากำหนดไว้แล้ว</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=85adc9525f2c" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>