مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت چهارم - تکمیل مستندات نوع‌های خروجی API
Swashbuckle.AspNetCore چگونه اطلاعات خود را فراهم می‌کند؟

در برنامه‌های ASP.NET Core، اطلاعات OpenAPI بر اساس سرویس توکاری به نام ApiExplorer تولید می‌شود که کار آن فراهم آوردن متادیتای مرتبط با یک برنامه‌ی وب است. برای مثال توسط این سرویس می‌توان به لیست کنترلرها، متدها و پارامترهای آن‌ها دسترسی یافت. Swashbuckle.AspNetCore به کمک ApiExplorer کار تولید OpenAPI Specification را انجام می‌دهد. برای فعالسازی این سرویس نیازی نیست کار خاصی انجام شود و زمانیکه ()services.AddMvc را فراخوانی می‌کنیم، ثبت و معرفی این سرویس نیز جزئی از آن است.


اهمیت تولید Response Types صحیح

در قسمت‌های قبل مشاهده کردیم که اگر متدی برای مثال در قسمتی از آن return NotFound یا 404 را داشته باشد، این نوع از خروجی، در OpenAPI Specification تولیدی لحاظ نمی‌شود و ناقص است و یا حتی ممکن است Response Type پیش‌فرض تولیدی که 200 است، ارتباطی به هیچکدام از نوع‌های خروجی یک اکشن متد نداشته باشد و نیاز به اصلاح آن است. این مورد برای تکمیل مستندات یک API ضروری است و استفاده کنندگان از یک API باید بدانند چون نوع خروجی‌هایی را ممکن است در شرایط مختلف، دریافت کنند.


روش تغییر و اصلاح Response Type پیش‌فرض OpenAPI Specification

اکشن متد GetBook کنترلر کتاب‌ها، دارای دو نوع return Ok و return NotFound است؛ اما OpenAPI Specification تولیدی پیش‌فرض، تنها حالت return Ok یا 200 را مستند می‌کند. برای تکمیل مستندات این اکشن متد، می‌توان به صورت زیر عمل کرد:
/// <summary>
/// Get a book by id for a specific author
/// </summary>
/// <param name="authorId">The id of the book author</param>
/// <param name="bookId">The id of the book</param>
/// <returns>An ActionResult of type Book</returns>
/// <response code="200">Returns the requested book</response>
[HttpGet("{bookId}")]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<Book>> GetBook(Guid authorId, Guid bookId)
در اینجا با استفاده از ویژگی ProducesResponseType، می‌توان StatusCodes بازگشتی از متد را به صورت صریحی مشخص کرد. وجود این متادیتاها سبب خواهد شد تاOpenAPI Specification تولیدی، به نحو صحیحی حالت 404 را نیز لحاظ کند.
در اینجا StatusCodes.Status400BadRequest را نیز مشاهده می‌کنید. هرچند حالت return BadRequest در کدهای این اکشن متد وجود خارجی ندارد، اما در صورت بروز مشکلی در فراخوانی و یا پردازش آن، به صورت خودکار توسط فریم‌ورک بازگشت داده می‌شود. بنابراین مستندسازی آن نیز ضروری است.

برای آزمایش آن، برنامه را اجرا کنید. در قسمت مستندات متد فوق، اکنون سه حالت 404، 400 و 200 قابل مشاهده هستند. برای نمونه بر روی دکمه‌ی try it out آن کلیک کرده و زمانیکه authorId و bookId را درخواست می‌کند، دو Guid اتفاقی و کاملا بی‌ربط را وارد کنید. همچنین Controls Accept header را نیز بر روی application/json قرار دهید. سپس بر روی دکمه‌ی execute در ذیل آن کلیک نمائید. یک چنین خروجی 404 کاملی را مشاهده خواهید کرد:


در این تصویر، response body بر اساس rfc 7807 تولید می‌شود و استاندارد گزارش مشکلات یک API است. این مورد اکنون به صورت یک اسکیمای جدید در انتهای مستندات تولیدی نیز قابل مشاهده‌است:



بهبود مستندات تشخیص نوع‌های مدل‌های خروجی اکشن متدها

مورد دیگری که در اینجا جالب توجه است، تشخیص نوع خروجی، در حالت return Ok است:


در اینجا اگر بر روی لینک Schema، بجای Example value پیش‌فرض کلیک کنیم، تصویر فوق حاصل می‌شود. تشخیص این نوع، به علت استفاده‌ی از ActionResult از نوع Book، به صورت زیر است که در ASP.NET Core 2.1 برای همین منظور (تکمیل مستندات Swagger) معرفی شده‌است:
public async Task<ActionResult<Book>> GetBook(Guid authorId, Guid bookId)
بنابراین از ASP.NET Core 2.1 به بعد، بهتر است در APIها خود از IActionResult استفاده نکنید و شروع به کار با <ActionResult<T نمائید تا بتوان مستندات بهتری را تولید کرد. اگر از IActionResult استفاده کنید، دیگر خبری از Example value و Schema تصویر فوق نخواهد بود و از روی متادیتای این اکشن متد نمی‌توان نوع خروجی آن‌را تشخیص داد. البته در این حالت می‌توان این مشکل را به صورت زیر نیز حل کرد؛ اما باز هم بهتر است از <ActionResult<T استفاده کنید:
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Book))]

یک نکته: در این تصویر، در قسمت توضیحات حالت 200، عبارت "Returns the requested book" مشاهده می‌شود. اما در حالت‌های دیگر response types تعریف شده، عبارات پیش‌فرض bad request و یا not found نمایش داده شده‌اند. نحوه‌ی بازنویسی این پیش‌فرض‌ها، با تکمیل مستندات XMLای اکشن متد و ذکر response code دلخواه، به صورت زیر است:
/// <response code="200">Returns the requested book</response>


استفاده از API Analyzers برای بهبود OpenAPI Specification تولیدی

