مقدمة
في المقالات السابقة ، وصفنا: النطاق ، الأسس المنهجية ، مثال على الهندسة المعمارية والهيكل . في هذه المقالة ، أود أن أوضح كيفية وصف العمليات ، ومبادئ تجميع المتطلبات ، وكيف تختلف متطلبات العمل عن المتطلبات الوظيفية ، وكيفية الانتقال من المتطلبات إلى التعليمات البرمجية. تحدث عن مبادئ استخدام Case Case وكيف يمكنهم مساعدتنا. استكشف أمثلة لخيارات التنفيذ لأنماط تصميم Interactor و Service Layer.

يتم إعطاء الأمثلة الواردة في المقالة باستخدام حل LunaPark الخاص بنا ، وسوف تساعدك في الخطوات الأولى في النهج الموصوفة.
متطلبات وظيفية منفصلة عن متطلبات العمل.
مرارًا وتكرارًا ، يحدث أن العديد من أفكار العمل لا تتحول حقًا إلى المنتج النهائي المقصود. يرجع ذلك غالبًا إلى عدم القدرة على فهم الفرق بين متطلبات العمل والمتطلبات الوظيفية ، الأمر الذي يؤدي في النهاية إلى مجموعة غير كافية من المتطلبات ، والوثائق غير الضرورية ، وتأخير المشروع ، وإخفاقات المشروع الكبرى.
أو في بعض الأحيان نواجه بعض المواقف التي ، على الرغم من أن الحل النهائي يلبي احتياجات العملاء ، ولكن بطريقة ما لا يتم تحقيق أهداف العمل.
لذلك ، لا بد من فصل متطلبات العمل عن المتطلبات الوظيفية حتى تبدأ في تحديدها. لنأخذ مثالا.
لنفترض أننا نكتب طلبًا لشركة توصيل البيتزا ، وقررنا إنشاء نظام لتتبع البريد السريع. متطلبات العمل هي كما يلي:
"قدم نظامًا يعتمد على الويب ونظامًا لتتبع الموظفين على أساس الموظف المتنقل والذي يلتقط ناقلي البريد على طرقهم ويحسن الكفاءة من خلال مراقبة نشاط شركات الشحن وغيابهم عن العمل وإنتاجية العمل".
هنا يمكننا التمييز بين عدد من الميزات المميزة التي ستشير إلى أن هذه متطلبات من العمل:
- تتم كتابة متطلبات العمل دائمًا من وجهة نظر العميل ؛
- هذه متطلبات واسعة وعالية المستوى ولكنها لا تزال موجهة جزئيًا ؛
- أنها ليست أهداف الشركة ، ولكنها تساعد الشركة على تحقيق الأهداف ؛
- أجب عن الأسئلة " لماذا " و " ماذا ". ماذا تريد الشركة أن تتلقى؟ ولماذا هي في حاجة إليها.
المتطلبات الوظيفية هي الإجراءات التي يجب على النظام تنفيذها لتنفيذ متطلبات العمل. وبالتالي ، فإن المتطلبات الوظيفية مرتبطة بالحل أو البرمجيات المطورة. نقوم بصياغة المتطلبات الوظيفية للمثال أعلاه:
- يجب أن يعرض النظام خط الطول والعرض للموظف عبر GPS / GLONASS ؛
- يجب أن يعرض النظام مواقف الموظفين على الخريطة ؛
- يجب أن يسمح النظام للمديرين بإرسال إشعارات إلى مرؤوسيهم الميدانيين.
نسلط الضوء على الميزات التالية:
- تتم كتابة المتطلبات الوظيفية دائمًا من وجهة نظر النظام ؛
- هم أكثر تحديدا وتفصيلا.
- بفضل الوفاء بالمتطلبات الوظيفية ، تم تطوير حل فعال يلبي احتياجات العمل وأهداف العميل ؛
- أجب عن السؤال " كيف ". كيف يحل النظام متطلبات العمل
ينبغي قول بضع كلمات عن المتطلبات غير الوظيفية (والمعروفة أيضًا باسم "متطلبات الجودة") ، والتي تفرض قيودًا على التصميم أو التنفيذ (على سبيل المثال ، متطلبات الأداء والأمان والتوافر والموثوقية). هذه المتطلبات تجيب على السؤال " ما " الذي يجب أن يكون عليه النظام.
التنمية هي ترجمة متطلبات الأعمال إلى متطلبات وظيفية. البرمجة التطبيقية هي تنفيذ المتطلبات الوظيفية ، والنظام - غير وظيفي.
استخدام الحالات
غالبًا ما يكون تنفيذ المتطلبات الوظيفية هو الأكثر تعقيدًا في النظم التجارية. في بنية خالصة ، يتم تنفيذ المتطلبات الوظيفية من خلال طبقة Use Case .
لكن بالنسبة للمبتدئين ، أريد أن أنتقل إلى المصدر. Ivar Jacobson - مؤلف تعريف Use Case ، أحد مؤلفي UML ، ومنهجية RUP ، في مقالته Use-Case 2.0. يحدد Hub of Software Development 6 مبادئ لاستخدام حالات الاستخدام:
- اجعلها بسيطة من خلال سرد القصص ؛
- لديك خطة استراتيجية ، أن تكون على بينة من الصورة الكاملة ؛
- التركيز على المعنى
- يصطف النظام في طبقات.
- تسليم النظام خطوة بخطوة ؛
- تلبية احتياجات الفريق.
نحن نعتبر باختصار كل من هذه المبادئ ، فهي مفيدة لنا لمزيد من التفاهم. في ما يلي ترجماتي المجانية ، مع الاختصارات والإدخالات ، أوصي بشدة أن تتعرف على النص الأصلي.
البساطة من خلال رواية القصص
السرد هو جزء من ثقافتنا. هذه هي الطريقة الأسهل والأكثر فعالية لنقل المعرفة والمعلومات من شخص إلى آخر. هذه هي أفضل طريقة لتوضيح ما يجب على النظام فعله ومساعدة الفريق على التركيز على الأهداف المشتركة.
حالات الاستخدام تعكس أهداف النظام. لفهم حالة الاستخدام ، نقول ، ونروي قصة معينة. تحكي القصة كيفية تحقيق الهدف وكيفية حل المشكلات التي تنشأ على طول الطريق. استخدام الحالات ، مثل كتاب القصص ، يوفر وسيلة لتحديد وتغطية جميع القصص المختلفة ولكن ذات الصلة بطريقة بسيطة وشاملة. هذا يجعل من السهل جمع وتوزيع وفهم متطلبات النظام.
يرتبط هذا المبدأ بلغة Ubiques من منهج DDD.
فهم الصورة كاملة
بغض النظر عن النظام الذي تقوم بتطويره ، كبيره ، صغيره ، البرامج ، الأجهزة أو الأعمال التجارية ، فهم فهم الصورة الكبيرة مهم جدا. بدون فهم النظام ككل ، لا يمكنك اتخاذ القرارات الصحيحة بشأن ما يجب تضمينه في النظام ، وما الذي يجب استبعاده ، وكم سيكلفه ، وما الفوائد التي سيحققها.
يقترح Ivar Jacobson استخدام الرسم التخطيطي لحالة الاستخدام ، وهو مناسب جدًا لجمع المتطلبات. إذا تم تجميع المتطلبات وضوحا ، فإن خريطة السياق الخاصة بـ Eric Evans هي الخيار الأفضل. غالبًا ما يتم تفسير نهج Scrum بحيث لا يقضي الأشخاص وقتًا في الخطة الاستراتيجية ، مع مراعاة التخطيط ، بعد أكثر من أسبوعين ، من بقايا الماضي. وقعت دعاية جيف ساذرلاند على تدفق المياه ، والأشخاص الذين أكملوا دورات تدريبية لمدة أسبوعين لبرنامج الماجستير في برنامج Scrum Masters الذين سُمح لهم بإدارة المشروعات قاموا بعملهم. لكن الفطرة السليمة تدرك أهمية التخطيط الاستراتيجي. ليست هناك حاجة لوضع خطة استراتيجية مفصلة ، ولكن ينبغي أن يكون.
التركيز على القيمة
عند محاولة فهم كيفية استخدام النظام ، من المهم دائمًا التركيز على القيمة التي سيوفرها لمستخدميه والأطراف المعنية الأخرى. تتشكل القيمة فقط عند استخدام النظام. لذلك ، من الأفضل التركيز على كيفية تطبيق النظام بدلاً من التركيز على قوائم طويلة من الميزات أو القدرات التي يمكنه تقديمها.
توفر حالات الاستخدام هذا التركيز ، مما يساعدك على التركيز على كيفية استخدام النظام من قبل مستخدم معين لتحقيق هدفه. تغطي حالات الاستخدام العديد من الطرق لاستخدام النظام: تلك التي تحقق أهدافها بنجاح ، وتلك التي تحل أي صعوبات تنشأ.
علاوة على ذلك ، يعطي المؤلف خطة رائعة ، والتي ينبغي إيلاء الاهتمام الأقرب:

