حاوية حقن التبعية لـ Rust: نظرة عامة على مكتبة dicy
في عالم البرمجة النظامية، تشتهر Rust بأدائها العالي وأمان الذاكرة. ومع ذلك، عند بناء تطبيقات كبيرة، غالبًا ما يواجه المطورون مشكلة إدارة التبعيات. يؤدي الربط اليدوي للمكونات إلى "كود صلب"، ويعقد الاختبار، ويقلل المرونة. الحل هو حاوية حقن التبعية (DI) التي تعمل على أتمتة إنشاء وحقن التبعيات. في هذه المقالة، سنستعرض مكتبة dicy - واحدة من أكثر حاويات DI نضجًا لـ Rust.
ما هو حقن التبعية ولماذا هو مطلوب في Rust؟
حقن التبعية هو نمط يحصل فيه الكائن على تبعياته من الخارج، بدلاً من إنشائها بنفسه. في Rust، هذا مهم بشكل خاص بسبب نظام الأنواع الصارم والملكية (ownership). تتولى حاوية DI المهام التالية:
- إنشاء نسخ من الخدمات (singleton, transient, scoped).
- حل رسم بياني للتبعيات (الحقن التكراري).
- إدارة دورة حياة الكائنات.
- إمكانية استبدال التطبيقات أثناء الاختبار (كائنات وهمية).
مكتبة dicy: نظرة عامة على الإمكانيات
dicy هي حاوية DI خفيفة الوزن ولكنها قوية، مكتوبة بلغة Rust نقية بدون كود غير آمن. تدعم:
- الحل التلقائي للتبعيات عبر المُنشئات أو المصانع.
- عدة أوضاع لدورة الحياة: Singleton, Transient, Scoped (لـ actix-web, axum وأطر عمل أخرى).
- المصانع غير المتزامنة - إنشاء الخدمات باستخدام async/await.
- الحقن عبر السمات (traits) - ربط سمة بتطبيق محدد.
- الحاويات المتداخلة (لعزل الوحدات).
التثبيت والإعداد الأساسي
أضف dicy إلى ملف Cargo.toml:
[dependencies]dicy = "0.6"مثال 1: حقن بسيط عبر المُنشئ
لننشئ خدمتين: Config (singleton) و Database (transient) التي تعتمد على Config.
use dicy::{Container, Module, Provide, Singleton, Transient};
#[derive(Clone)]struct Config { url: String,}
impl Config { fn new() -> Self { Config { url: "postgres://localhost".into() } }}
struct Database { config: Config,}
impl Database { fn new(config: Config) -> Self { Database { config } }}
// تعريف وحدة مع التسجيلstruct AppModule;
impl Module for AppModule { fn configure(container: &mut Container) { container .register::<Config>(Singleton::new(Config::new)) .register::<Database>(Transient::new(Database::new)); }}
fn main() { let container = Container::new(); container.install(AppModule);
// الحصول على Database مع Config المحقون تلقائيًا let db: Database = container.resolve().unwrap(); println!("اتصال بقاعدة البيانات: {}", db.config.url);}مثال 2: المصانع غير المتزامنة والتبعيات ذات النطاق (Scoped)
لتطبيقات الويب، غالبًا ما يكون من الضروري إنشاء خدمات بنطاق طلب HTTP (scoped). تدعم dicy ذلك:
use dicy::{Container, Module, Scoped, AsyncFactory, Provide};use async_trait::async_trait;
struct RequestContext { user_id: u64,}
#[async_trait]trait UserService { async fn get_name(&self) -> String;}
struct UserServiceImpl { ctx: RequestContext,}
#[async_trait]impl UserService for UserServiceImpl { async fn get_name(&self) -> String { format!("المستخدم رقم #{}", self.ctx.user_id) }}
struct WebModule;
impl Module for WebModule { fn configure(container: &mut Container) { container