مسیرراه‌ها
SQL Server
آخرین تاریخ بروزرسانی 93/10/21


SQL Server 2005

SQL Server 2008

SQL Server 2012

SQL Serve 2014


مطالب
سیستم‌های توزیع شده در NET. - بخش هفتم- معرفی Apache Kafka
سرچشمه Kafka از LinkedIn آغاز و سپس در سال 2011 توسط Apache بصورت open source ارائه شد. هدف آن ارائه یک بستر جریان داده‌ای توزیع شده‌است که اساس آن، Publish-Subscribe می‌باشد . سادگی اضافه کردن قابلیت‌های مقیاس پذیری افقی، تحمل خطا و افزایش کارآیی توسط این بستر باعث شده‌است که هزاران شرکت از آن بعنوان بستر ارتباطی قسمتهای مختلف سیستمها و زیرسیستمهای خود استفاده کنند.
همانطور که گفته شد وظیفه و هدف اصلی Apache Kafka، ارائه یک بستر برای مدیریت و کنترل جریان‌های اطلاعاتی با کارآیی بسیار بالا، در سیستم‌ها و زیرسیستمهای مختلف است. یعنی شما می‌توانید با ایجاد کردن یک Pipeline برای جریان اطلاعات خود، وابستگی مستقیم سیستمها و زیرسیستمها را از بین ببرید؛ آن هم بصورتی که بروز مشکلی در هر قسمت، کمترین میزان تاثیر را در سایر قسمتها داشته باشد.
فرض کنید شما تعداد زیادی سیستم و زیرسیستم مختلف را داشته باشید که هر کدام از آنها نیازمند ارتباط با برخی از قسمتهای دیگر است. در این صورت شما دو راه دارید: اول اینکه در هر قسمت سرویس‌هایی را برای ارتباط با سایر قسمت‌ها پیاده سازی کنید و هر قسمت بصورت مستقیم با سایر قسمتها در ارتباط باشد.

مشخصا کنترل و مدیریت جریان اطلاعاتی در این پیاده سازی کار بسیار دشواری است. تغییر هر قسمت، تاثیر مستقیمی بر روی سایر قسمتها دارد و در صورتی که هریک از قسمتها با مشکلی روبرو شوند، سایر قسمتهای مرتبط نیز با مشکل روبرو می‌شوند. این مشکل زمانی بسیار نمایان می‌شود که در معماری‌هایی مانند میکروسرویس، بدلیل بالا رفتن تعداد زیرسیستم‌ها و ارتباطات آنها، مدیریت این ارتباطات کار بسیار دشوار، پرهزینه و پیچیده‌ای می‌شود.
روش Apache Kafka برای رفع مشکل فوق به این صورت است که Kafka با بر عهده گرفتن مدیریت ارتباطات و جریان داده‌ای قسمتهای مختلف، به شما کمک می‌کند تا تیم پیاده سازی، تنها تمرکزشان را بر روی Businessی که می‌خواهند پیاده سازی کنند، قرار دهند. با این روش می‌توانیم به راحتی سیستمهایی را پیاده سازی کنیم که از نظر ارتباطی در حالت معمول، پیچیده یا بسیار پیچیده‌اند.

همانطور که می‌بینید دیگر نیازی نیست تا قسمتهای مختلف بصورت مستقیم با یکدیگر در ارتباط باشند؛ تمامی ارتباطات از طریق Kafka انجام می‌شود. تغییر یک قسمت، تاثیر زیادی بر روی سایر قسمتها ندارد. از دسترس خارج شدن یا بروز هر گونه مشکلی در یک قسمت، بر روی کل سیستم تاثیر زیادی ندارد. پیامهای مربوط به یک قسمت تا زمانی که پردازش نشده‌اند از بین نمی‌روند. پس سیستمها می‌توانند در حالت Offline نیز به کار خود ادامه دهند. شما می‌توانید  در این روش تمامی قسمتهای  سیستم را بصورت یک Cluster پیاده سازی کنید. بنابراین احتمال از دسترس خارج شدن هر قسمت به کمترین میزان می‌رسد. اما حتی درصورتی که یک قسمت بصورت موقت از دسترس خارج شود، پیامهای مرتبط با آن قسمت تا زمانی که دوباره به جریان پردازش بازگردد، از بین نمی‌روند. پس از اضافه شدن قسمت از دسترس خارج شده، بلافاصله تمامی پیامهای مرتبط با آن قسمت برایش ارسال می‌شوند. برای بالا رفتن میزان کارآیی و تحمل خطا، به راحتی می‌توانید خود Kafka را نیز بصورت یک Cluster پیاده سازی کنید و با بالا رفتن تعداد درخواست، در صورت نیاز می‌توانید عملیات مقیاس پذیری افقی را به راحت‌ترین روش ممکن انجام دهید.

نمایی از معماری کلی Apache Kafka: 


برای شروع به آموزش Apache Kafka بهتر است ابتدا با مفاهیم و اصطلاحات آن آشنا شویم:

Producer:
  ارسال کننده پیام. Application، سیستم یا زیرسیستمی که عملیات Publish پیام را برای Topic خاص از Kafka Server انجام می‌دهد.

Consumer:
دریافت کننده پیام. Application، سیستم یا زیرسیستمی که بر روی یک یا چند Topic خاص، Subscribe کرده‌است (همچنین هر Consumer می‌تواند روی یک یا چند Partition از یک Topic خاص نیز Subscribe کند).

Consumer Group:
 گروهی از Consumer‌ها می‌باشند که با یک group.id، مشخص شده‌اند. عموما این گروه شامل یک Replicate از یک Application است؛ مانند گروه ارسال کننده ایمیل (یک زیر سیستم ارسال کننده ایمیل که چندین بار در سرور‌های مختلف اجرا شده است). Kafka این ضمانت را به ما می‌دهد که هر پیام ذخیره شده در یک Topic، برای تمامی Consumer Group‌های مرتبط ارسال شود؛ اما در هر Consumer Group، تنها یک دریافت کننده داشته باشد. یعنی هر پیام در هر Consumer Group، تنها توسط یک Consumer دریافت می‌شود.

Broker :
 قسمتی که تمامی پیامها را  از Producer دریافت می‌کند، سپس آن‌ها را در Log مربوط به Topic مشخص شده ذخیره می‌کند و پس از آن، پیام ذخیره شده را برای تمامی Consumerهای مرتبط ارسال می‌کند.

Topic: 
یک دسته بندی برای ذخیره کردن پیامهای Publish شده می‌باشد. Topicها همانند مفهوم Tableها در SQL Server می‌باشند. همانطور که می‌دانید هر Table از قبل تعریف شده‌است. یک کاربر با ارسال یک درخواست ثبت، داده‌ها را در آن ذخیره می‌کند و سپس گروهی از کاربران از داده‌های ثبت شده استفاده می‌کنند. در مفهموم Topic نیز ابتدا ما Topic مورد نظر را با خصوصیاتی که باید داشته باشد تعریف می‌کنیم (البته می‌توان بصورت Dynamic نیز آن را تعریف کرد؛ اما این روش توصیه نمی‌شود). سپس Producer پیام مربوطه را به همراه نام Topic برای Broker ارسال می‌کند. Broker پیام را در Partition مربوطه از Topic ذخیره می‌کند و سپس پیام برای تمامی Consumer‌های مربوطه ارسال می‌شود.

