سير العمل - محرك العمليات التجارية ل. صافي كور

صورة


مرحبا بالجميع!


قررنا دعم موضوع ترحيل المشروع باستخدام Windows Workflow Foundation إلى .Net Core ، والذي بدأه زملاء من DIRECTUM ، لأننا واجهنا مشكلة مماثلة قبل عامين وذهبنا بطريقتنا الخاصة.


لنبدأ بالقصة


منتجنا الرائد ، Avanpost IDM ، هو دورة حياة الحساب ونظام إدارة وصول الموظفين. إنه يعرف كيفية إدارة الوصول تلقائيًا استنادًا إلى نموذج الدور ، ووفقًا للطلبات. في فجر تشكيل المنتج ، كان لدينا نظام خدمة ذاتية بسيط إلى حد ما مع سير عمل بسيط خطوة بخطوة ، والذي لم يكن المحرك مطلوبًا من حيث المبدأ.


صورة


ومع ذلك ، عندما واجهنا عملاء كبيرين ، أدركنا أن هناك حاجة إلى أداة أكثر مرونة ، لأن متطلباتهم المتعلقة بعمليات تنسيق حقوق الوصول سعت إلى قواعد سير عمل جيد وثقل. بعد تحليل المتطلبات ، قررنا تطوير محرر العمليات الخاص بنا بتنسيق BPMN ، بما يتناسب مع احتياجاتنا. سنتحدث عن تطوير المحرر باستخدام React.js + SVG في وقت لاحق قليلاً ، واليوم سنناقش موضوع الواجهة الخلفية - محرك سير العمل أو محرك العمليات التجارية.


متطلبات


في بداية تطوير النظام ، كان لدينا المتطلبات التالية للمحرك:


  • دعم الرسوم البيانية للعملية ، وهي صيغة مفهومة ، والقدرة على البث من تنسيقنا إلى تنسيق المحرك
  • تخزين حالة العملية
  • عملية دعم الإصدار
  • دعم التنفيذ المتوازي (فروع) العملية
  • ترخيص مناسب لاستخدام الحل في منتج تجاري منسوخ
  • دعم للتحجيم الأفقي

بعد تحليل السوق (لعام 2014) ، توصلنا إلى حل غير بديل تقريبًا لـ .Net: Windows Workflow Foundation.


أساس سير عمل Windows (WWF)


WWF هي تقنية Microsoft لتحديد مهام سير العمل وتنفيذها وإدارتها.


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


يمكنك رسم عملية مباشرة في Visual Studio. يتم تخزين الرسم البياني لعملية الأعمال المترجمة في هامل ، وهو مناسب جدًا - يتم وصف التنسيق ، ومن الممكن إنشاء مصمم عمليات مكتوب ذاتيًا. هذا من جهة. من ناحية أخرى ، فإن Xaml ليس هو التنسيق الأكثر ملاءمة لتخزين وصف - فالبرنامج المتجمّع لعملية حقيقية إلى حد ما أوضح أنه ضخم ، ليس أقله بسبب التكرار. من الصعب للغاية فهم ذلك ، لكن عليك فهمه.


ولكن إذا كان بإمكان المرء ، عاجلاً أم آجلاً ، فهم مخططات زين وتعلم قراءتها ، فإن الافتقار إلى الشفافية في تشغيل المحرك نفسه يزيد من المتاعب بالفعل عندما يستخدم المستخدمون النظام. عندما يأتي الخطأ من أحشاء Wf ، فإنه ليس من الممكن دائمًا اكتشاف 100٪ بالضبط سبب سبب الفشل. المصدر المغلق والقسوة النسبية لا تساعد القضية. في كثير من الأحيان كانت إصلاحات الشوائب بسبب الأعراض.


في الإنصاف ، يجدر التوضيح هنا أن المشكلات الموضحة أعلاه ، بالنسبة للجزء الأكبر ، ابتليت بنا بسبب التخصيص القوي على Wf. سيقول أحد القراء بشكل مؤكد أننا أنشأنا نحن أنفسنا مجموعة من المشاكل ، ثم قمنا بحلها بطوليًا. كان من الضروري صنع محرك عصامي من البداية. بشكل عام ، سيكونون على حق.


في الخلاصة ، كان الحل يعمل بشكل مستقر بما فيه الكفاية وذهب بنجاح إلى الإنتاج. لكن انتقال منتجاتنا إلى .Net Core أجبرنا على التخلي عن الصندوق العالمي للطبيعة والبحث عن محرك آخر لعملية الأعمال ، لأن اعتبارًا من مايو 2019 ، لم يتم ترحيل Windows Workflow Foundation إلى .Net Core. بينما كنا نبحث عن محرك جديد - موضوع مقالة منفصلة ، ولكن في النهاية استقرنا على Workflow Core.


سير العمل الأساسية


Workflow Core هو محرك مجاني للعمل التجاري. تم تطويره بموجب ترخيص MIT ، أي أنه يمكن استخدامه بأمان في التطوير التجاري.


يتم ذلك بنشاط من قبل شخص واحد ، والعديد منهم يقدمون طلب سحب بشكل دوري. هناك منافذ للغات أخرى (Java و Python والعديد غيرها).


وضع المحرك خفيف الوزن. في الواقع ، هذا مجرد مضيف للتنفيذ المتسلسل للإجراءات المجمعة حسب أي قواعد أعمال.


يحتوي المشروع على وثائق ويكي . لسوء الحظ ، لا يصف كل ميزات المحرك. ومع ذلك ، سيكون من غير المعقول طلب وثائق كاملة - مشروع مفتوح المصدر مدعوم من قبل أحد المتحمسين. لذلك ، ستكون ويكي كافية للبدء.


من خارج الصندوق ، يوجد دعم لتخزين حالة العملية في وحدة التخزين الخارجية (تخزين الثبات). مقدمو الخدمة قياسيون بالنسبة إلى:


  • MongoDB
  • مزود خدمة
  • كيو
  • سكليتي
  • الأمازون DynamoDB

اكتب مزودك ليس مشكلة. نحن نأخذ مصادر أي معيار ونفعل كمثال.


يتم دعم القياس الأفقي ، أي أنه يمكنك تشغيل المحرك على عدة عقد في وقت واحد ، مع وجود نقطة تخزين واحدة لحالات العملية (تخزين مستمر واحد). في هذه الحالة ، يجب أن يكون موضع قائمة انتظار المهام الداخلية للمحرك في وحدة التخزين العامة (rabbitMQ ، كخيار). لاستبعاد تنفيذ مهمة واحدة من خلال عدة عقد ، يتم توفير مدير قفل في نفس الوقت. عن طريق القياس مع مزودي التخزين الخارجيين ، هناك تطبيقات قياسية:


  • استئجار أزور التخزين
  • رديس
  • AWS DynamoDB
  • SQLServer (في المصدر هناك ، ولكن لا شيء يقال في الوثائق)

التعرف على شيء جديد أسهل في البدء بمثال. لذلك دعونا نفعل ذلك. سوف أصف بناء عملية بسيطة من البداية ، مع شرح. مثال قد يبدو بسيطا مستحيل. أوافق - إنه بسيط. أكثر لتبدأ.


دعنا نذهب.


الخطوة (الخطوة)


الخطوة هي خطوة في العملية التي يتم فيها تنفيذ أي إجراءات. بنيت العملية برمتها من سلسلة من الخطوات. خطوة واحدة يمكن أن تؤدي العديد من الإجراءات ، ويمكن تكرارها ، على سبيل المثال ، لبعض الأحداث من الخارج. هناك مجموعة من الخطوات الممنوحة بالمنطق "خارج الصندوق":


  • WAITFOR
  • إذا
  • في حين
  • ForEach
  • تأخير
  • مواز
  • جدول
  • تكرر

بالطبع ، في بعض الأوليات المضمنة لا يمكنك تحمل العملية. نحتاج إلى خطوات تكمل مهام العمل. لذلك ، في الوقت الحالي ، ضعهم جانباً واتخاذ الخطوات من خلال منطقنا الخاص. للقيام بذلك ، تحتاج إلى أن ترث من تجريد StepBody .


