إنشاء تطبيقات الأعمال المستندة إلى الرسائل باستخدام NServiceBus وناقل خدمة Azure

إن NServiceBus هو إطار عمل للمراسلة التجارية توفره Particular Software. وهو مبني على ناقل خدمة Azure ويساعد المطورين على التركيز على منطق تسلسل العمل من خلال تجريد مشكلات البنية الأساسية. في هذا الدليل، سنبني حلاً لتبادل الرسائل بين خدمتين. كما سنعرض كيفية إعادة محاولة الرسائل الفاشلة تلقائياً ومراجعة خيارات استضافة هذه الخدمات في Azure.

إشعار

يتوفر رمز هذا البرنامج التعليمي على موقع ويب Particular Software Docs.

المتطلبات الأساسية

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

هام

يتطلب NServiceBus المستوى القياسي على الأقل. لن يعمل المستوى الأساسي.

تنزيل الحل وإعداده

  1. تنزيل التعليمة البرمجية من موقع ويب Particular Software Docs. يتكون الحل SendReceiveWithNservicebus.sln من ثلاثة مشاريع:

    • المرسل: تطبيق وحدة تحكم يرسل الرسائل
    • المتلقي: تطبيق وحدة تحكم يتلقى الرسائل من المرسل ويجيب مرة أخرى
    • المشتركة: مكتبة فئات تحتوي على عقود الرسالة المشتركة بين المرسل والمتلقي

    يُظهر الرسم التخطيطي التالي، الذي تم إنشاؤه بواسطة ServiceInsight، وهو أداة تصور وتصحيح من Particular Software، تدفق الرسالة:

    صورة تعرض الرسم التخطيطي للتسلسل

  2. افتح SendReceiveWithNservicebus.sln في محرر التعليمات البرمجية المفضل لديك (على سبيل المثال، Visual Studio 2022).

  3. افتح appsettings.json في كل من مشروعي المتلقي والمرسل وقم بتعيين AzureServiceBusConnectionString على سلسلة الاتصال لمساحة اسم ناقل خدمة Azure.

    • يمكن العثور على هذا في مدخل Microsoft Azure ضمن Service Bus Namespace>Settings>Shared access policies>RootManageSharedAccessKey>Primary Connection String .
    • AzureServiceBusTransport يحتوي أيضا على منشئ يقبل مساحة الاسم وبيانات اعتماد الرمز المميز، والتي في بيئة الإنتاج ستكون أكثر أمانا، ولكن لأغراض هذا البرنامج التعليمي سيتم استخدام مفتاح الوصول المشترك سلسلة الاتصال.

تحديد عقود الرسائل المشتركة

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

أولاً سنراجع الفئة Ping.cs

public class Ping : NServiceBus.ICommand
{
    public int Round { get; set; }
}

تحدد الفئة Ping الرسالة التي يرسلها المرسل إلى المتلقي. إنها فئة C# بسيطة تطبق NServiceBus.ICommand، واجهة من حزمة NServiceBus. هذه الرسالة هي إشارة للقارئ وNServiceBus بأنها أمر، على الرغم من وجود طرق أخرى لتحديد الرسائل دون استخدام الواجهات.

فئة الرسالة الأخرى في المشاريع المشتركة هي Pong.cs:

public class Pong : NServiceBus.IMessage
{
    public string Acknowledgement { get; set; }
}

Pong هو أيضاً عنصر C# بسيط على الرغم من أنه ينفذ NServiceBus.IMessage. تمثل واجهة IMessage رسالة عامة ليست أمراً ولا حدثاً، وتُستخدم عادة للردود. في نموذجنا، هو رد يرسله المتلقي مرة أخرى إلى المرسل للإشارة إلى تلقي رسالة.

Ping وPong هما نوعا الرسائل اللذين ستستخدمهما. الخطوة التالية هي تكوين المرسل لاستخدام ناقل خدمة Azure وإرسال رسالة Ping.

إعداد المرسل

المرسل هو نقطة نهاية ترسل رسالتنا Ping. هنا، يمكنك تكوين المرسل لاستخدام ناقل خدمة Azure كآلية النقل، ثم إنشاء مثيل Ping وإرساله.