Partition:
یکی از تفاوتهای بسیار مهم Kafka با سایر Message broker‌ها مانند RabitMQ که باعث بالارفتن کارآیی آن نیز شده‌است، قابلیت Partition در Topic‌ها می‌باشد. در واقع هر Topic از یک یا چندین Partition برای ذخیره داده‌ها استفاده می‌کند. تعریف درست تعداد Partition‌ها در یک Topic، تاثیر مستقیمی بر درجه همزمانی و کارآیی در آن Topic و کل سیستم دارد. در Kafka تمامی پیامها به همان ترتیبی که وارد شده‌اند، در Partition‌های یک Topic ذخیره می‌شوند و به همان ترتیب نیز برای Consumer‌ها ارسال می‌شوند.
بطور مثال فرض کنید تعداد Partition‌های یک Topic با نام DepartmentMessage یک می‌باشد (از این Topic برای ذخیره پیامهای واحدهای مختلف یک سازمان استفاده می‌شود). در این صورت تمامی پیامهای دریافتی تنها در یک Partition ذخیره می‌شوند.

هر خانه از یک Partition، توسط یک شناسه از نوع int و با نام offset در دسترس است. تمامی پیامهای جدید ارسالی توسط Producer با offset ی بزرگتر از offset موجود در این Partition ذخیره می‌شوند؛ یعنی در انتهای آن قرار می‌گیرند. در مثال فوق در صورت دریافت پیام جدید، offset آن با عدد 10 مقداردهی می‌شود. همچنین عملیات خواندن نیز از کوچکترین offsetی که هنوز  مقدار آن توسط Consumer‌ها خوانده نشده‌است، انجام می‌شود. همانطور که مشخص است، بدلیل اینکه تعداد Partitionهای این مثال عدد یک می‌باشد، تمامی درخواست‌های Producer‌ها در یک Partition قرار می‌گیرند و تمامی Consumer‌ها نیز از طریق یک Partition به پیامها دسترسی دارند؛ یعنی در صورت بالا بردن تعداد Producer‌ها یا Consumer‌ها، کارآیی بالا نمی‌رود. البته با اینکه کنترل مقدار اولیه offset برای شروع یک Consumer به دست خود Consumer و Zookeeper است، اما در اکثر موارد تمامی Consumer‌های یک Topic باید از یک نقطه، شروع به خواندن داده‌ها کنند. در این حالت تا زمانیکه پیام با offset 1، توسط Consumerی خوانده نشود، هیچ Consumerی نمی‌تواند پیام شماره 2 را بخواند. استفاده کردن از یک Partition بیشتر زمانی کاربرد دارد که بخواهید تمامی پیامهایتان، واقعا در یک صف قرار بگیرند.
حال فرض کنید در سازمان شما سه واحد اداری، مالی و آموزش وجود دارد. در این صورت بدلیل اینکه تمامی پیامها در یک Partition ذخیره می‌شوند، تا زمانی که یک واحد تمامی پیامهای مرتبط با خود را از ابتدای Partition نخوانده‌است، دیگر واحدها نمی‌توانند به پیامهای مرتبط با خود دسترسی داشته باشند. پس در این صورت ما می‌توانیم تعداد Partition‌های این Topic را عدد 3 درنظر بگیریم؛ بصورتی که پیامهای مرتبط با هر واحد در یک Partition جدا قرار بگیرد.

در این روش هر Producer زمانیکه پیامی را برای این Topic ارسال می‌کند، یک Key نیز برای آن مشخص می‌کند و این Key نشان دهنده این است که پیام جدید باید در کدام Partition ذخیره شود. یعنی بصورت همزمان می‌توانید در هر سه Partition، پیامهایتان را ذخیره کنید؛ بصورتی که بطور مثال تمامی پیامهای مربوط به واحد اداری، در Partition 0  و تمامی پیامهای مربوط به واحد مالی، در Partition 1 و واحد آموزش، در Partition 2 ذخیره شوند و همچنین عملیات خواندن از این Topic نیز می‌تواند بصورت همزمان در واحدهای مختلف انجام شود.
باید در تعریف تعداد Partition‌های یک Topic این نکته را در نظر بگیرید که این تعداد کاملا به نیازمندی شما و کارآیی که شما مد نظر دارید، بستگی دارد. تعداد این Partition‌ها حتی می‌تواند به تعداد User‌های یک سیستم نیز تعریف شود. علاوه بر آن باید بدانید که هر Partition در هر زمان تنها توسط یک Primary Broker می‌تواند در دسترس سایر قسمتها قرار بگیرد و تمامی عملیات خواندن و نوشتن در Partition توسط این Kafka Server انجام می‌شود و در صورتیکه به هر دلیلی این سرور از دسترس خارج شود، مدیریت این Partition به سرور‌های دیگر داده می‌شود.

Cluster:
مجموعه ای از Brokerها می‌باشد که بصورت یک Cluster اجرا شده‌اند. این کار باعث بالا رفتن کارآیی و تحمل خطا می‌شود.

Primary Broker:
یک Kafka Server که مسئول خواندن و نوشتن در یک Partition است. در یک Cluster هر Partition در یک زمان تنها یک Primary Broker دارد. این Primary Broker همزمان می‌تواند برای Partition‌های دیگر نقش Replicas Broker را بازی کند. انتخاب یک Primary Broker برای یک Partition توسط ZooKeeper انجام می‌شود.

Replicas Brokers :
Kafka Serverهایی که شامل یک کپی از Partition می‌باشند. عملیات خواندن و نوشتن در Partition توسط Primary انجام می‌شود. در صورتیکه Primary از دسترس خارج شود، ZooKeeper یکی از Replicas Broker‌ها را بعنوان Primary در نظر می‌گیرد. همچنین این نکته را باید در نظر بگیرید که هر Replicate همزمان می‌تواند Primary پارتیشن‌های دیگر باشد.

Replication Factor :
این خصوصیت احتمال از دست دادن داده‌های یک Topic را به حداقل می‌رساند؛ به این صورت که هر پیام از یک Topic، در چندین سرور مختلف که تعداد آنها توسط این خصوصیت مشخص می‌شود، نگهداری می‌شود.

Apache ZooKeeper :
Kafka هیچ Stateی را نگه نمی‌دارد (اصطلاحا stateless می‌باشد). برای ذخیره کردن و مدیریت تمامی Stateها از جمله اینکه در حال حاضر Primary Broker برای یک Partition چه سروری است، یا اینکه پیامهای یک Partition تا کدام offset توسط Consumer‌ها خوانده شده‌اند یا اینکه کدام Consumer در حال حاضر در یک Consumer Group مسئول یک Partition می‌باشد، توسط Apache Zookeeper انجام می‌شود.

ضمانت‌هایی که Kafka می‌دهد:
  1. تمامی پیامهای دریافتی در یک Partition از یک Topic، به همان ترتیبی که دریافت می‌شوند ذخیره می‌شوند.
  2. Consumer‌ها تمامی پیامها را در یک Partition به همان ترتیبی که ذخیره شده‌اند، دریافت می‌کنند.
  3. در یک Topic با Replication Factorی با مقدار N، درجه تحمل خطا N - 1 می‌باشد.

