هدف ارائه راه حلی برای مدیریت Transactionها به عنوان یک Cross Cutting Concern، توسط ApplicationServiceها میباشد.
- دوره Aspect oriented programming
- بررسی مفاهیم معکوس سازی وابستگیها و ابزارهای مرتبط با آن
- طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها
پیش فرض ما این است که شما از EF به عنوان OR-Mapper استفاده میکنید و الگوی Context Per Request را پیاده سازی کرده اید یا از طریق پیاده سازی الگوی Container Per Request به داشتن Context یکتا برای هر درخواست رسیده اید.
کتابخانه StructureMap.Mvc5 پیاده سازی از الگوی Container Per Request را با استفاده از امکانات Nested Container مربوط به StructureMap ارائه میدهد. اشیاء موجود در Nested Container طول عمر Singleton دارند.
واسط ITransaction
public interface ITransaction : IDisposable { void Commit(); void Rollback(); }
واسط بالا 3 متد را که برای مدیریت تراکنش لازم میباشد، در اختیار استفاده کننده قرار میدهد.
واسط IUnitOfWork
public interface IUnitOfWork : IDisposable { ... ITransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Snapshot); ITransaction Transaction { get; } IDbConnection Connection { get; } bool HasTransaction { get; } }
اعضای جدید واسط IUnitOfWork کاملا مشخص هستند.
پیاده سازی واسط ITransaction، توسط یک Nested Type در دل کلاس DbContextBase انجام میگیرد.
public abstract class DbContextBase : DbContext { ... #region Fields private ITransaction _currenTransaction; #endregion #region NestedTypes private class DbContextTransactionAdapter : ITransaction { private DbContextTransaction _transaction; public DbContextTransactionAdapter(DbContextTransaction transaction) { Guard.NotNull(transaction, nameof(transaction)); _transaction = transaction; } public void Commit() { _transaction?.Commit(); } public void Rollback() { if (_transaction?.UnderlyingTransaction.Connection != null) _transaction.Rollback(); } public void Dispose() { _transaction?.Dispose(); _transaction = null; } } #endregion #region Public Methods ... public ITransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted) { if (_currenTransaction != null) return _currenTransaction; return _currenTransaction = new DbContextTransactionAdapter(Database.BeginTransaction(isolationLevel)); } #endregion #region Properties ... public ITransaction Transaction => _currenTransaction; public IDbConnection Connection => Database.Connection; public bool HasTransaction => _currenTransaction != null; #endregion } public class ApplicationDbContext : DbContextBase, IUnitOfWork, ITransientDependency { }
کلاس DbContextTransactionAdapter همانطور که از نام آن مشخص میباشد، پیاده سازی از الگوی Adapter برای وفق دادن DbContextTransaction با واسط ITransaction، میباشد. متد BeginTransaction در صورتی که تراکنشی برای وهله جاری DbContext ایجاد نشده باشد، تراکنشی را ایجاد کرده و فیلد currentTransaction_ را نیز مقدار دهی میکند.
TransactionalAttribute
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public sealed class TransactionalAttribute : Attribute { public IsolationLevel IsolationLevel { get; set; } = IsolationLevel.ReadCommitted; public TimeSpan? Timeout { get; set; } }
TransactionInterceptor
public class TransactionInterceptor : ISyncInterceptionBehavior { private readonly IUnitOfWork _unitOfWork; public TransactionInterceptor(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public IMethodInvocationResult Intercept(ISyncMethodInvocation methodInvocation) { var transactionAttribute = GetTransactionaAttributeOrNull(methodInvocation.InstanceMethodInfo); if (transactionAttribute == null || _unitOfWork.HasTransaction) return methodInvocation.InvokeNext(); using (var transaction = _unitOfWork.BeginTransaction(transactionAttribute.IsolationLevel)) { var result = methodInvocation.InvokeNext(); if (result.Successful) transaction.Commit(); else { transaction.Rollback(); } return result; } } private static TransactionalAttribute GetTransactionaAttributeOrNull(MemberInfo methodInfo) { var transactionalAttribute = ReflectionHelper.GetAttributesOfMemberAndDeclaringType<TransactionalAttribute>( methodInfo ).FirstOrDefault(); return transactionalAttribute; } }
واسط ISyncInterceptionBehavior، مربوط میشود به کتابخانه جانبی دیگری که برای AOP توسط تیم StructureMap به نام StructureMap.DynamicInterception ارائه شدهاست. در متد Intercept، ابتدا چک میشود که که آیا این متد با TransactionAttribute تزئین شده و طی درخواست جاری برای Context جاری تراکنشی ایجاد نشده باشد؛ سپس تراکنش جدیدی ایجاد شده و بدنه اصلی متد اجرا میشود و نهایتا در صورت موفقیت آمیز بودن عملیات، تراکنش مورد نظر Commit میشود.
در آخر لازم است این Interceptor در تنظیمات اولیه StructureMap به شکل زیر معرفی شود:
Policies.Interceptors(new DynamicProxyInterceptorPolicy( type => typeof(IApplicationService).IsAssignableFrom(type), typeof(AuthorizationInterceptor), typeof(TransactionInterceptor), typeof(ValidationInterceptor)));
نکته: فرض کنید در بدنه اکشن متد یک کنترلر ASP.NET MVC یا ASP.NET Core، دو متد تراکنشی فراخوانی شود؛ در این صورت شاید لازم باشد که این دو متد طی یک تراکنش واحد به جای تراکنشهای مجزا، اجرا شوند؛ بنابراین نیاز است از الگوی Transaction Per Request استفاده شود. برای این کار میتوان یک ActionFilterAttribute سفارشی ایجاد کرد که ایجاد کننده تراکنش باشد و متدهای داخلی که هر کدام جدا تراکنشی بودند، نیز از تراکنش ایجاد شده استفاده کنند.
Defensive Coding به معنی است که شما با انجام یکسری کارها و در نظر گرفتن یکسری زیر ساختها در توسعهی نرم افزار خود، به اهداف ذیل دست پیدا کنید:
1. Quality (کیفیت)
2. Comprehensible (جامعیت)
3. Predictable (قابلیت پیش بینی)
دستیابی به هر کدام از این اهداف و روشهای اعمال آنها بر روی یک پروژهی نرم افزاری، در ادامه بحث خواهند شد.
1. Clean Code
یکی از اهداف Defensive Coding که در ابتدای مقاله بحث شد جامعیت یا Comprehension بود. برای رسید به این هدف از مفهومی به نام Clean Code استفاده میشود. Clean Code علاوه بر این مسئله، در پی ساده کردن ساختار بندی پشتیبانی و کاهش باگهای نرم افزار نیز هست. ویژگیهای Clean Code در بالا با توجه به شکل ذیل تشریح میشوند:
· Easy to read
یک کد Clean قابلیت خوانایی بالایی دارد. بسیاری از برنامه نویسان در سطوح مختلف با اهمیت این مسئله در توسعه نرم افزار آشنایی دارند. ولی بسیاری از همین برنامه نویسان این اصول را رعایت نمیکنند و سعی نمیکنند با اصول پیاده سازی آن در نرم افزارآشنا شوند.
اگر قابلیت خوانایی یک کد بالا باشد:
§ شما میتوانید Pattern های موجود در کد خود را که میتوانید به عنوان نامزدهایی جهت Refactoring هستند، تشخیص دهید.
§ برنامه نویسان دیگر به راحتی قصد و اهداف ( intent ) شما را از نوشتن یک کد خاص درک خواهند کرد و در طول زمان با خطاهای زیادی روبرو نمیشوند.
§ توسعهی راحتتر و در شرایط وجود فشار، ایجاد سریع یک قابلیت جدید در نرم افزار.
· Clear intent
یک کد Clear دارای اهداف روشن و قابل فهمی میباشد.
· Simple
پیچیدگی با کم هزینه بودن توسعهی و پشتیبانی تضاد مستقیم دارد. بنابراین سادگی در کدها باید جزو اهداف اصلی قرار بگیرد.
· Minimal
کد باید به گونهای باشد که تنها یک چیز را انجام داده و آن را به درستی انجام دهد. همچنین وابستگی بین اجزای کد باید در کمترین حد ممکن باشند.
· Thoughtful
یک کد Clean کدی است که ساختار آن متفکرانه طراحی شده باشد. از نحوهی طراحی یک کلاس گرفته تا layering و Tiering پروژه باید کاملا هوشمندانه و با توجه به پارامترهای موجود باشند. همچنین خطاهای خطرناک و استثناءها باید کاملا هندل شوند.
همهی ما با دیدن کد بالا سریعا مفهوم اسپاگتی کد به ذهنمان خطور میکند. تغییر، توسعه و پشتیبانی نرم افزارهایی که کد آنها به این صورت نوشته شده است، بسیار سخت و پر هزینه میباشد. در این حالت تغییر هر یک از اجزاء ممکن است بر سایر قسمتهای دیگر تاثیرات مختلفی داشته باشد. راه کاری که در این حالت ارائه میشود، Refactoring میباشد. در این روش کد را به کلاسها و متدهایی بر حسب عملکرد تقسیم خواهیم کرد. در نهایت کد تولید شده دارای کمترین تاثیر بر سایر قسمتها خواهد بود. توجه داشته باشید که با انجام این کار، قدمی به سوی SOC یا Separation Of Concern برداشتهاید.
1. Testable Code & Unit Test
یکی دیگر از اهداف Defensive Coding افزایش کیفیت یا Quality میباشد که برای رسیدن به این هدف از مفهوم Testable Code & Unit Test استفاده میشود. بسیاری از ویژگیهای Testable Code و Clean Code با هم مشابه میباشند. برای مثال Refactor کردن هر متد به متدهای کوچکتر، تست آن را سادهتر خواهند کرد. در نتیجه نوشتن کدهای Testable ، با نوشتن کدهای clean شروع میشود.
در این قسمت اشارهای به Unit Test شده است؛ اما این مفهوم میتواند به یک مفهوم گستردهتر به نام Automated Code testing، تعمیم داده شود. به این دلیل که تست فقط به Unit Testing محدود نمیشود و میتواند شامل سایر انواع تستها مانند integration test نیز باشد.
برای مثال شکل ذیل را در نظر بگیرید. در انتهای این سناریو یک Page جدید اضافه شده است. خوب؛ برای تست کد اضافه شده، مجبورید برنامه را اجرا کنید، login کنید، دادههای مورد نظر را در فرم وارد کرده و در نهایت شرایط لازم را جهت تست، فراهم کنید تا بتوانید کد جدید را تست کنید. در این بین با خطایی مواجه میشوید. پس برنامه را متوقف میکنید و تغییرات لازم را اعمال میکنید. حال فرض کنید این خطا به این زودیها رفع نشود. در این حالت باید فرآیند بالا را چندین و چند بار انجام دهید. نتیجه اینکه این روش بسیار زمان بر و پر هزینه خواهد بود. البته میزان هزینه و زمان رابطهی نزدیکی با وسعت تغییرات دارند. برای رفع مسائلی از این دست مایکروسافت زیرساختی به نام MS Test ارائه داده است که میتوان با آن سناریوهای تست متفاوتی را پیاده سازی و اجرا نمود. متاسفانه این مسئله در بسیار از جوامع توسعه نرم افزار رعایت نمیشود و در بسیاری از این جوامع، نیروی انسانی، این فرآیند و فرآیندهایی از این دست را انجام میدهند. درحالیکه چنین فرآیندهایی به راحتی توسط ابزارهای ارائه شدهی توسط شرکتهای مختلف قابل مدیریت است.
1. Predictability
یکی دیگر از اهداف Defensive Coding، قابلیت پیش بینی یا Predictability میباشد. فرآیند تشخیص و پیش بینی خطاها را Predictability میگویند. با درنظر گرفتن امکان وقوع خطاهای مختلف و تصمیم گرفتن در مورد اینکه در هنگام رخ دادن این خطا باید چه کاری صورت بگیرد، میتوان در رسیدن به این هدف قدم بزرگی برداشت.
برای رسیدن به این هدف باید اصل Trust but Verify را دنبال کنیم. برای مثال این اصل به ما میگوید که در هنگام تعریف متدهای public باید یکسری موارد را در نظر بگیریم. یک متد باید از یکسری قراردادها پیروی کند. یک متد قرارداد میکند که یکسری پارامترها را با یک data type خاص به عنوان ورودی دریافت کند. قرارداد میکند که یک مقدار خاص با یک data type خاص را به عنوان نوع بازگشتی بازگرداند یا اینکه هیچ مقداری را باز نگرداند و در نهایت یک متد متعهد میشود که یکسری Exception تعریف شده و پیش بینی شده را صادر کند. اما برای اینکه مطمئن شویم یک application واقعا قابل پیش بینی است و این اصل را به درستی پیاده سازی کرده است، اعتماد میکنیم اما Verify را هم انجام میدهیم. برای verify کردن باید پارامترها، دیتاهای متغیر، مقادیر بازگشتی و استثناءها به گونهای بررسی شوند که مطمئن شویم انتظارت ما را برآورده کردهاند.
زیاده روی بیش از حد خوب نیست و آدم باید همیشه حد اعتدال را رعایت کند. این مسئله اینجا هم صادق است؛ به گونهای که زیاده روی بیش از حد در پیاده سازی و اعمال هر کدام یک از این مواردی که در بالا ذکر گردید، ممکن است باعث پیچیدگی ساختار کد و به طبع آن Application شود. بنابراین رعایت حد اعتدال میتواند در رسیدن به این هدف بسیار مهم باشد.
پس از وصول یک درخواست از طریق سیستم مسیریابی، factory پیش فرض (DefaultControllerFactory) به بررسی rout data پرداخته تا خاصیت Controller آن را بیابد و سعی در پیدا کردن کلاسی در برنامه خواهد داشت که مشخصات ذیل را دارا باشد:
- دارای سطح دسترسی public باشد.
- Abstract نباشد.
- حاوی پارامتر generic نباشد.
- نام کلاس دارای پسوند Controller باشد.
- پیاده سازی کننده اینترفیس IContoller باشد.
اگر بخواهید به فضاهای نام خاصی برای یافتن آنها توسط factory پیش فرض، برتری قائل شوید، باید در متد Application_Start فایل global.asax.cs مانند ذیل عمل نمایید:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; using ControllerExtensibility.Infrastructure; namespace ControllerExtensibility { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.DefaultNamespaces.Add("MyControllerNamespace"); ControllerBuilder.Current.DefaultNamespaces.Add("MyProject.*"); } } }
مهمترین دلیلی که نیاز داریم factory پیش فرض را سفارشی کنیم، استفاده از تزریق وابستگیها (DI) به کنترلرهاست. راههای متعددی برای این کار وجود دارند که انتخاب بهترین روش بسته به چگونگی بکارگیری DI در برنامه شماست:
الف) تزریق وابستگی به کنترلر با ایجاد یک controller activator سفارشی
کدهای اینترفیس IControllerActivator مطابق ذیل است:
namespace System.Web.Mvc { using System.Web.Routing; public interface IControllerActivator { IController Create(RequestContext requestContext, Type controllerType); } }
using ControllerExtensibility.Controllers; using System; using System.Web.Mvc; using System.Web.Routing; namespace ControllerExtensibility.Infrastructure { public class StructureMapControllerActivator : IControllerActivator { public IController Create(RequestContext requestContext, Type controllerType) { return (IController)ObjectFactory.GetInstance(controllerType); } } }
برای استفاده از این activator سفارشی نیاز داریم وهلهای از آن را به عنوان پارامتر به سازندهی کلاس DefaultControllerFactory ارسال کنیم و نتیجه را در متد Application_Start فایل global.asax.cs ثبت کنیم.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; using ControllerExtensibility.Infrastructure; namespace ControllerExtensibility { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(new StructureMapControllerActivator())); } } }
میتوان متدهای کلاس مشتق شدهی از DefaultControllerFactory را override کرد و برای اهدافی نظیر DI از آن بهره جست. جدول ذیل سه متدی که میتوان با تحریف آنها به مقصود رسید، توصیف شدهاند:
متد | نوع بازگشتی | توضیحات |
CreateController | IController | پیاده سازی کنندهی متد Createontroller از اینترفیس IControllerFactory است و به صورت پیش فرض متد GetControllerType را جهت تعیین نوعی که باید وهله سازی شود، صدا میزند و سپس کنترلر وهله سازی شده را به متد GetControllerInstance ارسال میکند. |
GetControllerType | Type | وظیفهی نگاشت درخواست رسیده را به Controller type عهده دار است. |
GetControllerInstance | IController | وظیفه ایجاد وهلهای از نوع مشخص شده را عهده دار است. |
شیوهی تحریف متد GetControllerInstance
public class StructureMapControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return ObjectFactory.GetInstance(controllerType) as Controller; } }
ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());
نمونهای عملی آنرا در مقالهی (EF Code First #12) و یا دورهی «بررسی مفاهیم معکوس سازی وابستگیها و ابزارهای مرتبط با آن» میتوانید بررسی کنید.
توسعه چابک بانکهای اطلاعاتی
روشهایی برای حذف D از CRUD
مراحل انجام این بازسازی کد
- تمامی امکانات کلاس مبدا را که متد مورد نظر از آنها استفاده میکند، بررسی نمایید که آیا آنها نیز نیاز به انتقال دارند یا خیر.
- کلاسهای پدر و فرزند کلاس مبدا را برای یافتن تعاریف دیگری از متد مورد نظر بررسی نمایید. اگر تعاریف دیگری وجود داشتند به راحتی نمیتوان متد را جابجا کرد. در این صورت اگر قصد جابجایی داشتید، باید به فکر جابجایی رابطه چند ریختی موجود نیز باشید.
- متد را در کلاس مقصد ایجاد نمایید.
- بدنه متد را به متد مقصد منتقل نمایید و تمامی امکانات استفاده شده در آن را طوری تغییر دهید که در کلاس جدید کار کند. اگر متد، نیاز به اشارهای به کلاس مبدا داشت، باید تعیین نمایید که به چه صورت این اشاره انجام شود. اگر مکانیزم مدیریت خطایی (exception handling) در متد مبدا پیاده سازی شده بود، تعیین کنید که آیا متد مبدا نیز کماکان امر مدیریت خطا را انجام خواهد داد، یا به متد مقصد انتقال خواهد یافت.
- کد کلاس مقصد را کامپایل و تست نمایید.
- اگر متد مبدا را به عنوان فراخوان متد مقصد نگه داشتید، باید تصمیم بگیرید که کلاس مقصد در آن متد به چه صورت استفاده خواهد شد.
- فراخوانی متد مقصد را به بدنه متد مبدا اضافه کنید.
- کد را کامپایل و تست نمایید.
- در مورد سرنوشت متد مبدا تصمیم گیری نمایید که آیا نیازی به وجود آن هست یا خیر. در صورتیکه از متد مبدا در مکانهای زیادی استفاده شده یا متد در کتابخانه یا فریم ورکی است که کنترلی بر روی استفاده کنندگان آن وجود ندارد، احتمالا باقی ماندن متد به عنوان صرفا فراخوان، ایده خوبی باشد.
- اگر متد مبدا را حذف کردید تمامی استفاده از آن را باید به متد مقصد تغییر دهید. توجه داشته باشید ممکن است سناریو ساختن کلاس جدید با کلاس قدیمی متفاوت باشد.
- مجددا کد را کامپایل و تست نمایید.
public class Event { public List<Person> Participants { get; internal set; } } public class Person { public int Id { get; private set; } public void Participate(Event ev) { var isParticipatedAlready = ev.Participants.Any(ff => ff.Id == Id); if (isParticipatedAlready) return; ev.Participants.Add(this); } }
public class Event { protected List<Person> Participants { get; set; } public void Participate(Person person) { var isParticipatedAlready = Participants.Any(ff => ff.Id == person.Id); if (isParticipatedAlready) return; Participants.Add(person); } } public class Person { public int Id { get; private set; } }
WPF allows you to build modern desktop applications for Windows, and part of building an application is debugging code and optimizing performance. In Alessandro Del Sole’s WPF Debugging and Performance Succinctly, you will learn how to debug a WPF application by leveraging all the powerful tools in Visual Studio, including the most recent additions that allow you to investigate the behavior of the UI at runtime. Also, you will learn how to analyze and improve an application’s performance in order to provide your customers with the best possible experience and thereby make them happy.
- Debugging WPF Applications
- Stepping Through Code
- Working with Debug Windows
- Debugger Visualizers and Trace Listeners
- XAML Debugging
- Analyzing the UI Performances
- Analyzing the Application Performances
روشهایی برای بهبود کیفیت کدها
مهاجرت به AngularJS 2.0
"Our goal with Angular 2 is to make the best possible set of tools for
building web apps not constrained by maintaining backwards compatibility
with existing APIs." —Angular blog