PRODمنصة BaaS الأوروبية السياديةافتح لوحة المعلومات →

الهندسة · 10 دقيقة للقراءة

مساحات عمل الشحن للواجهة الخلفية Rust متعددة الخدمات

Affane Daylami · Fondateur · 12 أغسطس 2026

العودة إلى بلوق

تثير الواجهة الخلفية متعددة الخدمات في Rust نفس السؤال سريعًا: مستودع واحد لكل خدمة، أم مساحة عمل Cargo واحدة؟ قررت Aurabase للخيار الثاني. إحدى عشرة خدمة، وCLI، وخمس مكتبات مشتركة موجودة في جذر Cargo.toml واحد، مع Cargo.lock واحد للجميع. إليك كيفية إنشاء مساحة العمل هذه، وقراءتها سطرًا تلو الآخر.

تم إنشاء هذا النص الإنجليزي تلقائيًا من النص الأصلي الفرنسي ولم تتم مراجعته بعد.
تمت ترجمة هذه الصفحة تلقائيًا. النسخة الإنجليزية موثوقة.

وهذا ليس مثالاً تعليميًا تم اختراعه لهذه المناسبة. يأتي كل مقتطف تعليمات برمجية أدناه من جذر Aurabase Cargo.toml ويظهر صندوقه، كما هو موجود في المستودع اليوم - بما في ذلك تناقضين وجدناهما أثناء إعادة قراءتهما لهذه المقالة، واللذان نقوم بتوثيقهما كما هو بدلاً من إصلاحهما بهدوء قبل النشر.

الأساسيات
  • تعلن مساحة عمل Aurabase root Cargo عن 18 عضوًا صريحًا: 11 خدمة وaura-cliCLI و5 libs مشتركة وaurabase-rs SDK - ضمن resolver = "2" وCargo.lockواحد.
  • [workspace.dependencies] مركزية الإصدارات المشتركة؛ يرث كل صندوق بـ { workspace = true } بدلاً من تعيين رقمه الخاص - إلا عندما يتم تجاوز الصندوق بصمت.
  • لا يعد المجلد الموجود ضمن services/ عضوًا تلقائيًا في مساحة العمل: قائمة members صريحة، وليست كرة أرضية، وذلك على وجه التحديد لتتمكن من استبعاد التعليمات البرمجية التي لا تحتوي على Rust.
  • [profile.release] ينطبق مرة واحدة فقط على مساحة العمل بأكملها - خيار مثل panic = "unwind" ثم ينطبق على جميع الخدمات الإحدى عشرة في وقت واحد.
#
لماذا مساحة العمل

مستودع واحد لكل خدمة، أم Cargo.lock واحد فقط؟

تقوم مساحة العمل Cargo بتجميع صناديق متعددة ضمن Cargo.lock واحد ودليل هدف واحد - وهي الوظيفة نفسها التي يوفرها مدير حزم Rust لهذه الحالة. يبدو أن أحد عشر ثنائيًا منفصلاً من Rust، كل منها في مستودعها الخاص، أكثر استقلالية للوهلة الأولى. من الناحية العملية، هذا يعني أحد عشر إصدارًا مميزًا Cargo.lock، وأحد عشر دقة إصدار قد تتباين بمرور الوقت، ولا يوجد ضمان بأن خدمتين تجمعان نفس الإصدار منaxum أو sqlx.

تحل مساحة عمل Cargo هذه المشكلة على مستوى مدير الحزم، وليس على مستوى انضباط الفريق. يتشارك جميع الأعضاء في Cargo.lock واحد في الجذر: يتم حل التبعية المشتركة مرة واحدة، إلى إصدار مماثل، للرسم البياني بأكمله. وهذا أيضًا ما يجعل عامل إعادة البناء عبر الخدمات (تغيير التوقيع في aura-core، على سبيل المثال) مرئيًا لـ cargo build --workspaceواحد، بدلاً من اكتشاف الخدمة حسب الخدمة في الإنتاج. يعد هذا أحد الاختيارات التي تميز نواة الصدأ الموحدة بنسبة 100% عن الخدمة المجمعة غير المتجانسة حسب الخدمة.

#
التشريح

جذر Cargo.toml: المحلل والأعضاء