تا اینجا با اهداف، مفاهیم و اصطلاحات Apache Kafka آشنا شدیم. در بخش بعد به راه اندازی قسمتهای مختلف آن در Ubuntu می‌پردازیم و می‌بینیم که به چه صورت می‌توان به راحتی یک Cluster از سرورهای Kafka را ایجاد کرد.
نظرات مطالب
4# آموزش سیستم مدیریت کد Git : نصب و پیکر‌‏بندی
اگر شما به سایت‌های مدیریت کدی نظیر github مراجعه کنید و تعداد کاربران و یا پروژه‌های قرار گرفته بر روی آن‌ها را در نظر بگیرید متوجه محبوبیت سیستم مدیریت کد git خواهید شد در مورد تفاوت‌های سیستم‌های CVS و DVCS در مقاله اول توضیحاتی داده شد و در مقاله بعد درباره نحوه ذخیره سازی اطلاعات که باعث افزایش سرعت چشمگیر در عملیات check-in و check-out می‌شود
در ضمن در git و در همه سیستم‌های مدیریت کد امکان دستیابی به کد‌های قبل وجود دارد و به طور کلی این یکی از اهداف سیستم‌های مدریت کد است.
خود من هم یک برنامه نویس دات نت هستم اما دلیلی ندارد که مجبور باشیم هر آنچه که مایکروسافت ساخته را استفاده کنیم
من با هر دو سیستم TFS و Git کار کردم و به شخصه استفاده و راه اندازی آن را از TFS ساده‌تر می‌بینم چون تنها یکی از کاربردهای TFS مدیریت کد است بنابراین شما به طور نسبی با سیستم پیچیده‌تری سرو کار خواهید داشت.
اما در نهایت نیاز شما به  معماری مورد استفاده در مدیریت کد‌های خود تعیین کننده است
اگر یک سیستم مدیریت کد توزیع شده لازم دارید بهترین انتخاب git است
موفق باشید
مطالب
آشنایی با TransactionScope
TransactionScope روشی برای پیاده سازی تراکنش در .Net است که برای اولین بار در دات نت 2 معرفی شده است. روش پیاده سازی آن بسیار ساده است و همین سادگی و راحتی کار با اون باعث شده است که خیلی از برنامه نویس‌ها رو متمایل به خودش کنه.  در ادامه به روش استفاده و مزایا و معایب این روش برای پیاده سازی تراکنش‌ها می‌پردازیم.
این روش دارای تمام خواص یک تراکنش است(اصطلاحا به این خواص  ACID Properties گفته میشود)
1-Atomic : به این معناست که تمام دستورات بین بلاک (دستورات SQL و سایر عملیات) باید به صورت عملیات اتمی  کار کنند. یعنی یا تمام عملیات موفقیت آمیز است یا همه با شکست روبرو می‌شوند.
2 - Consistent: به این معناست که اگر تراکنش موفقیت آمیز بود پایگاه داده  باید در شروع تراکنش بعدی تغییرات لازم رو انجام داده باشد و در غیر این صورت پایگاه داده باید به حالت قبل از شروع تراکنش برگردد.
3- Isolated: اگر چند تا تراکنش هم زمان شروع شوند اجرای هیچ کدوم از اون‌ها نباید بر اجرای بقیه تاثیر بزاره.
4- Durable: یعنی تغییرات حاصل شده بعد از اتمام تراکنش باید دائمی باشند.

روش کار به این صورت است تمام کارهایی که قصد داریم در طی یک تراکنش انجام شوند باید در یک بلاک قرار بگیرند و تا زمانی که متد  Complete فراخوانی شود. در این بلاک شما هر عملیاتی رو که به عنوان جزئی از تراکنش می‌دونید قرار بدید. در صورتی که کنترل اجرا به فراخوانی دستور Complete برسه تمام موارد قبل از این دستور Commit  می‌شوند در غیر این صورت RollBack.
به مثال زیر دقت کنید.
ابتدا به پروژه مربوطه باید اسمبلی System.Transaction رو اضافه کنید.
using ( TransactionScope scope = new TransactionScope() )
  {
       //Statement1
       //Statement2
       //Statement3
         scope.Complete();
    }
تمام دستوراتی که در این بلاک نوشته شوند بعد از فراخوانی دستور scope.Complete اصطلاحا Commit می‌شوند. اگر به هر دلیلی فراخوانی دستورات به scope.Complete  نرسد عمل RollBack انجام می‌شود. در نتیجه برای این که عمل RollBack رو انجام دهید بهتره که قبل از دستور  Complete یک Exception رو پرتاب کنید که باعث فراخوانی Dispose می‌شود. کد زیر
using ( System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope() )
            {
                if ( result == 0 )
                {
                    throw new ApplicationException();
                }
                scope.Complete();
            }
نکته حائز اهمیت این است که اگر در هنگام اجرای برنامه به این روش به خطای
MSDTC on server {} is unavailable
برخوردید باید سرویس MSDTC رو Start کنید.برای این کار باید سرویس Distributed Transaction Coordinator رو از لیست سرویس‌های ویندوز پیدا کنید و بر روی اون راست کلیک کرده و دکمه Start رو بزنید.
نکته 1: میزان Timeout در این تراکنش‌ها چه قدر است؟
برای بدست آوردن مقدار Timeout در این گونه تراکنش‌ها می‌توانید از کلاس TransactionManager استفاده کنید. به صورت زیر :

var defaultTimeout = TransactionManager.DefaultTimeout
var maxTimeout = TransactionManager.MaximumTimeout
مقدار پیش فرض برای DefaultTimeout یک دقیقه است و برای MaximumTimeout ده دقیقه است. البته خاصیت‌های بالا به صورت فقط خواندنی هستند و نمی‌تونید از این راه مقدار Timeout هر تراکنش را افزایش یا کاهش دهیم. برای این کار بهتره از روش زیر استفاده کنیم.
TransactionOptions option = new TransactionOptions(); 
 option.Timeout = TimeSpan.MaxValue;

  using ( System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope(TransactionScopeOption.Required ,option) ) { scope.Complete(); }