يوضح الرسم البياني حالة الاستخدام ، "السحب النقدي في ماكينة الصراف الآلي". يتم وصف أسهل طريقة لتحقيق الهدف في الاتجاه الأساسي (التدفق الأساسي). يتم وصف الحالات الأخرى بأنها تدفق بديل. تساعد هذه الاتجاهات في سرد القصص ، وهيكل النظام ، والمساعدة في كتابة الاختبارات.
طبقات
تتطلب معظم الأنظمة الكثير من العمل قبل أن تكون جاهزة للاستخدام. لديهم العديد من المتطلبات ، التي يعتمد معظمها على متطلبات أخرى ، يجب تنفيذها قبل تلبية المتطلبات وتقييمها.
من الخطأ الكبير إنشاء مثل هذا النظام في وقت واحد. يجب أن يتم بناء النظام من قطع ، لكل منها قيمة واضحة للمستخدمين.
هذه الأفكار لها صدى مع النهج رشيقة والأفكار المجال .
خطوة بخطوة إطلاق المنتج
تطورت معظم أنظمة البرمجيات على مدار أجيال عديدة. لا يتم إنتاجها في وقت واحد ؛ إنها مبنية كسلسلة من الإصدارات ، كل منها مبني على إصدار سابق. حتى الإصدارات نفسها غالبًا لا تخرج في وقت واحد ، ولكنها تتطور من خلال سلسلة من الإصدارات الوسيطة. توفر كل خطوة إصدارًا واضحًا وقابل للاستخدام من النظام. هذه هي الطريقة التي يجب أن تنشأ جميع النظم.
تلبية احتياجات الفريق
لسوء الحظ ، لا يوجد حل عالمي لمشاكل تطوير البرمجيات ؛ الفرق المختلفة والمواقف المختلفة تتطلب أساليب مختلفة ومستويات مختلفة من التفاصيل. بغض النظر عن الأساليب والتقنيات التي تختارها ، يجب عليك التأكد من أنها قابلة للتكيف بما يكفي لتلبية الاحتياجات الحالية للفريق.
يحثك إريك إيفانز في كتابه على عدم قضاء الكثير من الوقت في وصف جميع العمليات من خلال UML. يكفي استخدام أي مخططات مرئية. الفرق المختلفة ، تتطلب المشروعات المختلفة مستوى مختلفًا من التفاصيل ، حيث يتحدث مؤلف UML نفسه عن هذا.
تطبيق
في العمارة الخالصة ، يعرّف Robert Martin حالات الاستخدام التالية:
تقوم حالات الاستخدام هذه بتنظيم تدفق البيانات من وإلى الكيانات ، وتوجيه تلك الكيانات إلى استخدام قواعد الأعمال الحرجة الخاصة بها لتحقيق أهداف حالة الاستخدام.
دعنا نحاول ترجمة هذه الأفكار إلى كود. دعنا نتذكر المخطط من المبدأ الثالث لاستخدام " حالات الاستخدام" ونأخذها كأساس. النظر في عملية تجارية معقدة حقا: "الطبخ فطيرة الملفوف".
دعنا نحاول تحليلها:
- التحقق من توافر المنتج ؛
- أخذهم من الأسهم ؛
- يعجن العجين.
- دع العجين يرتفع
- تحضير الحشوة
- اصنع فطيرة
- خبز فطيرة.
ننفذ هذا التسلسل بأكمله من خلال Interactor ، وسيتم تنفيذ كل خطوة من خلال وظيفة أو كائن وظيفي في طبقة الخدمة.
تسلسل الإجراءات (التفاعل)