كل شيء يبدأ بالإعلان [workspace] وقائمته members. في Aurabase، تتم كتابة هذه القائمة بخط اليد، ويتم تجميعها حسب الدور - الخدمات والأدوات والمكتبات - بدلاً من إنشاؤها بواسطة نمط عام مثل services/*.

Cargo.tomltoml
[workspace]
resolver = "2"

members = [
    # الخدمات
    "services/aura-gateway",
    "services/aura-auth",
    "services/aura-db",
    # … 8 خدمات أخرى (aura-realtime، aura-storage، aura-ai، …)
    # أدوات
    "aura-cli",
    # ليبس
    "libs/aura-core",
    # ...4 ليب أخرى
    "aurabase-rs",
]

resolver = "2" ليست تفاصيل تجميلية. يقوم محلل v2 الخاص بـ Cargo بعزل وظائف build-dependencies والتبعيات الخاصة بالهدف (target.'cfg(windows)'، على سبيل المثال) عن بقية الرسم البياني - ولم تعد تتسرب إلى الثنائي النهائي. كما أنه يوحد إصدارات نفس التبعية عبر جميع أعضاء مساحة العمل الذين يشاركونها: axumواحد، مرة واحدة فقط، وليس أحد عشر قرارًا مستقلاً.

#
تراث

Workspace.package: نسخة واحدة، طبعة واحدة، مشتركة من حيث المبدأ

يعلن [workspace.package] مرة واحدة عن الحقول المشتركة - الإصدار، والإصدار، والمؤلفين، والترخيص - التي يمكن أن يرثها كل صندوق باستخدام version.workspace = true بدلاً من نسخها.

Cargo.toml (الجذر)toml
[workspace.package]
version = "0.1.1"
edition = "2021"

# Services/aura-gateway/Cargo.toml
[package]
name = "aura-gateway"
version.workspace = true

تتبع جميع خدمات Aurabase الإحدى عشرة هذا النمط. انحراف صندوقين، وهذا هو أول اثنين من التناقضات التي تم العثور عليها أثناء إعداد هذه المقالة: aura-cli يعلن version = "0.2.0" في نسخة مطبوعة، وlib libs/aura-migrations يعلن version = "0.1.0" - كلاهما مختلف عن 0.1.1 لمساحة العمل.

ماذا يعني

version.workspace = true اختياري، حقلًا تلو الآخر، وصندوقًا تلو الآخر. لا شيء يمنع الصندوق من الاحتفاظ بترقيمه الخاص - طوعًا (أداة منشورة بشكل منفصل، على سبيل المثال) أو عن طريق النسيان. يجب أن تتحقق عملية تدقيق مساحة العمل من صندوق الحقل هذا بواسطة صندوق، ولا تفترض الوراثة.

#
إلغاء البيانات المكررة

Workspace.dependeency: مصدر للحقيقة، ما لم يتجاوزه الصندوق

[workspace.dependencies] يقوم بمركزية التبعيات المشتركة بين عدة صناديق. تشير كل خدمة إليها بـ { workspace = true } بدلاً من تعيين قيد الإصدار الخاص بها.

Cargo.toml (الجذر)toml
[workspace.dependencies]
axum = { version = "0.8", features = ["ws", "multipart", "macros"] }
sqlx = { version = "0.8", default-features = false, features = […] }
tower_governor = { version = "0.8", features = ["axum"] }
governor = "0.10"

# Services/aura-gateway/Cargo.toml — لا يرث حاكم مساحة العمل
governor = "0.8"  # النسخة المحلية، مختلفة

هذا هو التناقض الحقيقي الثاني: تقوم مساحة العمل بمركزة governor في الإصدار 0.10، لكن aura-gateway تعيد تعريف السطر الخاص بها governor = "0.8" بدلاً من الوراثة - تطبق البوابة حد المعدل الخاص بها مع الصندوق governor العاري، عندما aura-auth, aura-ai, aura-db وaura-functions يمر عبر tower_governor في البرامج الوسيطة. مساحة العمل لا تمنع الاختلاف: إنها تجعلها مرئية فقط، إذا بذلنا عناء المقارنة.

ومع ذلك، ليست كل التبعيات تستحق أن تكون مركزية. لا يظهر wasmtime في أي مكان في [workspace.dependencies]: يستخدمه صندوق واحد فقط، aura-functions، في وقت تشغيل WASM الخاص به، لذلك يظل معلنًا محليًا. القاعدة التي نطبقها: رفع التبعية إلى مستوى مساحة العمل من لحظة مشاركة صندوقين أو أكثر فيها، وليس قبل ذلك.

#
الرسم البياني الداخلي

libs/ إلى الخدمات/: والذي يعتمد على ماذا

تم الإعلان عن libs الخمسة المشتركة (aura-core, aura-crypto, aura-db-adapters, aura-migrations, aura-telemetry) كتبعيات مسار في [workspace.dependencies] — aura-core = { path = "libs/aura-core" } — ثم تختار كل خدمة ما تحتاجه بالفعل.

يظل الرسم البياني الناتج قابلاً للقراءة من ملف إلى آخر. aura-gateway يعتمد فقط على ثلاثة من أصل خمسة libs الداخلية - aura-coreو aura-crypto و aura-telemetry. بما فيه الكفاية للتوجيه، قم بتوقيع JWT للعربة الجانبية PostgREST وتصدير آثارها، دون لمس aura-db-adapters أو aura-migrations. يضيف aura-provisioner``aura-migrations: فهو يعيد تشغيل عمليات ترحيل المخطط الناتجة عن إنشاء المشروع.

الحالة الأضيق هي aura-migrator، وهي الحالة الثنائية المخصصة التي تقوم بعمليات ترحيل CI/CD. من بين مكتبات Aurabase الداخلية، إنتاجها [dependencies] يسرد فقطaura-migrations — aura-core يظهر فقط في [dev-dependencies]للاختبار. وبالتالي فإن الملف الثنائي الذي تم تسليمه إلى الإنتاج لا يتضمن أي رمزaura-core؛ إنه موجود فقط خلال cargo test.

#
حالة ملموسة

صندوق واحد، عدة ثنائيات: تقسيم الهالة في الوقت الحقيقي

لا تتطلب مساحة العمل إنشاء عضو جديد في كل مرة تريد فيها عملية جديدة قابلة للنشر. يظل aura-realtime عضوًا واحدًا في مساحة العمل، لكن Cargo.toml الخاص به يعلن عن ثلاثة جداول [[bin]] مميزة حول نفس [lib]المشتركة.

Cargo.tomltoml
[lib]
name = "aura_realtime"

[[bin]]
name = "aura-realtime"

[[bin]]
name = "aura-realtime-cdc-worker"
path = "src/bin/cdc_worker.rs"

[[bin]]
name = "aura-realtime-ws-front"
path = "src/bin/ws_front.rs"

البيان نفسه يوثق الحدود بين الاثنين. يلتقط cdc-worker تغييرات Postgres عن طريق الاستقصاء وينشرها على NATS في مركز النشر/الفرعي (Client::publish_with_headers)، دون فتح خادم WebSocket على الإطلاق - JetStream، في هذه الخدمة نفسها، يخدم حصريًا التواجد عبر المثيلات KV، وليس توزيع CDC. ws-front يستهلك فقط NATS ويحتفظ باتصالات WebSocket/SSE، دون لمس مركز السيطرة على الأمراض (CDC) - التفاصيل الكاملة موجودة في وثائق المحرك في الوقت الفعلي. يشترك كلا الثنائيين في نفس كود تصفية RLS عبر [lib]، لكن يتم نشرهما وتوسيع نطاقهما بشكل مستقل في Kubernetes. هذه هي الإشارة الصحيحة لاختيار عدة ثنائيات في صندوق بدلاً من عضو جديد في مساحة العمل: نفس المنطق الداخلي وطبولوجيا النشر المختلفة.

#
فخ لتجنب

الملف ضمن الخدمات/ ليس بالضرورة عضوًا في Cargo

المجلد aura-edge-runtime موجود في مستودع Aurabase. ومع ذلك، فهو لا يحتوي على أي Cargo.toml - فقط TypeScript (index.ts, envelope.ts)، ولا يظهر في أي مكان في قائمة members لمساحة العمل الجذر.

أسوس

هذا هو بالضبط سبب كتابة قائمة members الخاصة بـ Aurabase بخط اليد بدلاً من استبدالها بنمط عام مثل services/*. كان من الممكن أن تحاول الكرة الأرضية تضمين هذا المجلد الذي لا يحتوي على Rust في مساحة العمل، مما يؤدي إلى فشل الدقة نتيجة لذلك. تسمح القائمة الصريحة للمجلدات التي لا تتحدث نفس اللغة بالتواجد تحت نفس الأصل services/.

يعمم الدرس: حساب خدمات الواجهة الخلفية لـ Rust من خلال سرد المجلدات الفرعية لـ services/ يعطي رقمًا خاطئًا. فقط الجذر Cargo.toml هو المخوّل على ما يتم تجميعه فعليًا في مساحة العمل.

#
تجميع

[profile.release]: إعداد واحد لمساحة العمل بأكملها

ينطبق [profile.release] المعلن في جذر مساحة العمل على جميع الأعضاء المجمعين في وضع الإصدار - مكان واحد فقط لضبطه، وليس أحد عشر. تقوم Cargo بتوثيق جميع المفاتيح المتاحة في مرجع ملف تعريف التجميع ؛ Aurabase ينشط خمسة فقط.

Cargo.toml (الجذر)toml
[profile.release]
lto = "thin"          # LTO عبر الصناديق، الأداء/توازن وقت البناء
codegen-units = 1     # أفضل البطانة الشاملة
panic = "unwind"   # ذعر معزول بواسطة tokio/axum → 500، وليس انهيارًا عالميًا
strip = "symbols"
opt-level = 3

يتم توثيق اختيار panic = "unwind" بدلاً من "abort" مباشرة في تعليق الملف: يتم اعتراض الذعر في معالج axum بواسطة tokio، وترجع المهمة 500، وتستمر العملية في خدمة الطلبات المتزامنة الأخرى. إن مكاسب أداءabort لا تستحق خسارة العزل بين الاستعلامات.

#
في الممارسة العملية

قم بتجميد سلسلة الأدوات والأوامر المهمة

تقوم مساحة العمل الموحدة بتعيين إصدارات التبعية، ولكن ليس إصدار المترجم نفسه. rust-toolchain.toml، في الجذر، يجمد سلسلة الأدوات (channel = "1.93" اليوم) لأي اتصال مباشر إلى cargo أو rustc في المستودع.

هذا الملف موجود على وجه التحديد بسبب حدوث انجراف صامت: قام إجراء CI بتثبيت قناة stable اليوم دون قراءة rust-toolchain.toml، بينما تم تجميع صورة Docker للإصدار مع النسخة المجمدة. يمكن أن يجتاز الالتزام جميع الاختبارات باستخدام Rust المستقر الحالي، ثم ينقطع عند إنشاء الصورة - التي يتم اكتشافها بعد الدمج، وليس قبل ذلك. rustupيحترم هذا الملف لأي أمر مباشر: لقد كان الإصلاح الضروري الوحيد، دون التأثير على سير عمل CI.

terminalbash
# ترجمة مساحة العمل بأكملها
cargo build --workspace

# اختبار صندوق واحد (وليس مساحة العمل بأكملها)
cargo test -p aura-auth

# الوبر الصارم على مساحة العمل بأكملها، التحذيرات = الأخطاء
cargo clippy --workspace --all-targets -- -D warnings

# التنسيق
cargo fmt --all

تفصيل أخير، لأولئك الذين قرأوا البيان قبل تنفيذه: يقوم الصندوق aura-cli بتجميع ملف ثنائي يسميه جدوله [[bin]] aurabase، وليس aura. يعرض غلاف npm المنشور، @aurabase/cli، الأمرين - aura وaurabase وكلاهما يشيران إلى نفس البرنامج النصي. اسم [[bin]] البضائع، واسم الصندوق، والاسم المكشوف بواسطة غلاف npm هي ثلاثة أشياء منفصلة؛ لا يمكن تخمين أي منهما من الاثنين الآخرين.

#
خلاصة

قائمة التحقق: أين تعلن ماذا

تظهر خمسة قرارات في كل مرة تقوم فيها بإضافة صندوق إلى مساحة عمل Cargo. هنا يتم الإعلان عن كل منها، استنادًا إلى مثال Aurabase الموضح أعلاه.

قائمة الأعضاء[مساحة العمل] أعضاءالجذر - قائمة صريحة، وليس الكرة الأرضية
النسخة/الطبعة المشتركة[مساحة العمل.حزمة]الجذر - version.workspace = صحيح، حسب الصندوق، اختياري
التبعية مشتركة بين أكثر من صندوقين[مساحة العمل.تبعيات]الجذر - ثم {مساحة العمل = صحيح} في كل صندوق
الاعتماد على مستهلك واحد[تبعيات] الصندوقمحليا، دون المرور عبر مساحة العمل
بناء الملف الشخصي[الملف الشخصي.الإصدار]الجذر فقط - ينطبق على جميع الأعضاء

لتدقيق مساحة عمل Cargo الحالية - الخاصة بك أو الخاصة بالمشروع الذي تتولى إدارته - تكفي أربع عمليات فحص للعثور على نوع التناقضات الموثقة أعلاه:

  1. قارن [workspace.package].version بـ version لكل صندوق - القيمة المختلفة ليست بالضرورة خطأ، ولكنها تستحق التوثيق.
  2. قارن [workspace.dependencies] بالتبعيات المعلنة محليًا بواسطة كل صندوق - حدد الأسماء الموجودة على كلا الجانبين بإصدارات مختلفة.
  3. قارن قائمة members في الجذر Cargo.toml بالمجلدات الفرعية الفعلية في المستودع - المجلد المفقود من members ليس بالضرورة سهوًا.
  4. تحقق من الاسم الفعلي لكل ثنائي تم تجميعه ([[bin]] name) بدلاً من افتراض أنه يطابق اسم الصندوق أو الاسم المكشوف بواسطة غلاف npm محتمل.

هل أنت جاهز للنشر؟

الواجهة الخلفية الخاصة بك في خمس دقائق.

لا حاجة لبطاقة ائتمان · 500 ميجابايت مجانًا · 50000 MAU