توضیح درباره انواع TransactionScopeOption
1 - Required  : یعنی نیاز به تراکنش وجود دارد. در صورتی که تراکنش در یک تراکنش دیگر شروع شود نیاز به ساختن تراکنش جدید نیست و از همان تراکنش قبلی برای این کار استفاده می‌شود.
2 - RequiresNew: در هر صورت برای محدوده یک تراکنش تولید می‌شود.
3- Suppress : به عنوان محدوه تراکنش در نظر گرفته نمی‌شود.
using(TransactionScope scope1 = new TransactionScope())
{
     try
     {          
          using(TransactionScope scope2 = new  TransactionScope(TransactionScopeOption.Suppress))
          {
               //به دلیل استفاده از Suppress این محدوده خارج از تراکنش محسوب می‌شود
          }
          //شروع محدوده تراکنش
   }
     catch
     {}
   //Rest of scope1
}
مزایا استفاده از این روش
1-این روش از تراکنش‌های توزیع شده پشتیبانی می‌کند . یعنی می‌تونید از چند تا منبع داده استفاده کنید یا می‌تونید از یک تراکنش چند تا Connection به یک منبع داده باز کنید.(استفاده از چند تاconnection در طی یک تراکنش)
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
    string strCmd = "SQL to Execute";
    conn = new SqlClient.SqlConnection("Connection to DB1");
    conn.Open()
    objCmd = new SqlClient.SqlCommand(strCmd, conn);
    objCmd.ExecuteNonQuery();
    string strCmd2 = "SQL to Execute";
    conn2 = new SqlClient.SqlConnection("Connection to DB2");
    conn2.Open()
    objCmd2 = new SqlClient.SqlCommand(strCmd2, conn2);
    objCmd2.ExecuteNonQuery();
}
2- پیاده سازی این روش  واقعا راحت است .
3- با DataProvider‌های متفاوت نظیر Oracle و OleDb و ODBC سازگار است.
4- از تراکنش‌های تو در تو به خوبی پشتیبانی میکنه(Nested Transaction)
using(TransactionScope scope1 = new TransactionScope()) 
{ 
     using(TransactionScope scope2 = new  TransactionScope(TransactionScopeOption.Required)) 
     {
     ...
     } 

     using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew)) 
     {
     ...
     } 

     using(TransactionScope scope4 = new   TransactionScope(TransactionScopeOption.Suppress)) 
    {
     ...
    } 
}
5- به خوبی توسط سرویس‌های WCF پشتیبانی می‌شود و برای سیستم‌های SOA مبتنی بر WCF مناسب است.معایب :
*استفاده از این روش در سیستم هایی که تعداد کاربران آنلاین آن زیاد است و هم چنین تعداد تراکنش‌های موجود نیز در سطح سیستم خیلی زیاد باشه مناسب نیست.
*تراکنش‌های استفاده شده از این روش کند هستند.(مخصوصا که تراکنش در سطح دیتابیس با تعداد و حجم داده زیاد باشه)
امکان تغییر IsolationLevel در طی انجام یک تراکنش امکان پذیر نیست.
(به شخصه مواد * رو در سطح یک پروژه با شرایط کاربران  و حجم داده زیاد تست کردم و نتیجه مطلوب حاصل نشد)

موفق باشید.
مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت دوم - الگوی Service Locator
در قسمت قبل برای دریافت وهله‌ای از سرویس TestService، به صورت ()<serviceProvider.GetService<ITestService عمل کردیم. این روش در اصل الگوی Service Locator نام دارد که جزئیات بیشتری از آن‌را در این قسمت بررسی خواهیم کرد.


قلب سیستم تزریق وابستگی‌های NET Core. اینترفیس IServiceProvider است

IServiceProvider که اساس IoC Container برنامه‌های مبتنی بر NET Core. را تشکیل می‌دهد، در اسمبلی System.ComponentModel و در فضای نام System تعریف شده‌است:
namespace System
{
    public interface IServiceProvider
    {
        object GetService(Type serviceType);
    }
}
زمانیکه به کمک IServiceCollection، تمام اینترفیس‌ها و کلاس‌های خود را به IoC Container معرفی کردیم، مرحله‌ی بعدی، فراهم آوردن روشی برای دریافت وهله‌ای از این سرویس‌ها توسط متد GetService است.
استفاده‌ی مستقیم از اینترفیس IServiceProvider برای دسترسی به وهله‌های سرویس‌ها، اصطلاحا الگوی Service Locator نامیده می‌شود و باید تا حد ممکن از آن پرهیز کرد؛ چون وابستگی مستقیمی از IoC Container را درون کدهای ما قرار می‌دهد و به این ترتیب یک مرحله، نوشتن آزمون‌های واحد برای آن‌را مشکل‌تر می‌کند؛ چون زمان وهله سازی از یک سرویس، دقیقا مشخص نیست به چه وابستگی‌هایی نیاز دارد. به همین جهت همیشه باید با روش تزریق وابستگی‌ها در سازنده‌ی کلاس شروع کرد و اگر به هر دلیلی این روش مهیا نبود و توسط سیستم تزریق وابستگی‌های جاری شناسایی و یا پشتیبانی نمی‌شد (مانند تزریق وابستگی در سازنده‌های Attributes)، آنگاه می‌توان به الگوی Service Locator مراجعه کرد.
برای مثال در اکثر قسمت‌های برنامه‌های ASP.NET Core امکان تزریق وابستگی‌ها در سازنده‌ی کنترلرها، میان افزارها و سایر اجزای آن وجود دارد و در این حالات نیازی به مراجعه‌ی مستقیم به IServiceProvider برای دریافت وهله‌های سرویس‌های مورد نیاز نیست. به عبارتی نگرانی در مورد IServiceProvider بهتر است مشکل IoC Container باشد و نه ما.

در مثال زیر، روش استفاده‌ی از IServiceProvider را جهت انجام تزریق وابستگی‌ها (یا به عبارتی بهتر، روش دسترسی به وهله‌های وابستگی‌ها) را مشاهده می‌کنید:
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace CoreIocServices
{
    public interface IProductService
    {
        void Delete(int id);
    }

    public class ProductService : IProductService
    {
        private readonly ITestService _testService;
        private readonly ILogger<ProductService> _logger;

        public ProductService(IServiceProvider serviceProvider)
        {
            _testService = serviceProvider.GetRequiredService<ITestService>();
            _logger = serviceProvider.GetService<ILogger<ProductService>>() ?? NullLogger<ProductService>.Instance;
        }

        public void Delete(int id)
        {
            _testService.Run();
            _logger.LogInformation($"Deleted a product with id = {id}");
        }
    }
}
این روش یا کار مستقیم با Service locator، هر چند کار می‌کند، اما روشی است که باید تا حد ممکن از آن پرهیز کنید؛ زیرا:
- با نگاه کردن به امضای سازنده‌ی این سرویس مشخص نیست که دقیقا از چه وابستگی‌هایی استفاده می‌کند. اینکار نوشتن آزمون‌های واحد آن‌را مشکل می‌کند.
- این سرویس یک وابستگی اضافه‌تر را به نام IServiceProvider، نیز پیدا کرده‌است که اگر از روش متداول تزریق وابستگی‌ها در سازنده‌ی کلاس استفاده می‌شد، نیازی به ذکر آن نبود.
- پیاده سازی Dispose Pattern در این حالت مشکل‌تر است و در قسمتی دیگر بررسی خواهد شد.



تفاوت‌های بین متدهای ()<GetService<T  و  ()<GetRequiredService<T

از آنجائیکه دیگر از NET 1.0. استفاده نمی‌کنیم، استفاده‌ی از متد GetService با امضایی که در اینترفیس IServiceProvider تعریف شده و strongly typed نیست، بیشتر برای کارهای پویا مناسب است. به همین جهت دو نگارش جنریک از آن در اسمبلی Microsoft.Extensions.DependencyInjection.Abstractions با امضای زیر تعریف شده‌اند که نمونه‌ای از آن‌را در قسمت قبل نیز استفاده کردیم و برای استفاده‌ی از آن‌ها ذکر فضای نام Microsoft.Extensions.DependencyInjection ضروری است:
namespace Microsoft.Extensions.DependencyInjection
{
    public static class ServiceProviderServiceExtensions
    {
        public static T GetRequiredService<T>(this IServiceProvider provider);
        public static T GetService<T>(this IServiceProvider provider);
    }
}
اکنون این سؤال مطرح می‌شود که تفاوت‌های بین این دو متد چیست؟
- متد GetService یک شیء سرویس از نوع T را بازگشت می‌دهد و یا نال؛ اگر سرویسی از نوع T، پیشتر به سیستم معرفی نشده باشد.
- متد GetRequiredService یک شیء سرویس از نوع T را بازگشت می‌دهد و یا اگر سرویسی از نوع T پیشتر به سیستم معرفی نشده باشد، استثنای InvalidOperationException را صادر می‌کند.