أوصي بشدة ببدء تطوير عملية تجارية معقدة باستخدام تسلسل الإجراءات . بتعبير أدق ، ليس كذلك ، يجب عليك تحديد مجال المجال الذي تنتمي إليه عملية الأعمال. توضيح جميع متطلبات العمل. حدد جميع الكيانات المشاركة في العملية. توثيق متطلبات وتعريفات كل كيان في قاعدة المعرفة.
قم برسم كل شيء على الورق بخطوات. في بعض الأحيان تحتاج إلى رسم تخطيطي تسلسل. مؤلفها هو نفسه الذي اخترع حالة الاستخدام - إيفار جاكوبسون. اخترع الرسم التخطيطي من قبله عندما كان يقوم بتطوير نظام صيانة شبكة الهاتف لإريكسون ، على أساس دائرة الترحيل. أنا حقا أحب هذا الرسم البياني ، ومصطلح تسلسل ، في رأيي ، هو أكثر تعبيرا من مصطلح Interactor . ولكن بالنظر إلى زيادة انتشار هذا الأخير ، سوف نستخدم المصطلح المألوف - المتفاعل .
يمكن أن تكون بعض التلميحات البسيطة عندما تصف عملية تجارية مفيدة لك ، فقد تصبح القاعدة الرئيسية لسير العمل: "نتيجة لأي نشاط تجاري ، يجب إعداد مستند". على سبيل المثال ، نحن نعمل على تطوير نظام الخصم. تقديم خصم ، في الواقع ، من وجهة نظر العمل ، نبرم اتفاقية بين الشركة والعميل. يجب توضيح جميع الشروط في هذا العقد. هذا هو ، في مجال DiscountSystem ، سيكون لديك Entites :: Contract. لا تربط الخصم بالعميل ، ولكن قم بإنشاء عقد الكيان الذي يصف قواعد توفيره.
دعنا نعود إلى وصف عملية أعمالنا ، بعد أن أصبحت شفافة لجميع الأشخاص المشاركين في تطويرها ، وكل معرفتك ثابتة. أوصي بأن تبدأ كتابة التعليمات البرمجية مع تسلسل الإجراءات .
قالب تصميم التسلسل مسؤول عن:
- تسلسل الإجراءات ؛
- تنسيق البيانات المرسلة بين الإجراءات ؛
- معالجة الأخطاء التي ارتكبتها الإجراءات أثناء تنفيذها ؛
- عودة نتيجة مجموعة الإجراءات المرتكبة ؛
- هام : إن المسؤولية الأكثر أهمية لهذا النمط من التصميم هي تنفيذ منطق الأعمال.
أود أن أتحدث عن المسؤولية الأخيرة بمزيد من التفصيل إذا كان لدينا نوع من العملية المعقدة - يجب أن نصفها بطريقة توضح ما يحدث دون الخوض في التفاصيل الفنية. يجب أن تصفها بشكل صريح كما تسمح لك مهارات البرمجة الخاصة بك . عهد إلى هذه الفئة بالعضو الأكثر خبرة في فريقك.
دعنا نعود إلى الكعكة: دعنا نحاول وصف عملية تحضيرها من خلال Interactor .
تطبيق
أعطي مثالاً على التنفيذ باستخدام حل LunaPark ، الذي قدمناه في مقال سابق.
module Kitchen module Sequences class CookingPieWithabbage < LunaPark::Interactors::Sequence TEMPERATURE = Values::Temperature.new(180, unit: :cel) def call! Services::CheckProductsAvailability.call list: ingredients dough = Services::BeatDough.call from: Repository::Products.get(beat_ingredients) filler = Services::MakeabbageFiller.call from: Repository::Products.get(filler_ingredients) pie = Services::MakePie.call dough, with: filler bake = Services::BakePie.new pie, temp: TEMPERATURE sleep 5.min until bake.call pie end private attr_accessor :beat_ingredients, :filler_ingredients attr_accessor :pie def ingredients_list beat_ingredients_list + filler_ingredients_list end end end end
كما نرى ، call!
يصف المنطق التجاري بأكمله لعملية الخبز الكعكة. وأنها مريحة للاستخدام لفهم منطق التطبيق.
أيضا ، يمكننا بسهولة وصف عملية فطيرة السمك عن طريق استبدال MakeabbageFiller
مع MakeFishFiller
. وبالتالي ، فإننا نغير بسرعة عملية الأعمال ، دون تعديلات كبيرة في الكود. وأيضًا ، يمكننا ترك كلا التسلسلين في نفس الوقت ، وتوسيع نطاق حالات العمل.
متفق عليه
- طريقة
call!
هي طريقة مطلوبة ؛ وهي تصف ترتيب الإجراءات . - يمكن وصف كل معلمة تهيئة من خلال واضع أو
attr_acessor
:
class Foo < LunaPark::Interactors::Sequence
- يجب أن تكون بقية الطرق خاصة.
مثال للاستخدام
beat_ingredients = [ Entity::Product.new :flour, 500, :gr, Entity::Product.new :oil, 50, :gr, Entity::Product.new :salt, 1, :spoon, Entity::Product.new :milk, 150, :ml, Entity::Product.new :egg, 1, :unit, Entity::Product.new :yeast, 1, :spoon ] filler_ingredients = [ Entity::Product.new :cabbage, 500, :gr, Entity::Product.new :salt, 1, :spoon, Entity::Product.new :pepper, 1, :spoon ] cooking = CookingPieWithabbage.call( beat_ingredients: beat_ingredients, filler_ingredients: filler_ingredients )
يتم تمثيل العملية من خلال الكائن ولدينا جميع الأساليب اللازمة للاتصال بها - هل نجحت المكالمة ، هل حدث أي خطأ أثناء المكالمة ، وإذا كان الأمر كذلك ، فما هو؟
خطأ في التعامل
إذا استذكرنا الآن المبدأ الثالث لتطبيق حالة الاستخدام ، فإننا نلاحظ حقيقة أنه بالإضافة إلى السطر الرئيسي ، كان لدينا أيضًا توجيهات بديلة . هذه هي الأخطاء التي يجب علينا التعامل معها. تأمل مثالاً: لا نريد بالتأكيد أن تسير الأحداث بهذه الطريقة ، لكن لا يمكننا فعل أي شيء حيال ذلك ، فالحقيقة القاسية هي أن الفطائر يتم حرقها بشكل دوري.
Interactor يعترض كافة الأخطاء الموروثة من فئة LunaPark::Errors::Processing
.
كيف يمكننا تتبع الكعكة؟ للقيام بذلك ، حدد الخطأ Burned
في إجراء BakePie
.
module Kitchen module Errors class Burned < LunaPark::Errors::Processing; end end end
وأثناء الخبز ، تأكد من أن فطيرةنا لم تحترق:
module Kitchen module Services class BakePie < LunaPark::Callable def call
في هذه الحالة ، سيعمل فخ الخطأ ، وسنكون قادرين على التعامل معهم في
.
يتم اعتبار الأخطاء غير الموروثة من Processing
بمثابة أخطاء في النظام وسيتم اعتراضها على مستوى الخادم. ما لم ينص على خلاف ذلك ، سيتلقى المستخدم 500 ServerError.
استخدام الممارسة
1. حاول وصف جميع المكالمات في طريقة الاتصال!
يجب عدم تنفيذ كل إجراء بطريقة منفصلة ، مما يجعل الكود أكثر انتفاخًا. عليك أن تنظر إلى الفصل بأكمله عدة مرات لفهم كيفية عمله. تفسد الوصفة لخبز فطيرة:
module Service class CookingPieWithabbage < LunaPark::Interactors::Sequence def call! check_products_availability make_cabbage_filler make_pie bake end private def check_products_availability Services::CheckProductsAvailability.call list: ingredients end
استخدم استدعاء الإجراء مباشرة في الفصل الدراسي. من وجهة نظر روبي ، قد يبدو هذا النهج غير عادي ، لذلك يبدو أكثر قابلية للقراءة:
class DrivingStart < LunaPark::Interactors::Sequence def call! Service::CheckEngine.call Service::StartUpTheIgnition.call car, with: key Service::ChangeGear.call car.gear_box, to: :drive Service::StepOnTheGas.call car.pedals[:right] end end
2. إذا أمكن ، استخدم طريقة فئة الاتصال
3. لا تقم بإنشاء كائنات وظيفية من أجل كتابة التعليمات البرمجية ؛ انظر إلى الموقف
طبقة الخدمة

