ทำ Docker Image Java ที่ใช้ OpenCv ได้

ทำ Docker Image Java ที่ใช้ OpenCv ได้

ในปัจจุบันเทคโนโลยีที่เกี่ยวกับการประมวลผลภาพกำลังเป็นที่นิยมไม่ว่าจะเป็นการตรวจจับใบหน้าแล้วเอาไปตรวจสอบกับฐานข้อมูลว่าเป็นคนคนเดียวกันไหม หรือนำภาพไปหาว่าในภาพนั้นมีอะไรอยู่ในภาพนั้นบ้าง ซึ่งเป็นอะไรที่ดูว้าวแล้วเอาไปใช้ทำอะไรได้มากมาย ตัวอย่างที่ใช้กันบ่อยก็คือ ถ่ายรูปหน้า กับรูปบัตรประชาชน แล้วเทียบกันว่าใช่คนเดียวกันไหม หรือตั้งกล้องไว้หน้าบริษัทแล้วทำการตรวจสอบหน้าว่าใช่คนในบริษัทไหม ถ้าใช่ถึงเปิดประตูให้เข้า

OpenCv

OpenCv คือตัว Open Source Lib ที่ใช้ในการประมวลผลภาพที่เปิดให้เราใช้ได้แบบฟรีๆ (ต้องขอขอบคุณเหล่า Dev ทั่วโลกที่ร่วมด้วยช่วยกันสร้างของดีแบบนี้ให้ใช้แบบฟรีๆ) แล้วก็มีคนเอาไปใช้มากมาย มีคนทำตัวเชื่อมให้สามารถใช้กับหลายๆภาษาไม่ว่าจะเป็น Java , Python สามารถใช้บน Windows, Linux , Mac

แล้วจะทำ Docker Image ยังไงล่ะ

ทุกอย่างดูสวยหรูแต่ปัญหาของผมคือแล้วจะทำ Docker Image ยังไงวะ วิธีที่ง่ายที่สุดคือหา Official Docker Image ว่ามันมีที่เป็น Java11 OpenCv version 4.5.1 ไหม ซึ่งปรากฏว่าไม่มีจ้า (มีแต่มีของคนทั่วไปที่เขา Push ขึ้น Dockerhub ไว้ จะเอามาใช้ก็เสี่ยง) ซึ่งด้วยเหตุผลนี้ทำให้ต้องมาทำ Docker Image เอง

ซึ่งจะได้ Dockerfile แบบนี้

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM openjdk:11-jdk AS builder
RUN apt update && apt install -y cmake g++ ant wget unzip

RUN wget -O opencv.zip https://github.com/opencv/opencv/archive/4.5.1.zip

RUN mkdir -p /build
RUN unzip opencv.zip -d /
RUN mv opencv-4.5.1 opencv

WORKDIR /build
RUN cmake -DBUILD_SHARED_LIBS=OFF ../opencv
RUN make -j1

FROM openjdk:11-jre

COPY --from=builder /build/lib/libopencv_java451.so /usr/lib
COPY --from=builder /build/bin/opencv-451.jar /usr/lib

เดี๋ยวค่อยๆ อธิบายกัน

1
FROM openjdk:11-jdk AS builder

ตัว Build นี้นั้นผมทำเป็น Multi-stage Build โดย stage builder นั้นจะต้องทำการ Compile ตัว Lib OpenCv ให้อยู่ในรูปที่สามารถนำไปใช้งานได้เลยก่อน แล้วค่อยเอาไปใส่อีก stage ที่จะเอาไปเป็น Image เพื่อใช้งาน โดยส่วนนี้ต้องใช้เป็น openjdk:11-jdk ก่อนเพราะตอน Compile Lib ไปใช้นั้น Ant ในการ compile ตัว Code ของ Java ก็เลยต้องใช้เป็น JDK

1
RUN apt update && apt install -y cmake g++ ant wget unzip

ส่วนนี้คือการ Download lib ที่จำเป็นในการ Download source code แล้วก็ compile

1
2
3
4
5
6
7
RUN wget -O opencv.zip https://github.com/opencv/opencv/archive/4.5.1.zip

RUN mkdir -p /build
RUN unzip opencv.zip -d /
RUN mv opencv-4.5.1 opencv

WORKDIR /build

ส่วนนี้คือการ Download Source code ของ OpenCv version 4.5.1 มาจากนั้นทำการแตก zip ให้อยู่ใน directory ที่ต้องการ

1
RUN cmake -DBUILD_SHARED_LIBS=OFF ../opencv

ส่วนนี้คือคำสั่ง Config ว่าจะ Compile แบบไหน โดยตัว -DBUILD_SHARED_LIBS=OFF อันนี้เป็นการบอกว่าเราจะทำการ Compile แบบไม่ใช้ share lib คือ build แล้วทุกอย่างเป็นไฟล์เดียว เอาไปวางแล้วใช้งานได้เลย (ต้องเป็น platform เดียวกันกับตัวที่ build ด้วยนะ ไม่ใช่ build บน windows เอาไปใช้บน Linux แบบนี้ไม่ได้นะ (ตัว Linux แต่ละ Distro อาจไม่เหมือนกันด้วย ดังนั้นใช้ Distro ไหนก็ควรใช้ Distro นั้น Version นั้นในการ compile ))

1
RUN make -j1

ส่วนนี้จะเป็นการเริ่ม compile code โดย -j คือการบอกว่าอนุญาตให้ทำงานแบบ parallel โดยของผม set ไว้ที่ 1 เพราะ cpu ของเครื่อง VM ทดลองผมมีแค่ core เดียว ถ้าใครเครื่องแรงจะ compile หลายๆ core พร้อมกันก็เปลี่ยน -j เป็นจำนวน core ที่อยาก parallel นะครับ โดยผลลัพธ์การ compile ทั้งหมดจะอยู่ใน directory /build

1
FROM openjdk:11-jre

ส่วนนี้คือการสร้าง Image ที่จะเอาไปเป็นตัวตั้งต้นให้ตัว java ที่อยากจะใช้งาน opencv เอาไปใช้งาน จะเห็นว่าใช้ openjdk:11-jre แทนเพราะ java ที่ต้องการ Run อย่างเดียวไม่ต้องการทำการ compile จึงไม่จำเป็นต้องใช้ JDK

1
2
COPY --from=builder /build/lib/libopencv_java451.so /usr/lib
COPY --from=builder /build/bin/opencv-451.jar /usr/lib

ส่วนนี้คือการเอาไฟล์ที่ได้จาก compile จาก stage build มาใส่ใน docker image ที่เราจะเอาไปใช้

เมื่อเข้าใจทุกอย่างแล้วก็สั่ง Build

1
docker build -t jre11-opencv4.5.1

การ Build ใช้เวลานานประมาณนึง (ของผมใช้เวลาประมาณ 1 ชั่วโมง)

ทดลองใช้งาน

คุณสามารถลองใช้งานโดยการเขียน code ง่ายๆ Load lib ของ OpenCv มาใช้งานโดยสามารถดูได้ที่ตัวอย่าง https://www.baeldung.com/java-opencv โดยการที่เราจะใช้ opencv version ไหนนั้นขึ้นอยู่กับตัว lib ที่เรามาใช้ว่าเขาใช้ version ไหนโดยจากตัวอย่างเราใช้ lib ของ https://mvnrepository.com/artifact/org.openpnp/opencv โดยผมใช้ OpenCv version 4.5.1 ดังนั้นผมจึงต้องใช้ lib ที่ support

สรุป

สำหรับตอนนี้เราได้รู้วิธีการ Build Image OpenCv มาใช้งานแล้ว ซึ่งที่ผมมาเขียนตอนนี้เพราะตอนทำงานต้องใช้แล้วมันไม่มีคนทำเลย ใน Internet ก็ไม่มีคนเขียนอธิบายไว้เลย ก็เลยมาเขียนอธิบายเลยละกัน คนอื่นๆที่ติดปัญหาเหมือนกันจะได้ไม่ต้องเสียเวลา ค้นหาลองผิดลองถูกให้เสียเวลา

Ref

เพลงประกอบการเขียน Code

โดยส่วนตัวผมชอบ MV เพลงนี้มาก ผมชอบตรงความจริงของมัน มันไม่สวยงาม มันคือเรื่องจริงที่เราอาจได้เจอ แต่ผมนับถือใจตัวพระเอกนะที่มีวิธีจัดการกับเหตุการณ์แบบนี้ได้ดี

ข้อดีในเรื่องไม่ดี

ข้อดีในเรื่องไม่ดี

บอกก่อนเลยว่าเนื้อหาต่อจากนี้ไม่ใช่เรื่องที่ถูกต้อง หรือตรงใจ เป็นแค่มุมมองจากคนที่อาจจะไม่ค่อยมองโลกเหมือนคนอื่น อาจจะเรียกว่าคนโลกแคบหรือคนบ้า แล้วส่วนใหญ่ที่จะได้อ่านต่อจากนี้เป็นความคิดเห็นของผมเพียงคนเดียว ดังนั้นโปรดอย่าเอามันเป็นบรรทัดฐานตัดสินทุกอย่างบนโลก ตัดสินคน มันก็แค่ความเห็นจากคนที่มองอีกมุม ด้วยเส้นแบ่งคนละเส้น

โอเคถ้าคุณมาอ่านมาถึงตรงนี้แสดงว่าคุณคงมีเวลาว่างมาอ่านอ้วกทางความคิดของผม ผมอาจจะเป็นคนโรคจิตประเภทหนึ่งที่เวลาคิดอะไรหรือเจอเรื่องอัดอั้นอะไรจะต้องเขียนมันระบายออกมาและนี่ก็เป็นเรื่องหนึ่งที่ผมอยากจะระบายมันออกมาผ่านตัวอักษร เรื่องมันเริ่มที่ผมเล่น Facebook แล้วมีคนแชร์เรื่องผู้หญิงคนหนึ่งพูดข้อดีของโรคโควิท 2019 ว่าทำให้รู้ถึงคุณค่าสิ่งที่เสียไป ซึ่งผมก็แบบเออมันก็จริง แต่กระแสกลับเป็นไปต่อว่าผู้หญิงคนนั้นจนเขาต้องออกมาขอโทษ ผมนี่เฮ้ย มันใช่เหรอวะ คือถ้าออกมาบอกว่าเออมันมีเรื่องแย่แบบนั้นแบบนี้จากโควิท โอเคมันก็ไม่มีอะไรแต่กลายเป็นต่อว่าผู้หญิงคนนั้นว่า “คุณคิดถึงข้อดีของเรื่องโควิทได้ยังไง” บางคนรุนแรงถึงกับด่า จัดกลุ่มเขาว่าเป็นคนรวย ต่างๆนาๆ

ความจริง

ความจริงคือสิ่งที่เกิดขึ้นจริง เช่น ตอนเด็กๆโดนครูตี อันนี้คือความจริงครับว่าตอนเด็กโดนครูตี คราวนี้เรื่องมันเริ่มประหลาดเมื่อ คนนึงบอกว่า “ตอนเด็กโดนครูตีทุกวันนี้เลยได้ดี” แต่อีกคนพูดว่า “ตอนเด็กโดนครูตีวันนี้เลยกลายเป็นคนเกลียดโรงเรียน” แล้วความจริงคืออะไร ความจริงคือ ตอนเด็กๆโดนครูตี ครับ ส่วนข้อความที่ตามมาคือการตัดสินจากคนต่อความจริงที่เกิดขึ้นมาแล้วส่งผลกระทบต่อคนคนนั้นว่าเขาคิดว่าสิ่งนั้นทำให้เกิดข้อดีหรือข้อเสียอะไร

โรค Covid 19

ความจริงโรค Covid 19 คือโรคระบาดที่ทำให้เกิดคนตาย นั่นคือความจริงเกี่ยวกับ Covid 19 คราวนี้มาพูดถึงเรื่อง Work from home ที่เกิดจาก Covid 19 คนที่ Work from home ได้โดยมีอุปกรณ์สิ่งต่างๆพร้อมอยู่แล้ว เขาก็สามารถตัดสินให้มันเป็นข้อดีได้ เพราะมันสามารถลดค่าใช้จ่ายในการเดินทาง เวลาในการเดินทางได้ แต่สำหรับคนที่ต้องทำงานแบบ Work from home โดยที่ไม่มีอุปกรณ์เช่น ไม่มี Notebook ไม่มีอินเตอร์เน็ตที่บ้านนั่นย่อมเป็นข้อเสียใช่ไหมครับ โอเคนี่คือเรื่องเดียวกันแต่ต่างคนต่างมองมันเป็นข้อดีข้อเสีย

การที่มีโรค Covid 19 เราสามารถหาข้อดีข้อเสียของมันได้เยอะแยะครับ มันอยู่ที่คนคนนั้นจะมองได้ สำหรับผมมันมีข้อดีมากมายครับ

  • รถไม่ติด
  • ลดค่าเดินทางของผมลงได้เดือนละเกือบ 3,000 บาท
  • มีเวลาเพิ่มในการพักผ่อน ผมสามารถตื่น 8 โมง จากปกติตื่น 6 โมง
  • พิสูจน์ให้บริษัทเห็นว่าอาชีพโปรแกรมเมอร์สามารถทำงานแบบ Work from home ได้
  • มีเวลาอยู่กับครอบครัวมากขึ้น
  • ได้เห็นความล้มเหลวแบบไม่มีข้อแก้ตัวของรัฐบาล ซึ่งเป็นภาพที่ไม่ว่าคนกลุ่มไหนก็ไม่สามารถพูดได้ว่ามันดีแล้วได้อย่างเต็มปาก

แต่การที่คนคนนึงบอกข้อดีของความจริงหนึ่งมันไม่ได้แปลว่าเขาไม่เห็นเรื่องไม่ดีนะ เช่นผมที่บอกว่ามีข้อดีตั้งมากมาย ข้อเสียมันก็มี

  • การเกิด Covid 19 ทำให้บริษัทรายได้ลดลง ทำให้ไม่สามารถจ้างพนักงานเพิ่มได้ การปรับเงินเดือนของคนในบริษัทลดลง
  • บริษัทพยายามหาธุรกิจใหม่ เพื่อหารายได้มาแทนส่วนที่ขาดไปจาก Covid-19 ซึ่งนั่นทำให้งานเยอะขึ้น แต่พนักงานมีเท่าเดิมแปลว่า พนักงานทำงานหนักขึ้น
  • มีคนที่ไม่ควรตายจากโรคนี้เยอะมาก หลายคนไม่ควรตาย แต่ก็ต้องมาตาย เราสูญเสียทรัพยากรในการพัฒนาประเทศไปตั้งเท่าไหร่
  • คนตกงานไม่มีงานทำมากมาย เพราะ ร้านปิด
  • ปัญหาตกงานนำพาให้คนไม่มีหนทางมากและเลือกทางที่ผิดเช่น ขโมย ปล้น ต่างๆนาๆ

การพูดข้อดีของเรื่องไม่ดีมีมานานแล้ว

การพูดข้อดีของเรื่องไม่ดีนั้นมีมานานมาก ไม่ว่าจะเป็นคำคมต่างๆนาๆ เช่น พลิกวิกฤตให้เป็นโอกาส , เวลาเราตกต่ำเราจะเห็นคนที่จริงใจกับเราที่สุด ต่างๆนาๆ ผมจะลองยกเรื่องที่ผมเคยได้ยินมาเล่าให้ฟังครับ

  • คนไม่ดีก็มีข้อดีคือ การทำให้เรารู้ว่าสิ่งไม่ดีคืออะไร (อันนี้น่าจะปราชญ์ชาวจีนสักคน ไม่ขงจื๊อ ก็ เล่าจื๊อ ที่คนนับถือบูชา และประโยคนี้พบบ่อยมากในหนังสือ)

  • นิทานเกี่ยวกับต้นไม้ใหญ่ต้นหนึ่งที่ไม่มีคนตัดเพราะไม้มันห่วยแตกมาก เอาไปแปรรูปก็ไม่ได้เพราะเนื้อไม้มันห่วยแตก จะเอาไปฟืนก็ไม่ดีเพราะมันมีแต่ควัน แต่นักปราชญ์ท่านนึงกลับบอกว่านั่นคือข้อดีเพราะทำให้มันไม่ถูกตัด (ไปหาอ่านได้ ผมเห็นผ่านหนังสือนิทาน หนังสือพิมพ์ก็มีลง)

  • คนขายรองเท้าไปเจอหมู่บ้านที่ไม่มีคนขายรองเท้าเลย เขาหมดกำลังใจเพราะไม่มีใครขายรองเท้าเลย แต่เจ้านายเขากลับบอกว่านี่คือข้อดีเพราะจะได้ขายรองเท้าได้

  • เพลง ผ่าน ของ Slot machine (ในเวลาตกต่ำอาจเจอคนจริงใจ)

  • เพลง ความจนวัดใจ ของ ศิริพร (ชื่อเพลงก็บอกแล้ว)
  • โฆษณา 100 pipers (เรารู้ว่าใครคือเพื่อนแท้เมื่อคุณมีปัญหา)