بنابراین تنها تفاوت این دو متد، در نحوه‌ی رفتار آن‌ها با درخواست وهله‌ای از یک سرویس پیشتر ثبت نشده‌است؛ یکی نال را باز می‌گرداند و دیگری یک استثناء را صادر می‌کند.


با توجه به این تفاوت‌ها کدامیک از متدهای GetService و یا GetRequiredService را باید استفاده کرد؟

همانطور که پیشتر نیز در توضیحات الگوی Service locator عنوان شد، هیچکدام! ابتدا با تزریق وابستگی‌های در سازنده‌ی کلاس شروع کنید و اگر تامین این وابستگی، توسط IoC Container جاری پشتیبانی نمی‌شد، آنگاه نیاز به استفاده‌ی از یکی از نگارش‌های متد GetService خواهد بود و متد توصیه شده نیز GetRequiredService است و نه GetService؛ به این دلایل:
- حذف کدهای تکراری: اگر از GetService استفاده کنید، نیاز خواهید داشت پس از تمام فراخوانی‌های آن، بررسی نال بودن آن‌را نیز انجام دهید. برای حذف این نوع کدهای تکراری، بهتر است از همان متد GetRequiredService استفاده کنید که به صورت توکار این بررسی را نیز انجام می‌دهد.
- پشتیبانی از روش Fail Fast و یا همان Defensive programming: اگر بررسی نال بودن GetService را فراموش کنید، در سطرهای بعدی، یافتن علت NullReferenceException صادر شده مشکل‌تر از رسیدگی به InvalidOperationException صادر شده‌ی توسط GetRequiredService خواهد بود که توضیحات دقیقی را در مورد سرویس ثبت نشده ارائه می‌دهد.
- اگر بر روی IoC Container پیش‌فرض NET Core. یک IoC Container دیگر را مانند AutoFac قرار داده‌اید، استفاده‌ی از GetRequiredService، سبب می‌شود تا اینگونه IoC Containerهای ثالث بتوانند اطلاعات مفیدتری را از سرویس‌های ثبت نشده ارائه دهند.

تنها حالتی که استفاده‌ی از روش GetService را نیاز دارد، شرطی کردن ثبت و معرفی کردن سرویس‌ها به IoC Container است؛ اگر سرویسی ثبت شده بود، آنگاه قطعه کدی اجرا شود.
نظرات مطالب
آموزش سیلورلایت 4 - قسمت‌های 21 تا 27
MVVM فقط یک الگوی جدا سازی منطق برنامه از لایه نمایشی آن است. سؤال شما مربوط به multi-tier architecture است؛ جائیکه قسمت نمایشی کار از قسمت نگهداری اطلاعات از قسمت مدیریت کار با اطلاعات جدا شده است و اتفاقا سیلورلایت به صورت ذاتی چند tier است (حداقل دو tiers آن روی دو کامپیوتر مجزا قرار دارند؛ قسمت سیلورلایتی سمت کاربر و قسمت سرویس‌ها در سروری مجزا).
در یک پروژه WCF RIA Services کلیه اعمال کار با بانک اطلاعاتی در همان سمت سرور و توسط سرویس‌هایی که اضافه می‌کنید انجام خواهد شد. فقط صدا زدن متدهای این سرویس‌ها است که در سمت کاربر و برنامه سیلولایتی صورت خواهد گرفت (در همان ViewModel).
از آنجائیکه قسمت عمده مدل سیستم حین نمایش از همان جداول بانک اطلاعاتی شما تشکیل خواهد شد که در سمت سرور در سرویس‌های تعریف شده قابل دسترسی می‌شود، روش WCF RIA Services تعریف مجدد این‌ها را در سمت کاربر به صورت خودکار انجام می‌دهد. به همراه replicate کردن تمام مسایل اعتبار سنجی و غیره به سمت کاربر.
مطالب
مروری بر چند تجربه‌ی کاری با SQLite




SQLite بانک اطلاعاتی سریع، کم حجم و سورس بازی است که استفاده از آن در دات نت فریم ورک بسیار ساده است. فقط کافی است پروایدر مربوط به آن را دریافت کنید و در کدهای قدیمی خود هر جایی مثلا sqlconnection داشتید آن‌را تبدیل به sqliteconnection کنید و امثال آن (به بیان دیگر، پروایدر تهیه شده از معماری ADO.NET پیروی می‌کند و عملا دانش قبلی شما به سادگی قابل استفاده و ارتقاء است). علاوه بر آن پروایدر ADO.NET‌ تهیه شده برای آن، پشتیبانی از Entity framework را هم ارائه می‌دهد.
این دیتابیس تحت سیستم عامل‌های مختلف مهیا است و مهم‌ترین مزیت آن عدم نیاز به نصب آن می‌باشد.

مزایا:
- سورس باز و رایگان
- مهیا بودن آن در سایر پلتفرم‌ها (ویندوز، لینوکس و ...)
- نیازی به نصب ندارد و فقط یک DLL بومی است. این مورد برای کاربرانی که در مدیریت بانک‌های اطلاعاتی پیچیده مشکل دارند، یک مزیت مهم است.
- امکان تشیکل دیتابیس در حافظه. این نکته و توانایی، در آزمون‌های واحد بسیار جالب توجه است. می‌توانید با سرعت بالا دیتابیسی واقعی را در حافظه تشکیل داده، کلیه آزمون‌های واحد خود را اجرا کرده و پس از پایان کار، اثری از دیتابیس و تغییر داده‌ها و مشکلات بازگردانی اطلاعات به حالت اول وجود نخواهد داشت.


ملاحظات:
الف) مرتب سازی SQLite حساس به حروف کوچک و بزرگ است.
برای برگشت به عادت متداولی که وجود دارد می‌شود به صورت زیر عمل کرد:
select f1 from tbl1 order by f1 COLLATE NOCASE
یک COLLATE NOCASE اضافه شده است.

ب) رعایت نکات مرتبط با سیستم‌های 64 بیتی
در مورد سیستم‌های 64 بیتی و دات نت قبلا مطلبی را نوشته بودم : {+}. این مطلب دقیقا اینجا کاربرد پیدا می‌کند، از این لحاظ که SQLite یک بانک اطلاعاتی Native است. اگر برنامه‌ی دات نت شما برای حالت Any CPU تهیه شده است، در سیستم‌های 32 بیتی نیاز است تا DLL مرتبط SQLite را توزیع کنید و در سیستم‌های 64 بیتی DLL مرتبط 64 بیتی آن نیاز خواهد بود. در غیراینصورت برنامه‌ی شما در بدو امر کرش کرده و اجرا نخواهد شد.

