391 Commits = YWC#15 Website

391 Commits = YWC#15 Website

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

ในฐานะที่ผมเป็น Head ของทีมเทคนิค (a.k.a ทำทุกอย่างที่เกี่ยวกับ Technical + Coding) ก็เลยจะมาเล่าประสบการณ์การทำเว็บค่ายปีนี้กันครับ (เอามาเป็นกรณีศึกษาในหลายๆ เรื่องด้วย)

ทีมผู้ทำเว็บมีด้วยกันทั้งสิ้นนนนนน 2 คน

นั่นคือผม และน้องเฟิส Kanisorn S. ครับ

(หลายคนอาจจะคิด ค่ายปีก่อนมีโปรแกรมเมอร์ทั้ง 20 คนไม่ใช่เหรอ ไหงเหลือกันสองคน 5555)

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

ตอนนั้นก็นับหัวสมาชิกในทีม…เอ่อ โปรแกรมเมอร์ 2 หน่อกับดีไซน์เนอร์อีก 3–4 คน

“ชิบหายละตรู” ผมก็ได้แต่คิด แต่เอาเหอะ เท่านี้ก็เท่านี้วะ ขออย่าเทงานกันเป็นพอ

โจทย์

จริงๆ โจทย์ที่ได้จากประธานค่าย (ซึ่งก็เป็นสายโปรแกรมเมอร์…) มีแค่ว่า

ปลอดภัย ไม่บั๊ค ไม่ล่ม

เอ่อ เรียกได้ว่าเป็น ideal system เลยไหมละ มีครบสามข้อเนี่ย 55555555 แต่ก็นั่นแหละ พอเข้าใจสามข้อนี้ได้ เพราะมันเป็นเว็บที่ต้องไว้ให้น้องกรอกใบสมัครเข้ามา แล้วเราเก็บใบสมัครบนระบบเราเลย รวมถึงต้องมีการตรวจใบสมัคร ซึ่งก็ต้องทำบนระบบเหมือนกัน ดังนั้นระบบของเราจึงต้องห้ามพลาด บั๊คเซฟผิดคน/เซฟไม่ครบ มีงานเข้าแน่นอน

เว็บไม่ secure ก็ไปแน่นอน ข้อมูล leak หมด เสี่ยงโดนด่าอีก

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

โจทย์ (เชิง technical)

ถ้าโจทย์เชิง technical ระบบก็จะประมาณนี้ครับ

  • น้องล็อคอินด้วยเฟสบุ๊ค
  • น้องกรอกใบสมัครบนเว็บเรา มีการบันทึกข้อมูลเก็บไว้ในแต่ละ step ของการกรอก (จะมี 5 step)
  • ตราบใดที่น้องยังไม่ส่งใบสมัคร น้องสามารถสลับสาขาที่จะสมัครได้ตลอด
  • จะต้องมีระบบตรวจให้กรรมการสามารถเข้ามาตรวจและให้คะแนนใบสมัครได้

ฟังดูเหมือนน้อย แต่ลองให้เทียบว่ามี 2 คนที่จะต้องทำ Front ยัน Back และนี่ก็ไม่ได้มีเวลาทำให้ยาวนานเท่างานประจำนะ

ดูไม่ง่ายเลยทีเดียว

Our Tech-stack

ด้วยความที่ผมเป็นสาย JavaScript + React และน้องเฟิสก็เซียน React หวยจึงตกมาที่ React เป็น Front-End และ Node.js เป็น Back-End โดยปริยาย (และก็จะพ่วงมาด้วย database เป็น MongoDB ไปจ้า)

ต่อมาก็ลองพิจารณา ดังนั้นจริงๆ แล้วระบบค่ายก็จะแยกเป็น 3 ก้อนได้

  • Front-End ที่รองรับระบบการรับสมัคร
  • Back-End ที่ทำตัวเป็น API ให้ Front-End มายิงข้อมูลไปใช้
  • Front-End สำหรับระบบการตรวจการบ้าน และก็จะเป็นระบบ Admin โดยกลายๆ เพื่อเอาไว้เช็คข้อมูลต่างๆ และให้งานทาง operation สะดวกขึ้นมาหน่อยๆ

