مطالب
Functional Programming یا برنامه نویسی تابعی - قسمت سوم – Immutability

در ادامه مطالب مربوط به برنامه نویسی تابعی، قصد دارم بیشتر وارد کد شویم و مباحث عنوان شده را در دنیای کد پیاده سازی کنیم. هدف این قسمت، refactor کردن کد موجود به یک معماری immutable هست.  پیشتر درباره immutable ‌ها صحبت کردیم. ابتدا برای یکسان سازی ادبیات مورد استفاده، چند کلمه را مجددا تعریف خواهیم کرد:

  • Immutability: عدم توانایی تغییر داده
  • State: داده‌هایی که در طول زمان تغییر می‌کنند
  • Side Effect: تغییری که روی داده‌ها اتفاق می‌افتد

در قطعه کد زیر سعی شده‌است تفاوت یک کلاس Stateless و stateful را به سادگی نشان دهیم:

    //Stateful
    public class UserProfile
    {
        private User _user;
        private string _address;

        public void UpdateUser(int userId, string name)
        {
            _user = new User(userId, name);
        }
    }

    //Stateless
    public class User
    {
        public User(int id, string name)
        {
            Id = id;
            Name = name;
        }

        public int Id { get; }
        public string Name { get; }
    }


چرا Immutable بودن مهم است؟ 

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

در واقع انتظار داریم که به ازای یک ورودی بر اساس بدنه‌ی متد، یک خروجی داشته باشیم؛ ولی در واقعیت تاثیری که اجرای متد بر روی state کل کلاس خواهد گذاشت، از دید ما پنهان است و باعث به وجود آمدن مشکلات بعدی خواهد شد. برای مثال قطعه کد بالا را به صورت Honest بازنویسی میکنیم: 

    public class UserProfile
    {
        private readonly User _user;
        private readonly string _address;

        public UserProfile(User user,string address)
        {
            _user = user;
            _address = address;
        }
        public UserProfile UpdateUser(int userId, string name)
        {
            var newUser = new User(userId, name);
            return  new UserProfile(newUser,_address);
        }
    }

    public class User
    {
        public User(int id, string name)
        {
            Id = id;
            Name = name;
        }

        public int Id { get; }
        public string Name { get; }
    }

در این مثال متد UpdateUser به جای  void، یک شی از جنس کلاس UserProfile را بر می‌گرداند. کلاس UserProfile هم برای وهله سازی نیاز به یک شیء از جنس User و Address را دارد. بنابراین مطمئن هستیم که مقدار دهی شده‌اند. نکته دیگر در قطعه کد بالا این است که به ازای هر بار فراخوانی متد، یک شیء جدید بدون وابستگی به وهله سازی اشیاء دیگر، برگردانده میشود.


Immutable بودن باعث می‌شود: 

  • خوانایی کد افزایش پیدا کند
  • جای واحدی برای Validate کردن داشته باشیم
  • به صورت ذاتی Thread Safe باشیم


در مورد محدودیت‌هایی که در کار با اشیاء Immutable باید در نظر داشته باشیم، می‌توان به مصرف بالای رم و سی پی یو، اشاره کرد. در واقع به نسبت حالت mutate، تعداد اشیاء بیشتری ساخته خواهند شد. در فریمورک دات نت برای کار با اشیا immutable امکاناتی در نظر گرفته شده که این هزینه را کاهش می‌دهند. به طور مثال می‌توانیم از کلاس ImmutableList استفاده کنیم و از ایجاد اشیاء اضافه‌تر و تحمیل بار اضافی به GC جلوگیری کنیم. یک مثال: 

//Create Immutable List
ImmutableList<string> list = ImmutableList.Create<string>();
ImmutableList<string> list2 = list.Add("Salam");

//Builder
ImmutableList<string>.Builder builder = ImmutableList.CreateBuilder<string>();
builder.Add("avali");
builder.Add("dovomi");
builder.Add("sevomi");

ImmutableList<string> immutableList = builder.ToImmutable();


چطور با side effect کنار بیایم؟ 

یکی از الگوهای رایج برای این کار، مفهوم جدا سازی Command/Query است. به طور ساده تمامی عملیاتی را که تاثیر گذار هستند، به صورت Command در نظر میگیریم. Command ‌ها معمولا هیچ نوعی را بازگشت نمیدهند و همینطور بر عکس این قضیه برای Query ‌ها صادق است. اشتباه رایج درباره این الگو، محدود کردن این الگو به معماری‌های خاصی مانند Domain Driven می‌باشد؛ در صورتیکه الزامی برای رعایت این الگو در سایر معماری‌ها وجود ندارد. 

به مثال زیر دقت کنید. سعی کردم قسمت‌های Command و Query را از هم جدا کنم: 

در واقع هر برنامه می‌تواند شامل دو قسمت باشد:

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

در واقع یک هسته Immutable، ورودی را دریافت کرده و خروجی‌های مورد نیاز را تولید میکند و همه این‌ها در دل یک پوستهMutable پیاده سازی می‌شوند که ما در اینجا به آن اصطلاحا Mutable Shell میگوییم.   

برای مسائلی که در بالا صحبت شد، نمونه‌‌ای را آماده کرده‌ام. این نمونه به طور ساده یک سیستم مدیریت نوبت است که نوبت‌ها را در فایلی ذخیره و بازیابی میکند ( mutate ) و منطق مربوط به نوبت‌ها و زمان ویزیت آن میتواند به صورت immutable پیاده سازی شود. این کد در دو حالت functional و غیر functional پیاده سازی شده تا به خوبی تفاوت آن را در حالت قبل و بعد از برنامه نویسی تابعی بتوانیم درک کنیم. به جهت خوانایی بیشتر و دسترسی به کد‌ها، آن‌ها را روی گیت‌هاب قرار داده و شما میتوانید از اینجا سورس کد مورد نظر را بررسی کنید. سعی شده در این مثال تمامی مواردی که در این قسمت ذکر شد را پیاده سازی کنیم. امیدوارم که مطالب مربوط به برنامه نویسی تابعی یا functional programming توانسته باشد دیدگاه جدیدی را به کدهایی که مینویسیم بدهد. در  قسمت‌های بعدی به مواردی مانند مدیریت exception ‌ها و کار با null ‌ها و ... خواهیم پرداخت.