اکنون این سؤال مطرح می‌شود که پس از این تغییرات، هنوز چه مواردی در OpenAPI Specification تولیدی ما وجود خارجی ندارند و بهتر است اضافه شوند. برای پاسخ به این سؤال، از زمان ارائه‌ی ASP.NET Core 2.2، بسته‌ی جدید Microsoft.AspNetCore.Mvc.Api.Analyzers نیز ارائه شده‌است که کار آن دقیقا بررسی همین نقایص و کمبودها است. بنابراین ابتدا آن‌را به فایل OpenAPISwaggerDoc.Web.csproj اضافه کرده و سپس دستور dotnet restore را صادر می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Api.Analyzers" Version="2.2.0" />
  </ItemGroup>
پس از نصب این ابزار جدید که به صورت افزونه‌ای برای کامپایلر #‍C کار می‌کند، بلافاصله اخطارهایی توسط کامپایلر ظاهر خواهند شد؛ مانند:
Controllers\BooksController.cs(40,17): warning API1000: Action method returns undeclared status code '404'.
Controllers\BooksController.cs(89,13): warning API1000: Action method returns undeclared status code '201'.
همانطور که ملاحظه می‌کنید، هنوز هم نیاز به تعریف تعدادی ProducesResponseType فراموش شده وجود دارد که آن‌ها را به صورت زیر اضافه خواهیم کرد:
/// <summary>
/// Get the books for a specific author
/// </summary>
/// <param name="authorId">The id of the book author</param>
/// <returns>An ActionResult of type IEnumerable of Book</returns>
[HttpGet()]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesDefaultResponseType]
public async Task<ActionResult<IEnumerable<Book>>> GetBooks(Guid authorId)
در ابتدا نوع‌های خروجی اکشن متد GetBooks را تکمیل می‌کنیم که آن نیز دارای return NotFound و همچنین return Ok است. به علاوه در اینجا ویژگی جدید ProducesDefaultResponseType را نیز ملاحظه می‌کنید که یک چنین خروجی را تولید می‌کند:


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


ساده سازی کدهای تکراری تعریف ProducesResponseTypeها

