Back to "الجزء ٢ - تحويل الخدمات )وقليل من صدم النيران("

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET Email Newsletter Hangfire

الجزء ٢ - تحويل الخدمات )وقليل من صدم النيران(

Monday, 23 September 2024

أولاً

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

المشاريع

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

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

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

منسusCd

هذا المشروع هو مشروع على شبكة الإنترنت ASP.NET Cour (8)، وهو يحمل جميع المراقبين والآراء لعرض صفحاتها على المستخدم.

الـCliscised. DbConCon

وهذا يحمل تعريفي الرئيسي للسياق المستخدم للتفاعل مع قاعدة البيانات. بما في ذلك النص الأساسي لـ EF.

٪ في الغالب cid.

هذا هو مشروع مكتبة الفئة الرئيسية الذي يحتوي على جميع الخدمات التي تتفاعل مع قاعدة البيانات/ملفات العلامات/خدمات البريد الإلكتروني وما إلى ذلك.

أُسس معظمها أُسس معظمها أُسس معظمها أُسستها

هذا مشروع مكتبة صنفي يحمل جميع النماذج المشتركة التي تستخدم من قبل كل من مشروعي Lusesluluced و Luselucid.ScheduleerServices.

SCSSSERSSLS

هذا هو تطبيق الويب الذي يسيطر على خدمة Halfire التي تدير المهام المقررة فضلا عن نقاط النهاية للتعامل مع إرسال البريد الإلكتروني.

ويرد أدناه هيكل هذا الجدول. يمكنك أن ترى أن خدمة الجدول و الميزات cid الرئيسية لا تستخدم إلا فئة DbConcord لـ مبدئية.

graph LR Mostlylucid[Mostlylucid] --> |Intialize| Mostlylucid_DbContext[Mostlylucid.DbContext] Mostlylucid[Mostlylucid] --> Mostlylucid_Shared[Mostlylucid.Shared] Mostlylucid[Mostlylucid] --> Mostlylucid_Services[Mostlylucid.Services] Mostlylucid_DbContext[Mostlylucid.DbContext] --> Mostlylucid_Shared[Mostlylucid.Shared] Mostlylucid_Services[Mostlylucid.Services] --> Mostlylucid_DbContext[Mostlylucid.DbContext] Mostlylucid_Services[Mostlylucid.Services] --> Mostlylucid_Shared[Mostlylucid.Shared] Mostlylucid_SchedulerService[Mostlylucid.SchedulerService] --> Mostlylucid_Shared[Mostlylucid.Shared] Mostlylucid_SchedulerService[Mostlylucid.SchedulerService] --> |Intialize|Mostlylucid_DbContext[Mostlylucid.DbContext] Mostlylucid_SchedulerService[Mostlylucid.SchedulerService] --> Mostlylucid_Services[Mostlylucid.Services]

وكما هو معتاد، أستخدم طريقة تمديد كمدخل لإنشاء قاعدة البيانات.

public static class Setup
{
    public static void SetupDatabase(this IServiceCollection services, IConfiguration configuration,
        IWebHostEnvironment env, string applicationName="mostlylucid")
    {
        services.AddDbContext<IMostlylucidDBContext, MostlylucidDbContext>(options =>
        {
            if (env.IsDevelopment())
            {
                options.EnableDetailedErrors(true);
                options.EnableSensitiveDataLogging(true);
            }
            var connectionString = configuration.GetConnectionString("DefaultConnection");
            var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
            {
                ApplicationName = applicationName
            };
            options.UseNpgsql(connectionStringBuilder.ConnectionString);
        });
    }
}

وبصرف النظر عن تسمية هذه الطريقة، لا يعتمد أي مشروع من المستوى الأعلى على مشروع DbCont project.

ملاحظة: من المهم تجنب شرط عرقي عند وضع صيغة DbContex (خاصة إذا قمت بإدارة الهجرات) للقيام بذلك هنا ببساطة قمت للتو بإضافة اعتماد في ملفي المتراكم Doker للتأكد من أن المشروع الرئيسي لـ missuluscid يعمل قبل البدء في خدمة Server Shedueler. هذا هو إلى خدمة تعريف لـ جدولService في ملفّ.

  depends_on:
      - mostlylucid 
  healthcheck:
      test: ["CMD", "curl", "-f", "http://mostlylucid:80/healthy"]
      interval: 30s
      timeout: 10s
      retries: 5

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

تدّي إطلاق النار

اخترت استخدام (هالفاير) لمعالجة جدول مواعيدي لأنه لديه دمج ملائم مع (أس بي نت) الأساسية وسهل الاستخدام.

لـ هذا بوصة جدول Serfice مشروع I اضفت التالي نوجت حزم.

dotnet add package Hangfire.AspNetCore
dotnet add package Hangfire.PostgreSql

وهذا يعني أنني يمكن أن استخدام Postgres كمخزن بلدي للمهام المقررة.

الآن لديّ (هالفاير) يُمكنني البدء بإضافة وظائفي المُحددة.

public static class JobInitializer
{
    private const string AutoNewsletterJob = "AutoNewsletterJob";
    private const string DailyNewsletterJob = "DailyNewsletterJob";
    private const string WeeklyNewsletterJob = "WeeklyNewsletterJob";
    private const string MonthlyNewsletterJob = "MonthlyNewsletterJob";
    public static void InitializeJobs(this IApplicationBuilder app)
    {
      var scope=  app.ApplicationServices.CreateScope();
        var recurringJobManager = scope.ServiceProvider.GetRequiredService<RecurringJobManager>();
      
        recurringJobManager.AddOrUpdate<NewsletterSendingService>(AutoNewsletterJob,  x =>  x.SendNewsletter(SubscriptionType.EveryPost), Cron.Hourly);
        recurringJobManager.AddOrUpdate<NewsletterSendingService>(DailyNewsletterJob,  x =>  x.SendNewsletter(SubscriptionType.Daily), "0 17 * * *");
        recurringJobManager.AddOrUpdate<NewsletterSendingService>(WeeklyNewsletterJob,  x =>  x.SendNewsletter(SubscriptionType.Weekly), "0 17 * * *");
        recurringJobManager.AddOrUpdate<NewsletterSendingService>(MonthlyNewsletterJob,  x => x.SendNewsletter(SubscriptionType.Monthly), "0 17 * * *");
    }
}

هنا ترون أن لدي وظيفة لكل من SubscriptionType وهو علم يحدد عدد المرات التي ترسل فيها الرسالة الإخبارية. أنا استخدم الـ Cron لترتيب تردد الوظيفة.

كما لدي خيار اليوم للاشتراكات الشهرية والأسبوعية على حد سواء حددت الوقت إلى 17:00 (5 مساء) حيث أن هذا هو الوقت المناسب لإرسال الرسائل الإخبارية (نهاية اليوم في المملكة المتحدة والبدء في الولايات المتحدة). ولدي أيضا وظيفة تعمل كل ساعة لإرسال الرسالة الإخبارية إلى أولئك الذين اشتركوا في كل وظيفة.

ثم هذا يُطلق إلى a SendNewsletter فـي NewsletterSendingService -مصنفة. -مصنفة. والذي سأتطرق إليه بمزيد من التفاصيل في رسالة لاحقة

Dtos ورسم الخرائط

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

يمكنك استخدام نُهُج مثل AutoMapsper / Mapster etc للقيام بهذا التخطيط. استخدام هذه يمكن أيضاً أن يكون له مزايا أداء كبيرة للاستفسارات. Ass noTtracking () كما يمكنك رسم الخرائط مباشرة إلى Dto وتجنب الارتفاعات العامة للتغييرات التتبعية. على سبيل المثال: تمديد قابل للتمديد هذا الطريق يسمح لك بالرسم مباشرة إلى dto في الإقتران.

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

public static class BlogPostEntityMapper
{
   public static BlogPostDto ToDto(this BlogPostEntity entity, string[] languages = null)
    {
        return new BlogPostDto
        {
            Id = entity.Id.ToString(),
            Title = entity.Title,
            Language = entity.LanguageEntity?.Name,
            Markdown = entity.Markdown,
            UpdatedDate = entity.UpdatedDate.DateTime,
            HtmlContent = entity.HtmlContent,
            PlainTextContent = entity.PlainTextContent,
            Slug = entity.Slug,
            WordCount = entity.WordCount,
            PublishedDate = entity.PublishedDate.DateTime,
            Languages = languages ?? Array.Empty<string>()
        };
    }
}

هنا يمكنك أن ترى أنا خريطة BlogPostEntity مـن المكـان الرئيسي إلـى BlogPostDto الذي هو هدف النقل الخاص بي
الهدف هو أن خدمات الطرف الأمامي لا تعرف أي شيء عن كائن الكيان وهي 'مُستَقطبة' من بنية البيانات الأساسية.

في هذه الخدمات على المستوى الأعلى لدي رمز لخرائط هذه Dtos إلى عرض Models التي تستخدم بعد ذلك في الحسابات المالية.

    public static PostListModel ToPostListModel(this BlogPostDto dto)
    {
        return new PostListModel
        {
            Id = dto.Id,
            Title = dto.Title,
            Language = dto.Language,
            UpdatedDate = dto.UpdatedDate.DateTime,
            Slug = dto.Slug,
            WordCount = dto.WordCount,
            PublishedDate = dto.PublishedDate,
            Languages = dto.Languages
        };
    }
    
    public static BlogPostViewModel ToViewModel(this BlogPostDto dto)
    {
        return new BlogPostViewModel
        {
            Id = dto.Id,
            Title = dto.Title,
            Language = dto.Language,
            Markdown = dto.Markdown,
            UpdatedDate = dto.UpdatedDate.DateTime,
            HtmlContent = dto.HtmlContent,
            PlainTextContent = dto.PlainTextContent,
            Slug = dto.Slug,
            WordCount = dto.WordCount,
            PublishedDate = dto.PublishedDate,
            Languages = dto.Languages
        };
    }

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

الآن لدي كل هذه النماذج جاهزة يمكنني البدء بإضافة أساليب التحكم لاستخدامها. سأغطي هذا التدفق في الجزء التالي

في الإستنتاج

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

logo

©2024 Scott Galloway