به هنگام طراحی مدل مدل دامنه [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