مواردی مانند StatusCodes.Status400BadRequest و یا 406 را در حالت عدم قبول درخواست (مثلا با انتخاب یک accept header اشتباه) و یا 500 را در صورت وجود استثنائی در سمت سرور، باید به تمام اکشن متدها نیز اضافه کرد؛ چون می‌توانند تحت شرایطی، نوع‌های خروجی معتبری باشند. برای خلاصه سازی این عملیات، یا می‌توان این ویژگی‌ها را بجای قراردادن آن‌ها در بالای تعریف امضای اکشن متدها، به بالای تعریف کلاس کنترلر انتقال داد. با اینکار ویژگی که به یک کنترلر اعمال شده باشد به تمام اکشن متدهای آن نیز اعمال خواهد شد و یا حتی برای عدم تعریف این ویژگی‌های تکراری به ازای هر کنترلر موجود، می‌توان آن‌ها را به صورت سراسری تعریف کرد:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(setupAction =>
            {
                setupAction.Filters.Add(
                    new ProducesResponseTypeAttribute(StatusCodes.Status400BadRequest));
                setupAction.Filters.Add(
                    new ProducesResponseTypeAttribute(StatusCodes.Status406NotAcceptable));
                setupAction.Filters.Add(
                    new ProducesResponseTypeAttribute(StatusCodes.Status500InternalServerError));
                setupAction.Filters.Add(
                    new ProducesDefaultResponseTypeAttribute());

                setupAction.ReturnHttpNotAcceptable = true;
// ...
به این ترتیب یکسری از نوع‌های خروجی که ممکن است توسط خود فریم‌ورک به صورت خودکار بازگشت داده شوند، به صورت سراسری به تمام اکشن متدهای برنامه اعمال می‌شوند و دیگر نیازی به تعریف دستی آن‌ها نخواهد بود.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: OpenAPISwaggerDoc-04.zip

در قسمت بعد، روش‌های دیگری را برای تکمیل مستندات خروجی API بررسی می‌کنیم.
مطالب
شروع به کار با DNTFrameworkCore - قسمت 1 - معرفی و نحوه استفاده از آن

پروژه DNTFrameworkCore  که قصد پشتیبانی از آن را دارم، یک زیرساخت سبک وزن و توسعه پذیر با پشتیبانی از طراحی چند مستاجری، با تمرکز بر کاهش زمان و افزایش کیفیت توسعه سیستم‌های تحت وب مبتنی بر ASP.NET Core، توسعه داده شده است. 


اهدافی که این زیرساخت دنبال می‌کند

  • ارائه ساختارهای مشترک بین پروژه‌های مختلف از جمله Cross-Cutting Concern‌ها و ...
  • دنبال کردن اصل DRY به منظور متمرکز شدن صرف برروی منطق تجاری سیستم نه انجام و حل یکسری مسائل تکراری
  • کاهش زمان توسعه و اختصاص زمان بیشتر برای نوشتن آزمون‌های واحد منطق تجاری
  • کاهش باگ و جلوگیری از پخش شدن باگ‌های پیاده سازی در سراسر سیستم
  • کاهش زمان آموزش نیروهای جدید برای ملحق شدن به تیم تولید شما با حداقل دانش طراحی و برنامه نویسی شیء گرا
  • ارائه راهکاری یکپارچه برای توسعه پذیر بودن منطق تجاری پیاده سازی شده از طریق در معرض دید قرار دادن یکسری «Extensibility Point» با استفاده از رویکرد Event-Driven


امکانات این زیرساخت در زمان نگارش مطلب جاری


نحوه استفاده از بسته‌های نیوگت مرتبط

PM> Install-Package DNTFrameworkCore -Version 1.0.0
 services.AddDNTFramework()
     .AddDataAnnotationValidation()
     .AddModelValidation()
     .AddValidationOptions(options =>
     {
         /*options.IgnoredTypes.Add(typeof());*/
     })
     .AddMemoryCache()
     .AddAuditingOptions(options =>
     {
         // options.Enabled = true;
         // options.EnabledForAnonymousUsers = false;
         // options.IgnoredTypes.Add(typeof());
         // options.Selectors.Add(new NamedTypeSelector("SelectorName", type => type == typeof()));
     }).AddTransactionOptions(options =>
     {
         // options.Timeout=TimeSpan.FromMinutes(3);
         //options.IsolationLevel=IsolationLevel.ReadCommitted;
     });

متدهای الحاقی بالا برای ثبت سرویس‌ها و تنظیمات مرتبط با مکانیزم‌های اعتبارسنجی خودکار، مدیریت تراکنش‌ها، لاگ آماری، Eventing و سایر امکانات ذکر شده، در IoC Container  توکار ASP.NET Core استفاده خواهند شد.

PM> Install-Package DNTFrameworkCore.EntityFramework -Version 1.0.0
services.AddDNTUnitOfWork<ProjectDbContext>();

‎بسته نیوگت بالا شامل پیاده سازی مبتنی بر EF Core برای واسط‌های تعریف شده در بسته نیوگت DNTFrameworkCore، می‌باشد؛ از جمله آن می‌توان به CrudService پایه اشاره کرد.  متد الحاقی AddDNTUnitOfWork برای ثبت و معرفی واسط‌های IUnitOfWork و ITransactionProvider به عنوان مهیا کننده تراکنش به همراه پیاده سازهای آنها و همچنین ثبت یک سری Hook تعریف شده برای ردیابی تغییرات، در سیستم تزریق وابستگی، استفاده خواهد شد.

همچنین با نصب بسته بالا، امکان استفاده از مهیا کننده Logging با امکان ذخیره سازی در بانک اطلاعاتی را خواهید داشت:

 public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseDefaultServiceProvider((context, options) =>
                {
                    options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
                })
                .ConfigureLogging((hostingContext, logging) =>
                {
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                    logging.AddConsole();
                    logging.AddDebug();
                    logging.AddEntityFramework<ProjectDbContext>(options => options.MinLevel = LogLevel.Warning);
                })
                .UseStartup<Startup>();

متد جنریک الحاقی AddEntityFramework برای ثبت مهیا کننده مذکور استفاده می‌شود. 

PM> Install-Package DNTFrameworkCore.Web -Version 1.0.0

بسته نیوگت بالا شامل یکسری سرویس برای اعمال دسترسی‌های پویا، CrudController مبتنی بر ASP.NET Core Web API، فیلتر مدیریت سراسری خطاهای برنامه و سایر امکاناتی که در ادامه مقالات با جزئیات بیشتری بررسی خواهیم کرد، می‌باشد. برای ثبت سرویس‌های تعریف شده می‌توانید از متد الحاقی AddDNTCommonWeb و به منظور تغییر محل ذخیره سازی کلیدهای موقت رمزنگاری مرتبط با Data Protection API و انتقال آنها به بانک اطلاعاتی، استفاده کنید. 

services.AddDNTCommonWeb()
    .AddDNTDataProtection();

نکته: برای انتقال کلیدهای موقت رمزنگاری به بانک اطلاعاتی، نیاز است تا از متد الحاقی زیر که در بسته نیوگت DNTFrameworkCore.EntityFramework موجود می‌باشد، به شکل زیر استفاده کنید:

services.AddDNTProtectionRepository<ProjectDbContext>();

‎‎

اگر نیاز به شماره گذاری خودکار دارید، بسته زیر را نیز می‌بایست نصب کنید:
PM> Install-Package DNTFrameworkCore.EntityFramework.SqlServer -Version 1.0.0

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

services.AddDNTNumbering(options =>
{
    options.NumberedEntityMap[typeof(Task)] = new NumberedEntityOption
    {
        Start = 100,
        Prefix = "Task-",
        IncrementBy = 5
    };
});

‎‎به عنوان مثال برای شماره گذاری موجودیت Task، لازم است تنظیمات مرتبط آن را به شکل بالا به سیستم شماره گذاری معرفی کنید.

اگر قصد استفاده از کتابخانه FluentValidation را داشته باشید، می‌بایست بسته زیر را نیز نصب کنید:

PM> Install-Package DNTFrameworkCore.FluentValidation -Version 1.0.0  

برای ثبت و معرفی Adapter مرتبط، به سیستم اعتبارسنجی خودکار معرفی شده، لازم است از طریق متد الحاقی AddFluentModelValidation به شکل زیر اقدام کنید:

 services.AddDNTFramework()
     .AddDataAnnotationValidation()
     .AddModelValidation()
     .AddFluentModelValidation()
     .AddValidationOptions(options =>
     {
         /*options.IgnoredTypes.Add(typeof());*/
     })
     .AddMemoryCache()
     .AddAuditingOptions(options =>
     {
         // options.Enabled = true;
         // options.EnabledForAnonymousUsers = false;
         // options.IgnoredTypes.Add(typeof());
         // options.Selectors.Add(new NamedTypeSelector("SelectorName", type => type == typeof()));
     }).AddTransactionOptions(options =>
     {
         // options.Timeout=TimeSpan.FromMinutes(3);
         //options.IsolationLevel=IsolationLevel.ReadCommitted;
     });

‎‎

برای شروع پروژه جدید، نصب این بسته‌ها کفایت می‌کند. اگر نیاز به طراحی MultiTenancy دارید، بسته زیر را برای شناسایی Tenant جاری و از این قبیل کارها نیز می‌بایست نصب کنید:

PM> Install-Package DNTFrameworkCore.Web.MultiTenancy -Version 1.0.0
برای ثبت و معرفی ITenantResolver شخصی سازی شده خود، می‌توانید از متد الحاقی زیر استفاده کنید:
services.AddMultiTenancy<TenantResolver>();
همچنین نیاز است با استفاده از متد الحاقی زیر، Middleware تعریف شده در کتابخانه را به «HTTP Request Pipeline» سیستم معرفی کرده و ثبت کنید:
app.UseMultiTenancy();
نکته: بسته نیوگت DNTFrameworkCore.Web.MultitTenancy به منظور مهیا کردن طول عمر Tenant-Singleton، وابستگی به کتابخانه StructureMap نیز دارد. البته این بسته نیاز به بهبود هم دارد که در ادامه اعمال خواهد شد. همچنین امضای متدهای الحاقی بالا، در انتشارهای بعدی به AddDNTMultiTenancy و UseDNTMultiTenancy تغییر خواهند کرد.
در نهایت به منظور تزئین خودکار و پویای سرویس‌های برنامه برای اعمال یکسری Cross-Cutting Concern معرفی شده در بالا، از جمله اعتبارسنجی ورودی‌ها، مدیریت تراکنش‌ها و ... می‌توانید پس از ثبت و معرفی سرویس‌های خود به سیستم تزریق وابستگی توکار، با استفاده از Interceptor‌های پیاده سازی شده در زیرساخت، به شکل زیر اقدام کنید:
services.Scan(scan => scan
    .FromCallingAssembly()
    .AddClasses(classes => classes.AssignableTo<ISingletonDependency>())
    .AsMatchingInterface()
    .WithSingletonLifetime()
    .AddClasses(classes => classes.AssignableTo<IScopedDependency>())
    .AsMatchingInterface()
    .WithScopedLifetime()
    .AddClasses(classes => classes.AssignableTo<ITransientDependency>())
    .AsMatchingInterface()
    .WithTransientLifetime()
    .AddClasses(classes => classes.AssignableTo(typeof(IDomainEventHandler<>)))
    .AsImplementedInterfaces()
    .WithTransientLifetime());

foreach (var descriptor in services.Where(s => typeof(IApplicationService).IsAssignableFrom(s.ServiceType))
    .ToList())
{
    services.Decorate(descriptor.ServiceType, (target, serviceProvider) =>
        ProxyGenerator.CreateInterfaceProxyWithTargetInterface(
            descriptor.ServiceType,
            target, serviceProvider.GetRequiredService<ValidationInterceptor>(),
            (IInterceptor) serviceProvider.GetRequiredService<TransactionInterceptor>()));
}
به عنوان مثال در اینجا از ValidationInterceptor و TransactionInterceptor استفاده شده است.

پروژه نمونه‌ای هم برای نمایش امکانات زیرساخت را از اینجا می توانید دریافت کنید.
آزمون‌های واحد مرتبط با قسمت هایی از این زیرساخت را نیز می‌توانید در اینجا مشاهده کنید.
اشتراک‌ها
فریمورک تهیه شده با Asp.net Mvc و Asp.net Web Api به صورت NTier با پشتیبانی از چند زبانگی

These are some key features of the framework itself:

Modular and NLayered architecture
Multi-tenancy
Dependency Injection
Domain Driven Design
Unit of work implementation
Flexible Localization system
Automatic data filters
Audit logging
Setting management
Menu management
Authorization
Exception handling
Validation
Logging
Event bus for domain events
Auto-creating Web API layer for Application Services
Auto-creating Javascript proxy layer to use Web API layer
Javascript helper methods for ajax, notifications, message boxes, making UI busy...
Easily working with embedded resources
Useful extension and helper methods   

فریمورک تهیه شده با Asp.net Mvc و Asp.net Web Api به صورت NTier با پشتیبانی از چند زبانگی
اشتراک‌ها
ساخت کلیدهای میانبر در برنامه های تحت وب

یکی از ویژگی‌های که در برنامه‌های دسکتاپ هست کلیدهای میانبر است.

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

ساخت کلیدهای میانبر در برنامه های تحت وب
نظرات مطالب
EF Code First #7

سعید جان میدونم که اینکار میشه- مطمئنا شماره ملیشون رو  uniqe کردم!

ما برای انتخاب کلید اصلی دو حالت داریم -

1- استفاده از کلیدهای طبیعی  مثل شماره پرسنلی

2- استفاده از کلیدهای جانشین مثل یک فیلد identity - این حالت موقعی استفاده میشه که کلید طبیعی نداشته باشیم

مطالب
آموزش TypeScript #1
با گسترش روز افزون زبان برنامه نویسی Javascript و استفاده هر چه بیشتر آن در تولید برنامه‌های تحت وب این زبان به یکی از قدرت‌های بزرگ در تولید برنامه‌های مبتنی بر وب تبدیل شده است. ترکیب این زبان با Css و Html5 تقریبا هر گونه نیاز برای تهیه و توسعه برنامه‌های وب را حل کرده است. جاوا اسکریپت در ابتدا برای اسکریپت نویسی سمت کلاینت برای صفحات وب ایجاد شد و برای سال‌ها به‌عنوان ابزاری برای مدیریت کردن رویدادهای صفحات وب محدود شده بود  و در نتیجه بسیاری از امکانات لازم برای برنامه‌نویسی برنامه‌های مقیاس بزرگ را به‌همراه نداشت. امروزه به قدری Javascript  توسعه داده شده است که حتی در تولید برنامه‌های Native مانند Windows Store  و برنامه‌های تحت Cloud نیز استفاده می‌شود. پیشرفت‌های صورت گرفته و اشاره شده در این حوزه موجب شد تا شاهد پیداش برنامه‌های مبتنی بر جاوا اسکریپت با سایزهای بی سابقه‌ای باشیم و این بیانگر این بود که تولید برنامه‌های مبتنی بر جاوا اسکریپت در مقیاس‌های بزرگ امر دشواری است و اینک TypeScript توسط غول نرم افزاری جهان پا به عرصه گذاشته که این فرآیند را آسان‌تر نماید. به کمک TypeScript می‌توان برنامه تحت JavaScript در مقیاس بزرگ تولید کرد به طوری با هر مرورگر و سیستم عاملی سازگار باشد. TypeScript از شی گرایی نیز پشتیبانی می‌کند و خروجی آن در نهایت به JavaScript کامپایل می‌شود. خیلی‌ها عقیده دارند که هدف اصلی مایکروسافت از تولید و توسعه این زبان رقابت با CoffeeScript است.  CoffeeScript یک زبان متن باز است که در سال 2009 توسط Jeremy Ashkenas ایجاد شده است و سورس آن در GitHub موجود می‌باشد. در آینده، بیشتر به مباحث مربوط به CoffeeScript و آموزش آن خواهم پرداخت.

در تصویر ذیل یک مقایسه کوتاه بین CoffeeScript و TypeScript را مشاهده می‌کنید.

با TypeScript چه چیزهایی به دست خواهیم آورد؟

یک نکته مهم این است که این زبان به خوبی در Visual Studio پشتیبانی می‌شود و قابلیت Intellisense نوشتن برنامه به این زبان را دلپذیرتر خواهد کرد و از طرفی دیگر به نظر من یکی از مهم‌ترین مزیت هایی که TypeScript در اختیار ما قرار می‌دهد این است که می‌توانیم به صورت Syntax آشنای شی گرایی کد نویسی کنیم و خیلی راحت‌تر کد‌های خود را سازمان دهی کرده و از نوشتن کد‌های تکراری اجتناب کنیم.

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

به دلیل اینکه کد‌های TypeScript ابتدا کامپایل شده و بعد تبدیل به کد‌های JavaScript می‌شوند در نتیجه قبل از رسیدن به مرحله اجرای پروژه، ما از خطاهای موجود در کد خود مطلع خواهیم شد.

البته این نکته را نیز فراموش نخواهیم کرد که این زبان تازه متولد شده است(سال 2012 توسط Anders Hejlsberg) و همچنان در حال توسعه است و این در حال حاضر مهم‌ترین عیب این زبان می‌تواند باشد چون هنوز به پختگی سایر زبان‌های اسکریپتی در نیامده است.

در ذیل یک مثال کوچک به زبان TypeScript و JavaScript را برای مقایسه در خوانایی و راحتی کد نویسی قرار دادم:

TypeScript:

class Greeter {
    greeting: string;

    constructor (message: string) {
        this.greeting = message;
    }

    greet() {
        return "Hello, " + this.greeting;
    }
}
بعد از کامپایل کد بالا به کدی معادل زیر در JavaScript تبدیل خواهد شد:
var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
})();
توضیح چند واژه در TypeScript