في الأسلوب Main لـ Program.cs، يمكنك تكوين نقطة النهاية للمرسل:

var host = Host.CreateDefaultBuilder(args)
    // Configure a host for the endpoint
    .ConfigureLogging((context, logging) =>
    {
        logging.AddConfiguration(context.Configuration.GetSection("Logging"));

        logging.AddConsole();
    })
    .UseConsoleLifetime()
    .UseNServiceBus(context =>
    {
        // Configure the NServiceBus endpoint
        var endpointConfiguration = new EndpointConfiguration("Sender");

        var connectionString = context.Configuration.GetConnectionString("AzureServiceBusConnectionString");
        // If token credentials are to be used, the overload constructor for AzureServiceBusTransport would be used here
        var routing = endpointConfiguration.UseTransport(new AzureServiceBusTransport(connectionString));
        endpointConfiguration.UseSerialization<SystemJsonSerializer>();

        endpointConfiguration.AuditProcessedMessagesTo("audit");
        routing.RouteToEndpoint(typeof(Ping), "Receiver");

        endpointConfiguration.EnableInstallers();

        return endpointConfiguration;
    })
    .ConfigureServices(services => services.AddHostedService<SenderWorker>())
    .Build();

await host.RunAsync();

هناك الكثير لفكه هنا، لذا سنراجعه خطوة بخطوة.

تكوين مضيف لنقطة النهاية

يتم تكوين الاستضافة والتسجيل باستخدام خيارات مضيف Microsoft العام القياسية. في الوقت الحالي، يتم تكوين نقطة النهاية لتشغيلها بصورة تطبيق وحدة تحكم ولكن يمكن تعديلها ليتم تشغيلها في Azure Functions مع تغييرات طفيفة، والتي سنناقشها لاحقاً في هذه المقالة.

تكوين نقطة نهاية NServiceBus

بعد ذلك، يمكنك أن تطلب من المضيف استخدام NServiceBus مع أسلوب الامتداد .UseNServiceBus(…). يأخذ الأسلوب دالة رد اتصال التي ترجع نقطة النهاية التي سيتم تشغيلها عند تشغيل المضيف.

في تكوين نقطة النهاية، يمكنك تحديد AzureServiceBus للنقل، وتوفير سلسلة اتصال من appsettings.json. بعد ذلك، ستقوم بإعداد التوجيه بحيث يتم إرسال رسائل من النوع Ping إلى نقطة نهاية تسمى "Receiver". يسمح لـ NServiceBus بأتمتة عملية إرسال الرسالة إلى الوجهة دون طلب عنوان المتلقي.

سيقوم الاستدعاء EnableInstallers بإعداد طوبولوجيا في مساحة اسم ناقل خدمة Azure عند تشغيل نقطة النهاية، وإنشاء قوائم الانتظار المطلوبة عند الضرورة. في بيئات الإنتاج، تعد البرمجة النصية التشغيلية خياراً آخر لإنشاء طوبولوجيا.

إعداد خدمة الخلفية لإرسال الرسائل

الجزء الأخير من المرسل هو SenderWorker، خدمة خلفية تم تكوينها لإرسال رسالة Ping كل ثانية.

public class SenderWorker : BackgroundService
{
    private readonly IMessageSession messageSession;
    private readonly ILogger<SenderWorker> logger;

    public SenderWorker(IMessageSession messageSession, ILogger<SenderWorker> logger)
    {
        this.messageSession = messageSession;
        this.logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            var round = 0;
            while (!stoppingToken.IsCancellationRequested)
            {
                await messageSession.Send(new Ping { Round = round++ });;

                logger.LogInformation($"Message #{round}");

                await Task.Delay(1_000, stoppingToken);
            }
        }
        catch (OperationCanceledException)
        {
            // graceful shutdown
        }
    }
}

يتم إدخال IMessageSession المستخدم في ExecuteAsync إلى SenderWorker ويسمح لنا بإرسال الرسائل باستخدام NServiceBus خارج معالج الرسائل. يحدد التوجيه الذي قمت بتكوينه في Sender وجهة الرسائل Ping. يحتفظ بطوبولوجيا النظام (أي الرسائل يتم توجيهها إلى أي عناوين) باعتبارها مسألة منفصلة عن كود العمل.

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

