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

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

۳ مطلب در تیر ۱۳۹۵ ثبت شده است

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 

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