نظرات مطالب
آموزش سیلورلایت 4 - قسمت‌های 21 تا 27
دست شما درد نکند. کار بسیار بزرگی انجام می‌دهید. امیدوارم همیشه پاینده باشید.
مطالب
رفع مشکل نصب به روز رسانی ASP.NET MVC 3

در مورد نحوه نصب پیشنیازهای ASP.NET MVC 3 پیشتر توضیح داده شد. یک روش دیگر هم برای اینکار مهیا است؛ اگر به مشکل برخوردید حین نصب.
  • دریافت ASP.NET MVC 3 RTM (نگارش RTM یعنی نگارش نهایی ارائه شده به صنعت)
  • دریافت ASP.NET MVC 3 Tools Update (این هم شامل یک سری به روز رسانی است؛ مثلا قالب اینترانت به آن اضافه شده و غیره)

اگر حین نصب ASP.NET MVC 3 Tools Update، کارها خوب پیش نرفت و دست آخر زمانیکه فایل log خطا را باز کردید در پایان آن ذکر شده بود « Installation failed with error code: 0x80070643» باید این مراحل را طی کنید:
الف) فایل نصاب را با استفاده از برنامه 7-zip آنپک کنید (فایل‌های داخلی آن‌را استخراج کنید).
ب) فایل VS10-KB*.msp یا vs10-kb*.msi را یافته و بر روی آن کلیک راست کنید. گزینه install را از منوی باز شده انتخاب نمائید تا کار نصب شروع شود.
ج) در حین نصب این پیغام را دریافت خواهید کرد: «can't find vs_setup.msi». در این حالت بر روی دکمه browse کلیک کرده و این فایل vs_setup.msi را که در DVD نصب خود VS 2010 اصلی وجود دارد به آن معرفی کنید. علت هم به این بر می‌گردد که پروسه نصب VS 2010 شما از ابتدا ناقص بوده است و نیاز است یک سری فایل دیگر در ابتدا بر روی سیستم نصب گردند.
اکنون کار نصب بدون مشکل پیش خواهد رفت. لازم به ذکر است که این پیغام خطا را حین عملیات نصب معمولی MVC3 «دریافت نخواهید کرد».
د) سپس یکبار دیگر هم فایل setup.exe اصلی را بدون مراحل فوق اجرا کنید تا خیالتان از این بابت راحت شود و تمام موارد نصب نشده نیز نصب گردند (مهم!).

این مراحل باید مشکل را حل کنند، در غیراینصورت یک سری راه حل دیگر هم در اینجا ذکر شده است: http://support.microsoft.com/kb/2531566
و خلاصه آن این است که فایل C:\Windows\Microsoft.NET\Framework\v4.0.30319\web.config را پیش از نصب rename کنید و اجازه دهید تا نصاب یک نمونه جدید را ایجاد کند.

پروژه‌ها
TaskoMask : سیستم مدیریت تسک سورس باز مبتنی بر NET 6, Microservices, DDD
توضیح
پروژه‌ی سورس باز TaskoMask یک سیستم مدیریت تسک رایگان می‌باشد که سورس آن را می‌توانید از طریق این آدرس در گیت هاب مشاهده کنید. همچنین این پروژه از طریق آدرس taskomask.ir نیز در دسترس میباشد.
برای استفاده مفیدتر از این پروژه، داکیومنت‌های مورد نیاز شامل بخش دامین، معماری، وب سرویس و ... ایجاد شده اند که از طریق ریپازیتوری این پروژه بر روی گیت هاب در دسترس هستند.


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


ویژگی ها
در اینجا برخی ویژگی‌های مهم این پروژه را بررسی میکنیم ولی برای اطلاع از لیست دقیق ویژگی‌ها و امکانات این پروژه و نیز دسترسی به داکیومنت‌ها و بورد‌های آن، بهتر است به ریپازیتوری آن در گیت هاب مراجعه کنید.
NET 6.
#C
Blazor
 ASP.NET Web API
ASP.NET MVC
MongoDB
Redis
MediatR
Microservices
 DDD
CQRS
Event Sourcing
Notification
Repository
Onion Architecture 
 Acceptance Testing
Integration Testing
Unit Testing
UI Testing
E2E Testing


درخواست مشارکت
پروژه تا حد امکان به عنوان یک پروژه‌ی واقعی ولی مینیمال طراحی شده تا نگهداری آن در بلند مدت میسر بوده و همچنین کلیه داکیومنت‌های مورد نیاز فراهم شده تا امکان مشارکت در آن آسان‌تر شود.
از تمام برنامه نویسان عزیز با هر سطح از توانایی دعوت میکنیم که در توسعه‌ی این پروژه به عنوان یک مرجع کوچک برای کمک به مسیر رشد سایر برنامه نویسان مشارکت کنند.


درخواست حمایت 
اگر این پروژه به هر نحوی برای شما سودمند بود با دادن یک ستاره ⭐ در گیت هاب ما را حمایت کنید.


تصاویر پروژه