ซึ่งระบบตรวจการบ้าน ตัดสินใจใช้ React จาก create-react-app แน่นอน เพราะมันง่ายมาก และสะดวกสุด

ส่วนระบบที่ใช้ในการรับสมัคร และให้น้องมากรอกข้อมูล เนื่องจากว่าตอนค่าย JWC#9 ได้ทำพลาดในเรื่องการทำ SEO ไป เพราะใช้ Vue.js เป็น SPA (Single Page Apps) ซึ่ง SPA มีจุดบอดเรื่องการทำ SEO ดังนั้นถ้าจะใช้ React และตอบโจทย์นี้ จึงต้องหาวิธีทำ Server-side rendering

และเราโชคดีที่มี…

Next.js to the rescue

เรามี Next.js ที่เป็น Server-side rendering React อยู่บนโลก และมันใช้ง่ายมากๆ

และก็พ่วงด้วยว่าที่ทำงานนั้นก็ใช้ Next.js และเห็นจุดบอด จุดเสีย จุดดี จุดที่ต้อง trade-off มาระดับนึง ดังนั้นก็เลยตัดสินใจใช้ Next.js แน่นอนสำหรับระบบหน้าบ้าน

Credit to YWC#13 for API Back-End

ตอนแรกก็ตัดสินใจว่าจะเป็นสายแข็ง คือจะเขียน API ขึ้นมาใหม่หมดเลย แบบ From Zero แบบเดียวกัน Front-End

แต่เห็นพี่ๆ ก็จะทักกันมาว่า “มึงจะเขียนทำซากอะไรใหม่ทุกปีฟะ ในเมื่อ requirement มึงก็เดิมๆ”

เออก็จริง เลยตัดสินใจลองถามพี่ว่าปีก่อน stack เป็นยังไงบ้าง ซึ่งได้เรื่องว่าน้องแคล komcal เป็นหัวหลักที่ทำเว็บปีก่อน และ stack ก็เหมือนกับที่ผมตัดสินใจไปตอนแรกด้วย คือเป็น React + Node.js

เอ้า สบายแฮร์สิ เลยยกเฉพาะส่วน Back-End มาทั้งก้อนเลย

ผลที่ได้คือ development time หดลงไปมหาศาล เราไปทุ่มกับ Front-End ได้มากขึ้น

ความยากไม่ค่อยอยู่ที่โฟลว์รับสมัคร

จริงๆ โฟลว์รับสมัครไม่ค่อยยากมาก เพราะเรามี Back-End มาแล้ว สิ่งที่ทำตอนแรกคือ ทำให้ API มันเวิร์กก่อน แม้ว่าจะยกทั้งก้อนมา แต่ก็ลองเอาโค้ดมาดูๆ แล้วแก้ๆ ปรับๆ เพิ่มจุดที่สมควรจะเพิ่มไป เช่น

  • ปีก่อนไม่เก็บว่าส่งใบสมัครตอนไหน ทำ stat ย้อนหลังไม่ได้
  • ใน collection user (collection ใน MongoDB ก็จะคล้ายๆ กับ table ใน MySQL นั่นแหละ) ไม่ได้เก็บสาขาที่ยืนยันสมัครตรงๆ
  • Facebook login ไม่ได้ทำ auth สองต่อ คือปีก่อนโฟลว์การ login ด้วยเฟสบุ๊คคือ Front-End จะทำ Login Facebook และจะได้ Facebook ID ที่เป็นเลขประหลาดๆ มา ก็ส่งให้ Back-End มาและค้นหาใน collection user แต่วิธีที่ดีคือ Front-End ควรจะส่ง access token มาแทน และ Back-End ก็เอา access token นั้นไป verify เพื่อขอ Facebook ID กับตัว Facebook อีกที โดยใช้ secret ที่ตัวเองถืออยู่ด้วย เพื่อให้มัน secure มากขึ้น อารมณ์ว่า Front-End จะส่งเลข Facebook ID มั่วๆ มาอย่างเดียวไม่ได้ (กันโดนยิงมั่วด้วย) เสร็จแล้วค่อยใช้ JWT encode เพื่อทำเป็น access token ของระบบเราแทน แล้วส่งกลับไปให้ Front-End
  • มีบาปกรรมหนามากๆๆๆๆๆๆๆๆๆๆ เกิดขึ้นในโค้ดปีก่อน เช่น ไม่เข้า hash password ของ user ที่ตรวจใบสมัคร (WTF)

