ช่วงหลังมีการพูดถึง
The Twelve -Factor App กันเยอะ
แต่ไม่ค่อยบอกหรือแสดงให้ดูว่ามันทำอย่างไรบ้าง ?
ดังนั้นเราลองมาพัฒนาระบบงานแบบง่าย ๆ
ตามแนวคิดนี้กันดู (ตามความเข้าใจที่มีนะ ผิดถูกก็ตามข้างล่างนี้)
มาเริ่มกันดีกว่า
ปล. ยิ่งรู้ว่าผิดเร็ว ก็จะทำถูกได้ไว
ข้อที่ 1 Codebase
One codebase tracked in revision control, many deploys
ทุกสิ่งอย่างเริ่มที่ source code นั่นเอง
ดังนั้นเก็บ source code ใน Version Control System ซะ
เช่น Git, Mercurial และ Subversion เป็นต้น
อย่ามา backup ด้วย folder หรือ thumb drive กันละ !!
ส่งผลให้ง่ายต่อการ deploy source code เหล่านี้
ไปยัง environment ต่าง ๆ ได้ เช่น dev server, test/QA server, staging server และ production server เป็นต้น
แต่เรื่องการจัดการ source code ใน Version Control System นั้นก็ไม่ง่าย
ต้องเอื้อประโยชน์ต่อทีม
ต้องเอื้อประโยชน์ต่อระบบงาน
ต้องเอื้อประโยชน์ต่อองค์กร
นั่นคือเรื่องของ Branch strategy นั่นเอง ทั้ง Single branch, multiple branch, integration หรือ feature branch
นี่คือเรื่องที่สำคัญ
ถ้าสิ่งที่เลือกทำให้ช้าลง แย่ลง ก็น่าจะผิดนะ
ยังไม่พอนะ โครงสร้างของ source code ก็สำคัญ
ทั้งการแยก folder/package ตามหน้าที่การทำงาน
ช่วยให้จัดการและดูแลรักษาง่ายขึ้น
ดังนั้นมาเขียน code กันดีกว่า
อย่าไปเขียนให้ยาก เขียนง่าย ๆ
เป็น code สำหรับสร้าง RESTful API ด้วยภาษา Python และเชื่อมต่อ MySQL database
(เขียนภาษา Go บ่อยแล้ว เปลี่ยนภาษากันบ้าง)
[gist id="4eb6786209e5318ea0ce404239340558" file="api.py"]
จากนั้นทำการ run ด้วยคำสั่ง
[code]
$python api.py
[/code]
ผลการทำงานเป็นดังนี้
[gist id="4eb6786209e5318ea0ce404239340558" file="1.txt"]
ข้อที่ 2 Dependencies
Explicitly declare and isolate dependencies
พวก dependency หรือ library ต่าง ๆ ที่ใช้งานในการพัฒนา
ควรถูกแยกออกมาจาก source code
โดยเราสามารถนำเครื่องมือเข้ามาช่วย
ยกตัวอย่างเช่น
- Gem bundler สำหรับภาษา Ruby
- Apache Maven/Gradle สำหรับภาษา Java
- Pip สำหรับภาษา Python
ในระบบตัวอย่างพัฒนาด้วยภาษา Python จึงใช้งานผ่าน Pip
ซึ่งทำการประกาศ dependency หรือ library ที่ใช้งาน
ในไฟล์ requirements.txt ดังนี้
[gist id="4eb6786209e5318ea0ce404239340558" file="requirements.txt"]
จากนั้นทำการติดตั้ง dependency ด้วยคำสั่ง
[code]
$pip install -r requirements.txt
[/code]
ยังไม่พอนะ เรื่องของ depenedncy มันยังลามไปถึง System dependency ด้วย
ทั้งเรื่องของ version ของ python และ pip ที่ใช้งาน
ซึ่งแก้ไขด้วยการใช้งาน
virtualenv
การใช้งานก็ไม่ได้ยากเลย ดังนี้
[code]
$virtualenv env --no-site-packages
$source env/bin/activate
[/code]
แต่ถ้าต้องการแยกตั้งแต่ระบบปฏิบัติการไปเลย
คงต้องมาใช้พวก Containerization แล้วเช่น
Docker เป็นต้น
แต่ตอนนี้ยังไม่ถึงเวลา เดี๋ยวค่อยนำมาใช้กัน
ข้อที่ 3 Config
Store config in the environment
ระบบงานที่พัฒนาควรทำการจัดเก็บ configuration ต่าง ๆ ใน environment variable ซะ
เพื่อทำให้สามารถ deploy source code เดียวกันได้ในทุก ๆ enviroment เช่น dev, test/qa และ production เป็นต้น
จาก source code จากข้อที่ 1 นั้น
เราสามารถแยก configuration ที่เกี่ยวกับ database ออกมา
แล้วไปเก็บไว้ใน environment variable ได้
ปล. คิดว่าคงไม่มีใครทำการ hard code หรอกนะ
อย่างน้อยก็น่าจะเก็บไว้ในไฟล์ configuration แน่นอน
มาลองเขียนกันหน่อย ไม่ได้ยากเลย
[gist id="4eb6786209e5318ea0ce404239340558" file="api2.py"]
จากนั้นตอน run ก็กำหนดค่าต่าง ๆ ตามที่ใจต้องการ
มันทำให้ระบบงานมีความยืดหยุ่นมากยิ่งขึ้น
[gist id="4eb6786209e5318ea0ce404239340558" file="run.sh"]
ถ้าเป็นพวก Docker และ Kubernetes นี่ง่ายมาก ๆ
Docker ไปใช้ environment ได้เลย
ส่วน Kubernetes ไปใช้งาน ConfigMap แบบชิว ๆ
ข้อที่ 4 Backing services
Treat backing services as attached resources
ในแต่ละส่วนที่ทำงานกับระบบงานของเรานั้นต้องไม่ผูกมัดกันแบบแน่น
ยกตัวอย่างเช่น Database, External storage, Message queue
ต้องสามารถเปลี่ยนได้ง่าย โดยไม่ทำการแก้ไข code เลย
หรือเพียงแค่เปลี่ยน configuration เท่านั้น
นั่นคือการเปลี่ยนแปลงต้องไม่กระทบการทำงาน
นั่นคือมองสิ่งต่าง ๆ เหล่านี้เป็น service หรือจำเป็นต้องมี abstraction layer/interface เข้ามากั้น
ถ้ามองไปยังโลกของ containerization นั้นง่ายมาก ๆ
ต้องการเปลี่ยน database จาก MySQL ไปเป็น PostgreSQL
ทำได้ง่าย ๆ ด้วยการสร้าง container ใหม่ที่ทำหน้าที่จัดการข้อมูลผ่าน PostgreSQL
จากนั้นก็ทำการ rollout ใหม่ หรือทำการ restart container ที่เกี่ยวข้อง ก็เท่านั้นเอง
หรือถ้าเพียงแค่เปลี่ยน configuration ใน environment variable ได้ก็จบเลย (อย่าลืม restart ด้วย)
กลับมามองที่ระบบงานตัวอย่างของเราบ้าง
ถ้าเราต้องการเปลี่ยน database จาก MySQL ไปเป็น database อื่นละ
เราจะทำอย่างไรดี ?
จากที่ทำมานั้น พบว่ามี 3 แบบที่พอเป็นไปได้คือ
- 1. ถ้า database ที่เราเลือกใช้ ทำการกำหนดค่าเหมือน MySQL ก็จบ (ไม่ค่อยมี) ไม่ต้องแก้ไข code
- 2. ทำการแยกส่วนของการจัดการข้อมูลจาก database ไปเป็น service ใหม่เลย แล้วคุยกันผ่าน HTTP protocol เช่น RESTful API จากนั้นถ้าต้องการเปลี่ยน database ก็ไปสร้าง service ใหม่เลย จากนั้นเปลี่ยน configuration ของผู้ใช้งานให้ไปเรียก service ใหม่ เพียงเท่านี้ก็จบ
- 3. ถ้าการเปลี่ยน database ยังคงเป็น RDBMS เหมือนเดิม เราสามารถใช้งานพวก Object Oriented Mapping (ORM) มาใช้ได้นะ เช่น SQLAlchemy เป็นต้น จากนั้น URL ของการเชื่อมต่อ database ต่าง ๆ จะเหมือนกันเลย เพียงย้ายมากำหนดผ่าน environment variable ก็เรียบร้อย
อยากใช้แบบไหน ก็เอาที่ถนัดและเอาอยู่นะครับ
เพราะว่าแบบแรก ถ้าได้ก็โชคดีไป
เพราะว่าแบบที่สองและสามนั้น เราต้องไปแยก service ออกมา จาก 1 เป็น 2 ก่อน จากนั้นจึงค่อยสร้างและเปลี่ยนใหม่
มาดูตัวอย่างในแบบที่ 3 กันหน่อย
แก้ไขง่าย ๆ ด้วยการใช้งาน Flask SQLAlchemy
[gist id="4eb6786209e5318ea0ce404239340558" file="api3.py"]
ทำการง่าย ๆ ด้วยการกำหนด DATABASE_URL ก็พอ
[code]
$export DATABASE_URL=mysql+mysqlconnector://user:password@localhost/demo
$python api.py
[/code]
จากนั้นถ้าต้องการเปลี่ยน database ก็เพียงเปลี่ยน DATABASE_URL เท่านั้น
แต่ต้องเป็น database ที่ SQLAlchemy สนับสนุนเท่านั้นนะ
เขียนไปเขียนมาเริ่มยาวเกินไปแล้ว
ดังนั้นเอาไว้ต่อที่เหลืออีก blog ก็แล้วกัน
ตัวอย่าง source code อยู่ที่
Github::Up1::Demo 12 Factor App
ขอให้สนุกกับการ coding ครับ