اشتراکها
مثالهای بررسی تازههای EF Core 8x
اشتراکها
سری آموزش استفاده از Roslyn
اشتراکها
دسته بندی الگوهای طراحی
الگوهای طراحی از نظر پیچیدگی ، سطح جزئیات و مقیاس کاربرد برای کل سیستم در حال طراحی متفاوت هستند. تشبیه به راه سازی را دوست دارم: شما میتوانید با نصب برخی از چراغهای راهنمایی و یا ایجاد یک تپل چند سطحی با معابر زیرزمینی برای عابرین پیاده ، یک تقاطع را ایمنتر کنید.
به ابتداییترین و سطح پایینترین الگوها اغلب اصطلاحا منفرد گفته میشود. آنها معمولاً فقط در یک زبان برنامه نویسی کاربرد دارند.
کلیترین و سطح بالاترین الگوها، الگوهای معماری است. توسعه دهندگان میتوانند این الگوها را تقریباً به هر زبانی پیاده سازی کنند. برخلاف الگوهای دیگر ، میتوان از آنها برای طراحی معماری کل برنامه استفاده کرد.
علاوه بر این ، همه الگوها را میتوان با توجه به هدف آنها طبقه بندی کرد. این مطلب شامل سه گروه اصلی از الگوها است:
- الگوهای خلاقیت مکانیسمهای ساخت شی را ایجاد میکنند که انعطاف پذیری و استفاده مجدد از کد موجود را افزایش میدهد.
- الگوهای ساختاری نحوه جمع آوری اشیا و کلاسها را به ساختارهای بزرگتر توضیح میدهد ، در حالی که سازهها را انعطاف پذیر و کارآمد نگه میدارد.
- الگوهای رفتاری از برقراری ارتباط موثر و تعیین مسئولیت بین اشیا مراقبت میکنند.
نظرات مطالب
معماری میکروسرویسها
من در مورد همه مشکلات میکرو سرویس زیاد با شما موافق نیستم
- از آنجایی که ارتباط بین سرویسها در بستر شبکه انجام میشود، انتظار کندی عملکرد سرویسها دور از ذهن نیست. (اتفاقا بخاطر توزیع برنامه بر روی چند سیستم در زمانی که بار زیادی بر روی سیستم هست پاسخ گویی به کاربر میتونه خیلی بسرعت انجام بپذیره و اتفاقا یکی از مزایای اون هست)
- به دلیل ارتباطات شبکهای، احتمال آسیب پذیریهای امنیتی در این نوع برنامهها بیشتر است. (البته بیشتر این توزیع در server farm انجام میشه ،یعنی پشت فایروال و کسی جز سرورها در این شبکه خصوصی وجود ندارد، نمیگم نیست ولی خیلی نیست)
- نوشتن سرویسهایی که در بستر شبکه با سایر سرویسها در ارتباط هستند سختی و مشکلات خود را دارد. برنامهنویس در این شرایط، درگیر برقراری ارتباط، رمزگذاری دادهها در صورت نیاز و تبدیل آنها میشود.(همان موارد بالا)
- به دلیل مجزا بودن بخشهای مختلف برنامه، مانیتور کردن و ردیابی عملکرد سرویسها، یکی از کارهای اصلی توسعه دهنده یا استفاده کننده از برنامه است. (اینم خودش یک فایده است و طبق اصل SRP و تفاوت MicroServic با SOA بیشتر بر همین نکته تاکید داره که یک میکرو سرویس کاملا مستقل میباشد و راحتتر قابل مانیتور کردن و ردیابی عملکرد سرویس میباشد
- در مجموع سرعت برنامههای نوشته شده با معماری Microservices کندتر از برنامههای نوشته شده با معماری Monolithic است. دلیل آن محیط اجرایی برنامهها است. برنامههایی با معماری Monolithic بر روی حافظه سرور پردازش میشوند. (باز تاکید که اصل استفاده از میکرو سرویس برای سیستم هایی با تراکنش بالا میباشد ،هدف توسعه راحتر و بدون تاثیر بر بقیه سرویسها و حتی بدون توقف آنها میباشد، همچنین امکان horizontal Scalability نرم افزار و بالا بردن تعداد سرورهای ارائه دهنده سرویس براحتی بوجود خواهد امد ، پس میتونه سرعت رو خیلی بالا ببره و مشکل توقف سرویس که در خیلی از سامانههای ایرانی میبینیم رو از بین میبره )
در این رابطه آقای راد در دو قسمت به صورت مختصر و مفید این کتابخانه قدرتمند رو همراه با ارائه چندین مثال کاربردی معرفی کردند:
قسمت اول
قسمت دوم
در تکمیل قسمتهای فوق بنده میخوام مثالی رو در این رابطه براتون بذارم، هدف از ارائه این مثال اتوماتیک سازی یک فرآیند روتین میباشد، به این صورت که در جایی که بنده مشغول به کار هستم یک سری لایسنس آنتی ویروس برای کلاینتها در یک شبکه با مقیاس متوسط تهیه گردیده است، حال یک نسخه رایگان نیز برای کاربرانی که قصد دارند آنتی ویروس را برای سیستم شخصی خود نصب کنند نیز موجود میباشد که نیاز به آپدیت دارد معمولا آپدیتها هر چند روز یکبار یا هر هفته در دو نسخه 64 و 32 بیتی ارائه میشوند، روال معمول برای دریافت آپدیت مراجعه به سایت و دانلود نسخههای مربوطه میباشد.
حال توسط کتابخانه قدرتمند Quartz.NET این فرآیند روتین را به صورت اتوماتیک میخواهیم انجام دهیم، استفاده از کتابخانه ذکر شده سخت نیست همانطور که در دو مطلب قبلی مرتبط ذکر گردیده، تنها پیاده سازی چندین اینترفیس است و بس.
در این کد که همانند کدهای پیشنیازهای مطلب است، در خط 33 از متد WithDailyTimeIntervalSchedule استفاده شده است و همانطور که مشخص است وظیفه تعیین شده و هر روز ساعت 7 اجرا میشود.
مورد بعدی عملیات دانلود فایل میباشد که در ادامه مشاهده خواهید کرد، صفحه ایی که لینک فایلهای دانلود را ارائه داده است دو نسخه مد نظر ما را در ابتدا لیست کرده است و با استفاده از web scrapingمی توانیم موارد تعیین شده را استخراج کنیم برای این منظور از کتابخانه htmlagilitypack استفاده میکنیم، تطبیق دو مورد(لینک) اول جهت دریافت نسخههای 32 و 64 بیتی به کمک Regular Expression میسر است و همانطور که در شکل زیر مشاهده میکنید از سمت چپ تاریخ به صورت 8 رقم، سه رقم قسمت دوم و ارقام و حروف قسمت سوم است به اضافه پسوند فایل مشخص است :
توضیح کدهای فوق :
ابتدا توسط متد LoadHtml خط 14 صفحه مورد نظر که حاوی لینکها میباشد رو Load میکنیم، سپس توسط یک حلقه foreach خط 16 مقدار خصوصیت href تمام لینکهای موجود در صفحه را استخراج میکنیم مثلا مقدار خصوصیت href در لینکها به صورت زیر میباشد :
همانطور که مشخص است در دو مورد فوق تنها نام فایل متفاوت میباشد، همانطور که بحث شد برای نام فایلها هم میتوانیم یک Pattern را به صورت زیر داشته باشیم :
در خط 20 نیز عملیات تطبیق تمام hrefهای موجود در صفحه را توسط Regular Expression فوق تطبیق میدهیم، اگر تطبیق با موفقیت انجام پذیرفت باید نام فایل و همچنین تاریخ موجود در نام فایل را نیز توسط دو Regular Expression استخراج کنیم(خط 23 و 24) در ادامه برای جدا کردن مقادیر سال ، ماه ، روز از امکان Groups در RegEx استفاده کرده ایم:
در ادامه تاریخ استخراج شده را با تاریخ روز جاری مقایسه میکنیم اگر مساوی بود عملیات دانلود فایلها توسط یک Task تعریف شده به صورت همزمان بر روی سرور مربوطه دانلود میشوند.
البته لازم به ذکر است که کدهای فوق مسلما نیاز یه Refactoring دارند منتها هدف از ارائه این مثال آشنایی بیشتر با کتابخانههای فوق میباشد.
نکته آخر اینکه برنامه فوق به حالتهای مختلفی میتواند اجرا گردد مثل یک برنامه وب یا یک سرویس ویندوزی و ... ، بهترین حالت یک سرویس ویندوز میباشد، ولی در حالت خام در حال حاضر یک ویندوز اپلیکیشن ساده میباشد که بر روی سرور RUN شده است که در آینده به صورت یک سرویس ویندوز ارائه خواهد شد.
قسمت اول
قسمت دوم
در تکمیل قسمتهای فوق بنده میخوام مثالی رو در این رابطه براتون بذارم، هدف از ارائه این مثال اتوماتیک سازی یک فرآیند روتین میباشد، به این صورت که در جایی که بنده مشغول به کار هستم یک سری لایسنس آنتی ویروس برای کلاینتها در یک شبکه با مقیاس متوسط تهیه گردیده است، حال یک نسخه رایگان نیز برای کاربرانی که قصد دارند آنتی ویروس را برای سیستم شخصی خود نصب کنند نیز موجود میباشد که نیاز به آپدیت دارد معمولا آپدیتها هر چند روز یکبار یا هر هفته در دو نسخه 64 و 32 بیتی ارائه میشوند، روال معمول برای دریافت آپدیت مراجعه به سایت و دانلود نسخههای مربوطه میباشد.
حال توسط کتابخانه قدرتمند Quartz.NET این فرآیند روتین را به صورت اتوماتیک میخواهیم انجام دهیم، استفاده از کتابخانه ذکر شده سخت نیست همانطور که در دو مطلب قبلی مرتبط ذکر گردیده، تنها پیاده سازی چندین اینترفیس است و بس.
namespace SymantecUpdateDownloader { using System; using System.IO; using Quartz; using Quartz.Impl; using System.Globalization; public class TestJob : IJob { public void Execute(IJobExecutionContext context) { new Download().Scraping(); } } public interface ISchedule { void Run(); } public class TestSchedule : ISchedule { public void Run() { DateTimeOffset startTime = DateBuilder.FutureDate(2, IntervalUnit.Second); IJobDetail job = JobBuilder.Create<HelloJob>() .WithIdentity("job1") .Build(); ITrigger trigger = TriggerBuilder.Create() .WithIdentity("trigger1") .StartAt(startTime) .WithDailyTimeIntervalSchedule(x => x.OnEveryDay().StartingDailyAt(new TimeOfDay(7, 0)).WithRepeatCount(0)) .Build(); ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sc = sf.GetScheduler(); sc.ScheduleJob(job, trigger); sc.Start(); } } }
مورد بعدی عملیات دانلود فایل میباشد که در ادامه مشاهده خواهید کرد، صفحه ایی که لینک فایلهای دانلود را ارائه داده است دو نسخه مد نظر ما را در ابتدا لیست کرده است و با استفاده از web scrapingمی توانیم موارد تعیین شده را استخراج کنیم برای این منظور از کتابخانه htmlagilitypack استفاده میکنیم، تطبیق دو مورد(لینک) اول جهت دریافت نسخههای 32 و 64 بیتی به کمک Regular Expression میسر است و همانطور که در شکل زیر مشاهده میکنید از سمت چپ تاریخ به صورت 8 رقم، سه رقم قسمت دوم و ارقام و حروف قسمت سوم است به اضافه پسوند فایل مشخص است :
public class Download { static WebClient wc = new WebClient(); static ManualResetEvent handle = new ManualResetEvent(true); private DateTime myDate = new DateTime(); public void Scraping() { using (WebClient client = new WebClient()) { client.Encoding = System.Text.Encoding.UTF8; var doc = new HtmlAgilityPack.HtmlDocument(); ArrayList result = new ArrayList(); doc.LoadHtml(client.DownloadString("https://www.symantec.com/security_response/definitions/download/detail.jsp?gid=savce")); var tasks = new List<Task>(); foreach (var href in doc.DocumentNode.Descendants("a").Select(x => x.Attributes["href"])) { if (href == null) continue; string s = href.Value; Match m = Regex.Match(s, @"http://definitions.symantec.com/defs/(\d{8}-\d{3}-v5i(32|64)\.exe)"); if (m.Success) { Match date = Regex.Match(m.Value, @"(\d{4})(\d{2})(\d{2})"); Match filename = Regex.Match(m.Value, @"\d{8}-\d{3}-v5i(32|64)\.exe"); int year = Int32.Parse(date.Groups[0].Value); int month = Int32.Parse(date.Groups[1].Value); int day = Int32.Parse(date.Groups[3].Value); myDate = new DateTime( Int32.Parse(date.Groups[1].Value), Int32.Parse(date.Groups[2].Value), Int32.Parse(date.Groups[3].Value)); if (myDate == DateTime.Today) { tasks.Add(DownloadUpdate(m.Value, filename.Value)); } else { MessageBox.Show("امروز آپدیت موجود نیست"); } } } DownloadTask = Task.WhenAll(tasks); } } private static Task DownloadTask; private Task DownloadUpdate(string url, string fileName) { var wc = new WebClient(); return wc.DownloadFileTaskAsync(new Uri(url), @"\\10.1.0.15\SymantecUpdate\\" + fileName); } }
ابتدا توسط متد LoadHtml خط 14 صفحه مورد نظر که حاوی لینکها میباشد رو Load میکنیم، سپس توسط یک حلقه foreach خط 16 مقدار خصوصیت href تمام لینکهای موجود در صفحه را استخراج میکنیم مثلا مقدار خصوصیت href در لینکها به صورت زیر میباشد :
http://definitions.symantec.com/defs/20130622-007-v5i32.exe
http://definitions.symantec.com/defs/20130622-007-v5i64.exe
همانطور که مشخص است در دو مورد فوق تنها نام فایل متفاوت میباشد، همانطور که بحث شد برای نام فایلها هم میتوانیم یک Pattern را به صورت زیر داشته باشیم :
(\d{8}-\d{3}-v5i(32|64)\.exe)
int year = Int32.Parse(date.Groups[0].Value); int month = Int32.Parse(date.Groups[1].Value); int day = Int32.Parse(date.Groups[3].Value);
البته لازم به ذکر است که کدهای فوق مسلما نیاز یه Refactoring دارند منتها هدف از ارائه این مثال آشنایی بیشتر با کتابخانههای فوق میباشد.
نکته آخر اینکه برنامه فوق به حالتهای مختلفی میتواند اجرا گردد مثل یک برنامه وب یا یک سرویس ویندوزی و ... ، بهترین حالت یک سرویس ویندوز میباشد، ولی در حالت خام در حال حاضر یک ویندوز اپلیکیشن ساده میباشد که بر روی سرور RUN شده است که در آینده به صورت یک سرویس ویندوز ارائه خواهد شد.
اگر یک پروژهی جدید ASP.NET Core 6x را شروع کنیم، دو فایل قدیمی Program.cs و Startup.cs آن یکی شدهاند و اینبار فقط یک Program.cs قابل مشاهدهاست؛ با چنین محتوای ساده شدهای:
که مفاهیم C# 10.0 مانند «ساده سازی تعریف فضاهای نام در C# 10.0» و «کاهش تعداد بار تعریف usingها در C# 10.0 و NET 6.0.» در آنها نیز قابل مشاهدهاست. همچنین در اینجا، تمام تنظیمات WebApplication هم قرار خواهند گرفت؛ عنوان کردهاند که از ابتدا هم این تنظیمات در اصل متعلق به همینجا بودهاند، چرا تمام آنها را داخل یک فایل اسکریپت مانند قرار ندهیم و تعداد لایههای abstractions را کاهش ندهیم؟
البته این روش شاید برای برنامههای کوچک جالب بهنظر برسد، اما برای برنامههای بزرگتر میتوان به گزینههای زیر نیز توجه داشت.
گزینهی ارتقاء 1: هیچ کاری نکنید!
اگر میخواهید برنامههای NET 5. خود را به دات نت 6 ارتقاء دهید و نگران هستید که با دو فایل قدیمی Program.cs و Startup.cs آن باید چکار کنیم، پاسخ سادهی آن این است: هیچ کاری نکنید!
شیوهی قدیمی مبتنی بر generic host و Startup، کاملا در دات نت 6 پشتیبانی میشوند؛ از این جهت که WebApplication جدید دات نت 6، صرفا یک محصور کنندهی پیچیدگیهای generic host است. بنابراین برای ارتقاء پروژههای ASP.NET Core 5x به 6x، تنها کافی است فایل csproj خود را ویرایش کرده و TargetFramework آنرا به net6.0 تغییر دهید. پس از آن Program.cs و Stratup.cs قبلی شما بدون هیچ مشکلی و بدون نیاز به هیچ تغییری، با دات نت 6 هم کار خواهند کرد.
گزینهی ارتقاء 2: از کلاس Startup قبلی خود استفادهی مجدد کنید
اما اگر واقعا علاقمندیم که از WebApplication جدید استفاده کنیم و همچنین نمیخواهیم همهچیز را داخل Program.cs قرار دهیم، چکار باید کرد؟
فرض کنید ساختار کلاس Startup موجود شما چنین شکلی را دارد که به همراه سازندهای است که IConfigurationRoot را دریافت میکند و همچنین دارای دو متد ConfigureServices و Configure نیز هست:
تا پیش از دات نت 6، متد <UseStartup<T که در فایل Program.cs قرار داشت، کار استفاده از تنظیمات کلاس فوق را به صورت خودکار انجام میداد. این متد دیگر در سیستم جدید مبتنی بر WebApplication دات نت 6 وجود ندارد، اما میتوان به صورت زیر آنرا برگرداند:
در اینجا روش نمونه سازی دستی کلاس Startup قدیمی را مشاهده میکنید که به همراه فراخوانی دستی دو متد ConfigureServices و Configure آن نیز هست. به این ترتیب میتوان از کلاس قدیمی آغازین برنامههای دات نت 5، در برنامههای دات نت 6 نیز استفاده کرد.
گزینهی ارتقاء 3: استفاده از متدهای محلی در فایل Program.cs
اگر بخواهیم سیستم طراحی مینیمال دات نت 6 را رعایت کنیم، میتوان بجای ایجاد یک فایل Startup مجزا، متدهای تنظیمی آنرا به صورت تعدادی متد محلی، در همان فایل Program.cs قرار داد تا کمی ساختار پیدا کند(!)؛ چیزی شبیه به طراحی زیر که همان متدهای قبلی فایل Startup را در انتهای فایل Program.cs جاری به صورت متدهایی محلی، مشاهده میکنید؛ به همراه متدهای اختیاری دیگری برای تنظیم میانافزارها و یا endpoints:
در کل این قالب جدید دات نت 6، هیچ نوع الگو و یا پیشنیاز خاصی را جهت انجام تنظیمات آغازین برنامه توصیه نمیکند؛ از این رو میتوان به هر نحوی که علاقمند بودیم، آنرا شکل دهیم.
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run();
البته این روش شاید برای برنامههای کوچک جالب بهنظر برسد، اما برای برنامههای بزرگتر میتوان به گزینههای زیر نیز توجه داشت.
گزینهی ارتقاء 1: هیچ کاری نکنید!
اگر میخواهید برنامههای NET 5. خود را به دات نت 6 ارتقاء دهید و نگران هستید که با دو فایل قدیمی Program.cs و Startup.cs آن باید چکار کنیم، پاسخ سادهی آن این است: هیچ کاری نکنید!
شیوهی قدیمی مبتنی بر generic host و Startup، کاملا در دات نت 6 پشتیبانی میشوند؛ از این جهت که WebApplication جدید دات نت 6، صرفا یک محصور کنندهی پیچیدگیهای generic host است. بنابراین برای ارتقاء پروژههای ASP.NET Core 5x به 6x، تنها کافی است فایل csproj خود را ویرایش کرده و TargetFramework آنرا به net6.0 تغییر دهید. پس از آن Program.cs و Stratup.cs قبلی شما بدون هیچ مشکلی و بدون نیاز به هیچ تغییری، با دات نت 6 هم کار خواهند کرد.
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> </PropertyGroup> </Project>
گزینهی ارتقاء 2: از کلاس Startup قبلی خود استفادهی مجدد کنید
اما اگر واقعا علاقمندیم که از WebApplication جدید استفاده کنیم و همچنین نمیخواهیم همهچیز را داخل Program.cs قرار دهیم، چکار باید کرد؟
فرض کنید ساختار کلاس Startup موجود شما چنین شکلی را دارد که به همراه سازندهای است که IConfigurationRoot را دریافت میکند و همچنین دارای دو متد ConfigureServices و Configure نیز هست:
public class Startup { public Startup(IConfigurationRoot configuration) { Configuration = configuration; } public IConfigurationRoot Configuration { get; } public void ConfigureServices(IServiceCollection services) { // ... } public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime) { // ... } }
var builder = WebApplication.CreateBuilder(args); var startup = new Startup(builder.Configuration); startup.ConfigureServices(builder.Services); var app = builder.Build(); startup.Configure(app, app.Lifetime); app.Run();
گزینهی ارتقاء 3: استفاده از متدهای محلی در فایل Program.cs
اگر بخواهیم سیستم طراحی مینیمال دات نت 6 را رعایت کنیم، میتوان بجای ایجاد یک فایل Startup مجزا، متدهای تنظیمی آنرا به صورت تعدادی متد محلی، در همان فایل Program.cs قرار داد تا کمی ساختار پیدا کند(!)؛ چیزی شبیه به طراحی زیر که همان متدهای قبلی فایل Startup را در انتهای فایل Program.cs جاری به صورت متدهایی محلی، مشاهده میکنید؛ به همراه متدهای اختیاری دیگری برای تنظیم میانافزارها و یا endpoints:
var builder = WebApplication.CreateBuilder(args); ConfigureConfiguration(builder.configuration); ConfigureServices(builder.Services); var app = builder.Build(); ConfigureMiddleware(app, app.Services); ConfigureEndpoints(app, app.Services); app.Run(); void ConfigureConfiguration(ConfigurationManager configuration) => { } void ConfigureServices(IServiceCollection services) => { } void ConfigureMiddleware(IApplicationBuilder app, IServiceProvider services) => { } void ConfigureEndpoints(IEndpointRouteBuilder app, IServiceProvider services) => { }
در سمت IDP:همان قسمت «نصب و راه اندازی - تعریف کاربران، منابع و کلاینتها» است. در این دمو از نگارش درون حافظهای استفاده شده. امکان تعویض آن با « IdentityServer4.EntityFramework » وجود دارد که نیاز به یک بحث مجزا برای آن هست.