در بخش publish از منوی debug میتوانید نوع خروجی را مشخص کنید که برنامه با debug پابلیش شود یا realease
یکی از مواردی که عموما در برنامه نویسی با آن سر و کار داریم، parse اطلاعات با فرمتهای مختلف است. از CSV تا XML تا ... JSON .
در مورد کار با XML در دات نت فریم ورک، فضاهای نام مرتبط زیادی وجود دارند؛ برای مثال System.Xml.Linq و System.Xml . همچنین یک روش دیگر هم برای کار با اطلاعات XML ایی در دات نت وجود دارد. میشود کلاس معادل یک فایل XML را تولید و سپس اطلاعات آنرا به این کلاس نگاشت کرد. اطلاعات بیشتر : (^). این برنامه کار خود مایکروسافت است.
در مورد JSON از دات نت سه و نیم به بعد کارهایی صورت گرفته مانند : (^). اما آنچنان دلچسب نیست. جهت رفع این خلاء کتابخانهی سورس باز و بسیار کاملی در این زمینه به نام JSON.NET تهیه شده که از این آدرس قابل دریافت است: (^)
و خبر خوب اینکه امکان تهیه کلاسهای معادل اطلاعات JSON ایی هم مدتیاست توسط برنامه نویسهای مستقل تهیه شده است. یا میتوان از امکانات توکار دات نت استفاده کرد یا از کتابخانههایی مانند JSON.NET یا از هیچکدام! میتوان یک راست کل اطلاعات JSON ایی دریافتی را به یک یا چند کلاس معادل آن نگاشت کرد:
- پروژه سورس باز JSON C# Class Generator
- و یا یک ابزار آنلاین مشابه: json2csharp
در قسمت آخر این سری، نگاهی خواهیم داشت به نحوهی توزیع برنامههای React و نکات مرتبط با آن.
افزودن متغیرهای محیطی
در برنامهی نمایش لیست فیلمهایی که تا قسمت 29 آنرا بررسی کردیم، از فایل src\config.json برای ذخیره سازی اطلاعات تنظیمات برنامه استفاده شد. هرچند این روش کار میکند اما بر اساس محیطهای مختلف توسعه، متغیر نیست. اغلب برنامهها باید بتوانند حداقل در سه محیط توسعه، آزمایش و تولید، بر اساس متغیرها و تنظیمات خاص هر کدام، کار کنند. برای مثال بر روی سیستمی که کار توسعه در آن انجام میشود، میخواهیم apiUrl متفاوتی را نسبت به حالتیکه برنامه توزیع میشود، داشته باشیم.
برای رفع این مشکل، برنامههایی که توسط create-react-app تولید میشوند، دارای پشتیبانی توکاری از متغیرهای محیطی هستند. برای این منظور نیاز است در ریشهی پروژه (جائیکه فایل package.json قرار دارد) فایل جدید env. را ایجاد کرد. در ویندوز برای ایجاد یک چنین فایلهایی که فقط از یک پسوند تشکیل میشوند، باید نام فایل را به صورت .env. وارد کرد؛ سپس خود ویندوز نقطهی نهایی را حذف میکند. البته اگر از ادیتور VSCode برای ایجاد این فایل استفاده میکنید، نیازی به درج نقطهی انتهایی نیست. در این فایل environment ایجاد شده میتوان تمام متغیرهای محیطی مورد نیاز را با مقادیر پیشفرض آنها درج کرد. همچنین میتوان این مقادیر پیشفرض را بر اساس محیطهای مختلف کاری، بازنویسی کرد. برای مثال میتوان فایل env.development. را اضافه کرد؛ به همراه فایلهای env.test. و env.production.
متغیرهای محیطی به صورت key=value درج میشوند. این کلیدها نیر باید با REACT_APP_ شروع شوند؛ در غیر اینصورت، کار نخواهند کرد. برای مثال در فایل env.، دو متغیر پیشفرض زیر را تعریف میکنیم:
اکنون برای خواندن این متغیرها برای مثال در فایل index.js (و یا هر فایل جاوا اسکریپتی دیگری در برنامه)، سطر زیر را درج میکنیم:
process به معنای پروسهی جاری برنامهاست (و مرتبط است به پروسهی node.js ای که برنامهی React را اجرا میکند) و خاصیت env، به همراه تمام متغیرهای محیطی برنامه میباشد. در این حالت اگر برنامه را اجرا کنیم، در کنسول توسعه دهندگان مرورگر، به یک چنین خروجی خواهیم رسید:
در این خروجی، متغیر "NODE_ENV: "development به صورت خودکار با تولید بستههای مخصوص ارائهی نهایی، به production تنظیم میشود. سایر متغیرهای محیطی تعریف شده را نیز در اینجا ملاحظه میکنید. با توجه به خواص شیء env، برای مثال جهت دسترسی به نام برنامه میتوان از مقدار process.env.REACT_APP_NAME استفاده کرد.
یک نکته: با هر تغییری در مقادیر متغیرهای محیطی، نیاز است یکبار دیگر برنامه را از ابتدا توسط دستور npm start، راه اندازی مجدد کرد؛ چون این فایلها به صورت خودکار ردیابی نمیشوند.
نحوهی پردازش متغیرهای محیطی درج شدهی در برنامه
اگر همان سطر لاگ کردن خروجی process.env را به صورت زیر تغییر دهیم:
و برنامه را مجددا اجرا کنیم، با مراجعهی به برگهی Sources و انتخاب مسیر localhost:3000/static/js/main.chunk.js و سپس جستجوی "My App Name" ای که در اینجا اضافه کردیم (با فشردن دکمههای Ctrl+F)، به خروجی زیر خواهیم رسید:
همانطور که مشاهده میکنید، فراخوانی console.log ما، دیگر به همراه متغیر process.env.REACT_APP_NAME نیست؛ بلکه مقدار اصلی این متغیر در اینجا درج شدهاست. بنابراین اگر در در حین توسعهی برنامه، از متغیرهای محیطی استفاده شود، این متغیرها با مقادیر اصلی آنها در حین پروسهی Build نهایی، جایگزین میشوند.
Build برنامههای React برای محیط تولید
اجرای دستور npm start، سبب ایجاد یک Build مخصوص محیط توسعه میشود که بهینه سازی نشدهاست و به همراه اطلاعات اضافی قابل توجهی جهت دیباگ سادهتر برنامهاست. برای رسیدن به یک خروجی بهینه سازی شدهی مخصوص محیط تولید و ارائهی نهایی باید دستور npm run build را در خط فرمان اجرا کرد. خروجی نهایی این دستور، در پوشهی جدید build واقع در ریشهی پروژه، قرار میگیرد. اکنون میتوان کل محتویات این پوشه را جهت ارائهی نهایی در وب سرور خود، مورد استفاده قرار داد.
پس از پایان اجرای دستور npm run build، پیام «امکان ارائهی آن توسط static server زیر نیز وجود دارد» ظاهر میشود:
اگر علاقمند باشید تا خروجی حالت production تولید شده را نیز به صورت محلی آزمایش کنید، ابتدا باید static server یاد شده را توسط دستور npm install فوق نصب کنید. سپس ریشهی پروژه را در خط فرمان باز کرده و دستور serve -s build را صادر کنید (البته اگر با خط فرمان به پوشهی build وارد شدید، دیگر نیازی به ذکر پوشهی build نخواهد بود). اکنون میتوانید برنامه را در آدرس http://localhost:5000 در مرورگر خود بررسی نمائید.
البته با توجه به اینکه backend سرور برنامههای ما نیز در همین آدرس قرار دارد و در صورت ورود این آدرس، به صورت خودکار به https://localhost:5001/index.html هدایت خواهید شد، میتوان این پورت پیشفرض را با اجرای دستور serve -s build -l 1234 تغییر داد. اکنون میتوان آدرس جدید http://localhost:1234 را در مرورگر آزمایش کرد که ... با خطای زیر کار نمیکند:
روش رفع این مشکل را در قسمت 23 بررسی کردیم و در اینجا جهت بهبود آن میتوان متد WithOrigins فایل Startup.cs را به صورت زیر تکمیل کرد:
یک نکته: زمانیکه از دستور npm start استفاده میشود، متغیرهای محیطی از فایل env.development. خوانده خواهند شد و زمانیکه از دستور npm run build استفاده میشود، این متغیرها از فایل env.production. تامین میشوند. در این حالتها اگر متغیری در این دو فایل درج نشده بود، از مقدار پیشفرض موجود در فایل env. استفاده میگردد. از فایل env.test. با اجرای دستور npm test، به صورت خودکار استفاده میشود.
آماده سازی برنامهی React، برای توزیع نهایی
تا اینجا برنامهی React تهیه شده، اطلاعات apiUrl خودش را از فایل config.json دریافت میکند. اکنون میخواهیم بر اساس حالات مختلف توسعه و تولید، از apiUrlهای متفاوتی استفاده شود. به همین جهت به فایل env.production. مراجعه کرده و تنظیمات ذیل را به آن اضافه میکنیم:
البته فعلا همین متغیرها را به فایل env.development. نیز میتوان اضافه کرد؛ چون backend سرور ما در هر دو حالت، در این مثال، در آدرس فوق قرار دارد.
اکنون به برنامه مراجعه کرده و در هرجائی که ارجاعی به فایل config.json وجود دارد، سطر import آنرا حذف میکنیم. با این تغییر، تمام آدرسهایی مانند:
را به صورت زیر ویرایش میکنیم:
در ادامه برای تامین مقدار apiUrl به صورت خودکار، به فایل src\services\httpService.js مراجعه کرده و در ابتدای آن، یک سطر زیر را اضافه میکنیم:
به این ترتیب تمام درخواستهای ارسالی توسط Axios، دارای baseURL ای خواهند شد که از فایل متغیر محیطی جاری تامین میشود. همانطور که پیشتر نیز عنوان شد، این مقدار در زمان Build، با مقدار ثابتی که از فایل env جاری خوانده میشود، جایگزین خواهد شد.
همچنین adminRoleName مورد نیاز در فایل src\services\authService.js را نیز از همان فایل env جاری تامین میکنیم:
پس از این تغییرات، نیاز است برای حالت توسعه، یکبار دیگر دستور npm start و یا برای حالت تولید، دستور npm run build را اجرا کرد تا اطلاعات درج شدهی در فایلهای env.، پردازش و جایگزین شوند.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-34-frontend.zip و sample-34-backend.zip
افزودن متغیرهای محیطی
در برنامهی نمایش لیست فیلمهایی که تا قسمت 29 آنرا بررسی کردیم، از فایل src\config.json برای ذخیره سازی اطلاعات تنظیمات برنامه استفاده شد. هرچند این روش کار میکند اما بر اساس محیطهای مختلف توسعه، متغیر نیست. اغلب برنامهها باید بتوانند حداقل در سه محیط توسعه، آزمایش و تولید، بر اساس متغیرها و تنظیمات خاص هر کدام، کار کنند. برای مثال بر روی سیستمی که کار توسعه در آن انجام میشود، میخواهیم apiUrl متفاوتی را نسبت به حالتیکه برنامه توزیع میشود، داشته باشیم.
برای رفع این مشکل، برنامههایی که توسط create-react-app تولید میشوند، دارای پشتیبانی توکاری از متغیرهای محیطی هستند. برای این منظور نیاز است در ریشهی پروژه (جائیکه فایل package.json قرار دارد) فایل جدید env. را ایجاد کرد. در ویندوز برای ایجاد یک چنین فایلهایی که فقط از یک پسوند تشکیل میشوند، باید نام فایل را به صورت .env. وارد کرد؛ سپس خود ویندوز نقطهی نهایی را حذف میکند. البته اگر از ادیتور VSCode برای ایجاد این فایل استفاده میکنید، نیازی به درج نقطهی انتهایی نیست. در این فایل environment ایجاد شده میتوان تمام متغیرهای محیطی مورد نیاز را با مقادیر پیشفرض آنها درج کرد. همچنین میتوان این مقادیر پیشفرض را بر اساس محیطهای مختلف کاری، بازنویسی کرد. برای مثال میتوان فایل env.development. را اضافه کرد؛ به همراه فایلهای env.test. و env.production.
متغیرهای محیطی به صورت key=value درج میشوند. این کلیدها نیر باید با REACT_APP_ شروع شوند؛ در غیر اینصورت، کار نخواهند کرد. برای مثال در فایل env.، دو متغیر پیشفرض زیر را تعریف میکنیم:
REACT_APP_NAME=My App REACT_APP_VERSION=1
console.log(process.env);
در این خروجی، متغیر "NODE_ENV: "development به صورت خودکار با تولید بستههای مخصوص ارائهی نهایی، به production تنظیم میشود. سایر متغیرهای محیطی تعریف شده را نیز در اینجا ملاحظه میکنید. با توجه به خواص شیء env، برای مثال جهت دسترسی به نام برنامه میتوان از مقدار process.env.REACT_APP_NAME استفاده کرد.
یک نکته: با هر تغییری در مقادیر متغیرهای محیطی، نیاز است یکبار دیگر برنامه را از ابتدا توسط دستور npm start، راه اندازی مجدد کرد؛ چون این فایلها به صورت خودکار ردیابی نمیشوند.
نحوهی پردازش متغیرهای محیطی درج شدهی در برنامه
اگر همان سطر لاگ کردن خروجی process.env را به صورت زیر تغییر دهیم:
console.log("My App Name", process.env.REACT_APP_NAME);
همانطور که مشاهده میکنید، فراخوانی console.log ما، دیگر به همراه متغیر process.env.REACT_APP_NAME نیست؛ بلکه مقدار اصلی این متغیر در اینجا درج شدهاست. بنابراین اگر در در حین توسعهی برنامه، از متغیرهای محیطی استفاده شود، این متغیرها با مقادیر اصلی آنها در حین پروسهی Build نهایی، جایگزین میشوند.
Build برنامههای React برای محیط تولید
اجرای دستور npm start، سبب ایجاد یک Build مخصوص محیط توسعه میشود که بهینه سازی نشدهاست و به همراه اطلاعات اضافی قابل توجهی جهت دیباگ سادهتر برنامهاست. برای رسیدن به یک خروجی بهینه سازی شدهی مخصوص محیط تولید و ارائهی نهایی باید دستور npm run build را در خط فرمان اجرا کرد. خروجی نهایی این دستور، در پوشهی جدید build واقع در ریشهی پروژه، قرار میگیرد. اکنون میتوان کل محتویات این پوشه را جهت ارائهی نهایی در وب سرور خود، مورد استفاده قرار داد.
پس از پایان اجرای دستور npm run build، پیام «امکان ارائهی آن توسط static server زیر نیز وجود دارد» ظاهر میشود:
> npm install -g serve > serve -s build
البته با توجه به اینکه backend سرور برنامههای ما نیز در همین آدرس قرار دارد و در صورت ورود این آدرس، به صورت خودکار به https://localhost:5001/index.html هدایت خواهید شد، میتوان این پورت پیشفرض را با اجرای دستور serve -s build -l 1234 تغییر داد. اکنون میتوان آدرس جدید http://localhost:1234 را در مرورگر آزمایش کرد که ... با خطای زیر کار نمیکند:
Access to XMLHttpRequest at 'https://localhost:5001/api/genres' from origin 'http://localhost:1234' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
WithOrigins("http://localhost:3000", "http://localhost:1234")
یک نکته: زمانیکه از دستور npm start استفاده میشود، متغیرهای محیطی از فایل env.development. خوانده خواهند شد و زمانیکه از دستور npm run build استفاده میشود، این متغیرها از فایل env.production. تامین میشوند. در این حالتها اگر متغیری در این دو فایل درج نشده بود، از مقدار پیشفرض موجود در فایل env. استفاده میگردد. از فایل env.test. با اجرای دستور npm test، به صورت خودکار استفاده میشود.
آماده سازی برنامهی React، برای توزیع نهایی
تا اینجا برنامهی React تهیه شده، اطلاعات apiUrl خودش را از فایل config.json دریافت میکند. اکنون میخواهیم بر اساس حالات مختلف توسعه و تولید، از apiUrlهای متفاوتی استفاده شود. به همین جهت به فایل env.production. مراجعه کرده و تنظیمات ذیل را به آن اضافه میکنیم:
REACT_APP_API_URL=https://localhost:5001/api REACT_APP_ADMIN_ROLE_NAME=Admin
اکنون به برنامه مراجعه کرده و در هرجائی که ارجاعی به فایل config.json وجود دارد، سطر import آنرا حذف میکنیم. با این تغییر، تمام آدرسهایی مانند:
const apiEndpoint = apiUrl + "/users";
const apiEndpoint = "/users";
axios.defaults.baseURL = process.env.REACT_APP_API_URL;
همچنین adminRoleName مورد نیاز در فایل src\services\authService.js را نیز از همان فایل env جاری تامین میکنیم:
const adminRoleName = process.env.REACT_APP_ADMIN_ROLE_NAME;
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-34-frontend.zip و sample-34-backend.zip
اشتراکها
NET Framework 4.5.3. منتشر شد
در آن RyuJIT به عنوان JIT Compiler جدید پیش فرض سیستمهای 64 بیتی قرار داده شدهاست. همچنین 150 API جدید به همراه به روز رسانی 50 API موجود در آن پیش بینی شدهاند. تغییرات صورت گرفته
مطالب دورهها
آشنایی با AOP Interceptors
در حین استفاده از Interceptors، کار مداخله و تحت نظر قرار دادن قسمتهای مختلف کدها، توسط کامپوننتهای خارجی صورت خواهد گرفت. این کامپوننتهای خارجی، به صورت پویا، تزئین کنندههایی را جهت محصور سازی قسمتهای مختلف کدهای شما تولید میکنند. اینها، بسته به تواناییهایی که دارند، در زمان اجرا و یا حتی در زمان کامپایل نیز قابل تنظیم میباشند.
ابزارهایی جهت تولید AOP Interceptors
متداولترین کامپوننتهای خارجی که جهت تولید AOP Interceptors مورد استفاده قرار میگیرند، همان IOC Containers معروف هستند مانند StructureMap، Ninject، MS Unity و غیره.
سایر ابزارهای تولید AOP Interceptors، از روش تولید Dynamic proxies بهره میگیرند. به این ترتیب مزین کنندههایی پویا، در زمان اجرا، کدهای شما را محصور خواهند کرد. (نمونهای از آنرا شاید در حین کار با ORMهای مختلف دیده باشید).
نگاهی به فرآیند Interception
زمانیکه از یک IOC Container در کدهای خود استفاده میکنید، مراحلی چند رخ خواهند داد:
الف) کد فراخوان، از IOC Container، یک شیء مشخص را درخواست میکند. عموما اینکار با درخواست یک اینترفیس صورت میگیرد؛ هرچند محدودیتی نیز وجود نداشته و امکان درخواست یک کلاس از نوعی مشخص نیز وجود دارد.
ب) در ادامه IOC Container به لیست اشیاء قابل ارائه توسط خود نگاه کرده و در صورت وجود، وهله سازی شیء درخواست شده را انجام و نهایتا شیء مطلوب را بازگشت خواهد داد.
ج) سپس، کد فراخوان، وهله دریافتی را مورد پردازش قرار داده و شروع به استفاده از متدها و خواص آن خواهد نمود.
اکنون با اضافه کردن Interception به این پروسه، چند مرحله دیگر نیز در این بین به آن اضافه خواهند شد:
الف) در اینجا نیز در ابتدا کد فراخوان، درخواست وهلهای را بر اساس اینترفیسی خاص به IOC Container ارائه میدهد.
ب) IOC Container نیز سعی در وهله سازی درخواست رسیده بر اساس تنظیمات اولیه خود میکند.
ج) اما در این حالت IOC Container تشخیص میدهد، نوعی که باید بازگشت دهد، علاوه بر وهله سازی، نیاز به مزین سازی توسط Aspects و پیاده سازی Interceptors را نیز دارد. بنابراین نوع مورد انتظار را در صورت وجود، به یک Dynamic Proxy، بجای بازگشت مستقیم به فراخوان ارائه میدهد.
د) در ادامه Dynamic Proxy، نوع مورد انتظار را توسط Interceptors محصور کرده و به فراخوان بازگشت میدهد.
ه) اکنون فراخوان، در حین استفاده از امکانات شیء وهله سازی شده، به صورت خودکار مراحل مختلف اجرای یک Aspect را که در قسمت قبل بررسی شدند، سبب خواهد شد.
نحوه ایجاد Interceptors
برای ایجاد یک Interceptor دو مرحله باید انجام شود:
الف) پیاده سازی یک اینترفیس
ب) اتصال آن به کدهای اصلی برنامه
در ادامه قصد داریم از یک IOC Container معروف به نام StructureMap در یک برنامه کنسول استفاده کنیم. برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نوگت ویژوال استودیو فراخوانی کنید:
پس از آن یک برنامه کنسول جدید را ایجاد کنید. (هدف از استفاده از این نوع پروژه خاص، توضیح جزئیات یک فناوری، بدون درگیر شدن با لایه UI است)
البته باید دقت داشت که برای استفاده از StructureMap نیاز است به خواص پروژه مراجعه و سپس حالت Client profile را به Full profile تغییر داد تا برنامه قابل کامپایل باشد.
اکنون کدهای این برنامه را به نحو فوق تغییر دهید.
در اینجا یک اینترفیس نمونه و پیاده سازی آنرا ملاحظه میکنید. همچنین نحوه آغاز تنظیمات StructureMap و نحوه دریافت یک وهله متناظر با IMyType نیز بیان شدهاند.
نکتهی مهمی که در اینجا باید به آن دقت داشت، وضعیت شیء myType حین فراخوانی متد myType.DoSomething است. شیء myType در اینجا، دقیقا یک وهلهی متداول از کلاس myType است و هیچگونه دخل و تصرفی در نحوه اجرای آن صورت نگرفته است.
خوب! تا اینجای کار را احتمالا پیشتر نیز دیده بودید. در ادامه قصد داریم یک Interceptor را طراحی و مراحل چهارگانه اجرای یک Aspect را در اینجا بررسی کنیم.
در ادامه نیاز خواهیم داشت تا یک Dynamic proxy را نیز مورد استفاده قرار دهیم؛ از این جهت که StructureMap تنها دارای Interceptorهای وهله سازی اطلاعات است و نه Method Interceptor. برای دسترسی به Method Interceptors نیاز به یک Dynamic proxy نیز میباشد. در اینجا از Castle.Core استفاده خواهیم کرد:
برای دریافت آن تنها کافی است دستور پاور شل فوق را در خط فرمان کنسول پاورشل نوگت در VS.NET اجرا کنید.
سپس کلاس ذیل را به پروژه جاری اضافه کنید:
در کلاس فوق کار Method Interception توسط امکانات Castle.Core انجام شده است. این کلاس باید اینترفیس IInterceptor را پیاده سازی کند. در این متد سطر invocation.Proceed دقیقا معادل فراخوانی متد مورد نظر است. مراحل چهارگانه شروع، پایان، خطا و موفقیت نیز توسط try/catch/finally پیاده سازی شدهاند.
اکنون برای معرفی این کلاس به برنامه کافی است سطرهای ذیل را اندکی ویرایش کنیم:
در اینجا تنها سطر EnrichAllWith آن جدید است. ابتدا یک پروکسی پویا تولید شده است. سپس این پروکسی پویا کار دخالت و تحت نظر قرار دادن اجرای متدهای اینترفیس IMyType را عهده دار خواهد شد.
برای مثال اکنون با فراخوانی متد myType.DoSomething، ابتدا کنترل برنامه به پروکسی پویای تشکیل شده توسط Castle.Core منتقل میشود. در اینجا هنوز هم متد DoSomething فراخوانی نشده است. ابتدا وارد بدنه متد public void Intercept خواهیم شد. سپس سطر invocation.Proceed، فراخوانی واقعی متد DoSomething اصلی را انجام میدهد. در ادامه باز هم فرصت داریم تا مراحل موفقیت، خطا یا خروج را لاگ کنیم.
تنها زمانیکه کار متد public void Intercept به پایان میرسد، سطر پس از فراخوانی متد myType.DoSomething اجرا خواهد شد.
در این حالت اگر برنامه را اجرا کنیم، چنین خروجی را نمایش میدهد:
بنابراین در اینجا نحوه دخالت و تحت نظر قرار دادن اجرای متدهای یک کلاس عمومی خاص را ملاحظه میکنید. برای اینکه کنترل کامل را در دست بگیریم، کلاس پروکسی پویا وارد عمل شده و اینجا است که این کلاس پروکسی تصمیم میگیرد چه زمانی باید فراخوانی واقعی متد مورد نظر انجام شود.
برای اینکه فراخوانی قسمت On Error را نیز ملاحظه کنید، یک استثنای عمدی را در متد DoSomething قرار داده و مجددا برنامه را اجرا کنید.
ابزارهایی جهت تولید AOP Interceptors
متداولترین کامپوننتهای خارجی که جهت تولید AOP Interceptors مورد استفاده قرار میگیرند، همان IOC Containers معروف هستند مانند StructureMap، Ninject، MS Unity و غیره.
سایر ابزارهای تولید AOP Interceptors، از روش تولید Dynamic proxies بهره میگیرند. به این ترتیب مزین کنندههایی پویا، در زمان اجرا، کدهای شما را محصور خواهند کرد. (نمونهای از آنرا شاید در حین کار با ORMهای مختلف دیده باشید).
نگاهی به فرآیند Interception
زمانیکه از یک IOC Container در کدهای خود استفاده میکنید، مراحلی چند رخ خواهند داد:
الف) کد فراخوان، از IOC Container، یک شیء مشخص را درخواست میکند. عموما اینکار با درخواست یک اینترفیس صورت میگیرد؛ هرچند محدودیتی نیز وجود نداشته و امکان درخواست یک کلاس از نوعی مشخص نیز وجود دارد.
ب) در ادامه IOC Container به لیست اشیاء قابل ارائه توسط خود نگاه کرده و در صورت وجود، وهله سازی شیء درخواست شده را انجام و نهایتا شیء مطلوب را بازگشت خواهد داد.
ج) سپس، کد فراخوان، وهله دریافتی را مورد پردازش قرار داده و شروع به استفاده از متدها و خواص آن خواهد نمود.
اکنون با اضافه کردن Interception به این پروسه، چند مرحله دیگر نیز در این بین به آن اضافه خواهند شد:
الف) در اینجا نیز در ابتدا کد فراخوان، درخواست وهلهای را بر اساس اینترفیسی خاص به IOC Container ارائه میدهد.
ب) IOC Container نیز سعی در وهله سازی درخواست رسیده بر اساس تنظیمات اولیه خود میکند.
ج) اما در این حالت IOC Container تشخیص میدهد، نوعی که باید بازگشت دهد، علاوه بر وهله سازی، نیاز به مزین سازی توسط Aspects و پیاده سازی Interceptors را نیز دارد. بنابراین نوع مورد انتظار را در صورت وجود، به یک Dynamic Proxy، بجای بازگشت مستقیم به فراخوان ارائه میدهد.
د) در ادامه Dynamic Proxy، نوع مورد انتظار را توسط Interceptors محصور کرده و به فراخوان بازگشت میدهد.
ه) اکنون فراخوان، در حین استفاده از امکانات شیء وهله سازی شده، به صورت خودکار مراحل مختلف اجرای یک Aspect را که در قسمت قبل بررسی شدند، سبب خواهد شد.
نحوه ایجاد Interceptors
برای ایجاد یک Interceptor دو مرحله باید انجام شود:
الف) پیاده سازی یک اینترفیس
ب) اتصال آن به کدهای اصلی برنامه
در ادامه قصد داریم از یک IOC Container معروف به نام StructureMap در یک برنامه کنسول استفاده کنیم. برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نوگت ویژوال استودیو فراخوانی کنید:
PM> Install-Package structuremap
البته باید دقت داشت که برای استفاده از StructureMap نیاز است به خواص پروژه مراجعه و سپس حالت Client profile را به Full profile تغییر داد تا برنامه قابل کامپایل باشد.
using System; using StructureMap; namespace AOP00 { public interface IMyType { void DoSomething(string data, int i); } public class MyType : IMyType { public void DoSomething(string data, int i) { Console.WriteLine("DoSomething({0}, {1});", data, i); } } class Program { static void Main(string[] args) { ObjectFactory.Initialize(x => { x.For<IMyType>().Use<MyType>(); }); var myType = ObjectFactory.GetInstance<IMyType>(); myType.DoSomething("Test", 1); } } }
در اینجا یک اینترفیس نمونه و پیاده سازی آنرا ملاحظه میکنید. همچنین نحوه آغاز تنظیمات StructureMap و نحوه دریافت یک وهله متناظر با IMyType نیز بیان شدهاند.
نکتهی مهمی که در اینجا باید به آن دقت داشت، وضعیت شیء myType حین فراخوانی متد myType.DoSomething است. شیء myType در اینجا، دقیقا یک وهلهی متداول از کلاس myType است و هیچگونه دخل و تصرفی در نحوه اجرای آن صورت نگرفته است.
خوب! تا اینجای کار را احتمالا پیشتر نیز دیده بودید. در ادامه قصد داریم یک Interceptor را طراحی و مراحل چهارگانه اجرای یک Aspect را در اینجا بررسی کنیم.
در ادامه نیاز خواهیم داشت تا یک Dynamic proxy را نیز مورد استفاده قرار دهیم؛ از این جهت که StructureMap تنها دارای Interceptorهای وهله سازی اطلاعات است و نه Method Interceptor. برای دسترسی به Method Interceptors نیاز به یک Dynamic proxy نیز میباشد. در اینجا از Castle.Core استفاده خواهیم کرد:
PM> Install-Package Castle.Core
سپس کلاس ذیل را به پروژه جاری اضافه کنید:
using System; using Castle.DynamicProxy; namespace AOP00 { public class LoggingInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { try { Console.WriteLine("Logging On Start."); invocation.Proceed(); //فراخوانی متد اصلی در اینجا صورت میگیرد Console.WriteLine("Logging On Success."); } catch (Exception ex) { Console.WriteLine("Logging On Error."); throw; } finally { Console.WriteLine("Logging On Exit."); } } } }
اکنون برای معرفی این کلاس به برنامه کافی است سطرهای ذیل را اندکی ویرایش کنیم:
static void Main(string[] args) { ObjectFactory.Initialize(x => { var dynamicProxy = new ProxyGenerator(); x.For<IMyType>().Use<MyType>(); x.For<IMyType>().EnrichAllWith(myTypeInterface => dynamicProxy.CreateInterfaceProxyWithTarget(myTypeInterface, new LoggingInterceptor())); }); var myType = ObjectFactory.GetInstance<IMyType>(); myType.DoSomething("Test", 1); }
برای مثال اکنون با فراخوانی متد myType.DoSomething، ابتدا کنترل برنامه به پروکسی پویای تشکیل شده توسط Castle.Core منتقل میشود. در اینجا هنوز هم متد DoSomething فراخوانی نشده است. ابتدا وارد بدنه متد public void Intercept خواهیم شد. سپس سطر invocation.Proceed، فراخوانی واقعی متد DoSomething اصلی را انجام میدهد. در ادامه باز هم فرصت داریم تا مراحل موفقیت، خطا یا خروج را لاگ کنیم.
تنها زمانیکه کار متد public void Intercept به پایان میرسد، سطر پس از فراخوانی متد myType.DoSomething اجرا خواهد شد.
در این حالت اگر برنامه را اجرا کنیم، چنین خروجی را نمایش میدهد:
Logging On Start. DoSomething(Test, 1); Logging On Success. Logging On Exit.
برای اینکه فراخوانی قسمت On Error را نیز ملاحظه کنید، یک استثنای عمدی را در متد DoSomething قرار داده و مجددا برنامه را اجرا کنید.
OLTP درون حافظهای، مهمترین ویژگی جدید SQL Server 2014 است. موتور بانک اطلاعاتی disk based اس کیوال سرور، حدود 15 تا 20 سال قبل تهیه شدهاست و موتور جدید درون حافظهای OLTP آن، بزرگترین بازنویسی این سیستم از زمان ارائهی آن میباشد و شروع این پروژه به 5 سال قبل بر میگردد. علت تهیهی آن نیز به نیازهای بالای پردازشهای همزمان مصرف کنندگان این محصول در سالهای اخیر، نسبت به 15 سال قبل مرتبط است. با استفاده از امکانات OLTP درون حافظهای، امکان داشتن جداول معمولی disk based و جداول جدید memory optimized با هم در یک بانک اطلاعاتی میسر است؛ به همراه مهیا بودن تمام زیرساختهایی مانند تهیه بک آپ، بازیابی آنها، امنیت و غیره برای آنها.
آیا جداول بهینه سازی شدهی برای حافظه، همان DBCC PINTABLE منسوخ شده هستند؟
در نگارشهای قدیمیتر اس کیوال سرور، دستوری وجود داشت به نام DBCC PINTABLE که سبب ثابت نگه داشتن صفحات جداول مبتنی بر دیسک یک دیتابیس، در حافظه میشد. به این ترتیب تمام خواندنهای مرتبط با آن جدول، از حافظه صورت میگرفت. مشکل این روش که سبب منسوخ شدن آن گردید، اثرات جانبی آن بود؛ مانند خوانده شدن صفحات جدیدتر (با توجه به اینکه ساختار پردازشی و موتور بانک اطلاعاتی تغییری نکرده بود) و نیاز به حافظهی بیشتر تا حدی که کل کش بافر سیستم را پر میکرد و امکان انجام سایر امور آن مختل میشدند. همچنین اولین ارجاعی به یک جدول، سبب قرار گرفتن کل آن در حافظه میگشت. به علاوه ساختار این سیستم نیز همانند روش مبتنی بر دیسک، بر اساس همان روشهای قفل گذاری، ذخیره سازی اطلاعات و تهیه ایندکسهای متداول بود.
اما جداول بهینه سازی شدهی برای حافظه، از یک موتور کاملا جدید استفاده میکنند؛ با ساختار جدیدی برای ذخیره سازی اطلاعات و تهیه ایندکسها. دسترسی به اطلاعات آنها شامل قفل گذاریهای متداول نیست و در آن حداقل زمان دسترسی به اطلاعات درنظر گرفته شدهاست. همچنین در آنها data pages یا index pages و کش بافر نیز وجود ندارد.
نحوهی ذخیره سازی و مدیریت اطلاعات جداول بهینه سازی شده برای حافظه
جداول بهینه سازی شده برای حافظه، فرمت ردیفهای کاملا جدیدی را نیز به همراه دارند و جهت قرارگرفتن در حافظه ودسترسی سریع به آنها بهینه سازی شدهاند. برخلاف جداول مبتنی بر دیسک سخت که اطلاعات آنها در یک سری صفحات خاص به نامهای data or index pages ذخیره میشوند، اینگونه جداول، دارای ظروف مبتنی بر صفحه نیستند و از مفهوم چند نگارشی برای ذخیره سازی اطلاعات استفاده میکنند؛ به این معنا که ردیفها به ازای هر تغییری، دارای یک نگارش جدید خواهند بود و بلافاصله در همان نگارش اصلی به روز رسانی نمیشوند.
در اینجا هر ردیف دارای یک timestamp شروع و یک timestamp پایان است. timestamp شروع بیانگر تراکنشی است که ردیف را ثبت کرده و timestamp پایان برای مشخص سازی تراکنشی بکار میرود که ردیف را حذف کرده است. اگر timestamp پایان، دارای مقدار بینهایت باشد، به این معنا است که ردیف متناظر با آن هنوز حذف نشدهاست. به روز رسانی یک ردیف در اینجا، ترکیبی است از حذف یک ردیف موجود و ثبت ردیفی جدید. برای یک عملیات فقط خواندنی، تنها نگارشهایی که timestamp معتبری داشته باشند، قابل مشاهده خواهند بود و از مابقی صرفنظر میگردد.
در OLTP درون حافظهای که از روش چندنگارشی همزمانی استفاده میکند، برای یک ردیف مشخص، ممکن است چندین نگارش وجود داشته باشند؛ بسته به تعداد باری که یک رکورد به روز رسانی شدهاست. در اینجا یک سیستم garbage collection همیشه فعال، نگارشهایی را که توسط هیچ تراکنشی مورد استفاده قرار نمیگیرند، به صورت خودکار حذف میکند؛ تا مشکل کمبود حافظه رخ ندهد.
آیا میتوان به کارآیی جداول بهینه سازی شده برای حافظه با همان روش متداول مبتنی بر دیسک اما با بکارگیری حافظهی بیشتر و استفاده از یک SSD RAID رسید؟
خیر! حتی اگر کل بانک اطلاعاتی مبتنی بر دیسک را در حافظه قرار دهید به کارآیی روش جداول بهینه سازی شدهی برای حافظه نخواهید رسید. زیرا در آن هنوز مفاهیمی مانند data pages و index pages به همراه یک buffer pool پیچیده وجود دارند. در روشهای مبتنی بر دیسک، ردیفها از طریق page id و row offset آنها قابل دسترسی میشوند. اما در جداول بهینه سازی شدهی برای حافظه، ردیفهای جداول با یک B-tree خاص به نام Bw-Tree در دسترس هستند.
میزان حافظهی مورد نیاز برای جداول بهینه سازی شدهی برای حافظه
باید درنظر داشت که تمام جداول بهینه سازی شدهی برای حافظه، به صورت کامل در حافظه ذخیره خواهند شد. بنابراین بدیهی است که نیاز به مقدار کافی حافظه در اینجا ضروری است. توصیه صورت گرفته، داشتن حافظهای به میزان دو برابر اندازهی اطلاعات است. البته در اینجا چون با یک سیستم هیبرید سر و کار داریم، حافظهی کافی جهت کار buffer pool مختص به جداول مبتنی بر دیسک را نیز باید درنظر داشت.
همچنین اگر به اندازهی کافی حافظه در سیستم تعبیه نشود، شاهد شکست مداوم تراکنشها خواهید بود. به علاوه امکان بازیابی و restore جداول را نیز از دست خواهید داد.
البته لازم به ذکر است که اگر کل بانک اطلاعاتی شما چند ترابایت است، نیازی نیست به همین اندازه یا بیشتر حافظه تهیه کنید. فقط باید به اندازهی جداولی که قرار است جهت قرار گرفتن در حافظه بهینه سازی شوند، حافظه تهیه کنید که حداکثر آن 256 گیگابایت است.
چه برنامههایی بهتر است از امکانات OLTP درون حافظهای SQL Server 2014 استفاده کنند؟
- برنامههایی که در آنها تعداد زیادی تراکنش کوتاه مدت وجود دارد به همراه درجهی بالایی از تراکنشهای همزمان توسط تعداد زیادی کاربر.
- اطلاعاتی که توسط برنامه زیاد مورد استفاده قرار میگیرند را نیز میتوان در جداول بهینه سازی شده جهت حافظه قرار داد.
- زمانیکه نیاز به اعمال دارای write بسیار سریع و با تعداد زیاد است. چون در جداول بهینه سازی شدهی برای حافظه، صفحات دادهها و ایندکسها وجود ندارند، نسبت به حالت مبتنی بر دیسک، بسیار سریعتر هستند. در روشهای متداول، برای نوشتن اطلاعات در یک صفحه، مباحث همزمانی و قفلگذاری آنرا باید در نظر داشت. در صورتیکه در روش بهینه سازی شدهی برای حافظه، به صورت پیش فرض از حالتی همانند snapshot isolation و همزمانی مبتنی بر نگارشهای مختلف رکورد استفاده میشود.
- تنظیم و بهینه سازی جداولی با تعداد Read بالا. برای مثال، جداول پایه سیستم که اطلاعات تعاریف محصولات در آن قرار دارند. این نوع جداول عموما با تعداد Readهای بالا و تعداد Write کم شناخته میشوند. چون طراحی جداول مبتنی بر حافظه از hash tables و اشارهگرهایی برای دسترسی به رکوردهای موجود استفاده میکند، اعمال Read آن نیز بسیار سریعتر از حالت معمول هستند.
- مناسب جهت کارهای data warehouse و ETL Staging Table. در جداول مبتنی بر حافظه امکان عدم ذخیره سازی اطلاعات بر روی دیسک سخت نیز پیش بینی شدهاست. در این حالت فقط اطلاعات ساختار جدول، ذخیرهی نهایی میگردد و اگر سرور نیز ری استارت گردد، مجددا میتواند اطلاعات خود را از منابع اصلی data warehouse تامین کند.
محدودیتهای جداول بهینه سازی شدهی برای حافظه در SQL Server 2014
- تغیر اسکیما و ساختار جداول بهینه سازی شدهی برای حافظه مجاز نیست. به بیان دیگر دستور ALTER TABLE برای اینگونه جداول کاربردی ندارد. این مورد جهت ایندکسها نیز صادق است. همان زمانیکه جدول ایجاد میشود، باید ایندکس آن نیز تعریف گردد و پس از آن این امکان وجود ندارد.
تنها راه تغییر اسکیمای اینگونه جداول، Drop و سپس ایجاد مجدد آنها است.
البته باید درنظر داشت که SQL Server 2014، اولین نگارش این فناوری را ارائه دادهاست و در نگارشهای بعدی آن، بسیاری از این محدودیتها قرار است که برطرف شوند.
- جداول بهینه سازی شدهی برای حافظه حتما باید دارای یک ایندکس باشند. البته اگر یک primary key را برای آنها تعریف نمائید، کفایت میکند.
- از unique indexها پشتیبانی نمیکند، مگر اینکه از نوع primary key باشد.
- حداکثر 8 ایندکس را میتوان بر روی اینگونه جداول تعریف کرد.
- امکان تعریف ستون identity در آن وجود ندارد. اما میتوان از قابلیت sequence برای رسیدن به آن استفاده کرد.
- DML triggers را پشتیبانی نمیکند.
- کلیدهای خارجی و قیود را پشتیبانی نمیکند.
- حداکثر اندازهی یک ردیف آن 8060 بایت است. بنابراین از نوعهای دادهای max دار و XML پشتیبانی نمیکند.
این مورد در حین ایجاد جدول بررسی شده و اگر اندازهی ردیف محاسبهی شدهی آن توسط SQL Server 2014 بیش از 8060 بایت باشد، جدول را ایجاد نخواهد کرد.
اگر سرور را ری استارت کنیم، چه اتفاقی برای اطلاعات جداول بهینه سازی شدهی برای حافظه رخ میدهد؟
حالت DURABILTY انتخاب شدهی در حین ایجاد جدول بهینه سازی شدهی برای حافظه، تعیین کنندهای این مساله است. اگر SCHEMA_ONLY انتخاب شده باشد، کل اطلاعات شما با ری استارت سرور از دست خواهد رفت؛ البته اطلاعات ساختار جدول حفظ خواهد گردید. اگر حالت SCHEMA_AND_DATA انتخاب شود، اطلاعات شما پس از ریاستارت سرور نیز در دسترس خواهد بود. این اطلاعات به صورت خودکار از لاگ تراکنشها بازیابی شده و مجددا در حافظه قرار میگیرند.
حالت SCHEMA_ONLY برای مصارف برنامههای data warehouse بیشتر کاربرد دارد. جایی که اطلاعات قرار است از منابع دادهی مختلفی تامین شوند.
برای مطالعه بیشتر
SQL Server 2014: NoSQL Speeds with Relational Capabilities
SQL Server 2014 In-Memory OLTP Architecture and Data Storage
Overview of Applications, Indexes and Limitations for SQL Server 2014 In-Memory OLTP Tables
Microsoft SQL Server 2014: In-Memory OLTP Overview
SQL Server in Memory OLTP for Database Developers
Exploring In-memory OLTP Engine (Hekaton) in SQL Server 2014 CTP1
آیا جداول بهینه سازی شدهی برای حافظه، همان DBCC PINTABLE منسوخ شده هستند؟
در نگارشهای قدیمیتر اس کیوال سرور، دستوری وجود داشت به نام DBCC PINTABLE که سبب ثابت نگه داشتن صفحات جداول مبتنی بر دیسک یک دیتابیس، در حافظه میشد. به این ترتیب تمام خواندنهای مرتبط با آن جدول، از حافظه صورت میگرفت. مشکل این روش که سبب منسوخ شدن آن گردید، اثرات جانبی آن بود؛ مانند خوانده شدن صفحات جدیدتر (با توجه به اینکه ساختار پردازشی و موتور بانک اطلاعاتی تغییری نکرده بود) و نیاز به حافظهی بیشتر تا حدی که کل کش بافر سیستم را پر میکرد و امکان انجام سایر امور آن مختل میشدند. همچنین اولین ارجاعی به یک جدول، سبب قرار گرفتن کل آن در حافظه میگشت. به علاوه ساختار این سیستم نیز همانند روش مبتنی بر دیسک، بر اساس همان روشهای قفل گذاری، ذخیره سازی اطلاعات و تهیه ایندکسهای متداول بود.
اما جداول بهینه سازی شدهی برای حافظه، از یک موتور کاملا جدید استفاده میکنند؛ با ساختار جدیدی برای ذخیره سازی اطلاعات و تهیه ایندکسها. دسترسی به اطلاعات آنها شامل قفل گذاریهای متداول نیست و در آن حداقل زمان دسترسی به اطلاعات درنظر گرفته شدهاست. همچنین در آنها data pages یا index pages و کش بافر نیز وجود ندارد.
نحوهی ذخیره سازی و مدیریت اطلاعات جداول بهینه سازی شده برای حافظه
جداول بهینه سازی شده برای حافظه، فرمت ردیفهای کاملا جدیدی را نیز به همراه دارند و جهت قرارگرفتن در حافظه ودسترسی سریع به آنها بهینه سازی شدهاند. برخلاف جداول مبتنی بر دیسک سخت که اطلاعات آنها در یک سری صفحات خاص به نامهای data or index pages ذخیره میشوند، اینگونه جداول، دارای ظروف مبتنی بر صفحه نیستند و از مفهوم چند نگارشی برای ذخیره سازی اطلاعات استفاده میکنند؛ به این معنا که ردیفها به ازای هر تغییری، دارای یک نگارش جدید خواهند بود و بلافاصله در همان نگارش اصلی به روز رسانی نمیشوند.
در اینجا هر ردیف دارای یک timestamp شروع و یک timestamp پایان است. timestamp شروع بیانگر تراکنشی است که ردیف را ثبت کرده و timestamp پایان برای مشخص سازی تراکنشی بکار میرود که ردیف را حذف کرده است. اگر timestamp پایان، دارای مقدار بینهایت باشد، به این معنا است که ردیف متناظر با آن هنوز حذف نشدهاست. به روز رسانی یک ردیف در اینجا، ترکیبی است از حذف یک ردیف موجود و ثبت ردیفی جدید. برای یک عملیات فقط خواندنی، تنها نگارشهایی که timestamp معتبری داشته باشند، قابل مشاهده خواهند بود و از مابقی صرفنظر میگردد.
در OLTP درون حافظهای که از روش چندنگارشی همزمانی استفاده میکند، برای یک ردیف مشخص، ممکن است چندین نگارش وجود داشته باشند؛ بسته به تعداد باری که یک رکورد به روز رسانی شدهاست. در اینجا یک سیستم garbage collection همیشه فعال، نگارشهایی را که توسط هیچ تراکنشی مورد استفاده قرار نمیگیرند، به صورت خودکار حذف میکند؛ تا مشکل کمبود حافظه رخ ندهد.
آیا میتوان به کارآیی جداول بهینه سازی شده برای حافظه با همان روش متداول مبتنی بر دیسک اما با بکارگیری حافظهی بیشتر و استفاده از یک SSD RAID رسید؟
خیر! حتی اگر کل بانک اطلاعاتی مبتنی بر دیسک را در حافظه قرار دهید به کارآیی روش جداول بهینه سازی شدهی برای حافظه نخواهید رسید. زیرا در آن هنوز مفاهیمی مانند data pages و index pages به همراه یک buffer pool پیچیده وجود دارند. در روشهای مبتنی بر دیسک، ردیفها از طریق page id و row offset آنها قابل دسترسی میشوند. اما در جداول بهینه سازی شدهی برای حافظه، ردیفهای جداول با یک B-tree خاص به نام Bw-Tree در دسترس هستند.
میزان حافظهی مورد نیاز برای جداول بهینه سازی شدهی برای حافظه
باید درنظر داشت که تمام جداول بهینه سازی شدهی برای حافظه، به صورت کامل در حافظه ذخیره خواهند شد. بنابراین بدیهی است که نیاز به مقدار کافی حافظه در اینجا ضروری است. توصیه صورت گرفته، داشتن حافظهای به میزان دو برابر اندازهی اطلاعات است. البته در اینجا چون با یک سیستم هیبرید سر و کار داریم، حافظهی کافی جهت کار buffer pool مختص به جداول مبتنی بر دیسک را نیز باید درنظر داشت.
همچنین اگر به اندازهی کافی حافظه در سیستم تعبیه نشود، شاهد شکست مداوم تراکنشها خواهید بود. به علاوه امکان بازیابی و restore جداول را نیز از دست خواهید داد.
البته لازم به ذکر است که اگر کل بانک اطلاعاتی شما چند ترابایت است، نیازی نیست به همین اندازه یا بیشتر حافظه تهیه کنید. فقط باید به اندازهی جداولی که قرار است جهت قرار گرفتن در حافظه بهینه سازی شوند، حافظه تهیه کنید که حداکثر آن 256 گیگابایت است.
چه برنامههایی بهتر است از امکانات OLTP درون حافظهای SQL Server 2014 استفاده کنند؟
- برنامههایی که در آنها تعداد زیادی تراکنش کوتاه مدت وجود دارد به همراه درجهی بالایی از تراکنشهای همزمان توسط تعداد زیادی کاربر.
- اطلاعاتی که توسط برنامه زیاد مورد استفاده قرار میگیرند را نیز میتوان در جداول بهینه سازی شده جهت حافظه قرار داد.
- زمانیکه نیاز به اعمال دارای write بسیار سریع و با تعداد زیاد است. چون در جداول بهینه سازی شدهی برای حافظه، صفحات دادهها و ایندکسها وجود ندارند، نسبت به حالت مبتنی بر دیسک، بسیار سریعتر هستند. در روشهای متداول، برای نوشتن اطلاعات در یک صفحه، مباحث همزمانی و قفلگذاری آنرا باید در نظر داشت. در صورتیکه در روش بهینه سازی شدهی برای حافظه، به صورت پیش فرض از حالتی همانند snapshot isolation و همزمانی مبتنی بر نگارشهای مختلف رکورد استفاده میشود.
- تنظیم و بهینه سازی جداولی با تعداد Read بالا. برای مثال، جداول پایه سیستم که اطلاعات تعاریف محصولات در آن قرار دارند. این نوع جداول عموما با تعداد Readهای بالا و تعداد Write کم شناخته میشوند. چون طراحی جداول مبتنی بر حافظه از hash tables و اشارهگرهایی برای دسترسی به رکوردهای موجود استفاده میکند، اعمال Read آن نیز بسیار سریعتر از حالت معمول هستند.
- مناسب جهت کارهای data warehouse و ETL Staging Table. در جداول مبتنی بر حافظه امکان عدم ذخیره سازی اطلاعات بر روی دیسک سخت نیز پیش بینی شدهاست. در این حالت فقط اطلاعات ساختار جدول، ذخیرهی نهایی میگردد و اگر سرور نیز ری استارت گردد، مجددا میتواند اطلاعات خود را از منابع اصلی data warehouse تامین کند.
محدودیتهای جداول بهینه سازی شدهی برای حافظه در SQL Server 2014
- تغیر اسکیما و ساختار جداول بهینه سازی شدهی برای حافظه مجاز نیست. به بیان دیگر دستور ALTER TABLE برای اینگونه جداول کاربردی ندارد. این مورد جهت ایندکسها نیز صادق است. همان زمانیکه جدول ایجاد میشود، باید ایندکس آن نیز تعریف گردد و پس از آن این امکان وجود ندارد.
تنها راه تغییر اسکیمای اینگونه جداول، Drop و سپس ایجاد مجدد آنها است.
البته باید درنظر داشت که SQL Server 2014، اولین نگارش این فناوری را ارائه دادهاست و در نگارشهای بعدی آن، بسیاری از این محدودیتها قرار است که برطرف شوند.
- جداول بهینه سازی شدهی برای حافظه حتما باید دارای یک ایندکس باشند. البته اگر یک primary key را برای آنها تعریف نمائید، کفایت میکند.
- از unique indexها پشتیبانی نمیکند، مگر اینکه از نوع primary key باشد.
- حداکثر 8 ایندکس را میتوان بر روی اینگونه جداول تعریف کرد.
- امکان تعریف ستون identity در آن وجود ندارد. اما میتوان از قابلیت sequence برای رسیدن به آن استفاده کرد.
- DML triggers را پشتیبانی نمیکند.
- کلیدهای خارجی و قیود را پشتیبانی نمیکند.
- حداکثر اندازهی یک ردیف آن 8060 بایت است. بنابراین از نوعهای دادهای max دار و XML پشتیبانی نمیکند.
این مورد در حین ایجاد جدول بررسی شده و اگر اندازهی ردیف محاسبهی شدهی آن توسط SQL Server 2014 بیش از 8060 بایت باشد، جدول را ایجاد نخواهد کرد.
اگر سرور را ری استارت کنیم، چه اتفاقی برای اطلاعات جداول بهینه سازی شدهی برای حافظه رخ میدهد؟
حالت DURABILTY انتخاب شدهی در حین ایجاد جدول بهینه سازی شدهی برای حافظه، تعیین کنندهای این مساله است. اگر SCHEMA_ONLY انتخاب شده باشد، کل اطلاعات شما با ری استارت سرور از دست خواهد رفت؛ البته اطلاعات ساختار جدول حفظ خواهد گردید. اگر حالت SCHEMA_AND_DATA انتخاب شود، اطلاعات شما پس از ریاستارت سرور نیز در دسترس خواهد بود. این اطلاعات به صورت خودکار از لاگ تراکنشها بازیابی شده و مجددا در حافظه قرار میگیرند.
حالت SCHEMA_ONLY برای مصارف برنامههای data warehouse بیشتر کاربرد دارد. جایی که اطلاعات قرار است از منابع دادهی مختلفی تامین شوند.
برای مطالعه بیشتر
SQL Server 2014: NoSQL Speeds with Relational Capabilities
SQL Server 2014 In-Memory OLTP Architecture and Data Storage
Overview of Applications, Indexes and Limitations for SQL Server 2014 In-Memory OLTP Tables
Microsoft SQL Server 2014: In-Memory OLTP Overview
SQL Server in Memory OLTP for Database Developers
Exploring In-memory OLTP Engine (Hekaton) in SQL Server 2014 CTP1
زمانیکه کنترلر یک API را توسط قالبهای پیشفرض آن ایجاد میکنیم، یک سری اکشن متد پیشفرض Get/Post/Put/Delete در آن قابل مشاهده هستند. میتوان این نوع خروجی این نوع متدها را به نحو سادهتری نیز مستند کرد:
در اینجا با ذکر ویژگی ApiConventionMethod، از نوع DefaultApiConventions، برای تولید مستندات خروجی متدی از نوع Get استفاده شدهاست. اگر به تعریف کلاس توکار DefaultApiConventions مراجعه کنیم، در مورد متد Get، یک چنین ویژگیهایی را به صورت خودکار اعمال میکند:
البته باید دقت داشت که DefaultApiConventions برای قالب پیشفرض کنترلرهای API طراحی شدهاست و همچنین اگر فیلترهای سراسری را مانند قسمت قبل فعال کرده باشیم، اعمال نخواهند شد و از همان فیلترهای سراسری استفاده میشود.
امکان اعمال DefaultApiConventions به تمام متدهای یک کنترلر API نیز به صورت زیر با استفاده از ویژگی ApiConventionType اعمال شدهی به کلاس کنترلر میسر است:
یا حتی میتوان بجای اعمال دستی ApiConventionType به تمام کنترلرهای API، آنرا به کل پروژه و اسمبلی جاری اعمال کرد:
اینکار را در کلاس Startup و پیشاز تعریف فضای نام آن به نحو فوق میتوان انجام داد. به این ترتیب DefaultApiConventions، به تمام کنترلرهای موجود در این اسمبلی اعمال میشوند. بنابراین با اعمال سراسری آن میتوان ApiConventionType اعمالی بر کلاس ConventionTestsController را حذف کرد.
ایجاد ApiConventions سفارشی
همانطور که عنوان شد، اگر متدهای API شما دقیقا همان نامهای پیشفرض Get/Post/Put/Delete را داشته باشند، توسط DefaultApiConventions مدیریت خواهند شد. در سایر حالات، مثلا اگر بجای نام Post، از نام Insert استفاده شد، باید ApiConventions سفارشی را ایجاد کرد:
همانطور که ملاحظه میکنید، نحوهی تشکیل این کلاس، با public static class DefaultApiConventions توکاری که پیشتر در مورد آن بحث شد، یکی است. نوع کلاس آن static است و با نام متدی که قصد اعمال به آنرا داریم، سازگاری دارد. سپس تعدادی ویژگی خاص، به این متد اعمال شدهاند.
پس از آن برای اعمال این ApiConventions جدید میتوان به صورت زیر عمل کرد:
در اینجا حالت نوع ApiConventionMethod به کلاس جدید CustomConventions اشاره میکند و نام متد آن نیز Insert درنظر گرفته شدهاست. در این حالت حتی اگر نام این اکشن متد را به InsertTest تغییر دهیم، باز هم کار میکند؛ چون بر اساس پارامتر دوم ویژگی ApiConventionMethod عمل کرده و متد متناظر را پیدا میکند. اما اگر آنرا توسط ApiConventionType به خود کنترلر اعمال کنیم، فقط بر اساس ApiConventionNameMatch است که باز هم به متد InsertTest اعمال خواهد شد؛ چون در اینجا Prefix همان معنای StartsWith را میدهد. به علاوه در اینجا object model به عنوان پارامتر تعریف شدهاست و در سمت اکشن متد کنترلر، string value را داریم. در این مورد نیز ویژگیهای اعمال شده به معنای صرفنظر از نوع و نام پارامتر تعریف شدهی در ApiConvention ما هستند (Any در اینجا به معنای صرفنظر از تطابق دقیق است).
سؤال: آیا استفادهی از این ApiConventions ایدهی خوبی است؟
همانطور که در ابتدای بحث نیز عنوان شد، اگر فیلترهای سراسری را مانند قسمت قبل فعال کرده باشیم، از اعمال ApiConventions صرفنظر میشود. همچنین حالت پیشفرض آنها برای حالتهای متداول و ساده مفید هستند و برای سایر حالات باید کدهای زیادی را نوشت. به همین جهت خود مایکروسافت هم استفادهی از ApiConventions را صرفا برای کنترلرهای API ای که دقیقا مطابق با قالب پیشفرض آنها تهیه شدهاند، توصیه میکند. بنابراین استفادهی از Attributes که در قسمت قبل آنها را بررسی کردیم، مقدم هستند بر استفادهی از ApiConventions و تعدادی از بهترین تجربههای کاربری در این زمینه به شرح زیر میباشند:
- از API Analyzers که در قسمت قبل معرفی شد، برای یافتن کمبودهای نقایص مستندات استفاده کنید.
- از ویژگی ProducesDefaultResponseType استفاده کنید؛ اما تا جائیکه میتوانید، جزئیات ممکن را به صورت صریحی مستند نمائید.
- Attributes را به صورت سراسری معرفی کنید.
بهبود مستندات Content negotiation
فرض کنید میخواهید لیست کتابهای یک نویسنده را دریافت کنید. در اینجا خروجی ارائه شده، با فرمت JSON تولید میشود؛ اما ممکن است XML ای نیز باشد و یا حالتهای دیگر، بسته به تنظیمات برنامه. کار Content negotiation این است که مصرف کنندهی یک API، دقیقا مشخص کند، چه نوع فرمت خروجی را مدنظر دارد. هدری که برای این منظور استفاده میشود، accept header نامدارد و ذکر آن اجباری است؛ هر چند تعدادی از APIها بدون وجود آن نیز سعی میکنند حالت پیشفرضی را ارائه دهند.
Swagger-UI به نحوی که در تصویر فوق ملاحظه میکنید، امکان انتخاب Accept header را مسیر میکند. در این حالت اگر application/json را انتخاب کنیم، خروجی JSON ای را دریافت میکنیم. اما اگر text/plain را انتخاب کنیم، چون توسط API ما پشتیبانی نمیشود، خروجی از نوع 406 یا همان Status406NotAcceptable را دریافت خواهیم کرد. بنابراین وجود گزینهی text/plain در اینجا غیرضروری و گمراه کنندهاست و نیاز است این مشکل را برطرف کرد:
در اینجا ویژگی جدیدی را به نام Produces مشاهده میکنید که به کل اکشن متدهای یک کنترلر API اضافه شدهاست. کار آن محدود کردن فرمت خروجی اکشن متدها با ذکر media-types مورد نظر است.
پس از اعمال این ویژگی، تاثیر آنرا بر روی Swagger-UI در شکل زیر مشاهده میکنید که اینبار تنها به یک مورد مشخص، محدود شدهاست:
در اینجا اگر قصد داشته باشیم خروجی XML را نیز پشتیبانی کنیم، میتوان به صورت زیر عمل کرد:
- ابتدا در کلاس Startup، نیاز است OutputFormatter متناظری را به سیستم معرفی نمود:
- سپس ویژگی Produces را نیز تکمیل میکنیم تا این نوع خروجی را پشتیبانی کند:
با این خروجی:
نکتهی مهم: اگر Produces را اصلاح نکنیم، تعریف XmlSerializerOutputFormatter و ارسال یک درخواست با هدر Accept از نوع application/xml، هیچ تاثیری نداشته و باز هم JSON بازگشت داده میشود.
در این حالت اگر Controls Accept header را در UI از نوع xml انتخاب کنیم و سپس با کلیک بر روی دکمهی try it out و ذکر id یک نویسنده، لیست کتابهای او را درخواست کنیم، خروجی نهایی XML ای آن قابل مشاهده خواهد بود:
البته تا اینجا فقط Swagger-UI را جهت محدود کردن به دو نوع خروجی با فرمت JSON و XML، اصلاح کردهایم؛ اما این مورد به معنای محدود کردن سایر ابزارهای آزمایش یک API مانند postman نیست. در این نوع موارد، تمام مدیاتایپهای ارسالی پشتیبانی نشده، سبب تولید خروجی با فرمت JSON میشوند. برای محدود کردن آنها به خروجی از نوع 406 میتوان تنظیم ReturnHttpNotAcceptable را به true انجام داد تا اگر برای مثال درخواست application/xyz ارسال شد، صرفا یک استثناء بازگشت داده شود و نه خروجی JSON:
بهبود مستندات نوع بدنهی درخواست
تا اینجا فرمت accept header را دقیقا مشخص و مستند کردیم؛ اما اگر به تصویر فوق دقت کنید، در حین ارسال اطلاعاتی از نوع POST به سرور، چندین نوع Request body را میتوان انتخاب کرد که الزاما تمام آنها توسط API ما پشتیبانی نمیشود. برای رفع این مشکل میتوان از ویژگی Consumes استفاده کرد که نوع مدیتاتایپهای مجاز ورودی را مشخص میکند:
بعد از این تغییر، نوع بدنهی درخواست نیز محدود میشود:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: OpenAPISwaggerDoc-05.zip
namespace OpenAPISwaggerDoc.Web.Controllers { [Route("api/[controller]")] [ApiController] public class ConventionTestsController : ControllerBase { // GET: api/ConventionTests/5 [HttpGet("{id}", Name = "Get")] [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Get))] public string Get(int id) { return "value"; }
using Microsoft.AspNetCore.Mvc.ApiExplorer; namespace Microsoft.AspNetCore.Mvc { public static class DefaultApiConventions { [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] [ProducesDefaultResponseType] [ProducesResponseType(200)] [ProducesResponseType(404)] public static void Get( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] object id); } }
امکان اعمال DefaultApiConventions به تمام متدهای یک کنترلر API نیز به صورت زیر با استفاده از ویژگی ApiConventionType اعمال شدهی به کلاس کنترلر میسر است:
namespace OpenAPISwaggerDoc.Web.Controllers { [Route("api/[controller]")] [ApiController] [ApiConventionType(typeof(DefaultApiConventions))] public class ConventionTestsController : ControllerBase
[assembly: ApiConventionType(typeof(DefaultApiConventions))] namespace OpenAPISwaggerDoc.Web { public class Startup
ایجاد ApiConventions سفارشی
همانطور که عنوان شد، اگر متدهای API شما دقیقا همان نامهای پیشفرض Get/Post/Put/Delete را داشته باشند، توسط DefaultApiConventions مدیریت خواهند شد. در سایر حالات، مثلا اگر بجای نام Post، از نام Insert استفاده شد، باید ApiConventions سفارشی را ایجاد کرد:
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApiExplorer; namespace OpenAPISwaggerDoc.Web.AppConventions { public static class CustomConventions { [ProducesDefaultResponseType] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] public static void Insert( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] object model) { } } }
پس از آن برای اعمال این ApiConventions جدید میتوان به صورت زیر عمل کرد:
namespace OpenAPISwaggerDoc.Web.Controllers { [Route("api/[controller]")] [ApiController] public class ConventionTestsController : ControllerBase { [HttpPost] [ApiConventionMethod(typeof(CustomConventions), nameof(CustomConventions.Insert))] public void Insert([FromBody] string value) { }
سؤال: آیا استفادهی از این ApiConventions ایدهی خوبی است؟
همانطور که در ابتدای بحث نیز عنوان شد، اگر فیلترهای سراسری را مانند قسمت قبل فعال کرده باشیم، از اعمال ApiConventions صرفنظر میشود. همچنین حالت پیشفرض آنها برای حالتهای متداول و ساده مفید هستند و برای سایر حالات باید کدهای زیادی را نوشت. به همین جهت خود مایکروسافت هم استفادهی از ApiConventions را صرفا برای کنترلرهای API ای که دقیقا مطابق با قالب پیشفرض آنها تهیه شدهاند، توصیه میکند. بنابراین استفادهی از Attributes که در قسمت قبل آنها را بررسی کردیم، مقدم هستند بر استفادهی از ApiConventions و تعدادی از بهترین تجربههای کاربری در این زمینه به شرح زیر میباشند:
- از API Analyzers که در قسمت قبل معرفی شد، برای یافتن کمبودهای نقایص مستندات استفاده کنید.
- از ویژگی ProducesDefaultResponseType استفاده کنید؛ اما تا جائیکه میتوانید، جزئیات ممکن را به صورت صریحی مستند نمائید.
- Attributes را به صورت سراسری معرفی کنید.
بهبود مستندات Content negotiation
فرض کنید میخواهید لیست کتابهای یک نویسنده را دریافت کنید. در اینجا خروجی ارائه شده، با فرمت JSON تولید میشود؛ اما ممکن است XML ای نیز باشد و یا حالتهای دیگر، بسته به تنظیمات برنامه. کار Content negotiation این است که مصرف کنندهی یک API، دقیقا مشخص کند، چه نوع فرمت خروجی را مدنظر دارد. هدری که برای این منظور استفاده میشود، accept header نامدارد و ذکر آن اجباری است؛ هر چند تعدادی از APIها بدون وجود آن نیز سعی میکنند حالت پیشفرضی را ارائه دهند.
Swagger-UI به نحوی که در تصویر فوق ملاحظه میکنید، امکان انتخاب Accept header را مسیر میکند. در این حالت اگر application/json را انتخاب کنیم، خروجی JSON ای را دریافت میکنیم. اما اگر text/plain را انتخاب کنیم، چون توسط API ما پشتیبانی نمیشود، خروجی از نوع 406 یا همان Status406NotAcceptable را دریافت خواهیم کرد. بنابراین وجود گزینهی text/plain در اینجا غیرضروری و گمراه کنندهاست و نیاز است این مشکل را برطرف کرد:
namespace OpenAPISwaggerDoc.Web.Controllers { [Produces("application/json")] [Route("api/authors/{authorId}/books")] [ApiController] public class BooksController : ControllerBase
پس از اعمال این ویژگی، تاثیر آنرا بر روی Swagger-UI در شکل زیر مشاهده میکنید که اینبار تنها به یک مورد مشخص، محدود شدهاست:
در اینجا اگر قصد داشته باشیم خروجی XML را نیز پشتیبانی کنیم، میتوان به صورت زیر عمل کرد:
- ابتدا در کلاس Startup، نیاز است OutputFormatter متناظری را به سیستم معرفی نمود:
namespace OpenAPISwaggerDoc.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(setupAction => { setupAction.OutputFormatters.Add(new XmlSerializerOutputFormatter());
namespace OpenAPISwaggerDoc.Web.Controllers { [Produces("application/json", "application/xml")] [Route("api/authors/{authorId}/books")] [ApiController] public class BooksController : ControllerBase
در این حالت اگر Controls Accept header را در UI از نوع xml انتخاب کنیم و سپس با کلیک بر روی دکمهی try it out و ذکر id یک نویسنده، لیست کتابهای او را درخواست کنیم، خروجی نهایی XML ای آن قابل مشاهده خواهد بود:
البته تا اینجا فقط Swagger-UI را جهت محدود کردن به دو نوع خروجی با فرمت JSON و XML، اصلاح کردهایم؛ اما این مورد به معنای محدود کردن سایر ابزارهای آزمایش یک API مانند postman نیست. در این نوع موارد، تمام مدیاتایپهای ارسالی پشتیبانی نشده، سبب تولید خروجی با فرمت JSON میشوند. برای محدود کردن آنها به خروجی از نوع 406 میتوان تنظیم ReturnHttpNotAcceptable را به true انجام داد تا اگر برای مثال درخواست application/xyz ارسال شد، صرفا یک استثناء بازگشت داده شود و نه خروجی JSON:
namespace OpenAPISwaggerDoc.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(setupAction => { setupAction.ReturnHttpNotAcceptable = true; // Status406NotAcceptable
بهبود مستندات نوع بدنهی درخواست
تا اینجا فرمت accept header را دقیقا مشخص و مستند کردیم؛ اما اگر به تصویر فوق دقت کنید، در حین ارسال اطلاعاتی از نوع POST به سرور، چندین نوع Request body را میتوان انتخاب کرد که الزاما تمام آنها توسط API ما پشتیبانی نمیشود. برای رفع این مشکل میتوان از ویژگی Consumes استفاده کرد که نوع مدیتاتایپهای مجاز ورودی را مشخص میکند:
namespace OpenAPISwaggerDoc.Web.Controllers { [Produces("application/json", "application/xml")] [Route("api/authors/{authorId}/books")] [ApiController] public class BooksController : ControllerBase { /// <summary> /// Create a book for a specific author /// </summary> /// <param name="authorId">The id of the book author</param> /// <param name="bookForCreation">The book to create</param> /// <returns>An ActionResult of type Book</returns> [HttpPost()] [Consumes("application/json")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task<ActionResult<Book>> CreateBook( Guid authorId, [FromBody] BookForCreation bookForCreation) {
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: OpenAPISwaggerDoc-05.zip
سلام ممنمون از مطلب خوبتون. شما در مورد انجام این کار در دات نت این رو نوشتید (در دنیای دات نت با استفاده از امکانات Reflection قابل انجام است و یا حتی بازنویسی اسمبلیها و افزودن کدهای IL مورد نیاز به آنها که به آن IL Weaving هم گفته میشود. ). من میخوام اسمبلی اصلی شیرپوینت رو در حین اجرا تغییر بدم ولی Reflection سربار زیادی داره و نمیخوام اصل اسمبلی رو تغییر بدم. آیا کتابخانه یا فریم ورکی رو سراغ داریدکه بشه کدهای دات نت رو در حین اجرا تغییر بده؟
مطالب دورهها
الگوی معکوس سازی کنترل چیست؟
معکوس سازی کنترل (Inversion of Control) الگویی است که نحوه پیاده سازی اصل معکوس سازی وابستگیها (Dependency inversion principle) را بیان میکند. با معکوس سازی کنترل، کنترل چیزی را با تغییر کنترل کننده، معکوس میکنیم. برای نمونه کلاسی را داریم که ایجاد اشیاء را کنترل میکند؛ با معکوس سازی آن به کلاسی یا قسمتی دیگر از سیستم، این مسئولیت را واگذار خواهیم کرد.
IoC یک الگوی سطح بالا است و به روشهای مختلفی به مسایل متفاوتی جهت معکوس سازی کنترل، قابل اعمال میباشد؛ مانند:
- کنترل اینترفیسهای بین دو سیستم
- کنترل جریان کاری برنامه
- کنترل بر روی ایجاد وابستگیها (جایی که تزریق وابستگیها و DI ظاهر میشوند)
سؤال: بین IoC و DIP چه تفاوتی وجود دارد؟
در DIP (قسمت قبل) به این نتیجه رسیدیم که یک ماژول سطح بالاتر نباید به جزئیات پیاده سازیهای ماژولی سطح پایینتر وابسته باشد. هر دوی اینها باید بر اساس Abstraction با یکدیگر ارتباط برقرار کنند. IoC روشی است که این Abstraction را فراهم میکند. در DIP فقط نگران این هستیم که ماژولهای موجود در لایههای مختلف برنامه به یکدیگر وابسته نباشند اما بیان نکردیم که چگونه.
معکوس سازی اینترفیسها
هدف از معکوس سازی اینترفیسها، استفاده صحیح و معنا دار از اینترفیسها میباشد. به این معنا که صرفا تعریف اینترفیسها به این معنا نیست که طراحی صحیحی در برنامه بکار گرفته شده است و در حالت کلی هیچ معنای خاصی ندارد و ارزشی را به برنامه و سیستم شما اضافه نخواهد کرد.
برای مثال یک مسابقه بوکس را درنظر بگیرید. در اینجا Ali یک بوکسور است. مطابق عادت معمول، یک اینترفیس را مخصوص این کلاس ایجاد کرده، به نام IAli و مسابقه بوکس از آن استفاده خواهد کرد. در اینجا تعریف یک اینترفیس برای Ali، هیچ ارزش افزودهای را به همراه ندارد و متاسفانه عادتی است که در بین بسیاری از برنامه نویسها متداول شده است؛ بدون اینکه علت واقعی آنرا بدانند و تسلطی به الگوهای طراحی برنامه نویسی شیءگرا داشته باشند. صرف اینکه به آنها گفته شده است تعریف اینترفیس خوب است، سعی میکنند برای همه چیز اینترفیس تعریف کنند!
تعریف یک اینترفیس تنها زمانی ارزش خواهد داشت که چندین پیاده سازی از آن ارائه شود. در مثال ما پیاده سازیهای مختلفی از اینترفیس IAli بیمفهوم است. همچنین در دنیای واقعی، در یک مسابقه بوکس، چندین و چند شرکت کننده وجود خواهند داشت. آیا باید به ازای هر کدام یک اینترفیس جداگانه تعریف کرد؟ ضمنا ممکن است اینترفیس IAli متدی داشته باشد به نام ضربه، اینترفیس IVahid متد دیگری داشته باشد به نام دفاع.
کاری که در اینجا جهت طراحی صحیح باید صورت گیرد، معکوس سازی اینترفیسها است. به این ترتیب که مسابقه بوکس است که باید اینترفیس مورد نیاز خود را تعریف کند و آن هم تنها یک اینترفیس است به نام IBoxer. اکنون Ali، Vahid و سایرین باید این اینترفیس را جهت شرکت در مسابقه بوکس پیاده سازی کنند. بنابراین دیگر صرف وجود یک کلاس، اینترفیس مجزایی برای آن تعریف نشده و بر اساس معکوس سازی کنترل است که تعریف اینترفیس IBoxer معنا پیدا کرده است. اکنون IBoxer دارای چندین و چند پیاده سازی خواهد بود. به این ترتیب، تعریف اینترفیس، ارزشی را به سیستم افزوده است.
به این نوع معکوس سازی اینترفیسها، الگوی provider model نیز گفته میشود. برای مثال کلاسی که از چندین سرویس استفاده میکند، بهتر است یک IService را ایجاد کرده و تامین کنندههایی، این IService را پیاده سازی کنند. نمونهای از آن در دنیای دات نت، Membership Provider موجود در ASP.NET است که پیاده سازیهای بسیاری از آن تاکنون تهیه و ارائه شدهاند.
معکوس سازی جریان کاری برنامه
جریان کاری معمول یک برنامه یا Noraml flow، عموما رویهای یا Procedural است؛ به این معنا که از یک مرحله به مرحلهای بعد هدایت خواهد شد. برای مثال یک برنامه خط فرمان را درنظر بگیرید که ابتدا میپرسد نام شما چیست؟ در مرحله بعد مثلا رنگ مورد علاقه شما را خواهد پرسید.
برای معکوس سازی این جریان کاری، از یک رابط کاربری گرافیکی یا GUI استفاده میشود. مثلا یک فرم را درنظر بگیرید که در آن دو جعبه متنی، کار دریافت نام و رنگ را به عهده دارند؛ به همراه یک دکمه ثبت اطلاعات. به این ترتیب بجای اینکه برنامه، مرحله به مرحله کاربر را جهت ثبت اطلاعات هدایت کند، کنترل به کاربر منتقل و معکوس شده است.
معکوس سازی تولید اشیاء
معکوس سازی تولید اشیاء، اصل بحث دوره و سری جاری را تشکیل میدهد و در ادامه مباحث، بیشتر و عمیقتر بررسی خواهد گردید.
روش متداول تعریف و استفاده از اشیاء دیگر درون یک کلاس، وهله سازی آنها توسط کلمه کلیدی new است. به این ترتیب از یک وابستگی به صورت مستقیم درون کدهای کلاس استفاده خواهد شد. بنابراین در این حالت کلاسهای سطح بالاتر به ماژولهای سطح پایین، به صورت مستقیم وابسته میگردند.
برای اینکه این کنترل را معکوس کنیم، نیاز است ایجاد و وهله سازی این اشیاء وابستگی را در خارج از کلاس جاری انجام دهیم. شاید در اینجا بپرسید که چرا؟
اگر با الگوی طراحی شیءگرای Factory آشنا باشید، همان ایده در اینجا مدنظر است:
در این مثال بر اساس تنظیمات کاربر، قرار است شکل دکمههای نمایش داده شده در صفحه تغییر کنند.
حال در این برنامه اگر قرار باشد کار و کنترل محل وهله سازی این دکمهها معکوس نشود، در هر قسمتی از برنامه نیاز است این سوئیچ تکرار گردد (برای مثال در چند ده فرم مختلف برنامه). بنابراین بهتر است محل ایجاد این دکمهها به کلاس دیگری منتقل شود مانند ButtonFactory و سپس از این کلاس در مکانهای مختلف برنامه استفاده گردد:
در این حالت علاوه بر کاهش کدهای تکراری، اگر حالت دکمه جدیدی نیز طراحی گردید، نیاز نخواهد بود تا بسیاری از نقاط برنامه بازنویسی شوند.
بنابراین در مثال فوق، کنترل ایجاد دکمهها به یک کلاس پایه قرار گرفته در خارج از کلاس جاری، معکوس شده است.
انواع معکوس سازی تولید اشیاء
بسیاری شاید تصور کنند که تنها راه معکوس سازی تولید اشیاء، تزریق وابستگیها است؛ اما روشهای چندی برای انجام اینکار وجود دارد:
الف) استفاده از الگوی طراحی Factory (که نمونهای از آنرا در قسمت قبل مشاهده کردید)
ب) استفاده از الگوی Service Locator
در این الگو بر اساس یک سری تنظیمات اولیه، نوع خاصی از یک اینترفیس درخواست شده و نهایتا وهلهای که آنرا پیاده سازی میکند، به برنامه بازگشت داده میشود.
ج) تزریق وابستگیها
در اینجا نوع وابستگی مورد نیاز کلاس Form1 در سازنده آن تعریف شده و کار تهیه وهلهای از آن وابستگی در خارج از کلاس صورت میگیرد.
به صورت خلاصه هر زمانیکه تولید و وهله سازی وابستگیهای یک کلاس را به خارج از آن منتقل کردید، کار معکوس سازی تولید وابستگیها انجام شده است.
IoC یک الگوی سطح بالا است و به روشهای مختلفی به مسایل متفاوتی جهت معکوس سازی کنترل، قابل اعمال میباشد؛ مانند:
- کنترل اینترفیسهای بین دو سیستم
- کنترل جریان کاری برنامه
- کنترل بر روی ایجاد وابستگیها (جایی که تزریق وابستگیها و DI ظاهر میشوند)
سؤال: بین IoC و DIP چه تفاوتی وجود دارد؟
در DIP (قسمت قبل) به این نتیجه رسیدیم که یک ماژول سطح بالاتر نباید به جزئیات پیاده سازیهای ماژولی سطح پایینتر وابسته باشد. هر دوی اینها باید بر اساس Abstraction با یکدیگر ارتباط برقرار کنند. IoC روشی است که این Abstraction را فراهم میکند. در DIP فقط نگران این هستیم که ماژولهای موجود در لایههای مختلف برنامه به یکدیگر وابسته نباشند اما بیان نکردیم که چگونه.
معکوس سازی اینترفیسها
هدف از معکوس سازی اینترفیسها، استفاده صحیح و معنا دار از اینترفیسها میباشد. به این معنا که صرفا تعریف اینترفیسها به این معنا نیست که طراحی صحیحی در برنامه بکار گرفته شده است و در حالت کلی هیچ معنای خاصی ندارد و ارزشی را به برنامه و سیستم شما اضافه نخواهد کرد.
برای مثال یک مسابقه بوکس را درنظر بگیرید. در اینجا Ali یک بوکسور است. مطابق عادت معمول، یک اینترفیس را مخصوص این کلاس ایجاد کرده، به نام IAli و مسابقه بوکس از آن استفاده خواهد کرد. در اینجا تعریف یک اینترفیس برای Ali، هیچ ارزش افزودهای را به همراه ندارد و متاسفانه عادتی است که در بین بسیاری از برنامه نویسها متداول شده است؛ بدون اینکه علت واقعی آنرا بدانند و تسلطی به الگوهای طراحی برنامه نویسی شیءگرا داشته باشند. صرف اینکه به آنها گفته شده است تعریف اینترفیس خوب است، سعی میکنند برای همه چیز اینترفیس تعریف کنند!
تعریف یک اینترفیس تنها زمانی ارزش خواهد داشت که چندین پیاده سازی از آن ارائه شود. در مثال ما پیاده سازیهای مختلفی از اینترفیس IAli بیمفهوم است. همچنین در دنیای واقعی، در یک مسابقه بوکس، چندین و چند شرکت کننده وجود خواهند داشت. آیا باید به ازای هر کدام یک اینترفیس جداگانه تعریف کرد؟ ضمنا ممکن است اینترفیس IAli متدی داشته باشد به نام ضربه، اینترفیس IVahid متد دیگری داشته باشد به نام دفاع.
کاری که در اینجا جهت طراحی صحیح باید صورت گیرد، معکوس سازی اینترفیسها است. به این ترتیب که مسابقه بوکس است که باید اینترفیس مورد نیاز خود را تعریف کند و آن هم تنها یک اینترفیس است به نام IBoxer. اکنون Ali، Vahid و سایرین باید این اینترفیس را جهت شرکت در مسابقه بوکس پیاده سازی کنند. بنابراین دیگر صرف وجود یک کلاس، اینترفیس مجزایی برای آن تعریف نشده و بر اساس معکوس سازی کنترل است که تعریف اینترفیس IBoxer معنا پیدا کرده است. اکنون IBoxer دارای چندین و چند پیاده سازی خواهد بود. به این ترتیب، تعریف اینترفیس، ارزشی را به سیستم افزوده است.
به این نوع معکوس سازی اینترفیسها، الگوی provider model نیز گفته میشود. برای مثال کلاسی که از چندین سرویس استفاده میکند، بهتر است یک IService را ایجاد کرده و تامین کنندههایی، این IService را پیاده سازی کنند. نمونهای از آن در دنیای دات نت، Membership Provider موجود در ASP.NET است که پیاده سازیهای بسیاری از آن تاکنون تهیه و ارائه شدهاند.
معکوس سازی جریان کاری برنامه
جریان کاری معمول یک برنامه یا Noraml flow، عموما رویهای یا Procedural است؛ به این معنا که از یک مرحله به مرحلهای بعد هدایت خواهد شد. برای مثال یک برنامه خط فرمان را درنظر بگیرید که ابتدا میپرسد نام شما چیست؟ در مرحله بعد مثلا رنگ مورد علاقه شما را خواهد پرسید.
برای معکوس سازی این جریان کاری، از یک رابط کاربری گرافیکی یا GUI استفاده میشود. مثلا یک فرم را درنظر بگیرید که در آن دو جعبه متنی، کار دریافت نام و رنگ را به عهده دارند؛ به همراه یک دکمه ثبت اطلاعات. به این ترتیب بجای اینکه برنامه، مرحله به مرحله کاربر را جهت ثبت اطلاعات هدایت کند، کنترل به کاربر منتقل و معکوس شده است.
معکوس سازی تولید اشیاء
معکوس سازی تولید اشیاء، اصل بحث دوره و سری جاری را تشکیل میدهد و در ادامه مباحث، بیشتر و عمیقتر بررسی خواهد گردید.
روش متداول تعریف و استفاده از اشیاء دیگر درون یک کلاس، وهله سازی آنها توسط کلمه کلیدی new است. به این ترتیب از یک وابستگی به صورت مستقیم درون کدهای کلاس استفاده خواهد شد. بنابراین در این حالت کلاسهای سطح بالاتر به ماژولهای سطح پایین، به صورت مستقیم وابسته میگردند.
برای اینکه این کنترل را معکوس کنیم، نیاز است ایجاد و وهله سازی این اشیاء وابستگی را در خارج از کلاس جاری انجام دهیم. شاید در اینجا بپرسید که چرا؟
اگر با الگوی طراحی شیءگرای Factory آشنا باشید، همان ایده در اینجا مدنظر است:
Button button; switch (UserSettings.UserSkinType) { case UserSkinTypes.Normal: button = new Button(); break; case UserSkinTypes.Fancy: button = new FancyButton(); break; }
حال در این برنامه اگر قرار باشد کار و کنترل محل وهله سازی این دکمهها معکوس نشود، در هر قسمتی از برنامه نیاز است این سوئیچ تکرار گردد (برای مثال در چند ده فرم مختلف برنامه). بنابراین بهتر است محل ایجاد این دکمهها به کلاس دیگری منتقل شود مانند ButtonFactory و سپس از این کلاس در مکانهای مختلف برنامه استفاده گردد:
Button button = ButtonFactory.CreateButton();
بنابراین در مثال فوق، کنترل ایجاد دکمهها به یک کلاس پایه قرار گرفته در خارج از کلاس جاری، معکوس شده است.
انواع معکوس سازی تولید اشیاء
بسیاری شاید تصور کنند که تنها راه معکوس سازی تولید اشیاء، تزریق وابستگیها است؛ اما روشهای چندی برای انجام اینکار وجود دارد:
الف) استفاده از الگوی طراحی Factory (که نمونهای از آنرا در قسمت قبل مشاهده کردید)
ب) استفاده از الگوی Service Locator
Button button = ServiceLocator.Create(IButton.Class)
ج) تزریق وابستگیها
Button button = GetTheButton(); Form1 frm = new Form1(button);
به صورت خلاصه هر زمانیکه تولید و وهله سازی وابستگیهای یک کلاس را به خارج از آن منتقل کردید، کار معکوس سازی تولید وابستگیها انجام شده است.
نظرات مطالب
UrlRewriter توسط Intelligencia.UrlRewriter
url routing از دات نت 3 و نیم، سرویس پک یک به بعد به صورت توکار و استاندارد، اضافه شده. فقط برای حالت دات نت سه و نیم باید یک سری تنظیمات اضافهتر به وب کانفیگ اضافه شوند (تعریف System.Web.Routing.UrlRoutingModule باید در قسمت httpModules ذکر شود).