.NET Aspire: بناء التطبيقات السحابية الأصلية بالطريقة الصحيحة

· 10 دقيقة قراءة

إذا سبق لك إنشاء تطبيق موزع في .NET، فأنت تعرف التدريبات. يمكنك تشغيل واجهة برمجة تطبيقات الويب، وإضافة عامل في الخلفية، واستخدام Redis للتخزين المؤقت، وPostgreSQL للاستمرارية، وربما RabbitMQ للمراسلة - وفجأة تقضي وقتًا أطول في توصيل البنية التحتية بدلاً من كتابة منطق الأعمال. سلاسل الاتصال متناثرة عبر ملفات appsettings.json، والفحوصات الصحية التي نسيت تهيئتها، وإمكانية المراقبة التي تعد دائمًا “مشكلة السباق التالي.”

لقد كنت هناك. مرات أكثر مما أود أن أعترف به.

هذه هي بالضبط المشكلة التي تم تصميم .NET Aspire لحلها. بعد تشغيله في مرحلة الإنتاج لعدة أشهر، أريد أن أشارككم ما تعلمته - الأشياء الجيدة والعظيمة والصعبة.

ما هو .NET Aspire؟

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

في جوهرها، أسباير يمنحك أربعة أشياء:

  1. AppHost — مشروع يحدد هيكل التطبيق الموزع بالكامل. ما هي الخدمات الموجودة، وما الذي تعتمد عليه، وكيفية اتصالها.
  2. افتراضيات الخدمة — مشروع مشترك يقوم بتكوين الاهتمامات الشاملة مثل فحوصات السلامة وسياسات المرونة وOpenTelemetry — مرة واحدة لجميع خدماتك.
  3. المكونات — حزم NuGet التي توفر عمليات تكامل موحدة مع خدمات الدعم مثل Redis وPostgreSQL وRabbitMQ وAzure Storage والمزيد.
  4. لوحة تحكم المطور — واجهة مستخدم في الوقت الفعلي تعرض السجلات والتتبعات والمقاييس لتطبيقك الموزع بالكامل أثناء التطوير المحلي.

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

إعداد مشروعك الأول أسباير

البدء واضح ومباشر. ستحتاج إلى .NET 8 أو إصدار أحدث وتثبيت حمل عمل Aspire:

dotnet workload update
dotnet workload install aspire

الآن أنشئ مشروعًا جديدًا لـ Aspire:

dotnet new aspire-starter -n MyCloudApp

يؤدي هذا إلى إنشاء حل بأربعة مشاريع:

  • MyCloudApp.AppHost — المنسق
  • MyCloudApp.ServiceDefaults — التكوين المشترك
  • MyCloudApp.ApiService — نموذج لواجهة برمجة تطبيقات الويب
  • MyCloudApp.Web — واجهة Blazor الأمامية

قم بتشغيل AppHost وسترى على الفور لوحة تحكم Aspire مفتوحة في متصفحك، وتعرض جميع خدماتك وسجلاتها وحالتها الصحية. لا يوجد ملف Docker Compose. لا توجد إدارة يدوية للمنافذ. إنه يعمل فقط.

dotnet run --project MyCloudApp.AppHost

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

نمط AppHost

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

إليك ما يبدو عليه AppHost الواقعي:

var builder = DistributedApplication.CreateBuilder(args);

// Infrastructure resources
var postgres = builder.AddPostgres("postgres")
    .WithPgAdmin()
    .AddDatabase("catalogdb");

var redis = builder.AddRedis("cache");

var rabbitmq = builder.AddRabbitMQ("messaging")
    .WithManagementPlugin();

// Application services
var catalogApi = builder.AddProject<Projects.CatalogApi>("catalog-api")
    .WithReference(postgres)
    .WithReference(redis)
    .WithReference(rabbitmq);

var orderApi = builder.AddProject<Projects.OrderApi>("order-api")
    .WithReference(postgres)
    .WithReference(rabbitmq);

var frontend = builder.AddProject<Projects.WebFrontend>("frontend")
    .WithExternalHttpEndpoints()
    .WithReference(catalogApi)
    .WithReference(orderApi);