Program : یک برنامه TypeScript مجموعه ای از یک یا چند Source File است. این Source File‌ها شامل کد‌های پیاده سازی برنامه هستند ولی در خیلی موارد برای خوانایی بیشتر برنامه می‌توان فقط تعاریف را در این فایل‌های سورس قرار داد.
Module: ماژول در TypeScript شبیه به مفاهیم فضای نام یا namespace در دات نت است و می‌تواند شامل چندین کلاس یا اینترفیس باشد.
Class : مشابه به مفاهیم کلاس در دات نت است و دقیقا همان مفهوم را دارد. یک کلاس می‌تواند شامل چندین تابع و متغیر با سطوح دسترسی متفاوت باشد. در TypeScript مجاز به استفاده از کلمات کلیدی public و private نیز می‌باشید. یک کلاس در Typescript می‌تواند یک کلاس دیگر را توسعه دهد(ارث بری در دات نت) و چندین اینترفیس را پیاده سازی نماید.
Interface: یک اینترفیس فقط شامل تعاریف است و پیاده سازی در آن انجام نخواهد گرفت. یک اینترفیس می‌تواند چندین اینترفیس دیگر را توسعه دهد.
Function: معادل متد در دات نت است. می‌تواند پارامتر ورودی داشته باشد و در صورت نیاز یک مقدار را برگشت دهد.
Scope: دقیقا تمام مفاهیم مربوط به محدوده فضای نام و کلاس و متد در دات نت در این جا نیز صادق است. 


