وهذا ليس مثالاً تعليميًا تم اختراعه لهذه المناسبة. يأتي كل مقتطف تعليمات برمجية أدناه من جذر Aurabase Cargo.toml ويظهر صندوقه، كما هو موجود في المستودع اليوم - بما في ذلك تناقضين وجدناهما أثناء إعادة قراءتهما لهذه المقالة، واللذان نقوم بتوثيقهما كما هو بدلاً من إصلاحهما بهدوء قبل النشر.
- تعلن مساحة عمل Aurabase root Cargo عن 18 عضوًا صريحًا: 11 خدمة و
aura-cliCLI و5 libs مشتركة وaurabase-rsSDK - ضمن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/*.
resolver = "2" ليست تفاصيل تجميلية. يقوم محلل v2 الخاص بـ Cargo بعزل وظائف build-dependencies والتبعيات الخاصة بالهدف (target.'cfg(windows)'، على سبيل المثال) عن بقية الرسم البياني - ولم تعد تتسرب إلى الثنائي النهائي. كما أنه يوحد إصدارات نفس التبعية عبر جميع أعضاء مساحة العمل الذين يشاركونها: axumواحد، مرة واحدة فقط، وليس أحد عشر قرارًا مستقلاً.
Workspace.package: نسخة واحدة، طبعة واحدة، مشتركة من حيث المبدأ
يعلن [workspace.package] مرة واحدة عن الحقول المشتركة - الإصدار، والإصدار، والمؤلفين، والترخيص - التي يمكن أن يرثها كل صندوق باستخدام 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 } بدلاً من تعيين قيد الإصدار الخاص بها.
هذا هو التناقض الحقيقي الثاني: تقوم مساحة العمل بمركزة 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]المشتركة.
البيان نفسه يوثق الحدود بين الاثنين. يلتقط 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 ينشط خمسة فقط.
يتم توثيق اختيار panic = "unwind" بدلاً من "abort" مباشرة في تعليق الملف: يتم اعتراض الذعر في معالج axum بواسطة tokio، وترجع المهمة 500، وتستمر العملية في خدمة الطلبات المتزامنة الأخرى. إن مكاسب أداءabort لا تستحق خسارة العزل بين الاستعلامات.
قم بتجميد سلسلة الأدوات والأوامر المهمة
تقوم مساحة العمل الموحدة بتعيين إصدارات التبعية، ولكن ليس إصدار المترجم نفسه. rust-toolchain.toml، في الجذر، يجمد سلسلة الأدوات (channel = "1.93" اليوم) لأي اتصال مباشر إلى cargo أو rustc في المستودع.
هذا الملف موجود على وجه التحديد بسبب حدوث انجراف صامت: قام إجراء CI بتثبيت قناة stable اليوم دون قراءة rust-toolchain.toml، بينما تم تجميع صورة Docker للإصدار مع النسخة المجمدة. يمكن أن يجتاز الالتزام جميع الاختبارات باستخدام Rust المستقر الحالي، ثم ينقطع عند إنشاء الصورة - التي يتم اكتشافها بعد الدمج، وليس قبل ذلك. rustupيحترم هذا الملف لأي أمر مباشر: لقد كان الإصلاح الضروري الوحيد، دون التأثير على سير عمل CI.
تفصيل أخير، لأولئك الذين قرأوا البيان قبل تنفيذه: يقوم الصندوق aura-cli بتجميع ملف ثنائي يسميه جدوله [[bin]] aurabase، وليس aura. يعرض غلاف npm المنشور، @aurabase/cli، الأمرين - aura وaurabase وكلاهما يشيران إلى نفس البرنامج النصي. اسم [[bin]] البضائع، واسم الصندوق، والاسم المكشوف بواسطة غلاف npm هي ثلاثة أشياء منفصلة؛ لا يمكن تخمين أي منهما من الاثنين الآخرين.
قائمة التحقق: أين تعلن ماذا
تظهر خمسة قرارات في كل مرة تقوم فيها بإضافة صندوق إلى مساحة عمل Cargo. هنا يتم الإعلان عن كل منها، استنادًا إلى مثال Aurabase الموضح أعلاه.
| قائمة الأعضاء | [مساحة العمل] أعضاء | الجذر - قائمة صريحة، وليس الكرة الأرضية |
|---|---|---|
| النسخة/الطبعة المشتركة | [مساحة العمل.حزمة] | الجذر - version.workspace = صحيح، حسب الصندوق، اختياري |
| التبعية مشتركة بين أكثر من صندوقين | [مساحة العمل.تبعيات] | الجذر - ثم {مساحة العمل = صحيح} في كل صندوق |
| الاعتماد على مستهلك واحد | [تبعيات] الصندوق | محليا، دون المرور عبر مساحة العمل |
| بناء الملف الشخصي | [الملف الشخصي.الإصدار] | الجذر فقط - ينطبق على جميع الأعضاء |
لتدقيق مساحة عمل Cargo الحالية - الخاصة بك أو الخاصة بالمشروع الذي تتولى إدارته - تكفي أربع عمليات فحص للعثور على نوع التناقضات الموثقة أعلاه:
- قارن
[workspace.package].versionبـversionلكل صندوق - القيمة المختلفة ليست بالضرورة خطأ، ولكنها تستحق التوثيق. - قارن
[workspace.dependencies]بالتبعيات المعلنة محليًا بواسطة كل صندوق - حدد الأسماء الموجودة على كلا الجانبين بإصدارات مختلفة. - قارن قائمة
membersفي الجذرCargo.tomlبالمجلدات الفرعية الفعلية في المستودع - المجلد المفقود منmembersليس بالضرورة سهوًا. - تحقق من الاسم الفعلي لكل ثنائي تم تجميعه (
[[bin]] name) بدلاً من افتراض أنه يطابق اسم الصندوق أو الاسم المكشوف بواسطة غلاف npm محتمل.