Rust والهندسة المعمارية السداسية: كود نظيف بدون تنازلات

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

Rust والهندسة المعمارية السداسية: نكتب كودًا لا نخاف من تغييره

\n\n

كل مطور واجه مرة واحدة على الأقل موقفًا تحول فيه تغيير بسيط في قاعدة البيانات أو تغيير إطار عمل HTTP إلى إعادة هيكلة جهنمية. يتشابك منطق الأعمال مع تفاصيل التنفيذ، ويتحول الكود إلى "معكرونة". بالنسبة لـ Rust، مع تركيزها على الأمان والأداء، هذا الخلط هو جريمة حقيقية.

\n\n

حل هذه المشكلة هو الهندسة المعمارية السداسية (المعروفة أيضًا باسم Ports and Adapters). في هذه المقالة، سنشرح كيفية تطبيق هذا النمط في Rust، باستخدام قوة نظام الأنواع والـ traits. ستتعلم كيفية عزل منطق المجال عن العالم الخارجي، وكتابة كود قابل للاختبار، وتغيير برامج تشغيل قاعدة البيانات أو خادم الويب بسهولة. هذه مهارة أساسية لأي شخص يريد بناء أنظمة معقدة وقابلة للصيانة في Rust.

\n\n

ما هي الهندسة المعمارية السداسية؟ (باختصار شديد)

\n\n

تخيل شكلًا سداسيًا. في المركز - منطق أعمالك (المجال). على الحواف - "منافذ" (واجهات) يتواصل من خلالها المجال مع العالم الخارجي. وفي الخارج - "محولات" (تطبيقات محددة) تتصل بالمنافذ.

\n\n

الفكرة الأساسية: المجال لا يعرف شيئًا عن كيفية حفظ البيانات أو إرسال الطلبات. إنه يعرف فقط عن المنافذ - traits مع دوال. المحولات (مثل مستودع PostgreSQL أو وحدة تحكم HTTP) تقوم بتنفيذ هذه الـ traits.

\n\n

في Rust، يتناسب هذا المفهوم بشكل مثالي بفضل الـ traits (ما يعادل الواجهات) و الـ generics. يقوم المترجم (Compiler) بالتحقق من جميع الاتصالات في مرحلة البناء، وليس في وقت التشغيل.

\n\n

هيكل المشروع في Rust

\n\n

للبدء، سننشئ هيكل مجلدات يفصل بصريًا المجال عن البنية التحتية:

\n\n
src/\n├── domain/          # منطق أعمال نقي (لا تبعيات!)\n│   ├── mod.rs\n│   ├── models.rs    # الكيانات وقيم الكائنات\n│   └── services.rs  # الخدمات التي تعمل مع المنافذ\n├── ports/           # Traits (واجهات) للعالم الخارجي\n│   ├── mod.rs\n│   └── repositories.rs\n├── adapters/        # تطبيقات المنافذ (قواعد بيانات، HTTP، إلخ.)\n│   ├── mod.rs\n│   ├── postgres_repo.rs\n│   └── in_memory_repo.rs  # للاختبارات\n├── main.rs          # بناء التطبيق (Wiring)\n└── lib.rs           # واجهة برمجة التطبيقات العامة للمكتبة
\n\n

الخطوة 1: المجال - قلب النظام

\n\n

لنبدأ بنموذج بسيط لمستخدم. لا توجد في المجال أي تبعية للمكتبات الخارجية (حتى serde، إذا لم يكن ذلك مطلوبًا من قبل منطق الأعمال).

\n\n
// src/domain/models.rs\npub struct User {\n    pub id: String,\n    pub name: String,\n    pub email: String,\n}\n\nimpl User {\n    pub fn new(id: String, name: String, email: String) -> Self {\n        // يمكن أن يكون هناك تحقق هنا\n        Self { id, name, email }\n    }\n}
\n\n

الآن سنحدد خدمة المجال. ستتعامل مع المستخدمين، لكنها لن تعرف من أين يأتون.

\n\n
// src/domain/services.rs\nuse crate::ports::repositories::UserRepository;\n\npub struct UserService {\n    repo: Box<dyn UserRepository>,\n}\n\nimpl UserService {\n    pub fn new(repo: Box<dyn UserRepository>) -> Self {\n        Self { repo }\n    }\n\n    pub fn create_user(&mut self, name: String, email: String) -> Result<User, String> {\n        let id = uuid::Uuid::new_v4().to_string(); // هل هي تبعية بالفعل؟ نعم، لكنها تفصيل\n        let user = User::new(id, name, email);\n        self.repo.save(&user)?;\n        Ok(user)\n    }\n\n    pub fn get_user(&self, id: &str) -> Result<Option<User>, String> {\n        self.repo.find_by_id(id)\n    }\n}
\n\n

هام: نحن نستخدم Box<dyn UserRepository> للتوزيع الديناميكي. في Rust، هذا طبيعي للتطبيقات المعقدة. للحالات البسيطة، يمكن استخدام الـ generics.

\n\n

الخطوة 2: المنافذ - عقود للعالم الخارجي

المدونات

توصيات الكتب