در معماری میکروسرویس، هر سرویس دیتابیس مربوط به خود را دارد. بنابراین برای انجام یک تراکنش احتیاج به یک تراکنش توزیع شده میان سرویسها هست، که یکپارچگی دادهها را با چندین تراکنش محلی تضمین کند. الگوی saga برای پیاده سازی این تراکنش توزیع شده ارائه شده است.
- choreography-based saga
- orchestration-based saga
پیشنیازها (الزامی)
«بررسی مفاهیم معکوس سازی وابستگیها و ابزارهای مرتبط با آن»
«اصول طراحی SOLID»
«مطالعهی بیشتر»
تزریق وابستگیها (یا Dependency injection = DI) به معنای ارسال نمونهای/وهلهای از وابستگی (یک سرویس) به شیء وابستهی به آن (یک کلاینت) است. در فرآیند تزریق وابستگیها، یک کلاس، وهلههای کلاسهای دیگر مورد نیاز خودش را بجای وهله سازی مستقیم، از یک تزریق کننده دریافت میکند. بنابراین بجای نوشتن newها در کلاس جاری، آنها را به صورت وابستگیهایی در سازندهی کلاس تعریف میکنیم تا توسط یک IoC Container تامین شوند. در اینجا به فریم ورکهایی که کار وهله سازی این وابستگیها را انجام میدهند، IoC Container و یا DI container میگوییم (IoC = inversion of control ).
چندین نوع تزریق وابستگیها وجود دارند که دو حالت زیر، عمومیترین آنها است:
الف) تزریق در سازندهی کلاس: لیست وابستگیهای یک کلاس، به عنوان پارامترهای سازندهی آن ذکر میشوند.
ب) تزریق در خواص یا Setter injection: کلاینت خواصی get و set را به صورت public معرفی میکند و سپس IoC Container با وهله سازی آنها، وابستگیهای مورد نیاز را تامین خواهد کرد.
تزریق وابستگیها در ASP.NET Core
برخلاف نگارشهای قبلی ASP.NET، این نگارش جدید از ابتدا با دید پشتیبانی کامل از DI طراحی شدهاست و این مفهوم، در سراسر اجزای آن به صورت یکپارچهای پشتیبانی میشود. همچنین به همراه یک minimalistic DI container توکار نیز هست .
این IoC Container توکار از 4 حالت طول عمر ذیل پشتیبانی میکند:
- instance: در هربار نیاز به یک وابستگی خاص، تنها یک وهله از آن در اختیار مصرف کننده قرار میگیرد و در اینجا شما هستید که مسئول تعریف نحوهی وهله سازی این شیء خواهید بود (برای بار اول).
- transient: هربار که نیاز به وابستگی خاصی بود، یک وهلهی جدید از آن توسط IoC Container تولید و ارائه میشود.
- singleton: در این حالت تنها یک وهله از وابستگی درخواست شده در طول عمر برنامه تامین میشود.
- scoped: در طول عمر یک scope خاص، تنها یک وهله از وابستگی درخواست شده، در اختیار مصرف کنندهها قرار میگیرد. برای مثال مرسوم است که به ازای یک درخواست وب، تنها یک وهله از شیءایی خاص در اختیار تمام مصرف کنندههای آن قرار گیرد (single instance per web request).
طول عمر singleton، برای سرویسها و کلاسهای config مناسب هستند. به این ترتیب به کارآیی بالاتری خواهیم رسید و دیگر نیازی نخواهد بود تا هر بار این اطلاعات خوانده شوند. حالت scoped برای وهله سازی الگوی واحد کار و پیاده سازی تراکنشها مناسب است. برای مثال در طی یک درخواست وب، یک تراکنش باید صورت گیرد.
حالت scoped در حقیقت نوع خاصی از حالت transient است. در حالت transient صرفنظر از هر حالتی، هربار که وابستگی ویژهای درخواست شود، یک وهلهی جدید از آن تولید خواهد شد. اما در حالت scoped فقط یک وهلهی از وابستگی مورد نظر، در بین تمام اشیاء وابستهی به آن، در طول عمر آن scope تولید میشود.
بنابراین در برنامههای وب دو نوع singleton برای معرفی کلاسهای config و نوع scoped برای پیاده سازی تراکنشها و همچنین بالابردن کارآیی برنامه در طی یک درخواست وب (با عدم وهله سازی بیش از اندازهی از کلاسهای مختلف مورد نیاز)، بیشتر از همه به کار برده میشوند.
یک مثال کاربردی: بررسی نحوهی تزریق یک سرویس سفارشی به کمک IoC Container توکار ASP.NET Core
مثال جاری که بر اساس ASP.NET Core Web Application و با قالب خالی آن ایجاد شدهاست، دارای نام فرضی Core1RtmEmptyTest است. در همین پروژه بر روی پوشهی src، کلیک راست کرده و گزینهی Add new project را انتخاب کنید و سپس یک پروژهی جدید از نوع NET Core -> Class library. را به آن، با نام Core1RtmEmptyTest.Services اضافه کنید (تصویر فوق).
در ادامه کلاس نمونهی سرویس پیامها را به همراه اینترفیس آن، با محتوای زیر به آن اضافه کنید:
در ادامه به پروژهی Core1RtmEmptyTest مراجعه کرده و بر روی گره references آن کلیک راست کنید. در اینجا گزینهی add reference را انتخاب کرده و سپس Core1RtmEmptyTest.Services را انتخاب کنید، تا اسمبلی آنرا بتوان در پروژهی جاری استفاده کرد.
انجام اینکار معادل است با افزودن یک سطر ذیل به فایل project.json پروژه:
در ادامه قصد داریم این سرویس را به متد Configure کلاس Startup تزریق کرده و سپس خروجی رشتهای آنرا توسط میان افزار Run آن نمایش دهیم. برای این منظور فایل Startup.cs را گشوده و امضای متد Configure را به نحو ذیل تغییر دهید:
همانطور که در قسمت قبل نیز عنوان شد، متد Configure دارای امضای ثابتی نیست و هر تعداد سرویسی را که نیاز است، میتوان در اینجا اضافه کرد. یک سری از سرویسها مانند IApplicationBuilder و IHostingEnvironment پیشتر توسط IoC Container توکار ASP.NET Core معرفی و ثبت شدهاند. به همین جهت، همینقدر که در اینجا ذکر شوند، کار میکنند و نیازی به تنظیمات اضافهتری ندارند. اما سرویس IMessagesService ما هنوز به این IoC Container معرفی نشدهاست. بنابراین نمیداند که چگونه باید این اینترفیس را وهله سازی کند.
در این حالت اگر برنامه را اجرا کنیم، به این خطا برخواهیم خورد:
برای رفع این مشکل، به متد ConfigureServices کلاس Startup مراجعه کرده و سیم کشیهای مرتبط را انجام میدهیم. در اینجا باید اعلام کنیم که «هر زمانیکه به IMessagesService رسیدی، یک وهلهی جدید (transient) از کلاس MessagesService را به صورت خودکار تولید کن و سپس در اختیار مصرف کننده قرار بده»:
در اینجا نحوهی ثبت یک سرویس را در IoC Containser توکار ASP.NET Core ملاحظه میکنید. تمام حالتهای طول عمری که در ابتدای بحث عنوان شدند، یک متد ویژهی خاص خود را در اینجا دارند. برای مثال حالت transient دارای متد ویژهی AddTransient است و همینطور برای سایر حالتها. این متدها به صورت جنریک تعریف شدهاند و آرگومان اول آنها، اینترفیس سرویس و آرگومان دوم، پیاده سازی آنها است (سیم کشی اینترفیس، به کلاس پیاده سازی کنندهی آن).
پس از اینکار، مجددا برنامه را اجرا کنید. اکنون این خروجی باید مشاهده شود:
و به این معنا است که اکنون IoC Cotanier توکار ASP.NET Core، میداند زمانیکه به IMessagesService رسید، چگونه باید آنرا وهله سازی کند.
چه سرویسهایی به صورت پیش فرض در IoC Container توکار ASP.NET Core ثبت شدهاند؟
در ابتدای متد ConfigureServices یک break point را قرار داده و برنامه را در حالت دیباگ اجرا کنید:
همانطور که ملاحظه میکنید، به صورت پیش فرض 16 سرویس در اینجا ثبت شدهاند که تاکنون با دو مورد از آنها کار کردهایم.
امکان تزریق وابستگیها در همه جا!
در مثال فوق، سرویس سفارشی خود را در متد Configure کلاس آغازین برنامه تزریق کردیم. نکتهی مهم اینجا است که برخلاف نگارشهای قبلی ASP.NET MVC (یعنی بدون نیاز به تنظیمات خاصی برای قسمتهای مختلف برنامه)، میتوان این تزریقها را در کنترلرها، در میان افزارها، در فیلترها در ... همه جا و تمام اجزای ASP.NET Core 1.0 انجام داد و دیگر اینبار نیازی نیست تا نکتهی ویژهی نحوهی تزریق وابستگیها در فیلترها یا کنترلرهای ASP.NET MVC را یافته و سپس اعمال کنید. تمام اینها از روز اول کار میکنند. همینقدر که کار ثبت سرویس خود را در متد ConfigureServices انجام دادید، این سرویس در سراسر اکوسیستم ASP.NET Core، قابل دسترسی است.
نیاز به تعویض IoC Container توکار ASP.NET Core
قابلیت تزریق وابستگیهای توکار ASP.NET Core صرفا جهت برآورده کردن نیازمندیهای اصلی آن طراحی شدهاست و نه بیشتر. بنابراین توسط آن قابلیتهای پیشرفتهای را که سایر IoC Containers ارائه میدهند، نخواهید یافت. برای مثال تعویض امکانات تزریق وابستگیهای توکار ASP.NET Core با StructureMap این مزایا را به همراه خواهد داشت:
• امکان ایجاد child/nested containers (پشتیبانی از سناریوهای چند مستاجری)
• پشتیبانی از Setter Injection
• امکان انتخاب سازندهای خاص (اگر چندین سازنده تعریف شده باشند)
• سیم کشی خودکار یا Conventional "Auto" Registration (برای مثال اتصال اینترفیس IName به کلاس Name به صورت خودکار و کاهش تعداد تعاریف ابتدای برنامه)
• پشتیبانی توکار از Lazy و Func
• امکان وهله سازی از نوعهای concrete (یا همان کلاسهای معمولی)
• پشتیبانی از مفاهیمی مانند Interception و AOP
• امکان اسکن اسمبلیهای مختلف جهت یافتن اینترفیسها و اتصال خودکار آنها (طراحیهای افزونه پذیر)
روش تعویض IoC Container توکار ASP.NET Core با StructureMap
جزئیات این جایگزین کردن را در مطلب «جایگزین کردن StructureMap با سیستم توکار تزریق وابستگیها در ASP.NET Core 1.0» میتوانید مطالعه کنید.
یا میتوانید از روش فوق استفاده کنید و یا اکنون قسمتی از پروژهی رسمی استراکچرمپ در آدرس https://github.com/structuremap/structuremap.dnx جهت کار با NET Core. طراحی شدهاست. برای کار با آن نیاز است این مراحل طی شوند:
الف) دریافت بستهی نیوگت StructureMap.Dnx
برای این منظور بر روی گره references کلیک راست کرده و گزینهی manage nuget packages را انتخاب کنید. سپس در برگهی browse آن، StructureMap.Dnx را جستجو کرده و نصب نمائید (تیک مربوط به انتخاب pre releases هم باید انتخاب شده باشد):
انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
ب) جایگزین کردن Container استراکچرمپ با Container توکار ASP.NET Core
پس از نصب بستهی StructureMap.Dnx، به کلاس آغازین برنامه مراجعه کرده و این تغییرات را اعمال کنید:
در اینجا ابتدا خروجی متد ConfigureServices، به IServiceProvider تغییر کردهاست تا استراکچرمپ این تامین کنندهی سرویسها را ارائه دهد. سپس Container مربوط به استراکچرمپ، وهله سازی شده و همانند روال متداول آن، یک سرویس و کلاس پیاده سازی کنندهی آن معرفی شدهاند (و یا هر تنظیم دیگری را که لازم بود باید در اینجا اضافه کنید). در پایان کار متد Configure آن و پس از این متد، نیاز است متدهای Populate فراخوانی شوند (اولی تعاریف را اضافه میکند و دومی کار تنظیمات را نهایی خواهد کرد).
سپس وهلهای از IServiceProvider، توسط استراکچرمپ تامین شده و بازگشت داده میشود تا بجای IoC Container توکار ASP.NET Core استفاده شود.
در این مثال چون در متد Scan، کار بررسی اسمبلی لایه سرویس برنامه با قراردادهای پیش فرض استراکچرمپ انجام شدهاست، دیگر نیازی به سطر تعریف config.For نیست. در اینجا هرگاه IName ایی یافت شد، به کلاس Name متصل میشود (name هر نامی میتواند باشد).
«بررسی مفاهیم معکوس سازی وابستگیها و ابزارهای مرتبط با آن»
«اصول طراحی SOLID»
«مطالعهی بیشتر»
تزریق وابستگیها (یا Dependency injection = DI) به معنای ارسال نمونهای/وهلهای از وابستگی (یک سرویس) به شیء وابستهی به آن (یک کلاینت) است. در فرآیند تزریق وابستگیها، یک کلاس، وهلههای کلاسهای دیگر مورد نیاز خودش را بجای وهله سازی مستقیم، از یک تزریق کننده دریافت میکند. بنابراین بجای نوشتن newها در کلاس جاری، آنها را به صورت وابستگیهایی در سازندهی کلاس تعریف میکنیم تا توسط یک IoC Container تامین شوند. در اینجا به فریم ورکهایی که کار وهله سازی این وابستگیها را انجام میدهند، IoC Container و یا DI container میگوییم (IoC = inversion of control ).
چندین نوع تزریق وابستگیها وجود دارند که دو حالت زیر، عمومیترین آنها است:
الف) تزریق در سازندهی کلاس: لیست وابستگیهای یک کلاس، به عنوان پارامترهای سازندهی آن ذکر میشوند.
ب) تزریق در خواص یا Setter injection: کلاینت خواصی get و set را به صورت public معرفی میکند و سپس IoC Container با وهله سازی آنها، وابستگیهای مورد نیاز را تامین خواهد کرد.
تزریق وابستگیها در ASP.NET Core
برخلاف نگارشهای قبلی ASP.NET، این نگارش جدید از ابتدا با دید پشتیبانی کامل از DI طراحی شدهاست و این مفهوم، در سراسر اجزای آن به صورت یکپارچهای پشتیبانی میشود. همچنین به همراه یک minimalistic DI container توکار نیز هست .
این IoC Container توکار از 4 حالت طول عمر ذیل پشتیبانی میکند:
- instance: در هربار نیاز به یک وابستگی خاص، تنها یک وهله از آن در اختیار مصرف کننده قرار میگیرد و در اینجا شما هستید که مسئول تعریف نحوهی وهله سازی این شیء خواهید بود (برای بار اول).
- transient: هربار که نیاز به وابستگی خاصی بود، یک وهلهی جدید از آن توسط IoC Container تولید و ارائه میشود.
- singleton: در این حالت تنها یک وهله از وابستگی درخواست شده در طول عمر برنامه تامین میشود.
- scoped: در طول عمر یک scope خاص، تنها یک وهله از وابستگی درخواست شده، در اختیار مصرف کنندهها قرار میگیرد. برای مثال مرسوم است که به ازای یک درخواست وب، تنها یک وهله از شیءایی خاص در اختیار تمام مصرف کنندههای آن قرار گیرد (single instance per web request).
طول عمر singleton، برای سرویسها و کلاسهای config مناسب هستند. به این ترتیب به کارآیی بالاتری خواهیم رسید و دیگر نیازی نخواهد بود تا هر بار این اطلاعات خوانده شوند. حالت scoped برای وهله سازی الگوی واحد کار و پیاده سازی تراکنشها مناسب است. برای مثال در طی یک درخواست وب، یک تراکنش باید صورت گیرد.
حالت scoped در حقیقت نوع خاصی از حالت transient است. در حالت transient صرفنظر از هر حالتی، هربار که وابستگی ویژهای درخواست شود، یک وهلهی جدید از آن تولید خواهد شد. اما در حالت scoped فقط یک وهلهی از وابستگی مورد نظر، در بین تمام اشیاء وابستهی به آن، در طول عمر آن scope تولید میشود.
بنابراین در برنامههای وب دو نوع singleton برای معرفی کلاسهای config و نوع scoped برای پیاده سازی تراکنشها و همچنین بالابردن کارآیی برنامه در طی یک درخواست وب (با عدم وهله سازی بیش از اندازهی از کلاسهای مختلف مورد نیاز)، بیشتر از همه به کار برده میشوند.
یک مثال کاربردی: بررسی نحوهی تزریق یک سرویس سفارشی به کمک IoC Container توکار ASP.NET Core
مثال جاری که بر اساس ASP.NET Core Web Application و با قالب خالی آن ایجاد شدهاست، دارای نام فرضی Core1RtmEmptyTest است. در همین پروژه بر روی پوشهی src، کلیک راست کرده و گزینهی Add new project را انتخاب کنید و سپس یک پروژهی جدید از نوع NET Core -> Class library. را به آن، با نام Core1RtmEmptyTest.Services اضافه کنید (تصویر فوق).
در ادامه کلاس نمونهی سرویس پیامها را به همراه اینترفیس آن، با محتوای زیر به آن اضافه کنید:
namespace Core1RtmEmptyTest.Services { public interface IMessagesService { string GetSiteName(); } public class MessagesService : IMessagesService { public string GetSiteName() { return "DNT"; } } }
انجام اینکار معادل است با افزودن یک سطر ذیل به فایل project.json پروژه:
{ "dependencies": { // same as before "Core1RtmEmptyTest.Services": "1.0.0-*" },
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IMessagesService messagesService)
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IMessagesService messagesService) { app.Run(async context => { var siteName = messagesService.GetSiteName(); await context.Response.WriteAsync($"Hello {siteName}"); }); }
System.InvalidOperationException No service for type 'Core1RtmEmptyTest.Services.IMessagesService' has been registered. at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType) at Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder) System.Exception Could not resolve a service of type 'Core1RtmEmptyTest.Services.IMessagesService' for the parameter 'messagesService' of method 'Configure' on type 'Core1RtmEmptyTest.Startup'. at Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder) at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<IMessagesService, MessagesService>(); }
پس از اینکار، مجددا برنامه را اجرا کنید. اکنون این خروجی باید مشاهده شود:
و به این معنا است که اکنون IoC Cotanier توکار ASP.NET Core، میداند زمانیکه به IMessagesService رسید، چگونه باید آنرا وهله سازی کند.
چه سرویسهایی به صورت پیش فرض در IoC Container توکار ASP.NET Core ثبت شدهاند؟
در ابتدای متد ConfigureServices یک break point را قرار داده و برنامه را در حالت دیباگ اجرا کنید:
همانطور که ملاحظه میکنید، به صورت پیش فرض 16 سرویس در اینجا ثبت شدهاند که تاکنون با دو مورد از آنها کار کردهایم.
امکان تزریق وابستگیها در همه جا!
در مثال فوق، سرویس سفارشی خود را در متد Configure کلاس آغازین برنامه تزریق کردیم. نکتهی مهم اینجا است که برخلاف نگارشهای قبلی ASP.NET MVC (یعنی بدون نیاز به تنظیمات خاصی برای قسمتهای مختلف برنامه)، میتوان این تزریقها را در کنترلرها، در میان افزارها، در فیلترها در ... همه جا و تمام اجزای ASP.NET Core 1.0 انجام داد و دیگر اینبار نیازی نیست تا نکتهی ویژهی نحوهی تزریق وابستگیها در فیلترها یا کنترلرهای ASP.NET MVC را یافته و سپس اعمال کنید. تمام اینها از روز اول کار میکنند. همینقدر که کار ثبت سرویس خود را در متد ConfigureServices انجام دادید، این سرویس در سراسر اکوسیستم ASP.NET Core، قابل دسترسی است.
نیاز به تعویض IoC Container توکار ASP.NET Core
قابلیت تزریق وابستگیهای توکار ASP.NET Core صرفا جهت برآورده کردن نیازمندیهای اصلی آن طراحی شدهاست و نه بیشتر. بنابراین توسط آن قابلیتهای پیشرفتهای را که سایر IoC Containers ارائه میدهند، نخواهید یافت. برای مثال تعویض امکانات تزریق وابستگیهای توکار ASP.NET Core با StructureMap این مزایا را به همراه خواهد داشت:
• امکان ایجاد child/nested containers (پشتیبانی از سناریوهای چند مستاجری)
• پشتیبانی از Setter Injection
• امکان انتخاب سازندهای خاص (اگر چندین سازنده تعریف شده باشند)
• سیم کشی خودکار یا Conventional "Auto" Registration (برای مثال اتصال اینترفیس IName به کلاس Name به صورت خودکار و کاهش تعداد تعاریف ابتدای برنامه)
• پشتیبانی توکار از Lazy و Func
• امکان وهله سازی از نوعهای concrete (یا همان کلاسهای معمولی)
• پشتیبانی از مفاهیمی مانند Interception و AOP
• امکان اسکن اسمبلیهای مختلف جهت یافتن اینترفیسها و اتصال خودکار آنها (طراحیهای افزونه پذیر)
روش تعویض IoC Container توکار ASP.NET Core با StructureMap
جزئیات این جایگزین کردن را در مطلب «جایگزین کردن StructureMap با سیستم توکار تزریق وابستگیها در ASP.NET Core 1.0» میتوانید مطالعه کنید.
یا میتوانید از روش فوق استفاده کنید و یا اکنون قسمتی از پروژهی رسمی استراکچرمپ در آدرس https://github.com/structuremap/structuremap.dnx جهت کار با NET Core. طراحی شدهاست. برای کار با آن نیاز است این مراحل طی شوند:
الف) دریافت بستهی نیوگت StructureMap.Dnx
برای این منظور بر روی گره references کلیک راست کرده و گزینهی manage nuget packages را انتخاب کنید. سپس در برگهی browse آن، StructureMap.Dnx را جستجو کرده و نصب نمائید (تیک مربوط به انتخاب pre releases هم باید انتخاب شده باشد):
انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
{ "dependencies": { // same as before "StructureMap.Dnx": "0.5.1-rc2-final" },
پس از نصب بستهی StructureMap.Dnx، به کلاس آغازین برنامه مراجعه کرده و این تغییرات را اعمال کنید:
public class Startup { public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddDirectoryBrowser(); var container = new Container(); container.Configure(config => { config.Scan(_ => { _.AssemblyContainingType<IMessagesService>(); _.WithDefaultConventions(); }); //config.For<IMessagesService>().Use<MessagesService>(); config.Populate(services); }); container.Populate(services); return container.GetInstance<IServiceProvider>(); }
سپس وهلهای از IServiceProvider، توسط استراکچرمپ تامین شده و بازگشت داده میشود تا بجای IoC Container توکار ASP.NET Core استفاده شود.
در این مثال چون در متد Scan، کار بررسی اسمبلی لایه سرویس برنامه با قراردادهای پیش فرض استراکچرمپ انجام شدهاست، دیگر نیازی به سطر تعریف config.For نیست. در اینجا هرگاه IName ایی یافت شد، به کلاس Name متصل میشود (name هر نامی میتواند باشد).
در مطالب قبلی (1 , 2) الگوی CQRS معرفی شد. همانطور که میبینید، پیاده سازی این الگو هرچند با فریمورک آمادهای همچون SimpleCQRS، دارای پیچیدگی زیادی است و باعث نوشتن حجم زیادی کد میشود.
فریمورک MediatR توسط توسعه دهنده کتابخانهی محبوب AutoMapper ایجاد شدهاست. این فریمورک پیاده سازی کاملی از الگوی طراحی Mediator در NET. است که داخل خود، تمام پیچیدگیهای پیاده سازی CQRS را Abstract کرده و با حداقل کد ممکن، میتوانید بهراحتی CQRS را داخل پروژهی خود پیاده سازی کنید.
در این سری مطالب به بررسی کامل الگوی CQRS و مزایا و معایب استفاده از آن میپردازیم و سپس با استفاده از کتابخانهی Mediatr، این الگو را داخل یک پروژه پیاده سازی میکنیم.
CQRS
در CQRS متدهای برنامه به 2 بخش Read و Write تقسیم میشوند. بخشهایی که State کلی برنامه ( شامل Database, Cookie, Session, LocalStorage, Memory و ... ) را تغییر میدهند، Command و بخشهایی که صرفا جنبه خواندنی دارند و وضعیت سیستم را تغییر نمیدهند مثل خواندن و نشان دادن اطلاعات از دیتابیس، Query مینامند.
* نکته : Naming Convention مورد استفاده برای Commandها به صورت دستوری است و کار Command در نام آن مشخص است؛ مثال : RegisterUser, SendForgottenPasswordEmail, PlaceOrder
* نکته : Naming Convention مورد استفاده برای Commandها به صورت دستوری است و کار Command در نام آن مشخص است؛ مثال : RegisterUser, SendForgottenPasswordEmail, PlaceOrder
مزایا:
1- شما میتوانید تکنولوژیهای مورد استفادهی در بخشهای Command و Query برنامهی خود را بهراحتی از هم جدا سازید. بهعنوان مثال Apache Cassandra در ذخیره سازی دادهها ( Write Side ) به عنوان یک دیتابیس قابل اعتنا شناخته میشود و از طرفی دیگر ElasticSearch بدلیل سرعت فوق العادهی خود، برای خواندن دادهها استفاده میشود. در این روش، دیتابیسها باید Sync باشند تا دادههای بهروز به کاربر نمایش داده شود که این موضوع چالشهای خود همچون Eventual Consistency و Strong Consistency را دارد که در مقالات بعدی آنها را بررسی خواهیم کرد.
2- در برنامههای معمول، اکثرا بخش Read Side، بیشتر از Write Side استفاده میشود و کاربران معمولا اطلاعات را دریافت و میبینند تا اینکه در آن تغییری ایجاد کنند؛ در این صورت شما میتوانید بخش Read برنامهی خود را Scale کرده و تعداد سیستم یا منابع بیشتری را به این قسمت از برنامهی خود اختصاص دهید ( Horizontal Scaling, Vertical Scaling ).
3- این جداسازی باعث تمرکز بیشتر شما بر روی قسمتهای مختلف برنامه میشود؛ بخشهایی که وضعیت سیستم را تغییر میدهند از بخشهایی که صرفا دادههایی را خوانده و نمایش میدهند، بطور کامل جدا شدهاند و بهراحتی قابلیت تغییر هرکدام از این بخشها را خواهید داشت.
3- این جداسازی باعث تمرکز بیشتر شما بر روی قسمتهای مختلف برنامه میشود؛ بخشهایی که وضعیت سیستم را تغییر میدهند از بخشهایی که صرفا دادههایی را خوانده و نمایش میدهند، بطور کامل جدا شدهاند و بهراحتی قابلیت تغییر هرکدام از این بخشها را خواهید داشت.
معایب : معمولا از معایب این الگو، از پیچیدگی پیاده سازی آن یاد میشود که در این آموزش با استفاده از Mediatr سعی بر از بین بردن این پیچیدگی را داریم.
Events
Eventها رویدادهایی هستند که خبر انجام کاری را که قبلا داخل سیستم انجامش به پایان رسیده است، به Consumerهای خود میدهند. بعنوان مثال میخواهیم بعد از ثبت نام موفق یک کاربر داخل سیستم، Notification و یا ایمیلی را به او ارسال کنیم. بعد از ثبت نام کاربر میتوانیم Event ای به نام UserRegistered را که شامل Username و Email کاربر در بدنه خود است، Raise کنیم.
Eventها میتوانند چندین Consumer داشته باشند؛ بنابراین میتوانیم یک EventHandler را برای UserRegistered بنویسیم که Email ارسال کند و EventHandler دیگری ایجاد کنیم که Notification ای را برای کاربر بفرستد.
Eventها میتوانند چندین Consumer داشته باشند؛ بنابراین میتوانیم یک EventHandler را برای UserRegistered بنویسیم که Email ارسال کند و EventHandler دیگری ایجاد کنیم که Notification ای را برای کاربر بفرستد.
* نکته : Naming Convention مورد استفاده برای Eventها به صورت گذشتهاست و خبر یک کار، که قبلا انجام شده است را میدهد؛ مثال : UserRegistered, OrderPlaced
Event Sourcing
Event Sourcing به معنای ذخیرهی تمام Eventهای رخ داده در برنامه داخل یک دیتابیس Append-Only است. در این نوع دیتابیسها فقط میتوانیم Eventهای جدیدی به آن اضافه کنیم و قادر به ویرایش و حذف Eventها نیستیم؛ چون منطق Event، کارهایی است که در گذشته اتفاق افتادهاند و ما قادر به تغییر چیزی که در گذشته رخ دادهاست، نیستیم.
مزیت Event Sourcing این است که State برنامه را در زمانهای مختلفی نگه داشتهایم و میتوانیم وضعیت سیستم را در تاریخی مشخص، پیدا کنیم و در صورت بهوجود آمدن مشکلی در سیستم، وضعیت آن را تا قبل از به مشکل خوردن، بررسی کنیم.
بعنوان مثال مبلغ یک حساب بانکی را در نظر بگیرید. یکی از راههای بهروز نگه داشتن این مبلغ بعد از هر تراکنش، در نظر گرفتن یک فیلد برای مبلغ و انجام عمل Update بعد از هر تراکنش بطور مستقیم برروی آن است. در این روش بهدلیل آپدیت کردن مستقیم این فیلد داخل دیتابیس، ما وضعیت قبلی (مبلغ قبلی) را از دست خواهیم داد و برای رسیدن به مبلغ قبلی مجبور به زدن چندین کوئری دیتابیسی و دریافت تراکنشهای قبلی و ... برای رسیدن به وضعیت قبلی سیستم هستیم.
روش دیگری وجود دارد که بجای بهروزرسانی مداوم state جاری، تمام Event هایی که در آن تراکنشی داخل سیستم رخ داده و این تراکنش State برنامه را تحت تاثیر خود قرار دادهاست، داخل یک دیتابیس اضافه نماییم. در این صورت بدلیل داشتن تمام رویدادهای اتفاق افتادهی در برنامه، میتوان وضعیت جاری سیستم را شبیه سازی و متوجه شد.
* در این سری آموزشی از دیتابیس Event Store برای پیاده سازی Event Sourcing استفاده خواهیم کرد.
مزیت Event Sourcing این است که State برنامه را در زمانهای مختلفی نگه داشتهایم و میتوانیم وضعیت سیستم را در تاریخی مشخص، پیدا کنیم و در صورت بهوجود آمدن مشکلی در سیستم، وضعیت آن را تا قبل از به مشکل خوردن، بررسی کنیم.
بعنوان مثال مبلغ یک حساب بانکی را در نظر بگیرید. یکی از راههای بهروز نگه داشتن این مبلغ بعد از هر تراکنش، در نظر گرفتن یک فیلد برای مبلغ و انجام عمل Update بعد از هر تراکنش بطور مستقیم برروی آن است. در این روش بهدلیل آپدیت کردن مستقیم این فیلد داخل دیتابیس، ما وضعیت قبلی (مبلغ قبلی) را از دست خواهیم داد و برای رسیدن به مبلغ قبلی مجبور به زدن چندین کوئری دیتابیسی و دریافت تراکنشهای قبلی و ... برای رسیدن به وضعیت قبلی سیستم هستیم.
روش دیگری وجود دارد که بجای بهروزرسانی مداوم state جاری، تمام Event هایی که در آن تراکنشی داخل سیستم رخ داده و این تراکنش State برنامه را تحت تاثیر خود قرار دادهاست، داخل یک دیتابیس اضافه نماییم. در این صورت بدلیل داشتن تمام رویدادهای اتفاق افتادهی در برنامه، میتوان وضعیت جاری سیستم را شبیه سازی و متوجه شد.
* در این سری آموزشی از دیتابیس Event Store برای پیاده سازی Event Sourcing استفاده خواهیم کرد.
در مقالهی بعدی، امکانات فریمورک MediatR را بررسی خواهیم کرد.
مطالب
WebStorage: قسمت دوم
در این مقاله قصد داریم نحوهی کدنویسی webstorage را با کتابخانههایی که در مقاله قبل معرفی کردیم بررسی کنیم.
ابتدا روش ذخیره سازی و بازیابی متداول آن را بررسی میکنیم که تنها توسط دو تابع صورت میگیرد. مطلب زیر برگرفته از w3Schools است:
دسترسی به شیء webstorage به صورت زیر امکان پذیر است:
window.localStorage window.sessionStorage
if(typeof(Storage) !== "undefined") { // Code for localStorage/sessionStorage. } else { // Sorry! No Web Storage support.. }
localStorage.setItem("lastname", "Smith"); //======================== localStorage.getItem("lastname");
var a=localStorage.lastname;
//ذخیره مقدار store.set('username', 'marcus') //بازیابی مقدار store.get('username') //حذف مقدار store.remove('username') //حذف تمامی مقادیر ذخیره شده store.clear() //ذخیره ساختار store.set('user', { name: 'marcus', likes: 'javascript' }) //بازیابی ساختار به شکل قبلی var user = store.get('user') alert(user.name + ' likes ' + user.likes) //تغییر مستقیم مقدار قبلی store.getAll().user.name == 'marcus' //بازخوانی تمام مقادیر ذخیر شده توسط یک حلقه store.forEach(function(key, val) { console.log(key, '==', val) })
همچنین بهتر هست از یک فلگ برای بررسی فعال بودن storage استفاده نمایید. به این دلیل که گاهی کاربرها از پنجرههای private استفاده میکنند که ردگیری اطلاعات آن ممکن نیست و موجب خطا میشود.
<script src="store.min.js"></script> <script> init() function init() { if (!store.enabled) { alert('Local storage is not supported by your browser. Please disable "Private Mode", or upgrade to a modern browser.') return } var user = store.get('user') // ... and so on ... } </script>
در صورتیکه بخشی از دادهها را توسط localstorage ذخیره نمایید و بخواهید از طریق storage به آن دسترسی داشته باشید، خروجی string خواهد بود؛ صرف نظر از اینکه شما عدد، شیء یا آرایهای را ذخیره کردهاید.
در صورتیکه ساختار JSON را ذخیره کرده باشید، میتوانید رشته برگردانده شده را با json.stringify و json.parse بازیابی و به روز رسانی کنید.
در حالت cross browser تهیهی یک sessionStorage امکان پذیر نیست. ولی میتوان به روش ذیل و تعیین یک زمان انقضاء آن را محدود کرد:
var storeWithExpiration = { // دریافت کلید و مقدار و زمان انقضا به میلی ثانیه set: function(key, val, exp) { //ایجاد زمان فعلی جهت ثبت تاریخ ایجاد store.set(key, { val:val, exp:exp, time:new Date().getTime() }) }, get: function(key) { var info = store.get(key) //در صورتی که کلید داده شده مقداری نداشته باشد نال را بر میگردانیم if (!info) { return null } //تاریخ فعلی را منهای تاریخ ثبت شده کرده و در صورتی که //از مقدار میلی ثاینه بیشتر باشد یعنی منقضی شده و نال بر میگرداند if (new Date().getTime() - info.time > info.exp) { return null } return info.val } } // استفاده عملی از کد بالا // استفاده از تایمر جهت نمایش واکشی دادهها قبل از نقضا و بعد از انقضا storeWithExpiration.set('foo', 'bar', 1000) setTimeout(function() { console.log(storeWithExpiration.get('foo')) }, 500) // -> "bar" setTimeout(function() { console.log(storeWithExpiration.get('foo')) }, 1500) // -> null
مورد بعدی استفاده از سورس cross-storage است. اگر به یاد داشته باشید گفتیم یکی از احتمالاتی که برای ما ایجاد مشکل میکند، ساب دومین هاست که ممکن است دسترسی ما به یک webstorage را از ساب دومین دیگر از ما بگیرد.
این کتابخانه به دو جز تقسیم شده است یکی هاب Hub و دیگری Client .
ابتدا نیاز است که هاب را آماده سازی و با ارائه یک الگو از آدرس وب، مجوز عملیات را دریافت کنیم. در صورتیکه این مرحله به فراموشی سپرده شود، انجام هر نوع عمل روی دیتاها در نظر گرفته نخواهد شد.
CrossStorageHub.init([ {origin: /\.example.com$/, allow: ['get']}, {origin: /:\/\/(www\.)?example.com$/, allow: ['get', 'set', 'del']} ]);
valid.example.com
ولی دامنه زیر را نامعتبر اعلام میکند:
invalid.example.com.malicious.com
{ 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE', 'Access-Control-Allow-Headers': 'X-Requested-With', 'Content-Security-Policy': "default-src 'unsafe-inline' *", 'X-Content-Security-Policy': "default-src 'unsafe-inline' *", 'X-WebKit-CSP': "default-src 'unsafe-inline' *", }
پس کار را بدین صورت آغاز میکنیم، یک فایل به نام hub.htm درست کنید و هاب را آماده سازید:
hub.htm
<script type="text/javascript" src="~/Scripts/cross-storage/hub.js"></script> <script> CrossStorageHub.init([ {origin: /.*localhost:300\d$/, allow: ['get', 'set', 'del']} ]); </script>
در فایل دیگر که کلاینت شناخته میشود باید فایل hub معرفی شود تا تنظیمات هاب خوانده شود:
var storage = new CrossStorageClient('http://localhost:3000/example/hub.html'); var setKeys = function () { return storage.set('key1', 'foo').then(function() { return storage.set('key2', 'bar'); }); };
نحوهی ذخیره سازی بدین شکل هم طبق مستندات صحیح است:
storage.onConnect().then(function() { return storage.set('key', {foo: 'bar'}); }).then(function() { return storage.set('expiringKey', 'foobar', 10000); });
برای خواندن دادههای ذخیره شده به نحوه زیر عمل میکنیم:
storage.onConnect().then(function() { return storage.get('key1'); }).then(function(res) { return storage.get('key1', 'key2', 'key3'); }).then(function(res) { // ... });
کد بالا نحوهی خواندن مقادیر را به شکلهای مختلفی نشان میدهد و مقدار بازگشتی آنها یک آرایه از مقادیر است؛ مگر اینکه تنها یک مقدار برگشت داده شود. مقدار بازگشتی در تابع بعدی به عنوان یک آرگومان در دسترس است. در صورتی که خطایی رخ دهد، قابلیت هندل آن نیز وجود دارد:
storage.onConnect() .then(function() { return storage.get('key1', 'key2'); }) .then(function(res) { console.log(res); // ['foo', 'bar'] })['catch'](function(err) { console.log(err); });
برای باقی مسائل چون به دست آوردن لیست کلیدهای ذخیره شده، حذف کلیدهای مشخص شده، پاکسازی کامل دادهها و ... به مستندات رجوع کنید.
در اینجا جهت سازگاری با مرورگرهای قدیمی خط زیر را به صفحه اضافه کنید:
<script src="https://s3.amazonaws.com/es6-promises/promise-1.0.0.min.js"></script>
ذخیرهی اطلاعات به شکل یونیکد، فضایی دو برابر کدهای اسکی میبرد و با توجه به محدود بودن حجم webstorage به 5 مگابایت ممکن است با کمبود فضا مواجه شوید. در صورتیکه قصد فشرده سازی اطلاعات را دارید میتوانید از کتابخانه lz-string استفاده کنید. ولی توجه به این نکته ضروری است که در صورت نیاز، عمل فشرده سازی را انجام دهید و همینطوری از آن استفاده نکنید.
آخرین موردی که بررسی میشود استفاده از IndexedDB API است که با استفاده از آن میتوان با webstorage همانند یک دیتابیس رفتار کرد و به سمت آن کوئری ارسال کرد.
var request = indexedDB.open("library"); request.onupgradeneeded = function() { // The database did not previously exist, so create object stores and indexes. var db = request.result; var store = db.createObjectStore("books", {keyPath: "isbn"}); var titleIndex = store.createIndex("by_title", "title", {unique: true}); var authorIndex = store.createIndex("by_author", "author"); // Populate with initial data. store.put({title: "Quarry Memories", author: "Fred", isbn: 123456}); store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567}); store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678}); }; request.onsuccess = function() { db = request.result; };
برای انجام عملیات خواندن و نوشتن باید از تراکنشها استفاده کرد:
var tx = db.transaction("books", "readwrite"); var store = tx.objectStore("books"); store.put({title: "Quarry Memories", author: "Fred", isbn: 123456}); store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567}); store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678}); tx.oncomplete = function() { // All requests have succeeded and the transaction has committed. };
var tx = db.transaction("books", "readonly"); var store = tx.objectStore("books"); var index = store.index("by_author"); var request = index.openCursor(IDBKeyRange.only("Fred")); request.onsuccess = function() { var cursor = request.result; if (cursor) { // Called for each matching record. report(cursor.value.isbn, cursor.value.title, cursor.value.author); cursor.continue(); } else { // No more matching records. report(null); } };
کدهای بالا همه در مستندات معرفی شده وجود دارند و ما پیشتر توضیح ابتدایی در مورد آن دادیم و برای کسب اطلاعات بیشتر میتوانید به همان مستندات معرفی شده رجوع کنید. برای idexedDB هم میتوانید از این منابع + + + استفاده کنید که خود w3c منبع فوق العادهتری است.
اشتراکها
سایت Learn Entity Framework Core
در الگوهایی که به عنوان واسط بین اپلیکیشن و دیتابیس تعریف میکنیم نام دو الگوی Repository و Unit of work به چشم میخورد. در این سایت بارها این مباحث به صورت گفتمان و مقالات تکرار شدهاند و میدانیم که این الگوها کمک شایانی برای بالا بردن کارآیی برنامه، عدم تکرار کد، قابلیت استفاده مجدد و راحتی کار برای آزمونهای واحد و چهارچوبهای تقلید میکنند.
Unit of Work یا الگوی کار در واقع یک الگو، جهت جمع آوری عملیات کار با دیتابیس است که همه عملیات را تحت یک تراکنش به سمت دیتابیس ارسال میکند تا مبحث Atomic بودن عملیات، به مرحله اجرا گذاشته شود. در صورتیکه یکی از عملیات با نقص یا خطایی روبرو شود، کل عملیات Roll back یا برگشت میخورد. از آنجا که دیتابیسهای معدودی چون Ravendb این مراحل را تا حدی پیاده سازی میکنند نباید از مونگو هم چنین انتظاری نداشته باشید. مونگو برخورد تراکنشی یا اتمیک ندارد؛ پس پیاده سازی الگوی واحد کاری تاثیری بر روی روند کاری آن ندارد. هر چند تعدادی مثال بدین شکل پیاده شدهاند، ولی در عمل حقیقی نیستند و تنها یک حرکت مشابه داشتهاند.
ولی الگوی repository برای پرهیز از تکرار کد، قابلیت به روزرسانی کد و همچنین عملیاتی چون آزمونهای واحد و چهارچوب تقلید به کار میرود. وابستگی بین اشیاء را کاهش داده و باعث ایجاد یک کد با دوامتر میگردد.
ابتدا قبل از هر چیزی نیاز است تا اتصالات یا ساخت کانکشن به سرور و همچنین دریافت دیتابیس مورد نظر را در قالب یک کلاس تعریف نماییم. نام آن را MongoDbContext میگذارم:
در حالت بالا شما میتوانید در سازنده کلاس اتصال را برقرار کرده و دیتابیس را دریافت نمایید و از متد GetCollection در سطوح بالاتر، نوع کالکشن درخواستی خود را اعلام کنید. اگر به خط اول کلاس دقت نمایید میبینید که ما از اینترفیسی به نام IMongoDbContext که شامل خطوط زیر میباشد استفاده کردیم و دلیل استفاده این است که اگر قرار باشد از کلاس کانتکست، در کلاسهای repository استفاده شود، ایجاد وابستگی میکند. چرا که معلوم نیست این کانتکست دقیقا چیست و از کجا آمده است و در آزمون واحد و همچنین تقلید دست ما را میبندد و الگوی repository را مردود اعلام میکند. پس از این حیث یک اینترفیس با محتوای زیر تولید کردهایم که از این پس از آن در کدها استفاده میکنیم و پر کردن این اینترفیسها را از طریق تزریق وابستگیها در حالت Constructor Injection که سادهترین نوع آن است انجام میدهیم:
در صورتیکه دوست دارید محتوایهای دیگری را چون کانکشن استرینگ و .. نیز در اینجا بگنجانید، به عهده خود شماست. تنها نیاز به تغییراتی کوچک است.
در مرحله بعد یک IMongoDbRepositry ساخته و محتوای آن را به شکل زیر پر میکنیم:
در اینجا کلاسها را از نوع جنریک تعریف میکنیم تا کاربر بتواند هر نوع کلاسی را که نیاز دارد، به سمت این مخزن ارسال کند. در پیاده سازی هم به شکل زیر آن را تعریف میکنیم:
همانطور که میبینید در ابتدا در سازنده از طریق یک کتابخانهی تزریق وابستگیها (که در اینجا من از Structure map استفاده کردهام) شیء ImongoDbContext را مقدار دهی میکنیم. الان اگر در اینجا بجای تعریف اینترفیس، از همان کلاس مستقیما استفاده میکردیم، بین دو لایه repository و context یک وابستگی ایجاد میشد. ولی در اینجا کانتکست میتواند هر چیزی باشد. بعد از آن به تعریف متد مورد نظر پرداختهایم. البته با توجه به اینکه این تنها یک مثال است، بنده تنها یکی از این متدها را به عنوان نمونه نشان دادهام و میتوانید فایلهای کامل آن را در انتهای مقاله دریافت نمایید. همانطور که مشاهده میکنیم، متدها به صورت غیرهمزمان نوشته شدهاند که باعث مقیاس پذیری برنامه میشوند و در اینجا از متدهای همزمان استفاده نکردهایم؛ چرا که افرادی که از دیتابیسهای غیر رابطهای استفاده میکنند، نیاز به مقیاس پذیری بالایی دارند. به همین دلیل نیاز چندانی به استفاده از متدهای همزمان دیده نمیشود. ولی خودتان در صورت تمایل میتوانید آنها را به اینترفیس اضافه کنید. در ضمن در کد بالا متد خصوصی را جهت دریافت کالکشن نوشتهایم تا دریافت کالکشن را در کدها، تا حدی خلاصهتر و شیواتر کنیم.
الگوی بالا در یک کنترلر به شرح زیر استفاده شده است:
در کد بالا رستورانهایی را که 400 نفر یا بیشتر ظرفیت پذیرایی دارند، واکشی کرده و در ویوو نشان میدهد. در اینجا الگوی repo، توسط تزریق وابستگیها ساخته شده و کانتکست آنها به همین شکل ساخته خواهد شد و در کل کنترلر، قابلیت استفاده را دارند.
MongoRepository.zip
Unit of Work یا الگوی کار در واقع یک الگو، جهت جمع آوری عملیات کار با دیتابیس است که همه عملیات را تحت یک تراکنش به سمت دیتابیس ارسال میکند تا مبحث Atomic بودن عملیات، به مرحله اجرا گذاشته شود. در صورتیکه یکی از عملیات با نقص یا خطایی روبرو شود، کل عملیات Roll back یا برگشت میخورد. از آنجا که دیتابیسهای معدودی چون Ravendb این مراحل را تا حدی پیاده سازی میکنند نباید از مونگو هم چنین انتظاری نداشته باشید. مونگو برخورد تراکنشی یا اتمیک ندارد؛ پس پیاده سازی الگوی واحد کاری تاثیری بر روی روند کاری آن ندارد. هر چند تعدادی مثال بدین شکل پیاده شدهاند، ولی در عمل حقیقی نیستند و تنها یک حرکت مشابه داشتهاند.
ولی الگوی repository برای پرهیز از تکرار کد، قابلیت به روزرسانی کد و همچنین عملیاتی چون آزمونهای واحد و چهارچوب تقلید به کار میرود. وابستگی بین اشیاء را کاهش داده و باعث ایجاد یک کد با دوامتر میگردد.
ابتدا قبل از هر چیزی نیاز است تا اتصالات یا ساخت کانکشن به سرور و همچنین دریافت دیتابیس مورد نظر را در قالب یک کلاس تعریف نماییم. نام آن را MongoDbContext میگذارم:
public class MongoDbContext : IMongoDbContext { public const string DatabaseName = "MongoDbTest"; private static readonly IMongoClient _client; private static readonly IMongoDatabase Database; static MongoDbContext() { _client = new MongoClient(); Database = _client.GetDatabase(DatabaseName); } public IMongoCollection<TEntity> GetCollection<TEntity>() { return Database.GetCollection<TEntity>(typeof(TEntity).Name.ToLower() + "s"); } }
public interface IMongoDbContext { IMongoCollection<TEntity> GetCollection<TEntity>(); }
در مرحله بعد یک IMongoDbRepositry ساخته و محتوای آن را به شکل زیر پر میکنیم:
public interface IMongoDbRepository { Task<List<TEntity>> GetMany<TEntity>(FilterDefinition<TEntity> filter) where TEntity : class, new(); }
public class MongoRepository : IMongoDbRepository { private IMongoDbContext _mongoDbContext ; public MongoRepository(IMongoDbContext mongoDbContext) { _mongoDbContext = mongoDbContext; } public async Task<List<TEntity>> GetMany<TEntity>(FilterDefinition<TEntity> filter) where TEntity : class, new() { var collection = GetCollection<TEntity>(); var entities = await collection.Find(filter).ToListAsync(); return entities; } private IMongoCollection<TEntity> GetCollection<TEntity>() { return _mongoDbContext.GetCollection<TEntity>(); } }
الگوی بالا در یک کنترلر به شرح زیر استفاده شده است:
public class HomeController : Controller { private IMongoDbRepository _mongoDbRepository; public HomeController(IMongoDbRepository mongoDbRepository) { this._mongoDbRepository = mongoDbRepository; } // GET: Home public async Task<ActionResult> Index() { var filter = Builders<Resturant>.Filter.Gte("Capacity", 400); var c =await _mongoDbRepository.GetMany<Resturant>(filter); return View(c); } }
MongoRepository.zip
برای ارسال پیامک به صورت ترتیبی از حلقه استفاده کنید. زمانیکه job اجرا شد، ابتدا لیست اشخاصی را که باید پیامک دریافت کنند از دیتابیس واکشی کرده و سپس در طی یک حلقه، به آنها پیام ارسال کنید.
نظرات مطالب
EF Code First #6
سلام آقای نصیری - میخواستم بدونم نحوه ایجاد Unique Constraint روی فیلدهای دیتابیس با روش Code First به چه شکلی است؟
دیتابیس من Sql Ce 4.0 SP1 هستش و از Entity Framework 5.0 هم استفاده میکنم.
ممنون.
با سلام؛
مشکلی که من دارم نمیدانم مربوط میشود به مدیریت حافظه یا موضوعی دیگر
من یک وب سایت کوچک دارم که با تکنولوژیهای زیر ایجاد شده:
ASP.Net MVC 4, Entity Framework 4 , SQL CE
آن را بر روی یک ویندوز سرور 2012 نسخه دیتاسنتر نصب کردم
سرور : 2GB Ram و CPU Dual Core 1.8
غیر از این سایت هیچ سایت دیگری بر روی این سرور میزبانی نشده است.
صفحات با سرعت نسبتاً خوبی باز میشوند، اما به هر شکلی iis را تنظیم میکنم، اگر پس از 2 یا 3 دقیقه درخواستی به سمت سرور ارسال نگردد، برنامه از حافظه خارج میشود. اگر درخواستی برای مشاهده صفحه به سرور ارسال شود 15 تا 20 ثانیه طول میکشد تا دوباره کامپایل انجام شود و صفحه درخواستی نمایش یابد.
تصویر تنظیمات Application Pool
پ.ن: لطفاً اگر امکان دارد بهترین تنظیمات را برای سروری که فقط به یک سایت میخواهد سرویس دهد عنوان کنید.