مطالب
Minimal API's در دات نت 6 - قسمت پنجم - پیاده سازی الگوی CQRS
تا قسمت قبل موفق شدیم فایل Program.cs برنامه‌ی Minimal API's را خلوت کنیم و همچنین زیرساختی را برای توسعه‌ی مبتنی بر ویژگی‌ها، ارائه دهیم. اما ... هنوز endpoints ما چنین شکلی را دارند:
        endpoints.MapGet("/api/authors", async (MinimalBlogDbContext ctx) =>
        {
            var authors = await ctx.Authors.ToListAsync();
            return authors;
        });

        endpoints.MapPost("/api/authors", async (MinimalBlogDbContext ctx, AuthorDto authorDto) =>
        {
            var author = new Author();
            author.FirstName = authorDto.FirstName;
            author.LastName = authorDto.LastName;
            author.Bio = authorDto.Bio;
            author.DateOfBirth = authorDto.DateOfBirth;

            ctx.Authors.Add(author);
            await ctx.SaveChangesAsync();

            return author;
        });
 و یک چنین رویه‌ای جهت کار مستقیم با DbContext در اکشن متدهای MVC هیچگاه توصیه نمی‌شود. برای مثال به طور معمول، عملیاتی که در بدنه‌ی Lambda expressions فوق انجام شده، عموما به Repositories و Services محول شده و در نهایت از سرویس‌ها، در اکشن متدها استفاده می‌شود. در معماری جاری که در پیش گرفته‌ایم، دو لایه‌ی Repositories و Services حذف شده‌اند و دیگر خبری از آن‌ها نیست. در اینجا کار سرویس‌ها و مخازن، به هندلرهای معماری CQRS واگذار خواهند شد. هر هندلر نیز متکی به خود است و مستقل از سایر هندلرها طراحی می‌شود و این‌ها صرفا بر اساس نیازهای ویژگی جاری توسعه خواهند یافت و دقیقا در همان پوشه‌ی ویژگی مورد بررسی نیز قرار می‌گیرند؛ و نه پراکنده در لایه‌ای و یا پروژه‌ای دیگر. به این ترتیب درک یک ویژگی متکی به خود برنامه، ساده‌تر شده و در طول زمان، نگهداری و توسعه‌ی آن نیز ساده‌تر خواهد شد. مشکل داشتن سرویس‌هایی بزرگ که در معماری‌های متداول وجود دارند، استفاده‌ی از متدهای آن‌ها در چندین اکشن متد و چندین کنترلر مختلف است و اگر یکی از متدهای این سرویس بزرگ ما تغییر کند، بر روی چندین کنترلر تاثیر می‌گذارد که ممکن است سبب از کار افتادگی بعضی از آن‌ها شود؛ اما در اینجا هرکاری که انجام می‌شود و هر هندلری که توسعه می‌یابد، فقط مختص به یک کار و یک ویژگی مشخص است.


ایجاد Command و هندلر مخصوص ایجاد یک نویسنده‌ی جدید


در الگوی CQRS، یک دستور، کاری را بر روی بانک اطلاعاتی انجام می‌دهد. برای مثال در اینجا قرار است نویسنده‌ای را ثبت کند. در ادامه می‌خواهیم بدنه‌ی endpoints.MapPost فوق را با الگوی CQRS انطباق دهیم. به همین جهت به یک Command نیاز داریم:
using MediatR;
using MinimalBlog.Domain.Model;

namespace MinimalBlog.Api.Features.Authors;

public class CreateAuthorCommand : IRequest<Author>
{
    public AuthorDto AuthorDto { get; set; } = default!;
}
اینترفیس IRequest کتابخانه‌ی MediatR که در انتهای قسمت قبل به پروژه اضافه شد، چنین امضایی را دارد:
public interface IRequest<out TResponse> : IBaseRequest
یعنی <IRequest<Author به این معنا است که قرار است «خروجی» این عملیات، یک Author باشد و CreateAuthorCommand می‌تواند شامل تمام خواصی باشد که در جهت برآورده کردن این دستور مورد نیاز هستند؛ برای مثال کل اطلاعات شیء AuthorDto در اینجا.

سپس نیاز به یک هندلر است تا دستور رسیده را پردازش کند:
namespace MinimalBlog.Api.Features.Authors;

public class CreateAuthorCommandHandler : IRequestHandler<CreateAuthorCommand, Author>
{
    private readonly MinimalBlogDbContext _context;
    private readonly IMapper _mapper;

    public CreateAuthorCommandHandler(MinimalBlogDbContext context, IMapper mapper)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
        _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
    }

    public async Task<Author> Handle(CreateAuthorCommand request, CancellationToken cancellationToken)
    {
        if (request == null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        var toAdd = _mapper.Map<Author>(request.AuthorDto);
        _context.Authors.Add(toAdd);
        await _context.SaveChangesAsync(cancellationToken);
        return toAdd;
    }
}
اینترفیس IRequestHandler چنین امضایی را دارد:
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse>
که اولین آرگومان جنریک آن، همان Command ای است که قرار است پردازش کند و خروجی آن، اطلاعاتی است که قرار است بازگشت دهد. یعنی متد Handle فوق، قرار است عملیات endpoints.MapPost را پیاده سازی کند و در اینجا با استفاده از AutoMapper، انتساب‌های آن حذف و ساده شده‌اند و مابقی آن، با بدنه‌ی lambda expression مربوط به endpoints.MapPost، یکی است. این هندلر، معادل یک یا چند متد از متدهای یک سرویس بزرگ است که در اینجا به صورت اختصاصی جهت پردازش فرمانی در کنار هم قرار می‌گیرند و متکی به خود هستند.