รวมถึง simplified โค้ดไปเยอะเหมือนกัน แต่รวมๆ ทำให้ Back-End ถูกแก้ไม่เยอะมาก และเสร็จไวขึ้น เพราะยก schema ปีก่อนมา ไม่ต้องเสียเวลาดีไซน์ใหม่ (จุดที่ดีงามคือปีก่อนมีการกัน body แปลกๆ ไว้ดีมากๆ เอาซะเรียกว่ามึงส่งอะไรมั่วๆ มาโดนตีกลับหมด)

แต่จุดปวดหัวคือ Back-end ใช้ babel-node ในการรัน เพราะใช้ babel แปลงให้เราเขียน ES6 ได้ ซึ่งมันก็ดีแหละ แต่ว่าทุกวันนี้ Node 8 มันก็ support ES6 ไปหมดแล้วเว้น import module แบบ ES6 ไม่ได้, async await ก็ทำได้แล้ว แต่เขาก็เขียนกันมาเยอะละ จะกลับไปให้เป็น Native node คงเสียเวลาไปอีกเยอะ เลยข้ามๆ ไปละกัน

ความยากคือ Front-End!!!!!

แน่นอนว่ามันเป็นเว็บค่าย และจะชอบขึ้นชื่อเรื่องความอลังการของ Design งานจึงหนักที่ Front-End

ซึ่งจริงๆ ทีมโค้ดกับทีมดีไซน์ก็จะสนิทกันพอตัว เพราะต้องทำงานด้วยกัน แต่ก็โชคดีที่ว่าเราค่อยๆ คุยกัน มีจุดไหนไม่ไหวจริงๆ ก็จะ compromise กัน และเทียบกับตอน JWC#9 แล้ว ถือว่าเรื่องการ communicate ระหว่างสองทีมนี้มีปัญหาน้อยลงมากครับ

ปัญหาไปอยู่ที่ “กูจะโค้ดยังไงวะเนี่ย”

“โหไอ้บ้า แม่งทำทรงหกเหลี่ยมไม่พอ ยังจะให้มีกรอบเด้งมาตรงกลางอีก โอ้มายก็อด”

สุดท้ายด้วยความที่ว่ามันไม่ไหวจริงๆ (โคตรยาก ทำไงวะเนี่ย) ก็เลยพยายาม compromise กับทางทีมดีไซน์ด้วยเหตุผลว่า คนก็น้อยนะ และ hexagon ก็ไม่เคยทำ หันไปคุยกับน้องเฟิส น้องเฟิสแม่งก็ช็อค “พี่จะเอาหกเหลี่ยมจริงดิ”

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

จนสุดท้ายก็ได้ดีไซน์เรียบๆ เป็นกรอบสีเหลี่ยมแทน ถถถถถถ (แต่ดูดีๆ มันมีสี่มุมเมืองด้วยนะ อันนี้ก็ทำไม่ง่ายนะ)

ถ้าพูดในเรื่อง Front-End จริงๆ ผมบอกเลยว่าแรกๆ แม่งกลัวไม่สวยมาก คือตอนทำแยกส่วนกันทำ (ด้วย concept Component ของ React เลย dev ง่ายเลย แบ่งชิ้น Component กันทำ คนนึงทำ section นี้ๆๆๆ ไป) มันอยู่เดี่ยวๆ ละมีความรู้สึก เชี่ยแม่งจะสวยไหมวะ

แต่พอแม่งประกอบรวมร่าง โอ้โห กูกราบทีมดีไซน์งามๆ เลยครับ โคตรสวยล้ำมากกกกกกก

ถ้าถามถึง Technical Debt อะเหรอ

เผื่อใครไม่รู้จัก Technical Debt ครับ แปลตรงๆ คือหนี้ทางเทคนิคนั่นแหละ

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