builder.Build().Run();
```اقرأ هذا الرمز بصوت عالٍ. إنه يوثق نفسه عمليا. "تشير واجهة برمجة تطبيقات الكتالوج إلى PostgreSQL وRedis وRabbitMQ. وتشير الواجهة الأمامية إلى واجهة برمجة تطبيقات الكتالوج وواجهة برمجة تطبيقات الطلب." هذه هي الهندسة المعمارية الخاصة بك، معبرًا عنها بالكود.

بعض الأشياء الجديرة بالملاحظة:

- **`WithReference`** يقوم برفع الأحمال الثقيلة. يقوم تلقائيًا بإدخال سلاسل الاتصال وعناوين URL الخاصة بالخدمة في المشروع المستهلك عبر متغيرات البيئة والتكوين. لا تحتاج خدماتك إلى معرفة *مكان* تشغيل Redis  حيث تتولى Aspire ذلك.
- **`WithPgAdmin()`** و **`WithManagementPlugin()`** يقومان بتدوير واجهات المستخدم الإدارية لـ PostgreSQL وRabbitMQ إلى جانب الخدمات الفعلية. أثناء التطوير، هذه لا تقدر بثمن.
- **`WithExternalHttpEndpoints()`** يضع علامة على الخدمة على أنها يمكن الوصول إليها خارجيًا، وهو أمر مهم في وقت النشر.

### تكوين الموارد

يمكنك تكوين الموارد من خلال التحكم الدقيق:

```csharp
var postgres = builder.AddPostgres("postgres")
    .WithEnvironment("POSTGRES_MAX_CONNECTIONS", "200")
    .WithDataVolume("postgres-data")
    .AddDatabase("catalogdb");

var redis = builder.AddRedis("cache")
    .WithDataVolume("redis-data");

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

افتراضيات الخدمة: البطل المجهول

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

إليك ما يبدو عليه Extensions.cs النموذجي في ServiceDefaults:

public static class Extensions
{
    public static IHostApplicationBuilder AddServiceDefaults(
        this IHostApplicationBuilder builder)
    {
        builder.ConfigureOpenTelemetry();
        builder.AddDefaultHealthChecks();
        builder.Services.AddServiceDiscovery();

        builder.Services.ConfigureHttpClientDefaults(http =>
        {
            http.AddStandardResilienceHandler();
            http.AddServiceDiscovery();
        });

        return builder;
    }

    public static IHostApplicationBuilder ConfigureOpenTelemetry(
        this IHostApplicationBuilder builder)
    {
        builder.Logging.AddOpenTelemetry(logging =>
        {
            logging.IncludeFormattedMessage = true;
            logging.IncludeScopes = true;
        });

        builder.Services.AddOpenTelemetry()
            .WithMetrics(metrics =>
            {
                metrics.AddAspNetCoreInstrumentation()
                    .AddHttpClientInstrumentation()
                    .AddRuntimeInstrumentation();
            })
            .WithTracing(tracing =>
            {
                tracing.AddAspNetCoreInstrumentation()
                    .AddHttpClientInstrumentation()
                    .AddGrpcClientInstrumentation();
            });

        builder.AddOpenTelemetryExporters();
        return builder;
    }

    public static IHostApplicationBuilder AddDefaultHealthChecks(
        this IHostApplicationBuilder builder)
    {
        builder.Services.AddHealthChecks()
            .AddCheck("self", () => HealthCheckResult.Healthy(),
                ["live"]);

        return builder;
    }
}

ثم في Program.cs لكل خدمة، سطر واحد يفعل كل شيء:

var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();

// Your service-specific configuration...

var app = builder.Build();
app.MapDefaultEndpoints();
app.Run();

تمنحك هذه المكالمة الفردية AddServiceDefaults() ما يلي:

  • OpenTelemetry مع التسجيل المنظم والمقاييس والتتبع الموزع
  • فحوصات السلامة مع نقاط النهاية الحيوية والاستعداد
  • اكتشاف الخدمة حتى تتمكن الخدمات من العثور على بعضها البعض بالاسم
  • سياسات المرونة في جميع مكالمات HTTP الصادرة (إعادة المحاولة، وقواطع الدائرة، والمهلات)

هذه هي الأشياء التي تفصل بين العرض التوضيحي “يعمل على جهازي” وبين النظام الجاهز للإنتاج. وأسباير تجعلها الخيار الافتراضي، وليس فكرة لاحقة.

مكونات أسباير

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

ريديس

var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.AddRedisDistributedCache("cache");

هذا كل شيء. تأتي سلسلة الاتصال من AppHost عبر WithReference. يسجل المكون IDistributedCache مدعومًا بواسطة Redis، مع فحوصات السلامة وأجهزة OpenTelemetry التي تم توصيلها بالفعل.

يمكنك أيضًا استخدام Redis للتخزين المؤقت للمخرجات:

builder.AddRedisOutputCache("cache");

PostgreSQL مع Entity Framework Core

builder.AddNpgsqlDbContext<CatalogDbContext>("catalogdb", settings =>
{
    settings.DisableRetry = false;
});

يؤدي هذا إلى تسجيل DbContext الخاص بك من خلال اتصال بقاعدة بيانات catalogdb المحددة في AppHost. ويتضمن تجميع الاتصالات والفحوصات الصحية وسياسات إعادة المحاولة.

رابيتMQ

