ในบทความนี้เราจะสรุปสาระสำคัญจากงาน .Net Summer Meetup ที่จัดขึ้น ณ ออฟฟิศของ Seven Peaks ให้ผู้ที่สนใจได้รับรู้ โดยผู้เชี่ยวชาญของเราได้แนะนำให้ผู้ชมได้รู้จักกับโลกของ .Net เริ่มจากสิ่งที่ควรรู้ขั้นพื้นฐานอย่างหลักการทำงานแบบ SOLID ใน .Net และความรู้ที่นักพัฒนาสาย .Net ทุกคนควรรู้ จากนั้นจึงนำเข้าสู่เรื่องของเคสที่พบได้ทั่วไปเกี่ยวกับ dependency injection และปิดท้ายด้วยการใช้งาน GraphQL API ใน .Net เพื่อแชร์ข้อมูลไปยังหลายๆ แพลตฟอร์ม
ในขั้นแรก Giorgio Desideri ตำแหน่ง Tech Lead Cloud Solutions ผู้มีอารมณ์ขันเต็มเปี่ยมของเราจะให้ความรู้คุณเกี่ยวกับหลักการทำงานแบบ SOLID ใน .Net ซึ่งเป็นหนึ่งในชุดของหลักการที่โดดเด่นที่สุดในประวัติศาสตร์ของวิศวกรรมซอฟต์แวร์ จะเป็นอย่างไรบ้าง มาเริ่มกันเลย
หลักการทำงานแบบ SOLID ที่ใช้ในภาษา C# และ .Net นั้นไม่ใช่เรื่องใหม่สำหรับวงการพัฒนาซอฟต์แวร์แต่อย่างใด แต่ก็น่าแปลกใจที่มีนักพัฒนาจำนวนไม่น้อยที่ยังไม่รู้ แม้ว่าทั้งภาษาและแพลตฟอร์มนี้จะมีมานานกว่า 40 แล้วก็ตาม โดย Giorgio ได้พูดถึงเรื่องนี้เพื่อย้ำเตือนให้นักพัฒนาได้เข้าใจคอนเซปต์นี้อีกครั้ง เพื่อช่วยให้พวกเขาสามารถพัฒนาแอปพลิเคชันได้ประสบความสำเร็จตามเป้าหมายที่วางไว้
SOLID เป็นคำที่ย่อมาจากSingle Responsibility Principle (SRP), Open closed Principle (OSP), Liskov substitution Principle (LSP), Interface Segregation Principle (ISP), และ Dependency Inversion Principle (DIP).
ซึ่ง SOLID เป็นรูปแบบการทำงานที่ช่วยให้การพัฒนาผลิตภัณฑ์ออกมามีคุณภาพสูงสุด โดยคอนเซปต์ทั้งหมดจะมีความเชื่อมโยงกัน เนื่องจากเมื่อเลือกใช้คอนเซปต์หนึ่ง ส่วนที่เหลือก็ต้องทำตามให้สอดคล้องไปพร้อมกัน
“ควรมีแค่เหตุผลเดียวเท่านั้นที่จะต้องไปแก้ไขคลาส” – Martin, Robert C.
เป็นที่รู้กันดีว่า “ทุกๆ โมดูล คลาส และฟังก์ชัน ควรมีหน้าที่เพียงแค่อย่างเดียวในแต่ละส่วนของการใช้งานซอฟต์แวร์” นั่นหมายความว่า แต่ละคลาสหรือโครงสร้างที่คล้ายคลึงกันในโค้ดของคุณควรมีหน้าที่เดียวเท่านั้น และองค์ประกอบย่อยทั้งหมดของมันควรเกี่ยวข้องกับหน้าที่นั้นๆ ด้วย ถ้าคุณต้องการแก้ไขมัน คุณก็จำเป็นต้องแก้พวกมันทั้งหมด
“Entity ของซอฟต์แวร์ (เช่น คลาส, โมดูล, ฟังก์ชัน, และอื่นๆ) ควรเปิดให้พร้อมสำหรับการเพิ่มความสามารถได้ แต่ก็ต้องปิดไว้ไม่ให้สามารถแก้ไขได้”
หมายความว่า เมื่อคุณเขียนโค้ด คลาส ฟังก์ชัน โมดูล หรืออะไรก็ตาม คุณควรปรับให้มันมีความยืดหยุ่นและรองรับการเพิ่มความสามารถในอนาคต แต่ต้องมีความปลอดภัยมากพอที่จะป้องกันการแก้ไขโดยไม่ได้รับอนุญาตได้
ในปี 1988 Barbara Liskov ได้เสนอคอนเซปต์หนึ่งเอาไว้ว่า
“Let ϕ ( x ) be a property provable about objects x of type T.
Then ϕ ( y ) should be true for objects y of type S where S is a subtype of T.”
ซึ่งสามารถแปลสรุปให้เข้าใจง่ายๆ ได้ว่า “คลาสย่อยต้องสามารถทำหน้าที่ของคลาสแม่ได้” หลักการนี้พัฒนามาจาก Open-Closed Principle โดยในการพัฒนาซอฟต์แวร์จริงนั้น แนวคิดนี้คือการบอกว่าคลาสย่อยอะไรก็ตามต้องสามารถทำงานแทนคลาสแม่ได้ รวมถึงต้องทำงานได้เหมือนกันโดยไม่ต้องแก้ไขอะไรเลย
“ไม่ควรมีโค้ดใดของ client ถูกบังคับให้ไป implement method ที่ไม่ได้นำไปใช้งาน”
ข้อความนี้ทำให้เราสรุปได้ว่า การออกแบบอินเทอร์เฟซให้มีประโยชน์นั้น มันควรจะเชื่อมโยงกับโค้ดที่ใช้งานมันอยู่ได้เป็นอย่างดี มากกว่าโค้ดที่นำมันไป implement ซึ่งแทนที่จะกำหนด method ด้วยการอ้างอิงจากการนำคลาสไป implement แต่สิ่งที่ควรจะเป็นคือ method ของอินเทอร์เฟซควรถูกกำหนดโดย client code ว่าจำเป็นต้องนำไปใช้งานจริงๆ
“โมดูลในระดับสูงไม่ควร import อะไรมาจากโมดูลระดับต่ำ ทั้งสองแบบควรขึ้นอยู่กับ abstraction”
“Abstraction ไม่ควรอ้างอิงละเอียดต่างๆ แต่รายละเอียด (ที่เป็นการทำ concrete implementation) ควรอ้างอิง abstraction”
พูดอีกนัยหนึ่งก็คือ โมดูลระดับสูงจะสร้างตรรกะอันซับซ้อนเอาไว้ในระบบ (แอปพลิเคชัน) ในขณะที่โมดูลระดับต่ำจะสร้างฟีเจอร์เพื่อประโยชน์ใช้สอยต่างๆ สิ่งสำคัญคือโมดูลระดับสูงต้องสามารถนำกลับมาใช้ได้ง่ายและจะต้องไม่เกิดความเปลี่ยนแปลงหากมีการแก้ไขที่โมดูลระดับต่ำ
dependency injection (DI) คือหลักการออกแบบที่นำไปใช้ในการ implement Inversion of Control(IoC)
รูปแบบของ dependency injection จะมีความเกี่ยวข้องกับคลาส 3 ประเภท ได้แก่
client class (dependent class) คือคลาสที่ขึ้นอยู่กับ service class
service class (dependency) คือคลาสที่สร้าง service ให้กับ client class
injector class ทำหน้าที่ส่งต่อ object ของ service class ไปยัง client class
injector ทำหน้าที่ส่งต่อ service (dependency) ไปทาง constructor ของ client class ซึ่งเป็นประเภทที่ใช้กันอย่างแพร่หลายที่สุด
injector ทำหน้าที่ส่งต่อ dependency ไปทาง public property ของ client class
client class ทำการ implement อินเทอร์เฟซที่ประกาศ method เพื่อส่งต่อ dependency และ injector จะใช้อินเทอร์เฟซนี้เพื่อส่งต่อ dependency ไปยัง client class
เรากำลังจะพูดถึง constructor injection การที่จะเตรียม client class ให้พร้อมสำหรับ constructor injection ได้นั้น เราจำเป็นต้องเปลี่ยนจากการ implement ในกรอบสีแดงข้างล่างซ้ายที่เราเห็นว่ามี dependency อยู่ใน constructor ไปยังกรอบสีเขียวด้านล่างขวา ที่เราจะได้รับ dependency โดยตรงในรูปแบบของพารามิเตอร์ที่อยู่ใน constructor แทน
ด้วยการแก้ไขง่ายๆ นี้ เราจะได้ประโยชน์มากมายจากการใช้งาน dependency injection เช่น
เราสามารถเขียนโปรแกรมแบบ loose coupling ที่ component พึ่งพากันน้อยได้ เพราะว่าไม่ต้อง implement คลาสนี้กับ service class อีกต่อไปแล้ว ทำให้คลาสใดก็ตามที่ implement _productRepository อินเทอร์เฟซของมันจะถูกส่งต่อไปและทำงานได้ตามปกติ
การใช้ dependency injection คือหนทางเดียวที่สามารถรองรับการทำ unit testing ที่เหมาะสมได้ เพราะถ้าเราไม่ส่งต่อ repositories กันแบบนี้ เราก็ไม่มีทางที่จะมาร์กมันทีหลังได้ เว้นเสียแต่ว่าเราจะเริ่มใช้ interceptor แปลกๆ มาช่วย แต่นั่นก็เป็นเรื่องที่ยุ่งยากมากเกินไป
ช่วยให้โค้ดที่เราเขียนดูเรียบร้อย อ่านง่าย สบายตาขึ้น
โมดูลระดับสูงไม่ควรอ้างอิงโมดูลระดับต่ำ และทั้งสองอย่างควรอ้างอิง abstraction ซึ่งในที่นี้คืออินเทอร์เฟซ
เราควรส่งสิ่งที่ object สร้างขึ้นนอก dependant class ไปยังคลาสที่อยู่ภายนอก นั่นคือ container หรือ framework ที่ชื่อว่า IoC container
ASP.NET Core มี IoC container พื้นฐานที่ built-in มาให้ในระบบเลย ซึ่งจะแสดงอยู่ในอินเทอร์เฟซของ IServiceProvider และ IServiceCollection ที่รองรับ constructor dependency injection แบบ default
จากนั้น คุณก็สามารถ register service ได้ ด้วยคำสั่งต่อไปนี้,
จากนั้นจึงทำการ resolve service ดังนี้
ก่อนที่จะพูดถึงคอนเซปต์ของ Service Lifetime คุณควรทำความเข้าใจก่อนว่า service scope คืออะไร
service scope คือ child DI container ที่มีอายุสั้น ดังนั้น ทุกครั้งที่คุณสร้าง scope ขึ้นมาใหม่ คุณจะสร้าง DI container ที่มีอายุสั้นกว่า Root scope และในการสร้าง scope ใหม่ขึ้นมานั้น คุณจะมีทางเลือกอยู่สองทาง
ทางเลือกแรกคือ การใช้ CreateScope ที่แสดงโดย IServiceProvider อีกทางคือการปล่อยให้ web application ของ .Net สร้าง scope ของแต่ละ request ที่ได้รับมาโดยอัตโนมัติ
Services lifetime คือวิธีการควบคุมว่า service ควรมีอายุยาวนานแค่ไหนก่อนที่จะโดน dispose และมีวิธีการ register service อยู่ด้วยกัน 3 วิธี ได้แก่ transient, singleton, และ scoped โดยคุณมีหน้าที่เลือกอายุขัยที่เหมาะสมที่สุดให้กับ service ของคุณ
service instance จะถูกสร้างขึ้นทุกครั้งที่ service ได้รับ request หาก service สามารถ dispose ได้ มันก็จะโดน dispose เมื่อ service scope โดน dispose แล้ว
คุณสามารถใช้ transient lifetime ได้หาก service ของคุณ…
มีเพียง instance เดียวของ service ที่ถูกสร้างขึ้น singleton service จะไม่โดน dispose จนกว่า root scope โดน dispose ซึ่งมักจะเกิดขึ้นเมื่อออกจากแอปพลิเคชัน
คุณสามารถใช้ singleton lifetime ได้หาก service ของคุณ…
instance ของ service จะถูกสร้างขึ้นใหม่ในแต่ละ scope ซึ่งมันจะทำหน้าที่เสมือนเป็น singleton อยู่ใน scope นั้น หาก service เป็นแบบที่ dispose ได้ มันจะโดน dispose เมื่อ service scope โดน dispose
คุณสามารถใช้ scoped lifetime ได้หาก…
“GraphQL คือ ภาษาสำหรับการเข้าถึงข้อมูล หรือ query language สำหรับเชื่อมต่อกับ API และการประมวลผลเพื่อเชื่อมโยงข้อมูลของ query กับข้อมูลที่คุณมีอยู่”
GraphQL ทำให้การพัฒนาความสามารถของ API เป็นเรื่องง่ายขึ้น ทำให้ client สามารถ request เฉพาะแค่ข้อมูลที่ต้องการได้ และเป็นเครื่องมือที่ดีของนักพัฒนา ทั้งยังสร้างคำบรรยายข้อมูลที่เข้าใจง่ายให้กับ API ของคุณได้อีกด้วย
เป็นเหมือนระบบที่มี query จากนั้นเราก็ต้องกำหนดบาง type หรือบาง object ด้วย entity ที่เราสามารถนำไปรัน query ได้ ซึ่งใน GraphQL มี object ต่อไปนี้
GraphQL มี operation ที่ใช้งานกันบ่อยที่สุด ดังนี้
|
|
|
อุปสรรคที่เรากำลังพยายามผ่านมันไปให้ได้
GraphQL | REST |
---|---|
แอปพลิเคชันที่ทำงานแบบเรียลไทม์ | Non-Interactive (System to System) |
แอปพลิเคชันมือถือ | Microservices |
มี object hierarchy ที่ซับซ้อน | มี object hierarchy ที่เรียบง่าย |
มี query ที่ซับซ้อน | มี query ที่เรียบง่าย และทำงานซ้ำๆ |
พัฒนายาก | พัฒนาง่ายกว่า |
client สามารถอ่านข้อมูลได้ง่ายกว่า | มีความซับซ้อน client อ่านข้อมูลได้ยากกว่า |
สำหรับ GraphQL จะมีความยืดหยุ่นมากกว่าในการ query และสามารถ manipulate ได้แบบ dynamic มากกว่า REST API ดังนั้น คุณจึงสนุกและเล่นกับมันได้ด้วยการปรับแต่ง object ตามที่ต้องการ นอกจากนั้น คุณยังสามารถทำ web socket ได้ในแบบที่ protocol อื่นทำไม่ได้อีกด้วย
เท่านั้นยังไม่พอ client ยังสามารถอ่านข้อมูลจาก GraphQL ได้ง่ายกว่า เนื่องจากตอนที่คุณกำหนด type นั้น คุณกำหนดด้วย Scalars ทำให้เมื่อคุณอยู่บน explorer หรือใช้งานแอปฯ มือถืออยู่ คุณก็สามารถกำหนด type ได้ ด้วยเหตุนี้ เมื่อมีใครมาใช้งาน GraphQL API ก็จะเกิด error ทันทีด้วยการบอกว่า type ไม่ตรงกัน ดังนั้น client จึงทำงานง่ายขึ้นนิดหน่อยด้วยการสร้าง documentation ซึ่งคุณสามารถเล่นกับมันได้เต็มที่
อย่างไรก็ตาม REST API ก็ยังไม่ถูกเลิกใช้ไปเสียทีเดียว เพราะการพัฒนานั้นสามารถทำได้ง่ายกว่า แต่มีความซับซ้อนกว่านิดหน่อยในการทำ integration เพราะว่าเรายังต้องสร้าง documentation อยู่ ซึ่งมันจะยังรับข้อมูลทั้งหมดแม้ว่าจะไม่ต้องการก็ตาม
ในวิดีโอข้างล่างนี้เป็นเนื้อหาของการบรรยายจากผู้เชี่ยวชาญเกี่ยวกับหลักการทำงานแบบ SOLID ใน .Net ต่อด้วย dependency injection และ GraphQL และยังได้บรรยายต่อเนื่องไปถึงประเด็นของอุปสรรคและวิธีการเอาชนะพวกมัน ซึ่งเนื้อหาเหล่านี้อาจเป็นประโยชน์สำหรับคุณ