การพูดทำนองนี้เกิดขึ้นบ่อยในประวัติศาสตร์มนุษย์ ถ้านับจากขงจื๊อกับเล่าจื๊อก็ 2600 ปี มาแล้ว สำหรับผมมันคือเรื่องปกติที่คนคนนึงพยายามหาเรื่องดีจากเรื่องไม่ดี มันหาได้เรื่อยๆแหละ

มาลองดูข้อดีจากเรื่องไม่ดีกัน

ตรงส่วนนี้จะมีความบ้าและการกระทบกระเทือนจิตใจคนปกติทั่วไปเยอะอยู่ ดังนั้นผมขอเตือนว่า ความจริงก็คือความจริง ส่วนต่อจากนี้คือการตัดสินจากการได้ประโยชน์หรือเสียประโยชน์

  1. ลูกค้าด่าว่าทำงานห่วย

    ถ้าคุณทำงานบริการคุณย่อมเจอเหตุการณ์นี้ ครับ มันคือเรื่องไม่ดีครับ เพราะเราน่าจะเสียลูกค้าท่านนั้น ไม่แน่เขาอาจไปบอกต่อทำให้เสียลูกค้าไปอีก แต่มันก็มีข้อดีนะครับ เพราะเราจะได้รู้ว่าการบริการของเรามันห่วยตรงไหน มันแจ้ง Error ให้ลูกค้าได้ไม่ดีพอ มัน Error บ่อย Response ช้า ซึ่งเมื่อเรารู้แล้วเราก็เอาสิ่งนี้ไปแก้ไข ให้ระบบดีขึ้น นี่นับว่าเป็นข้อดีจากการโดนลูกค้าด่าว่าทำงานห่วยไหมครับ

  2. อินเดียตกเป็นอาณานิคมของอังกฤษ

    การตกเป็นอาณานิคมของอีกประเทศย่อมไม่ดี แล้วการที่อังกฤษไปปกครองอินเดียก็มีเรื่องไม่ดีเกิดขึ้นมากมาย ก่อให้เกิดสงครามขึ้นด้วย แต่คุณรู้ไหมครับ อังกฤษออกกฏหมายห้ามพิธีสตีในอินเดีย (พิธีสตีคือเอาผู้หญิงที่สามีตายเข้ากองไฟตามสามีไปด้วยในงานศพสามี ไปหาอ่านได้ครับ) ในประเทศอินเดีย ทำให้ผู้หญิงจำนวนมากรอดตายจากการบังคับเข้าพิธีสตี นี่นับว่าเป็นข้อดีของการที่อินเดียตกเป็นอาณานิคมของอังกฤษไหมครับ

  3. สงคราม

    เราย่อมรู้ว่าสงครามก่อให้เกิดความสูญเสียมากมาย คนตาย สร้างความเกลียดชัง แต่คุณรู้ไหม สงครามทำให้ก่อเกิดนวัตกรรมมากมาย ไม่ว่าจะเป็นศาสตร์การเข้ารหัสลับจนเป็นจุดเริ่มต้นการส่งข้อมูลที่มีความปลอดภัย (สมัยจูเลียตซีซาร์ก็พัฒนา Caesar cipher ในการสารในการสงคราม) สงครามช่วยเร่งการพัฒนาการถนอมอาหารจนเกิดอาหารกระป๋องสำเร็จรูปให้เรากินมากมาย โครงการ ARPANET ที่พัฒนาจนกลายมาเป็นเครือข่าย Internet ที่ทำให้ผมกับคุณที่กำลังอ่านสามารถใช้ Internet ได้ ก็เกิดจากการต้องการสร้างเทคโนโลยีเพื่อใช้ในการสงคราม ทั้งหมดที่ว่ามานี้ถือว่าเป็นข้อดีจากสงครามไหมครับ

  4. การใช้ระเบิดนิวเคลียร์ที่ญี่ปุ่น

    การใช้ระเบิดนิวเคลียร์ที่ญี่ปุ่นทำให้คนตายมากมาย ซึ่งจริงๆเขาไม่สามารถทำก็สามารถชนะสงครามได้ แต่เหตุการณ์นั้นทำให้นักวิทยาศาสตร์ได้ข้อมูลเกี่ยวกับการระเบิดนั่นว่ามันรุนแรงเกินควบคุมแค่ไหน จนถึงเป็นข้อมูลประกอบการตัดสินใจในเรื่องโครงการอาวุธนิวเคลียร์ว่ามันมีผลร้ายแรงมาก ไม่ว่าจะความเสียหาย ฤดูหนาวนิวเคลียร์ ซึ่งอาจทำให้มนุษย์สูญพันธุ์ได้ จนเป็นส่วนช่วยให้โครงการอาวุธนิวเคลียร์ลดลง นี่นับว่าเป็นข้อดีที่เกิดจาก การใช้ระเบิดนิวเคลียร์ที่ญี่ปุ่น ไหมครับ

  5. เครื่องบินตก

    การที่เครื่องบินตกทำให้มีคนตายมากมายเป็นการสูญเสียที่ไม่ควรเกิด แต่คุณรู้ไหมครับ ทุกการที่มีการเกิดเครื่องบินตก ระบบการบินจะพัฒนามากขึ้นเพื่อไม่เกิดเหตุการณ์แบบนั้นอีก ผมเคยดูสารคดีเกี่ยวกับเครื่องบินตกที่เกิดจากไม่ได้ Lock ประตูเครื่องบินเนื่องจากพนักงานอ่านไม่ออก (เครื่องบินไม่ใช่ของประเทศอังกฤษมั้ง) ก็เลยไม่ได้ Lock ตามที่ป้ายบอก เขารู้ว่ามันมีสาเหตุจากสิ่งนั้น เบื้องต้นเขาก็แก้ปัญหาโดยการเพิ่มป้ายเป็นหลายๆภาษา มีแจ้งสัญญาณแจ้งเตือนนักบิน จนสุดท้ายกลายเป็น Lock แบบอัตโนมัติ การออกแบบเครื่องบินก็เกิดปัญหาเนื่องจากเรายังไม่รู้เรื่องความดัน การที่เครื่องบินตกเพราะเรื่องความดันทำให้เราทราบว่ามีเรื่องความดันที่ต้องพึงระวัง การออกแบบเครื่องบินก็จะป้องกันปัญหาเรื่องความดัน สิ่งนี้ทำให้การบินปลอดภัยมากขึ้น ถ้าเรามองแบบหยาบๆว่าทุกการตกของเครื่องบินนั้นช่วยให้ระบบการบินปลอดภัยมากขึ้น นี่นับเป็นข้อดีจากเครื่องบินตกไหมครับ

  6. การตายของสัตว์ทดลอง

    วัคซีน เซรุ่ม ยา เป็นสิ่งที่ต้องทดลองว่ามันมีผลดีแค่ไหนก่อนจะนำมาใช้งานจริง จึงต้องผ่านการทดลอง แล้วคุณจะทดลองกับอะไรล่ะครับ ทดลองกับมนุษย์เลยเหรอ ใช่ครับในขั้นแรกเขาย่อมทดลองกับสิ่งอื่นนั่นคือ สัตว์ทดลอง นั่นเอง (แต่เขามีจริยธรรมในการทดลองนะครับ แล้วรู้หรือไม่ครับก็ตัวนักวิทยาศาสตร์เองนี่แหละที่เป็นคนร่างกฏขึ้นมา เพื่อป้องกันความบ้าคลั่งในการทดลองโดยไม่สนใจจริยธรรม) การทดลองทำให้สัตว์ตายเป็นเบือ ตายมากมาย แต่เมื่อยาสำเร็จขึ้นมาแล้วมันสามารถนำไปช่วยคนได้มากมาย นี่นับว่าเป็นข้อดีที่ได้จากการใช้สัตว์ทดลองแล้วตายไหมครับ

  7. การฆ่าตัวตาย

    การฆ่าตัวตายเกิดขึ้นมากมาย ซึ่งนั่นมีผลเสียมากมาย เช่น ฆ่าตัวตายแล้วเอาคนอื่นตายตามไปด้วย ตายแล้วสร้างภาระให้กับสังคม เช่นกระโดดให้รถไฟชน ทำให้รถไฟดีเลย์ (ในญี่ปุ่นมีเยอะแยะ) ทำให้ทรัพยากรมนุษย์ที่จะใช้ในการพัฒนาโลกลดลงไปหนึ่งคน แต่การฆ่าตัวตายของเจ้าหน้าที่ป่าไม้ท่านหนึ่งก่อให้เกิดกระแสสังคม เป็นวาระแห่งชาติ เปิดโปงเรื่องต่างๆ นี่นับว่าเป็นข้อดีไหมครับ แล้วการที่คนหนึ่งคนตายไปนั่นแปลว่า ขยะ ที่จะเกิดจากการที่คนคนนึงสร้างใน 1 ชีวิตลดไปตั้งเท่าไหร่ (ผมจำไม่ได้ละว่ากี่ตัน ดูจากสารคดีช่อง Thai pbs) นี่นับว่าเป็นข้อดีจากการฆ่าตัวตายไหมครับ

  8. คนแถวบ้านผมตาย

    โอเคจากประโยคนี้คุณอาจจะมองว่ามันเป็นเรื่องไม่ดีครับ แต่มันมีข้อดีที่ผมได้รับจากคนแถวบ้านผมตายคือ มันเป็นไอเวรตัวนึงที่ขับรถเสียงดัง ก่อความเดือดร้อนให้คนในหมู่บ้าน การที่มันตายย่อมเป็น “ข้อดี” ของผมไหมครับ

แล้วมันยังไงล่ะ

ผมก็ไม่รู้ว่าที่ผมเขียนไปทั้งหมดมันแปลว่าอะไร ผมก็แค่อยากเขียนสิ่งที่อยากเขียน ข้อดีที่ผมเขียนเรื่องนี้คือผมได้ระบายเรื่องที่อัดแน่นในสมอง แต่ข้อเสียคือ คนที่มาอ่านอาจจะวิจารณ์ผม มองผมเป็นคนวิปลาส และไม่แน่เขาอาจจะส่งเมลล์มาด่าผม ตามตัวผม เอาผมไปแขวนแค่ผมเห็นข้อดีจากเรื่องไม่ดีก็ได้ สำหรับผมการเห็นทั้งข้อดีทั้งข้อเสียจากความจริงหนึ่งนั่นเป็นเรื่องที่ดี เพราะเราจะไม่อยู่ในโลกที่มืดมน หรือ โลกที่สดใส แต่เราจะอยู่ในโลกแห่งความจริง โลกที่ทุกอย่างมีข้อดี ข้อเสีย สังคมที่เราต้องการคือการสามารถออกมาพูดเรื่องที่คุณมองว่ามันดี หรือ ไม่ดี ได้อย่างเสรีและปลอดภัยไม่ใช่เหรอ ไม่ต้องกลัวว่าจะโดนรุมประนาม รุมต่อว่ามิใช่หรือ แต่อย่างว่านั่นคือโลกที่เราหวังไม่ใช่โลกแห่งความจริง

เพราะความหวังคือหน้ากากบนความสิ้นหวัง

Spring - Log Http Request , Response

Spring - Log Http Request , Response

ปัญหาหนึ่งที่มักเจอเสมอเวลาติดต่อกับ Thrid party คืออีกฝั่งตอบ Error กลับมา ซึ่ง Error ที่ตอบกลับมานั้นถ้าโชคดีหน่อยเขาจะบอกรายละเอียดแน่ชัดมาเลยว่าเป็นที่อะไร แต่ในกรณีที่โชคไม่ดีหน่อยเพราะ API ที่เขาทำนั้นเขาทำแบบเร่ง หรือ เขามั่นใจว่าคุณจะทำการส่งข้อมูลที่ถูกต้องให้เขาเสมอ เมื่อเกิดการ Error ขึ้นเขาก็แค่บอกว่า Error กลับไป ไม่ได้บอกเป็นที่อะไร ซึ่งด้วยผลลัพธ์แบบนี้บอกเลยว่า “หัวร้อน” แน่นอนครับ และเมื่อเขาเป็น Thrid party คุณไม่ใช่เจ้านายเขา คุณไม่มีอำนาจใดๆมากพอไปสั่งเขาได้ (ผมเคยเห็นคนมีอำนาจมากพอสั่งหน่วยงานอื่นได้นะ ผมนี่อย่างว้าวกับคนนั้นมากเลย) ดังนั้นวิธีการที่คุณพอจะทำได้คือ เอา Request , Response ที่ในรอบนี้เกิดปัญหานั้น ส่งให้ Third party เพื่อให้เขาบอกว่ามันเกิดปัญหาจากอะไร

Source code

source code ทั้งหมดที่ใช้เป็นตัวอย่างสามารถดูได้ที่ https://github.com/surapong-taotiamton/spring-log-request-response

Spring Http request

สมมุติผมทำ API ที่รับ Request param แล้วยิงต่อไปดึงข้อมูลต่อที่ https://jsonplaceholder.typicode.com/ เพื่อจำลองการติดต่อกับ Thrid party โดยถ้าเราเขียน Code โดยใช้ Spring เราสามารถเขียน Code เพื่อดึงข้อมูลได้ประมาณนี้

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
public class TestThirdPartyController {

private static final Logger logger = LoggerFactory.getLogger(TestThirdPartyController.class);

@GetMapping("/todo/{id}")
public ToDoDto getToDo(
@PathVariable("id") String id
) {
String url = "https://jsonplaceholder.typicode.com/todos/" + id;
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<ToDoDto> responseEntity = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, ToDoDto.class);
return responseEntity.getBody();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ToDoDtoClass

package blog.surapong.example.logrequestresponse.api.dto;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

@Getter
@Setter
@Accessors(chain = true)
@EqualsAndHashCode
@ToString
public class ToDoDto {
private Long userId;
private Long id;
private String title;
private Boolean completed;
}

โดย Code ไม่มีอะไรทำมากไปกว่าคือรับ Id จาก Path variable จากนั้นยิงต่อไปที่ https://jsonplaceholder.typicode.com/todos/ เพื่อเอา Response กลับมา ซึ่งเมื่อทดลองผ่านหน้าจอจะได้ดังภาพ

โอเคคราวนี้ถ้าเราใส่ id เป็น 3000 เราจะได้ Error ดังภาพ

คราวนี้ก็จะเกิด Error ขึ้นครับ คราวนี้ถ้าเราอยากจะทราบว่าเราส่ง Request Response อะไรไปให้ Third party บ้างเราก็ต้องเขียน Code เพิ่มขึ้นมาครับ ซึ่งมีคนแนะนำไว้แล้วซึ่งตามไปอ่านได้ที่ : https://www.baeldung.com/spring-resttemplate-logging เขาอธิบายวิธีการไว้หมดแล้ว

อ้าวแล้วจะเขียนทำไมวะ จริงๆคือด้วยความขี้เกียจของผมคือ ไม่อยากเขียนเองเลยไปหาต่อว่ามีใครเขียนอะไรแบบนี้ไว้แล้วบ้าง ซึ่งก็เจอครับ ซึ่งนั่นก็คือ zalando-logbook

ใช้ Zalando-Logbook ทำ Log Request Response

เพิ่ม Dependency ลงใน Pom

เพิ่ม Dependency ลงใน pom นะครับ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- Log http request response -->

<dependencies>

<!-- Another lib -->

<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-starter</artifactId>
<version>2.6.2</version>
</dependency>

<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-core</artifactId>
<version>2.6.2</version>
</dependency>

<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-httpclient</artifactId>
<version>2.6.2</version>
</dependency>

</dependencies>

เพิ่ม Config ใน application.properties

เพิ่มค่า config เข้าไปใน application.properties ดังค่าด้านล่าง

1
logging.level.org.zalando.logbook=TRACE

ทำการแก้ไขตรงส่วน Call Http requeset ไปที่ Third party

ในการเรียก Http request ไปหา Thrid party นั้นเราจะใช้ RestTemplate ของ Spring ซึ่งมันสามารถสร้าง Interceptor เข้าไปใน RestTemplate เพื่อ Log Request Response ได้ โดยถ้าอ่านตัวอย่างจาก baeldung เขาจะให้เรา Implement เอง แต่ของผมนั้นจะไปใช้ Lib ของ zalando ซึ่งทำให้การสร้าง RestTemplate ของผมจึงแยกมาเป็น Private method ดังด้านล่าง เพราะทุกครั้งที่ทำการ Config RestTemplate แบบนี้ทุกครั้ง เลยแยกออกมาเลยเพื่อจะได้ใช้ได้หลายที่

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private RestTemplate createRestTemplate() {


// ตรงนี้สร้างตัว Client request factory ให้ RestTemplate โดยสร้างเป็นแบบ Buffering เพื่อให้สามารถเข้าไปอ่าน
// Request Response ได้หลายรอบ ไม่ติดปัญาเรื่อง Stream ที่อ่านได้รอบเดียว

SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
simpleClientHttpRequestFactory.setOutputStreaming(false);

ClientHttpRequestFactory factory =
new BufferingClientHttpRequestFactory(simpleClientHttpRequestFactory);

RestTemplate restTemplate = new RestTemplate(factory);

// ตรงนี้เป็นการสร้าง Interceptor เพื่ออ่านค่า Request Response ซึ่งเราใช้ตัวของ
// Zalando log book

Logbook logbook = Logbook.create();
LogbookClientHttpRequestInterceptor interceptor = new LogbookClientHttpRequestInterceptor(logbook);

restTemplate.getInterceptors().add(interceptor);
return restTemplate;
}

อันนี้ส่วนที่เรียกใช้งาน โดยขอเพิ่มอีก Endpoint นึงเลยเพื่อง่ายในการดูความแตกต่าง

1
2
3
4
5
6
7
8
9
@GetMapping("/todo/log/{id}")
public ToDoDto getToDoLog(
@PathVariable("id") String id
) {
String url = "https://jsonplaceholder.typicode.com/todos/" + id;
RestTemplate restTemplate = createRestTemplate();
ResponseEntity<ToDoDto> responseEntity = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, ToDoDto.class);
return responseEntity.getBody();
}