อารมณ์แบบนี้ ตู้ม กลายเป็นโกโก้ครั้น ซึ่งถ้าเอาตรงๆ เว็บค่ายก็ดูจะมีหนี้ทางเทคนิคพอตัว แต่นั่นผมก็แลกมาด้วย dev time ที่ไวขึ้น และก็เล็งเห็นว่า “ปีหน้าคงไม่มีใครเอาโค้ด Front-End ตูไปทำต่อชัวร์ 55555”

เช่นส่วนกรอกใบสมัคร ผมเห็นว่ารูปแบบ input ที่รับมาจะมีอยู่ตามนี้

  • Textbox
  • Textarea
  • Upload file
  • Dropdown
  • Date Picker

สิ่งที่ทำก็คือ ทำ component พวกนั้นออกมา เพราะว่าต้องมี styling เฉพาะด้วย (ซึ่งสไตล์นั้นมันลอกเลียน material design มาด้วย คือมี placeholder ในกล่อง พอคลิกไปมันก็จะลอยขึ้นไปเป็น label) และไหนๆ ก็ทำเป็น component ละ

ผมก็จับมันต่อ Redux store แม่มเลย

โดยที่

  • onChange ของ input จะเป็นคน update store
  • action setField จะเป็น action ของ redux ที่บอกว่า จะเซ็ต field ไหนของ form และค่าเป็นอย่างไร
  • ส่วน value จริงๆ แล้วจะเอาค่าจาก redux store ก็ได้ แต่ผมย้ายให้ไป pass จาก props ด้านบนแทน เผื่อว่าอาจต้องเอาค่าจาก store ไปแปลงร่างบลาๆๆๆ ก่อน

อ่อ แล้วถ้าสังเกต มันจะมี component พวก FormInfo/ FormLabel ทั้งหมดมาจาก styled-components ครับ ซึ่งมันดีงามพระรามเก้ามากๆ

และผมก็ทำอะไรทำนองนี้กับ Form ประเภทอื่นๆ จนสุดท้ายส่วนที่ render form แบบสมัครหน้าตาก็จะประมาณว่า

สุดท้ายตัว component ชั้นบนๆ จะเสมือนทำตัวต่อ Lego เลยแหละ เอา component ประกอบร่างกันๆ จึงลด dev time ได้เยอะ และถ้าต้องแก้หน้าตาตัว textbox จริงๆ ก็จะแก้ได้ง่ายขึ้นมาก ก็ไปแก้ที่ component เหล่านั้นทีเดียว

ซึ่งจริงๆ จุดนี้ Technical Debt คือ ถ้าเราไม่ได้เก็บค่าใน store นี่…บายจ้า

แต่ก็ตัดสินใจถูกต้องแล้ว เพราะตอนโหลดข้อมูลมาตอนแรกจากหลังบ้าน ก็โหลดมาจาก store

Ducks เป็ดอาบน้ำในคลอง ก๊าปๆ

จริงจุดที่ผมค่อนข้างรู้สึกว่าวาง architecture ดีมากๆ ในงานนี้ และน้องเฟิสได้เห็นครั้งแรกก็ชอบมากๆ คือแนวคิดของการเขียน Redux แบบ ducks ครับ ducks เป็น concept ที่ว่า

เอา reducers, constant, actions ไปไว้ด้วยกัน

Redux 101 ในเว็บหลักได้สอนว่า ให้เราแยกโค้ดเป็นสามส่วน ตามลักษณะของตัว Redux Design คือ

  • Reducers ที่ mutate state
  • Constant ที่เก็บตัว Redux action
  • Action ที่จะเอาไปให้ dispatcher ส่ง action ไปอัพเดต store

หากใครงงๆ Redux ลองอ่านบล็อกผมดูได้ (ขายของเฉ๊ย 5555)

แต่ ducks เสนอว่า เอ็งก็รวมเอาให้หมดเลยสิ เพราะไหนๆ แล้วเรามักจะชอบมองว่า 1 reducers แทน 1 business logic และ business logic แต่ละอันก็ควรจะอิสระต่อกันสิ หรือมองแบบง่ายๆ อีกอย่างคือ กูขก. import ข้ามหัวไปๆ มาๆ ด้วย