مشکلات:
الف) کلید خارجی بی خاصیت!
SQLite از کلید خارجی پشتیبانی می‌کند اما آن‌را اعمال نمی‌کند! برای اینکه کلید خارجی را اعمال کنید باید خودتان تریگر بنویسید تا این‌کار را انجام دهد.

ب) پشتیبانی در حد صفر از مباحث همزمانی و تردینگ.
اگر برنامه شما مالتی ترد است، در بد مخمصه‌ای گرفتار شده‌اید. مدام با پیغام database is locked مواجه خواهید شد. (چه انتظاری داشتید؟ یک dll کمتر از 2 مگابایت که قرار نیست کار غول‌های دیتابیسی را انجام دهد)
بنابراین اصلا تصورش را هم نکنید که از این دیتابیس به عنوان بانک اطلاعاتی یک سایت (و محیط‌های چند کاربره) بتوان استفاده کرد و کاربران دچار مشکل نشوند.

ج)حجم بالای دیتابیس و عدم کش
از مباحث caching که در دیتابیس‌های معظم دیگر به صورت توکار وجود دارد خبری نیست. برای مثال اگر یک کوئری قرار است تعدادی را شمارش نماید، حاصلی کش نشده و اگر صدبار هم به صورت متوالی آن‌را فراخوانی کنید باز هم از نو محاسبات آن انجام خواهد شد.
این مورد در حجم بالای دیتابیس واقعا مهم است و نمودش را با دیتابیسی با حجم بالای یک گیگ به وضوح مشاهده خواهید. افت کارآیی و همچنین قرچ و قرچ مداوم هارد دیسک سیستم! (چون به کش رجوع نمی‌شود)

د) امنیت
روی بانک‌های اطلاعاتی اکسس حداقل می‌توان یک کلمه‌ی عبور را قرار داد (که در کسری از ثانیه قابل شکستن است!). در SQLite استاندارد هیچ خبری از این مباحث نبوده و امنیت را باید خودتان تامین کنید. (البته یک نسخه‌ی تجاری هم از این بانک اطلاعاتی با پشتیبانی از رمزنگاری اطلاعات موجود است : +)

ه) مرتب سازی فارسی
هر چند SQLite هیچ مشکلی در ثبت اطلاعات یونیکد و خصوصا متون فارسی ندارد، اما با مرتب سازی کلمات یونیکد مشکل داشته و بر اساس کد اسکی آن‌ها عمل می‌کند. هر چند امکان تعریف Collation سفارشی در آن ممکن است : + (البته ممکن بودن با موجود بودن متفاوت است)


نتیجه گیری:
- SQLite برای دیتابیسی تا حدود یک گیگ که فقط یک نفر قرار باشد از آن استفاده کند انتخاب بسیار مناسبی است (برای مثال فایرفاکس از آن برای ذخیره سازی تنظیمات خودش استفاده می‌کند).

مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت دوم
در قسمت قبل معماری اپلیکیشن‌های N-Tier و بروز رسانی موجودیت‌های منفصل توسط Web API را بررسی کردیم. در این قسمت بروز رسانی موجودیت‌های منفصل توسط WCF را بررسی می‌کنیم.

بروز رسانی موجودیت‌های منفصل توسط WCF

سناریویی را در نظر بگیرید که در آن عملیات CRUD توسط WCF پیاده سازی شده اند و دسترسی داده‌ها با مدل Code-First انجام می‌شود. فرض کنید مدل اپلیکیشن مانند تصویر زیر است.

همانطور که می‌بینید مدل ما متشکل از پست‌ها و نظرات کاربران است. برای ساده نگاه داشتن مثال جاری، اکثر فیلدها حذف شده اند. مثلا متن پست ها، نویسنده، تاریخ و زمان انتشار و غیره. می‌خواهیم تمام کد دسترسی داده‌ها را در یک سرویس WCF پیاده سازی کنیم تا کلاینت‌ها بتوانند عملیات CRUD را توسط آن انجام دهند. برای ساختن این سرویس مراحل زیر را دنبال کنید.

  • در ویژوال استودیو پروژه جدیدی از نوع Class Library بسازید و نام آن را به Recipe2 تغییر دهید.
  • با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
  • سه کلاس با نام‌های Post, Comment و Recipe2Context به پروژه اضافه کنید. کلاس‌های Post و Comment موجودیت‌های مدل ما هستند که به جداول متناظرشان نگاشت می‌شوند. کلاس Recipe2Context آبجکت DbContext ما خواهد بود که بعنوان درگاه عملیاتی EF عمل می‌کند. دقت کنید که خاصیت‌های لازم WCF یعنی DataContract و DataMember در کلاس‌های موجودیت‌ها بدرستی استفاده می‌شوند. لیست زیر کد این کلاس‌ها را نشان می‌دهد.
[DataContract(IsReference = true)]
public class Post
{
    public Post()
    {
        comments = new HashSet<Comments>();
    }
    
    [DataMember]
    public int PostId { get; set; }
    [DataMember]
    public string Title { get; set; }
    [DataMember]
    public virtual ICollection<Comment> Comments { get; set; }
}

[DataContract(IsReference=true)]
public class Comment
{
    [DataMember]
    public int CommentId { get; set; }
    [DataMember]
    public int PostId { get; set; }
    [DataMember]
    public string CommentText { get; set; }
    [DataMember]
    public virtual Post Post { get; set; }
}

public class EFRecipesEntities : DbContext
{
    public EFRecipesEntities() : base("name=EFRecipesEntities") {}

    public DbSet<Post> posts;
    public DbSet<Comment> comments;
}
  • یک فایل App.config به پروژه اضافه کنید و رشته اتصال زیر را به آن اضافه نمایید.
<connectionStrings>
  <add name="Recipe2ConnectionString"
    connectionString="Data Source=.;
    Initial Catalog=EFRecipes;
    Integrated Security=True;
    MultipleActiveResultSets=True"
    providerName="System.Data.SqlClient" />
</connectionStrings>
  • حال یک پروژه WCF به Solution جاری اضافه کنید. برای ساده نگاه داشتن مثال جاری، نام پیش فرض Service1 را بپذیرید. فایل IService1.cs را باز کنید و کد زیر را با محتوای آن جایگزین نمایید.
[ServiceContract]
public interface IService1
{
    [OperationContract]
    void Cleanup();
    [OperationContract]
    Post GetPostByTitle(string title);
    [OperationContract]
    Post SubmitPost(Post post);
    [OperationContract]
    Comment SubmitComment(Comment comment);
    [OperationContract]
    void DeleteComment(Comment comment);
}
  • فایل Service1.svc.cs را باز کنید و کد زیر را با محتوای آن جایگزین نمایید. بیاد داشته باشید که پروژه Recipe2 را ارجاع کنید و فضای نام آن را وارد نمایید. همچنین کتابخانه EF 6 را باید به پروژه اضافه کنید.
public class Service1 : IService
{
    public void Cleanup()
    {
        using (var context = new EFRecipesEntities())
        {
            context.Database.ExecuteSqlCommand("delete from [comments]");
            context. Database.ExecuteSqlCommand ("delete from [posts]");
        }
    }