كما ذكرنا ، يصف المنطاد منطق الأعمال على أعلى مستوى. تكشف طبقة الخدمة ( طبقة الخدمة) بالفعل عن تفاصيل تنفيذ المتطلبات الوظيفية. إذا كنا نتحدث عن عمل الفطيرة ، فعندئذٍ على مستوى المتفاعل ، نقول ببساطة "عجن العجينة" ، دون الخوض في تفاصيل حول كيفية تعجنها. يوصف عملية العجن على مستوى الخدمة . دعنا نعود إلى المصدر الأصلي ، الكتاب الأزرق الكبير :
في المجال المطبق ، هناك عمليات لا يمكنها العثور على مكان طبيعي في كائن من النوع Entity أو Value Object. فهي بطبيعتها ليست أشياء ، ولكنها أنشطة. ولكن نظرًا لأن أساس نموذج التصميم الخاص بنا هو أسلوب الكائن ، فسوف نحاول تحويلهم إلى كائنات.
في هذه المرحلة ، من السهل ارتكاب خطأ شائع: التخلي عن محاولة وضع العملية في كائن مناسب لها ، وبالتالي الوصول إلى البرمجة الإجرائية. ولكن إذا وضعت العملية بقوة في كائن مع تعريف غريب لها ، فإن هذا سيجعل الكائن نفسه يفقد نقاوته ، مما يجعل من الصعب فهمه وإعادة تفاعله. إذا قمت بتنفيذ الكثير من العمليات المعقدة في كائن بسيط ، يمكن أن يتحول إلى ما غير مفهوم ، ما تفعله ليس واضحًا. تتضمن هذه العمليات غالبًا كائنات أخرى في مجال الموضوع ويتم التنسيق بينها لأداء مهمة مشتركة. المسؤولية الإضافية تخلق سلاسل من التبعية بين الكائنات ، وتخلط بين المفاهيم التي يمكن النظر فيها بشكل مستقل.
عند اختيار موقع لتنفيذ وظيفي ، استخدم دائمًا المنطق السليم. مهمتك هي جعل النموذج أكثر تعبيرا. دعونا نلقي نظرة على مثال ، "نحن بحاجة إلى قطع الخشب":
module Entities class Wood def chop
هذه الطريقة ستكون خطأ. الحطب لن يقطع نفسه ، نحن بحاجة إلى فأس:
module Entities class Axe def chop(sacrifice)
إذا استخدمنا نموذج أعمال مبسط ، فسيكون ذلك كافيًا. ولكن إذا احتجت العملية إلى نموذج أكثر تفصيلًا ، فسنحتاج إلى شخص يقوم بقطع الحطب ، وربما بعض السجلات التي سيتم استخدامها كقاعدة للعملية.
module Entities class Human def chop_firewood(wood, axe, chock)
كما ربما خمنت بالفعل ، هذه ليست فكرة جيدة. ليس كلنا منخرطون في قطع الأخشاب ، وهذا ليس واجب مباشر على أي شخص. نلاحظ في كثير من الأحيان مدى تحميل النماذج في روبي أون ريلز ، والتي تحتوي على منطق مماثل: الحصول على خصومات ، إضافة سلع إلى السلة ، سحب الأموال إلى الرصيد. لا ينطبق هذا المنطق على الكيان ، ولكن على العملية التي يشارك فيها هذا الكيان.
module Services class ChopFirewood
بعد أن اكتشفنا المنطق الذي نخزنه في الخدمات ، سنحاول تنفيذ واحد منهم. في معظم الأحيان ، يتم تنفيذ الخدمات من خلال الأساليب أو الأشياء الوظيفية.
كائنات وظيفية
كائن وظيفي يفي بمتطلبات وظيفية واحدة. في أكثر أشكاله بدائية ، يحتوي الكائن الوظيفي على طريقة عامة واحدة - call
.
module Serivices class Sum def initialize(x, y) @x = x @y = y end def call x + y end def self.call(x,y) new(x,y).call end private attr_reader :x, :y end end
هذه الأشياء لها العديد من المزايا: فهي موجزة ، فهي بسيطة للغاية للاختبار. هناك عيب ، يمكن أن تتحول هذه الكائنات إلى عدد كبير. هناك عدة طرق لتجميع الكائنات المتشابهة ؛ في جزء من مشاريعنا ، نقسمها حسب النوع:
- كائن خدمة (خدمة) - كائن ، يقوم بإنشاء كائن جديد ؛
- القيادة (القيادة) - يغير الكائن الحالي ؛
- الوصي (الحرس) - إرجاع خطأ إذا حدث خطأ ما.
كائن الخدمة
في تطبيقنا ، الخدمة - تنفذ متطلبات وظيفية وتُرجع دائمًا قيمة.
module KorovaMilkBar module Services class FindMilk < LunaPark::Callable GLASS_SIZE = Values::Unit.wrap '200g' def initialize(fridge:) @fridge = fridge end def call fridge.shelfs.find { |shelf| shelf.has?(GLASS_SIZE, of: :milk) } end private attr_reader :fridge end end end FindMilk.call(fridge: the_red_one)
القيادة (قيادة)
في تطبيقنا ، يقوم Command - بتنفيذ إجراء واحد ، بتعديل الكائن ، إذا كان true صحيحاً. في الواقع ، لا يقوم الفريق بإنشاء كائن ، لكنه يعدل كائنًا موجودًا.
module KorovaMilkBar module Commands class FillGlass < LunaPark::Callable def initialize(glass, with:) @glass = glass @content = with end def call glass << content true end private attr_reader :fridge end end end glass = Glass.empty milk = Milk.new(200, :gr) glass.empty?
الوصي (الحرس)
يقوم الحارس بفحص منطقي وفي حالة الفشل يعطي خطأ في المعالجة. لا يؤثر هذا النوع من الكائنات على الاتجاه الرئيسي بأي شكل من الأشكال ، ولكنه يغيرنا إلى الاتجاه البديل إذا حدث خطأ ما.
عند تقديم الحليب ، من المهم التأكد من أنه طازج:
module KorovaMilkBar module Guards class IsFresh < LunaPark::Callable def initialize(product) @products = products end def call products.each do |product| raise Errors::Rotten, "
قد تجد أنه من المناسب فصل الكائنات الوظيفية حسب النوع. يمكنك إضافة الخاصة بك ، على سبيل المثال ، منشئ - يخلق كائن بناء على المعلمات.
متفق عليه
- طريقة
call
هي الطريقة العامة الإلزامية فقط. - طريقة
initialize
هي الطريقة العامة الاختيارية الوحيدة. - يجب أن تكون بقية الطرق خاصة.
- يجب أن توارث الأخطاء المنطقية من فئة
LunaPark::Errors::Processing
.
خطأ في التعامل
هناك نوعان من الأخطاء التي يمكن أن تحدث أثناء تشغيل الإجراء .
أخطاء وقت التشغيل
يمكن أن تحدث مثل هذه الأخطاء نتيجة لانتهاك منطق المعالجة.
على سبيل المثال:
- عند إنشاء بريد إلكتروني للمستخدم محجوز ؛
- عندما تحاول شرب الحليب ، فقد انتهى.
- رفضت خدمة microservice أخرى الإجراء (لسبب منطقي ، وليس لأن الخدمة غير متوفرة).
في جميع الاحتمالات ، سيرغب المستخدم في معرفة هذه الأخطاء. أيضا ، ربما هذه هي الأخطاء
والتي يمكن أن نتوقع.
يجب أن يتم توريث هذه الأخطاء من LunaPark::Errors::Processing
أخطاء النظام
الأخطاء التي حدثت نتيجة لتعطل النظام.
على سبيل المثال:
- قاعدة البيانات لا تعمل ؛
- شيء مقسوما على صفر.
في جميع الأحوال ، لا يمكننا التنبؤ بهذه الأخطاء ولا يمكننا أن نقول للمستخدم أي شيء ، باستثناء أن كل شيء سيء جدًا ، ونرسل للمطورين تقريرًا يدعو إلى اتخاذ إجراء. يجب أن يتم توريث هذه الأخطاء من SystemError
هناك أيضًا أخطاء في التحقق من الصحة ، والتي سنناقشها بمزيد من التفصيل في المقالة التالية.
استخدام الممارسة
1. استخدام المتغيرات لزيادة قابلية القراءة
module Fishing
2. تمرير الكائنات ، وليس المعلمات
حاول أن تجعل أداة التهيئة بسيطة إذا لم تكن معالجة المعلمات هي غرضها.
تمرير الكائنات ، وليس المعلمات.
module Service
3. استخدم اسم الإجراءات - فعل الفعل وكائن التأثير.
4. إذا كان ذلك ممكنا ، استخدم طريقة فئة الاتصال
عادةً ما يكون مثيل لفئة الإجراءات ، ونادراً ما يستخدم بخلاف الكتابة لإجراء مكالمة.
5. معالجة الأخطاء ليست مهمة خدمة
وحدات
حتى هذه اللحظة ، نظرنا في تطبيق طبقة الخدمة كمجموعة من الكائنات الوظيفية. ولكن يمكننا بسهولة وضع الأساليب على هذه الطبقة:
module Services def sum(a, b) a + b end end
هناك مشكلة أخرى تواجهنا وهي عدد كبير من مرافق الخدمات. بدلاً من "نموذج الدهون في القضبان" ، الذي وصلنا إليه ، حصلنا على "مجلد خدمات الدهون". هناك عدة طرق لتنظيم الهيكل للحد من حجم المأساة. اريك ايفانز يحل هذا عن طريق الجمع بين عدد من الوظائف في فئة واحدة. تخيل أننا بحاجة إلى تصميم العمليات التجارية للمربية ، أرينا روديونوفنا ، يمكنها إطعام بوشكين ووضعه في الفراش:
class NoonService def initialize(arina_radionovna, pushkin)
هذا النهج هو أكثر صحة من وجهة نظر OOP. لكننا نقترح التخلي عنها ، على الأقل في المراحل الأولية. لا يبدأ المبرمجون ذوو الخبرة في كتابة الكثير من التعليمات البرمجية في هذه الفئة ، مما يؤدي في النهاية إلى زيادة الاتصال. بدلاً من ذلك ، يمكنك استخدام الوحدة النمطية التي تمثل النشاط على أنه بعض التجريد:
module Services module Noon class ToFeed def call!
عند التقسيم إلى وحدات ، يجب ملاحظة اقتران خارجي منخفض (اقتران منخفض) بتماسك داخلي عالي (تماسك عالٍ) ، لكننا نستخدم وحدات مثل الخدمات أو التفاعلات ، وهذا يتعارض أيضًا مع أفكار العمارة الخالصة. هذا هو الاختيار الواعي الذي يسهل الإدراك. من خلال اسم الملف ، نحن نفهم نوع التصميم الذي ينفذه هذا أو ذاك ، إذا كان هذا واضحًا بالنسبة للمبرمج ذي الخبرة ، فليس هذا هو الحال دائمًا بالنسبة للمبتدئين. بمجرد أن يكون فريقك جاهزًا ، تجاهل هذا الفائض.
لاقتباس مقتطفات صغيرة أخرى من الكتاب الأزرق الكبير:
اختيار الوحدات النمطية التي تحكي تاريخ النظام وتحتوي على مجموعات متماسكة من المفاهيم. من هذا في كثير من الأحيان ينشأ الاعتماد المنخفض للوحدات على بعضها البعض من تلقاء نفسها. ولكن إذا لم يكن الأمر كذلك ، فابحث عن طريقة لتغيير النموذج بطريقة تفصل المفاهيم عن بعضها البعض ، أو ابحث عن المفهوم الذي كان مفقودًا في النموذج ، والذي يمكن أن يصبح أساسًا للوحدة النمطية وبذلك يجمع عناصر النموذج معًا بطريقة طبيعية وذات مغزى. تحقيق الاعتماد المنخفض على الوحدات النمطية على بعضها البعض ، بمعنى أنه يمكن تحليل المفاهيم في الوحدات النمطية المختلفة وتصورها بشكل مستقل عن بعضها البعض. قم بتحسين النموذج حتى تظهر الحدود الطبيعية فيه وفقًا للمفاهيم عالية المستوى لموضوع الموضوع ، ولا يتم تقسيم الكود المقابل وفقًا لذلك.
أعط أسماء الوحدات التي سيتم تضمينها في اللغة الموحدة. يجب أن تعكس كل من النماذج نفسها وأسمائها المعرفة وفهم مجال الموضوع.
موضوع الوحدات كبير ومثير للاهتمام ، لكن بشكل واضح يتجاوز موضوع هذا المقال. في المرة القادمة سوف نتحدث معك حول المستودعات والمحولات . لقد فتحنا قناة برقية مريحة حيث نود مشاركة المواد حول موضوع DDD. نحن نرحب بأسئلتك وردود الفعل.