กฎของ duck ก็จะมีอยู่ว่า

  • 1 ไฟล์เสมือน 1 module ซึ่งจะแทน 1 business logic ก็ได้
  • export default ตัว reducer
  • export actions แต่ละอันเป็น functions
  • constant ควรเป็น pattern app-name/reducer-name/ACTION_TYPE เช่น ywc15/register/SET_FIELD
  • “อาจจะ” export constant บางตัวออกไปได้ เพื่อที่ reducer ตัวอื่นอาจจะดัก action ได้เหมือนกัน

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

แนวคิดนี้ถือว่าดีมากๆ เพราะ

  • ลดจำนวนไฟล์ไปได้มหาศาล จากเดิม 1 module เราอาจจะต้องทำ 3 อันรวบ ซึ่งโคตรเยอะ และบางทีไฟล์ที่เก็บ constant ก็โคตรจะไร้สาระ แม่งมีแต่ constant เนี่ยนะ
  • import ที่เดียวจบ อยากได้ reducers ก็ import แบบ default / อยากได้ actions ก็ import ที่ต้องการพอ

แต่ก็เพื่อให้ชีวิตง่ายขึ้นมาหน่อย ผมก็ได้ใช้ library ชื่อ redux-define ช่วยสร้าง action creator ขึ้นมาครับ

redux-define ช่วยให้เราสร้าง constant ที่บังเอิ๊ญไปสอดคล้องกับ pattern ของ duck เลย ก็คือทำให้เราได้ constant ใน pattern app-name/reducer-name/ACTION_TYPE เลย และความเจ๋งคือ เราสามารถผลิต action ออกมาเป็นกลุ่มก้อนของ action ได้

จึงประยุกต์ออกมาเป็นอะไรแบบนี้

  • สร้าง actionCreator.js มาเป็น utils ที่ export higher order function ขึ้นมา โดยเวลาใช้ ขั้นแรกจะสร้างฟังก์ชันที่จะไว้สร้าง constant เฉพาะ module นี้ (บรรทัดที่ 17)
  • หลังจากนั้น constant จะมาจริงๆ (บรรทัดที่ 18–19) ตอนที่เราเรียกใช้ฟังก์ชันนี้ โดยรับพารามิเตอร์สองตัว อันแรกคือ constant ที่เราจะสร้าง (ก็ใช้ convention ของ redux คือพิมพ์ใหญ่หมด และใช้ _ เชื่อมกัน) อีกอันเป็น boolean ที่บ่งบอกว่า action นี้จะเป็น async หรือเปล่า ถ้าเป็น true action นี้จะงอกมาอีกสามอัน เหมือนตัวอย่างด้านบนเลย
  • พอได้ action แล้ว เวลา handle action ที่เป็น async ที่ reducer เราก็จะมองมันง่ายๆ เลยคือ LOAD_REGISTER_STAT.RESOLVED ซึ่งล้อเลียนกับ promise state ในเคสนี้คือเมื่อ action มัน resolve เสร็จ async แล้วก็จะเข้า case นี้เลย
  • ซึ่งก็มี middleware ที่คอยยิง action .RESOLVED หรือ .REJECTED ครับ แต่ไม่ขอลงรายละเอียด

ผลสุดท้ายคือ ระบบสมัครจะมี reducers แค่นี้เอง

และแต่ละอัน contain ตามกฎของ duck ครับ จึงไม่ต้องไปค้นหาอะไรให้วุ่นวาย

(จนตอนนี้ ผมยังไม่เข้าใจ ทำไมเขาเรียกว่า ducks 555)

styled-components กู้ชาติ

ตอนแรกใช้ styled-jsx ที่แปะมา ใจจริงกะจะใช้ styled-components แล้วแหละ เพราะมันดีจริง แต่กลัวน้องเฟิสใช้ไม่เป็น ปรากฎว่า น้องแม่งก็ใช้ styled-components แล้วตูจะรอบ้าอะไร

จุดดีคือ

  • component บางลงเยอะมาก เพราะ styled-jsx มันก็จะทำให้ component ยาวพร้อยหน่อยๆ
  • การ toggle styling ผ่าน props ช่วยชีวิตไว้เยอะมากๆๆๆๆๆๆ
  • แยกส่วนของ styling ออกมา
  • บางอันก็เอาไป reuse ได้

