تولید نرم افزار با الگو

وبلاگ شخصی ابراهیم خانی

Aggregate

با طراحی entity های و value object ها، نوبت به ایجاد رابطه میان آنها می رسد (هر چند این فرآیند  تکراری-افزایشی [1] است و قائل شدن ترتیب برای آن محلی از اعراب ندارد). این نقطه از تصمیم گیری نکات و ظرایفی به دنبال دارد که بعضا تولید کنندگان با تجربه در صنعت نرم افزار (آنچه در بعضی از شرکت های نرم افزاری شناخته شده داخلی مشاهده کرده ام که افرادی تحت عنوان تحلیل گر، با بررسی نیازمندی ها به طور مستقیم مدل داده [2] استخراج می کنند) از آنها غافل می مانند. برای مصون ماندن از چنین دام هایی بهترین راه حل توجه به هدفی است که طراحی مدل دامنه دنبال می کند؛ اطمینان از برقراری ثابت ها [3] و use case ها. در واقع طراحی مدل داده (که بسته به نوع ذخیره سازی، تصویری از مدل دامنه است) و واسط کاربری نتایجی هستند که بر طراحی مدل دامنه مترتب می شوند.

برای طراحی رابطه میان اجزاء مدل دامنه توجه به دو اصل شناخته شده مهندسی نرم افزار راهگشا خواهد بود؛[4] KISS  و [5] YAGNI .برای بالابردن سادگی و قابلیت فهم مدل دامنه می بایست از ورود اضافاتی که باعث پیچیدگی آن می شوند جلوگیری کرد. یک راه نیل به این هدف جلوگیری از ایجاد روابط در مدل دامنه برای پاسخ گویی به نیازمندی هایی که ممکن است در آینده به وجود آیند، است (اصل YAGNI).  راه دیگر تبدیل رابط دو طرفه غیرلازم به روابط یک طرفه و استفاده از شناسه (به جای ارجاع به شی [6]) برای ایجاد ارتباط بین اشیا است (اصل KISS). 

بعد از ایجاد یک مدل از دامنه که صحت روابط در آن به درستی بررسی شده است، چالش های فنی زیادی برای انتخاب مرزهای سازگاری و تراکنش [7] وجود خواهد داشت. به عبارت دیگر با توجه به اینکه مدل دامنه به ذخیره و بازیابی نیاز خواهد داشت و امکان دسترسی به منبع ذخیره سازی به صورت همزمان وجود خواهد داشت می بایستی راهکارهایی به منظور حفظ ثابت های دامنه و رو یا رویی با مسایل مربوط به دسترسی همزمان [8] اتخاذ شود. در طراحی مدل رانه [9] الگوی Aggregate به عنوان پاسخی برای این چالش ها در نظر گرفته شده است. در این الگو اشیایی که برای برقراری یک ثابت دامنه  درتعامل هستند در یک Aggregate قرار می گیرند و ذخیره و بازیابی آنها عنوان یک واحد و یکباره انجام می شود. به منظور برقراری ثابت های دامنه، تمامی تعاملات با یک Aggregate توسط Aggregate root صورت می پذیرد که یکی از اشیاء موجود در Aggregate است. بررسی این ثابت ها با توجه به اینکه ارتباطات در یک Aggregate از نوع ارجاع به شی است امکان پذیر است. در واقع طراحی دامنه رانه با استفاده از Aggregate به دنبال بکارگیری راهکارهای لایه بندی [10] و بخش بندی [11] برای بالا بردن سطح انتزاع [12] و مدیریت پیچیدگی [13] است. به بیان ساده تر یک Aggregate مجموعه ای از اشیاء همبند [14] در مدل دامنه هستند که با توجه به use case ها در قلمرو مساله [15] به عنوان یک واحد در نظر گرفته می شوند.



[1] - Iterative-Incremental
[2] - Data model
[3] - Domain invariant
[4] - Keep It Simple Stupid
[5] - You Aren't Gonna Need It
[6] - Object reference
[7] - Consistency and transaction boundary
[8] - Concurrency 
[9] - Domain Driven Design
[10] - Layering 
[11] - Partitioning
[12] - Abstraction
[13] - Complexity management 
[14] - Cohesive
[15] - Problem domain
۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

entity

در روش طراحی دامنه رانه [1]، هنگام طراحی مدل دامنه [2] موجودیت هایی رو به رو خواهیم شد که دارای شناسه هستند. به عبارت دیگر در طول زمان می بایست به یک موجودیت دسترسی پیدا کرد و این ویژگی دوم آنهاست که وجود شناسه نیز برای پاسخ گویی به آن است. به عبارت دیگر این موجودیت ها دارای شناسه هستند تا بتوان در زمان های مختلف مورد دسترسی قرار گیرند. برای مدل کردن این موجودیت ها از الگویی به نام entity استفاده می شود.