public abstract class StepBody : IStepBody { public abstract ExecutionResult Run(IStepExecutionContext context); } 

يتم تنفيذ طريقة التشغيل عندما تدخل العملية خطوة. من الضروري وضع المنطق اللازم فيه.


  public abstract class StepBody : IStepBody { public abstract ExecutionResult Run(IStepExecutionContext context); } 

خطوات دعم حقن التبعية. للقيام بذلك ، يكفي تسجيلهم في نفس الحاوية مثل التبعيات اللازمة.


من الواضح أن العملية تحتاج إلى سياقها الخاص - وهو المكان الذي يمكن فيه إضافة نتائج التنفيذ الوسيطة. يحتوي Wf core على سياقه الخاص لتنفيذ عملية تخزن معلومات حول حالتها الحالية. يمكنك الوصول إليه باستخدام متغير السياق من الأسلوب Run (). بالإضافة إلى المدمج في ، يمكننا استخدام سياقنا.


سنقوم بتحليل طرق وصف وتسجيل العملية بمزيد من التفاصيل أدناه ، في الوقت الحالي ، نحن ببساطة نحدد فئة معينة - السياق.


  public class ProcessContext { public int Number1 {get;set;} public int Number2 {get;set;} public string StepResult {get;set;} public ProcessContext() { Number1 = 1; Number2 = 2; } } 

في المتغيرات رقم نكتب الأرقام. في متغير StepResult - نتيجة الخطوة.


قررنا في السياق. يمكنك كتابة خطوتك الخاصة:


  public class CustomStep : StepBody { private readonly Ilogger _log; public int Input1 { get; set; } public int Input2 { get; set; } public string Action { get; set; } public string Result { get; set; } public CustomStep(Ilogger log) { _log = log; } public override ExecutionResult Run(IStepExecutionContext context) { Result = ”none”; if (Action ==”sum”) { Result = Number1 + Number2; } if (Action ==”dif”){ Result = Number1 - Number2; } return ExecutionResult.Next(); } } 

المنطق بسيط للغاية: يأتي رقمان واسم العملية في الإدخال. تتم كتابة نتيجة العملية إلى متغير الإخراج النتيجة . إذا لم يتم تعريف العملية ، فإن النتيجة لن تكون.


قررنا في السياق ، هناك خطوة مع المنطق الذي نحتاجه أيضا. الآن نحن بحاجة إلى تسجيل عمليتنا في المحرك.


وصف العملية. التسجيل في المحرك.


هناك طريقتان لوصف العملية. الأول هو الوصف في الكود - القرص الصلب.


يوصف العملية من خلال واجهة بطلاقة . من الضروري أن ترث من واجهة IWorkflow <T> المعممة ، حيث T هي فئة سياق النموذج. في حالتنا ، هذا هو ProcessContext .