پس از این تغییرات، بدنه‌ی lambda expression مربوط به endpoints.MapPost به صورت زیر تغییر کرده و ساده می‌شود:
endpoints.MapPost("/api/authors", async (IMediator mediator, AuthorDto authorDto) =>
{
     var command = new CreateAuthorCommand { AuthorDto = authorDto };
     var author = await mediator.Send(command);
     return author;
});
در اینجا تزریق وابستگی IMediator را مشاهده می‌کنید. با فراخوانی متد Send آن، شیء‌ای به هندلر متناظری ارسال شده، پردازش می‌شود و در نهایت شیءای را بازگشت خواهد داد. برای مثال در اینجا شیء Dto یک نویسنده به هندلر CreateAuthorCommandHandler ارسال و تبدیل به شیءای از نوع Author مربوط به دومین برنامه شده، سپس در بانک اطلاعاتی ذخیره می‌شود و در نهایت این نویسنده که اکنون به همراه یک Id نیز هست، بازگشت داده می‌شود. بنابراین هر هندلر یک object in و یک object out دارد که به عنوان آرگومان‌های جنریک IRequestHandler تعریف می‌شوند.



نکته 1: await داخل بدنه‌ی lambda expression مربوط به endpoints را فراموش نکنید. تمام متدهای IMediator از نوع aysnc هستند؛ هرچند روش نامگذاری SendAsync را رعایت نکرده‌اند و اگر این await فراموش شود، مشاهده خواهید کرد که برنامه در حین فراخوانی endpoints در مرورگر، در حالت هنگ و صبر کردن نامحدود قرار می‌گیرد، بدون اینکه کاری را انجام دهد و یا حتی استثنایی را صادر کند.


نکته 2: در پیاده سازی هندلر، استفاده از cancellationToken را نیز مشاهده می‌کنید. تقریبا تمام متدهای async مربوط به EF-Core به همراه پارامتری جهت دریافت cancellationToken هم هستند. اگر کاربری قصد لغو یک درخواست طولانی را داشته باشد و بر روی دکمه‌ی stop مرورگر کلیک کند و یا حتی صفحه را چندین بار ریفرش کند، این به معنای abort درخواست(های) رسیده‌است. وجود این cancellationTokenها، بار سرور را کاهش داده و عملیات در حال اجرای سمت سرور را در یک چنین حالت‌هایی متوقف می‌کند.
البته هندلری که در اینجا تعریف شده، این cancellationToken را باید از mediator دریافت کند که در کدهای endpoint فوق، چنین نیست. برای رفع این مشکل باید به صورت زیر عمل کرد:
endpoints.MapGet("/api/authors", async (IMediator mediator, CancellationToken ct) =>
        {
            var request = new GetAllAuthorsQuery();
            var authors = await mediator.Send(request, ct);
            return authors;
        });
این مورد را می‌توان به صورت یک best practice، به تمام endpoints اضافه کرد.


نکته 3: هندلرها عموما چیزی را بازگشت نمی‌دهند؛ صرف نظر از هندلر فوق که نیاز بوده تا Id شیء ذخیره شده را بازگشت دهد، عموما به همراه هیچ خروجی نیستند. به همین جهت در حین تعریف آن‌ها فقط کافی است در آرگومان‌های جنریک آن‌ها، نوع خروجی را ذکر نکنیم:
public class Handler : IRequestHandler<Command>
در یک چنین حالتی، امضای IRequestHandler به صورت خودکار به همراه خروجی از نوع Unit خواهد بود:
public interface IRequestHandler<in TRequest> : IRequestHandler<TRequest, Unit> where TRequest : IRequest<Unit>
که این Unit معادل Void در کتابخانه‌ی mediator است و به نحو زیر در هندلرها مدیریت می‌شود:
public async Task<Unit> Handle(Command request, CancellationToken cancellationToken)
{
   // ...
   return Unit.Value;
}
در یک چنین حالتی، تعریف یک Command نیز بر اساس اینترفیس IRequest انجام می‌شود:
public class Command : IRequest


ایجاد Query و هندلر مخصوص بازگشت لیست نویسنده‌‌ها

در الگوی CQRS، یک کوئری قرار است اطلاعاتی را بازگشت دهد و ... وضعیت بانک اطلاعاتی را تغییر نمی‌دهد. بنابراین در اینجا یک IRequest که قرار است لیستی از نویسندگان را بازگشت دهد، تعریف می‌کنیم. بدنه‌ی آن هم می‌تواند خالی باشد و یا به همراه خواصی مانند اطلاعات صفحه بندی و یا مرتب سازی گزارشگیری رسیده‌ی از درخواست:
using MediatR;
using MinimalBlog.Domain.Model;

namespace MinimalBlog.Api.Features.Authors;

public class GetAllAuthorsQuery : IRequest<List<Author>>
{
}
سپس نیاز به یک هندلر است تا درخواست رسیده را پردازش کند. این هندلر، کوئری فوق را دریافت کرده و لیست کاربران را بازگشت می‌دهد:
namespace MinimalBlog.Api.Features.Authors;

public class GetAllAuthorsHandler : IRequestHandler<GetAllAuthorsQuery, List<Author>>
{
    private readonly MinimalBlogDbContext _context;