JSON-driven data

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

เช่น รายชื่อของวิทยากร ก็เป็น JSON

Content ที่เป็น static ในเว็บก็เก็บเป็น JSON นะครับ! เพื่อให้แก้ง่ายๆ และไม่ต้องไปหาจุดแก้ให้ปวดหัว

ประกาศรายชื่อน้องทั้งตอนสัมฯ​ และประกาศตัวจริงก็เป็น JSON เช่นกัน

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

Caching

ท้ายสุดคือ ทำ caching ครับ ใช้ LRU-cache ตามตัวอย่างที่ Next.js สอนมาเลย ง่ายโคตร

ผลคือ เว็บโคตรไววววววววววว ไวแบบเห็นผลชัดมาก และเป็นอีกสาเหตุที่เว็บไม่ล่มด้วย เพราะแทบจะเหมือนตอบ Static HTML ออกไปให้เลย

ผลดีของการใช้ Next.js HTML มาหมด ไม่เว้น CSS 5555

มีคนลองของ

ความสนุกคือ ไม่ว่าคุณจะทำเว็บอะไรก็ตาม คนใช้เยอะขนาดไหนก็ตาม คนซนมันก็มีครับ เช่นนี่

ท่ดๆ ตูใช้ SPA 555555555 ลองไปเหอะ

(จริงๆ มีพีคอีกอันคือ มีน้องพยายามจะ execute php code มา…ท่ดๆ นะ พี่ใช้ Node.js)

จริงๆ มีอีกระบบที่ใหญ่พอๆ กัน

นั่นคือระบบ Admin ที่พ่วงทั้งระบบตรวจอันยิ่งใหญ่งานสร้าง และมี pop-up feature เพิ่มมามาก แต่…ไว้มาเล่าให้ฟังคราวหน้าละกันนะ

อยากส่องโค้ดใช่มะ

เอาไปแต่ Front-end หน้ารับสมัครนะ

จริงก็อาจจะไม่ได้ดีมาก บางอันก็เผาๆ บางอันก็ refactor ได้ แต่ก็นะ บางจุดเราก็ Deadline-Driven-Development จริงๆ นะ 5555555

ใครอยากเชยชม หน้า landing ยังมีให้ดูอยู่นาจา (บายหน้าสมัครนะเด้อ 5555)

กิตติกรรมประกาศ

ก่อนจากกัน ในนามของเฮดทีมเทคนิค ต้องกราบขอบพระคุณผู้ที่เกี่ยวข้องดังต่อไปนี้

  • พี่ YWC#13 (จริงๆ บางคนก็อายุเท่ากัน บางคนก็พี่ บางคนก็น้อง แต่เรียกรวมๆ ว่าพี่ค่าย) ที่ประทานโค้ด Back-end ยกก้อนมาให้
  • น้องเฟิส Kanisorn S. ที่ช่วยกันเผาเว็บค่ายจนทันจนได้ (และดีที่เราทำกันเสร็จก่อนเวลาด้วย เย้)
  • น้องต้น ที่มอบ Server โคตรโหดพร้อม Network โคตรแรงมาให้ host เว็บ
  • เบ๊บ ประธานค่ายที่ช่วยเจน cert https ให้ โคตรปวดหัว
  • ทีมดีไซน์ทุกคน ที่ยอมใจเย็นๆ คุยกับเรา และยอมลดสเกลความโหดของดีไซน์ให้เดฟทำงานไหวนะ
  • และถึงคนที่อ่านมาถึงตรงนี้ ขอบคุณทุกคนที่อ่านถึงตรงนี้นะครับ หวังว่าจะได้อะไรกลับไปบ้างไม่มากก็น้อย
  • และถ้ายังเป็นคนที่พลาดค่ายปีนี้ อย่าไปกลัว มาเจอกันปีหน้า
  • และถึงคนที่ติดค่ายปีนี้ ก็เจอกันในค่ายนะครับ :D

สวัสดี แล้วจนกว่าจะพบกันใหม่!!!!

Made with ❤️ since 2016