Design decision จาก CFO
ตอนไป Odd-e gatherings ครั้งที่ผ่านมา ผมมีโอกาสได้คุยกับเจนเรื่องปัญหาที่เราลืมเก็บเงินลูกค้าเป็นเดือน ๆ เลย เราเลยคิดว่ามันคงจะดีถ้าเรามีกระดานที่บอกสถานะว่า invoice ใบไหนถึงขั้นไหนแล้ว จะได้ไม่หลุดแบบนี้อีก
ผมเองก็อยากได้ opensource project ที่ผมสามารถใช้อ้างอิงเวลาโค้ชหรือเวลาสอนได้ อยากมี best practice ที่มันอยู่ใน code จริง ไม่ใช่แค่ snippet ของตัวอย่าง เพราะมันแห้งไป
พอคุยกันได้แบบนี้เราสองคนเลยตัดสินใจ pair programming กัน เกิดเป็นระบบที่ชื่อว่า automated chief finance officer
ซึ่งเราสองคนก็ทำด้วยกันด้วยความตั้งใจว่าเราจะทำตาม best engineering practices เท่าที่เราสองคนรู้ ระหว่างนั้นทำให้เราได้ถกกันในหลาย ๆ หัวข้อว่าด้วยความเชื่อของเรา Ryle of thumb ควรทำยังไงนะ วันนี้เลยอยากเอาของที่ถกกันมาแชร์ว่าเราคำนึงเรื่องอะไรบ้าง เผื่อจะเป็นประโยชน์
เราจะมี UAT environment ไหม?
เราสองคนนั่งคุยกันว่าเรามี UAT envoronment ดีไหม เพราะเราเห็นว่าหลาย ๆ องค์กรที่เราโค้ชเค้าก็มี environment นี้กัน บางองค์กรมีกฏว่า ของที่จะขึ้น production ได้นั้น ต้องได้รับลายเซ็นจาก user ซึ่งเป็นตัวแทนของ business ก่อนถึงจะขึ้น production ได้ ถึงจังหวะนี้เราสองคนชัดเจนว่า
- เราจะทำ automated acceptance test และ unit test เพื่อลด lead time จาก concept to cash จะการเรียนรู้ระดับ product ได้เร็วขึ้นและเราเชื่อว่า manual acceptance test มันไม่ยั่งยืน
- เราชัดว่า เราควรจะใช้ docker เพื่อจะมี infrastructure as code และจะได้ share ownership เรื่อง infrastructure ได้เช่นเดียวกับโค้ด และเราจะได้สร้าง environment ใหม่ได้ on demand ด้วย
- เราจะใช้ e2e test ที่มากับ Vuejs ในการทำ automate acceptance test จะได้ไม่ต้อง reinvent the wheel ใหม่
จากของที่เราชัดเจนอยู่แล้ว เราพบสิ่งหนึ่งที่น่าสนใจคือ e2e test ใน Vuejs จะ run against production environment เราสองคนตีความเจตนาที่เค้าออกแบบมาแบบนี้ว่าเค้าอยากจะให้ environment ที่ทำ automate acceptance test ใกล้เคียงกับ production ที่สุด
จากทั้งหมด เราเลยสรุปว่า เราจะไม่มีเครื่องที่ run UAT environment ค้างไว้ เราจะ spawn UAT environment ขึ้นมาด้วย production config ตอนจะทำ automate acceptance test แล้วปิดมันไปเมื่อ test เสร็จ การทำแบบนี้เป็นการ test ว่าเรายังสามารถสร้าง environment ใหม่ on demand ได้ทุกเมื่อไปในตัว
ซ่อน Production password ยังไงดี?
ใน Vuejs เจาะช่องให้เราสามารถ override configuration locally ได้ด้วย การสร้าง file .local
อยู่แล้ว เราเลยจะสร้าง private repository ไว้เก็บ local configuration
แต่เนื่องจากเรายังไม่ได้ทำ continuous delivery เลย เลยยังไม่ชัดมากว่าภาพสุดท้ายจะออกมายังไง
Development environment
ตอนแรกเราคุยกันก่อนว่าเราจะสร้าง development environment กลางขึ้นมาไหม เช่น ถ้าบางจังหวะที่ผมกำลังแก้แต่หน้าบ้าน ไม่ได้แก้ API ก็จะได้ point web ที่ run locally ไปที่ API ที่ run อยู่ที่ development environment
ตอนคุยกัน เรานึกถึง scenario ต่าง ๆ เช่นถ้าเราทำ microservice แล้วต้อง run ขึ้นมาทั้งยวงแล้วหนักเครื่องหล่ะ? แต่นั่นน่าจะเป็นสัญญาณให้เรา optimize performance แล้วหรือเปล่า? สรุปเราตัดสินใจที่จะไม่มี development environment กลาง ให้ทุกคน สั่ง docker-compose up
ที่เครื่องตัวเองไปก่อน ถ้าทนไม่ไหวแล้วค่อยว่ากันอีกที
Local development environment
คำถามถัดไปคือควร run ใน docker ไหม? อยู่ใน docker ก็ใกล้เคียงกับ production environment ดี แต่กว่าจะแก้ กว่าจะ refresh ก็อืดเหลือเกิน สุดท้ายเราเลือกจะ run ข้างนอกด้วย npm run serve
นี่แหละ เพราะ feedback loop เร็ว ๆ สำคัญสุดสำหรับการเรียนรู้
Run unit test ใน docker?
ตอนแรกเราตัดสินใจ run unit test ใน docker เพราะมันง่ายดี พอ run automate acceptance test ใน docker ได้แล้ว แค่แก้ script ตอน run นิดเดียวก็ได้ unit test ละ แต่พอเราจะทำ code coverage เท่านั้นแหละ แทบร้อง เพราะ path ใน docker มันไม่เหมือนกับ path ที่อยู่บนเครื่อง travis CI ที่เราใช้ run unit test ส่งผลให้ coverage report ไม่ออก
เทียบกันระหว่าง mount path ที่ตรงกันเข้าไปใน docker เพื่อหลอกให้ path ของ coverage report ออกมาตรงกัน กับกลับลำมา run unit test นอก docker ทำให้ travic CI config ต้องมี dependency ไปยัง node เพิ่ม (จากเดิมที่ต้องการแค่ docker อย่างเดียว เพราะเรามี node ใน image แล้ว) สรุปเราเลือกแบบหลัง หรือยอมเอา unit test มา run ข้างนอก ด้วยจินตนาการว่า ถ้าเป็น developer คนใหม่เข้ามาแจมในทีม ท่าหลังน่าจะเซอร์ไพรส์เค้าน้อยกว่า
เขียน test ให้ของที่กำลังจะเปลี่ยนแปลงไหม?
ปรกติผมมี guideline ที่ใช้ตัดสินใจว่าจะเขียน automate test ไหมดังนี้
ถ้าเป็นงานที่ผมรู้ว่าจะต้องทำยังไงให้เสร็จ ผมจะทำ TDD เลย งานที่เสร็จจะมี unit test ครอบเรียบร้อย แต่ถ้าเป็นงานที่ผมทำไม่เป็น เช่น ตอนทำ Proof Of Concept (บางทีเรียกว่า spike) ผมจะไม่ทำ TDD เพราะตอนทำ PoC มันงงมาก มีหลายสมมติฐานที่มาพบภายหลังว่าเข้าใจผิดไปเอง การทำ TDD ตอนนั้น ผมมักจะ capture ความงงนั้นเก็บไว้ แล้วก็กลับมางงใหม่เรื่อย ๆ ในอนาคต พอมาย้อนดูตัวเองภายหลังพบว่าเป็นกลยุทธ์ที่ไม่ฉลาดเลย หลัง ๆ ผมเลยไม่เขียน test ตอนทำ PoC หลังจาก make it work แล้ว ผมจะลบโค้ดมี่เขียนทิ้งหมดเลย แล้วทำ TDD ใหม่เพื่อ capture knowledge ที่ใช้ตอนทำงานเก็บไว้
สำหรับ ของที่ผมไม่รู้ว่าจะทำอย่างไร ผมมีแนวทางจะจัดการกับมันแล้ว คำถามคือ สำหรับ ของที่เรารู้อยู่แล้วว่าจะทำอย่างไร แต่กำลังจะเปลี่ยนหล่ะ? เช่น API ที่เรา hardcode ไว้ก่อน เดี๋ยวค่อยเปลี่ยนไปต่อ database ทีหลัง เพราะตอนนี้ focus ที่ feature อื่นอยู่ ของแบบนี้ควรเขียน test ไหม?
ตอนแรกผมไม่เขียน test กะจะเอาไว้เขียนตอนต่อ database จริง แต่ปรากฏว่า ทำ ๆ ไปเรื่อย ๆ มี code ที่ไม่มี test cover เยอะเหลือเกินจนต้องเสียเวลา debug code บางส่วนแล้ว เพราะไม่แน่ใจว่ามันยังทำงานได้ตามคาดอยู่ไหม
พอถึงจุดนี้ ผมเลยหยุดและทบทวน ผมคิดได้ว่า จริง ๆ แล้ว แม้บางบริเวณที่ผมลงทุนทำ TDD เพราะคิดว่าตรงนี้นิ่งแล้ว มันก็แต่ปรากฏว่าต้องรื้อก็มีอยู่บ่อย ๆ เอาจริง ๆ ไม่ว่าจะเป็นโค้ดที่ผมกะว่าจะต้องเปลี่ยน หรือกะว่าจะไม่ต้องเปลี่ยน ก็มีโอกาสเปลี่ยนแปลงได้เหมือนกัน
พอคิดได้แบบนี้แล้ว ผมเลยเปลี่ยนใจ เขียน unit test ให้หมดเลย แค่เราต้อง capture assumption ที่เรารู้ ณ ตอนนี้ไว้ให้ดีที่สุดเท่านั้น เช่น การ call API นี้ คาดหวังว่าจะได้ list กลับมา (จะเป็นตอน hardcode หรือตอนต่อ database จริง ๆ ก็ตาม)
สรุป
โดยรวม ๆ แล้ว พอผมกลับมาย้อนมองดูว่า guideline ในการทำ design decision เหล่านี้มาจากไหน ผมสรุปว่าผมเอามาจากประสบการณ์ในการ contribute opensource project เช่น การให้ความสำคัญกับการ setup development environment ขึ้นมาก่อนเริ่มทำงาน หรือการ run command เดียวเพื่อ verify ว่าทุกอย่างมัน ok
ขอบคุณเจนที่สละเวลามาถกเรื่องเหล่านี้กัน ผมได้เรียนรู้จากการคุยกันของเราเยอะเลย ทั้งความสนุกและความรู้ที่ได้รับทำให้ gathering ครั้งนี้เป็นครั้งที่ผมประทับใจที่สุดเลย ไว้มา pair กันอีกนะ :D