    public Post GetPostByTitle(string title)
    {
        using (var context = new EFRecipesEntities())
        {
            context.Configuration.ProxyCreationEnabled = false;
            var post = context.Posts.Include(p => p.Comments).Single(p => p.Title == title);
            return post;
        }
    }

    public Post SubmitPost(Post post)
    {
        context.Entry(post).State =
            // if Id equal to 0, must be insert; otherwise, it's an update
            post.PostId == 0 ? EntityState.Added : EntityState.Modified;
        context.SaveChanges();
        return post;
    }

    public Comment SubmitComment(Comment comment)
    {
        using (var context = new EFRecipesEntities())
        {
            context.Comments.Attach(comment);
            if (comment.CommentId == 0)
            {
                // this is an insert
                context.Entry(comment).State = EntityState.Added);
            }
            else
            {
                // set single property to modified, which sets state of entity to modified, but
                // only updates the single property – not the entire entity
                context.entry(comment).Property(x => x.CommentText).IsModified = true;
            }
            context.SaveChanges();
            return comment;
        }
    }

    public void DeleteComment(Comment comment)
    {
        using (var context = new EFRecipesEntities())
        {
            context.Entry(comment).State = EntityState.Deleted;
            context.SaveChanges();
        }
    }
}


  • در آخر پروژه جدیدی از نوع Windows Console Application به Solution جاری اضافه کنید. از این اپلیکیشن بعنوان کلاینتی برای تست سرویس WCF استفاده خواهیم کرد. فایل program.cs را باز کنید و کد زیر را با محتوای آن جایگزین نمایید. روی نام پروژه کلیک راست کرده و گزینه Add Service Reference را انتخاب کنید، سپس ارجاعی به سرویس Service1 اضافه کنید. رفرنسی هم به کتابخانه کلاس‌ها که در ابتدای مراحل ساختید باید اضافه کنید.
class Program
{
    static void Main(string[] args)
    {
        using (var client = new ServiceReference2.Service1Client())
        {
            // cleanup previous data
            client.Cleanup();
            // insert a post
            var post = new Post { Title = "POCO Proxies" };
            post = client.SubmitPost(post);
            // update the post
            post.Title = "Change Tracking Proxies";
            client.SubmitPost(post);
            // add a comment
            var comment1 = new Comment { CommentText = "Virtual Properties are cool!", PostId = post.PostId };
            var comment2 = new Comment { CommentText = "I use ICollection<T> all the time", PostId = post.PostId };
            comment1 = client.SubmitComment(comment1);
            comment2 = client.SubmitComment(comment2);
            // update a comment
            comment1.CommentText = "How do I use ICollection<T>?";
            client.SubmitComment(comment1);
            // delete comment 1
            client.DeleteComment(comment1);
            // get posts with comments
            var p = client.GetPostByTitle("Change Tracking Proxies");
            Console.WriteLine("Comments for post: {0}", p.Title);
            foreach (var comment in p.Comments)
            {
                Console.WriteLine("\tComment: {0}", comment.CommentText);
            }
        }
    }
}
اگر اپلیکیشن کلاینت (برنامه کنسول) را اجرا کنید با خروجی زیر مواجه می‌شوید.

Comments for post: Change Tracking Proxies
Comment: I use ICollection<T> all the time


شرح مثال جاری

ابتدا با اپلیکیشن کنسول شروع می‌کنیم، که کلاینت سرویس ما است. نخست در یک بلاک {} using وهله ای از کلاینت سرویس مان ایجاد می‌کنیم. درست همانطور که وهله ای از یک EF Context می‌سازیم. استفاده از بلوک‌های using توصیه می‌شود چرا که متد Dispose بصورت خودکار فراخوانی خواهد شد، چه بصورت عادی چه هنگام بروز خطا. پس از آنکه وهله ای از کلاینت سرویس را در اختیار داشتیم، متد Cleanup را صدا می‌زنیم. با فراخوانی این متد تمام داده‌های تست پیشین را حذف می‌کنیم. در چند خط بعدی، متد SubmitPost را روی سرویس فراخوانی می‌کنیم. در پیاده سازی فعلی شناسه پست را بررسی می‌کنیم. اگر مقدار شناسه صفر باشد، خاصیت State موجودیت را به Added تغییر می‌دهید تا رکورد جدیدی ثبت کنیم. در غیر اینصورت فرض بر این است که چنین موجودیتی وجود دارد و قصد ویرایش آن را داریم، بنابراین خاصیت State را به Modified تغییر می‌دهیم. از آنجا که مقدار متغیرهای int بصورت پیش فرض صفر است، با این روش می‌توانیم وضعیت پست‌ها را مشخص کنیم. یعنی تعیین کنیم رکورد جدیدی باید ثبت شود یا رکوردی موجود بروز رسانی گردد. رویکردی بهتر آن است که پارامتری اضافی به متد پاس دهیم، یا متدی مجزا برای ثبت رکوردهای جدید تعریف کنیم. مثلا رکوردی با نام InsertPost. در هر حال، بهترین روش بستگی به ساختار اپلیکیشن شما دارد.

اگر پست جدیدی ثبت شود، خاصیت PostId با مقدار مناسب جدید بروز رسانی می‌شود و وهله پست را باز می‌گردانیم. ایجاد و بروز رسانی نظرات کاربران مشابه ایجاد و بروز رسانی پست‌ها است، اما با یک تفاوت اساسی: بعنوان یک قانون، هنگام بروز رسانی نظرات کاربران تنها فیلد متن نظر باید بروز رسانی شود. بنابراین با فیلدهای دیگری مانند تاریخ انتشار و غیره اصلا کاری نخواهیم داشت. بدین منظور تنها خاصیت CommentText را بعنوان Modified علامت گذاری می‌کنیم. این امر منجر می‌شود که Entity Framework عبارتی برای بروز رسانی تولید کند که تنها این فیلد را در بر می‌گیرد. توجه داشته باشید که این روش تنها در صورتی کار می‌کند که بخواهید یک فیلد واحد را بروز رسانی کنید. اگر می‌خواستیم فیلدهای بیشتری را در موجودیت Comment بروز رسانی کنیم، باید مکانیزمی برای ردیابی تغییرات در سمت کلاینت در نظر می‌گرفتیم. در مواقعی که خاصیت‌های متعددی می‌توانند تغییر کنند، معمولا بهتر است کل موجودیت بروز رسانی شود تا اینکه مکانیزمی پیچیده برای ردیابی تغییرات در سمت کلاینت پیاده گردد. بروز رسانی کل موجودیت بهینه‌تر خواهد بود.

برای حذف یک دیدگاه، متد Entry را روی آبجکت DbContext فراخوانی می‌کنیم و موجودیت مورد نظر را بعنوان آرگومان پاس می‌دهیم. این امر سبب می‌شود که موجودیت مورد نظر بعنوان Deleted علامت گذاری شود، که هنگام فراخوانی متد SaveChanges اسکریپت لازم برای حذف رکورد را تولید خواهد کرد.