إعداد المتلقي

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

مثل المرسل، قم بإعداد المتلقي كتطبيق وحدة تحكم باستخدام مضيف Microsoft العام. يستخدم نفس تكوين التسجيل ونقطة النهاية (مع ناقل خدمة Azure لنقل الرسالة) ولكن باسم مختلف، لتمييزه عن المرسل:

var endpointConfiguration = new EndpointConfiguration("Receiver");

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

معالج الرسائل Ping

يحتوي مشروع المتلقي على معالج رسائل باسم PingHandler:

public class PingHandler : NServiceBus.IHandleMessages<Ping>
{
    private readonly ILogger<PingHandler> logger;

    public PingHandler(ILogger<PingHandler> logger)
    {
        this.logger = logger;
    }

    public async Task Handle(Ping message, IMessageHandlerContext context)
    {
        logger.LogInformation($"Processing Ping message #{message.Round}");

        // throw new Exception("BOOM");

        var reply = new Pong { Acknowledgement = $"Ping #{message.Round} processed at {DateTimeOffset.UtcNow:s}" };

        await context.Reply(reply);
    }
}

دعنا نتجاهل التعليمة البرمجية ذات التعليق في الوقت الحالي؛ سنعود إليها لاحقاً عندما نتحدث عن التعافي من الفشل.

تطبق الفئة IHandleMessages<Ping>، والتي تحدد أسلوباً واحداً: Handle. تخبر هذه الواجهة NServiceBus أنه عندما تتلقى نقطة النهاية رسالة من نوع Ping، يجب معالجتها بواسطة الأسلوب Handle في هذا المعالج. يأخذ الأسلوب Handle الرسالة نفسها كمعلمة، وIMessageHandlerContext، ما يسمح بمزيد من عمليات المراسلة، مثل الرد، أو إرسال الأوامر، أو نشر الأحداث.

نحن PingHandler واضحون: عند Ping تلقي رسالة، قم بتسجيل تفاصيل الرسالة والرد مرة أخرى على المرسل برسالة جديدة Pong ، والتي تتم معالجتها لاحقا في المرسل PongHandler.

إشعار

في تكوين المرسل، حددت أنه يجب توجيه الرسائل Ping إلى المتلقي. يضيف NServiceBus بيانات التعريف إلى الرسائل التي تشير، من بين أمور أخرى، إلى أصل الرسالة. هذا هو السبب في أنك لست بحاجة إلى تحديد أي بيانات توجيه لرسالة الرد Pong؛ يتم توجيهها تلقائياً إلى مصدرها: المرسل.

مع تكوين كل من المرسل والمتلقي بشكل صحيح، يمكنك الآن تشغيل الحل.

قم بتشغيل الحل

لتشغيل الحل، تحتاج إلى تشغيل كل من المرسل والمتلقي. إذا كنت تستخدم Visual Studio Code، قم بتشغيل تكوين "Debug All". إذا كنت تستخدم Visual Studio، فقم بتكوين الحل لبدء تشغيل مشروعي المرسل والمتلقي:

  1. انقر بزر الماوس الأيمن فوق الحل في مستكشف الحلول
  2. حدد "Set Startup Projects"
  3. حدد "Multiple startup projects"
  4. لكل من المرسل والمتلقي، حدد "Start" في القائمة المنسدلة

تشغيل الحل. سيظهر تطبيقان لوحدة التحكم، أحدهما للمرسل والآخر للمتلقي.

في المرسل، لاحظ أنه يتم إرسال رسالة Ping كل ثانية، وذلك بفضل مهمة الخلفية SenderWorker. يعرض المتلقي تفاصيل كل رسالة Ping يتلقاها ويسجل المرسل تفاصيل كل رسالة Pong يتلقاها في الرد.

الآن بعد أن أصبح كل شيء يعمل، دعونا نقاطعه.