    public GetAllAuthorsHandler(MinimalBlogDbContext context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    public Task<List<Author>> Handle(GetAllAuthorsQuery request, CancellationToken cancellationToken)
    {
        return _context.Authors.ToListAsync(cancellationToken);
    }
}
پس از این تغییرات، بدنه‌ی lambda expression مربوط به endpoints.MapGet به صورت زیر تغییر کرده و ساده می‌شود:
endpoints.MapGet("/api/authors", async (IMediator mediator) =>
{
   var request = new GetAllAuthorsQuery();
   var authors = await mediator.Send(request);
   return authors;
});
مزیت استفاده‌ی از الگوی CQRS، تنها به حذف لایه‌ی سرویس و رسیدن به ویژگی‌هایی مستقل و متکی به خود، منحصر نیست. با استفاده از این الگو می‌توان مقیاس پذیری برنامه را نیز افزایش داد. برای مثال یک بانک اطلاعاتی بهینه سازی شده را صرفا برای کوئری‌ها، درنظر گرفت و بانک اطلاعاتی دیگری را تنها برای اعمال Write که Commands بر روی آن اجرا می‌شوند و در اینجا تنها نیاز به همگام سازی اطلاعات بانک اطلاعاتی Write، با بانک اطلاعاتی Read است که در بسیاری از اوقات پرکارتر از بانک‌های اطلاعاتی دیگر است:


و یا حتی معماری CQRS با معماری Event store نیز قابل ترکیب است:


در اینجا بجای استفاده از بانک اطلاعاتی Write، از یک Event store استفاده می‌شود. کار event store، دریافت رویدادهای write است و سپس باز پخش آن‌ها به بانک اطلاعاتی Read؛ تا کار همگام سازی به این نحو صورت گیرد.


روشی برای نظم دادن به نحوه‌ی تعریف کلاس‌های الگوی CQRS

تا اینجا برای مثال کلاسCreateAuthorCommand  را در یک فایل مجزا و سپس هندلر آن‌را به نام CreateAuthorCommandHandler در یک فایل دیگر تعریف کردیم. می‌توان جهت بالابردن خوانایی برنامه، کاهش رفت و برگشت‌ها برای یافتن کلاس‌های مرتبط و همچنین سهولت یافتن هندلرهای مرتبط با هر متد mediator.Send، از روش زیر نیز استفاده کرد:
public static class CreateAuthor
{
    public class Command : IRequest<AuthorGetDto>
    {
        // ...
    }

    public class Handler : IRequestHandler<Command, AuthorGetDto>
    {
       // ...
    }
}
در اینجا از nested classes استفاده شده‌است. ابتدا نام اصلی Command و یا کوئری ذکر می‌شود؛ که نام کلاس دربرگیرنده‌ی اصلی را تشکیل می‌دهد. سپس دو کلاس بعدی فقط Command و Handler نام می‌گیرند و نه هیچ نام دیگری. به این ترتیب به یکسری نام یک دست در کل پروژه خواهیم رسید. زمانیکه قرار است mediator.Send فراخوانی شود، اینبار چنین شکلی را پیدا می‌کند که مزیت آن، سهولت یافتن هندلر مرتبط، فقط با پیگیری کلاس اصلی CreateAuthor است:
var command = new CreateAuthor.Command { AuthorDto = authorDto };
var author = await mediator.Send(command, ct);

در مورد کوئری‌ها هم می‌توان به قالب مشابهی رسید که در اینجا هم کوئری و هندلر آن، ذیل نام اصلی مدنظر قرار می‌گیرند:
public static class GetAllAuthors
{
    public class Query : IRequest<List<AuthorGetDto>>
    {
       //...
    }

    public class Handler : IRequestHandler<Query, List<AuthorGetDto>>
    {
       //...
    }
}
و اگر کدهای نهایی این سری را که از قسمت اول قابل دریافت است بررسی کنید، از همین ساختار یکدست، برای تعاریف دستورات و کوئری‌ها استفاده شده‌است.
مطالب
روشی سریع برای ایجاد RSS و Sitemap در ASP.NET MVC
برای مطالعه روش‌های بدست آوردن خروجی xml مربوط به Rss و Sitemap،  میتوانید از مقالات مشخص شده استفاده کنید .[اینجا] و [اینجا].

در صورتیکه طراحی شما بر اساس MVC صورت گرفته است، در کمتر از چند دقیقه و در سه مرحله میتوانید پرونده Rss و Sitemap را برای همیشه ببندید. 

پیش از تشریح مراحل، به ساختار این دو فایل توجه کنید. 

مراحل کار : 

مرحله 1. ایجاد نوع(Type) مورد نیاز برای ایجاد Xml ‌های فوق

مرحله 2 . ایجاد کنترلر XML

مرحله 3. ایجاد مسیریابی(Routing)


مرحله 1 : ابتدا یک کلاس به منظور شکل دهی به اطلاعات، بر اساس خواسته‌های xml مرتبط با RSS و Sitemap تشکیل دهید:

public class PostToXml
    {
        
        public int PostId { get; set; }

        public string title { get; set; }

        public string link { get; set; }
        
        public string description { get; set; }

        public Nullable<DateTime> pubDate { get; set; }

      
    }

مرحله 2 : یک کنترلر به نام xml ایجاد کنید و اکشن متدهای زیر را درون آن قرار دهید :

       public ContentResult RSS()
        {
            
            var items = GetRssFeed();
            var rss = new XDocument(new XDeclaration("1.0", "utf-8", "yes"),
              new XElement("rss",
                new XAttribute("version", "2.0"),
                  new XElement("channel",
                    new XElement("title", "آخرین مطالب سایت"),
                    new XElement("link", "http://" + Request.Url.Host+"/rss"),
                    new XElement("description", "آخرین مطالب سایت من"),
                    new XElement("copyright","(c)" + DateTime.Now.Year + ", نام سایت من.تمامی حقوق محفوظ است"),
                  from item in items
                  select
                  new XElement("item",
                    new XElement("title", item.title),
                    new XElement("description", item.description),
                    new XElement("link", item.link),
                    new XElement("pubDate", item.pubDate)

                  )
                )
              )
            );
            return Content(rss.ToString(), "text/xml");
        }



        public ContentResult Sitemap()
        {
            XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
            var items = GetLinks();
            var sitemap = new XDocument(new XDeclaration("1.0", "utf-8", "yes"),
                new XElement(ns + "urlset",
                    from item in items
                    select
                    new XElement("url",
                      new XElement("loc", item.link),
                      new XElement("changefreq", "monthly"),
                      new XElement("priority", "0.5")
                      )
                    )
                  );
            return Content(sitemap.ToString(), "text/xml");
        }


        public IEnumerable<PostToXml> GetRssFeed()
        {
          // یک کوئری که لیستی از تایپ مشخص شده به ما بدهد
        }

