บทความชุด .NET 6 แบบจับมือทำ — #7:Dependency Injection & Service Lifetime
Dependency Injection
ก่อนอื่นเรามาทำความรู้จักกับ Dependency Non-Injection
กันก่อนนะครับ ก็คือการที่ Class นึงมีการเรียกใช้อีก Class นึง
ซึ่งเราจะต้องใช้คำสั่ง New
เพื่อที่จะสร้าง Object ของอีก Class มาใช้งาน ตรงนี้ละที่เราเรียกว่า Dependency
เพราะมีการพึ่งพา Class อื่นเกิดขึ้น
ทีนี้การใช้คำสั่ง New
ทำให้โครงสร้างของโปรแกรมเราไม่ยืดหยุ่น ปรับแต่งต่อยากครับ เพราะ Class แต่ละ Class สัมพันธ์กันแบบแน่นแยกออกจากกันลำบาก (High Coupling)
ลองดูตัวอย่างนี้นะครับ
ในตัวอย่างข้างบนจะพบว่า NewClass
นั้นผูกติดกับ EmailService
แน่นเลย เพราะไปสั่ง New
ที่ Constructor
ถ้าเราอยากจะเปลี่ยนเป็น Class อื่นก็จะกระทบ NewClass
อีก
Dependency Injection (DI)
คือการที่เราเลือก Inject Dependency เข้ามาทาง Constructor แทนที่จะ New ใน Class เหมือนเดิม ซึ่งจะมีข้อดีคือ Class ทั้งสองมีความยืดหยุ่นขึ้นมาก NewClass
สามารถที่จะใช้งาน Class อื่นแทนได้โดยที่ไม่กระทบ Code เดิมเลยครับ
ศึกษาเพิ่มเติมที่นี้นะครับ คุณสมเกียรติสรุปไว้ดีมากเลยครับ
Service Lifetime
Key Concepts
ก่อนที่จะอธิบายเรื่องของ Service Lifetime ผมอยากกลับมาทบทวนคำศัพท์ที่เกี่ยวข้องทั้งหมดก่อนนะครับ
Dependency Inversion Principle
: คือ Software Design Principle ที่แนะนำชี้ให้เห็นถึงปัญหาเกี่ยวกับเรื่องของ Dependency แต่ไม่ได้บอกว่าใช้เทคนิคอะไรในการแก้ไขปัญหาInversion of Control (IoC)
: คือการนำDependency Inversion Principle
มากำหนดแนวทางการพัฒนาให้ Components อ้างอิงกับ Abstraction แทนการอ้างอิงกับ Concrete Implementation.Dependency Injection (DI)
: คือ Design PatternในการนำIoC
มาพัฒนา โดยจะทำการ Inject Concrete Implementation ระหว่าง ComponentsIoC Container (DI Container)
: คือ Programming Framework ที่ช่วยทำDI
ให้โดยอัฒโนมัติ ซึ่งใน ASP.NET Core นั้นมี Build-InIoC Container (DI Container)
ให้เราพร้อมใช้ได้อย่างรวดเร็วเลยครับ ช่วยลดขั้นตอน ลด Code ในการพัฒนาDI
ลงไปได้เยอะมาก เพียงแต่เราจะต้องเรียนรู้การจัดการ State ของ Components ในขั้นตอนนี้ที่เรียกว่า Service Lifetimes
Service Lifetime Types
คือการจัดการกับอายุของ Instance ที่เราใช้งานแต่ละ Components ในขบวนการทำ DI โดย DI Container ซึ่งจะแบ่งออกเป็น 3 หัวข้อดังนี้
1.Transient
- Instance จะถูกสร้างใหม่ทุกครั้งที่มีการเรียกใช้งานภายในระบบ
- เหมาะสำหรับการทำงานที่ Lightweight, Stateless
- เราสามารถใช้คำสั่ง AddTransient เพื่อทำ DI แบบ Transient Lifetime ได้ครับ
- Transient services จะถูก Disposed หลังจากที่จบ 1 Request แล้วเสมอ
2. Scoped
- Instance จะถูกสร้างใหม่ทุกครั้งที่ Client Request เข้ามานะครับ (1 Connection = 1 Client Request)
- เหมาะสำหรับการทำงานที่ต้องการเรื่องของ Stateful เช่น Entity Framework (AddDbContext)
- เราสามารถใช้คำสั่ง AddScoped เพื่อทำ DI แบบ Scoped Lifetime ได้ครับ
- Scoped services จะถูก Disposed หลังจากที่จบ 1 Client Request แล้วเสมอ
3. Singleton
- Instance จะถูกสร้างแค่ครั้งแรกที่ถูกเรียก หลังจากนั้น Instance จะคงอยู่ตลอดไปจนกว่าเราจะปิดและเปิดระบบขึ้นมาใหม่
- เหมาะสำหรับบางงานที่ต้องการ Reuse Instance เดิมอยู่ตลอด เช่น Logging, Caching
- เราสามารถใช้คำสั่ง AddSingleton เพื่อทำ DI แบบ Singleton Lifetime ได้ครับ
DI Container คือช่วย Dispose Instance ให้โดยอัฒโนมัติเลยครับผม
Service Lifetime POC
ถึงเวลามาลองลงมือพิสูจน์การใช้งาน Service Lifetimes กันนะครับ
1.พัฒนา Service Lifetime Interface ใน Core Project
สร้าง Folder Interfaces/Infra/ServiceLifetimes ใน Core Project และสร้าง Files ดังนี้
ISingletonService.cs
=> เพื่อกำหนดมาตราฐานในการทดสอบ SingletonIScopedService.cs
=> เพื่อกำหนดมาตราฐานในการทดสอบ ScopedITransientService.cs
=> เพื่อกำหนดมาตราฐานในการทดสอบ Transient และจะมีการอ้างอิงไปยัง Singleton และ Scoped ด้วยนะครับ
ให้ Copy Code จากลิ้งนี้นะครับ
2.พัฒนา Service Lifetime ใน Infra Project
สร้าง Folder ServiceLifetimes ใน Infra Project และสร้าง Files ดังนี้
SingletonService.cs
=> กำหนดตัวแปรภายใน Class เพื่อใช้นับจำนวนว่ามีการเรียกใช้งาน Function ภายใน Class นี้กี่ครั้งแล้ว (ตั้งใจจะพิสูจน์ว่าค่านี้จะเพิ่มขึ้นเรื่อยๆตามจำนวน Client Request / Client Connection)ScopedService.cs
=> กำหนดตัวแปรภายใน Class เพื่อใช้นับจำนวนว่ามีการเรียกใช้งาน Function ภายใน Class นี้กี่ครั้งแล้ว (ตั้งใจจะพิสูจน์ว่าค่านี้จะเพิ่มขึ้นเรื่อยๆตามจำนวน System Request)TransientService.cs
=> กำหนดตัวแปรภายใน Class เพื่อใช้นับจำนวนว่ามีการเรียกใช้งาน Function ภายใน Class นี้กี่ครั้งแล้ว (ตั้งใจจะพิสูจน์ว่าค่าจะไม่เพิ่มขึ้นเลยเพราะการทำงานแบบ Transient จะ Dispose Instance ทุกครั้งที่ทำงานเสร็จแล้ว) นอกจากนั้นยังทดลองเรียกใช้ Singleton, Scoped ใน Class นี้ด้วยเพื่อให้เห็นผลลัพธ์ที่แตกต่างกัน
ให้ Copy Code จากลิ้งนี้นะครับ
3.พัฒนา Service Lifetime ใน API Project
ให้เพิ่ม File ข้างล่างนี้ลงไปยัง Folder Controllers
ServiceLifetimesController.cs
=> ตั้งใจำทำเพื่อทดสอบผ่าน Web API ให้เห็นผลลัพธ์ว่า ทุกครั้งที่ Initiate Instance จะได้ผลลัพธ์ที่แตกต่างกันคือ ถ้าเป็น Transient นั้น แต่ละ Instance จะใช้ State แยกกัน ค่าเลยไม่เพิ่มขึ้นเลย, ถ้าเป็น Scoped นั้น Instance จะใช้ State ร่วมกันใน 1 Client Request ค่าเลยเพิ่มขึ้นได้จนถึงจุดที่จบ Client Connection เท่านั้น พอมี Connection ใหม่ค่าก็เริ่มต้นใหม่, ถ้าเป็น Singleton คือทุก Instances จะใช้ State ร่วมกันตลอด ค่ามันเลยเพิ่มขึ้นเรื่อยๆ
แก้ไข File Program.cs เพื่อติดตั้ง DI Container ด้วย Service Lifetimes ทั้ง 3 ประเภทดังนี้
builder.Services.AddTransient<ITransientService, TransientService>();
builder.Services.AddScoped<IScopedService, ScopedService>();
builder.Services.AddSingleton<ISingletonService, SingletonService>();
ให้ Copy Code จากลิ้งนี้นะครับ
4. ดูผลลัพธ์การทำงาน
4. ดูผลลัพธ์การทำงาน
ลุยกันครับบบบ
- #0: เกริ่นนำ
- #1: ติดตั้งโปรแกรม
- #2: รวม Cheat Sheet
- #3: Empty Solution in GitHub
- #4: พัฒนาโครงสร้างของ Source Code
- #5: VS Code Debugging
- #6: Configuration
- #7: Dependency Injection & Service Lifetime
- #8: Domain Project
- #9: Core Project
- #10: Infra Project
- #11: API Project
- #12: Unit Testing
- #13: Logging and Monitoring
- #14:เพิ่มความปลอดภัยด้วย OAuth2 & OpenID Connect
- #15: Background Process & Scheduler
- #16: Health Checks
- #17: The Twelve-Factor App
- #18: Deployment — Docker
- #19: Deployment — Azure App Service
- #20: Deployment — Azure Container Apps