มาดูผลลัพธ์กัน

เราลองเรียกใช้งาน API ผ่าน http://127.0.0.1:8080/todo/log/1 จะได้ผลลัพธ์ดังภาพ

คราวนี้ลองไปดูใน Console log ของ Server จะได้

ในภาพอาจจะเห็นไม่เต็มมาลองดู Log เต็มๆ จะได้ดังภาพ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# ส่วนที่ยิงออกไปหา Thrid party
2564-05-16 14:05:18.680 TRACE 14560 --- [nio-8080-exec-1] org.zalando.logbook.Logbook : Outgoing Request: adf2666b770005ba
Remote: localhost
GET https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
Accept: application/json, application/*+json
Content-Length: 0

# ส่วนที่ได้ตอบกลับจาก Thrid party
2564-05-16 14:05:19.051 TRACE 14560 --- [nio-8080-exec-1] org.zalando.logbook.Logbook : Incoming Response: adf2666b770005ba
Duration: 366 ms
HTTP/1.1 200 OK
Accept-Ranges: bytes
Access-Control-Allow-Credentials: true
Age: 24883
alt-svc: h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400
Cache-Control: max-age=43200
CF-Cache-Status: HIT
CF-RAY: 6502c1ca8e36cbe4-SIN
cf-request-id: 0a159772930000cbe44012e000000001
Connection: keep-alive
Content-Length: 83
Content-Type: application/json; charset=utf-8
Date: Sun, 16 May 2021 07:05:19 GMT
Etag: W/"53-hfEnumeNh6YirfjyjaujcOPPT+s"
Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Expires: -1
NEL: {"report_to":"cf-nel","max_age":604800}
Pragma: no-cache
Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report?s=j1GrG6LPL61zeVNMq1b%2BvOiB%2FQgsoj9XZObn4GuEwHvo6tneWSaWQiD25XJildqvBj0eKheWN65tswB9%2Bw0UFhEDteZ4dqeSQJMZXDOyb4QfecfHQz1rihuTmQVd"}],"group":"cf-nel","max_age":604800}
Server: cloudflare
Vary: Origin, Accept-Encoding
Via: 1.1 vegur
X-Content-Type-Options: nosniff
X-Powered-By: Express
X-Ratelimit-Limit: 1000
X-Ratelimit-Remaining: 999
X-Ratelimit-Reset: 1620749436

{"userId":1,"id":1,"title":"delectus aut autem","completed":false}

ลองในกรณี Error กันบ้าง

คราวนี้มาลอง Test กรณี Error กันบ้าง โดยยิงไปที่ : http://127.0.0.1:8080/todo/log/3000

ซึ่งเมื่อไปดูใน Log แล้วจะได้ข้อความด้านล่าง ซึ่งเราจะเห็น Request Response โดยส่วน Response นั้นตอบกลับมาเป็น 404

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# ส่วนที่ยิงออกไปหา Thrid party
2564-05-16 14:15:26.734 TRACE 14560 --- [nio-8080-exec-4] org.zalando.logbook.Logbook : Outgoing Request: ca08db405127d62b
Remote: localhost
GET https://jsonplaceholder.typicode.com/todos/3000 HTTP/1.1
Accept: application/json, application/*+json
Content-Length: 0

# ส่วนที่ได้ตอบกลับจาก Thrid party
2564-05-16 14:15:26.943 TRACE 14560 --- [nio-8080-exec-4] org.zalando.logbook.Logbook : Incoming Response: ca08db405127d62b
Duration: 207 ms
HTTP/1.1 404 Not Found
Access-Control-Allow-Credentials: true
Age: 4122
alt-svc: h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400
Cache-Control: max-age=43200
CF-Cache-Status: HIT
CF-RAY: 6502d0a1f9dc5653-SIN
cf-request-id: 0a15a0b93b00005653f01d9000000001
Connection: keep-alive
Content-Length: 2
Content-Type: application/json; charset=utf-8
Date: Sun, 16 May 2021 07:15:27 GMT
Etag: W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8"
Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Expires: -1
NEL: {"report_to":"cf-nel","max_age":604800}
Pragma: no-cache
Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report?s=Pe3fXWnj8LWKvbjsXIXnl4%2Fqz%2FXwCBt4qn7Nfhl9USShQUIRP0%2FCtDXkbKD0kV1uGdx56xFCCaCb0sDspaOm6uJi%2BYeM%2FmXbiBUkeY6E6Uh72Z7tLg8M%2FvF0CCBq"}],"group":"cf-nel","max_age":604800}
Server: cloudflare
Vary: Origin, Accept-Encoding
Via: 1.1 vegur
X-Content-Type-Options: nosniff
X-Powered-By: Express
X-Ratelimit-Limit: 1000
X-Ratelimit-Remaining: 999
X-Ratelimit-Reset: 1621145265

{}

สรุป

สำหรับตอนนี้เราก็ได้วิธีการ Log Http Request Response เวลายิงไป Third party ไปแล้วนะครับ โดยในตัวอย่างของผมยิงเป็น Method GET ฝั่งที่ยิงออกไปเลยไม่ค่อยตัว Body แต่หากคุณลองไปยิงด้วย Method อื่น เช่น PUT , POST คุณจะเห็นตัว Body ของ Http Request ด้วยครับ โดยในส่วนของ Logbook นั้นสามารถกำหนด Format ของ Log ที่ออกมาได้ และสามารถเลือกได้ว่าจะเอาสส่วนไหนของทั้ง Request และ Response มาแสดงได้ โดยสามารถอ่าน Config ได้ที่ https://github.com/zalando/logbook ครับ

เพลงประกอบการเขียน Code

เพลงเก่าแต่ความหมายเพลงไม่เคยเก่า พี่มอสพี่แคทนี่เขาเป็นอมตะกันเหรอครับ หน้าตายังกะสตาฟไว้

โลกที่เต็มไปด้วยปิศาจ - The Demon-Haunted World

โลกที่เต็มไปด้วยปิศาจ

โลกที่เต็มไปด้วยปิศาจ - The Demon-Haunted World

หนังสือเล่มนี้ซื้อมาอ่านเพราะ Page : Tactschool แนะนำว่าเป็นหนังสือที่ไม่เกี่ยวกับการลงทุนที่น่าอ่าน ก็เลยซื้อมาอ่านดูว่ามันจะดีจริงหรือเปล่า ซึ่งพอได้อ่านแล้วรู้สึกชอบมากๆ ประมาณว่าอยากให้เพื่อนๆหรือคนในครอบครัวอ่านมาก เพราะมันเป็นการเปิดหูเปิดตา ทำลายความงมงาย เข้าใจวิทยาศาสตร์ทั้งด้านดีและไม่ดีของมัน

ปิศาจ

ชื่อหนังสือคือ โลกที่เต็มไปด้วยปิศาจ ซึ่งครั้งแรกที่เห็นก็นึกว่าเป็นหนังสือนิยายอะไรสักอย่าง (แถมเล่มหนาโคตรๆ หนาแบบตีหัวแตกอะ) พอได้อ่านก็เข้าใจว่าทำไมถึงใช้ชื่อนี้ เพราะปิศาจคือความบ้าบอต่างๆบนโลก ไม่ว่าจะความเชื่องมงายของลัทธิแม่มด มนุษย์ต่างดาว การโฆษณาเกินจริง ความบ้าบอของยุคล่าแม่มด คนทรงสื่อสารกับคนตาย ทฤษฎีสมคบคิดที่หาความจริงไม่ได้ และอื่นๆอีกมากมาย ซึ่งเราพบเห็นกันได้ทุกวัน ปิศาจเหล่านี้มีเต็มไปหมด แล้วเราจะเอาชนะปิศาจเหล่านั้นได้อย่างไรล่ะ คำตอบก็มีอยู่แล้วครับนั่นคือ วิทยาศาสตร์ ครับ

วิทยาศาสตร์กลายเป็นเรื่องยาก

ทุกวิชาบนโลกมีความยากความง่าย แต่วิชาจำพวกคณิตศาสตร์และวิทยาศาสตร์กลับกลายเป็นตัวร้ายในเรื่องการเรียน เป็นเรื่องยาก เด็กๆพากันเกลียด ซึ่งผมก็เป็นหนึ่งในนั้นในวิชาฟิสิกส์ เคมี จริงๆมันอาจจะเพราะวิธีการสอนที่ไม่ใช่การสอนให้เข้าใจ แต่เป็นการสอนให้ทำโจทย์ แก้สมการ พอคนเรียนไม่เข้าใจ ไม่รู้ว่าทำไปเพราะอะไร ความสนใจมันก็ลดน้อยลง จนสุดท้ายก็กลายเป็นเกลียด แต่ผมยังจำความสนุกในการเรียนวิทยาศาสตร์ตอน ม.ต้น การทดลอง ใส่ตัวแปรต้น ตัวแปรตาม ตัวแปรควบคุม ทดลอง พิสูจน์ว่ามันเป็นแบบที่คิดไหม การทดลองมันสนุกเพราะมันได้ทำแล้วหาคำตอบ จะล้มเหลว จะเละ ก็ไม่เป็นไร ความสนุกมันอยู่ตรงนี้ แต่พอไปอยู่ ม.ปลาย ทดลองก็ไม่ได้ทดลอง อยู่ดีๆก็นั่งท่องสูตร ปัญหาไม่ใช่มีแค่ที่ไทยครับ ที่ประเทศที่เราคิดว่าโคตรเจริญซึ่งก็คือ สหรัฐอเมริกาก็เจอ แถมปีที่คนเขียนเขาเขียนก็น่าจะช่วงปีเกือบๆ 2000 หรือ ต้น 2000 ซึ่งนั่นก็แปลว่าสหรัฐก็เจอปัญหาเกี่ยวกับการสอนวิทยาศาสตร์เหมือนกัน หนังสือว่าได้เจ็บแสบจริงๆถึง ครูวิทยาศาสตร์ที่ไม่เข้าใจวิทยาศาสตร์ มาสอนแบบท่องสูตร ให้ทำโจทย์หาผิดหาถูก ต่างๆนาๆ ไม่ได้สร้างแรงบันดาลใจให้เด็กอยากศึกษาวิทยาศาสตร์เลย ตอนอ่านนี่แบบ โอ้ ปัญหาเดียวกันเลยว่ะ

วิทยาศาสตร์ง่ายกว่าที่เราเข้าใจ

ผู้เขียนนั้นได้เล่าเกี่ยวกับวิทยาศาสตร์ได้อย่างน่าสนใจ เขาเล่าถึงชนเผ่าที่ห่างไกลความเจริญ ตอนอ่านคนส่วนใหญ่คงคิดว่าเกี่ยวอะไรกับวิทยาศาสตร์วะ แต่จริงๆมันเกี่ยวมากครับ ชนเผ่าเหล่านั้นเขามีวิทยาศาสตร์ของเขา การล่าสัตว์ของเขา การสะกดรอย ดูดินฟ้าอากาศ ต่างๆนาๆ มันเป็นวิทยาศาสตร์ การลองผิดลองถูก จดบันทึกว่าทำแบบนี้แล้วได้อะไร เกิดอะไร วิทยาศาสตร์เป็นเรื่องใกล้ตัวมากๆ เราไปมองว่าวิทยาศาสตร์จะต้องอยู่ในรูปของ คน IQ สูงๆ นั่งในห้องแคบๆ วิทยาศาสตร์อยู่ในชีวิตประจำวันของเรามากกว่าที่คิด

วิทยาศาสตร์อาจตอบคำถามเกี่ยวกับพระเจ้าได้

ถ้าเคยอ่านหนังสือปรัชญา ยุคกลางเคยมีการบอกว่าทุกสิ่งที่อย่างบนโลกนี้นั้นซับซ้อนเพราะพระเจ้านั้นสร้างทุกสิ่งขึ้นมาอย่างปรานีต ดังนั้นวิทยาศาสตร์ในยุคนั้นที่พยายามย่นย่อสิ่งต่างๆให้อยู่ในสมการ หรือ กฏต่างๆนั้นดูจะเป็นการดูหมิ่นพระเจ้าเสียเหลือเกิน แต่ความจริงก็คือความจริง วิทยาศาสตร์สามารถตอบคำถามได้ว่า เราสามารถสรุปแรงโน้มถ่วงของโลกให้อยู่ในสูตร อยู่ในสมการ เราสามารถพิสูจน์ได้ว่าสิ่งมีชีวิตเกิดจากการเรียงตัวของเซลล์ซึ่งเป็นหน่วยย่อย การที่มนุษย์เป็นใหญ่หาใช่พระเจ้ากำหนดแต่เป็นทฤษฎีวิวัฒนาการและความบังเอิญก็เท่านั้น (เรื่องทฤษฎีวิวฒนาการนี่สามารถลบล้างการมีอยู่ของพระเจ้าได้เลยในยุคนั้น เล่นเอา ดาวิน เกือบตายเลยมั้ง) อ้าวแล้วมันตอบคำถามของการมีอยู่ของพระเจ้าได้ยังไงล่ะ นักวิทยาศาสตร์เชื่อว่ากฏทุกอย่างนั้นสามารถย่นย่อให้เหลือเพียงสมการเดียวได้ ถ้าเราสามารถสรุปมันเหลือสมการเดียวได้ นั่นแหละ สมการนั้นแหละคือตัวตนของพระเจ้า พระเจ้าผู้สร้างกฏทุกอย่างไม่ว่าจะแรงดึงดูด ความร้อน แสง ซึ่งนั่นก็เหมือนจะตรงกับคำสอนศาสนาด้วยนะว่า พระเจ้าทรงมีเพียงหนึ่ง สมการที่ตอบทุกอย่างก็มีเพียงหนึ่ง อันนี้ผมเข้าใจว่าคนเขียนเขากำลังบอกว่า วิทยาศาสตร์อาจใช้เป็นตัวยืนยันการมีอยู่ของพระเจ้าให้คนที่เคารพบูชาศาสนาทราบ และเห็นว่า วิทยาศาสตร์ไม่ได้ทำลายศาสนา แต่เป็นกำลังพิสูจน์เรื่องพระเจ้าให้

มนุษย์ต่างดาว ร่างทรงติดต่อคนตาย และการพิสูจน์

หากคุยได้อ่านหนังสือเล่มนี้คุณอาจเปลี่ยนความเชื่อเรื่องมนุษย์ต่างดาว คนทรง ไปเลยก็ได้ ผู้เขียนหนังสือเล่มนี้เอาข้อมูลเกี่ยวกับมนุษย์ต่างดาว เหตุผลต่างๆมาให้อ่านกัน เช่น หากมนุษย์ต่างดาวเทพจริงและสื่อสารผ่านคนที่อ้างว่าติดต่อมนุษย์ต่างดาวได้ ก็ลองให้เขาลองแก้โจทย์ของ ทฤษฎีบทสุดท้ายของแฟร์มา แต่พวกมนุษย์ต่างดาวกลับตอบได้ว่ามาเป็นมิตร ต่างๆนาๆ เฮ้ย คุณคิดดูสิ สิ่งมีชีวิตระดับโคตรมีภูมิปัญญา น่าจะตอบโจทย์ของแฟร์มาได้สิ (มีนักคณิตศาสตร์ที่เป็นมนุษย์โลกพิสูจน์ได้แล้วนะครับ ดังนั้นมนุษย์ต่างดาวน่าจะตอบได้มากกว่า) หรือทำไมการบอกเล่าว่าโดนมนุษย์ต่างดาวจับตัวไปจะต้องโดนจับแก้ผ้า แล้ว ทดลอง แล้วปล่อยตัว มนุษย์ต่างดาวที่มีเทคโนโลยีล้ำสมัยกว่าเราจำเป็นต้องจับเราแก้ผ้าด้วยหรือ ทำไมเขาไม่ใช้เครื่องที่ไฮเทคดึง DNA ของเราไป Cloning เอาล่ะ (เรายังโคลนนิ่งแกะได้เลย) แล้วคิดแบบเป็นเหตุเป็นผลเขาจะปล่อยคนที่โดนจับไปให้กลับมาป่าวประกาศให้ชาวโลกทราบว่ามีมนุษย์ต่างดาว หรือในกรณีร่างทรงติดต่อคนตาย ทำไมเวลาเราติดต่อคนตายได้ ทำไมคนตายไม่สามารถบอกได้ว่าใครฆ่าในกรณีฆาตกรรม หรือไม่สามารถบอกรายละเอียดที่เป็นความลับเกี่ยวกับคดีที่ไม่เผยแพร่ไปในที่สาธารณะได้เลยล่ะ หรือ มีร่างทรงของคนยุคโบราณ ทำไมคนเหล่านั้นไม่สามารถบอกได้เลยว่า ในยุคนั้นเขาติดต่อกันอย่างไร สภาพสังคมในยุคนั้น การเมือง เศรษฐกิจ ความเชื่อในยุคนั้น ละแวกนั้นเป็นอย่างไร ไม่มีคำตอบจากปากร่างทรง (วิญญาณที่สิงอยู่ในขณะนั้น) เลย ทำไมล่ะ

วิทยาศาสตร์คือสิ่งที่พิสูจน์ตลอดเวลา

ผู้เขียนพูดถึงวิธีการทางวิทยาศาสตร์ว่าทำงานยังไง ตั้งทฤษฎีด้วยความรู้ที่มีอยู่ตอนนั้น พิสูจน์ทำไมมันถึงเป็นอย่างนั้น จากนั้นก็จะมีคนทำลายทฤษฎีนั้นด้วยวิธีต่างๆ หากไม่มีใครทำได้เราก็จะถือว่าทฤษฎีนั้นเป็นจริง แต่เมื่อเวลาเปลี่ยนไปด้วยความรู้ที่มากขึ้นจนสามารถพิสูจน์ว่าทฤษฎีนั้นไม่ถูกต้อง ทฤษฎีนั้นก็จะถูกปรับปรุงให้ดีขึ้นถูกต้องขึ้นเรื่อยๆ วิธีการของวิทยาศาสตร์เป็นไปด้วยวิธีการแบบนี้มันเลยพัฒนาไปตลอด ชาววิทยาศาสตร์พร้อมที่จะออกมายอมรับว่าทฤษฎีนั้นผิด และพร้อมที่จะเปลี่ยนแปลงให้ถูกต้องเสมอ กรณีที่ยกตัวอย่างง่ายๆคืออายุของโลก ตอนแรกที่เรายังไม่มีความรู้เรื่องธรณีวิทยามากพอ เราประมาณอายุของโลกด้วยการคำนวณว่าสารชนิดหนึ่ง (จำไม่ได้) ใช้เวลานานเท่าไหร่กว่าจะเย็นตัว ซึ่งได้เวลาประมาณ 2 หมื่นปี แต่เมื่อเวลาผ่านไปเรามีความรู้เกี่ยวกับธรณีวิทยามากขึ้น อายุของโลกก็ถูกเปลี่ยนไปเป็นแสนเป็นล้านปีไป นี่คือข้อดีของวิทยาศาสตร์แตกต่างกับศาสนาที่ยากมากจะยอมถูกตรวจสอบไม่ว่าจะเรื่องของพระเจ้า พระเยซู แค่เรื่องที่วิทยาศาสตร์สามารถพิสูจน์ได้ว่าโลกมีอายุนานกว่าที่ระบุไว้ในไบเบิ้ล แค่นี้ก็เป็นเรื่องเป็นราวใหญ่โตแล้ว (ลองคิดดูว่าถ้าตัวเองเป็นนักวิทยาศาสตร์ในยุคกลางจะน่ากลัวขนาดไหน)

สรุป

โดยรวมแล้วสำหรับผมหนังสือเล่มนี้ดีมากเพราะเล่าเรื่องราวที่ไม่เคยรู้มาก่อน ไม่ว่าจะเป็นเรื่องคลั่งเรื่องมนุษย์ต่างดาว การล่าแม่มด (อันนี้พีคของพีคไปหาอ่านดูแล้วคุณจะขอบคุณวิทยาศาสตร์) วิทยาศาสตร์ในแง่มุมต่างๆ ทั้งด้านดีงามและการนำไปใช้ที่ผิดวิธี การต่อสู้ของนักวิทยาศาสตร์ ประชาธิปไตยและวิทยาศาสตร์เกี่ยวกันได้อย่างไร และสุดท้ายวิทยาศาสตร์คือคบเพลิงไว้ต่อสู้กับโลกที่เต็มไปด้วยปิศาจนี้ ในส่วนของข้อเสียคงจะเป็นเรื่องการแปลที่ต้องสำนวนแปลกๆทำให้ต้องอ่านซ้ำหลายรอบ ซึ่งก็พอเข้าใจเพราะน่าจะเป็นศัพท์เก่าระดับหนึ่ง แถมคนเขียนก็คงจะใช้ศัพท์สูงในระดับหนึ่ง แต่โดยรวมแล้วถ้าใครพอมีเงิน 395 บาท แล้วอยากรู้เกี่ยวกับวิทยาศาสตร์ก็ลองดูไปซื้อมาอ่านดูครับรับรองคุ้มเงินแน่นอน

เพลงประกอบการเขียน Blog

เพลงในความทรงจำวัยเด็กกับการเล่น Rockman X3 ปัจจุบัน Rockman X ยังค้างอยู่ที่ภาค 8 อยู่เลย

Designing Your Work Life - คู่มือออกแบบชีวิตที่ใช่ - งานที่ชอบ

Designing Your Work Life - คู่มือออกแบบชีวิตที่ใช่ - งานที่ชอบ

Designing Your Work Life - คู่มือออกแบบชีวิตที่ใช่ - งานที่ชอบ

สำหรับหนังสือเล่มนี้ก็เป็นเหมือนหนังสือที่ชื่อ Designing Your Life คือไม่ได้ตั้งใจซื้อที่ซื้อเพราะต้องการให้ได้ส่งฟรี ซึ่งเมื่อได้อ่านเล่มแรกแล้วรู้สึกว่าเออได้เปิดมุมมองใหม่ๆ วิธีการเปลี่ยนมุมมอง เปลี่ยนกรอบการตั้งปัญหา การออกแบบชีวิต ก็เลยไม่รอช้าหยิบเล่มที่สองมาอ่าน ซึ่งเรื่องนี้ก็น่าสนใจเพราะเกี่ยวกับเรื่องงานที่ทำอยู่

หนังสือไม่ได้ว่าด้วยการลาออก

ปกติถ้าคุณเห็นหนังสือเกี่ยวกับงานในฝัน ผมว่าหลายเล่มจะบอกให้คุณลาออกจากงานไปทำตามที่ฝันเลยซึ่งเป็นการแก้ปัญหารูปแบบหนึ่ง แต่หนังสือเล่มนี้มาแปลกครับ เขาไม่ได้พูดเรื่องการลาออกจากงานแบบทันทีทันใด แต่มีขั้นตอนที่น่าสนใจเกี่ยวกับการมองงานในปัจจุบัน แต่ก็ไม่ต้องกลัวว่าจะไม่มีเรื่องการลาออกครับ หนังสือเล่มนี้พูดถึงเรื่องการลาออก วิธีการลาออกในรูปแบบต่างๆ ซึ่งอ่านแล้วทำเอาผมเปลี่ยนมุมมองเกี่ยวกับการลาออกไปเลย

เมื่อไหร่จะถึง

เป็นชื่อบทแรกๆของหนังสือ ตอนแรกผมก็งงๆว่าทำไมถึงใช้คำนี้ แต่พอเริ่มอ่านผมรู้สึกชอบการยกตัวอย่างแบบนี้มาก เมื่อไหร่จะถึงนั้นเป็นคำที่เวลาเราเดินทางไปเที่ยวไหนเรามักจะชอบพูด เหมือนกับว่าเมื่อไปถึงที่หมายแล้วเราจะพบความสุขที่สุขโคตรๆที่รออยู่ ซึ่งผมก็พอเห็นภาพ แต่คำถามที่ถามกลับมาคือ ทำไมตอนนี้คุณหาความสุขกับเวลาปัจจุบันไม่ได้ล่ะ ทำไมต้องถึงตรงนั้นก่อน ถึงตรงนี้ก่อน มันเหมือนกับอดเปรี้ยวไว้กินหวานอะ มันก็จริงที่เราอดเปรี้ยวจากการกินมะม่วงลูกนี้เพื่อรอมันกินหวานได้ แต่เราจำเป็นไอมะม่วงลูกเดียวเท่านั้นหรอ เราไปหาอย่างอื่นๆหวานๆกินไม่ได้เหรอ เช่นกัน ระหว่างเดินทางคุณหาความสุขระหว่างเดินทางไม่ได้เหรอ คุณอาจจะคุยกับเพื่อนร่วมทาง นั่งดู Netflix ก็ได้นี่ ความสุขจึงไม่จำเป็นต้องถึงตรงจุดหมายก็ได้ จริงๆถ้าคุณตีความใหญ่กว่านี้ ทำไมคุณถึงรอตายถึงจะไปมีความสุขกับชาติหน้า หรือบนสวรรค์ ทำไมตอนนี้คุณไม่หาความสุขล่ะ คุณจำเป็นต้องอดข้าวเย็นเพื่อให้มีความสุขเหรอ หรือคุณสามารถหาความสุขอื่นๆได้

อะไรที่คุณค่าให้ความหมาย

เช่นเดิมกับเล่มที่แล้ว เล่มนี้ยังพูดเรื่องนี้เหมือนเดิมคือ อะไรที่คุณให้ค่าและให้ความหมาย หนังสือบอกเราเขียนว่า ชีวิตคืออะไรในนิยามของคุณ อะไรที่ทำให้คุณรู้สึกมีความหมาย มีความสุข เช่นกัน หนังสือก็ให้เขียนว่างานของคุณคืออะไรที่ไม่ใช่การทำงานอะไร (แบบเป็นโปรแกรมเมอร์ เป็นนักกฏหมาย) แต่งานแบบไหนที่คุณอยากทำ อะไรที่ทำให้คุณว่างานมีความหมาย มีคุณค่า มีความสุขกับมัน (ทำในสิ่งใหม่ , ทำแล้วช่วยแก้ปัญหาให้คนรุ่นหลัง) ซึ่งเมื่อเราเขียนสิ่งเหล่านี้ขึ้นมาแล้ว เราจะได้เข็มทิศในชีวิตให้เราเห็นว่าจริงๆเราต้องการอะไร ซึ่งบางคนเข็มทิศชีวิตคือการใช้เวลากับครอบครัว ดังนั้นถ้าเขาอยากมีความสุขเขาควรจะต้องหางานที่ทำให้เขามีโอกาสใช้เวลากับครอบครัวมากขึ้นเป็นต้น

เปลี่ยนมุมมองเกี่ยวกับงาน

บางครั้งการลาออกอาจเป็นความคิดแรกที่ทำให้คุณลาออก แต่หนังสือบอกให้คุณลองปรับเปลี่ยนมุมมอง หนังสือยกตัวอย่างสถานการณ์สุดเลวร้ายที่ผู้จัดการคนใหม่ที่พึ่งมาพบว่างานที่เขากำลังจะต้องทำนั้นต้องเจอกับความวุ่นวายจากผู้บริหารที่สุดแสนจะเลวร้าย ระบบงานที่ยอดแย่ ซึ่งคุณก็อาจจะอยากลาออกเลย แต่ถ้าเขาลาออกในประวัติงานของเขาก็น่าจะมีปัญหาเพราะไปทำงานไม่ถึงเดือนแล้วลาออก การไปที่ใหม่จะลำบากขึ้นกว่าเดิม ในสถานการณ์แบบนี้คงจะทำให้ปวดหัว แต่หนังสือให้เล่าถึงการเปลี่ยนมุมมองของผู้จัดการคนนี้ ผู้จัดการคนนี้เริ่มหาว่าข้อดีของการทำงานที่นี่คืออะไร เขาพบว่าการทำงานที่นี่ได้เรียนรู้อะไรใหม่ๆ และได้ไปดีลงานกับแผนกบัญชีซึ่งเป็นความรู้ใหม่ ผู้จัดการจึงเปลี่ยนมุมมองความคิดเป็น เขาจะเรียนรู้อะไรได้จากที่นี่ เขาจะรู้อะไรเกี่ยวกับงานบัญชีได้เพิ่มขึ้นได้บ้าง ซึ่งจากการเปลี่ยนมุมมองความคิดนี้ทำให้เขาสามารถทำงานอยู่บริษัทนี้ได้อีก 2 ปีและลาออกอย่างสวยๆพร้อมมีความรู้เกี่ยวกับงานสายบัญชีและ Connection กับคนในสายบัญชีด้วย ซึ่งจริงหนังสือพูดถึงอีกหลายเรื่อง แถมมีกิจกรรมให้ทำหลายตัวด้วย

การเมืองในที่ทำงาน

บทนี้ก็ทำผมว้าวเหมือนกัน การเมืองในที่ทำงานนั้นเป็นเรื่องที่ผมเกลียดมาก ถ้ามีการพูดเรื่องพวกนี้ผมจะตัดการรับรู้และไปทำอย่างอื่นทันที แต่พอมาอ่านดันได้พบความรู้ใหม่ที่น่าสนใจ หนังสือแยก อำนาจตัดสินใจ กับ อิทธิพล ออกจากกัน อำนาจตัดสินใจคือคนที่มีสิทธิ์ในการอนุมัติทำบางอย่าง ส่วนคนที่มีอิทธิพลคือ คนที่มีผลทำให้คนที่มีอำนาจตัดสินใจเลือก หรือ ไม่เลือกวิธีการใดวิธีการหนึ่ง ถ้ายกตัวอย่างง่ายๆ คนมีอำนาจตัดสินใจคือคนที่หัวหน้าแผนกที่สามารถจะอนุมัติโครงการตัวอย่างเช่น อนุมัติให้ซื้อ Software version ใหม่ แต่ผู้มีอิทธิพลคือ คนที่สามารถบอกได้ว่าทำไมถึงต้องเป็น version นี้เพราะอะไร มันมีจะทำให้คนใช้งานได้มากขึ้น แล้วคนในบริษัทสามารถใช้ได้เลยไหม ว่าง่ายๆคนมีอิทธิพลคือคนที่อยู่ในเนื้องานจริงๆ และสร้างประโยชน์แก่บริษัทจริงๆ ผู้มีอำนาจตัดสินใจจึงต้องฟังผู้มีอิทธิพล หนังสือบอกว่าเราสามารถใช้จุดนี้ในการทำให้งานดีขึ้น หากคุณต้องการมีปากมีเสียงในบริษัท คุณไม่จำเป็นต้องเลื่อนตำแหน่ง แต่คุณจงทำตัวเองให้มีอิทธิพลซะ ว่าง่ายๆ คุณทำงานแล้วงานนั้นมีค่าแก่บริษัท ( แต่ละบริษัทมีแนวทางการให้คุณค่าอยู่ เช่น ถ้าคุณทำ Tools ขึ้นมาตัวนึงแล้วทำให้บริษัทสามารถออกงานได้ไวขึ้น และ บริษัทมีเป้าหมายขยายกำลังการผลิต นั่นแปลว่าคุณสร้างงานที่มีคุณค่าให้บริษัท แต่ถ้าบริษัทเน้นงานที่มีคุณภาพไร้ที่ติ สิ่งที่คุณทำอาจจะไม่ตอบโจทย์ 100 % คณค่าที่ได้อาจน้อยลง ) การทำแบบนี้ทำให้คุณเริ่มมีอิทธิพล และเมื่อคุณสะสมมันมากพอ ผู้มีอำนาจตัดสินใจจะเห็นคุณและเมื่อนั้นแหละคุณจะสามารถทำให้เกิดการตัดสินใจตามที่คุณต้องการได้ แต่มีวิธีง่ายกว่านั้นอีกครับ หากคุณรู้แล้วว่าใครเป็นผู้มีอิทธิพล คุณก็ไปหาคนคนนั้นแล้วคุณอธิบายวิธีการที่คุณอยากจะได้ว่ามันตอบโจทย์อะไรยังไง ถ้าคุณสามารถทำให้ผู้มีอิทธิพลนั้นเข้าใจว่าวิธีของคุณช่วยอะไรได้ ถ้าเขาเห็นด้วยกับคุณไม่นานเมื่อผู้มีอำนาจตัดสินใจจะตัดสินใจอะไรก็ต้องถามผู้มีอิทธิพล แล้วผู้มีอิทธิพลเห็นด้วยกับวิธีของคุณ คุณว่ามันจะเกิดอะไรขึ้นล่ะ ในหนังสือพูดเรื่องการเมืองในที่ทำงานไว้อีกหลายเรื่องและน่าสนใจมากๆ

ลาออกแบบสร้างสรรค์

หนังสือพูดการลาออกไว้ 3 แบบ คือ

  1. ตัดบัวไม่เหลือใย

    แบบนี้คือลาออกแบบจัดหนัก ด่าพ่อล่อแม่เอาเต็มที่เพราะไหนๆไม่อยู่ละ โดนทำมาเยอะจัดให้หนัก

  2. ออกแบบหงอยๆ

    แบบนี้คือลาออกธรรมดาทั่วไปครับ คือยื่นใบลาออกแล้วรอประมาณ 2 - 4 สัปดาห์ แล้วก็ออกจาบริษัท ระหว่างเวลานั้นคุณก็ไม่ต้องทำอะไรมากครับ อาจจะนั่งๆนอนๆด้วยซ้ำเพราะเมื่อคุณจะลาออกบริษัทก็คงไม่มีใครมอบหมายอะไรให้คุณ แล้วเมื่อถึงเวลาคุณลาออกไป

  3. ลาออกแบบสร้างสรรค์

    การลาออกแบบนี้ผมพึ่งเคยได้ยินแล้วก็น่าสนใจมาก เขาเปรียบการลาออกเหมือนตอนจบของหนังเรื่องนึง คนส่วนใหญ่มักจะให้ความสำคัญกับตอนจบ ตอนจบยิ่งดียิ่งเป็นที่น่าประทับใจและคิดถึง ดังนั้นทำไมคุณไม่ทำให้ตอนจบของการทำงานที่บริษัทนี้เป็นตอนจบที่สวยงามที่สุดล่ะ โดยการลาออกแบบสร้างสรรค์ที่หนังสือแนะนำก็มี จัดการงานที่คุณคิดว่าควรจะทำซะ ไม่ว่าจะเป็นจัดการปัญหาที่คุณมีอำนาจตัดสินใจให้มันจบ เขียนรายละเอียดปัญหาของงานที่คุยเคยเจอบ่อยๆ วิธีแก้มัน สิ่งสำคัญเกี่ยวกับงานนั้น ใครบ้างที่เกี่ยวข้องและขอความช่วยเหลือ หรือ เขียนแนวทางการทำงานคร่าวๆให้คนทำงานถัดไป แล้วก็ก่อนออกก็ควรรักษามิตรภาพระหว่างเพื่อนร่วมงานไว้ เพราะมันคือเส้นสายที่เรามี ไม่แน่ว่าในอนาคตเราอาจจะได้มาร่วมงานกันใหม่

สรุป

สำหรับหนังสือเล่มนี้ผมถือว่าคุ้มนะในราคา 325 บาท เพราะเปิดให้เห็นมุมมองใหม่ๆ ที่ไม่เคยคิดว่าจะมี ในหนังสือมีอีกหลายเรื่องที่ผมไม่ได้เล่า ซึ่งน่สนใจไม่แพ้กัน ส่วนใครจะเอาไปรองขาโต๊ะไหมอันนี้แล้วแต่เลยครับ ที่รองขาโต๊ะราคา 325 บาทก็ถือว่าเท่ห์ดี ปกเรียบ กระดาษลื่นดีครับ

เพลงประกอบการเขียน Blog

เป็นช่องที่ Cover เพลงแล้วลืมต้นฉบับเลยจริงๆ ผมกลับไปฟัง version ต้นฉบับแล้วไม่เพราะเท่า version นี้เลย

Designing Your Life - คู่มือออกแบบชีวิตด้วย Design Thinking

Designing Your Life - คู่มือออกแบบชีวิตด้วย Design Thinking

Designing Your Life - คู่มือออกแบบชีวิตด้วย Design Thinking

ออกตัวก่อนเลยว่าไม่ได้อยากอ่านหนังสือเล่มนี้เลยที่ซื้อเพราะอยากได้ค่าส่งฟรีก็เลยต้องซื้อหนังสือให้ถึงยอด เห็นเล่มนี้ขึ้นมาเป็นหนังสือแนะนำ ดูสำนักพิมพ์แล้วก็โอเคเคยอ่านหนังสือของสำนักพิมพ์นี้หลายรอบอยู่ก็เลยซื้อมาเลยละกัน ตอนแรกกะไว้ว่าหนังสือเล่มนี้จะต้องมาแนวพัฒนาตนเองแบบ ผมทำมาแล้วสำเร็จบลาๆ หรือมีวิธีการแบบเวอร์วังอ่านแล้วว้าวแต่น่าจะประยุกต์ใช้กับตัวเองได้ยาก แต่พอได้เริ่มอ่านแล้วสนุกจนวางไม่ลงเลยทีเดียว

ปัญหาทั่วไปที่คนมักเจอ

ส่วนตัวประทับใจการเล่าเรื่องของหนังสือเล่มนี้มากคือ เขาไม่ได้โผล่มาแบบขายของแบบทำตามเลยชีวิตดีขึ้นแน่นอน แต่เขาเริ่มโดยการยกกรณีศึกษาให้เราเห็น ซึ่งเป็นปัญหาที่คนส่วนใหญ่มักเจอซึ่งผมก็เป็นหนึ่งในนั้น ปัญหาของผมคือมันรู้สึกว่างเปล่ามาก ที่เราทำมาทั้งหมดเพื่ออะไร ทำอะไรก็รู้สึกไม่มีความหมาย ไม่อิ่มเอมใจ ไม่สุขใจ เกิดคำถามมากมายว่า มันเป็นเพราะที่งานที่ทำอยู่ไหมที่ไม่ดี หรือเป็นเพราะตัวเราเอง คำถามมันมากมายและก็ตอบคำถามนั้นไม่ได้ด้วย

เริ่มที่ตัวเอง

คำพูดดีดูจะเอียนๆเพราะหนังสือหลายๆเล่มพูดเหมือนกันให้เริ่มที่ตัวเอง แต่หนังสือเล่มนี้พูดได้ดีเกี่ยวกับการเริ่มที่ตัวเอง เริ่มที่ตัวเองคือคุณต้องรู้ก่อนว่าตัวเองตอนนี้เป็นยังไง ในหนังสือบอกให้วัด 4 อย่างคือ

  1. สุขภาพ : สุขภาพร่างกายเป็นยังไง

  2. งาน : งานในที่นี้คือสิ่งที่ทำแล้วได้ผลลัพธ์ที่อยากได้เป็นยังไง มีทำไหม หนักไปหรือเปล่า หรือไม่มีเลย

  3. กิจกรรมสร้างสุข : คือสิ่งที่ทำแล้วเกิดความสุขโดยไม่ต้องเครียด เช่น เล่นเกม อ่านหนังสือ ดูหนัง

  4. ความรัก : ความรักในนิยามของคุณเองเลย อาจจะเป็นความรักฉันท์ชู้สาว ความรักที่ให้กับเพื่อนมนุษย์

หนังสือเล่มนี้บอกให้คุณวัดมันออกมาจากนั้นก็อยู่ที่คุณเลยว่าจะปรับเปลี่ยนยังไงให้เหมาะสมกับคุณ การบอกแบบนี้นี่เห็นได้ยากจากหนังสือแบบนี้แต่มันคือสิ่งที่ถูกต้องเพราะแต่ละคนมีความต้องการไม่เหมือนกัน ดังนั้นคุณจะปรับเปลี่ยนอะไรก็แล้วแต่ใจคุณ

อะไรกันที่ทำให้เรามีความสุขและทุกข์ตอนทำงาน

คำถามนี้บางคนก็ตอบได้บางคนก็ตอบไม่ได้ ซึ่งวิธีการแก้ปัญหาของหนังสือเล่มนี้ก็ง่ายๆครับ จดบันทึกเอาครับว่าใน 1 วัน มีกิจกรรมอะไรที่เราทำบ้าง กิจกรรมไหนทำให้เราทุกข์ ทำให้เราสุข ซึ่งเมื่อเราเห็นแล้ววิธีจัดการง่ายๆกับเรื่องนี้คือ พยายามเพิ่มส่วนที่เกิดสุข กับ ลดส่วนที่เกิดทุกข์ เช่น ไปคุยกับหัวหน้างานว่างานแบบไหนที่เราอยากทำ หรือตำแหน่งไหนที่ทำให้ได้ทำงานแบบนั้น หรือถ้าไม่มีจริงๆ อาชีพไหนที่ทำให้เราได้ทำงานแบบนั้น จริงๆผมก็เคยเจอเหตุการณ์แบบนี้เหมือนกันคือมันทุกข์ทรมานมากตอนเขียน Frontend แต่สนุกมากตอนเขียน Backend ดังนั้นตอนคุยกับหัวหน้าก็เลยขอหัวหน้าไปว่า ผมถนัด Backend นะ อยากจะทำด้านนี้โดยเฉพาะไม่อยากทำ Frontend ทำอะทำได้แต่อาจจะช้าไม่สวย จะให้ทำก็ได้ หลังจากนั้นเหมือนหัวหน้าจะลดงาน Frontend ลง ให้ Backend จนตอนนี้แทบจะไม่ได้แตะแล้ว ซึ่งเหตุผลคงจะมาจากหลายๆเหตุผล เช่น ให้คนไม่ถนัดทำงานออกมาไม่ดี สุดท้ายทำมากๆก็เครียดแล้วก็ลาออก เพื่อไม่ให้เกิดเหตุการณ์แบบนั้นก็เลยปรับเปลี่ยนงานให้ แต่ไม่ใช่ทุกบริษัทจะยอมแบบนี้นะครับ ผมแค่โชคดีอยู่บริษัทที่คุยกันได้

แต่ละคนตีค่าคนละอย่าง

อีกเรื่องหนึ่งที่ผมชอบในหนังสือเล่มนี้คือเขาสอนให้เรานิยามสิ่งที่เราต้องการขึ้นมาเองเช่น งานในนิยามของคุณคืออะไร (ไม่ใช่ทำอะไร) งานที่มีความหมาย อะไรที่คุณให้ค่ากับงาน ผลตอบแทน ประสบการณ์ หรืออะไรที่จะเป็นตัวบอกว่านี่คืองานที่คุณอยากทำ เช่น ชีวิตคืออะไร ความหมายของชีวิตคุณ อะไรที่ทำให้ชีวิตมีความหมาย ความสัมพันธ์กับครอบครัวหรือเปล่า ความรักคืออะไร ความดีความชั่วคีออะไร โดยถ้าเรานิยามสิ่งพวกนี้ออกมาได้แล้ว เราก็สามารถเอามันมาเทียบกับชีวิตที่เป็นอยู่ตอนนี้ว่าเฮ้ย มันตรงกับที่นิยามหรือเปล่า ซึ่งถ้ามันไม่ตรงเนี่ยแสดงว่าคุณคงกำลังจะไม่มีความสุขในระดับหนึ่ง เช่น ถ้างานของที่ผมต้องการคือการเรียนรู้สิ่งใหม่ ทำอะไรให้เกิดประโยชน์กับผู้ใช้ แต่งานในปัจจุบันคือการทำอะไรเดิมซ้ำๆ แน่นอนทั้งสองสิ่งมันขัดกัน ผมย่อมรู้สึกไม่ค่อยสุข หรือ ถ้าสิ่งที่ทำให้ชีวิตผมดูมีคุณค่าคือการให้ความรู้คนอื่นแบบเข้าถึงได้ไม่ต้องใช้เงินเยอะ แต่ผมทำงานเป็นคนเปิดคอร์สสอนอบรมด้วยเงินหลายแสน ผมก็คงรู้สึกไม่ค่อยดีเท่าไหร่เช่นกัน

ลองออกแบบชีวิตหลายๆทาง

หนังสือเล่มนี้แนะนำให้เราลองออกแบบชีวิตดูว่าเราจะเป็นอะไรได้บ้าง สร้างมาหลายๆทาง แต่ไม่ได้สร้างเปล่าๆนะเพราะทุกครั้งที่สร้างทางขึ้นมาจะทำการใส่คำถามไปในแผนชีวิตนั้นด้วยเช่น มันเลี้ยงชีวิตได้ไหม สนุกจริงหรือเปล่า ต้องทำอะไรบ้าง ซึ่งด้วยวิธีการนี้ทำให้เราเห็นภาพชัดขึ้นแต่ก็ไม่เพ้อฝันจนเกินไปเพราะมันได้มีคำถามและเราต้องไปหาข้อมูลด้วยว่าการจะมีชีวิตแบบนั้นเราต้องทำอะไร ต้องเจอกับอะไร ตอนนี้เราพร้อมมากแค่ไหนที่จะไปมีชีวิตแบบนั้น

ปรับมุมมอง

จุดหนึ่งที่ผมชอบหนังสือเล่มนี้คือการปรับมุมมอง ซึ่งมันเป็นการปรับที่ไม่ได้รุนแรงอะไรมาก แบบให้เปลี่ยนศาสนา มันแค่การเปลี่ยนความเชื่อ เช่น เราต้องหาหนทางที่ใช่หนทางเดียว แต่เป็น เราต้องการแนวคิดหลายๆทาง จะได้ลองสำรวจว่าเราเป็นอะไรได้บ้าง

สรุป

จริงๆมีอีกหลายส่วนที่ไม่ได้พูดถึงในหนังสือเล่มนี้ซึ่งเป็นอีกหลายส่วนที่ดีมากๆ แถมสไตล์การเขียนยังสนุกอ่านแล้วไม่น่าเบื่อ มีอะไรให้องทำตลอด สำหรับหนังสือเล่มนี้กับราคาเกือบ 300 บาทนั้นผมถือว่าคุ้มมากที่จะซื้อมาลองอ่าน ลองทำตาม ซึ่งวิธีที่ให้ลองทำตามนั้นไม่ได้เสียเงินเลย สุดท้าย

ชีวิตลิขิตเอง

อย่าอ้อนขอ จงไขว่คว้าไม่งั้นไม่มีทางสำเร็จ

เพลงประกอบการเขียน Blog

ถือว่าเป็นเพลงที่เหมาะกับตอนนี้เสียเหลือเกิน ชีวิตเราเราลิขิตเองครับ อย่าให้ฟ้า ดิน พรหม เทพองค์ใด หรือ พระเจ้า มาลิขิตครับ ณ จุดสุดท้ายของเราเราจะได้พูดอย่างเต็มปากว่าชีวิตนี้เป็นของเราอย่างแท้จริง

Basic Spring Part 4 - Connect SQL Database

Basic Spring Part 4 - Connect SQL Database

เห็นช่วงนี้คนชอบเล่น surf skate เราก็อยากเล่นแต่อยากเล่นแบบนี้มากกว่า มันเรียกว่า lifting คือการเล่นคลื่นที่อยู่บนอากาศ ไม่มีในโลกแห่งความเป็นจริงอันใกล้ แต่มีในการ์ตูน

ผมเขียนเกี่ยวกับ Spring ไว้หลายตอน คุณสามารถกด Link ด้านล่างเพื่ออ่านที่เกี่ยวกับ Spring ตอนต่างๆได้เลย

สำหรับตอนนี้เราจะมาพูดถึงการต่อ Database กับนะครับว่าจะต่อ Database ยังไงต้องทำอะไรบ้าง สำหรับตอนนี้อาจจะใช้ Database อันนี้ผมแนะนำให้ใช้ Docker สร้าง Container ที่เป็น MySQL Database มาใช้งานนะครับ (เพราะง่าย) โดยวิธีการสร้างนั้นตาม Link นี้ไปเลย หรือใครไม่ถนัดก็สามารถ Install ตัว Mysql Server ได้เลยครับ

ในส่วนของ Code นั้นสามารถไปดูได้ที่ Github ได้เลย

Init Project

ทำการเข้า Web : https://start.spring.io/ จากนั้นทำการเลือกตามภาพด้านล่างและกด Generate

โดยตัวที่เราเพิ่มคือ Spring jpa data ซึ่งเป็น Lib ที่ทำให้เราเชื่อมต่อกับ SQL Database ได้ จากนั้นทำการเปิดด้วย IDE จากนั้นลองทำการ Start server ดูก็จะพบว่า Error ดังภาพซึ่งเกิดจากเราไม่ได้ทำการ Config ค่าในการเชื่อมต่อ Database

ดังนั้นจึงทำการ Config ก่อน โดยการ Config นั้นจะต้องทำที่ไฟล์ : src/main/resources/application.properties โดยต้องเพิ่ม properties ดังต่อไปนี้

1
2
3
4
5
6
# ตรงนี้คือ jdbc url คือ url ไปหาตัว database ซึ่ง database ของผมอยู่ที่ 192.168.56.101 port 3306 ซึ่งจะไม่เหมือนกับเครื่องของคุณแน่นอน
spring.datasource.url=jdbc:mysql://192.168.56.101:3306/webdb?useUnicode=yes&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&serverTimezone=Asia/Bangkok

# ตรงนี้คือ username password ในการเข้า database
spring.datasource.username=root
spring.datasource.password=root

เมื่อตั้งค่าเสร็จให้ลองทำการ Start server อีกครั้ง

ซึ่งจะพบว่า Error ใหม่ซึ่งเกี่ยวกับการหา Driver ไม่เจอซึ่งสามารถแก้ไขได้จากการเพิ่ม Dependency ที่เป็น Driver ของ mysql โดยไปเพิ่มที่ไฟล์ : pom.xml

1
2
3
4
5
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>

จากนั้นลองทำการ Start server อีกครั้ง ซึ่งคราวนี้จะพบว่าสามารถ Start server สำเร็จแล้ว

สร้าง Map Class กับ Table

ตัว Lib ที่เราใช้เชื่อมต่อกับ Database นั้นเราจะใช้ Lib : JPA โดย Concept ของ JPA คือเป็นตัวกลางที่จะแปลง Java Class ให้กลายเป็นข้อมูลที่จะไปอยู่ใน Table โดยตัว Lib จะแปลงข้อมูลเป็นคำสั่ง SQL ซึ่ง

  • ข้อดี

    • ง่ายในการเก็บข้อมูลไม่ต้องเขียน SQL เอง แค่ Set ค่าลงใส่ Object จากนั้นสั่ง Save ก็เป็นการ Insert หรือ Update ได้เลย เช่นกัน เวลาค้นหาข้อมูลก็จะดึงข้อมูลออกมาจาก Database มาใส่ใน Object ให้สามารถใช้งานได้เลย
    • สามารถเปลี่ยนตัว SQL Database เป็นยี่ห้ออื่นก็ได้ทันที เช่น สามารถเปลี่ยนจาก MYSQL เป็น MARIADB ได้เลยเพียงเปลี่ยน Config ไม่ต้องทำการแก้ Code เลย
  • ข้อเสีย

    • ไม่ยืดหยุ่นเท่าจัดการแบบ Native Query เพราะ JPA ต้องการความเป็นสามารถใช้ได้ทุก DB จึงไม่สามารถใช้ Function ที่เฉพาะเจาะจงกับ DB นั้นได้ (ทำได้แต่เปลี่ยน DB ต้องแก้ Code)
    • เวลาดึงข้อมูลนั้นจะดึงทุก Field ใน Table นั้นออกมาเพื่อ Map กับ Class ว่าง่ายๆมัน SELECT * เอา Field ที่ไม่ได้อยากใช้ออกมาด้วย ทำให้สิ้นเปลือง (สามารถดึงเฉพาะ Field ได้แต่ต้องสร้าง Class ใหม่)

Entity

Class ที่ใช้ Map กับตัว Table นั้นเราจะเรียกมันว่า Entity โดยเราจะลองสร้าง Class ที่ชื่อว่า People เก็บข้อมูลเกี่ยวกับบุคคล ดัง Code ข้างล่าง

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package test.spring.webdb.entity;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Getter
@Setter
@Accessors(chain = true)
@EqualsAndHashCode
@ToString
@Entity
@Table(name = "people")
public class People {

@Id
@Column(name = "ID")
private String id;

@Column(name = "FULL_NAME")
private String fullName;

@Column(name = "AGE")
private Integer age;

@Column(name = "ADDRESS")
private String address;
}
1
2
3
@Entity
@Table(name = "people")
public class People {

@Entity ส่วนนี้เป็นการบอกว่า Class นี้เป็น Entity

@Table เป็นการบอกว่า Enttiy นี้เชื่อมกับ Table ที่ชื่อ people

1
2
3
@Id
@Column(name = "ID")
private String id;

@Id ส่วนนี้บอกว่า Attribute นี้จะเป็น Primary key

@Column ส่วนนี้เป็นการบอกว่า Attribute นี้ผูกกับ Column ที่ชื่อ ID

Repository

Repository เป็น Service ที่เราใช้เชื่อมต่อกับ Database

1
2
3
4
5
6
7
8
package test.spring.webdb.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import test.spring.webdb.entity.People;

public interface PeopleRepository extends JpaRepository<People, String> {

}
1
public interface PeopleRepository extends JpaRepository<People, String>

ตรง JpaRepository<People, String> เป็นการบอกว่า Repository นี้จะใช้กับ Entity : People โดยมี Primary key เป็น type : String

ลองใช้งาน

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package test.spring.webdb.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import test.spring.webdb.entity.People;
import test.spring.webdb.repository.PeopleRepository;

import java.util.UUID;

@RestController
public class PeopleController {

@Autowired
private PeopleRepository peopleRepository;

@GetMapping("/test-create")
public void testCreate() {
People people = new People()
.setId(UUID.randomUUID().toString())
.setFullName("Normal Programmer")
.setAge(2)
.setAddress("In blog")
;
peopleRepository.save(people);
}
}

เราจะทำการสร้าง object : people แล้วทำการ save ค่าลง Database โดยใช้ peopleRepository ซึ่ง peopleRepository นั้นมาจากการ Inject เข้ามาโดยตัว Spring จะทำการสร้าง Object ให้เราโดยอัตโนมัติ คราวนี้เรามาลอง Start server และยิง Request ดูครับ ซึ่งจะพบว่า Error ซึ่งเกิดขึ้นเพราะเราไม่ได้สร้าง Table ไว้

ซึ่งวิธีแก้นั้นไม่ยากเราสามารถไปสร้าง Table เอง หรือ ให้ตัว spring ทำการสร้างได้โดยไปทำการ Config เพิ่มที่ application.properties

1
spring.jpa.hibernate.ddl-auto=update

โดยตัว config นี้จะทำการ Update ตัวโครงสร้าง table ให้อัตโนมัติแนะนำให้ใช้ช่วง dev อย่างเดียว

จากนั้นลองทำการ Start server อีกครั้งแล้วลองยิง Request จะเห็นว่าไม่ Error แล้ว ซึ่งเมื่อไป check ใน Database ก็จะเห็นว่ามีข้อมูลแล้วดังภาพ

มา Insert เข้าไปจริง แต่เราอยากเห็นคำสั่ง SQL อะ จะได้มั่นใจว่ามันทำงานถูกต้อง ตรงส่วนนี้ก็สามารถ Config ได้ครับ โดยเพิ่ม config ที่ application.properties

1
2
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true

ซึ่งเมื่อลองยิง Request ใหม่จะเห็นว่ามี SQL Query โผล่ขึ้นมาให้เห็นแล้ว ตรงนี้อาจจะงงว่าทำไมมี SELECT ด้วย คือไม่ต้องตกใจครับตัว JPA เขาต้องการเช็คก่อนว่าเคยมี Object นี้อยู่ใน Table ไหม เขาเลยต้องทำการ SELECT ด้วย ID ก่อนเพื่อหาว่ามีไหม ถ้าไม่มีเขาจะสร้าง SQL INSERT แต่ถ้ามีเขาจะสร้าง SQL UPDATE แทน

SELECT ด้วยเงื่อนไขต่างๆ

ตอนนี้เราสามารถเก็บข้อมูลลง Database ได้แล้ว คราวนี้เรามาลอง SELECT ข้อมูลกันดูบ้าง โดยอย่างแรกเพิ่มข้อมูลลง database กันก่อน

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('3aec3c1d-ffa5-4775-8528-80f8b99bc6e5', 'Thorstein Betchley', 36, '9 Springs Road');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('c9c03b28-5e59-4eec-9f3d-70c600c5c9f2', 'Vyky Paulazzi', 82, '6269 Arapahoe Parkway');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('ced90a98-25eb-4c43-9fe3-947a66454117', 'Tobie Orrock', 85, '880 Cambridge Street');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('c1c3c3f9-0e02-47e1-b2d8-485d0179bf51', 'Venita Leatt', 68, '40 Hanover Circle');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('ae6c5150-f999-4c4a-a570-599ea8eaf2ef', 'Teodorico Boraston', 67, '57 Alpine Road');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('d6018263-cf98-413d-9cb9-9d4b9aa1711a', 'Aron Spong', 51, '95968 Butternut Lane');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('42a520b2-ad93-4a5a-bb82-b0a3f4552145', 'Berny Anders', 53, '8 Nelson Park');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('c9f39612-7cda-4e16-9cd8-27809ca9e33f', 'Arny Crosland', 4, '21 Stoughton Terrace');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('48ac94ab-a1c4-40d6-a7d7-6ac01241b443', 'Kevon Glacken', 93, '01562 Briar Crest Junction');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('298dfe35-5fe5-4c1d-bd51-2b3143e46558', 'Dorian Showalter', 45, '22201 Montana Court');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('a06bbc3d-bd73-4531-9c8e-3354b8d79c80', 'Burton Brussell', 68, '33397 1st Place');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('817abbd8-6fd5-4be8-9b86-eb6d7de288b5', 'Reinwald Andrei', 38, '94 Bashford Trail');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('666de36b-ec97-452b-bd6e-2193a16f4dd2', 'Yul Blaxley', 21, '75042 East Plaza');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('6ba0ac17-ac7d-4343-a087-b4abdef0551a', 'Nell Fredson', 77, '53235 Thompson Park');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('cfc126e2-d605-4feb-a879-1c227ceb2d8a', 'Sigismond Bryenton', 64, '212 Orin Avenue');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('13ab7734-66fe-4c72-a8e1-f99d86c361a9', 'Maureen Hansill', 71, '07 Kipling Trail');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('8ecc0507-add3-4842-8198-b29d6890760f', 'Marta Lemonnier', 66, '6157 Carpenter Crossing');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('ccf3cab2-6574-45c0-a3e4-8cc0b9c31b25', 'Wynne Wilden', 59, '23 Thompson Way');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('971a428e-68b4-4f31-81c3-5ce6a7a63775', 'Sallee Stanaway', 77, '02 Menomonie Avenue');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('3dcefbc4-c164-4e17-b141-1e024b01e2ac', 'Cecilla Offa', 81, '56252 Burning Wood Lane');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('ece3f8c6-a5e6-41d7-bdd8-9c1270ac4ede', 'Merv Arnould', 54, '60743 Lillian Terrace');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('ae3ee502-b3eb-48d1-9e61-1562d667c8a6', 'Steffen Imlin', 17, '636 Continental Street');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('5e6fea01-18f1-47f2-9786-8374d14d5638', 'Clemente Ginglell', 73, '4872 Utah Point');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('9a24f317-81c7-42f2-a3d9-287a0476b1ca', 'Peri Putman', 19, '8 Maywood Junction');
insert into people (ID, FULL_NAME, AGE, ADDRESS) values ('a3d59c22-ee42-45c2-997b-551fce33e481', 'Barnebas Matic', 71, '7 Blue Bill Park Plaza');

Build in method

ตัว Spring มี Build in method ที่ใช้ในการค้นหาอยู่แล้วตัวอย่างเช่น findById() , findAll() ตรงนี้สามารถใช้ได้เลยไม่ต้องทำการเขียนเพิ่มเอง

สร้างเงื่อนไขโดยการใช้ชื่อ Method

ถ้า Advance ขึ้นมาหน่อยเราอยากข้อมูลด้วย Field ต่างๆด้วยเงื่อนไขเท่ากับ มากกว่าน้อยกว่า เราก็สามารถสร้างโดยการตั้งชื่อ method โดยมีรายละเอียดดังนี้ Link เช่น ถ้าอยากค้นหาด้วยเงื่อนไขอายุมากกว่า x และชื่อขึ้นต้นด้วย y ก็จะสามารถเขียนชื่อ Method ได้แบบนี้

1
2
3
public interface PeopleRepository extends JpaRepository<People, String> {
List<People> findByAgeGreaterThanAndFullNameStartingWith(Integer age, String fullName);
}

คราวนี้ลองไปทดลองใช้งานที่ Controller

1
2
3
4
@GetMapping("/test-select-with-method-name")
public List<People> testSelectWithMethod() {
return peopleRepository.findByAgeGreaterThanAndFullNameStartingWith(50, "B");
}

ซึ่งเมื่อลองใช้งานแล้วจะได้ผลลัพธ์ดังภาพด้านล่าง

อยากเขียน Query เองไม่อยากสร้างจากชื่อ Method

ในบางกรณีที่ Query มีความซับซ้อนมากๆ หรือ SELECT หลายๆ Field ตัว Spring ก็เปิดโอกาสให้เราเขียน Query นั้นเองโดยใช้ @Query โดย Syntax ที่ใช้ในการเขียนนั้นจะใช้ Syntax JPQL โดยถ้าเราจะใช้เงื่อนไขเดียวกับ Query ก่อนหน้านี้เราสามารถเขียนได้ดังนี้

1
2
3
4
5
6
7
8
9
public interface PeopleRepository extends JpaRepository<People, String> {
List<People> findByAgeGreaterThanAndFullNameStartingWith(Integer age, String fullName);

@Query("SELECT p FROM People p WHERE p.age > :age AND p.fullName like CONCAT(:fullName, '%') ")
List<People> myQuery(
@Param("age") Integer age,
@Param("fullName") String fullName
);
}

คราวนี้ทดลองใช้งานที่ Controller

1
2
3
4
@GetMapping("/test-select-with-query")
public List<People> testSelectWithQuery() {
return peopleRepository.myQuery(50, "B");
}

ซึ่งเมื่อทดลองยิงแล้วจะได้ผลลัพธ์ดังภาพด้านล่าง

ทำ Order และ Pagination ยังไง

คือการดึงข้อมูลนั้นเราคงไม่ดึงข้อมูลทั้งหมดที่ตรงเงื่อนไขกลับไปแน่นอนเพราะมันเปลืองแถมไม่แน่ว่าคน Select เขาไม่ได้สนใจข้อมูลหลังๆด้วย ลองนึกภาพการ Search ของ Google ดูครับ เขาจะแสดงผลการ Search เป็นหน้าๆ เพื่อลดการส่งข้อมูลไปกลับระหว่าง Client กับ Server โดยตัว Spring สามารถทำได้โดย

1
Page<People> findByAgeGreaterThanAndFullNameStartingWith(Integer age, String fullName, Pageable pageable);

จะเห็นว่าเราทำการเพิ่ม parameter อีกตัวคือ Pageable ซึ่งเป็นตัวแปรที่เป็นการบอกว่าจะ Order ด้วยอะไร จะเอาข้อมูล Page ไหน แล้วแต่ละ Page มีขนาดเท่าไหร่ โดยตัวอย่างการใช้งานจะเป็นแบบนี้

1
2
3
4
5
6
@GetMapping("/test-select-with-page")
public List<People> testSelectWithMethodAndPage() {
Pageable pageable = PageRequest.of(0, 2, Sort.by(Sort.Direction.DESC, "age"));
Page<People> result = peopleRepository.findByAgeGreaterThanAndFullNameStartingWith(50, "B", pageable);
return result.getContent();
}

โดยตัวอย่างนั้นเราจะดึงข้อมูล Page แรก (เริ่มที่ 0) และ Page มีขนาด 2 โดยเรียงแบบมากไปน้อยด้วยค่า age ซึ่งจะได้ผลลัพธ์เป็น

สรุป

สำหรับตอนนี้พูดถึงการเชื่อมต่อ Database ว่าทำยังไง ต้องสร้าง Entity ต้องสร้าง Repository ซึ่งด้วยความรู้ประมาณนี้ก็สามารถเขียน Web application server กันได้แล้ว ในส่วนของตอนถัดไปนั้นขอคิดดูก่อนว่าจะเขียนอะไรเพราะทั้ง 4 ตอนก็ครอบคลุมการเขียน Web application server แล้ว ไม่แน่อาจจะไปเขียนเรื่องอื่นเลยก็ได้ สุดท้ายก็ฝากติดตาม Page Normal Programmer ของผมด้วย

เพลงประกอบการเขียน Blog

เพลงเกี่ยวก้อยของวง Wisdom ลองไปฟังดูครับ

Basic Spring Part 3 - Dependency injection ใน Spring framework

Basic Spring Part 3 - Dependency injection ใน Spring framework

ผมเขียนเกี่ยวกับ Spring ไว้หลายตอน คุณสามารถกด Link ด้านล่างเพื่ออ่านที่เกี่ยวกับ Spring ตอนต่างๆได้เลย

ตอนที่แล้วเราสร้าง Project สร้าง Controller รับ Http request ได้แล้ว ตอนนี้เราจะมาทำความเข้าใจและใช้งาน Dependency injection ซึ่งบอกเลยว่าใช้งานง่ายมาก ง่ายจนคนไม่รู้ว่ามันคืออะไรแค่ใส่ Annotation ก็ทำงานได้ ตอนนี้อาจจะร่ายยาวหน่อย อาจจะมีน่าเบื่อบ้างแต่จะพยายามทำให้เข้าใจง่ายครับ ส่วน Code ทั้งหมดสามารถไปดูได้ที่ github เผื่อขี้เกียจเขียน

อยากสร้าง Web ที่ทำนายว่าจะได้แต่งงานตอนอายุเท่าไหร่จากวันเดือนปีเกิด

โอเคโจทย์เรื่องนี้ไม่ยากใช่ไหมครับ ผมแค่สร้าง Controller ขึ้นมา 1 ตัว จากนั้นเขียน method รับ Request ที่มาวันเดือนปีเกิดเข้ามาแล้วทำนายว่าจะได้แต่งงานตอนอายุเท่าไหร่ ซึ่งหน้าตา Code น่าจะออกมาประมาณนี้

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package test.spring.web.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import test.spring.web.dto.request.ForecastMarryRequestDto;
import test.spring.web.dto.response.ForecastMarryResponseDto;

import java.util.Random;

@RestController
public class ForecastController {

@PostMapping("forecast/marry")
public ForecastMarryResponseDto forecastMarry(
@RequestBody ForecastMarryRequestDto request
) {
Random random = new Random();
int age = ( random.nextInt(80) ) + 20;
return new ForecastMarryResponseDto()
.setAge(age);
}

}

ไม่ต้องแปลกใจเรื่องทำนายนะครับ ผมไม่รู้ว่ามันต้องใช้อะไรเป็นการทำนายว่าจะแต่งงานเท่าไหร่ เลยใช้การ Random เอาซะเลย (คนทำเว็บทำนายอาจจะทำแบบนี้ก็ได้) พอลองยิงก็จะได้ผลลัพธ์ประมาณนี้

คนเริ่มนิยมกับการทำนาย

หลังจาก Web เปิดตัวคนแห่กันมาใช้งานแล้วติดใจ เริ่มมีการมาขอใช้งานในช่องทางที่งมากขึ้น เช่น ส่งมาเป็นไฟล์ อาจเรียกผ่าน Backgrond process, เรียกผ่าน event trigger ซึ่งแน่นอนว่าไม่ได้เรียกผ่าน Controller ที่เดียวอีกแล้ว ด้วยความต้องการแบบนี้คุณจึงคิดว่าควรจะย้ายวิธีการทำนายไปไว้ที่เดียว ซึ่งถ้าเรามองดีๆการทำนายของเรานั้นเป็น Business logic คือส่วนที่เป็นเรื่องเกี่ยวกับ Flow การทำงาน เงื่อนไข ทำแบบนี้ได้ไม่ได้ วิธีคำนวณคืออะไร สิ่งเหล่านี้เราจะเรียกมันว่า Business logic ซึ่งเราจะแยกส่วน Business logic ออกมาจากส่วนที่เป็น Controller หรือส่วนเชื่อมต่ออื่นๆ โดยการออกแบบในแนวทางนี้มีรูปแบบหนึ่งที่นิยมคือ Hexagonal Architecture โดยสามารถไปอ่านเพิ่มเติมได้ตามลิ้งเหล่านี้

https://netflixtechblog.com/ready-for-changes-with-hexagonal-architecture-b315ec967749

https://blog.octo.com/en/hexagonal-architecture-three-principles-and-an-implementation-example/

https://medium.com/@TKonuklar/hexagonal-ports-adapters-architecture-e3617bcf00a0

ด้วยเหตุนี้ผมเลยย้าย Logic การคำนวณไปไว้ใน Class ใหม่ที่ชื่อ : ForecastService ซึ่งจะได้ code ประมาณนี้

1
2
3
4
5
6
7
8
9
10
11
package test.spring.web.service;

import java.util.Random;

public class ForecastService {
public int forecastMarry(int day, int month, int year) {
Random random = new Random();
int age = ( random.nextInt(80) ) + 20;
return age;
}
}

แล้วจะเอา ForcastService ไปใส่ใน Controller ยังไง

อ่านชื่อหัวข้อแล้วคุ้นๆไหมครับ ตอนนี้เรากำลังพยายามจะทำ Dependency Injection กันอยู่นั่นเอง เรื่อง Dependency Injection ผมได้เคยเล่าไปแล้วใน ตอนที่ 1 ลองไปอ่านดูได้

ด้วยความรู้ที่เรามีเรารู้ว่ามันมีวิธี Injection คร่าวๆ 3 วิธีคือ

  • Constuctor Injection
  • Getter Setter Injection
  • Method Injection

ปัญหามันอยู่ที่ทั้ง 3 วิธีนั้นเราต้องยุ่งเกี่ยวกับ Object ที่เราต้องการจะ Inject ตัว Dependency เข้าไป ซึ่งจากตัวอย่างของเรา เราต้องการ Inject ตัว ForecastService เข้าไปใน ForecastController ซึ่งตัว ForecastController นั้นเราไม่ได้สร้างตัว Spring framework มันเป็นคนสร้าง เราเลยไปยุ่งอะไรกับมันไม่ได้ ซึ่งไม่ต้องกลัวคนเขียน Spring เขาเห็นถึงปัญหานี้แล้วจึงทำการเพิ่ม Annotation ขึ้นมาช่วยเราก็คือ @Autowired และ @Service โอเคมาดูกัน

@Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package test.spring.web.service;

import org.springframework.stereotype.Service;

import java.util.Random;

@Service
public class ForecastService {
public int forecastMarry(int day, int month, int year) {
Random random = new Random();
int age = ( random.nextInt(80) ) + 20;
return age;
}
}

จาก Code จะเห็นว่าเราทำการเพิ่ม @Service เข้าไปตรงด้านบน Class การใส่ Annotation แบบนี้เป็นการบอกว่า Class : ForecastService นั้นเป็นตัว Dependency ตัวนึงนะ พอตัว Spring มัน Start ขึ้นมามันไล่อ่านเจอ Annotation Service มันก็จะสร้าง ForecastService ขึ้นมาแล้วไปเก็บไว้ที่ที่นึงซึ่งมันคือ IOC Container ลองไปอ่านเพิ่มเติมดู แต่ถ้าไม่อยากอ่านมองว่ามันไปสร้างตัวแปร Global ไว้ละกัน

@Autowired

คราวนี้เราจะมาแก้ Code ที่ ForecastController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RestController
public class ForecastController {

@Autowired
private ForecastService forecastService;

// อันนี้เก็บไว้ให้ดูความแตกต่าง
@PostMapping("forecast/marry")
public ForecastMarryResponseDto forecastMarry(
@RequestBody ForecastMarryRequestDto request
) {
Random random = new Random();
int age = ( random.nextInt(80) ) + 20;
return new ForecastMarryResponseDto()
.setAge(age);
}

@PostMapping("forecast/marry-with-service")
public ForecastMarryResponseDto forecastMarryWithService(
@RequestBody ForecastMarryRequestDto request
) {
int age = forecastService.forecastMarry(request.getDay(), request.getMonth(), request.getYear());
return new ForecastMarryResponseDto().setAge(age);
}
}

จะเห็นมีการประกาศ Attribute : ForecastService และด้านบนมีการประกาศ @Autowired ให้กับ Attribute นั้น การทำแบบนี้เป็นการบอก Spring ให้ทราบว่า เราต้องการให้ Inject ตัว ForecastService เข้ามาให้กับ Class ForecastController นี้ ซึ่งพอสั่ง Start server Spring ไล่ scan annotation มาเจอว่าต้องการให้ Inject : ForecastService เข้าไป มันจะไปหาใน IOC Container ว่ามีไหม ถ้าไม่มีมันจะไล่ Scan ต่อหาตัว Dependency : ForecastService ซึ่งถ้าไล่แล้วไม่เจอจริงๆมันจะ Error

และเมื่อเราลอง Start server และลองยิง Request ดูจะได้ผลลัพธ์แบบนี้

อยากสร้างตัว Dependency เอง

จากตัวอย่างที่แล้วเราใช้ @Service เพื่อให้ Spring ทำการสร้างตัว Dependency ขึ้นมาให้ (ตัว ForecastService) แต่ในบางกรณีเราอยากสร้าง Dependency ขึ้นมาเองด้วยวิธีการเขียน Code ไม่ใช่ประกาศ Annotation ซึ่งจะเจอบ่อยกรณีที่ไปเรียกใช้ Lib คนอื่นแล้วอยากให้ Object นั้นเป็น Dependency ซึ่งจะเกิดปัญหาคือเราไม่สามารถไปใส่ @Service บน Code ของคนอื่นได้ ซึ่งทางคนเขียน Spring ก็รู้ว่าน่าจะมีความต้องการประมาณนี้จึงสร้างเพิ่มตัว Annotation ให้เราเพิ่มเติมคือ @Configuration , @Bean

เราจะสร้าง Class : DependencyConfig ขึ้นมาดัง Code ด้านล่าง

1
2
3
4
5
6
7
8
9
10
11
12
13
package test.spring.web.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import test.spring.web.service.ForecastService;

@Configuration
public class DependencyConfig {
@Bean
public ForecastService forecastService() {
return new ForecastService();
}
}

@Configuration นั้นเป็นตัวที่บอกว่าให้ Spring รู้ว่าต้องไป Scan annotation ทั้งหมดใน Class นี้นะ

@Bean นั้นเป็นการบอกว่า ผลลัพธ์จาก Method นี้นั้นเป็นตัว Dependency นะ ให้เอาไปใส่ที่ IOC Container ด้วย

ปล. อย่าลืมไป Comment Annotation Service ที่ ForecastService นะครับ

ซึ่งเมื่อลอง Start server แล้วลองยิง Request ไปที่ Server จะพบว่ายังทำได้เหมือนเดิม

สรุป

สำหรับตอนนี้เราได้เรียนรู้วิธีการทำ Dependency injection ของ Spring ว่ามันมีการทำงานยังไง ต้องประกาศยังไงถึงจะใช้งานได้ โดยหากเข้าใจ Concept นี้แล้ว คุณจะสามารถนำไปประยุกต์ใช้ได้กับการเขียน Code ของคุณได้อีกมากมาย แค่ Concept การใช้ Dependency injection ก็ทำให้ Code ยืดหยุ่นได้โคตรๆแล้ว ในตอนต่อไปเราจะลองใช้งาน database กันดูครับ มาดูว่า Spring จะมีอะไรมาช่วยเราในการทำงานกับ Database บ้าง

เพลงประกอบการเขียน Blog

ตอนฟังเพลงนี้ครั้งแรกรู้สึกชอบความหมายเพลงนี้มากๆ ตรงกับประสบการณ์ตัวเองมากๆ แบบเจอผู้หญิงดีๆคนนึงแล้วมีคนชอบเขาเยอะมากๆ แต่พอเห็นเขาไม่มีแฟนเราก็ดีใจนะรู้สึกเหมือนในเพลงเลย (แต่จะดีกว่าถ้าเป็นแฟนเราอะนะ ฮ่าๆๆๆๆๆๆ)

Basic Spring Part 2 - Create Project & Start Web Application

Basic Spring Part 2 - Create Project & Start Web Application

ผมเขียนเกี่ยวกับ Spring ไว้หลายตอน คุณสามารถกด Link ด้านล่างเพื่ออ่านที่เกี่ยวกับ Spring ตอนต่างๆได้เลย

ตอนนี้เราจะมาเริ่มสร้าง Web application กัน โดยขั้นแรกต้องมี IDE ที่ใช้เขียน Code กันก่อน โดยผมจะใช้เป็น IntelliJ สามารถ Download ได้ตาม Link นี้ ส่วนท่านอื่นจะใช้ eclipse หรือ netbean ก็ได้ไม่มีปัญหาครับ แต่ภาพที่จะแสดงจะใช้ของ IntelliJ เป็นหลัก

Init project

สำหรับหลายคนอาจจะถามว่าจะเริ่มสร้าง Project ยังไง แต่ละ IDE สร้างมาก็หน้าตาไม่เหมือนกัน อันนี้ Spring เขามี Web ที่ช่วยให้เราสร้าง Project ของ spring ง่ายๆแล้ว โดยกดเข้าไปที่ https://start.spring.io/ นี้ จากนั้นลองตั้งค่าตามภาพ

ด้านขวาที่เป็น Dependency นั้นตอนนี้ Add ไปสองตัวคือ

  • Spring web

อันนี้เป็นส่วนที่ทำงานเกี่ยวกับ Web Application server ทำให้สามารถสร้าง web application server ได้ง่าย

  • Lombok

อันนี้เป็น Lib ช่วยทำ Getter Setter ให้ จะได้ไม่ต้องทำการเขียน Getter Setter เอง

จากนั้นทำการกด Generate จะได้ไฟล์ zip มา จากนั้นทำการแตกไฟล์ zip จะได้ Folder ออกมาจากนั้นใช้ Intelij เปิด Folder นั้น ซึ่งจะได้ดังภาพ

จากนั้นคลิกที่ไฟล์ WebApplication จากนั้นกดที่ลูกศรสีเขียวเพื่อ start : web application server ซึ่งจะได้ผลดังภาพ

คราวนี้มาเข้าผ่าน Browser ดูครับว่าเราได้สร้าง Web application server ได้ไหม โดยเข้าผ่าน url : http://127.0.0.1:8080/ ซึ่งจะได้ผลดังภาพ

เห็นมันขึ้น Error ไม่ต้องตกใจครับ เพราะเราไม่ได้ map path : / ไว้กับการทำงานอะไรเลยมันเลย Error 404 Not found แต่อันนี้ก็จะเห็นว่าเราสามารถ Start web application server ขึ้นมาได้แล้ว

Annotation กับ Spring

ต่อจากนี้จะเริ่มมีเขียน Code มากขึ้นเรื่อยๆ ซึ่งเพื่อป้องกันการพิมพ์ผิดหรือเกิดไม่อยากพิมพ์ก็สามารถไปดู Code ตัวเต็มได้ที่ github โอเคเรามาเริ่มกันเลย

Annotation คืออะไร อ่าผมไม่ชอบอธิบายอะไรพวกนี้เหมือนกันเอาเป็นว่ามีคนอธิบายเรื่องพวกนี้ไว้แล้วซึ่งน่าจะอธิบายดีกว่าผมแน่นอน โดยสำหรับผม Annotation คือการประกาศบางอย่างไว้เพื่อให้ตัวโปรแกรมสามารถมาใช้ประโยชน์ได้ภายหลัง ซึ่งตัว Spring framework ก็มีการสร้าง Annotation ของตัวขึ้นมาใช้เหมือนกัน

1
2
3
4
5
6
7
8
9
10
11
12
package test.spring.web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebApplication {

public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}

อันนี้ตัวแรกที่เราเห็นกันเลยก็คือ @SpringBootApplication อันนี้เป็นตัวบอกว่า Class ที่ Run นี้เป็น SpringBoot ครับ ถ้าเราลองลบ @SpringBootApplication ออกดูจะเห็นว่าเกิดการ Error เพราะว่าตอน Run ตัว SpringFramework นั้นจะมีการไล่ Scan annotation เพื่อนำไป Config ส่วนที่จำเป็นในการ Start Server

สร้างตัวจัดการ Request จาก Client

ตัวจัดการ Request จาก Client ของ Spring Framework นั้นจะเรียกว่า Controller ดังนั้นเรามาสร้าง Controller กันเลย โดยสร้าง class : HelloWorldController ที่ package : test.spring.web.controller

1
2
3
4
5
6
7
8
9
10
11
12
13
package test.spring.web.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {

@GetMapping("/test/helloworld")
public String helloWorld() {
return "Hello world";
}
}
1
@RestController

ส่วนนี้เป็นการบอกว่า Class นี้จะเป็นตัวจัดการ Client นะ

1
2
3
4
@GetMapping("/test/helloworld")
public String helloWorld() {
return "Hello world";
}

ตรงส่วน @GetMapping นั้นเป็นการบอกว่า Method : helloWorld() นั้นจะรับ Request จาก Client ที่ยิงมาที่ /test/helloworld ด้วย Http method GET

ซึ่งเมื่อลองเปิด Browser แล้วกดไปที่ http://127.0.0.1:8080/test/helloworld จะได้ผลลัพธ์ดังภาพ

โดยการทำงานของ Spring ก็ไม่มีอะไรมากครับ ก็ตอน Start ตัว Server ตัว Spring framework ก็จะไปอ่าน Annotation ต่างๆภายใต้ package : test.spring.web ซึ่งก็ได้เจอ @RestController ซึ่งทำให้รู้ว่า Class นี้เป็นตัว Handle Request ที่มาจาก Client จากนั้นไปเจอ @GetMapping(“/test/helloworld”) ก็ทราบว่า Method helloWorld (method ที่ annotation ) จะ Handle request ที่ยิงมาที่ /test/helloworld ซึ่งจะเห็นว่ามันง่ายมากๆแค่ประกาศ Annotation ตัว Spring จะไปทำการจัดการต่อให้เองทั้งหมด

รับ Http Method Post

ทำการเพิ่มอีก Method เข้าไปใน Class : HelloWorldController ตาม Code ด้านล่างนี้

1
2
3
4
5
6
7
8
@PostMapping("/test/echo")
public EchoResponseDto echo(
@RequestBody EchoRequestDto request
) {
EchoResponseDto echoResponseDto = new EchoResponseDto();
echoResponseDto.setEchoMessage("echo : " + request.getMessage());
return echoResponseDto;
}

ส่วนตัว EchoResponseDto , EchoRequestDto code ตามนี้

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package test.spring.web.dto.request;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

@Getter
@Setter
@Accessors(chain = true)
@EqualsAndHashCode
@ToString
public class EchoRequestDto {
private String message;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package test.spring.web.dto.response;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

@Getter
@Setter
@Accessors(chain = true)
@EqualsAndHashCode
@ToString
public class EchoResponseDto {
private String echoMessage;
}
1
@PostMapping("/test/echo")

ตรงนี้บอกว่ารับ Http request ที่ path : /test/echo โดยรับ method Post

1
2
3
public EchoResponseDto echo(
@RequestBody EchoRequestDto request
) {

ตรงนี้ของใหม่ครับ @RequestBody ส่วนนี้เป็นการบอกว่า Request Body ที่เป็น Json นั้นมีโครงสร้างยังไงโดยจาก Code ข้างบนคือ EchoRequestDto โดยเมื่อทำงานเสร็จแล้วจะ Return : EchoResponseDto เพื่อไปเป็น Http Response Body ในแบบ Json โดยเมื่อลอง Start server แล้วยิงด้วย Postman จะได้ผลลัพธ์ดังภาพ

สรุป

สำหรับตอนนี้เราได้รู้แล้วว่า Annotation คืออะไร การทำงานของ Spring framework นั้นทำงานยังไงกับ Annotation (scan Annotation แล้ว Config ส่วนต่างๆ) แล้วก็สร้าง Controller ของ Spring ไว้รับ Request จาก client ได้แล้วผ่านการใช้ Annotation ของ Spring ไม่ว่าจะเป็น @RestController , @GetMapping, @PostMapping, @RequestBody ก็จบตอนนี้ก็สามารถสร้าง Web application server ที่ยังไม่ต่อ Database ได้แล้ว ในตอนต่อไป เราจะมาพูดถึงการใช้งาน Dependency Injection ของ Spring

เพลงประกอบการเขียน Blog

เพลงนี้ฟังสบายมากๆ ความหมายดีอีกต่างหาก โดยสามารถติดตามวงนี้ได้ที่ The X_Official

Basic Spring Part 1 - Spring framework & Dependency Injection

Basic Spring Part 1 - Spring framework & Dependency Injection

ผู้หญิงคนนึงเคยบอกว่าชอบฤดูใบไม้ผลิเพราะมันสวยงามให้ความรู้สึกได้เริ่มต้นใหม่

ผมเขียนเกี่ยวกับ Spring ไว้หลายตอน คุณสามารถกด Link ด้านล่างเพื่ออ่านที่เกี่ยวกับ Spring ตอนต่างๆได้เลย

รอบนี้มาเขียนเรื่องเกี่ยวกับ Spring Framework ซึ่งน่าจะลากยาวได้หลายตอนอยู่ โดยจะพูดแต่เรื่องพื้นฐาน แล้วก็ทดลองใช้งานเบื้องต้น ซึ่งน่าจะเขียนไปเรื่อยๆจนเขียนต่อ Database ทำ Web application server ได้เลย (แบบง่ายๆ) โดยที่เขียนเกี่ยวกับเรื่องนี้เพราะมีน้องในบริษัทหลายคนกำลังจะใช้ Spring framework ก็เลยถือโอกาสเขียน Blog ไปด้วยเลย เผื่อใครต้องเขียน Spring framework จะได้มาร่วมมั่ว ร่วมลองไปด้วยกัน

Spring framwork คืออะไร

Spring framework คือ framework นึงในภาษา Java Framework คือโครงหรือรูปแบบในการเขียน Program โดยหากเราทำตามรูปแบบที่เขากำหนดให้จะทำให้เราสามารถพัฒนางานของเราได้เร็วขึ้น เช่น ถ้าเราจะเขียน Web application server เราอาจะต้องเขียนขารับ Http request โดยหากเรา Implement เองทั้งหมด เราต้อง Implement ตั้งแต่แกะ Http request แล้วก็ Parser เก็บข้อมูลส่วน Header แยกส่วน Body อาจจะต้องด้วยว่าเป็น Method อะไร จากนั้นจะต้องไป Map กับ Endpoint ไหนอีก แค่พูดมาแค่นี้ก็เหนื่อยละ ซึ่งงานพวกนี้มันต้องทำอยู่แล้ว เขาเลยสร้าง Framework ขึ้นมาเพื่อจัดการสิ่งนี้ แล้วเราไปเขียน Code ตามโครงสร้างของเขาเขาจะจัดการส่วนที่ทำซ้ำให้เรียบร้อย จากนั้นส่งตัว Header กับ Body ที่ใช้งานง่ายๆมาให้ ซึ่งการทำแบบนี้ทำให้เราพัฒนา Application ได้ไว แล้วลงรายละเอียดเฉพาะส่วนที่เป็น Business Logic อย่างเดียว โดยตัว Spring framework นั้นมีส่วนช่วยเรามากมายไม่ว่าจะส่วนรับ Http request ส่วนติดต่อ Database ส่วน Security

DI : Dependency Injection

หากดูที่รูปจะเห็นส่วน Spring core ที่เป็นส่วน DI ซึ่งส่วนนี้เป็น Concept เกี่ยวกับการเขียน Program โดยถ้าเข้าใจ Concept นี้แล้วจะเข้าใจ Anotation บางตัวเลย อ่า พอมาถึงต้องอธิบายจะอธิบายยังไงดีล่ะ งั้นลองดู Code ละกัน

1
2
3
4
5
6
7
8
9
10
11
12

public class SummaryService {
public Double summaryAccount(AccountService accountService) {

List<Account> accounts = accountService.getAllAccount();
Double sum = 0;
for (Account account : accounts) {
sum += account.money;
}
return sum;
}
}

จาก Code ไม่มีอะไรมากก็คือทำการรวมเงินครับโดยใช้ตัว AccountService ที่ส่งเข้ามาใน Method : summaryAccount เพื่อดึงค่า accounts ทั้งหมดออกมาจากนั้น For Loop รวมค่าแล้ว Return ออกไป

คราวนี้ลองดูตรง AccountService ที่ส่งเข้ามาครับจะเห็นว่า Method : summaryAccount นั้นจะทำงานได้นั้นต้องมี AccountService ถ้าไม่มีจะทำงานไม่ได้ ดังนั้น AccountService มันเลยเป็น Dependency

ตอนนี้เราได้คำว่า Dependency แล้ว คำต่อไปคือ Injection ก็คือการฉีดเข้าไปเหมือนดังภาพเลย

Dependency Injection ก็คือการฉีดสิ่งที่เป็น Dependency เข้าไป ถ้าในตัวอย่าง คือการฉีด AccountService เข้าไปใน Method : summaryAccount นั่นเอง

Dependency Injection นั้นมีหลายระดับครับ ตามความยืดหยุ่น โดยมีคนเคยจัดไว้ https://medium.com/@samueleresca/dependency-injection-overview-31b757a8dd51 มี 3 ระดับ

  • Constuctor Injection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class SummaryService {

private AccountService accountService;

private SummaryService(AccountService accountService) {
this.accountService = accountService;
}

public Double summaryAccount() {

List<Account> accounts = this.accountService.getAllAccount();
Double sum = 0;
for (Account account : accounts) {
sum += account.money;
}
return sum;
}
}

// Main class file

public class Test {
public static void main() {
AccountService accountService = new AccountService();
SummaryService summaryService = new SummaryService(accountService);
System.out.println(summaryService.summaryAccount()) ;
}
}

จะเห็นว่า AccountService นั้นจะถูก Inject ผ่าน Constructor เท่านั้น ดังนั้นมันจะไม่สามารถเปลี่ยน Account service ได้อีกหลังจากสร้าง Object ได้อีกแล้ว ซึ่งมีข้อดีคือ Object นี้เราจะรู้ว่ามันมี Account service อะไรอยู่ข้างใน สามารถ Track การทำงานของมันได้ไม่ลำบาก (ถ้ารู้ว่าอะไรใส่เข้าไปในตอนสร้างก็จะรู้ว่าตัวที่ทำงานคือตัวไหน) แต่มันก็มีข้อเสียคือมันเปลี่ยนแปลงไม่ได้แล้ว

  • Getter Setter Injection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class SummaryService {

private AccountService accountService;

public Double summaryAccount() {

List<Account> accounts = this.accountService.getAllAccount();
Double sum = 0;
for (Account account : accounts) {
sum += account.money;
}
return sum;
}

public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}

public AccountService getAccountService() {
return this.accountService;
}
}

// Main class file

public class Test {
public static void main() {
AccountService accountService = new AccountService();


SummaryService summaryService = new SummaryService();

summaryService.setAccountService(accountService);
System.out.println(summaryService.summaryAccount());

}
}

อันนี้จะยืดหยุ่นกว่า Constructor นิดนึงคือสามารถเปลี่ยนแปลงได้โดยใช้ Setter ทำการ set : AccountService เข้าไป โดยสามารถเปลี่ยนได้ตามต้องการ แต่ก็ยากระดับนึงคือต้องสั่ง Setter ทุกครั้ง และอาจมีปัญหาในเรื่อง Concurrent ตอน Setter พร้อมกันหลายๆ Thread

  • Method Injection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SummaryService {
public Double summaryAccount(AccountService accountService) {

List<Account> accounts = accountService.getAllAccount();
Double sum = 0;
for (Account account : accounts) {
sum += account.money;
}
return sum;
}
}

// Main class file

public class Test {
public static void main() {
AccountService accountService = new AccountService();

SummaryService summaryService = new SummaryService();

System.out.println(summaryService.summaryAccount(accountService));
}
}

แบบนี้จะยืดหยุ่นสุดคือการส่งเข้ามาเป็นตัวแปรทำให้ แต่ก็แลกมาด้วยการ Track SummaryService เรียกใช้ AccountService ไหน

แล้วทำไมต้องทำ Dependency Injection ล่ะ

จาก Code ด้านบนจะเห็นวิธีการทำ DI ไปแล้วแต่คำถามคือทำไปทำไม ทำไปเพื่ออะไร จริงๆผมควรจะพูดเรื่องนี้ก่อน DI แต่เขียนแบบนี้ก่อนก็ดีอีกแบบเพราะมันทำให้คนสงสัยพอมารู้คำตอบจะได้เชื่อมโยงได้ แตกต่างจากการเล่าแบบตรงไปตรงมาที่อาจจะผ่านมาแล้วผ่านไปเลย

ลองดู Code ด้านล่างนี้ที่เป็น Code ที่ดึงเอาทำการ summary ค่า ของ SummaryService

1
2
3
4
5
6
7
8
9
10
11
public class SummaryService {
public Double summaryAccount(AccountService accountService) {

List<Account> accounts = accountService.getAllAccount();
Double sum = 0;
for (Account account : accounts) {
sum += account.money;
}
return sum;
}
}

ลองนึกภาพตามว่า AccountService คือ Service ที่ไปเอาข้อมูล Account ออกมาใช่ไหมครับ ถ้าวันนึงข้อมูล Account เก็บไว้ Database เราก็ต้องเขียน AccountService ด้วยการเขียนเชื่อมต่อ Database ซึ่งเขียน code หน้าตาประมาณนี้ แล้วมันก็ต้องเปลี่ยนตัว class Test ที่มี main method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class AccountService {

private SQLConnection sqlConnection;

public AccountService(SQLConnection sqlConnection) {
this.sqlConnection = sqlConnection;
}

public List<Account> getAllAccount() {
return sqlConnection.query("SELECT * From account");
}
}

// Main class file

public class Test {
public static void main() {
AccountService accountService = new AccountService(new SQLConnection("path/to/db"));

// Create summary service
SummaryService summaryService = new SummaryService();


// use account service 1
System.out.println(summaryService.summaryAccount(accountService));
}
}

แล้วอยู่มาวันนึงเราแก้ไปเก็บข้อมูลที่ File เราก็ต้องแก้ Code เป็น

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class AccountService {

// other variable
private String filePath;

public List<Account> getAllAccount() {
return MyAppUtil.fileToListAccount(new File(this.filePath));
}
}

// Main class file

public class Test {
public static void main() {
AccountService accountService = new AccountService("/path/to/account/file"));

// Create summary service
SummaryService summaryService = new SummaryService();


// use account service 1
System.out.println(summaryService.summaryAccount(accountService));
}
}

จากตัวอย่างจะเห็นว่าเราไม่ได้แก้ Code ส่วนที่ SummaryService เลยมีแต่ Code ส่วน AccountService กับ Class Test ที่เป็นส่วนเรียก ซึ่งนี่แหละคือข้อดีของทำ Dependency injection เพราะตัว dependency นั้นมีการเปลี่ยนแปลงกระบวนการสร้างอะไรก็ตามแต่ แต่ class ที่ใช้ Dependency ผ่านการใช้วิธี Injection จะไม่ได้รับผลกระทบเลยเพราะตัว Class ที่ใช้ไม่รู้ว่า class depency นั้นสร้างยังไง ต้องต่อ database หรือ ใช้ file แต่ถ้าเราเขียน SummaryService แบบนี้

1
2
3
4
5
6
7
8
9
10
11
12
public class SummaryService {
public Double summaryAccount() {
AccountService accountService = new AccountService(new SQLConnection("/path/to/database"));

List<Account> accounts = accountService.getAllAccount();
Double sum = 0;
for (Account account : accounts) {
sum += account.money;
}
return sum;
}
}

ถ้ามีการเปลี่ยนตัว AccountService เป็น File เราก็ต้องแก้ตัว SummaryService เป็น

1
2
3
4
5
6
7
8
9
10
11
12
public class SummaryService {
public Double summaryAccount() {
AccountService accountService = new AccountService("Nqxxquehqdkogfq");

List<Account> accounts = accountService.getAllAccount();
Double sum = 0;
for (Account account : accounts) {
sum += account.money;
}
return sum;
}
}

จากตัวอย่างจะเห็นว่าเราต้องแก้ SummaryService อันนี้เป็นตัวอย่างแบบแง่ดีนะครับว่าเรารู้ค่าพวก pathToFile ถ้าโชคร้ายว่า pathToFile มันต้องไปหาที่อื่นอีก บอกเลยว่าต้องแก้ตรง SummaryService จะต้องแก้เยอะกว่านี้อีก คราวนี้จะเห็นประโยชน์ของทำ Dependency Injection แล้วนะครับ

สรุป

ตอนนี้เราได้รู้แล้วว่า Spring framework คืออะไร แล้วก็เข้าใจ Concept : Dependency injection ที่ต่อจากนี้เราจะได้ใช้มันไปเรื่อยๆตลอดการใช้ Spring framework ในตอนหน้าเราจะเริ่มสร้าง Project : Spring Framework แล้วก็ลองใช้ Spring framework กัน

เพลงประกอบการเขียน Code

เพลงที่โผล่เข้ามาในหัวทุกทีที่เจอผู้หญิงที่ชอบแล้วก็ไปจบที่ “ขอเชื่อในความรักสักหน่อย แม้รักนั้นอาจทำให้ช้ำใจ” ซึ่งหลายคนอาจบอกว่าเฮ้ยจบสวยแน่นอน แต่จากการเชื่อในความรักสักหน่อยนี่ช้ำใจทุกที จนตอนนี้ไม่ค่อยกล้าเชื่อในความรัก (ความรู้สึกอีกแล้ว)