         public IEnumerable<PostToXml> GetLinks()
        {
            // یک کوئری که لیستی از تایپ مشخص شده به ما بدهد
        }

این کنترلر دارای دو اکشن متد Rss و Sitemap است و این اکشن‌ها وظیفه‌ی ایجاد فایل‌های Xml را به عهده دارند. مواد اولیه این xml ‌ها از دو متد GetRssFeed و GetLinks تهیه می‌شوند. ما این مواد را در تمپلیت Rss و Sitemap جایگذاری خواهیم کرد. (به کمک دو اکشن متد Rss و Sitemap )

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

مرحله 3 : در مرحله آخر کافیست دو مورد زیر را به فایل RoutConfig.cs بیافزایید: 

routes.MapRoute(
              "Sitemap", "sitemap",
              new { controller = "XML", action = "Sitemap" });
 routes.MapRoute(
              "RSS", "rss",
               new { controller = "XML", action = "RSS" });

به کمک آدرس‌های زیر می‌توانید به آنچه که تهیه کرده‌اید دسترسی داشته باشید :

http://domain.com/rss
http://domain.com/sitemap  

فایل پروژه را دریافت کنید :

MVC_RSS_Sitemap-43ad3c6681734b34b91deaaabcdba871.rar 

مطالب
یکپارچه سازی TortoiseSVN و YouTrack
پیش نیاز
اگر در مورد TortoiseSVN و سورس کنترل اطلاعات پایه ندارید، کتاب  مدیریت فایلهای یک پروژه نرم افزاری با استفاده از Subversion  آقای نصیری را مطالعه کنید و همچنین سیستم پیگیری خطای  YouTrack را نگاهی بیاندازید (البته اگر اطلاعی ندارید).

مقدمه
هنگام کار روی یک پروژه، باگ ها، وظیفه‌ها و موضوعاتی به شما واگذار می‌شود که باید انها را انجام دهید. هنگام commit کردن تغییرات، برای مشخص شدن اینکه تغییرات مربوط به کدام Bug-Id بوده است بود است باید سیستم Bug/Issue Tracker رو با سورس کنترل یکپارچه کنیم. 

یکپارچه سازی TortoiseSVN و YouTrack
1- روی یک نسخه کاری پروژه راست کلیک، از منوی TortoiseSVN گزینه Properties را انتخاب کنید.

گزینه Properties در TortoiseSVN

2- از پنجر باز شده دکمه New، گزینه Other را انتخاب کنید. در پنجره باز شده از منوی کشویی مربوط به Property Name، مقادیر خصلت‌های زیر را تنظیم کنید:
bugtraq:url : آدرس YouTrack Sever که به این صورت وارد می‌شود: %http://localhost:8080/issue/%BUGID
bugtraq:message : درو اقع الگویی پیامی هست که برای نگهداری Bug-Id استفاده می‌شود و باید شامل کلمه %BUGID% باشد. مثلا: %Issue: %BUGID
bugtraq:number : مقدار این خصلت را false وارد کنید؛ چون Bug-Idهای‌های YouTrack می‌توانند شامل عدد و حروف باشند.

دیالوگ Propeties


بعد از اینکه این سه خصلت را مقداردهی کرید، تغییرات را Commit کنید. همانطور که می‌بینید یک Textbox (بالا، سمت راست) اضافه شده که محل وارد کردن Bug-Id مربوط به تغییرات است. از این پس، می‌توانید Bug-Id یا Issue-Id‌های مربوط به هر تغییرات را در آن Textbox وارد کنید.


همچنین تغییرات در پلاگین AnkhSVN در ویژال استودیو نیز اعمال می‌شود:


اکنون، در متن commitها شماره Bud-Id نیز ذکر شده است.


نکته 1: اگر YouTrack روی یک سرور نصب هست، بجای localhost نام کامپیوتر سرور یا آی پی آن را وارد کنید. پورت 8080 نیز بصورت پیش فرض است و اگر هنگام نصب آن را تغییر داده اید، اینجا نیز آنرا تغییر دهید.
نکته 2: خصلت bugtraq:message یک الگوی پیام از شما می‌گیرد؛ یعنی الگو را تحت هر شکلی می‌توان وارد کرد. بعنوان مثال الگو را به این شکل وارد کنید: "برای مشاهده جزئیات بیشتر به Bug-Id شماره %BUGID% مراجعه کنید."
نکته 3: اگر خصلت bugtraq:number مقدارش true باشد، برای وارد کردن Bug-Id فقط از عدد می‌توانید استفاده کنید. بصورت پیش فرض مقدار این خصلت true است.
نکته 4: می‌توانید این تنظیمات را در یک فایل Export کنید و در بقیه پروژه ها، با یک مرحله و بسادگی آنرا Import کنید.
خصلت‌های دیگری نیز می‌توان برروی مخزن کد اعمال کرد که از حوزه این مقاله خارج است. همچنین تنظمیات اختیاری جانبی دیگری نیز برای یکپارچه سازی وجود دارند. برای دیدن این تنظمیات روی نسخه کاری راست کلیک، از منوی TortoiseSVN گزینه Properties را انتخاب کنید و از پنجره باز شده روی دکمه New و گزینه ( Bugtraq (Issue tracker integration  را انتخاب کنید.

برای اطلاعات بیشتر در مورد این تنظیمات، داکیومنت یکپارچه سازی با سیستم‌های Bug tracking / Issue Tracking  را مطالعه کنید. 
مطالب
چطور مسیریابی‌های ASP.NET MVC را دیباگ کنیم؟
سؤال: من برای تهیه sitemap برنامه، یک route سفارشی نوشته‌ام تا یک فایل xml ایی را که در وب سرور، وجود خارجی ندارد، در آدرس‌های سایت قابل دسترسی کند. برای مثال:
            routes.MapRoute(
                "SiteMap_route", // Route name
                "sitemap.xml", // URL with parameters
                new { controller = "Sitemap", action = "index", name = UrlParameter.Optional, area = "" } // Parameter defaults
            );
با استفاده از این مسیریابی خاص، قرار است هر زمانیکه آدرس http://site/sitemap.xml در مرورگر وارد شد، برنامه در پشت صحنه، به صورت خودکار به کنترلر sitemap و اکشن متد index آن مراجعه کرده و یک محتوای پویای XML ایی را تولید کند و بازگشت دهد. اما ... کار نمی‌کند! یعنی آدرس یاد شده اصلا پاسخ نمی‌دهد. چرا؟ نحوه‌ی ثبت مسیریابی سفارشی تعریف شده نیز به صورت زیر است:
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

            routes.MapRoute(
                "SiteMap_route", // Route name
                "sitemap.xml", // URL with parameters
                new { controller = "Sitemap", action = "index", name = UrlParameter.Optional, area = "" } // Parameter defaults
            );
        }
پاسخ: اگر با تقدم و تاخر و معنای مسیریابی‌های تعریف شده آشنایی داشته باشید، شاید بلافاصله بتوانید مشکل را حدس بزنید. اما اگر تعداد مسیریابی‌های سفارشی تعریف شده زیاد باشد، اینکار ساده نیست و حتما نیاز به ابزار دیباگ دارد تا بتوان تشخیص داد که در صفحه جاری کدامیک از مسیریابی‌های تعریف شده کار را تمام کرده‌اند و نوبت به دیگری نرسیده است.

برای این منظور می‌توان از افزونه‌ای به نام RouteDebug نوشته یکی از اعضای سابق تیم ASP.NET MVC استفاده کرد:
کار کردن با آن نیز بسیار ساده است.
الف) ارجاعی را به اسمبلی RouteDebug.dll (حاصل از کامپایل پروژه فوق) به پروژه جاری ASP.NET MVC خود اضافه کنید.
ب) سپس به فایل Global.asax.cs خود مراجعه و در سطر آخر متد Application_Start آن، فراخوانی ذیل را اضافه نمائید:
 RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