builder.AddRabbitMQClient("messaging");

تسجيل IConnection من مكتبة عملاء RabbitMQ، تم تكوينها بالكامل وفحص صحتها.

تكاملات أزور

تمتلك Aspire أيضًا مكونات من الدرجة الأولى لخدمات Azure:

// Azure Blob Storage
builder.AddAzureBlobClient("blobs");

// Azure Service Bus
builder.AddAzureServiceBusClient("servicebus");

// Azure Key Vault
builder.AddAzureKeyVaultClient("secrets");
```النمط دائمًا هو نفسه: سطر واحد في AppHost لتحديد المورد، وسطر واحد في الخدمة المستهلكة لاستخدامه. تتدفق تفاصيل الاتصال تلقائيًا.

## لوحة تحكم المطور

تعد لوحة معلومات Aspire واحدة من تلك الميزات التي تبدو وكأنها من الأشياء الرائعة حتى تستخدمها فعليًا - فلا يمكنك تخيل العمل بدونها.

عند تشغيل AppHost محليًا، يتم تشغيل لوحة المعلومات وتمنحك:

- **نظرة عامة على الموارد**  نظرة سريعة على جميع خدماتك وبنيتك التحتية، مع مؤشرات الحالة
- **السجلات المنظمة**  سجل يتدفق في الوقت الفعلي من كل خدمة، قابل للتصفية والبحث
- **الآثار الموزعة**  آثار الطلب الشاملة التي تغطي خدمات متعددة، والتي يتم تصورها على شكل مخططات توضيحية
- **المقاييس**  معدلات طلبات HTTP، ومعدلات الخطأ، وزمن الاستجابة، والمقاييس المخصصة في الوقت الفعلي
- **سجلات وحدة التحكم**  stdout/stderr الأولي من كل حاوية ومشروع

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

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

لوحة المعلومات متاحة أيضًا كحاوية مستقلة، مما يعني أنه يمكنك استخدامها في البيئات المرحلية أو خطوط أنابيب CI:

```bash
docker run --rm -p 18888:18888 \
  mcr.microsoft.com/dotnet/aspire-dashboard:latest

النشر

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

تطبيقات حاوية Azure

مسار النشر الأكثر وضوحًا هو Azure Container Apps (ACA)، الذي يتمتع بدعم Aspire من الدرجة الأولى. يمكنك النشر مباشرة باستخدام Azure Developer CLI:

azd init
azd up

يكتشف الأمر azd init Aspire AppHost الخاص بك ويقوم بإنشاء البنية التحتية الضرورية كرمز. azd up يوفر كل شيء — سجل الحاوية، وتطبيقات الحاوية، وقواعد البيانات، ومثيلات Redis — استنادًا إلى طوبولوجيا AppHost لديك.

يصبح AppHost الخاص بك بشكل أساسي بيان النشر الخاص بك. نفس الكود الذي يحدد “catalog-api يعتمد على PostgreSQL وRedis” هو الذي يحرك عملية توفير البنية التحتية.

كوبيرنيت

بالنسبة لعمليات نشر Kubernetes، لا تقوم Aspire بإنشاء البيانات مباشرة، ولكن طوبولوجيا AppHost الخاصة بك تتوافق بشكل واضح مع موارد Kubernetes. يمكن لأداة المجتمع aspirate (Aspir8) إنشاء مخططات Helm أو بيانات Kubernetes من AppHost الخاص بك:

dotnet tool install -g aspirate
aspirate generate
aspirate apply

اعتبارات النشر

بعض الأشياء التي يجب وضعها في الاعتبار:- تتغير سلاسل الاتصال بين البيئات. محليًا، يقوم Aspire بتدوير الحاويات وإدخال سلاسل الاتصال تلقائيًا. في الإنتاج، ستشير إلى الخدمات المُدارة. يستخدم Aspire تكوين .NET القياسي، لذا تعمل متغيرات البيئة وAzure Key Vault كما هو متوقع.

  • لا يعمل AppHost في مرحلة الإنتاج. إنه أداة تنسيق للتطوير والنشر. في مرحلة الإنتاج، تعمل خدماتك بشكل مستقل، ويتم تكوينها من خلال متغيرات البيئة والمنسقين.
  • تتحول موارد البنية التحتية إلى خدمات مُدارة. تصبح حاوية PostgreSQL المحلية الخاصة بك هي قاعدة بيانات Azure لـ PostgreSQL. تصبح حاوية Redis المحلية لديك عبارة عن Azure Cache لـ Redis. كود الاستهلاك لا يتغير

نصائح من العالم الحقيقي

بعد تشغيل Aspire في الإنتاج لفترة من الوقت، إليكم الدروس التي وفرت لنا الوقت:

1. استخدم عمليات فحص دورة حياة الموارد المخصصة

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

var postgres = builder.AddPostgres("postgres")
    .AddDatabase("catalogdb")
    .WithHealthCheck();

يمكن لـ Aspire إجراء فحوصات صحية على الموارد والاحتفاظ بالخدمات التابعة حتى تصبح جاهزة بالفعل.

2. استخراج الأنماط الشائعة إلى الامتدادات

إذا كانت هناك خدمات متعددة تشترك في تكوينات مماثلة، فقم بإنشاء طرق ملحقة:

public static class AppHostExtensions
{
    public static IResourceBuilder<ProjectResource> AddWorkerService(
        this IDistributedApplicationBuilder builder,
        string name,
        IResourceBuilder<IResourceWithConnectionString> db,
        IResourceBuilder<IResourceWithConnectionString> messaging)
    {
        return builder.AddProject<Projects.WorkerService>(name)
            .WithReference(db)
            .WithReference(messaging)
            .WithReplicas(3);
    }
}

3. الاستفادة من النسخ المتماثلة لاختبار التحميل

var catalogApi = builder.AddProject<Projects.CatalogApi>("catalog-api")
    .WithReference(postgres)
    .WithReference(redis)
    .WithReplicas(5);

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

4. استخدم المعلمات للقيم الحساسة

لا تقم بتشفير بيانات الاعتماد، حتى بالنسبة للتنمية المحلية:

var dbPassword = builder.AddParameter("db-password", secret: true);

var postgres = builder.AddPostgres("postgres", password: dbPassword)
    .AddDatabase("catalogdb");

عند التشغيل محليًا، سيطالبك Aspire بالقيمة أو يقرأها من أسرار المستخدم. في CI/CD، يأتي من متغيرات البيئة.

5. اختبارات التكامل تصبح تافهة

تتضمن Aspire حزمة اختبار تجعل اختبار التكامل للتطبيقات الموزعة أمرًا سهلاً بشكل ملحوظ:

[Fact]
public async Task CatalogApiReturnsProducts()
{
    var appHost = await DistributedApplicationTestingBuilder
        .CreateAsync<Projects.MyCloudApp_AppHost>();

    await using var app = await appHost.BuildAsync();
    await app.StartAsync();

    var httpClient = app.CreateHttpClient("catalog-api");
    var response = await httpClient.GetAsync("/api/products");

    response.EnsureSuccessStatusCode();

    var products = await response.Content
        .ReadFromJsonAsync<List<Product>>();
    Assert.NotEmpty(products);
}

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

6. مراقبة استخدام الموارد محليًا

عند تشغيل حاويات متعددة محليًا، راقب استهلاك الموارد. يمكن لمثيل PostgreSQL وRedis وRabbitMQ المزود بواجهة مستخدم الإدارة أن يستهلك بسهولة 2-3 جيجابايت من ذاكرة الوصول العشوائي. إذا كنت تستخدم جهازًا مقيدًا، ففكر في استخدام تكوينات موارد أخف:

var redis = builder.AddRedis("cache")
    .WithContainerRuntimeArgs("--memory=256m");

الخلاصة

لقد غيّر .NET Aspire بشكل أساسي طريقة إنشاء التطبيقات الموزعة. ليس لأنه يقدم مفاهيم ثورية - فالفحوصات الصحية، وقياس OpenTelemetry، وتنسيق الحاويات ليست جديدة. ولكن لأنها تجعلها افتراضية. فهو يأخذ مئات القرارات الصغيرة التي يتعين عليك اتخاذها عادةً، وينفذها بافتراضيات معقولة، ويتيح لك تجاوزها عند الحاجة.نمط AppHost، في رأيي، هو أكبر فوز. إن التعبير عن طوبولوجيا التطبيق الموزعة على شكل تعليمات برمجية - غير متناثرة عبر ملفات Docker Compose، وبيانات Kubernetes، ومستندات README - يجعل النظام مفهومًا. يمكن لأعضاء الفريق الجدد فتح Program.cs في AppHost وفهم البنية بأكملها في دقائق.

إذا كنت تقوم بإنشاء تطبيقات موزعة باستخدام .NET، فامنح Aspire نظرة جادة. ابدأ بالقالب aspire-starter، واستكشف لوحة المعلومات، واستخدم المكونات تدريجيًا حسب حاجتك إليها. ليس عليك أن تبذل كل ما في وسعك في اليوم الأول، فشركة Aspire هي شركة إضافية من حيث تصميمها.

لقد ولت أيام قضاء أول سباق سريع في توصيل البنية الأساسية للبنية الأساسية. اسمح لشركة Aspire بالتعامل مع أعمال السباكة حتى تتمكن من التركيز على ما يهم فعليًا: تطبيقك.