آماده سازی Visual Studio برای شروع به کار
در ابتدا باید Template مربوطه به TypeScript را نصب کنید تا از طریف VS.Net بتوانیم به راحتی به این زبان کد نویسی کنیم. می‌توانید فایل نصب را از اینجا دانلود کنید. بعد از نصب از قسمت Template‌های موجود گزینه Html Application With TypeScript را انتخاب کنید

یا از قسمت Add  در پروژه‌های وب خود نظیر MVC گزینه TypeScript File را انتخاب نمایید.


در پست بعدی کد نویسی با این زبان را آغاز خواهیم کرد. 

مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت پنجم - نکات و مباحث تکمیلی
پس از بررسی مباحث و نکات پایه‌ای کار با کتابخانه‌ی Moq، در این قسمت تعدادی از نکات تکمیلی آن‌را بررسی خواهیم کرد.


حالت‌های عملکرد کتابخانه‌ی Moq

کتابخانه‌ی Moq، دو حالت عملکرد را دارد: Strict Mode و Loose mode. زمانیکه یک Mock object را نمونه سازی می‌کنیم، به صورت پیش‌فرض کتابخانه‌ی Moq، یک Loose mock را ایجاد می‌کند. در این حالت این شیء، مقادیر پیش‌فرض خواص و اشیاء را بازگشت می‌دهد و استثنائی را صادر نمی‌کند. اگر این موارد مدنظر نیستند، می‌توان به حالت Strict آن رجوع کرد که روش تنظیم آن به صورت زیر است:
var mockIdentityVerifier = new Mock<IIdentityVerifier>(MockBehavior.Strict);
در این حالت اگر متد آزمون واحد را اجرا کنیم، با پیام زیر، با شکست مواجه خواهد شد:
Test method Loans.Tests.LoanApplicationProcessorShould.Accept threw exception:
Moq.MockException: IIdentityVerifier.Initialize() invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.
در حالت Strict، تمام فراخوانی‌های شیء Mock شده باید دارای Setup باشند (نیازی به Setup تمام موارد نیست؛ فقط مواردی که در فراخوانی‌های آزمون واحد، مورد استفاده قرار می‌گیرند، حتما باید تنظیم شوند). برای نمونه در اینجا عنوان کرده‌است که در این آزمایش، تنظیمات متد Initialize انجام نشده‌است که با تعریف سطر زیر، این مشکل برطرف می‌شود:
mockIdentityVerifier.Setup(x => x.Initialize());