اکنون هر صفحه و آدرسی را که باز کنید، بجای محتوای اصلی صفحه، مسیریابی‌های فعال و برنده آن‌را مشاهده خواهید کرد. برای مثال در صفحه اول برنامه داریم:


نکته مهمی که در این تصویر باید به آن دقت داشت، اولین True سبز رنگی است که نمایش می‌دهد. یعنی اولین مسیریابی که کار هدایت و نمایش صفحه جاری را برعهده دارد. در اجرای عادی ASP.NET MVC، همینجا کار پردازش سیستم مسیریابی صفحه جاری خاتمه خواهد یافت و نوبت به سایرین نخواهد رسید.
در مورد صفحه sitemap.xml چطور؟ اگر این آدرس را در مرورگر، بدون فعال سازی افزونه RouteDebug وارد کنیم، پیام 404 را دریافت می‌کنیم. اگر افزونه را فعال کنیم، اینبار به صفحه زیر خواهیم رسید:


بله. همانطور که مشاهده می‌کنید، مسیریابی پیش فرض، اینبار نیز برنده بوده است و اولین تطابق صورت گرفته با آن صورت می‌گیرد. بنابراین اصلا کار به استفاده از مسیریابی سفارشی تعریف شده توسط ما نخواهد رسید.
بنابراین محل تعریف این مسیریابی را اکنون به پیش از مسیریابی پیش فرض انتقال می‌دهیم:
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "SiteMap_route", // Route name
                "sitemap.xml", // URL with parameters
                new { controller = "Sitemap", action = "index", name = UrlParameter.Optional, area = "" } // Parameter defaults
            );

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );      
        }
در ادامه اگر مجددا مسیر sitemap.xml را درخواست کنیم، به تصویر ذیل خواهیم رسید:


بله. با این تنظیم صورت گرفته، اینبار دیگر سیستم مسیریابی، برای تفسیر مسیر سفارشی تعریف شده، به سراغ مسیریابی پیش فرض نخواهد رفت و کار همینجا خاتمه می‌یابد.

سؤال: آیا اینکار تداخلی در عملکرد اصلی برنامه ایجاد نمی‌کند؟ مثلا اگر به مسیر index کنترلر home مراجعه کنیم، مشکلی نخواهد بود؟
پاسخ: خیر. برای آزمایش آن باز هم به افزونه RouteDebug مراجعه خواهیم کرد:


همانطور که مشخص است، مسیریابی برنده در این حالت، همان مسیریابی پیش فرض است و نه مسیریابی سفارشی آدرس خاص sitemap.xml سایت.


