دامین دریون دیزاین، انتیتی ها و ولیوآبجکتها
written on May 1, 2017در درون هر دامنه ایی، دامین مدل قلب یک نرم افزار طراحی شده به روش ددد می باشد جایی که تمام عملکرد و مدل اصلی نرم افزار در آن پیاده سازی میشود. به همین دلیل شناخت برخی الگوهای طراحی و مفاهیمی که در این لایه بسیار مورد استفاده قرار میگیرد از اهمیت بالایی برخوردار است. در این نوشتار به تشریح دو مورد از مهمترین این مفاهیم می پردازم که در پست های قبلی هم جسته و گریخته به آنها اشاره شده.
قسمت اول، دوم و سوم این نوشتار تلاش در شناساندن مفاهیم کلی ددد داشت که در این پست ادامه پیدا می کند.
درک انتیتی ها (Entity) و ولیو آبجکتها (Value Object)
یکی از مفاهیمی که تقریبا در تمام روش ها و فریمورک های مرتبط با طراحی نرم افزار حضور دارد انتیتی ها هستند. واقعیت اما این است که تفاوت معنی این مفهوم در هریک از این روش ها به شدت با دیگر روش ها متفاوت است. این تفاوت به ویژه در مورد مفهوم انتیتی در ددد خودش را نشان می دهد. در این روش انتیتی ها مدل کننده و نماینده ی مفاهیمی هستند که دارای هویت می باشند. در حالی که ولیو آبجکت ها صرفا حامل ارزش هستند و هویت (مستقل) ندارند. درک تفاوت بین انتیتی ها در مقابل ولیو آبجکت ها بسیار مهم است و تصمیم گیری های زیادی را ساده خواهد کرد. برای درک این تفاوت این مثال را در نظر بگیرید که یک شخص خاص، دارای یک هویت است. خیلی چیزها در مورد یک شخص به مرور زمان تغییر کرده یا به کل جایگزین خواهد شد، مانند لباس او، سن، چهره یا حتی سلولهای تشکیل دهنده ی بدنش. اما در نهایت آن شخص همواره همان هویت اولیه خود می باشد و با هیچ شخص دیگری قابل جایگزینی نیست. در نقطه ی مقابل یک اسکناس پول را در نظر بگیرید. این اسکناس (برای اکثر کاربردها) دارای هویت نیست و تنها نماینده ی یک ارزش می باشد. علت اینکه این اسکناس دارای هویت نیست این است که می توان آن را با هر اسکناس دیگر یا حتی مجموعه ایی از اسکناس های دیگر که در نهایت همان ارزش را دارند جایگزین کرد. مثال شخص مفهوم یک انتیتی و مثال اسکناس مفهوم یک ولیو آبجکت را روشن می کند. یک انتیتی هرچند هم از اجزای قابل تغییر شامل انتیتی های دیگر یا ولیو آبجکتها تشکیل شده باشد. اما در نهایت هویت خود را حفظ می کند. یک ولیو آبجکت را اما می توان همیشه تعویض کرد، چه با حفظ ارزش و چه با تغییر آن.
یک تفاوت عمده ی دیگر انتیتی ها و ولیوآبجکتها در مقایسه ی اشیا با هم آشکار می شود. دو شی از نوع ولیوآبجکت زمانی با هم مساوی هستند که تمام خصوصیات (Properties) آنها با هم مساوی باشند. در حالی که به عقیده ی بسیاری، دو شی از نوع انتیتی زمانی با هم مساوی هستند که عامل شناسایی هویت آنها (معمولا متشکل از یک یا ترکیبی از چند خصوصیت شی) با هم مساوی باشند و در نظر گرفتن همه ی خصوصیات لازم نیست. هرچند در این مورد لازم است تعریف دقیقتری از تساوی ارایه شود که ممکن است از دامنه ایی به دامنه ی دیگر یا حتی از تیمی به تیم دیگر تفاوت داشته باشد.
نکته ی بسیار مهمی که در شناسایی انتیتی ها از ولیو آبجکت ها باید مدنظر قرار بگیرد، فضا و زمینه ی کابردی، یا همان زبان فراگیری است که یک دامنه ی خاص براساس آن طراحی می شود. به عنوان مثال در یک دامنه ی طراحی شده برای یک سیستم بانکی، یک اسکناس به احتمال بسیار زیاد دارای هویت خواهد بود اما در بسیاری سیستم های دیگر چنین نیست. اینجا باز جای تاکید دارد که همان طور که گفته شد، شناسایی زبان فراگیر مهمترین و اولین قدم در طراحی یک دامنه می باشد.
مدل کردن ولیوآبجکتها
در یک طراحی مناسب، انتیتی ها و ولیو آبجکتها از رفتار مفاهیمی که در واقعیت مدل می کنند طبعیت می کنند (=دامین دریون دیزاین) به این مفهوم که ولیو آبجکتها هرگز بازاستفاده نمی شوند و هرگونه عملیاتی روی آنها با نتیجه ی ایجاد اشیا جدید پایان می یابد. به این مدل اشیا به اصطلاح immutable یعنی غیر قابل تغییر گفته می شود. کسانی که با روش های برنامه نویسی فانکشنال (Functional Programming) آشنایی داشته باشند، این نوع برخورد با اشیا را می شناسند. هرچند در ددد تنها با ولیو آبجکتها این طور رفتار میشود.
به عنوان نمونه کد جاوای زیر مدل کننده ی یک مبلغ پول را در نظر بگیرید:
public class MonetaryAmount {
private double amount;
private String currency;
public MonetaryAmount(double amount, String currency) {
this.amount = amount;
this.currency = currency;
}
public double getAmount(){ return amount; }
public String getCurrency(){ return currency; }
}
در اینجا لازم می دانم به چند نکته اشاره کنم: اولا در طول این نوشتار، زبان جاوا به عنوان یک زبان مرجع با قدرت بیان بالا که تقریبا برای هر کسی با تجربه ی برنامه نویسی قابل خواندن می باشد مورد استفاده قرار میگیرد. ثانیا هرچند شخصا مدت مدیدی است که با تکنولوژی های شرکتی (Java Enterprise Edition) آشنایی دارم اما برخی از این استانداردها و تکنولوژی ها را به شدت مورد انتقاد قرار می دهم. نمونه ی آن Java Bean می باشد که در صورت آشنایی خواننده متوجه می شود در کلاس بالا رعایت نشده است. در نهایت باز هم یادآوری می کنم که آنچه که در ددد بسیار مهم است این نکته است: استقلال از هرگونه فریمورک و استاندارد اضافه و تنها تکیه بر ویژگی های پایه ایی زبان آنهم تا زمانی که با اصول ددد مغایرت نداشته باشند.
فرض کنیم در دامنه ی در حال طراحی که کلاس بالا به آن تعلق دارد، عملکردی لازم است که طبق آن «هر مبلغ پول میتواند با مبلغی دیگر جمع شود، به شرطی که هردو واحد پولی یکسان داشته باشند». این عملکرد را می توان در کلاس بالا به صورت زیر پیاده سازی کرد (به خاطر داشته باشید، در ددد ساختارها فقط حامل اطلاعات نیستند و شامل عملکردها نیز می باشند):
public class MonetaryAmount {
… // rest of class as implemented above
public MonetaryAmount addUp(MonetaryAmount otherMonAmount) {
if(!this.getCurrency.equals(otherMonAmount.getCurrency())) {
throw new RuntimeException(“not matching currency while adding two monetary amounts”);
}
return new MonetaryAmount(
this.getAmount() + otherMonAmount.getAmount(),
this.getCurrency());
}
}
همانطور که دیده میشود در این کد پس از انجام تست های لازم اصل عملیات اضافه کردن مبلغ انجام میشود. مهمترین نکته این است که نتیجه یک شی جدید است و نه خود شی اصلی و نه شی دیگر تغییر نمی کنند. از آنجایی که MonetaryAmount نماینده یک ولیوآبجکت می باشد، این رفتار دقیقا باید رعایت شود. هر شی دیگری که یک مبلغ پولی را شامل میشود، در صورت تمایل برای تغییر آن فقط و فقط یک راه دارد و آن هم جایگزین کردن آن با یک شی جدید می باشد.
مدل کردن انتیتی ها
در نقطه ی مقابل ولیوآبجکتها، تغییراتی که در اثر اعمال مختلف روی یک انتیتی انجام می شود باعث تغییر در خود آن شی می شود، یعنی شی ماهیت خود را حفظ می کند. بنابراین یک انتیتی نیاز به خصوصیت یا ترکیبی از خصوصیات دارد که با استفاده از آن بتوان هویت آن را شناسایی کرد. برخلاف بسیاری روش های طراحی نرمافزار که این عامل شناسایی هویت با الهام از بانک اطلاعاتی یا هر روش ذخیره سازی اطلاعات در نظر گرفته میشود، در ددد این عامل شناسایی از واقعیت الهام گرفته میشود و مشکلات و جزییات تکنیکی دست و پاگیر کاربر نهایی نخواهد شد. به عنوان مثال در یک دامنه، عامل شناسایی هویت مدلی که برای یک شخص در نظر گرفته شده ممکن است ترکیبی از نام خانوادگی، تاریخ و محل تولد باشد. در نهایت عامل شناسایی هرچه که باشد همیشه باید امکان تغییر یافتن آن را هم در نظر داشت (ناشی از اشتباهات انسانی و …) که این خود دلیل دیگری از استقلال این عامل هویت از مسایل تکنینی مثل آیدی های دیتابیس و … می باشد.
حالا با هم مدل ساده شده ی یک کالا را در نظر می گیریم:
public class Commodity {
private String upc; // Universal Product Code ~> identity of a commodity
private MonetaryAmount price;
private int stockCount;
public Commodity(String upc, MonetaryAmount price, int stockCount) {
this.upc = upc;
this.price = price;
this.stockCount = stockCount;
}
… //getters for all three properties
}
فرض کنیم که قرار باشد در این کلاس عملیاتی انجام شود که در نتیجه ی آن قیمت کالا تغییر کند، مثلا افزایش با یک درصد خاص. این عملیات می تواند به صورت زیر پیاده سازی شود:
public class Commodity {
… //rest of implementation as above
public void increasePriceByPercent(double increase) {
… // all the logic for validating the increase amount
this.price = new MonetaryAmount(
(this.price.getAmount() * (100 + increase)) / 100,
this.price.getCurrency());
}
}
همانطور که مشاهده می شود خود انتیتی ثابت باقی می ماند (هویت ثابت) اما شی مدل کننده ی مبلغ با یک شی کاملا جدید جایگزین می شود.
دلیل استفاده از ولیوآبجکت ها
استفاده از انتیتی ها اجتناب ناپذیر است. به سادگی می توان اثبات کرد سیستم نرمافزاری که دارای اشیایی با هویت ثابت نباشد، قابل استفاده نخواهد بود. اما آنچه که جای سوال دارد دلیل لزوم استفاده از ولیوآبجکتها می باشد.
یکی از موارد مهم و مشترک بین تقریبا تمام روش های مهندسی نرمافزار، جلوگیری از تکرار میباشد. بنابراین به محض اینکه در بخشی از یک نرمافزار مجموعه ایی از اطلاعات و عملکردها در کنار هم یک واحد معنایی را ایجاد کنند که بتواند بیش از یکبار مورد استفاده قرار بگیرد، نیاز به استخراج آنها و ساختن یک مدل مستقل از آنها می باشد. در مثال بالا، مبلغ می توانست به عنوان بخشی از مدل کالا پیاده سازی شود، بدون اینکه نیازی به یک ساختار مستقل داشته باشد. اما چنانچه در دامنه ی مزبور ساختار دیگری هم نیاز به دربرگرفتن مبلغ و تمام عملیاتهای قابل انجام روی آن را داشته باشد، مبلغ باید به صورت یک ساختار مستقل (مثل بالا) مدل شود.
همچنین باید یادآوری کرد که در ددد واقعیت های دنیای خارج باید به بهترین و شبیهترین شکل در نرمافزار مدل شوند. همانطور که در دنیای واقعی بسیاری اشیا در خیلی از دامنه های کاربرد تنها حامل های ارزش و بدون هویت ثابت هستند، به همین دلیل در نرمافزار هم به مدل مشابهی نیاز است. هرچند تمام این مدلها می توانند به صورت انتیتی ها نیز مدل شوند، اما تجربه ثابت می کند که کار کردن با اشیا غیر قابل تغییر به مراتب ساده تر است. یکی از بزرگترین مزیت های استفاده از چنین اشیایی را می توان در سیستم های پردازش موازی یا با دسترسی همزمان بالا مشاهده کرد. از آنجایی که اشیای غیرقابل تغییر مانند ولیوآبجکتها هرگز استفاده مجدد نمی شوند، انواع مشکلاتی که در سیستم های موازی وجود دارد (تغییر همزمان و …) در مورد آنها صادق نیست.
در بخش بعدی نوشتار به معرفی مفاهیم بیشتری از لایه ی دامین مدل می پردازم.
Comments
Leave a comment
Previous comments
وحید said on February 22, 2022:
با تشکر از مطلب بسیار خوبتون یک نکته رو متوجه نشدم در انتهای نوشتار آورده اید که "یکی از موارد مهم و مشترک بین تقریبا تمام روش های مهندسی نرمافزار، جلوگیری از تکرار میباشد. بنابراین به محض اینکه در بخشی از یک نرمافزار مجموعه ایی از اطلاعات و عملکردها در کنار هم یک واحد معنایی را ایجاد کنند که بتواند 'بیش از یکبار مورد استفاده قرار بگیرد'، نیاز به استخراج آنها و ساختن یک مدل مستقل از آنها می باشد...." و در ادامه در پاراگراف آخر اینطور نوشتید که "از آنجایی که اشیای غیرقابل تغییر مانند ولیوآبجکتها هرگز استفاده مجدد نمی شوند...." من یکم گیج شدم که الان ولیو ابجکت ها مورد استفاده مجدد قرار میگیرند یا نه.
نقوی said on February 22, 2022:
با سلام. در این دو موردی که اشاره کردید تفاوت بین تکرار در کد و تکرار در زمان اجرا را باید در نظر بگیرید. تلاش ما کاهش تکرار در کد تا حد ممکن (با درنظر گرفتن فاکتورهای دیگر) می باشد. تکرار در زمان اجرا (به معنای ایجاد تعداد زیادی اشیا از یک نوع) صرفا زمانی لزوم پیدا میکند که پرفورمنس را به شدت تحت تاثیر قرار میدهد. البته این بهبود پرفورمنس اصلا چیزی نیست که شما در مرحلهی اول نیاز به پرداختن به اون رو داشته باشید.