در آخر متد GetPostByTitle یک پست را بر اساس عنوان پیدا کرده و تمام نظرات کاربران مربوط به آن را هم بارگذاری می‌کند. از آنجا که ما کلاس‌های POCO را پیاده سازی کرده ایم، Entity Framework آبجکتی را بر می‌گرداند که Dynamic Proxy نامیده می‌شود. این آبجکت پست و نظرات مربوط به آن را در بر خواهد گرفت. متاسفانه WCF نمی‌تواند آبجکت‌های پروکسی را مرتب سازی (serialize) کند. اما با غیرفعال کردن قابلیت ایجاد پروکسی‌ها (ProxyCreationEnabled=false) ما به Entity Framework می‌گوییم که خود آبجکت‌های اصلی را بازگرداند. اگر سعی کنید آبجکت پروکسی را سریال کنید با پیغام خطای زیر مواجه خواهید شد:

The underlying connection was closed: The connection was closed unexpectedly 

می توانیم غیرفعال کردن تولید پروکسی را به متد سازنده کلاس سرویس منتقل کنیم تا روی تمام متدهای سرویس اعمال شود.

در این قسمت دیدیم چگونه می‌توانیم از آبجکت‌های POCO برای مدیریت عملیات CRUD توسط WCF استفاده کنیم. از آنجا که هیچ اطلاعاتی درباره وضعیت موجودیت‌ها روی کلاینت ذخیره نمی‌شود، متدهایی مجزا برای عملیات CRUD ساختیم. در قسمت‌های بعدی خواهیم دید چگونه می‌توان تعداد متدهایی که سرویس مان باید پیاده سازی کند را کاهش داد و چگونه ارتباطات بین کلاینت و سرور را ساده‌تر کنیم.

مطالب
پیاده سازی Load Balancing در Nginx

زمانیکه تعداد کاربران سایت بیشتر میشود و ترافیک سایت افزایش می‌یابد عموما یک نمونه ( instance ) از برنامه نمیتواند پاسخگوی همه درخواست‌ها باشد و مجبور هستیم چندین نمونه برنامه را بر روی چند سرور اجرا کنیم. با این حال نیازمند یک وب سرور هستیم که درخواست‌های ارسال شده را در بین instance های موجود پخش کند. با انجام این کار تعداد ریکوئست‌ها در بین instance ها تقسیم میشوند. برای انجام اینکار میتوانیم از nginx استفاده کنیم. nginx قابلیت load balancing را دارد. برای پیاده سازی load balancing در nginx باید در فایل nginx.conf یک گروه از نوع upstream ایجاد کنید در کانتکست http.
http {
    upstream backend {
        server backend1.example.com;
        server backend2.example.com;
        server backend3.example.com;
    }
}
در کانفیگ بالا یک گروه به نام backend ایجاد کرده‌ایم که شامل سه سرور می‌باشد. یعنی سه instance از برنامه در حال اجرا هستند و میتوانیم ریکوئست‌های ارسال شده را بر روی این سرور‌ها ارسال کنیم. سپس باید در کانتکس server ( این کانتکست هم درون کانتکست http قرار دارد ) یک بلاک از نوع location ایجاد کنیم. بعد از کلمه location باید مسیری را که درخواست‌ها به آن ارسال میشوند را بنویسید که در مثال پایین همان روت وب سایت نوشته شده است ( / ). نوشتن / بعد از location تمامی درخواست‌ها را شامل میشود. همچنین میتوانید فقط درخواست‌هایی را که به api ها ارسال میشوند، فیلتر کنید که میتوانید به جای / از api/ استفاده کنید. با این کار تمامی درخواست‌هایی که url آنها با api/ شروع شود وارد بلاک location میشوند.
سپس برای ارسال کردن درخواست‌ها در بین برنامه‌های در حال اجرا باید از proxy_pass استفاده کنیم. بعد از کلمه proxy_pass باید نام upstream ی را که در بالا نوشته‌ایم، وارد کنیم. با انجام اینکار تمامی درخواست‌های ارسال شده به / در بین سرور‌های backend پخش میشوند.
http {
    upstream backend {
        server backend1.example.com;
        server backend2.example.com;
        server backend3.example.com;
    }
    
    server {
        location / {
            proxy_pass http://backend;
        }
    }
}
وب سرور nginx به طور پیشفرض از الگوریتم Round robin برای پخش کردن درخواست‌ها در بین سرور‌ها استفاده میکند. البته میتوانیم این الگوریتم را تغییر بدهیم. به طور مثال میتوانیم مشخص کنیم که به هرکدام از سرور‌ها چند درخواست ارسال شود.
با افزودن weight در کنار نام سرور‌ها مشخص میکنم که به طور مثال اگر 7 درخواست به سرور ارسال شود، 5 مورد به backend1 ارسال میشود و 2 مورد به backend2. همچنین میتوانیم مشخص کنیم که یک سرور به عنوان سرور backup باشد مانند backend4.
اگر سروری در دسترس نباشد میتوانید با اضافه کردن کلمه down بعد از نام سرور، از ارسال درخواست به آن جلوگیری کنید؛ مانند backend3 و یا قبل از کلمه server یک علامت # قرار دهید.
    upstream backend {
        server backend1.example.com weight=5;
        server backend2.example.com weight=2;
        server backend3.example.com down;
        server backend4.example.com backup;
    }
با انجام این کار سرور backend4 به عنوان سرور backup معرفی شده است و تا زمانیکه ما بقی سرورها در دسترس باشند، هیچ ریکوئستی به سرور backend4 ارسال نمیشود. اگر تمامی سرور‌های backend1 و backend2 از دسترس خارج شوند، آنگاه nginx درخواست‌های ارسال شده را به سرور backup یعنی backend4 ارسال میکند. البته به طور پیشفرض nginx سلامت سرورها را بررسی نمیکند و باید یک سری تنظیمات مربوط به health_check را ثبت کنیم. با اضافه کردن دستور health_check در پایین proxy_pass وب سرور nginx هر 5 ثانیه یک بار یک ریکوئست را به سرورهای backend ارسال میکند و اگر هرکدام از سرور‌ها ریسپانس کدی خارج از کدهای 200 تا 399 را ارسال کنند، درخواست‌های بعدی را به آن سرورها ارسال نمیکند.
server {
    location / {
        proxy_pass   http://backend;
        health_check;
    }
}
در کنار دستور health_check میتوانیم یک سری پارامتر را هم مشخص کنیم. به طور مثال میتوانیم مشخص کنیم که درخواست health_check را به چه uri ای ارسال کند و یا به چه پورتی درخواست را ارسال کند.
server {
    location / {
        proxy_pass   http://backend;
        health_check port=8080;
       #health_check uri=/healthcheck;
    }
}
(برای کامنت کردن یک دستور از # استفاده میشود).
همچنین میتوانیم یک درخواست سفارشی را برای مشخص کردن health_check ایجاد کنیم:
http {
    #...
    match welcome {
        status 200;
        header Content-Type = text/html;
        body ~ "Welcome to nginx!";
    }
    server {
        #...
        location / {
            proxy_pass http://backend;
            health_check match=welcome;
        }
    }
}
در کانفیگ بالا مشخص کرده‌ایم که برای نشان دادن در دسترس بودن هرکدام از سرورها باید استتوس کد 200 برگردانده شود و body دریافت شده حاوی !Welcome to nginx باشد.
نکته : match و health_check در نسخه تجاری nginx قابل استفاده می‌باشند.