یک نکته تکمیلی
افزونه گلیمپس نیز امکان دیباگ Routeها را دارد؛ اما توانایی بررسی مشکلات Routing یک خطای 404 مانند مثال فوق را حداقل تا زمان نگارش این مطلب ندارد و همان افزونه RouteDebug یاد شده، بهتر عمل می‌کند.
مطالب
آموزش TypeScript #5
در ادامه مباحث شی گرایی در TypeScript قصد داریم به مباحث مربوط به interface و طریقه استفاده از آن بپردازیم. همانند زبان‌های دات نتی در TypeScript نیز به راحتی می‌توانید interface تعریف کنید. در یک اینترفیس اجازه پیاده سازی هیچ تابعی را ندارید و فقط باید عنوان و پارامتر‌های ورودی و نوع خروجی آن را تعیین کنید. برای تعریف اینترفیس از کلمه کلیدی interface به صورت زیر استفاده خواهیم کرد.
export interface ILogger {
  log(message: string): void;
}
همان طور در پست‌های قبلی مشاهده شد از کلمه کلیدی export برای عمومی کردن اینترفیس استفاده می‌کنیم. یعنی این اینترفیس از بیرون ماژول خود نیز قابل دسترسی است.
حال نیاز به کلاسی داریم که این اینترفیس را پیاده سازی کند. این پیاده سازی به صورت زیر انجام می‌گیرد:
export class Logger implements ILogger
{
}
یا:
export class AnnoyingLogger implements ILogger {
   log(message: string): void{
         alert(message);
     } 
}
همانند دات نت یک کلاس می‌تواند چندین اینترفیس را پیاده سازی کند.(اصطلاحا به این روش explicit implementation یا پیاده سازی صریح می‌گویند)
export class MyClass implements IFirstInterface, ISecondInterface
{

}
*یکی از قابلیت جالب و کارآمد زبان TypeScript این است که در هنگام کار با اینترفیس‌ها حتما نیازی به پیاده سازی صریح نیست. اگر یک object تمام متغیر‌ها و توابع مورد نیاز یک اینترفیس را پیاده سازی کند به راحتی همانند روش explicit emplementation می‌توان از آن object استفاده کرد.  به این قابلیت Duck Typing  می‌گویند. مثال:
IPerson {
   firstName: string;
   lastName: string;
} 
class Person implements IPerson {
  constructor(public firstName: string, public lastName: string) {
  }
}

varpersonA: IPerson = newPerson('Masoud', 'Pakdel'); //expilict
varpersonB: IPerson = { firstName: 'Ashkan', lastName: 'Shahram'}; // duck typing
همان طور که می‌بینید object  دوم به نام personB تمام متغیر‌ها ی مورد نیاز اینترفیس IPerson را پیاده سازی کرده است در نتییجه کامپایلر همان رفتاری را که با object اول به نام personA دارد را با آن نیز خواهد داشت.

پیاده سازی چند اینترفیس به صورت همزمان
همانند دات نت که یک کلاس فقط می‌تواند از یک کلاس ارث ببرد ولی می‌تواند n  تا اینترفیس را پیاده سازی کند در TypeScript نیز چنین قوانینی وجود دارد. یعنی یک اینترفیس می‌تواند چندین اینترفیس دیگر را توسعه دهد(extend) و کلاسی که این اینترفیس را پیاده سازی می‌کند باید تمام توابع اینترفیس‌ها را پیاده سازی کند. مثال:
interface IMover {
 move() : void;
}

interface IShaker {
 shake() : void;
} 

interface IMoverShaker extends IMover, IShaker {
}
class MoverShaker implements IMoverShaker {
 move() {
 }
 shake() {
 }
}
*به کلمات کلیدی extends و implements و طریقه به کار گیری آن‌ها دقت کنید.

 instanceof

از instanceof زمانی استفاده می‌کنیم که قصد داشته باشیم که یک instance را با یک نوع مشخص مقایسه کنیم. اگر instance مربوطه از نوع مشخص باشد یا از این نوع ارث برده باشد مقدار true برگشت داده می‌شود در غیر این صورت مقدار false خواهد بود.
یک مثال:
var isLogger = logger instanceof Utilities.Logger; 
var isLogger = logger instanceof Utilities.AnnoyingLogger; 
var isLogger = logger instanceof Utilities.Formatter;
Method overriding
در TypeScript می‌توان مانند زبان‌های شی گرای دیگر Method overriding را پیاده سازی کرد. یعنی می‌توان متد‌های کلاس پایه را در کلاس مشتق شده تحریف کرد. با یک مثال به شرح این مورد خواهم پرداخت.
فرض کنید یک کلاس پایه به صورت زیر داریم:
class BaseEmployee
{   
    constructor (public fname: string,public lname: string) 
    {  
    }  
    sayInfo() 
    {  
       alert('this is base class method');
    }  
}
کلاس دیگری به نام Employee می‌سازیم که کلاس بالا را توسعه می‌دهد(یا به اصطلاح از کلاس بالا ارث می‌برد).
class Employee extends BaseEmployee
{  
   sayInfo() 
     {  
        alert('this is derived class method');
     }  
}  

window.onload = () =>  
{   
    var first: BaseEmployee= new Employee();     
    first.sayInfo();  
    var second: BaseEmployee = new BaseEmployee(); 
    second.sayInfo(); 
}
نکته مهم این است که دیگر خبری از کلمه کلیدی virtual برای مشخص کردن توابعی که قصد overriding آن‌ها را داریم نیست. تمام توابع که عمومی هستند را می‌توان override کرد.
*اگر در کلاس مشتق شده قصد داشته باشیم که به توابع و فیلد‌های کلاس پایه اشاره کنیم باید از کلمه کلیدی super استفاده کنیم.(معادل base در #C).
مثال:
class Animal {
    constructor (public name: string) {
    }
}

class Dog extends Animal {    
      constructor (public name: string, public age:number)
      {
        super(name);
      }

    sayHello() {
alert(super.name);
 } }
اگر به سازنده کلاس مشتق شده دقت کنید خواهید دید که پارامتر name را به سازنده کلاس پایه پاس دادیم: کد معادل در #C به صورت زیر است:
public class Dog : Animal 
{    
      public Dog (string name, int age):base(name)
      {
      }
}
در تابع sayHello نیز با استفاده از کلمه کلیدی super به فیلد name در کلاس پایه دسترسی خواهیم داشت.

*دقت کنید که مباحث مربوط به interface و private modifier و Type safety که پیش‌تر در مورد آن‌ها بحث شد، فقط در فایل‌های TypeScript و در هنگام کد نویسی و طراحی معنی دار هستند، زیرا بعد از کامپایل فایل‌های ts این مفاهیم در Javascript پشتیبانی نمی‌شوند در نتیجه هیچ مورد استفاده هم نخواهد داشت.

ادامه دارد...