المرونة في العمل

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

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

  1. افتح Program.cs في مشروع المرسل
  2. بعد السطر .EnableInstallers، أضف التعليمة البرمجية التالية:
endpointConfiguration.SendFailedMessagesTo("error");
var recoverability = endpointConfiguration.Recoverability();
recoverability.Immediate(
    immediate =>
    {
        immediate.NumberOfRetries(3);
    });
recoverability.Delayed(
    delayed =>
    {
        delayed.NumberOfRetries(2);
        delayed.TimeIncrease(TimeSpan.FromSeconds(5));
    });

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

throw new Exception("BOOM");

الآن، عندما يعالج المتلقي رسالة Ping، فإنه سيفشل. قم بتشغيل الحل مرة أخرى، ولنر ما سيحدث في المتلقي.

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

صورة تعرض سياسة إعادة المحاولة الفورية التي تعيد محاولة إرسال الرسائل حتى 3 مرات

وبطبيعة الحال، فإنها ستستمر في الفشل حتى عند استخدام إعادة المحاولة الفورية الثلاث، تبدأ سياسة إعادة المحاولة المتأخرة وتتأخر الرسالة لمدة 5 ثوانٍ:

صورة تعرض سياسة إعادة المحاولة المتأخرة التي تؤخر الرسائل بزيادات قدرها 5 ثوان قبل محاولة إجراء جولة أخرى من عمليات إعادة المحاولة الفورية

بعد مرور هذه الثواني الخمس، تتم إعادة محاولة الرسالة مرة أخرى ثلاث مرات (أي تكرار آخر لسياسة إعادة المحاولة الفورية). ستفشل هذه أيضاً وسيؤخر NServiceBus الرسالة مرة أخرى، هذه المرة لمدة 10 ثوانٍ، قبل المحاولة مرة أخرى.

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

صورة توضح الرسالة الفاشلة

يختلف مفهوم قائمة انتظار الخطأ المركزية عن آلية الرسائل الخامدة في ناقل خدمة Azure، الذي يحتوي على قائمة انتظار الرسائل الخامدة لكل قائمة انتظار معالجة. باستخدام NServiceBus، تعمل قوائم انتظار الرسائل الخامدة في ناقل خدمة Azure كقوائم انتظار رسالة غير قابلة للمعالجة حقيقية، بينما يمكن إعادة معالجة الرسائل التي تنتهي في قائمة انتظار الخطأ المركزية في وقت لاحق، إذا لزم الأمر.

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

بمجرد أن تكون الرسالة في قائمة انتظار الخطأ، يمكنك فحص تفاصيل الرسالة في الأداة التي تختارها، ثم تحديد ما يجب فعله بها. على سبيل المثال، باستخدام ServicePulse، أداة مراقبة بواسطة Particular Software، يمكننا عرض تفاصيل الرسالة وسبب الفشل:

صورة تظهر ServicePulse، من Particular Software

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

بعد ذلك، حان الوقت لمعرفة مكان نشر حلنا في Azure.

مكان استضافة الخدمات في Azure

في هذا النموذج، تم تكوين نقاط نهاية المرسل والمستقبل لتعمل كتطبيقات وحدة التحكم. كما يمكن استضافتها في العديد من خدمات Azure بما في ذلك Azure Functions، وAzure App Services، وAzure Container Instances، وAzure Kubernetes Services، وAzure VMs. على سبيل المثال، إليك كيفية تكوين نقطة نهاية المرسل لتعمل كدالة Azure:

[assembly: NServiceBusTriggerFunction("Sender")]
public class Program
{
    public static async Task Main()
    {
        var host = new HostBuilder()
            .ConfigureFunctionsWorkerDefaults()
            .UseNServiceBus(configuration =>
            {
                configuration.Routing().RouteToEndpoint(typeof(Ping), "Receiver");
            })
            .Build();

        await host.RunAsync();
    }
}

لمزيد من المعلومات حول استخدام NServiceBus مع الدوال، راجع Azure Functions مع ناقل خدمة Azure في وثائق NServiceBus.

الخطوات التالية

لمزيد من المعلومات حول استخدام NServiceBus مع خدمات Azure، راجع المقالات التالية: