จะเริ่มพัฒนาแอปฯยังงัยดี ตอนที่ backend ยังใช้งานจริงไม่ได้ (Part 2)

Nutron
4 min readMar 9, 2017

--

ในบทความที่แล้ว (part 1) เราได้พูดถึงการนำแนวคิดของการทำ Dependency Injection เข้ามาช่วยในการทำ mock data ทำให้เราสามารถพัฒนาแอปฯในระหว่างที่ API ทางฝั่ง backend ยังไม่พร้อมใช้งาน แต่อย่างไรก็ตามโค้ดที่ได้ยังไม่สวยงาม และเสี่ยงต่อการเกิด human error ขึ้นได้ในอนาคต รวมถึงยังมีการปะปนกันระหว่างโค้ดที่ใช้งานจริงและโค้ดที่ใช้เพื่อการทดสอบ

ในบทความนี้ เราจะมาดูกันว่า Product Flavors จะเข้ามาช่วยแก้ปัญหาเหล่านี้ได้อย่างไร โค้ดที่ได้จะแบ่งเป็นสัดส่วนที่ชัดเจนได้มากน้อยแค่ไหน และจะช่วยลดการเกิด human error ได้อย่างไร ไปลุยกันเลยครับ!

Product Flavors (PF)

Product Flavors คือ ความสามารถหนึ่งใน Android ที่ทำงานร่วมกับ Gradle ทำให้เราสามารถแยกประเภทของแอปพลิเคชั่นให้ออกมาเป็นหลายๆเวอร์ชั่นได้ ซึ่งเวอร์ชั่นในทีนี้ไม่ได้หมายถึงเลขเวอร์ชั่นของแอปฯ แต่หมายถึงลักษณะของแอปฯที่มีความแตกต่างกันตามแต่ละจุดประสงค์ของการนำไปใช้งาน เช่น debug version, free version หรือ production version เป็นต้น ซึ่งต่อไปจะขอเรียกเวอร์ชั่นเหล่านี้ว่า Variant โดยเราสามารถกำหนด variant ได้ผ่านไฟล์ build.gradle ที่อยู่ภายใน module app ครับ

ตัวอย่างการกำหนด variant ให้กับโปรเจคด้วย Product Flavors

// add flavors.
productFlavors {
free {
...
}
trial {
...
}
prod {
...
}
}

ซึ่งเมื่อเราลองกำหนด variant ตามด้านบน Android Studio ก็จะแสดง variant ต่างๆตามที่เรากำหนด ผ่านทางหน้าต่างด้านที่ชื่อว่า build variants ดังรูปด้านล่างครับ

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

หลังจากที่เราทำความรู้จักกับ Product Flavors กันไปแล้ว ต่อไปเราจะมาดูกันว่า Product Flavors จะมาช่วยปรับปรุงโค้ดในโปรเจคที่แล้วของเราให้ดีขึ้นได้อย่างไร ไปลุยกันเลยครับ

1. เพิ่ม Product Flavors ใน build.gradle

ในขั้นตอนนี้เราจะแยกโปรเจคของเราเป็น 2 variants โดยจะให้ชื่อว่า mock และ prod ให้เราไปที่ไฟล์ build.gradle ในโมดูลแอปฯ จากนั้นให้เพิ่มโค้ดตามโค้ดด้านล่างนี้

...buildTypes {
...
}
productFlavors {
mock {
applicationIdSuffix = ".mock"
}
prod {
}
}
...

จากนั้นให้เรากด sync gradle แล้วดูผมลัพธ์ที่หน้าต่าง Build variants ซึ่งควรจะได้ผลลัพธ์ดังนี้ครับ

2.สร้าง Folder Structure ให้กับแต่ละ Variant

หลังจากที่เรากำหนด variant ให้กับโปรเจคของเราเสร็จแล้ว ต่อไปเราจะสร้าง folder structure ให้กับแต่ละ variant ถึงแม้ว่าโค้ดหลักๆที่ใช้ จะอยู่ภายใต้ folder main แต่เนื่องจากแต่ละ variant มีโค้ด (รวมถึง resourse) บางส่วนที่แตกต่างกัน จึงจำเป็นต้องมีที่อยู่สำหรับโค้ดเหล่านั้นโดยเฉพาะ

ให้เราสร้างโฟลเดอร์ที่ชื่อว่า mock และ prod ภายใต้โฟลเดอร์ src หลังจากนั้น ให้สร้างโฟลเดอร์ java ภายใต้โฟลเดอร์เหล่านั้นอีกที ซึ่งจะได้หน้าตาโครงสร้างของโฟลเดอร์ประมาณนี้ครับ

3. Restructure files

หลังจากที่เราสร้างโฟลเดอร์สำหรับแต่ละ variant เสร็จแล้วต่อไปเราจะย้ายไฟล์ MockServiceApiImpl.java ไปไว้ใน variant mock โดยให้ไฟล์อยู่ภายใต้ package ที่ชื่อเดียวกันกับใน main ซึ่งในที่นี้คือ com.sample.di.data สุดท้ายจะได้หน้าตาโครงสร้างโฟลเดอร์ดังนี้

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

4. Refactor class Repositories

