Roslyn #6
پیشنیاز این بحث نصب مواردی است که در مطلب «شروع به کار با Roslyn » در قسمت دوم عنوان شدند:
الف) نصب SDK ویژوال استودیوی 2015
ب) نصب قالبهای ایجاد پروژههای مخصوص Roslyn
البته این قالبها چیزی بیشتر از ایجاد یک پروژهی کلاس Library جدید و افزودن ارجاعاتی به بستهی نیوگت Microsoft.CodeAnalysis، نیستند. اما درکل زمان ایجاد و تنظیم این نوع پروژهها را خیلی کاهش میدهند و همچنین یک پروژهی تست را ایجاد کرده و تولید بستهی نیوگت و فایل VSIX را نیز بسیار ساده میکنند.
هدف از تولید Analyzers
بسیاری از مجموعهها و شرکتها، یک سری قوانین و اصول خاصی را برای کدنویسی وضع میکنند تا به کدهایی با قابلیت خوانایی بهتر و نگهداری بیشتر برسند. با استفاده از Roslyn و آنالیز کنندههای آن میتوان این قوانین را پیاده سازی کرد و خطاها و اخطارهایی را به برنامه نویسها جهت رفع اشکالات موجود، نمایش داده و گوشزد کرد. بنابراین هدف از آنالیز کنندههای Roslyn، سهولت تولید ابزارهایی است که بتوانند برنامه نویسها را ملزم به رعایت استانداردهای کدنویسی کنند.
همچنین معلمها نیز میتوانند از این امکانات جهت ارائهی نکات ویژهای به تازهکاران کمک بگیرند. برای مثال اگر این قسمت از کد اینگونه باشد، بهتر است؛ مثلا بهتر است فیلدهای سطح کلاس، خصوصی تعریف شوند و امکان دسترسی به آنها صرفا از طریق متدهایی که قرار است با آنها کار کنند صورت گیرد.
این آنالیز کنندها به صورت پویا در حین تایپ کدها در ویژوال استودیو فعال میشوند و یا حتی به صورت خودکار در طی پروسهی Build پروژه نیز میتوانند ظاهر شده و خطاها و اخطارهایی را گزارش کنند.
بررسی مثال معتبری که میتواند بهتر باشد
در اینجا یک کلاس نمونه را مشاهده میکنید که در آن فیلدهای کلاس به صورت public تعریف شدهاند.
public class Student { public string FirstName; public string LastName; public int TotalPointsEarned; public void TakeExam(int pointsForExam) { TotalPointsEarned += pointsForExam; } public void ExtraCredit(int extraPoints) { TotalPointsEarned += extraPoints; } public int PointsEarned { get { return TotalPointsEarned; } } }
بنابراین در ادامه هدف ما این است که یک Roslyn Analyzer جدید را طراحی کنیم تا از طریق آن هشدارهایی را جهت تبدیل فیلدهای عمومی به خصوصی، به برنامه نویس نمایش دهیم.
با اجرای افزونهی View->Other windows->Syntax visualizer، تصویر فوق نمایان خواهد شد. بنابراین در اینجا نیاز است FieldDeclarationها را یافته و سپس tokenهای آنها را بررسی کنیم و مشخص کنیم که آیا نوع یا Kind آنها public است (PublicKeyword) یا خیر؟ اگر بلی، آن مورد را به صورت یک Diagnostic جدید گزارش میدهیم.
ایجاد اولین Roslyn Analyzer
پس از نصب پیشنیازهای بحث، به شاخهی قالبهای extensibility در ویژوال استودیو مراجعه کرده و یک پروژهی جدید از نوع Analyzer with code fix را آغاز کنید.
قالب Stand-alone code analysis tool آن دقیقا همان برنامههای کنسول بحث شدهی در قسمتهای قبل است که تنها ارجاعی را به بستهی نیوگت Microsoft.CodeAnalysis به صورت خودکار دارد.
قالب پروژهی Analyzer with code fix علاوه بر ایجاد پروژههای Test و VSIX جهت بسته بندی آنالایزر تولید شده، دارای دو فایل DiagnosticAnalyzer.cs و CodeFixProvider.cs پیش فرض نیز هست. این دو فایل قالبهایی را جهت شروع به کار تهیهی آنالیز کنندههای مبتنی بر Roslyn ارائه میدهند. کار DiagnosticAnalyzer آنالیز کد و ارائهی خطاهایی جهت نمایش به ویژوال استودیو است و CodeFixProvider این امکان را مهیا میکند که این خطای جدید عنوان شدهی توسط آنالایزر، چگونه باید برطرف شود و راهکار بازنویسی Syntax tree آنرا ارائه میدهد.
همین پروژهی پیش فرض ایجاد شده نیز قابل اجرا است. اگر بر روی F5 کلیک کنید، یک کپی جدید و محصور شدهی ویژوال استودیو را باز میکند که در آن افزونهی در حال تولید به صورت پیش فرض و محدود نصب شدهاست. اکنون اگر پروژهی جدیدی را جهت آزمایش، در این وهلهی محصور شدهی ویژوال استودیو باز کنیم، قابلیت اجرای خودکار آنالایزر در حال توسعه را فراهم میکند. به این ترتیب کار تست و دیباگ آنالایزرها با سهولت بیشتری قابل انجام است.
این پروژهی پیش فرض، کار تبدیل نام فضاهای نام را به upper case، به صورت خودکار انجام میدهد (که البته بیمعنا است و صرفا جهت نمایش و ارائهی قالبهای شروع به کار مفید است).
نکتهی دیگر آن، تعریف تمام رشتههای مورد نیاز آنالایزر در یک فایل resource به نام Resources.resx است که در جهت بومی سازی پیامهای خطای آن میتواند بسیار مفید باشد.
در ادامه کدهای فایل DiagnosticAnalyzer.cs را به صورت ذیل تغییر دهید:
using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace CodingStandards { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class CodingStandardsAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = "CodingStandards"; // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. internal static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); internal static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); internal const string Category = "Naming"; internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } public override void Initialize(AnalysisContext context) { // TODO: Consider registering other actions that act on syntax instead of or in addition to symbols context.RegisterSyntaxNodeAction(analyzeFieldDeclaration, SyntaxKind.FieldDeclaration); } static void analyzeFieldDeclaration(SyntaxNodeAnalysisContext context) { var fieldDeclaration = context.Node as FieldDeclarationSyntax; if (fieldDeclaration == null) return; var accessToken = fieldDeclaration .ChildTokens() .SingleOrDefault(token => token.Kind() == SyntaxKind.PublicKeyword); // Note: Not finding protected or internal if (accessToken.Kind() != SyntaxKind.None) { // Find the name of the field: var name = fieldDeclaration.DescendantTokens() .SingleOrDefault(token => token.IsKind(SyntaxKind.IdentifierToken)).Value; var diagnostic = Diagnostic.Create(Rule, fieldDeclaration.GetLocation(), name, accessToken.Value); context.ReportDiagnostic(diagnostic); } } } }
اولین کاری که در این کلاس انجام شده، خواندن سه رشتهی AnalyzerDescription (توضیحی در مورد آنالایزر)، AnalyzerMessageFormat (پیامی که به کاربر نمایش داده میشود) و AnalyzerTitle (عنوان پیام) از فایل Resources.resx است. این فایل را گشوده و محتوای آنرا مطابق تنظیمات ذیل تغییر دهید:
سپس کار به متد Initialize میرسد. در اینجا برخلاف مثالهای قسمتهای قبل، context مورد نیاز، توسط پارامترهای override شدهی کلاس پایه DiagnosticAnalyzer فراهم میشوند. برای مثال در متد Initialize، این فرصت را خواهیم داشت تا به ویژوال استودیو اعلام کنیم، قصد آنالیز فیلدها یا FieldDeclaration را داریم. پارامتر اول متد RegisterSyntaxNodeAction یک delegate یا Action است. این Action کار فراهم آوردن context کاری را برعهده دارد که نحوهی استفادهی از آنرا در متد analyzeFieldDeclaration میتوانید ملاحظه کنید.
سپس در اینجا نوع نود در حال آنالیز (همان نودی که کاربر در ویژوال استودیو انتخاب کردهاست یا در حال کار با آن است)، به نوع تعریف فیلد تبدیل میشود. سپس توکنهای آن استخراج شده و بررسی میشود که آیا یکی از این توکنها کلمهی کلیدی public هست یا خیر؟ اگر این فیلد عمومی تعریف شده بود، نام آنرا یافته و به عنوان یک Diagnostic جدید بازگشت و گزارش میدهیم.
ایجاد اولین Code fixer
در ادامه فایل CodeFixProvider.cs پیش فرض را گشوده و تغییرات ذیل را به آن اعمال کنید. در اینجا مهمترین تغییر صورت گرفته نسبت به قالب پیش فرض، اضافه شدن متد makePrivateDeclarationAsync بجای متد MakeUppercaseAsync از پیش موجود آن است:
using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace CodingStandards { [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CodingStandardsCodeFixProvider)), Shared] public class CodingStandardsCodeFixProvider : CodeFixProvider { public sealed override ImmutableArray<string> FixableDiagnosticIds { get { return ImmutableArray.Create(CodingStandardsAnalyzer.DiagnosticId); } } public sealed override FixAllProvider GetFixAllProvider() { return WellKnownFixAllProviders.BatchFixer; } public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; // Find the type declaration identified by the diagnostic. var declaration = root.FindToken(diagnosticSpan.Start) .Parent.AncestorsAndSelf().OfType<FieldDeclarationSyntax>() .First(); // Register a code action that will invoke the fix. context.RegisterCodeFix( CodeAction.Create("Make Private", c => makePrivateDeclarationAsync(context.Document, declaration, c)), diagnostic); } async Task<Document> makePrivateDeclarationAsync(Document document, FieldDeclarationSyntax declaration, CancellationToken c) { var accessToken = declaration.ChildTokens() .SingleOrDefault(token => token.Kind() == SyntaxKind.PublicKeyword); var privateAccessToken = SyntaxFactory.Token(SyntaxKind.PrivateKeyword); var root = await document.GetSyntaxRootAsync(c); var newRoot = root.ReplaceToken(accessToken, privateAccessToken); return document.WithSyntaxRoot(newRoot); } } }
کاری که در متد RegisterCodeFixesAsync انجام میشود، مشخص کردن اولین مکانی است که مشکلی در آن گزارش شدهاست. سپس به این مکان منوی Make Private با متد متناظر با آن معرفی میشود. در این متد، اولین توکن public، مشخص شده و سپس با یک توکن private جایگزین میشود. اکنون این syntax tree بازنویسی شده بازگشت داده میشود. با Syntax Factory در قسمت سوم آشنا شدیم.
خوب، تا اینجا یک analyzer و یک code fixer را تهیه کردهایم. برای آزمایش آن دکمهی F5 را فشار دهید تا وهلهای جدید از ویژوال استودیو که این آنالایزر جدید در آن نصب شدهاست، آغاز شود. البته باید دقت داشت که در اینجا باید پروژهی CodingStandards.Vsix را به عنوان پروژهی آغازین ویژوال استودیو معرفی کنید؛ چون پروژهی class library آنالایزرها را نمیتوان مستقیما اجرا کرد. همچنین یکبار کل solution را نیز build کنید.
پس از اینکه وهلهی جدید ویژوال استودیو شروع به کار کرد (بار اول اجرای آن کمی زمانبر است؛ زیرا باید تنظیمات وهلهی ویژهی اجرای افزونهها را از ابتدا اعمال کند)، همان پروژهی Student ابتدای بحث را در آن باز کنید.
نتیجهی اعمال این افزونهی جدید را در تصویر فوق ملاحظه میکنید. زیر سطرهای دارای فیلد عمومی، خط قرمز کشیده شدهاست (به علت تعریف DiagnosticSeverity.Error). همچنین حالت فعلی و حالت برطرف شده را نیز با رنگهای قرمز و سبز میتوان مشاهده کرد. کلیک بر روی گزینهی make private، سبب اصلاح خودکار آن سطر میگردد.
روش دوم آزمایش یک Roslyn Analyzer
همانطور که از انتهای بحث قسمت دوم بهخاطر دارید، این آنالایزرها را میتوان به کامپایلر نیز معرفی کرد. روش انجام اینکار در ویژوال استودیوی 2015 در تصویر ذیل نمایش داده شدهاست.
نود references را باز کرده و سپس بر روی گزینهی analyzers کلیک راست نمائید. در اینجا گزینهی Add analyzer را انتخاب کنید. در صفحهی باز شده بر روی دکمهی browse کلیک کنید. در اینجا میتوان فایل اسمبلی موجود در پوشهی CodingStandards\bin\Debug را به آن معرفی کرد.
بلافاصله پس از معرفی این اسمبلی، آنالایزر آن شناسایی شده و همچنین فعال میگردد.
در این حالت اگر برنامه را کامپایل کنیم، با خطاهای جدید فوق متوقف خواهیم شد و برنامه کامپایل نمیشود (به علت تعریف DiagnosticSeverity.Error).
در این قسمت در ابتدا نحوهی باز کردن یک پایگاه داهی چند بعدی را در محیط BIMS بررسی کرده و سپس چگونگی ساخت یک MDB را از پایه بررسی میکنیم. برای ادامه دادن این قسمت نیاز میباشد که پایگاه دادهی AdventureWorkDW2008 را در SSAS نصب کرده باشید .
در ابتدا مطابق شکل زیر منوی File سپس زیر منوی Open و Analysis Service Database را انتخاب نمایید.
در ادامه میبایست نام Server را مشخص نمایید و دقت داشته باشید که در اینجا منظور از نام سرور، نام سرور SSAS میباشد (در صورتیکه بر روی خود سرور در حال کار میباشید از . به جای نام سرور استفاده کنید). سپس در قسمت Database، نام پایگاه دادهی چند بعدی را انتخاب نمایید. در صورتی که به جز Adventure Work DW 2008 ، پایگاه دادههای چند بعدی دیگری را در SSAS داشته باشید، یک لیست از آنها را مشاهده خواهید کرد و در صورتیکه لیست شما خالی میباشد، احتمال دارد نام سرور اشتباه باشد یا روی سرویس SSAS مربوط به آن سرور هیچ پایگاه دادهی چند بعدی نصب نباشد.
حال مسیری را برای ذخیره سازی پروژهی جدید در نظر بگیرید:
پس از کمی شکیبایی، واکشی اطلاعات از روی پایگاه دادهی چند بعدی انتخاب شده انجام میشود و یک پروژه در ارتباط با آن پایگاه داده ساخته میشود.
همان طور که مشخص میباشد، یک شیء درون شاخهی Data Source وجود دارد که مشخص کنندهی ارتباط این پروژه با پایگاه دادهی Data Warehouse است. برای مشاهدهی این ارتباط، بر روی Adventure Work DW کلیک راست کنید و سپس گزینهی Open را انتخاب نمایید. در ادامه گزینهی Edit را بزنید.
سپس در پنجرهی جدید، تنظیمات رشتهی ارتباطی با DW را مشاهده نمایید
با زدن کلید Test Connection باید پیام Test Connection Succeeded را مشاهده نمایید. اکنون پنجرهها را با زدن کلید OK ببندید.
در قسمت Data Source View سه شی تعریف شده است؛ براساس دسته بندی مورد نظر و جاری در Business موجود در Adventure Work .
با کلیک راست کردن بر روی Adventure Works DW و انتخاب گزینهی Open، اقدام به باز کردن DSV انتخاب شده کنید. در صفحهی باز شده میتوانید انواع دیاگرام تهیه شده را مشاهده نمایید و همچنین لیستی از جداول موجود در این DSV مشخص میباشد.
با کلیک راست کردن در فضای خالی دیاگرام ، امکان Add/Remove کردن جداول را به دیاگرام دارید.
در شکل بالا بعد از انتخاب یک جدول در سمت راست و انتقال آن به سمت چپ میتوانید با زدن دکمهی Add Related Table براساس کلیدهای خارجی، جداول مرتبط با جدول انتخاب شده را به صورت خودکار انتخاب نمایید و به قسمت چپ انتقال دهید.
شما در ساخت Cube مشخص مینمایید که Cube را از کدام DSV خواهید ساخت. بنابراین انتخاب جداول در DSV ها میبایست براساس نوع Business شما باشد تا در ساخت Cube به مشکلی برخورد نکنید.
در ساختار درختی موجود در پنجرهی Solution در شاخهی Cube، میتوانید Adventure Works را باز کنید (کلیک راست و انتخاب Open ) .
در شکل بالا در سمت چپ، میتوانید Measure ها و Dimension های موجود در این Cube را مشاهده کنید. همچنین در قسمت بالا چندین Tab وجود دارند که در هر کدام تنظیمات بیشتری را بر روی Cube اعمال میکنیم. با توجه به اینکه طراحی Cube ها کاری تخصصی میباشد و نیاز به اطلاعات زیادی دارد اجازه دهید مقاله ای در خصوص طراحی Cube در SSAS جداگانه انتشار داده شود و فعلا در همین حد بسنده کنیم. با این حال در صورت نیاز میتوانید برای اطلاعات بیشتر در این خصوص کتاب Microsoft SQL Server Analysis Services 2008 With MDX از انتشارات Wrox را مطالعه نمایید.
در Solution Explorer در شاخهی ،Dimensions میتوانید تمامی بعدهایی که در تمامی Cube های شما استفاده شدهاند را مشاهده نمایید.
با انتخاب یک بعد (ترجیحا بعد Date ) و با کلیک راست کردن و انتخاب گزینهی Open آن را باز نمایید.
در پنجرهی باز شده میتوانید 4 Tab در بالا را مشاهد نمایید و در Tab نخست، Attribute ها و همچنین ساختار Hierarchies و در آخر Data source View را مشاهده نمایید.
در Attribute relationships می توانید ارتباط صفتهای یک بعد را مشخص نمایید.
در Browsing Tab میتوانید محتوای Dimension را بررسی نمایید (البته اگر در پروژهی جدید قرار دارید حتما میبایست پروژه را Deploy کرده باشید. در حالتیکه یک پایگاه داهی چند بعدی را باز میکنید، نیازی به Deploy کردن نمیباشد؛ زیرا حتما قبلا این کار انجام شده است (زیرا شما پایگاه دادهی چند بعدی را بعد از Deploy کردن پروژهی SSAS خواهید داشت ))
در صورتیکه مانند روش بالا یک پایگاه دادهی چند بعدی را باز کنیم، دیگر نیازی به Deploy کردن نمیباشد و فقط برای اعمال تغییرات روی پایگاه دادهی چند بعدی باید پروژه را Process کنیم و برای این منظور روی نام پروژه کلیک راست کرده و گزینهی Process را انتخاب کنید. با این کار تغییرات اعمال شده در BIMS روی پایگاه دادهی SSAS اعمال میگردند و دادهها با توجه به ساختار Cube ها دوباره پردازش میشوند.
برای ساخت یک پروژهی جدید به شکل زیر عمل میکنیم :
در ابتدا BIMS را باز کرده و سپس به منوی File رفته و در قسمت New گزینهی Project را انتخاب میکنیم. سپس در صفحهی باز شده، مطابق شکل زیر عمل کرده و یک پروژه از نوع Analysis Service Multidimensional … میسازیم.
سپس برروی شاخهی Data Source کلیک راست کرده و گزینهی New Data Source را میزنیم و پنجرههای ویزارد را به جلو میرویم.
در ابتدا باید یک Connection به DW تولید کنیم. برای این منظور در پنجرهی فوق دکمهی New را زده و اطلاعات را مطابق شکل زیر پر میکنیم.
و سپس OK را میزنیم.
در صورتی که SSAS در یک سرور دیگر نصب شده است در پنجرهی بعدی نیاز میباشد نام کاربری را که به سرویس SSAS در آن سرور دسترسی دارد را وارد کنیم.
در صورتی که SSAS روی سیستم Local نصب شده است و کاربری که با آن Login هستیم دسترسی کافی به SSAS را دارد، گزینهی Use the credentials of the current user را انتخاب میکنیم.
در صفحهی آخر یک نام برای DS انتخاب میکنیم.
سپس نیاز میباشد یک DSV بسازیم. برای این منظور روی شاخهی Data Source View کلیک راست کرده و گزینهی New را انتخاب کرده و سپس در پنجرهی Wizard باید Data Source ساخته شده در مرحلهی قبل را انتخاب کرده و سپس Next را بزنیم. در اینجا بر اساس بیزینسهای مختلف، راه کارهای گوناگونی را داریم. به عبارت دیگر میتوان جداول Fact و Dimension های مرتبط با آنرا بر اساس زیر سیستمهای مختلف انتخاب کرده و برای هر کدام از آنها یک DSV بسازیم. به نظر من میتوانیم تمامی جداول را در این مرحله انتخاب کرده و سپس این تفکیک بندی را در سطح Cube ها انجام داد. به طور کلی دقت داشته باشید به هیچ عنوان DSV و Cube های سیستم را خیلی تفکیک نکنید. زیرا در نوشتن کوئریها و Join بین Cube ها با مشکل و سختی روبرو خواهید شد. (از لحاظ تجربی تفکیک بندی به شرطی صورت گیرد که نیازی به Join کردن Cube ها در MDX Query ها نباشد.)
سپس یک نام برای DSV خود انتخاب کرده و Finish را بزنید.
خوب؛ آخرین مرحله ساخت Cube میباشد (البته در طراحی Cube مطالب بسیاری وجود دارند که در یک مقالهی دیگر تلاش خواهم کرد تمامی آن موارد را توضیح دهم.)
برای ساخت Cube ، روی شاخهی Cube کلیک راست کرده و گزینهی New را بزنید.
سپس Use Existing Table را انتخاب کرده و Next را بزنید.
در پنجرهی بعدی باید DSV را انتخاب کرد و بعد جداول مورد نیاز در طراحی Cube را انتخاب کنید. فراموش نکنید در صورت انتخاب یک Fact تمامی Dimension های مرتبط با آن را انتخاب نماید. دکمه Next را بزنید.
در پنجرهی بعدی باید جداول Fact را انتخاب کرده و دکمهی Next را بزنید.
سپس در پنجرهی بعدی دایمنشن را انتخاب نمایید. (ترجیحا اجازه بدهید خود BIMS برای شما Dimension ها را بسازد، هرچند که خود شما میتوانید بعدا به صورت دستی Dimension ها را ایجاد کنید).
بعد از زدن دکمهی Next نامی برای Cube خود انتخاب نمایید و سپس دکمهی Finish را بزنید.
بعد از ساخت Cube ، چندین دایمنشن به صورت خودکار ساخته میشوند . البته گاهی نیاز میباشد که اقدام به ساخت ساختارهای سلسله مراتبی در Dimension ها کنیم (این مورد را در یک مقاله جداگانه آموزش خواهم داد.)
پروژه با کلیدهای ترکیبی Ctrl+Shift+B ساخته میشود و بعد از اطمینان از درست بودن ساخت پروژه، آن را باید Deploy کرد.
برای Deploy کردن یک پروژه کافی است بعد از تنظیم کردن رشتهی ارتباطی در DS (قبلا توضیح داده شده است) روی پروژه کلیک راست کرده و گزینهی Deploy را بزنیم.
$.get('http://site-url', function(data) { //این تابع پس از پایان کار عملیات ایجکسی در آینده فراخوانی خواهد شد });
$.get('http://site-url/0', function(data0) { // callback #1 $.get('http://site-url/1', function(data1) { // callback #2 $.post('http://site-url/2', function(data2) { // callback #3 }); }); });
روشهای زیادی برای حل این مساله ارائه شدهاست و در حال حاضر کار کردن با promiseها متداولترین روش حل مدیریت فراخوانی کدهای همزمان جاوا اسکریپتی است. برای نمونه اگر از AngularJS استفاده کنید، سرویسهای آن برای دریافت اطلاعات از سرور، از یک چنین مفهومی استفاده میکنند.
Promise در جاوا اسکریپت چیست؟
شیء Promise، نمایانگر قراردادی است که در آینده میتواند مورد قبول واقع شود، یا رد گردد. بررسی این قرارداد، تنها یکبار میتواند رخ دهد (پذیرش یا رد آن). هنگامیکه این بررسی صورت گرفت (رد یا پذیرش آن و نه هردو)، یک callback برای اطلاع رسانی فراخوانی میگردد. سپس این callback میتواند یک Promise دیگر را سبب شود. به این ترتیب میتوان Promiseها را زنجیر وار به یکدیگر متصل کرد. برای نمونه jQuery به صورت توکار از promises پشتیبانی میکند:
// returns a promise $.get('http://site-url/0') .then(function(data) { // callback 1 // returns a promise return $.get('http://site-url/1'); }) .then(function(data) { // callback 2 // returns a promise return $.post('http://site-url/2'); }) .then(function(data) { // callback 3 });
در این حالت، هر callback حداقل سه کار را میتواند انجام دهد:
الف) یک promise دیگر را بازگشت دهد. نمونه آنرا با return $.get در کدهای فوق ملاحظه میکنید.
ب) خاتمه عادی. همینجا کار promise با مقدار بازگشت داده شده، پایان مییابد.
ج) صدور یک استثناء. سبب برگشت خوردن و عدم پذیرش promise میشود.
استفاده از Promises در سایر کتابخانهها
jQuery پیاده سازی توکاری از promises دارد؛ اما سایر کتابخانهها، مانند AngularJS ایی که مثال زده شده چطور عمل میکنند؟
استانداردی به نام +Promises/A جهت یک دست سازی پیاده سازیهای promise در جاوا اسکریپت پیشنهاد شدهاست. jQuery نیمی از آنرا پیاده سازی کردهاست؛ اما کتابخانهی دیگری به نام Q Library، پیاده سازی نسبتا مفصلتری را از این استاندارد ارائه میدهد. فریم ورک AngularJS نیز در پشت صحنه از همین کتابخانه برای پیاده سازی promises استفاده میکند.
آشنایی با کتابخانه Q
استفاده مقدماتی از Q همانند مثالی است که از jQuery ملاحظه کردید.
Q.fcall(callback1) .then(callback2);
Q.fcall(function() { return $.get('http://my-url'); }) .then(callback3);
function waitForClick() { var deferred = Q.defer(); $('#okButton').click(function() { deferred.resolve(); }); $('#cancelButton').click(function() { deferred.reject(); }); return deferred.promise; } Q.fcall(waitForClick) .then(function() { // ok button was clicked }, function() { // cancel button was clicked });
در ادامه کار، اینبار متد then، دو callback را قبول میکند. Callback اول پس از پذیرش قرار داد و Callback دوم پس از رد قرار داد، فراخوانی خواهد گردید.
در رنجیره تعریف شده، اگر معادلی برای reject درنظر گرفته نشده باشد، مانند مثال ذیل:
Q.fcall(myFunction1) .then(success1) .then(success2, failure1);
همچنین اگر نتیجهی success1 با شکست مواجه شود نیز failure1 فراخوانی میگردد. اما باید درنظر داشت که شکست success2، توسط failure1 مدیریت نمیشود.
Promises در AngularJS
در AngularJS امکانات کتابخانه Q توسط پارامتری به نام q$ در اختیار سرویسهای برنامه قرار میگیرد (تزریق میشود):
var app = angular.module("myApp", []); app.factory('dataSvc', function($http, $q){ var basePath="api/books"; getAllBooks = function(){ var deferred = $q.defer(); $http.get(basePath).success(function(data){ deferred.resolve(data); }).error(function(err){ deferred.reject("service failed!"); }); return deferred.promise; }; return{ getAllBooks:getAllBooks }; }); app.controller('HomeController', function($scope, $window, dataSvc){ function initialize(){ dataSvc.getAllBooks().then(function(data){ $scope.books = data; }, function(msg){ $window.alert(msg); }); } initialize(); });
اکنون در کنترلری که قرار است از این سرویس استفاده کند، متد then کتابخانه Q را ملاحظه میکنید که دو Callback متناظر resolve و reject مدیریت promise بازگشت داده شده را به همراه دارد. اگر عملیات Ajaxایی موفقیت آمیز باشد، شیء books را مقدار دهی میکند و اگر خیر، پیامی را به کاربر نمایش خواهد داد.
پشتیبانی مرورگرهای جدید از استاندارد Promise
در حال حاضر کروم 32 و نگارشهای شبانه فایرفاکس، Promise را که جزئی از استاندارد JavaScript شدهاست، به صورت توکار و بدون نیاز به کتابخانههای جانبی، پشتیبانی میکنند.
if (window.Promise) { // Check if the browser supports Promises var promise = new Promise(function(resolve, reject) { //asynchronous code goes here }); }
if (window.Promise) { console.log('Promise found'); var promise = new Promise(function(resolve, reject) { // async if (result) { resolve(data); } else { reject('error'); } }); promise.then(function(data) { console.log('Promise fulfilled.'); }, function(error) { console.log('Promise rejected.'); }); } else { console.log('Promise not available'); }
ممنون
public class Employee { public string EmployeeName { get; set; } public int EmployeeNo { get; set; } public void Insert(Employee e) { //Database Logic written here } public void GenerateReport(Employee e) { //Set report formatting } }
public class Employee { public string EmployeeName { get; set; } public int EmployeeNo { get; set; } } public class EmployeeDB { public void Insert(Employee e) { //Database Logic written here } public Employee Select() { //Database Logic written here } } public class EmployeeReport { public void GenerateReport(Employee e) { //Set report formatting } }
//Method with multiple responsibilities – violating SRP public void Insert(Employee e) { string StrConnectionString = ""; SqlConnection objCon = new SqlConnection(StrConnectionString); SqlParameter[] SomeParameters=null;//Create Parameter array from values SqlCommand objCommand = new SqlCommand("InertQuery", objCon); objCommand.Parameters.AddRange(SomeParameters); ObjCommand.ExecuteNonQuery(); }
//Method with single responsibility – follow SRP public void Insert(Employee e) { SqlConnection objCon = GetConnection(); SqlParameter[] SomeParameters=GetParameters(); SqlCommand ObjCommand = GetCommand(objCon,"InertQuery",SomeParameters); ObjCommand.ExecuteNonQuery(); } private SqlCommand GetCommand(SqlConnection objCon, string InsertQuery, SqlParameter[] SomeParameters) { SqlCommand objCommand = new SqlCommand(InsertQuery, objCon); objCommand.Parameters.AddRange(SomeParameters); return objCommand; } private SqlParameter[] GetParaeters() { //Create Paramter array from values } private SqlConnection GetConnection() { string StrConnectionString = ""; return new SqlConnection(StrConnectionString); }
معماری میکرو سرویس یا یکپارچه؟
برای درک میکروسرویسها، باید بدانیم کاربرد سیستمهای یکپارچه چیست و چه چیزی باعث شد در زمانهای اخیر از برنامههای یکپارچه به میکروسرویسها حرکت کنیم.
سیستمهای یکپارچه ( Monolithic applications )
اگر تمام عملکردهای یک پروژه در یک بخش واحد وجود داشته باشند، آن برنامه به عنوان یک برنامهی یکپارچه شناخته میشود. ما برنامهی خود را در لایههای مختلفی مانند Presentation ، Service ، UI طراحی میکنیم و سپس آن بخش از کدهای نوشته شده را به عنوان یک فایل خروجی به کار میگیریم. این چیزی نیست جز یک برنامهی یکپارچه، که در آن " mono " یک پایگاه کد منفرد حاوی تمام عملکردهای مورد نیاز را نشان میدهد.
چرا اصلا به سمت میکروسرویسها برویم؟
خب برای جواب به این سوال بهتر است معایب سیستمهای یکپارچه را مرور کنیم:
- مدیریت دشوار بخاطر گسترش برنامه در گذشت زمان
- برای تغییری کوچک، کل برنامه را دوباره باید منتشر ( publish ) کنیم
- با تغییر و آپدیت برنامه، زمان انتشار افزایش مییابد.
- درک دشوار برای توسعه دهندههای جدید هر پروژه
- برای تقسیم ترافیک روی قسمتهای مختلف برنامه، باید نمونههای کل برنامه را در چندین سرور منتشر کنیم که بسیار ناکارآمد و باعث استفادهی بیهوده از منابع میشود
- اگر از فناوری یا تکنولوژیهای جدید استفاده کنیم، برای عملکردی خاص، چه از نظر هزینه و چه از نظر زمان، بر کل برنامه تاثیر گذار است
- و در نهایت وجود یک باگ در هر ماژول میتواند کل برنامه را مختل کند.
و اما مزایای سیستمهای یکپارچه:
- توسعهی آن نسبت به میکروسرویسها ساده است.
- انتشار آن آسانتر است؛ زیرا فقط یک خروجی، مستقر شدهاست.
- در مقایسه با معماری میکروسرویسها، توسعهی آن نسبتا آسانتر و سادهتر است.
- مشکلات تأخیر و امنیت شبکه در مقایسه با معماری میکروسرویسها نسبتاً کمتر است.
- توسعه دهندگان نیازی به یادگیری برنامههای مختلف ندارند؛ آنها میتوانند تمرکز خود را بر روی یک برنامه حفظ کنند.
میکروسرویس ها
این یک سبک توسعه معماری است که در آن برنامه از سرویسهای کوچکتری تشکیل شدهاست که بخش کوچکی از عملکرد و دادهها را با برقراری ارتباط مستقیم با یکدیگر، با استفاده از پروتکلی مانند HTTP مدیریت میکند. به عبارتی دیگر خدمات یا سرویسهای کوچکی هستند که با هم کار میکنند.
معماری میکروسرویس تأثیر بسزایی در رابطهی بین برنامه و پایگاه داده دارد. بجای اشتراک گذاری یک پایگاه داده با سایر میکروسرویسها، هر میکروسرویس، پایگاه داده خاص خود را دارد که اغلب منجر به تکثیر برخی از دادهها میشود، اما اگر میخواهید از این معماری بهره مند شوید، داشتن یک پایگاه داده در هر میکروسرویس، ضروری است؛ زیرا اتصال ضعیف را تضمین میکند. مزیت دیگر داشتن یک پایگاه دادهی مجزا برای هر میکروسرویس این است که هر میکروسرویس میتواند از نوع پایگاه دادهای که برای نیازهای خود مناسبتر است، استفاده کند. هر سرویس یک ماژول را ارائه میدهد، به طوری که خدمات مختلف را میتوان به زبانهای برنامه نویسی مختلف نوشت. الگوهای زیادی در معماری میکروسرویس دخیل هستند مانند discovery و registry service ، Caching ، ارتباط API ، امنیت و غیره.
اصول میکروسرویسها:
تک مسئولیتی: یکی از اصولی است که به عنوان بخشی از الگوی طراحی SOLID تعریف شده است. بیان میکند که یک Unit ، یا یک کلاس، یک متد یا یک میکروسرویس باید تنها یک مسئولیت را داشته باشد. هر میکروسرویس باید یک مسئولیت داشته باشد و یک عملکرد واحد را ارائه دهد. شما همچنین میتوانید بگویید تعداد میکروسرویسهایی که باید توسعه دهید، برابر با تعداد عملکردهای مورد نیاز شما است. پایگاه داده نیز غیرمتمرکز است و به طور کلی، هر میکروسرویس، پایگاه داده خاص خود را دارد.
بر اساس قابلیتهای تجاری ساخته شده است: در دنیای امروزی که فناوریهای زیادی وجود دارند، همیشه فناوریای وجود دارد که برای اجرای یک عملکرد خاص مناسبتر است. اما در برنامههای یکپارچه، این یک اشکال بزرگ بود؛ زیرا ما نمیتوانیم از فناوریهای مختلف برای هر عملکرد استفاده کنیم و از این رو، نیاز به مصالحه در زمینههای خاص داریم. یک میکروسرویس هرگز نباید خود را از پذیرش پشته فناوری مناسب یا ذخیرهسازی پایگاه داده پشتیبان که برای حل هدف تجاری مناسبتر است، محدود کند؛ بهعنوان مثال، هر میکروسرویس میتواند بر اساس نیازهای تجاری از فناوریهای متفاوتی استفاده کند.
طراحی برای مدیریت خطاها: میکروسرویسها باید با در نظر گرفتن مدیریت خطاها طراحی شوند. میکروسرویسها باید از مزیت این معماری استفاده کنند و پایین آمدن یک میکروسرویس نباید بر کل سیستم تأثیر بگذارد و سایر عملکردها باید در دسترس کاربر باقی بمانند. اما در برنامههای کاربردی سیستمهای یکپارچه که خطای یک ماژول منجر به سقوط کل برنامه میشود، اینگونه نبود.
مزایای میکروسرویس ها:
- مدیریت آن آسان است زیرا نسبتا کوچکتر است.
- اگر در یکی از میکروسرویسها، بروزرسانی وجود داشته باشد، باید فقط آن میکروسرویس را مجدداً منتشر کنیم.
- میکروسرویسها مستقل هستند و از این رو به طور مستقل منتشر میشوند. زمان راه اندازی و انتشار آنها نسبتاً کمتر است.
- برای یک توسعهدهنده جدید بسیار آسان است که وارد پروژه شود، زیرا او باید فقط یک میکروسرویس خاص را که عملکردی را که روی آن کار میکند، درک کند و نه کل سیستم را.
- اگر یک میکروسرویس خاص به دلیل استفاده بیش از حد کاربران از آن عملکرد، با بار زیادی مواجه است، ما باید فقط آن میکروسرویس را تنظیم کنیم. از این رو، معماری میکروسرویس از مقیاس بندی افقی پشتیبانی میکند.
- هر میکروسرویس بر اساس نیازهای تجاری میتواند از فناوریهای مختلفی استفاده کند.
- اگر یک میکروسرویس خاص به دلیل برخی باگها از کار بیفتد، بر روی سایر میکروسرویسها تأثیر نمیگذارد و کل سیستم دست نخورده باقی میماند و به ارائه سایر عملکردها به کاربران ادامه میدهد.
معایب میکروسرویس ها:
- پیچیده است و پیچیدگی آن با افزایش تعداد ریز سرویسها افزایش مییابد.
- نیاز به نیروهای متخصص
- استقرار مستقل میکروسرویسها پیچیدهاست.
- میکروسرویسها از نظر استفاده از شبکه پرهزینه هستند؛ زیرا نیاز به تعامل با یکدیگر دارند و همه این تماسهای راه دور منجر به تأخیر شبکه میشود.
- امنیت کمتر به دلیل ارتباط بین سرویسها
- اشکال زدایی دشوار است