يبدو مثل هذا:


  public class SimpleWorkflow : IWorkflow<ProcessContext> { public void Build(IWorkflowBuilder<ProcessContext> builder) { //    } public string Id => "SomeWorkflow"; public int Version => 1; } 

الوصف نفسه سيكون داخل طريقة البناء . حقول المعرف والإصدار مطلوبة أيضًا. يدعم Wf core عملية الإصدار - يمكنك تسجيل إصدارات عملية n بنفس المعرف. يكون هذا مناسبًا عندما تحتاج إلى تحديث عملية موجودة وفي الوقت نفسه إعطاء "مباشر" للمهام الحالية.


نحن تصف عملية بسيطة:


  public class SimpleWorkflow : IWorkflow<ProcessContext> { public void Build(IWorkflowBuilder<ProcessContext> builder) { builder.StartWith<CustomStep>() .Input(step => step.Input1, data => data.Number1) .Input(step => step.Input2, data => data.Number2) .Input(step => step.Action, data => “sum”) .Output(data => data.StepResult, step => step.Result) .EndWorkflow(); } public string Id => "SomeWorkflow"; public int Version => 1; } 

إذا تمت ترجمتها إلى لغة "بشرية" ، فستتحول إلى شيء مثل هذا: تبدأ العملية بخطوة CustomStep . قيمة حقل الخطوة تؤخذ Input1 من حقل السياق Number1 ، قيمة حقل الخطوة Input2 مأخوذة من حقل السياق رقم 2 ، حقل الإجراء مضغوط على القيمة "مجموع" . تتم كتابة الإخراج من حقل النتيجة إلى حقل سياق StepResult . أكمل العملية.


موافق ، لقد تبين أن الشفرة قابلة للقراءة للغاية ، من الممكن تمامًا اكتشافها ، حتى بدون معرفة خاصة في C #.


أضف خطوة أخرى إلى عمليتنا ، والتي ستؤدي إلى إخراج نتيجة الخطوة السابقة إلى السجل:


  public class CustomStep : StepBody { private readonly Ilogger _log; public string TextToOutput { get; set; } public CustomStep(Ilogger log) { //    _log = log; } public override ExecutionResult Run(IStepExecutionContext context) { _log.Debug(TextToOutput); return ExecutionResult.Next(); } } 

وتحديث العملية:


  public class SimpleWorkflow : IWorkflow<ProcessContext> { public void Build(IWorkflowBuilder<ProcessContext> builder) { builder.StartWith<CustomStep>() .Input(step => step.Input1, data => data.Number1) .Input(step => step.Input2, data => data.Number2) .Input(step => step.Action, data => “sum”) .Output(data => data.StepResult, step => step.Result) .Then<OutputStep>.Input(step => step.TextToOutput, data => data.StepResult) .EndWorkflow(); } public string Id => "SomeWorkflow"; public int Version => 2; } 

الآن ، بعد الخطوة مع عملية الإضافة ، تتبع خطوة إخراج النتيجة إلى السجل. إلى المدخلات ، نقوم بتمرير متغير النتيجة والسياق الذي تمت كتابة نتيجة التنفيذ فيه في الخطوة الأخيرة. سأحصل على الحرية في التأكيد على أن مثل هذا الوصف من خلال كود (hardcode) في الأنظمة الحقيقية سيكون ذا فائدة قليلة. إلا لبعض العمليات المكتبية. هو أكثر إثارة للاهتمام بكثير لتكون قادرة على تخزين الدائرة بشكل منفصل. على الأقل ، لا يتعين علينا إعادة تجميع المشروع في كل مرة نحتاج فيها إلى تغيير شيء ما في العملية أو إضافة مشروع جديد. يوفر Wf core هذه الميزة عن طريق تخزين مخطط json. نواصل توسيع مثالنا.


وصف عملية Json


علاوة على ذلك ، لن أقدم وصفًا للكود. هذه ليست مثيرة للاهتمام بشكل خاص ، وسوف تضخيم المقال فقط.


Wf core يدعم وصف المخطط في json. في رأيي ، json مرئي أكثر من xaml (موضوع جيد لل holivar في التعليقات :)). هيكل الملف بسيط جدا:


 { "Id": "SomeWorkflow", "Version": 1, "DataType": "App.ProcessContext, App", "Steps": [ { /*step1*/ }, { /*step2*/ } ] } 

يشير حقل DataType إلى اسم فئة السياق المؤهل بالكامل واسم التجميع الذي تم وصفه به. الخطوات تخزن مجموعة من جميع الخطوات في العملية. املأ عنصر الخطوات :


 { "Id": "SomeWorkflow", "Version": 1, "DataType": "App.ProcessContext, App", "Steps": [ { "Id": "Eval", "StepType": "App.CustomStep, App", "NextStepId": "Output", "Inputs": { "Input1": "data.Number1", "Input2": "data.Number2" }, "Outputs": { "StepResult": "step.Result" } }, { "Id": "Output", "StepType": "App.OutputStep, App", "Inputs": { "TextToOutput": "data.StepResult" } } ] } 

دعنا نلقي نظرة فاحصة على هيكل وصف الخطوة عبر json.


يقوم الحقلان Id و NextStepId بتخزين معرف هذه الخطوة ومؤشر للخطوة التالية. علاوة على ذلك ، فإن ترتيب عناصر المجموعة غير مهم.


يشبه StepType حقل DataType ، وهو يحتوي على الاسم الكامل لفئة الخطوة (نوع يرث من StepBody وينفذ منطق الخطوة) واسم التجميع. أكثر إثارة للاهتمام هي كائنات المدخلات والمخرجات . يتم تعيينها في شكل رسم الخرائط.


في حالة المدخلات ، اسم عنصر json هو اسم حقل الفصل لخطوتنا ؛ قيمة العنصر هي اسم الحقل في الفصل ، سياق العملية.


بالنسبة إلى المخرجات ، على العكس من ذلك ، فإن اسم عنصر json هو اسم الحقل في الفصل ، وسياق العملية ؛ قيمة العنصر هي اسم حقل الفصل لخطوتنا.


لماذا يتم تحديد حقول السياق عبر البيانات. {Field_name} ، وفي حالة الإخراج ، الخطوة. {Field_name} ؟ لأن wf core ، يتم تنفيذ قيمة العنصر كتعبير C # ( يتم استخدام مكتبة التعبيرات الديناميكية ). هذا شيء مفيد إلى حد ما ، بمساعدته يمكنك وضع بعض منطق العمل مباشرة داخل المخطط ، إذا كان المهندس المعماري بالطبع يوافق على مثل هذا العار :).


نحن تنويع مخطط مع البدائية القياسية. إضافة شرطية إذا الخطوة ومعالجة حدث خارجي.


إذا

البدائية إذا . هنا تبدأ الصعوبات. إذا كنت معتادًا على bpmn ورسم العمليات في هذا الترميز ، فستجد إعدادًا سهلاً. وفقًا للوثائق ، يتم وصف الخطوة على النحو التالي:


 { "Id": "IfStep", "StepType": "WorkflowCore.Primitives.If, WorkflowCore", "NextStepId": "nextStep", "Inputs": { "Condition": "<<expression to evaluate>>" }, "Do": [ [ { /*do1*/ }, { /*do2*/ } ] ] } 

لا يوجد شعور بأن هناك شيئا خطأ هنا؟ لدي واحدة. يتم تعيين إدخال الخطوة إلى الشرط - التعبير. بعد ذلك ، قمنا بتعيين قائمة الخطوات داخل مجموعة Do (الإجراءات). لذا ، أين هو فرع كاذبة ؟ لماذا لا توجد مجموعة تفعل لخطأ؟ في الواقع هناك. من المفهوم أن فرع False هو مجرد مرور على طول العملية ، أي بعد المؤشر في NextStepId . في البداية ، كنت في حيرة من أمري بسبب هذا. حسنا ، فرزها. وإن لم يكن. إذا كانت الإجراءات العملية في حالة True بحاجة إلى وضعها داخل Do ، فهذا ما ستكون عليه json "الجميلة". وإذا كان هناك هذه إذا أرفق مع عشرات؟ كل شيء سوف يذهب جانبية. يقولون أيضًا أن المخطط على xaml يصعب قراءته. هناك اختراق صغير. فقط خذ الشاشة على نطاق أوسع. لقد ذكرنا أعلاه أن ترتيب الخطوات في المجموعة لا يهم ، فالانتقال يتبع العلامات. يمكن استخدامه. أضف خطوة أخرى:


 { "Id": "Jump", "StepType": "App.JumpStep, App", "NextStepId": "" } 

تخمين ما أنا يؤدي إلى؟ صحيح ، نحن نقدم خطوة خدمة ، والتي في العبور تأخذ العملية إلى خطوة في NextStepId .


تحديث مخططنا:


 { "Id": "SomeWorkflow", "Version": 1, "DataType": "App.ProcessContext, App", "Steps": [ { "Id": "Eval", "StepType": "App.CustomStep, App", "NextStepId": "MyIfStep", "Inputs": { "Input1": "data.Number1", "Input2": "data.Number2" }, "Outputs": { "StepResult": "step.Result" } }, { "Id": "MyIfStep", "StepType": "WorkflowCore.Primitives.If, WorkflowCore", "NextStepId": "OutputEmptyResult", "Inputs": { "Condition": "!String.IsNullOrEmpty(data.StepResult)" }, "Do": [ [ { "Id": "Jump", "StepType": "App.JumpStep, App", "NextStepId": "Output" } ] ] }, { "Id": "Output", "StepType": "App.OutputStep, App", "Inputs": { "TextToOutput": "data.StepResult" } }, { "Id": "OutputEmptyResult", "StepType": "App.OutputStep, App", "Inputs": { "TextToOutput": "\"Empty result\"" } } ] } 

تتحقق الخطوة If لمعرفة ما إذا كانت نتيجة الخطوة Eval فارغة. إذا لم تكن فارغة ، فسنعرض النتيجة ؛ وإذا كانت فارغة ، فستظهر الرسالة " نتيجة فارغة ". تنقل خطوة الانتقال العملية إلى خطوة الإخراج ، والتي تقع خارج مجموعة المهام . وهكذا ، حافظنا على "العمودي" للمخطط. أيضًا ، بهذه الطريقة ، يمكن للمرء القفز بخطوات إلى الخلف ، أي لتنظيم دورة. هناك بدائل مدمجة للحلقات في قلب wf ، لكنها ليست مريحة دائمًا. في bpmn ، على سبيل المثال ، يتم تنظيم الحلقات من خلال If .


استخدم هذا النهج أو المعيار ، الأمر متروك لك. بالنسبة لنا ، كانت هذه المنظمة خطوات أكثر ملاءمة.


WAITFOR

تسمح بدائية WaitFor للعالم الخارجي بالتأثير على العملية عندما تكون قيد التشغيل بالفعل. على سبيل المثال ، إذا كان أي مستخدم في مرحلة عملية الموافقة مطلوبًا من قِبل أي مستخدم. ستقف العملية في خطوة WaitFor حتى تستقبل حدثًا تشترك فيه.


الهيكل البدائي:


 { "Id": "Wait", "StepType": "WorkflowCore.Primitives.WaitFor, WorkflowCore", "NextStepId": "NextStep", "CancelCondition": "If(cancel==true)", "Inputs": { "EventName": "\"UserAction\"", "EventKey": "\"DoSum\"", "EffectiveDate": "DateTime.Now" } } 

ساوضح المعلمات قليلا.


CancelCondition - شرط لمقاطعة الانتظار. يوفر القدرة على مقاطعة انتظار الحدث والمتابعة خلال العملية. على سبيل المثال ، إذا كانت هناك عملية تنتظر أحداثًا مختلفة في نفس الوقت (يدعم wf core التنفيذ المتوازي للخطوات) ، فليس من الضروري انتظار وصول الجميع ، وفي هذه الحالة ، سوف يساعدنا CancelCondition . نضيف علامة منطقية إلى متغيرات السياق ، وعند استلام الحدث ، قمنا بتعيين العلامة على true - تم الانتهاء من جميع خطوات WaitFor .


EventName و EventKey - اسم ومفتاح الحدث. هناك حاجة إلى الحقول من أجل التمييز بين الأحداث ، أي في نظام حقيقي مع عدد كبير من عمليات العمل في وقت واحد. بحيث يفهم المحرك الحدث المقصود من أجل أي عملية وما هي الخطوة.


EffectiveDate - حقل اختياري يضيف الطابع الزمني للحدث. قد يكون مفيدًا إذا كنت بحاجة إلى نشر حدث "في المستقبل". من أجل نشرها على الفور ، يمكنك ترك المعلمة فارغة أو ضبط الوقت الحالي.


ليس من المناسب في جميع الحالات اتخاذ خطوة منفصلة لمعالجة ردود الفعل من الخارج ؛ بل إنه عادة ما يكون ضروريًا. يمكن تجنب خطوة إضافية عن طريق إضافة توقع لحدث خارجي ومنطق معالجته إلى الخطوة المعتادة. نحن نكمل خطوة CustomStep من خلال الاشتراك في حدث خارجي:


  public class CustomStep : StepBody { private readonly Ilogger _log; public string TextToOutput { get; set; } public CustomStep(Ilogger log) { _log = log; } public override ExecutionResult Run(IStepExecutionContext context) { //-  return ExecutionResult.WaitForEvent("eventName", "eventKey", DateTime.Now); } } 

استخدمنا طريقة تمديد WaitForEvent () القياسية. يقبل المعلمات المذكورة سابقًا EventName و EventKey و EffectiveDate . بعد الانتهاء من منطق مثل هذه الخطوة ، ستنتظر العملية الحدث الموضح وستتصل مرة أخرى بأسلوب Run () في وقت نشر الحدث في ناقل المحرك. ومع ذلك ، في النموذج الحالي ، لا يمكننا التمييز بين لحظات الإدخال الأولي في الخطوة والإدخال بعد الحدث. ولكني أود أن أفصل المنطق بطريقة أو بأخرى على مستوى الخطوة. وستساعدنا علامة EventPublished في ذلك. إنه موجود داخل السياق العام للعملية ، ويمكنك الحصول عليه كما يلي:


 var ifEvent=context.ExecutionPointer.EventPublished; 

بناءً على هذه العلامة ، يمكنك تقسيم المنطق بأمان إلى قبل وبعد حدث خارجي.


توضيح مهم - وفقًا لفكرة منشئ المحرك ، يمكن توقيع خطوة واحدة فقط على حدث واحد والرد عليها مرة واحدة. بالنسبة لبعض المهام ، يعد هذا الحد غير سارة للغاية. لقد اضطررنا حتى إلى "إنهاء" المحرك من أجل الابتعاد عن هذا الفارق الدقيق. سنقوم بتخطي وصفها في هذه المقالة ، وإلا لن تنتهي المقالة :). سيتم تناول ممارسات الاستخدام الأكثر تعقيدًا وأمثلة للتحسينات في المقالات التالية.


عملية التسجيل في المحرك. نشر الحدث على الحافلة.


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


سنستخدم طريقة تمديد AddWorkflow () القياسية ، والتي ستضع تبعياتها في حاوية IoC الخاصة بنا.


يبدو مثل هذا:


 public static IServiceCollection AddWorkflow(this IServiceCollection services, Action<WorkflowOptions> setupAction = null) 

IServiceCollection - واجهة - عقد لمجموعة من أوصاف الخدمة. يعيش داخل شركة DI من Microsoft (يمكن قراءة المزيد حول هذا الموضوع هنا )


WorkflowOptions - إعدادات المحرك الأساسية. ليس من الضروري ضبطها بنفسك ؛ فالقيم القياسية مقبولة تمامًا بالنسبة للتعارف الأول. نحن نذهب أبعد من ذلك.


إذا تم وصف العملية في التعليمات البرمجية ، فسيتم التسجيل مثل هذا:


 var host = _serviceProvider.GetService<IWorkflowHost>(); host.RegisterWorkflow<SomeWorkflow, ProcessContext>(); 

إذا تم وصف العملية عبر json ، فيجب تسجيلها على النحو التالي (بالطبع ، يجب تحميل وصف json مسبقًا من موقع التخزين):


 var host = _serviceProvider.GetService<IWorkflowHost>(); var definitionLoader = _serviceProvider.GetService<IDefinitionLoader>(); var definition = loader.LoadDefinition({*json  *}); 

علاوة على ذلك ، لكلا الخيارين ، سيكون الكود هو نفسه:


 host.Start(); //      host.StartWorkflow(definitionId, version, context); //      /// host.Stop(); / /     

المعلمة definitionId هي معرف العملية. ما هو مكتوب في حقل معرف العملية. في هذه الحالة ، id = SomeWorkflow .


تحدد معلمة الإصدار أي إصدار من العملية لتشغيله. يوفر المحرك القدرة على التسجيل الفوري لإصدارات العملية بمعرف واحد. يكون هذا مناسبًا عندما تحتاج إلى إجراء تغييرات على وصف العملية دون كسر المهام الجارية بالفعل - سيتم إنشاء مهام جديدة وفقًا للإصدار الجديد ، وستعيش المهام القديمة بهدوء على الإصدار القديم.


معلمة السياق هي مثيل لسياق العملية.


أساليب host.Start () و host.Stop () بدء وإيقاف عملية استضافة. إذا كان إطلاق العمليات في التطبيق مهمة تطبيقية ويتم تنفيذها بشكل دوري ، فيجب إيقاف الاستضافة. إذا كان التطبيق لديه التركيز الرئيسي على تنفيذ عمليات مختلفة ، ثم لا يمكن أن تتوقف استضافة.


هناك طريقة لإرسال رسائل من العالم الخارجي إلى ناقل المحرك ، والتي ستقوم بعد ذلك بتوزيعها على المشتركين:


 Task PublishEvent(string eventName, string eventKey, object eventData, DateTime effectiveDate = null); 

كان وصف المعلمات أعلى في المقالة ( انظر الجزء البدائي من WaitFor ).


استنتاج


لقد خاطرنا بالتأكيد عندما قررنا تأييدًا لمشروع Workflow Core - openource ، والذي تم تطويره بنشاط من قبل شخص واحد ، وحتى مع وجود وثائق ضعيفة للغاية. وعلى الأرجح لن تجد ممارسات حقيقية لاستخدام wf core في أنظمة الإنتاج (باستثناء نظامنا). بالطبع ، بعد اختيار طبقة منفصلة من التجريدات ، قمنا بتأمين أنفسنا ضد حالة الفشل والحاجة إلى العودة بسرعة إلى الصندوق العالمي للطبيعة ، على سبيل المثال ، أو حل مكتوب ذاتيًا ، لكن كل شيء سار بشكل جيد والفشل لم يتحقق.


أدى التبديل إلى محرك Workflow Core مفتوح المصدر إلى حل عدد معين من المشكلات التي منعتنا من العيش بسلام على الصندوق العالمي للطبيعة. الأهم منهم ، بالطبع ، دعم .Net Core وعدم وجود مثل هذا ، حتى في الخطط ، الصندوق العالمي للطبيعة.


فيما يلي المصدر المفتوح. من خلال العمل مع الصندوق العالمي للطبيعة (WWF) والحصول على العديد من الأخطاء من الأمعاء ، ستكون القدرة على قراءة المصدر على الأقل مفيدة للغاية. ناهيك عن تغيير شيء ما فيها. هنا مع Workflow Core الحرية الكاملة (بما في ذلك الترخيص - MIT). إذا ظهر خطأ فجأة من أحشاء المحرك ، فما عليك سوى تنزيل المصادر من جيثب وابحث بهدوء عن سبب حدوثه. نعم ، مجرد القدرة على تشغيل المحرك في وضع التصحيح مع نقاط التوقف بالفعل يسهل العملية إلى حد كبير.


بالطبع ، لحل بعض المشاكل ، جلبت Workflow Core مشاكلها الجديدة. كان علينا إجراء قدر كبير من التغييرات في قلب المحرك. ولكن. العمل على "الانتهاء" لأنفسهم أقل تكلفة في الوقت المناسب من تطوير المحرك الخاص بك من نقطة الصفر. كان الحل النهائي مقبولًا تمامًا من حيث السرعة والاستقرار ، مما أتاح لنا أن ننسى مشاكل المحرك والتركيز على تطوير القيمة التجارية للمنتج.


ملاحظة: إذا تبين أن الموضوع مثير للاهتمام ، فستكون هناك المزيد من المقالات حول wf core ، مع تحليل أعمق للمحرك وحلول لمشاكل العمل المعقدة.

Source: https://habr.com/ru/post/ar475902/


All Articles