طراحی مجدد سایت NuGet
public class Client { public delegate string Invoker(); public static Invoker Execute;//اضافه کردن یک آیتم جدید public static Invoker Redo;//حرکت به جلو public static Invoker Undo;//حرکت به عقب }
public class Command { public Command(Receiver receiver) { Client.Execute = receiver.Action; Client.Redo = receiver.Foreward; Client.Undo = receiver.Reverse; } }
public class Receiver { private readonly List<string> build = new List<string>(); private readonly List<string> oldBuild = new List<string>(); public string Action() { if (build.Count > 0) oldBuild.Add(build.LastOrDefault()); build.Add(build.Count.ToString(CultureInfo.InvariantCulture)); return build.LastOrDefault(); } public string Reverse() { string last = oldBuild.LastOrDefault(); if (last == null) return "EMPTY"; oldBuild.Remove(last); return last; } public string Foreward() { string oldIndex = oldBuild.LastOrDefault(); int index = oldIndex == null ? -1 : build.IndexOf(oldIndex); if ((index + 1) == build.Count) return "END"; oldBuild.Add(build.ElementAt(index + 1)); return oldBuild.LastOrDefault(); } }
new Command(new Receiver()); Console.WriteLine(Client.Execute()); Console.WriteLine(Client.Execute()); Console.WriteLine(Client.Undo()); Console.WriteLine(Client.Undo()); Console.WriteLine(Client.Undo()); Console.WriteLine(Client.Redo()); Console.WriteLine(Client.Redo()); Console.WriteLine(Client.Redo()); Console.WriteLine(Client.Execute());
public class DocumentPrinter { public void PrintDocument(string documentName) { var repository = new DocumentRepository(); var formatter = new DocumentFormatter(); var printer = new Printer(); var document = repository .GetDocumentByName(documentName); var formattedDocument = formatter.Format(document); printer.Print(formattedDocument); } }
var documentPrinter = new DocumentPrinter(); documentPrinter.PrintDocument(@"c:\doc.doc");
public class DocumentPrinter { private DocumentRepository _repository; private DocumentFormatter _formatter; private Printer _printer; public DocumentPrinter( DocumentRepository repository, DocumentFormatter formatter, Printer printer) { _repository = repository; _formatter = formatter; _printer = printer; } public void PrintDocument(string documentName) { var document = _repository.GetDocumentByName(documentName); var formattedDocument = _formatter.Format(document); _printer.Print(formattedDocument); } }
var repository = new DocumentRepository(); var formatter = new DocumentFormatter(); var printer = new Printer(); var documentPrinter = new DocumentPrinter(repository, formatter, printer); documentPrinter.PrintDocument(@"c:\doc.doc");
public interface IDocumentRepository { Document GetDocumentByName(string documentName); }
public class DocumentPrinter { private IDocumentRepository _repository; private IDocumentFormatter _formatter; private IPrinter _printer; public DocumentPrinter( IDocumentRepository repository, IDocumentFormatter formatter, IPrinter printer) { _repository = repository; _formatter = formatter; _printer = printer; } public void PrintDocument(string documentName) { var document = _repository.GetDocumentByName(documentName); var formattedDocument = _formatter.Format(document); _printer.Print(formattedDocument); } }
ObjectFactory.Configure(cfg => { cfg.For<IDocumentRepository>().Use<FilesystemDocumentRepository>(); cfg.For<IDocumentFormatter>().Use<DocumentFormatter>(); cfg.For<IPrinter>().Use<Printer>(); });
تقریبا تمام اعمال کار با شبکه در Silverlight از مدل asynchronous programming پیروی میکنند؛ از فراخوانی یک متد وب سرویس تا دریافت اطلاعات از وب و غیره. اگر در سایر فناوریهای موجود در دات نت فریم ورک برای مثال جهت کار با یک وب سرویس هر دو متد همزمان و غیرهمزمان در اختیار برنامه نویس هستند اما اینجا خیر. اینجا فقط روشهای غیرهمزمان مرسوم هستند و بس. خیلی هم خوب. یک چارچوب کاری خوب باید روش استفادهی صحیح از کتابخانههای موجود را نیز ترویج کند و این مورد حداقل در Silverlight اتفاق افتاده است.
برای مثال فراخوانیهای زیر را در نظر بگیرید:
private int n1, n2;
private void FirstCall()
{
Service.GetRandomNumber(10, SecondCall);
}
private void SecondCall(int number)
{
n1 = number;
Service.GetRandomNumber(n1, ThirdCall);
}
private void ThirdCall(int number)
{
n2 = number;
// etc
}
private void FetchNumbers()
{
int n1 = Service.GetRandomNumber(10);
int n2 = Service.GetRandomNumber(n1);
}
private void FetchNumbers()
{
int n1, n2;
Service.GetRandomNumber(10, result =>
{
n1 = result;
Service.GetRandomNumber(n1, secondResult =>
{
n2 = secondResult;
});
});
}
به عبارتی میخواهیم کل اعمال انجام شده در متد FetchNumbers هنوز Async باشند (ترد اصلی برنامه را قفل نکنند) اما پی در پی انجام شوند تا مدیریت آنها سادهتر شوند (هر لحظه دقیقا بدانیم که کجا هستیم) و همچنین کدهای تولیدی نیز خواناتر باشند.
روش استانداری که توسط الگوهای برنامه نویسی برای حل این مساله پیشنهاد میشود، استفاده از الگوی coroutines است. توسط این الگو میتوان چندین متد Async را در حالت معلق قرار داده و سپس در هر زمانی که نیاز به آنها بود عملیات آنها را از سر گرفت.
دات نت فریم ورک حالت ویژهای از coroutines را توسط Iterators پشتیبانی میکند (از C# 2.0 به بعد) که در ابتدا نیاز است از دیدگاه این مساله مروری بر آنها داشته باشیم. مثال بعد یک enumerator را به همراه yield return ارائه داده است:
using System;
using System.Collections.Generic;
using System.Threading;
namespace CoroutinesSample
{
class Program
{
static void printAll()
{
foreach (int x in integerList())
{
Console.WriteLine(x);
}
}
static IEnumerable<int> integerList()
{
yield return 1;
Thread.Sleep(1000);
yield return 2;
yield return 3;
}
static void Main()
{
printAll();
}
}
}
کامپایلر سی شارپ در عمل یک state machine را برای پیاده سازی این عملیات به صورت خودکار تولید خواهد کرد:
private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<>2__current = 1;
this.<>1__state = 1;
return true;
case 1:
this.<>1__state = -1;
Thread.Sleep(0x3e8);
this.<>2__current = 2;
this.<>1__state = 2;
return true;
case 2:
this.<>1__state = -1;
this.<>2__current = 3;
this.<>1__state = 3;
return true;
case 3:
this.<>1__state = -1;
break;
}
return false;
}
در حین استفاده از یک IEnumerator ابتدا در وضعیت شیء Current آن قرار خواهیم داشت و تا زمانیکه متد MoveNext آن فراخوانی نشود هیچ اتفاق دیگری رخ نخواهد داد. هر بار که متد MoveNext این enumerator فرخوانی گردد (برای مثال توسط یک حلقهی foreach) اجرای متد integerList ادامه خواهد یافت تا به yield return بعدی برسیم (سایر اعمال تعریف شده در حالت تعلیق قرار دارند) و همینطور الی آخر.
از همین قابلیت جهت مدیریت اعمال Async پی در پی نیز میتوان استفاده کرد. State machine فوق تا پایان اولین عملیات تعریف شده صبر میکند تا به yield return برسد. سپس با فراخوانی متد MoveNext به عملیات بعدی رهنمون خواهیم شد. به این صورت دیدگاهی پی در پی از یک سلسه عملیات غیرهمزمان حاصل میگردد.
خوب ما الان نیاز به یک کلاس داریم که بتواند enumerator ایی از این دست را به صورت خودکار مرحله به مرحله آن هم پس از پایان واقعی عملیات Async قبلی (یا مرحلهی قبلی)، اجرا کند. قبل از اختراع چرخ باید متذکر شد که دیگران اینکار را انجام دادهاند و کتابخانههای رایگان و یا سورس بازی برای این منظور موجود است.
ادامه دارد ...
در ادامه مطلب قبلی، یکی از مشکلاتی که طراحی Builder از آن رنج میبرد، نقض کردن قانون command query separation است که در ادامه دربارهی این اصل بیشتر بحث خواهیم کرد.
اصل Command query separation یا به اختصار CQS، در کتاب Object-Oriented Software Construction توسط Bertrand Meyer معرفی شدهاست. بر اساس آن، عملیاتهای سیستم باید یا Command باشند و یا Query و نه هر دوی آنها. وقتی یک کلاینت به امضای یک متد توجه میکند، اینکه این متد چه کاری را انجام میدهد Commands نام داشته و به شیء فرمان میدهد تا کاری را انجام بدهد. این عملیات وضعیت خود شیء و یا اشیاء دیگر را تغییر میدهد. در اینجا Queries به شیء فرمان میدهند تا نتیجهی سؤال ( ویا درخواست) را برگرداند.
در آن سوی دیگر، متدهایی را که وضعیت شیء را تغییر میدهند، به عنوان Command در نظر میگیریم (بدون آنکه مقداری را برگردانند). اگر این نوع متدها، مقداری را برگردانند، باعث سردرگمی کلاینت میشوند؛ زیرا کلاینت نمیداند این متد باعث تغییر شیء شدهاست و یا Query؟
public class FileStore { public string WorkingDirectory { get; set; } public string Save(int id, string message) { var path = Path.Combine(this.WorkingDirectory + id + ".txt"); File.WriteAllText(path, message); return path; } public event EventHandler<MessageEventArgs> MessageRead; public void Read(int id) { var path = Path.Combine(this.WorkingDirectory + id + ".txt"); var msg = File.ReadAllText(path); this.MessageRead(this, new MessageEventArgs { Message = msg }); } }
public string Read(int id) { var path = Path.Combine(this.WorkingDirectory + id + ".txt"); var msg = File.ReadAllText(path); this.MessageRead(this, new MessageEventArgs { Message = msg }); return msg; }
public void Save(int id, string message) { var path = Path.Combine(this.WorkingDirectory + id + ".txt"); File.WriteAllText(path, message); }
public class FileStore { public string WorkingDirectory { get; set; } public void Save(int id, string message) { var path = GetFileName(id); //ok to query from Command File.WriteAllText(path, message); } public string Read(int id) { var path = GetFileName(id); var msg = File.ReadAllText(path); return msg; } public string GetFileName(int id) { return Path.Combine(this.WorkingDirectory , id + ".txt"); } }
چکیده:
هدف اصلی از طراحی نرم افزار، غالب شدن بر پیچیدگیها میباشد. اصل CQS متدها را به دو دستهی Command و Query تقسیم میکند که Query ، اطلاعاتی را از وضعیت سیستم بر میگرداند، ولی command وضعیت سیستم را تغییر میدهد و مهمترین دستاورد CQS ما را به سمت کدی تمیزتر و با قابلیت درک بهتر میرساند.
How you shouldn’t implement base classes
public class Entity<T> { public T Id { get; protected set; } }
Motivation for such code it pretty clear: you have a base class that can be reused across multiple projects. For instance, if there is a web application with GUID Id columns in the database and a desktop app with integer Ids, it might seem a good idea to have the same class for both of them. In fact, this approach introduces accidental complexity because of premature generalization.
There is no need in using a single base entity class for more than one project or bounded context. Each domain has its unique path, so let it grow independently. Just copy and paste the base entity class to a new project and specify the exact type that will be used for the Id property.
در این مقاله حالتهایی را که الگوی طراحی Null Object، قادر به تشخیص آنها نیست را به وسیله الگوی طراحی Special case رفع میکنیم.
گاهی از اوقات پیش خواهد آمد که ما تسلیم شده و شیءای را برمیگردانیم که خودش نشان دهنده خطاست. اما همه شکستها یکسان نیستند. در ابتدا، کاربر ممکن است واجد شرایط انجام خرید نباشد. اما اگر واجد شرایط باشد، آیتم مورد نظر ممکن است در انبار نباشد. در صورت موجود بودن در انبار، حساب کاربر ممکن است مبلغ کافی نداشته باشد.
بجای برگشت دادن شیء Null در تمام این موارد، ما میتوانیم نتیجه را اصلاح کنیم و اساسا هر بار یک شیء مختلف را بازگردانیم. اینها هنوز هم نوعی از اشیاء Null هستند؛ ولی اینبار دارای معانی هستند. یکی از انها برای «حساب کاربری ناکافی» است، یکی دیگر برای «سایت در دست تعمیر و نگهداری» است و یا یکی دیگر از آنها «موجود نبودن در انبار» خواهد بود.
چنین اشیائی به موارد خاص ( Special Case ) اشاره میکنند. ما میتوانیم اشیاء مورد خاص ( Special Case ) را به عنوان نتایج واقعی عملیات بسازیم. فقط اگر تمام بررسیهای کسب و کار به پایان برسند و عملیات موفقیت آمیز باشد، آنگاه شیء واقعی نتیجه را باز میگردانیم.
نمونهای از پیاده سازی موارد خاص
این بخشی از رابط کاربری است که توسط سرویسهای برنامه کاربردی اجرا میشود:
public interface IApplicationServices { ... IReceiptViewModel LoggedInUserPurchase(string itemName); }
لایه نمایش انتظار دارد که لایه نرم افزار یک ویو مدل به خصوصی را برای آن تولید کند. در حال حاضر ما یک سناریوی موفقیت آمیز را داریم که در آن ویو مدل شامل اطلاعات واقعی از خرید است و چندین سناریوی شکست.
اگرعملیات خرید با هر کدام از شرایط زیر مواجه شود به شکست میانجامد:
1) سایت در دست تعمیر و نگهداری باشد.
2) کاربر ثبت نشده و یا فعال نیست.
3) آیتمی موجود نیست و یا وجود ندارد.
4) موجودی کاربر کم باشد.
public class DownForMaintenance: IReceiptViewModel { }
public class InvalidUser: IReceiptViewModel { public string UserName { get; private set; } public InvalidUser(string userName) { this.UserName = userName; } }
public class OutOfStock: IReceiptViewModel { public string UserName { get; private set; } public string ItemName { get; private set; } public OutOfStock(string userName, string itemName) { this.UserName = userName; this.ItemName = itemName; } }
public class InsufficientFunds: IReceiptViewModel { public string UserName { get; private set; } public decimal Amount { get; private set; } public string ItemName { get; private set; } public InsufficientFunds(string userName, decimal amount, string itemName) { this.UserName = userName; this.Amount = amount; this.ItemName = itemName; } }
public class ApplicationServices: IApplicationServices { ... public IReceiptViewModel LoggedInUserPurchase(string itemName) { if (IsDownForMaintenance()) return new DownForMaintenance(); return this.domain.Purchase(Session.LoggedInUserName, itemName); } private bool IsDownForMaintenance() { return File.Exists("maintenance.lock"); } }
public class DomainServices: IDomainServices { public IReceiptViewModel Purchase(string userName, string itemName) { User user = this.userRepository.Find(userName); if (user == null) return new InvalidUser(userName); Account account = this.accountRepository.FindByUser(user); return this.Purchase(user, account, itemName); } private IReceiptViewModel Purchase(User user, Account account, string itemName) { Product item = this.productRepository.Find(itemName); if (item == null) return new OutOfStock(user.UserName, itemName); ReceiptDto receipt = user.Purchase(item); MoneyTransaction transaction = account.Withdraw(receipt.Price); if (transaction == null) return new InsufficientFunds(user.UserName, receipt.Price, itemName); return receipt; } }
معماری میکروسرویسها
معماری Monolithic چیست؟
مشکلات معماری Monolithic
- در معماری Monolithic زمانیکه ترافیک برنامه در سمت سرور افزایش پیدا میکند، باید برای پاسخگویی، اندازه را افزایش داد. یعنی باید برنامه تحت وب خود را بر روی سرورهای مختلف مجددا اجرا نمود. بخشی به نام Load Balancer، وظیفه توزیع درخواستها را به سرورهای مختلف که بر روی هر یک، یک نسخه از برنامه در حال اجرا است، به عهده دارد. بر اساس توضیحی که از این معماری ارایه شد، در هر یک از این اجراها، کل برنامه با تمام متعلقاتی که دارد، فارغ از اینکه به همه آنها نیاز است یا نه، از منابع سرور استفاده میکند.
- در معماری Monolithic برنامهها بر اساس یک زبان برنامهنویسی مشخص، برای یک فریم ورک مشخص نوشته میشوند. این برنامهها اصطلاحا چند سکویی نیستند و کامپوننتهای نوشته شده برای آنها فقط در فریم ورک جاری قابل استفاده مجدد هستند.
- ممکن است برای هر تغییر ریز و درشت در برنامههای این معماری، نیاز به Build و Deploy مجدد کل برنامه باشد که احتمال از دسترس خارج شدن برنامه هم وجود دارد.
- اگر بخشی از برنامه از کار بیافتد، ممکن است باعث از کار افتادن کل برنامه یا بخشهایی از آن شود.
معماری Microservices
ارزش معماری Microservices
- از آنجایی که سرویسها از طریق زبان مشترک شبکه با یکدیگر در ارتباط هستند، میشود آنها را با زبانهای برنامهنویسی مختلف و بر روی فریمفرکهای متفاوت نوشت.
- بدیهی است که با این معماری، هر سرویس را میشود به صورت جداگانه ایجاد کرد و تغییر داد که باعث سرعت در به روزرسانی و فرآیند گسترش برنامه میشود.
- مانیتور کردن سرویسها سادهتر خواهد بود. از آنجایی که هر سرویس به صورت یک پردازش جداگانه اجرا خواهد شد، تعیین اینکه هر سرویس از چه منابعی و به چه اندازهای استفاده میکند، آسانتر خواهد بود.
- از آنجایی که این سرویسها از طریق شبکه در تبادل هستند، میشود از آنها در سایر برنامهها مجدداً استفاده کرد.
افزایش یک سرویس خاص
مشکلات معماری Microservices
- از آنجایی که برنامههای سمت سرور نوشته شده با این معماری به سرویسهای مختلفی تقسیم میشوند، گسترش و تنظیمات آنها میتواند کاری وقت گیر و طاقت فرسایی باشد.
- از آنجایی که ارتباط بین سرویسها در بستر شبکه انجام میشود، انتظار کندی عملکرد سرویسها دور از ذهن نیست.
- به دلیل ارتباطات شبکهای، احتمال آسیب پذیریهای امنیتی در این نوع برنامهها بیشتر است.
- نوشتن سرویسهایی که در بستر شبکه با سایر سرویسها در ارتباط هستند سختی و مشکلات خود را دارد. برنامهنویس در این شرایط، درگیر برقراری ارتباط، رمزگذاری دادهها در صورت نیاز و تبدیل آنها میشود.
- به دلیل مجزا بودن بخشهای مختلف برنامه، مانیتور کردن و ردیابی عملکرد سرویسها، یکی از کارهای اصلی توسعه دهنده یا استفاده کننده از برنامه است.
- در مجموع سرعت برنامههای نوشته شده با معماری Microservices کندتر از برنامههای نوشته شده با معماری Monolithic است. دلیل آن محیط اجرایی برنامهها است. برنامههایی با معماری Monolithic بر روی حافظه سرور پردازش میشوند.
چه زمانی از معماری Microservices استفاده کنیم؟