بنابراین هرچند کارکردن با حالت پیش‌فرض کتابخانه‌ی Moq ساده‌است، اما تنظیم حالت Strict سبب می‌شود تا تنظیمی را فراموش نکنیم و در نتیجه کیفیت آزمون واحد تهیه شده افزایش می‌یابد.


صدور استثناءها از طریق Mock objects

اگر در سیستم در حال آزمایش، قسمتی به بررسی خطاها اختصاص دارد، می‌توان توسط Mock objects استثناءهایی را تولید و به این ترتیب منطق بررسی خطاها را آزمایش کرد.
برای نمونه در متد Process کلاس LoanApplicationProcessor، یک try/catch را به قسمت CalculateScore اضافه می‌کنیم:
try
{
    _creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
}
catch
{
    return application.IsAccepted;
}
زمانیکه کار فراخوانی متد CalculateScore صورت می‌گیرد، برای تنظیم آزمون واحد آن می‌توان از متد Throws، برای صدور یک استثناء استفاده کرد:
mockCreditScorer.Setup(x =>
                    x.CalculateScore(It.IsAny<string>(), It.IsAny<string>()))
                .Throws(new InvalidOperationException("Test Exception"));
صدور این استثناء سبب خواهد شد تا درخواست شخص، رد شود. بنابراین در آزمایش آن می‌توان این مساله را بررسی کرد و از رسیدن به این قسمت (رد شدن درخواست) اطمینان حاصل نمود:
Assert.IsFalse(application.IsAccepted);


صدور رخدادها از طریق Mock objects

فرض کنید یک EventArgs سفارشی را به صورت زیر تعریف:
using System;

namespace Loans.Models
{
    public class CreditScoreResultArgs : EventArgs
    {
        public int Score { get; set; }
    }
}
و سپس رخدادی را به نحو زیر به ICreditScorer اضافه کرده‌ایم:
public interface ICreditScorer
{
   event EventHandler<CreditScoreResultArgs> ResultAvailable;
برای اینکه یک Mock object سبب بروز رخداد ResultAvailable شود (به صورت دستی و دقیقا در سطری که مشخص می‌کنیم)، می‌توان به صورت زیر عمل کرد:
mockCreditScorer.Raise(x => x.ResultAvailable += null, new CreditScoreResultArgs());
ابتدا توسط متد Raise، رخ‌داد مدنظر را ذکر می‌کنیم و سپس یک نمونه‌ی EventArgs را به آن ارسال خواهیم کرد.
روش دیگر انجام اینکار به صورت زیر است:
mockCreditScorer.Setup(x =>
                x.CalculateScore(It.IsAny<string>(), It.IsAny<string>()))
                .Raises(x => x.ResultAvailable += null, new CreditScoreResultArgs());
در این حالت با فراخوانی متد CalculateScore، رخداد ResultAvailable به صورت خودکار صادر می‌شود.


معرفی Partial Mocks

در اغلب آزمون‌های واحدی که تا اینجا بررسی شدند، ابتدا یک Mock object را ایجاد و سپس وهله‌ای از سرویس مدنظر را توسط آن تهیه می‌کنیم. در ادامه تعدادی از متدهای این سرویس را مانند متد Process کلاس LoanApplicationProcessor، فراخوانی می‌کنیم. اینکار سبب اجرای فعالیتی در این سیستم شده و به همراه آن تعاملی با اشیاء Mock شده نیز صورت می‌گیرد. در نهایت حالت و یا نتیجه‌ای را دریافت می‌کنیم و آن‌را با حالت یا نتیجه‌ای که انتظار داریم، مقایسه خواهیم کرد. در این روش پس از پایان اجرای سیستم در حال اجرا، حالت و نتیجه‌ی نهایی حاصل از عملکرد آن، مورد بررسی قرار می‌گیرد. این بررسی‌ها را نیز بر روی اینترفیس‌ها انجام دادیم. اگر بجای اینترفیس‌ها از یک class استفاده شود، به آن partial mock گفته می‌شود. عموما مواردی را که آزمایش آن‌ها سخت است، با Partial mocks پیاده سازی می‌کنند؛ مانند کار با فایل سیستم، کار با قطعه کدهای نامعین مانند DateTime.Now، اعداد اتفاقی و یا Guidها.

در مثال زیر، شبیه به متد آزمون واحد Accept که تاکنون آن‌را بررسی کردیم، از اشیاء Mock شده استفاده شده‌است؛ با یک تفاوت: بجای اینترفیس IIdentityVerifier، از کلاس پیاده سازی کننده‌ی آن که در اینجا IdentityVerifierServiceGateway است، استفاده شده:
namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {        
        [TestMethod]
        public void AcceptUsingPartialMock()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IdentityVerifierServiceGateway>();

            mockIdentityVerifier.Setup(x => x.CallService(applicant.Name, applicant.Age, applicant.Address))
                .Returns(true);

            var mockCreditScorer = new Mock<ICreditScorer>();
            mockCreditScorer.Setup(x => x.ScoreResult.ScoreValue.Score).Returns(110_000);

            var sut = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            sut.Process(application);

            Assert.IsTrue(application.IsAccepted);
        }
    }
}
در اینجا برای اینکه بتوانیم متد CallService را که private بوده، بررسی و تنظیم کنیم، آن‌را به public virtual تبدیل کرده‌ایم تا توسط Moq قابل دسترسی و همچنین قابل بازنویسی شود:
public virtual bool CallService(string applicantName, int applicantAge, string applicantAddress)


تبدیل DateTime.Now به یک مقدار ثابت قابل آزمایش توسط Partial Mocks

