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

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

۵ مطلب با کلمه‌ی کلیدی «ddd» ثبت شده است

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
۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

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 

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