ต่อไปเราจะปรับปรุง class Repositories ให้รองรับการแยก variant โดยให้เราแทนที่ class Repositories เดิมด้วยโค้ดต่อไปนี้

จากโค้ดด้านบนจะเห็นว่า จากเดิมที่ class Repositories เป็นผู้สร้าง object ServiceApi ขึ้นมาเอง เราเปลี่ยนให้ Repositories รับ object ของ ServiceApi เข้ามาแทน ทำให้การผูกมัดกันระหว่างโค้ดของ Repositories และ ServiceApi น้อยลง และยังทำให้โค้ดดูเป็นระเบียบมากขึ้น

5. สร้าง Injection class ในแต่ละ variant

จากเดิมที่การเข้าถึงแหล่งข้อมูลถูกกำหนดโดย class Repositories ในขั้นตอนนี้เราจะสร้าง class ที่ทำหน้าที่ในการกำหนดการเข้าถึงแหล่งข้อมูลแทน class Repositoriesโดยเราจะสร้าง class ที่ชื่อว่า Injection ไว้ในแต่ละ variant ดังนี้

ให้เราสร้าง class Injection ใน mock variant ดังนี้

class Injection ใน mock variant

และสร้าง Injection ใน prod variant ดังนี้

class Injection ใน mock variant

ซึ่งสุดท้ายหน้าตาโครงสร้างไฟล์เป็นเป็นดังนี้

จากโค้ดด้านบนจะเห็นว่า class Injection ของทั้งสอง variant ทำหน้าคืนค่า object Repository เหมือนกัน แต่จะแตกต่างกันตรงที่ object Repository ของแต่ละ variant ใช้ Service API ที่แตกต่างกัน โดย Repository ของ mock จะเป็น Repository ที่รับ MockServiceApi เข้าไป ส่วน Repository ของ prod จะเป็น Repository ที่รับ ServiceApi ซึ่งจะเห็นว่า Class Injection นี้เองที่เป็นกุญแจสำคัญที่ทำหน้าที่เลือกการเข้าถึงแหล่งข้อมูลของแอปฯให้เราตาม variant ที่เรากำหนด

การเรียกใช้งาน

หลังจากที่เราเตรียม class ทุกอย่างไว้แล้ว ต่อไปก็ได้เวลานำไปใช้งานครับ โดยจะขออธิบายจากการทำงานระหว่าง class GroceryActivity และ GroceryPresenter อีกเช่นเคย ดังนี้ครับ

ให้เราเปลี่ยนโค้ดตอนสร้าง GroceryPresenter ที่อยู่ใน class GroceryActivity เป็นดังนี้

จากโค้ดบรรทัดที่ 12 จะเห็นว่า จากเดิมที่เราสร้าง object ของ repository ด้วยการเรียก class Repositories เราเปลี่ยนมาเป็นเรียกผ่าน class Injection แทน โดย object Repository ที่ได้ จะมาจาก class Injection ที่อยู่ในแต่ละ variant ที่เราเลือก กล่าวคือ หากเราเลือก build ด้วย variant mock ตัว repository ที่ได้ก็จะมาจาก Injection ใน variant mock ในทางกลับกัน หากเรา Build ด้วย variant prod ตัว repository ที่ได้ก็จะมาจาก Injection ใน variant prod นั้นเอง ซึ่งจะเห็นว่าขั้นตอนนี้คือการรวมคุณสมบัติของ Dependency Injection และ ProductFlavors เข้าด้วยกัน ทำให้การสับเปลี่ยนเพื่อเข้าถึงแหล่งข้อมูลสามารถกำหนดได้ด้วยการเลือก variant

สรุป

การนำคุณสมบัติของการทำ Dependency Injection และ ProductFlavors มาใช้ ช่วยให้การทำ mock data ของเราเป็นระบบมากขึ้น เข้าถึงแหล่งข้อมูลที่เรากำหนดได้ง่ายขึ้น และทำให้เราสามารถพัฒนาแอปฯในระหว่างที่ backend ยังไม่พร้อมใช้งานได้สะดวกขึ้น นอกจากนี้ยังมีข้อดีอีกหลายอย่างที่เกิดจากการใช้ DI คู่กับ PF โดยจะขอสรุปออกมาเป็นข้อๆดังนี้

  • แยกส่วนของโค้ดที่ใช้ทดสอบระบบและโค้ดที่ใช้งานจริงได้อย่างชัดเจน
  • โค้ดมีความยืดหยุ่นมากขึ้น ง่ายต่อการเปลี่ยน tools หรือ library ในอนาคต
  • ลดการเกิดการผูกมัดกันระหว่างโค้ด
  • ลดการเกิด Human Error ที่เกิดจากการเลือกใช้แหล่งข้อมูลที่ผิดอย่างไม่ได้ตั้งใจ เนื่องจากการเข้าถึงแหล่งข้อมูลถูกกำหนดโดย variant แทนการกำหนดด้วยการแก้ไขโค้ด
  • ง่ายต่อการทำการทดสอบระบบ (unit test)

สุดท้ายนี้ หากใครสนใจเอาโค้ดไปลองเล่น ก็สามารถ check out โค้ดจาก Github ได้เลยครับ และเช่นเคย หากบทความนี้มีประโยชน์ ฝากกด ❤ ด้วยนะครับ

References

--

--