در کلاس IdentityVerifierServiceGateway، یک چنین کدی را داریم که از DateTime.Now نامشخص استفاده می‌کند و آزمون واحد نوشتن برای آن مشکل است؛ چون DateTime.Now در هربار که آزمایش اجرا می‌شود، تغییر می‌کند:
public bool Validate(string applicantName, int applicantAge, string applicantAddress)
{
    Connect();
    var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress);
    LastCheckTime = DateTime.Now;
    Disconnect();

    return isValidIdentity;
}
برای بالابردن قابلیت آزمون نویسی این کلاس، آن‌را به صورت زیر Refactor می‌کنیم تا DateTime.Now را به صورت یک متد public virtual دریافت کند:
public bool Validate(string applicantName, int applicantAge, string applicantAddress)
{
    Connect();
    var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress);
    LastCheckTime = GetCurrentTime();
    Disconnect();

    return isValidIdentity;
}

public virtual DateTime GetCurrentTime()
{
    return DateTime.Now;
}
اکنون آزمون واحد نویسی برای این کلاس توسط Mock objects بسیار ساده‌است:
var expectedTime = new DateTime(2000, 1, 1);
mockIdentityVerifier.Setup(x => x.GetCurrentTime())
    .Returns(expectedTime);
// ...
Assert.AreEqual(expectedTime, mockIdentityVerifier.Object.LastCheckTime);
در اینجا خروجی متد GetCurrentTime بر روی Mock object تهیه شده، به یک مقدار ثابت تنظیم شده‌است که با هر بار اجرای آزمایش در زمان‌های مختلف، تغییری نمی‌کند و وابسته‌ی به DateTime.Now نامشخص، نیست.


استفاده از متدهای protected بجای استفاده از متدهای public virtual در Partial Mocks

همانطور که مشاهده کردید، برای کار با Partial Mocks نیاز است متدهای معرفی شده، از نوع public virtual باشند. برای نمونه حتی مجبور شدیم یک متد private را نیز public کنیم. اگر علاقمند به این نوع تغییرات نیستید، می‌توان بجای public کردن متدهای private، آن‌ها را protected تعریف کرد. به همین جهت دو متدی را که تاکنون public virtual تعریف کردیم، تبدیل به protected virtual می‌کنیم.
پس از آن در کلاسی که آزمون‌های واحد را تهیه کردیم، ابتدا using Moq.Protected را ذکر می‌کنیم تا بتوانیم به قابلیت‌های ویژه‌ی کار با متدهای Protected دسترسی پیدا کنیم.
سپس روش تنظیم این نوع متدهای protected، چون دسترسی مستقیمی به آن‌ها وجود ندارد، به صورت زیر، با ذکر نام رشته‌ای آن‌ها تغییر می‌کند:
mockIdentityVerifier.Protected().Setup<bool>(
        "CallService",applicant.Name, applicant.Age, applicant.Address)
    .Returns(true);

var expectedTime = new DateTime(2000, 1, 1);
mockIdentityVerifier.Protected().Setup<DateTime>("GetCurrentTime")
    .Returns(expectedTime);
ابتدا متد Protected شیء Mock شده ذکر می‌شود و پس از آن متد Setup باید دقیقا نوع بازگشتی متد در حال تنظیم را ذکر کند؛ چون دیگر دسترسی strongly typed ای به آن نداریم. پس ا‌ز آن، لیست پارامترهای متد، ذکر می‌شوند.

روش دیگری نیز برای تعریف متدهای protected وجود دارد که اینبار strongly typed است. بالای متد آزمون واحد، اینترفیس private زیر را تعریف می‌کنیم:
interface IIdentityVerifierServiceGatewayProtectedMembers
{
   DateTime GetCurrentTime();
   bool CallService(string applicantName, int applicantAge, string applicantAddress);
}
که در آن متدهای تعریف شده، با متدهای protected در حال بررسی، امضای یکسانی دارند (و همواره با هر تغییری در برنامه نیز باید این وضعیت حفظ شود). در ادامه تعاریف تنظیمات این متدها به صورت strongly typed زیر قابل انجام است:
mockIdentityVerifier.Protected()
    .As<IIdentityVerifierServiceGatewayProtectedMembers>()
    .Setup(x => x.CallService(It.IsAny<string>(),
        It.IsAny<int>(),
        It.IsAny<string>()))
    .Returns(true);

var expectedTime = new DateTime(2000, 1, 1);
mockIdentityVerifier.Protected()
    .As<IIdentityVerifierServiceGatewayProtectedMembers>()
    .Setup(x => x.GetCurrentTime())
    .Returns(expectedTime);


معرفی روش دیگری بجای استفاده از متدهای protected

اگر در کدهای خود نیاز به استفاده‌ی بیش از حد از متدهای protected را مشاهده کردید، این مورد می‌توان نشانه‌ی امکان Refactoring این قسمت از کدها به سرویس‌هایی مجزا باشند. برای مثال می‌توان یک اینترفیس INowProvider را به صورت زیر تعریف کرد:
using System;

namespace Loans.Services.Contracts
{
    public interface INowProvider
    {
        DateTime GetNow();
    }
}
و سپس آن‌را به سازنده‌ی کلاس IdentityVerifierServiceGateway تزریق کرد:
    public class IdentityVerifierServiceGateway : IIdentityVerifier
    {
        private readonly INowProvider _nowProvider;
        
        public DateTime LastCheckTime { get; private set; }

        public IdentityVerifierServiceGateway(INowProvider nowProvider)
        {
            _nowProvider = nowProvider;
        }
 و متد GetCurrentTime را حذف و آن‌را با متد GetNow این سرویس جایگزین نمود:
        public bool Validate(string applicantName, int applicantAge, string applicantAddress)
        {
            Connect();
            var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress);
            LastCheckTime = _nowProvider.GetNow();
            // ...
 به این ترتیب نیاز به تنظیم متد protected بازگشت زمان، حذف شده و می‌توان از این سرویس جدید استفاده کرد:
var mockNowProvider = new Mock<INowProvider>();
mockNowProvider.Setup(x => x.GetNow()).Returns(expectedTime);

var mockIdentityVerifier =  new Mock<IdentityVerifierServiceGateway>(mockNowProvider.Object);


کدهای کامل این سری را از اینجا می‌توانید دریافت کنید: MoqSeries-05.zip
مطالب
5 دلیل برای استفاده از یک ابزار ORM

چرا باید از ابزارهای Object relational Mapper یا به اختصار ORM استفاده کرد؟ در اینجا سخن در مورد ORM خاصی نیست. هدف تبلیغ یک محصول ویژه هم نمی‌باشد و یک بحث کلی مد نظر است.
کار ابزارهای ORM خواندن ساختار دیتابیس شما بوده و سپس ایجاد کلاس‌هایی بر اساس این ساختار ، برقراری ارتباط بین اشیاء ایجاد شده و جداول، ویووها، رویه‌های ذخیره شده و غیره می‌باشد. همچنین این ابزارها امکان تعریف روابط one-to-one, one-to-many, many-to-one, و many-to-many بین اشیاء را نیز بر اساس ساختار دیتابیس شما فراهم می‌کنند.
در ادامه به فواید استفاده از ORM ها خواهیم پرداخت:

الف) یک ابزار ORM زمان تحویل پروژه را کاهش می‌دهد

