ViennaNET: مجموعة من المكتبات للواجهة الخلفية. الجزء 2

يواصل مجتمع مطوري Raiffeisenbank .NET مراجعة محتويات ViennaNET بشكل موجز. حول كيف ولماذا وصلنا إلى هذا، يمكنك قراءة الجزء الأول.

في هذه المقالة، سنستعرض المكتبات التي لم يتم النظر فيها بعد للتعامل مع المعاملات الموزعة وقوائم الانتظار وقواعد البيانات، والتي يمكن العثور عليها في مستودع GitHub الخاص بنا (المصادر هنام)، و حزم Nuget هنا.

ViennaNET: مجموعة من المكتبات للواجهة الخلفية. الجزء 2

فييناNET.Sagas

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

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

لا يزال تنفيذنا يتم في شكله الأساسي ولا يرتبط باستخدام أي طرق للتفاعل مع الخدمات الأخرى. ليس من الصعب استخدامه: فقط قم بإنشاء سليل للفئة المجردة الأساسية SagaBase<T>، حيث T هي فئة السياق الخاصة بك والتي يمكنك من خلالها تخزين البيانات الأولية اللازمة لعمل الملحمة، بالإضافة إلى بعض النتائج المتوسطة. سيتم تمرير مثيل السياق إلى كافة الخطوات أثناء التنفيذ. Saga بحد ذاتها عبارة عن فئة عديمة الحالة، لذا يمكن وضع المثيل في DI باعتباره Singleton للحصول على التبعيات الضرورية.

مثال للإعلان:

public class ExampleSaga : SagaBase<ExampleContext>
{
  public ExampleSaga()
  {
    Step("Step 1")
      .WithAction(c => ...)
      .WithCompensation(c => ...);
	
    AsyncStep("Step 2")
      .WithAction(async c => ...);
  }
}

مثال على المكالمة:

var saga = new ExampleSaga();
var context = new ExampleContext();
await saga.Execute(context);

يمكن الاطلاع على أمثلة كاملة لتطبيقات مختلفة هنا وفي التجمع مع الاختبارات.

فييناNET.Orm.*

مجموعة من المكتبات للعمل مع قواعد البيانات المختلفة عبر Nhibernate. نحن نستخدم نهج DB-First باستخدام Liquibase، لذلك لا توجد سوى وظيفة للعمل مع البيانات في قاعدة بيانات جاهزة.

ViennaNET.Orm.Seedwork и ViennaNET.Orm - التجميعات الرئيسية التي تحتوي على الواجهات الأساسية وتطبيقاتها، على التوالي. دعونا ننظر إلى محتوياتها بمزيد من التفصيل.

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

للوصول مباشرة إلى قاعدة البيانات، يتم استخدام آلية الموفر. كل نظام إدارة قواعد بيانات (DBMS) نستخدمه في فرقنا له تطبيقه الخاص: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

وفي الوقت نفسه، يمكن تسجيل العديد من مقدمي الخدمة في تطبيق واحد في نفس الوقت، مما يسمح، على سبيل المثال، في إطار خدمة واحدة، دون أي تكاليف لتعديل البنية التحتية، بإجراء ترحيل خطوة بخطوة من نظام إدارة قواعد البيانات (DBMS) إلى آخر. يتم تنفيذ آلية اختيار الاتصال المطلوب وبالتالي الموفر لفئة كيان معينة (التي يتم كتابة التعيين لجداول قاعدة البيانات لها) من خلال تسجيل الكيان في فئة BoundedContext (تحتوي على طريقة لتسجيل كيانات المجال) أو خلفها ApplicationContext (يحتوي على طرق لتسجيل كيانات التطبيق والطلبات والأوامر المباشرة)، حيث يتم قبول معرف الاتصال من التكوين كوسيطة:

"db": [
  {
    "nick": "mssql_connection",
    "dbServerType": "MSSQL",
    "ConnectionString": "...",
    "useCallContext": true
  },
  {
    "nick": "oracle_connection",
    "dbServerType": "Oracle",
    "ConnectionString": "..."
  }
],

مثال لسياق التطبيق:

internal sealed class DbContext : ApplicationContext
{
  public DbContext()
  {
    AddEntity<SomeEntity>("mssql_connection");
    AddEntity<MigratedSomeEntity>("oracle_connection");
    AddEntity<AnotherEntity>("oracle_connection");
  }
}

إذا لم يتم تحديد معرف الاتصال، فسيتم استخدام الاتصال المسمى "الافتراضي".

يتم تنفيذ التعيين المباشر للكيانات إلى جداول قاعدة البيانات باستخدام أدوات NHibernate القياسية. يمكنك استخدام الوصف من خلال ملفات XML ومن خلال الفئات. توجد مكتبة لسهولة كتابة مستودعات كعب الروتين في اختبارات الوحدة ViennaNET.TestUtils.Orm.

يمكن العثور على أمثلة كاملة لاستخدام ViennaNET.Orm.* هنا.

فيينا نت.المراسلة.*

مجموعة من المكتبات للعمل مع قوائم الانتظار.

للعمل مع قوائم الانتظار، تم اختيار نفس النهج كما هو الحال مع أنظمة إدارة قواعد البيانات المختلفة، أي أقصى نهج موحد ممكن من حيث العمل مع المكتبة، بغض النظر عن مدير قائمة الانتظار المستخدم. مكتبة ViennaNET.Messaging هو المسؤول بالتحديد عن هذا التوحيد، و ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue تحتوي على تطبيقات محول لـ IBM MQ، وRabitMQ، وKafka، على التوالي.

عند العمل مع قوائم الانتظار، هناك عمليتان: تلقي رسالة وإرسالها.

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

"messaging": {
    "ApplicationName": "MyApplication"
},
"rabbitmq": {
    "queues": [
      {
        "id": "myQueue",
        "queuename": "lalala",
        ...
      }
    ]
},

مثال لبدء الاستماع:

_queueReactorFactory.Register<MyMessageProcessor>("myQueue");
var queueReactor = queueReactorFactory.CreateQueueReactor("myQueue");
queueReactor.StartProcessing();

وبعد ذلك، عند بدء تشغيل الخدمة واستدعاء الطريقة لبدء الاستماع، ستنتقل جميع الرسائل من قائمة الانتظار المحددة إلى المعالج المقابل.

لتلقي رسالة واحدة في واجهة المصنع IMessagingComponentFactory هناك طريقة CreateMessageReceiverمما سيؤدي إلى إنشاء مستلم ينتظر رسالة من قائمة الانتظار المحددة له:

using (var receiver = _messagingComponentFactory.CreateMessageReceiver<TestMessage>("myQueue"))
{
    var message = receiver.Receive();
}

لإرسال رسالة تحتاج إلى استخدام نفسه IMessagingComponentFactory وإنشاء مرسل رسالة:

using (var sender = _messagingComponentFactory.CreateMessageSender<MyMessage>("myQueue"))
{
    sender.SendMessage(new MyMessage { Value = ...});
}

هناك ثلاثة خيارات جاهزة لإجراء تسلسل وإلغاء تسلسل الرسالة: النص فقط وXML وJSON، ولكن إذا لزم الأمر، يمكنك بسهولة إجراء تطبيقات الواجهة الخاصة بك IMessageSerializer и IMessageDeserializer.

لقد حاولنا الحفاظ على القدرات الفريدة لكل مدير قائمة انتظار، على سبيل المثال. ViennaNET.Messaging.MQSeriesQueue يسمح لك بإرسال ليس فقط الرسائل النصية، ولكن أيضًا رسائل البايت، و ViennaNET.Messaging.RabbitMQQueue يدعم التوجيه وقائمة الانتظار على الطاير. يقوم غلاف المحول الخاص بنا لـ RabbitMQ أيضًا بتنفيذ بعض مظاهر RPC: نرسل رسالة وننتظر استجابة من قائمة انتظار مؤقتة خاصة، والتي يتم إنشاؤها لرسالة استجابة واحدة فقط.

ها هو مثال على استخدام قوائم الانتظار مع الفروق الدقيقة في الاتصال الأساسية.

فييناNET.CallContext

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

شكرا لاهتمامكم، ونحن نتطلع إلى تعليقاتكم وطلباتكم!

المصدر: www.habr.com

إضافة تعليق