به منظور پیاده سازی این الگو و اختصاص شناسه به entity ها، سه راهکار وجود دارد. راهکار اول اختصاص شناسه های موجود در دامنه مساله [3] به entity هاست. برای این کار باید دقت کرد که اولا این شناسه ها واقعا شناسه هستند (مانند شناسه ملی برای انسان ها و یا کد شابک برای کتاب ها) و ثانیا در طول زمان تغییر نمی کنند.

 راهکار دوم استفاده از شناسه های تولید شده در دامنه پاسخ [4] است. برای انجام این کار نیز سه روش وجود دارد؛ استفاده از اعداد شمارشی، استفاده از guid و یا استفاده از رشته [5] هایی که از ترکیب ویژگی های entity به وجود می آیند. استفاده از اعداد شمارشی نیازمند استفاده از راهکاری جهت دخیره سازی حالت است تا در صورتی که فرآیند [6] در حال اجرا با مشکل مواجه شد، این حالت از بین نرود. 

راهکار سوم واگذاری مسئولیت تولید شناسه به پایگاه داده است. مشکل اصلی این راهکار این است که اختصاص شناسه به entity ها مستلزم یک تراکنش [7] با پایگاه داده خواهد بود و این بدان معناست که یک تراکنش کسب و کار ممکن است در بیش از یک تراکنش سیستم انجام شود. برای غلبه بر این مشکل از روشی به نام hi-low استفاده می شود. در این روش برای هر نوع entity شناسه ها به صورت بازه های مشخص (مثلا صد تایی) از پایگاه داده دریافت می شود و شماره آخرین شناسه دریافتی در پایگاه داده ذخیره می شود.

نکته ای که در طراحی entity ها نباید از آن غافل شد، توجه به اصل [8] SRP است. با توجه به اینکه entity ها وظیفه مدیریت شناسه را به عهده دارند در مواردی که با value object ای در ارتباط هستند، می بایست رفتارها [9] به value object انتساب داده شوند.  



[1] - Domain driven design
[2] - Domain model
[3] - Problem domain
[4] - Solution domain
[5] - String
[6] - Process
[7] - Transaction 
[8] - Single Responsibility Principle 
[9] - Behavior
۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

value object

به هنگام طراحی مدل مدل دامنه [1] در طراحی مدل رانه [2]  با ویژگی هایی از یک entity رو به رو خواهیم شد که چیزی فراتر از یک مقدار ساده هستند اما به خودی خود موجودیت مستقلی به حساب نمی آیند. به عنوان مثال پول در یک سیستم بانکی ویژگی یک حساب بانکی است که فاقد هویت مستقل است (در یک سیستم بانکی اهمیت ندارد که در حساب یک مشتری دو اسکناس هزار تومانی وجود داشته باشد و یا یک اسکناس دوهزار تومانی)، حال آنکه مدل کردن آن با یک مقدار عددی خاص تصمیم درستی نیست. این ویژگی دارای محدویت ها و رفتارهای خاصی است که در صورت مدل کردن آن با یک مقدار عددی به صورت سرگردان در مدل دامنه وجود خواهند داشت. مشکل اصلی این طراحی جداکردن رفتار [3] از داده [4] است که در واقع نقص encapsulation است. استفاده از مقادیر عددی برای نمایش چنین ویژگی هایی یکی از bad smell هایی است که در کتاب [5] تحت عنوان primitive obsession از آن یاد شده است. از طریف دیگر انجام آزمون های validation مربوط به این مقادیر در جاهای مختلف منجر به  bad smell خطرناک تری به نام duplicated code می شود. برای مدل سازی این ویژگی ها در طراحی مدل رانه از الگویی به نام value object استفاده می شود. ویژگی های value object عبارتند از :

  • فاقد شناسه بودن
  • غیرقابل تغییر بودن [6]

هر چند بر ویژگی هایی مانند غیرقابل تغییر بودن (که از راه کارهای برنامه نویسی رویه ای [7] است) نتایج سودمندی (مانند حذف اثرات جانبی [8]) مترتب است، باید توجه داشت که این ویژگی ها از رفتار پدیده ها در دامنه مساله [9] نشات گرفته شده اند. همانطور که بیان شد در یک سیستم بانکی پول فاقد هویت است (هر چند برای تولید کنندگان اسکناس شماره سریال آن مهم است) و ضمنا نمی توان ارزش پول را تغییر داد (مثلا هزار تومان را به دو هزار تومان تبدیل کرد) مگر اینکه پول جدیدی جایگزین پول اول شود.