اولین و مهم‌ترین دلیلی که بر اساس آن در یک پروژه، استفاده از ORM حائز اهمیت می‌شود، بحث بالا بردن سرعت برنامه نویسی و کاهش زمان تحویل پروژه به مشتری است. این کاهش زمان بسته به نوع پروژه بین 20 تا 50 درصد می‌تواند خود را بروز دهد.
بدیهی است ابزارهای ORM کار شگفت انگیزی را قرار نیست انجام دهند و شما می‌توانید تمام آن عملیات ‌را دستی هم به پایان رسانید؛ اما اجازه دهید یک مثال کوتاه را با هم مرور کنیم.
برای پیاده سازی یک برنامه متداول با حدود 15 تا 20 جدول، حدودا به 30 شیء برای مدل سازی سیستم نیاز خواهد بود و برنامه نویسی این مجموعه بین 5000 تا 10000 سطر کد را به خود اختصاص خواهد داد. بدیهی است برنامه نویسی و آزمایش این سیستم چندین هفته یا ماه به طول خواهد انجامید.
اما با استفاده از یک ORM ، عمده وقت شما به طراحی سیستم و ایجاد ارتباطات بین اشیاء و دیتابیس در طی یک تا دو روز صرف خواهد شد. ایجاد کد بر اساس این مجموعه و با کمک ابزارهای ORM ، آنی است و با چند کلیک صورت می‌گیرد.


ب) یک ابزار ORM کدی با طراحی بهتر را تولید می‌کند

ممکن است شما بگوئید که کد نویسی من بی‌نظیر است و از من بهتر کسی را نمی‌توانید پیدا کنید! به تمامی زوایای کار خود مسلطم و نیازی هم به این‌گونه ابزارها ندارم!
عده‌ای از شما به طور قطع این‌گونه‌اید؛ اما نه همه. در یک تیم متوسط، همه نوع برنامه نویس با سطوح مختلفی را می‌توانید پیدا کنید و تمامی ‌‌آن‌ها برنامه نویس‌ها و یا طراح‌های آنچنان قابلی هم نیستند. بنابراین امکان رسیدن به کدهایی که مطابق اصول دقیق برنامه نویسی شیء گرا نیستند و در آن‌ها الگوهای طراحی به خوبی رعایت نشده، بسیار محتمل است. همچنین در یک تیم زمانیکه از یک الگوی یکسان پیروی نمی‌شود، نتایج نهایی بسیار ناهماهنگ خواهند بود.
در مقابل استفاده از ORM های طراحی شده توسط برنامه نویس‌های قابل (senior (architect level) engineers) ، کدهایی را بر اساس الگوهای استاندارد و پذیرفته شده‌ی شیء‌گرا تولید می‌کنند و همواره یک روند کاری مشخص و هماهنگ را در یک مجموعه به ارمغان خواهند آورد.

ج) نیازی نیست تا حتما یک متخصص دات نت فریم ورک باشید تا از یک ORM استفاده کنید

قسمت دسترسی به داده‌ها یکی از اجزای کلیدی کارآیی برنامه شما است. اگر طراحی و پیاده سازی آن ضعیف باشد، کل برنامه را زیر سؤال خواهد برد. برای طراحی و پیاده سازی دستی این قسمت از کار باید به قسمت‌های بسیاری از مجموعه‌ی دات نت فریم ورک مسلط بود. اما هنگام استفاده از یک ORM مهمترین موردی را که باید به آن تمرکز نمائید بحث طراحی منطقی کار است و ایجاد روابط بین اشیاء و دیتابیس و امثال آن. مابقی موارد توسط ORM انجام خواهد شد و همچنین می‌توان مطمئن بود که پیاده سازی خودکار انجام شده این قسمت‌ها، بر اساس الگوهای طراحی شیء‌گرا است.


د) هنگام استفاده از یک ابزار ORM ، مدت زمان آزمایش برنامه نیز کاهش می‌یابد

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

ه) استفاده از یک ابزار ORM ، کار برنامه نویسی شما را ساده‌تر می‌کند

توضیح این قسمت نیاز به ذکر یک مثال دارد. لطفا به مثال زیر دقت بفرمائید:

try {
Employees objInfo = new Employees();
EmployeesFactory objFactory = new EmployeesFactory();

objInfo.EmployeeID = EmployeeID;
objFactory.Load(objInfo);

// code here to use the "objInfo" object
}
catch(Exception ex) {
// code here to handle the exception
}

به نظر شما کار کردن با یک یا چند شیء تولید شده که نمایانگر ساختار دیتابیس شما هستند و با استفاده از اینترفیس عمومی آن‌ها می‌توان تمامی اعمال بارگذاری، درج و حذف و غیره را انجام داد، ساده‌تر است یا کار کردن با کوهی از دستورات ADO.Net ؟


برداشتی آزاد از Five Reasons for using an ORM Tool