با توجه به اینکه برای بررسی تساوی در value object ها از مقدار ویژگی های بایستی استفاده شود، دو راهکار برای این کار وجود دارد. راهکار اول اینکه در کلاس پایه ای [10] که برای آن در نظر گرفته می شود با استفاده از reflection به ویژگی ها و مقادیر آن ها دسترسی یافت تساوی را با توجه به مقادیر آنها بررسی کرد. با توجه به سرباری که استفاده از reflection به همراه دارد استفاده از این روش مقرون به صرفه نیست. راهکار دوم قرار داده یک تابع abstract در کلاس پایه است. در این صورت هر یک از کلاس های مشتق شده [11] با بازنویسی [12] این تابع، ویژگی های خود را در اختیار تابعی که وظیفه بررسی تساوی را دارد قرار می دهند.

private IEnumerable<FieldInfo> GetMyFields()
{
    var type = GetType();
    var fieldInfos = new List<FieldInfo>();

    while (type != typeof(object))
    {
        var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        fieldInfos.AddRange(fields);
        type = type.BaseType;
    }
    return fieldInfos;
}

نکته مهم دیگری که نباید از آن غافل شد، ذخیره و بازیابی value object است. از آنجا که این اشیا فاقد شناسه هستند، ذخیره سازی آنها در پایگاه های داده که برای ذخیره سازی و بازیابی نیاز به شناسه دارند نیاز به توجه ویژه ای دارد. باید توجه داشت که شناسه ای که value object فاقد آن است مربوط به مدل دامنه است و اختصاص شناسه برای مدل ذخیره سازی به آن امکان پذیر است، هر چند بهتر است از این کار اجتناب شود. با توجه به آنچه آورده شد، سه راهکار برای ذخیره سازی value object وجود دارد. راهکار اول اینکه با استفاده از الگوهای [BLOB [13 و یا [14] CLOB، شی مورد نظر serialize  شده و در پایگاه داده ذخیره شود. طبیعی است که برای استفاده از این روش، value object هنگام بازیابی از پایگاه داده می بایست deserialize شود.  راه کار دوم استفاده از پایگاه داده های مبتنی بر document (مانند mongodb) است. با استفاده از این راهکار paradigm shift بین مدل دامنه و مدل ذخیره سازی از بین می رود. راهکار سوم ذخیره سازی ویژگی های value object در جدول شی ای که متعلق به آن است می باشد.

کد منبع [15] مربوط به این پست از این آدرس قابل دریافت است.



[1] - Domain model
[2] - Domain driven design
[3] - Behavior
[4] - Data
[5] - 

Refactoring: Improving the Design of Existing Code

[6] - Immutable

[7] - Functional programming

[8] - Side effects

[9] - Problem domain

[10] - Base

[11] - Derived

[12] - Override

[13] - Binary Large OBject

[14] - Character Large OBject

[15] - Source code
۱ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

domain driven design

هر سیستم نرم افزاری تصویری از یک راه حل برای پاسخگویی به نیازهای دامنه مساله [1] است. اما در این میان نرم افزارهای زیادی وجود دارد که قادر به توضیح چگونگی این کار نیستند [2]. روزگاری Heraclitus گفت : "در جهان یک چیز ثابت است، تغییر" [3]. نرم افزار ها نیز برای پاسخ گویی به نیازهای همین جهان متغییر پا به عرصه وجود گذاشته اند. نتیجه دو مدل مرتبط خواهد بود به طوری که تغییر در یکی (دامنه مساله) باعث تغییر در دیگری (دامنه پاسخ [4]) خواهد شد، بدون آنکه توضیحی برای چگونگی اعمال این تغییر باشد. با ادامه یافتن این روش در تولید نرم افزار معماری [5] BBOM حاصل می شود که از طرفی کابوس برنامه نویسان برای تغییر در کد خواهد شد و از طرف دیگر طولانی شدن فرآیند پاسخ به تغییرات برای صاحبان کسب و کار را به دنبال خواهد داشت. با توجه به آنچه گفته شد مشخص می شود که ریشه پیچیدگی در نرم افزار، دامنه مساله است و قسمت سخت تولید نرم افزار تایپ کردن نیست بلکه فهم دامنه مساله است.

طراحی دامنه رانه [6] مجموعه ای از روش ها (در سطوح مختلف ریزدانگی [7]) برای مدیریت پیچیدگی های دامنه مساله و در نتیجه ایجاد سیستم نرم افزاری با حداقل پیچیدگی است.  Practiceهای طراحی مدل رانه به دو قسمت تقسیم می شوند. استراتژیک و تاکتیکی. Practice های استراتژیک، توصیه هایی در راستای تقسیم بندی دامنه مساله به بخش های کوچک تر [8] و تمرکز به روی مهمترین بخش است.  در ادامه برای هر یک از بخش ها، مدلی ترسیم می شود با این تفاوت که مدل مربوط به زیردامنه های مهمتر، با کیفیت بالاتری ایجاد می شود. برای ایجاد مدل ها تیم تولید نرم افزار با متخصصین دامنه [9] همکاری می کنند. برای فراهم کردن بستر این همکاری نیاز به یک زبان مشترک است که در ادبیات طراحی مدل رانه، ubiquitous language است.  متناظر با هریک از قسمت های کوچکتر یک bounded context در نظر گرفته می شود که درون آن مدل سازی انجام می شود. در ادامه یک context map ترسیم می شود که نحوه ارتباط بین bounded context ها در آن بیان می شود. Practice های تاکتیکی بیشتر الگوهای [10] GOF و [11] EAA هستند که در فرآیند مدل سازی مورد استفاده قرار می گیرند.

این سوال که ماهیت طراحی مدل رانه چیست، یک جواب به دنبال نخواهد داشت. همانطور که در پست های قبلی بحث شد برای به کار گیری هدفمند الگوها در تولید نرم افزار دو راهکار وجود دارد؛ استفاده از زبان الگو [12] و یا استفاده از یک فرآیند مبتنی بر الگو. طراحی مدل رانه در حالی که ویژگی های هر یک از این دو راهکار را در خود دارد، به طور کامل در هیچ یک از این دسته ها قرار نمی گیرد. از یک طرف الگوهای تاکتیکی آن در قالب یک زبان الگو معرفی شده اند اما این بخش از طراحی مدل رانه بخش کم اهمیت تر آن است. از طرف Eric evans در مقدمه کتاب [13] به صراحت از رویکرد تکراری-افزایشی [14] برای مدل سازی و تعامل تولید کنندگان نرم افزار با متخصصین دامنه صحبت می کند که از توصیه های مهم متدولوژی های چابک [15] در تولید نرم افزار هستند، هر چند در ادامه تاکید می کند که روش او یک متدولوژی تولید نرم افزار نیست.

 طراحی دامنه رانه برای نیل هدف (غلبه بر پیچدگی دامنه مساله) از هر دو روش مدیریت پیچیدگی [16] (قسمت بندی [17] و لایه بندی [18]) استفاده می کند. تقسیم دامنه مساله به sub domain ها و دامنه پاسخ به bounded context ها به منظور استفاده از قسمت بندی برای مدیریت پیچدگی استفاده می شود. مدل سازی انجام شده نیز به منظور استفاده از لایه بندی و گذار نرم [19] از دامنه مساله به دامنه پاسخ است. باید توجه کرد که استفاده از مدل سازی و تبدیل مدل های ایجاد شده به کد مربوط به روش طراحی مدل رانه [20] است و طراحی دامنه رانه این روش را از طریق افزودن ubiquitous language به عنوان ابزار ارتباطی بین تولید کنندگان مدل کد و مدل طراحی غنی تر کرده است. 




[1] - Problem domain
[2] - Eric Evans : "code that does something useful, but without explaining how"
[3] - Heraclitus : "The only thing that is constant is change"
[4] - Solution domain
[5] - Big Boll Of Mud
[6] - Domain driven design
[7] - Granularity 
[8] - Subdomain
[9] - Domain expert 
[10] - Gang Of Four
[11] - Enterprise Application Architecture
[12] - Pattern language
[13] - Domain‐Driven Design: Tackling Complexity in the Heart of Software
[14] - Iterative-incremental 
[15] - Agile
[16] - Complexity management 
[17] - Partitioning
[18] - Layering 
[19] - Smooth 
[20] - Model driven design
۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

microkernel

در کتاب [1] الگوی microkernel، الگویی برای ایجاد سیستم نرم افزاری که قادر به سازگاری [2] با تغییرات در نیازمندی هاست تعریف شده است. به صورتی که یک بخش حداقلی به عنوان هسته [3] از سیستم جدا می شود و سایر بخش ها به عنوان افزونه [4] به سیستم اضافه خواهند شد. در این کتاب یک پیاده سازی از این الگو پیشنهاد شده است که از مولفه های زیر تشکیل شده است.

  • Internal server
  • External server
  • Adaptor
  • Client
  • Microkernel

همانطور که در تعریف الگو در پست های قبلی اشاره شد، الگوها صورتی منتزع از یک شکل محسوس هستند و به نظر می رسد آنچه در این کتاب پیشنهاد شده یکی از پیاده سازی های ممکن برای این الگو باشد. آنچه در این الگو اهمیت دارد استقلال افزونه ها از یکدیگر و قابلیت اضافه شدن افزونه های جدید به هسته سیستم بدون نیاز به نوشتن کد است. این افزونه ها می توانند به عنوان مولفه هایی که به صورت مستقل و یا مولفه هایی که به صورت متمرکز و در فرآیند [5] مربوط به هسته استقرار [6] یافته اند، پیاده سازی شوند.

یک مثال استفاده از این الگو سیستم مربوط به بورس است که کاربر شرایطی را بر اساس قیمت، حجم و سایر اطلاعات مربوط به سهام تعریف می کند. برای پیاده سازی این سیستم نیاز است که در بازه های زمانی متوالی داده های مربوط به معاملات استخراج و برقرار شرط های مختلف بر اساس این داده ها مورد ارزیابی قرار گیرد. همانطور که گفته شد شرط ها بر اساس پارامترهای آنها (قیمت، حجم، ...) به انواع مختلفی تقسیم می شوند و در طور زمان انواع جدید شرط ها به سیستم اضافه خواهد شد. الگوی microkernel برای پیاده سازی چنین سیستمی کاملا مناسب است. برای استفاده از این الگو در مثال مذکور، می توان مسئولیت دریافت اطلاعات در بازه های زمانی متوالی را به هسته سیستم سپرد و برای هر نوع شرط یک افزونه تعریف کرد.

یک پیاده سازی نوعی از این الگو در این آدرس قابل دسترسی است. در این کد به خاطر حفظ سادگی، افزونه ها در فرآیند مربوط به هسته اجرا می شوند و وظیفه جستجو و ایجاد آنها بر عهده PluginFactory است (در پوشه bin جستجو انجام می شود، بنابراین می بایست dll مربوط به افزونه ها در این پوشه کپی شود). این کار نیز برای حفظ سادگی کد انجام شده است و روش های جایگزین دیگری برای آن وجود دارد (مثلا در مرورگرها [7] با جستجو در مخزن افزونه ها، افزونه مورد نظر دانلود می شود)


public class PluginFactory
{
    private readonly PluginConfigurationSection _pluginConfigurationSection;

    public PluginFactory()
    {
        _pluginConfigurationSection = ConfigurationManager.GetSection("pluginSection") as PluginConfigurationSection;
    }

    public IEnumerable<IPlugin> GetPlugins()
    {
        var plugins = new List<IPlugin>();

        foreach (PluginElement plugin in _pluginConfigurationSection.PluginElementCollection)
        {
            var type = Type.GetType(plugin.Type);
            if (type == null)
                continue;
            var pluginInstance = Activator.CreateInstance(type) as IPlugin;
            if (pluginInstance == null)
                continue;
            plugins.Add(pluginInstance);
        }
        return plugins;
    }
}



[1] - Pattern-Oriented Software Architecture Volume 1: A System of Patterns

[2] - Adaptability

[3] - Core 

[4] - Plugin

[5] - Process

[6] - Deployed

[7] - Browser 

۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

event sourcing

به بیان ساده Event sourcing راهکاری برای ذخیره سازی حالت یک شی بر اساس رخداد هایی [1] است که از زمان ایجاد برای آن اتفاق افتاده است. در این صورت قادر خواهیم بود حالت هر شی در هر لحظه از زمان را با بازسازی این رخدادها، ایجاد کنیم. هر چند فواید بسیاری بر این روش مترتب است، نباید از جان مایه آن که توجه به رفتار [2] به جای حالت [3] است غافل شد.  سایر مزایای استفاده از این روش عبارتند از :

  • تغییر در سازوکار ذخیره سازی، تغییری در برنامه ایجاد نمی کند.
  • به خاطر سادگی ذخیره سازی رخدادها، کارایی بهبود می یابد.
  • عدم نیاز به لایه orm [4] برای ذخیره سازی.
  • نگهداری تاریخچه رخدادهایی که در سیستم اتفاق افتاده است.
  • سادگی تجمیع [5] با سایر زیر سیستم ها.
  • سیر در گذشته و پیش بینی آینده.
  • خطایابی سیستم که در حال کار است با تکرار رخداد هایی که منجر به خطا شده اند.

سناریوی اصلی پیاده سازی این الگوی معماری در قطعه کد زیر نشان داده شده است. با هر درخواست در لایه application service، شناسه aggregate root ای که برای پاسخ گویی به این درخواست وظیفه ای برعهده دارد مشخص می شود. سپس رخدادهایی که تا این لحظه برای این شی اتفاق افتاده است از منبع ذخیره سازی رخداد ها [6] دریافت می شود. با دریافت این رخدادها و اعمال آنها به شی مورد نظر، این شی به حالتی قبل از ورود درخواست می رسد. سپس بر اساس درخواست ورودی، متدی روی شی مورد نظر فراخوانی می شود. در پاسخ به این متد فراخوانی شده، دنباله ای از رخداد ها اتفاق می افتد که شی مورد نظر این دنباله را در اختیار دارد. در ادامه رخدادهای اتفاق افتاده در منبع ذخیره سازی رخدادها ذخیره می شود و پاسخ درخواست ورودی، ارسال می شود.


public Customer EsRoutine(Guid id, Action<Customer> action)
{
    var events = _eventStore.Get(id);

    var customer = new Customer(events);

    action(customer);

    _eventStore.Save(customer.Id, customer.Changes);

    return customer;
}


کد منبع مربوط به این پست از این آدرس قابل دریافت است.

هر چند این الگو مزایای زیادی به دنبال دارد، استفاده از آن چالش هایی به همراه دارد. اول اینکه با توجه به اینکه با هر درخواست ورودی می بایست aggregate root درگیر در این درخواست را با اعمال رخدادهایی که تا کنون اتفاق افتاده است به حالت فعلی رساند، در صورتی که تعداد این رخدادها زیاد باشد باعث ایجاد سربار بالایی برای سیستم خواهد شد. استفاده از snapshot و cache دو راه معمول برای حل این مشکل است. از طرف دیگر هر چند ذخیره سازی به صورت blob [7] و clob [8] باعث سادگی درج [9] اطلاعات می شود اما جستار [10] اطلاعات ذخیره شده با این روش ها مقرون به صرفه نیست. استفاده از الگوی cqrs [11] راه حلی شناخته شده برای این مشکل است که انشاا... در پست های آتی مورد بررسی قرار خواهد گرفت. 



[1] - Event

[2] - Behavior

[3] - State

[4] - Object Relational Mapper

[5] - Integration

[6] - Event store

[7] - Binary Large OBject

[8] - Character Large OBject

[9] - Insert

[10] - Query

[11] - Command Query Responsibility Segregation 

۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

code contract

در پست قبلی به design by contract اشاره شد. در .net framework 4 امکان پیاده سازی این روش با افزودن کلاس هایی که در فضای نام [1] System.Diagnostics.Contracts  قرار دارند ایجاد شد. باید توجه کرد که استفاده از این کلاس ها تنها برای تعریف قراردادها [2] است و وظیفه بررسی صحت این قرارداد ها در زمان اجرا و کامپایل به عهده ابزار جداگانه ای گذاشته شده است. به عبارت دیگر با کامپایل کدی که شامل فراخوانی متدهای مربوط به کلاس ایستای [3]  Contractاست، ابزاری به نام binary rewriter [4] فراخوانی می شود که باعث بازنویسی کد میانی تولید شده به منظور جایگزینی متدهای فراخوانی شده با متدهای کلاس _ContractRuntime (و کارهای دیگر مانند قرار دادن پیش شرط ها و پس شرط ها در محل مناسب) می شود. باید توجه کرد که تمامی متدها تعریف شده در کلاس Contract دارای ویژگی شرطی [5] CONTRACTS_FULL هستند. این بدین معنا است که بدون تعریف سمبل CONTRACTS_FULL این متدها اجرا نخواهند شد.


contract_full


در قطعه کد زیر یک پیش شرط برای تابع Messenger تعریف شده است. با کامپایل کردن این کد و مشاهده کد میانی [6] تولید شده، مشخص می شود که پیش شرط تعریف شده تاثیری در کد میانی حاصل ندارد. 


static void Messenger(string message)
     {
         Contract.Requires(message != null);

         Console.WriteLine($"message is {message}");
     }


علت این است که سمبل CONTRACTS_FULL تعریف نشده است. با تعریف این سمبل و کامپایل مجدد این قطعه کد و مشاهده کد میانی تولید شده، مشخص می گردد که فراخوانی متد روی کلاس Contract توسط ابزار binary rewriter به فراخوانی روی کلاس _ContractRuntime ترجمه شده است.


Contract_Runtime



[1] - Namespace
[2] - Contract
[3] - Static
[4] - ccrewrite.exe
[5] - Conditional attribute
[6] - IL
۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

pessimistic concurrency

همانطور که در پست قبلی اشاره شد، یکی از روش های دستکاری مسائل مربوط به همزمانی [1] الگوی optimistic lock است. اگرچه این الگو [2] قادر به حل مشکل lost update است اما کشف این وضعیت تنها هنگامی صورت می گیرد که کاربر داده هایی را وارد و اقدام به بروز رسانی [3] حالت [4] کرده است. اگر ماهیت تعامل کابران با  سیستم مورد نظر به صورتی باشد که این وضعیت به کرات صورت بگیرد، استفاده از این روش باعث اتلاف وقت کاربر و منابع سیستم است. راه کار دیگر برای حل این مشکل استفاده از الگوی pessimistic lock است. در این الگو در هر تراکنش [5] روی داده ها (در سطوح مختلف بسته به شرایط) قفل ایجاد می شود. در واقع این الگو به دنبال پیشگیری مشکلات مربوط به همزمانی است درحالی که الگوی خوش بینانه رویکرد درمانی دارد.

به طور کلی در پایگاه داده ها دو دسته از فعالیت ها وجود دارد که می بایست نسبت به یکدیگر همگام شوند. این دو دسته نویسنده ها [6] و خواننده ها [7] هستند. نویسنده ها دستورات select هستند که حالت پایگاه داده را تغییر نمی دهند و خواننده ها دستوراتdelete ,update ,insert  که باعث تغییر در حالت پایگاه داده می شوند. برای انجام عملیات خواندن، sql server یک قفل به نام قفل مشترک [8] روی سطری [9] که قرار است خوانده شود ایجاد می کند. همچنین زمان نوشتن یک قفل به نام قفل انحصاری [10] روی سطری که قرار است خوانده شود ایجاد می کند. نکته حائض اهمیت ناسازگاری این دو قفل با یکدیگر است، بدین معنا که امکان انجام خواندن و نوشتن روی یک سطر به طور همزمان وجود ندارد.

در sql server مفهوم isolation level برای توصیف مدت زمانی که خواننده ها قفل مشترک را در اختیار دارند به کار می رود.  سطح جداسازی پیش فرض read committed است. مشکل این سطح نبود پایداری در خواندن [11] است. به عبارت دیگر خواندن چند باره یک سطر در یک تراکنش، خروجی های مختلفی می تواند داشته باشد. علت این مشکل آزاد شدن قفل مشترک با خوانده شدن هر سطر داده است. در این سطح جداسازی هنگامی که نویسنده در یک تراکنش قفل اختصاصی را بدست می آورد، خواننده های دیگر نمی توانند قفل مشترک برای خواندن را بدست آورند.

برای غلبه بر این مشکل از سطح جداسازی repeatable read استفاده می شود. در این سطح جداسازی تنها پس از پایان تراکنش، قفل مشترک رها می شود. در این سطح جداسازی هنگامی که خواننده ای قفل مشترک را بدست آورد، سایر نویسندگان قادر به بدست آورد قفل اختصاصی نخواهند بود (بر عکس حالت read committed)

سطح جداسازی read uncommitted به معنای خواندن بدون به دست آوردن قفل مشترک است. در این حالت در صورتی که تراکنشی که در حال به روز رسانی داده است بازگشت کند [12] و پیش از این بازگشت در حالت جداسازی read uncommitted اقدام به خواندن آن شده باشد، حالتی را خوانده ایم که در پایگاه داده وجود ندارد. به عبارت بهتر در این حالت سازگاری [13] داده ها به خطر می افتد.

سخت گیرانه ترین سطح جداسازی serializable است که برای فراهم کردن read stability و جلوگیری از phantom read است. برای فراهم کردن این سطح جدا سازی sql server از روشی به نام key range lock استفاده می کند. در این روش به ازای هر سطر که مقدار آن برای ستون خاص در یک دامنه خاص قرار می گیرد یک قفل خاص هنگام خواندن قرار داده می شود. اگر تعداد این قفل ها بیش از 5000 شود، sql server قفل را روی کل جدول قرار می دهند. به هنگام استفاده از این قفل بهتر از است که در ستون مورد نظر یک non cluster index قرار داده شود.



[1] - Concurrency
[2] - Pattern
[3] - Update
[4] - State
[5] - Transaction
[6] - Writer
[7] - Reader
[8] - Sheared lock
[9] - Row
[10] - Exclusive lock
[11] - Read stability 
[12] - Rollback
[13] - Consistency

 

 

۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

optimistic concurrency

یکی از مشکلات مربوط به همزمانی که در پست قبلی به آن اشاره شد، lost update است. امکان به وجود آمدن این وضعیت در یک تراکنش سیستمی در sql server امکان پذیر نیست. اما هنگامی که نوبت به تراکنش های کسب و کار [1] می رسد داستان متفاوت می شود. از آنجایی که حین انجام یک تراکنش کسب و کار امکان انجام چند تراکنش سیستم وجود دارد، تظمینی برای جلوگیری از lost update وجود ندارد. یکی از راه های غلبه بر این مشکل استفاده از الگوی optimistic lock است.

ایده اصلی این الگو استفاده از row versioning است.  به عبارت دیگر برای هر سطر هنگام انجام عملیات مربوط به نوشتن یک version هم در پایگاه داده ثبت می شود. برای این version در sql server از نوع داده ی row version استفاده می شود. این نوع داده یک عدد افزایشی است که با هر عمل نوشتن در پایگاده داده افزایش می یابد.  برای بررسی lost update در هر عمل نوشتن، version سطری که در قرار است در پایگاه داده نوشته شود با version ای که پایگاه داده وجود دارد مقایسه می شود که عدم تطابق آنها نشانه تغییر در سطر مد نظر است. باید دقت کرد که optimistic lock قادر به شناسایی dirty read نیست (در پست های قبلی توضیح داده شد).

برای غلبه بر مشکل dirty read  با استفاده از روش خوشبینانه [2] می توان از سطح جداسازی [3] read committed snapshot استفاده کرد. این سطح جداسازی پیاده سازی optimistic از read committed است. در این پیاده سازی مشکل dirty read نخواهیم داشت. اساس روش های خوش بینانه عدم نیاز به قفل برای خواننده است. در این سطح جداسازی، در طول تراکنش مربوط به نوشتن، مقدار قدیمی یک ستون در version store که در tempdb ذخیره می شود نگهداری می شود. در خلال این تراکنش، هنگامی که یک خواننده قصد خواندن مقدار این ستون را دارد، مقدار اولیه این ستون را از version store می خواند.



[1] - Business transaction
[2] - Optimistic
[3] - Isolation level
۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

offline concurrency

از ویژگی های عمومی برنامه های سازمانی [1] دسترسی چندین کاربر به طور همزمان به آنها است. از آنجایی که در این دسترسی ها، حالت قبلی (که در پایگاه داده قرار دارد) مورد ارجاع قرار می گیرد، با چالش دسترسی چند process و یا thread به داده مشترک رو به رو خواهیم شد و این تعریف همزمانی [2] است. در نگاه اول به نظر می رسد استفاده از تراکنش ها [3] حلال همه مشکلات باشد. اما در بسیاری از موارد قرار دادن تراکنش کسب و کار [4] در یک تراکنش سیستم امکان پذیر نیست. در اینگونه از موارد الگوهایی برای دستکاری همزمانی وجود دارد که Fowler آنها را offline concurrency می نامد.

به طور کلی در پایگاه داده دو دسته از فعالیت ها وجود دارد که می بایست نسبت به یکدیگر همگام شوند. این دو دسته نویسنده ها[5] و خواننده ها[6] هستند. نویسنده ها دستورات select هستند که حالت پایگاه داده را تغییر نمی دهند و خواننده ها دستوراتdelete ,update ,insert  که باعث تغییر در حالت پایگاه داده می شوند.  دسترسی همزمان این فعالیت ها به یک داده در پایگاه داده مشکلاتی را به همراه خواهد داشت. این مشکلات عبارتند از :

Lost update : این مشکل زمانی به وجود می آید که دو تراکنش نویسنده به طور همزمان یک سطر داده را تغییر دهند. در این صورت تغییرات تراکنش اول توسط تراکنش دوم بازنویسی [7] خواهد شد.

Dirty read : این مشکل زمانی به وجود می آید که یک تراکنش خواننده حین اجرای یک تراکنش نویسنده، سطر داده ای که تراکنش نویسنده نوشته است را بخواند.

Non repeatable read : این مشکل زمانی به وجود می آید که در فاصله بین دو عمل خواندن در یک تراکنش خواننده، یک تراکنش نویسنده اقدام به تغییر سطر مورد نظر نماید. در نتیجه تراکنش خواننده مقادیر متفاوتی را برای سطر مورد نظر خواهد خواند.

Phantom read : این مشکل زمانی به وجود می آید که در فاصله بین دو عمل خواندن در یک تراکنش خواننده، یک تراکنش نویسنده اقدام به تغییر در جدول مورد نظر نماید. در نتیجه تعداد سطرهایی که تراکنش خواننده در دفعات مختلف خواهد خواند، متفاوت خواهد بود.

در پست های آتی الگوهای offline concurrency مورد بررسی قرار خواهد گرفت.



[1] - Enterprise
[2] - Concurrency
[3] - Transaction
[4] - Business transaction
[5] - Writer
[6] - Reader
[7] - Override 
۰ نظر موافقین ۰ مخالفین ۰
ابراهیم خانی