در این بخش، به مباحث Tactical Design و Model-Driven Development می پردازیم.
در بخش های قبل، در مورد اهمیت strategic design صحبت کردیم و به این پرداختیم که از طریق ارتباط متداوم با domain expertها، می توانیم در مورد domain به knowledge برسیم و ubiquitous language را شکل دهیم. همچنین می توانیم domain و مرزها را شناسایی کنیم و domainها را از نظر value ارزش گزاری کنیم. در مفهوم problem space قرار است که bounded countextها و relation بین آن ها را شناسایی کنیم.
در مباحث پیرامون tactical design، به نقطه ای رسیدیم که subdomainها و BCها را شناسایی کردیم و حالا قصد داریم ساختار BCها را بررسی کنیم.
در DDD، اصول و practiceهایی وجود دارد که در حوزه tactical design برای مدل کردن domain، راه حل هایی را ارائه می کند.
در تصویر زیر، یک state diagram را می بینیم که از لحظه بوجود آمدن یک domain object تا از بین رفتن آن را مدل می کند.
این کاری است که بصورت نرمال انجام می دهیم و احتمالا، develop در این حوزه را تجربه کرده ایم.
Domain-Model
مفهوم domain model، یک مدل نرم افزاری است که از روی problem domain ایجاد می کنیم. یک domain model قرار است نیازمندی هایی که در حوزه آن مدل وجود دارد را پاسخ دهد. این مدل در زبان های object oriented به شکل کلاس های مختلف ممکن است ایجاد شود. به مجموعه این کلاس ها که با هم کار می کنند اصطلاحا domain model گفته می شود.
در راستای مدل کردن در DDD، با اجزای سازنده domain model یا Building Blockها روبرو می شویم.
Entity
اولین جزئی که با آن روبرو هستیم، مفهوم Entity می باشد. entityها جزئی از domain model هستند که هویت آن ها از طریق identifier قابل شناسایی و track کردن است و ما می توانیم به آن ها point کنیم.
از این تعریف به این نتیجه می رسیم که اجزای دیگری در domain model وجود دارند که identifier ندارند و بنابراین از این طریق نیز شناسایی نمی شوند.
Value Object
زمانی که وارد طراحی می شویم، با اجزایی روبرو می شویم که این اجزا identifier ندارند و از لحاظ ماهیتی برای ما تفاوتی بین آن ها وجود ندارد. فرض کنید در domain model یک object به نام Money داریم که دارای ویژگی های Amount و Currency است. وقتی که moneyهای متفاوت را کنار هم می گذاریم، می بینیم که از طریق شناسه، مشخص نمی شوند و صرفا آبجتی هستند که یک صفت را در domain model توصیف می کنند. بنابراین مقدار آن ها برای ما اهمیت دارد.
اگر در domain خود، دو money با مقادیر 1000 دلار داشته باشیم، بنابراین این دو object با هم برابر هستند. وقتی به یک صفت مقداری نگاه می کنیم، آن صفت از طریق مقادیرش مشخص می شود.
در مورد ویژگی Address فکر کنیم. برای مثال، وقتی یک پیک موتوری یک مرسوله را می خواهد به مقصد ببرد، مقدار آن آدرس اهمیت پیدا می کند. اگر پیک، دو مرسوله داشته باشد که در یک آدرس باشند، از نظر پیک آن دو آدرس یکی هستند و با آن دو object به صورت یکسان رفتار می کند.
اگر این آدرس در فضای دیگری استفاده شود، ممکن است موضوع متفاوت شود. برای مثال در پست، کد پستی به طور مشخصی می تواند برای یک آدرس نقش identifier داشته باشد. بنابراین، بسته به نوع context تشخیص می دهیم که کدام مفهوم می تواند در نقش value object باشد. یک مفهوم در یک context ممکن است به شکل entity و در context دیگر به شکل value object وجود داشته باشد.
ملاک اینکه مفهومی را در یک مساله به عنوان value object در نظر بگیریم، این است که ماهیت آن را صرفا به شکل یک موضوع مقداری و صفتی از یک جزء دیگر در نظر بگیریم. بنابراین identifier و ماهیتی که بتوانیم به آن اشاره کنیم ندارد.
ویژگی های value object
از ویژگی های value object، همانطور که در تعریف هم اشاره شد، value objectها Identity-Less هستند.
مقایسه دو value object از طریق مقادیر آن انجام می شود. بنابراین Attribute-Based Equality از ویژگی دیگر value objectها است. اگر دو کالا با قیمت 20 و واحد دلار داشته باشیم، از طریق مقادیر attributeها، قاعدتا این دو کالا هم قیمت هستند.
زمانی که می گوییم در value object نیازی به track کردن life cycle نداریم، بنابراین می توانیم طراحی آن را Immutable کنیم. به این معنا که وقتی یک value object ساخته می شود، دیگر تغییر نمی کند و برای تغییر دادن آن، یک object جدید درست می کنیم.
در عین حال، value objectها می توانند Combinable باشند. به این معنی که می توانند با هم ترکیب شوند. البته هر value object ممکن است ماهیتا دارای این ویژگی نباشد. با توجه به immutable بودن، نتیجه ترکیب دو value object، یک object جدید خواهد شد.
از ویژگی دیگر value objectها، Self-Validation بودن آن ها است. به این معنی که خود رو validate می کنند و ما نمی توانیم یک value object را در یک state نادرست قرار دهیم.
به عنوان مثال می توانیم کلاس String ،DateTime و Timespanرا به عنوان یک value object در نظر بگیریم.
Invariant
یکی از موضوعاتی که در modeling برای ما اهمیت دارد، چک کردن مقادیر مختلفی است که به عنوان ورودی وارد domain ما می شوند. ما شرط هایی داریم که همیشه باید آن ها را کنترل کنیم و اگر این شرط ها را نقض کنیم، در مدل کردن business با مشکلات روبرو خواهیم شد. از این بابت که یک rule وجود دارد که نباید نقض شود و اگر آن را نقض کنیم، مدل ما کارایی نخواهد داشت.
به عنوان مثال، در فضای Auction یا حراجی، مقدار StartingPrice باید بزرگتر از صفر باشد. Bid به عنوان یک پیشنهاد جدید باید بزرگتر از StartingPrice باشد. Bidهای جدید باید از WinningBid بزرگتر باشند. بر روی یک Auction که تمام شده است نمی توانیم پیشنهاد بزنیم. همه این موارد، invariantهایی هستند که در این مساله شناسایی می شوند.
در DDD و در tactical modeling، تا زمانی که ما در مورد invariantها بر روی یک object صحبت می کنم، موضوع راحت است. در نظر بگیرید که DateTime یک invariant دارد که شما نمی توانید مقدار ماه را صفر وارد کنید. برای کنترل این موضوع، احتمالا در کلاس و در جایی که ماه قرار است مقدار دهی شود، آن را چک می کنیم.
مشکل از وجود یک گراف بزرگ بوجود می آید. در نظر داشته باشید، invariantهایی وجود دارند که روی یک دسته از objectها معنی پیدا می کنند. فرض کنید که یک Order داریم که لیستی از OrderLine دارد. اگر به عنوان یک invariant بگویند که مبلغ order نمی تواند از یک عدد مشخص بزرگتر باشد، این invariant، از مجموع قیمت هایی که روی itemهای این order وجود دارد تعیین می شود. یا بگویند کاربر نمی تواند کالای تکراری خریداری کند. بنابراین این invariant، از حوزه یک کلاس بیرون آمده و بین یک دسته از کلاس ها قرار گرفته است. قاعدتا order که لیستی از آیتم ها را درون خود دارد می تواند کنترل کند که از سقف تعیین شده عبور نکنیم.
اما در طراحی هایی که به طور معمول انجام می دهیم، ممکن است یک سرویس نوشته باشیم که order lineها را load می کند، تغییر می دهد و در دیتابیس ذخیره می کند. در این حالت که order وجود ندارد، کنترل این invariant سخت می شود.
بنابراین در مدلی که association یا رابطه های زیادی وجود دارد، invariant consistency کار پیچیده ای می شود. به این دلیل که invariantهایی وجود دارند که به دسته ای از objectها apply می شوند.
Aggregate
ایده Aggregate این است که یک گراف بزرگ، به اجزای autonomous یا مستقل شکسته شود. بعد از آن، قوانینی بر روی این اجزا قرار می گیرید که به کم شدن پیچیدگی ها کمک می کند.
در domain model، مجموعه ای از aggregateها داریم که هر کدام از آن ها دارای اجزای داخلی هستند.
برای اینکه بتوانیم کنترل خوبی روی invariantها داشته باشیم، آبجکت ها را جدا می کنیم و درون aggregate قرار می دهیم. برای مثال order و order line دارای consistency هستند و درون یک aggregate قرار می گیرند.
از اجزایی که برای قرار دادن در یک aggregate شناسایی می شوند، یکی را به عنوان aggregate root در نظر می گیریم که به عنوان نماینده اجزای آن aggregate شناسایی می شود.
به عنوان قانون اول، aggregate root باید یک entity باشد. وقتی که می خواهیم یک جزء را شناسایی کنیم باید دارای identifier باشد.
در قانون دوم، aggregate root تنها جزئی است که ما می توانیم از آن در یک object دیگر، یک reference نگهداری کنیم. بنابراین نمی توانیم شناسه اجزای داخلی یک aggregate را در جای دیگر نگهداری کنیم.
در قانون سوم، aggregate root باید global identity داشته باشد و داخل یک aggregate می توانیم local identity داشته باشیم. زمانی که در مورد global identity صحبت می کنیم یعنی id بین تمام aggregateها دارای مقدار unique است. و local identity یعنی اگر entity وجود داشته باشد، در سطح یک aggregate دارای شناسه unique باشد. برای مثال دو سینما که دارای شناسه های مختلفی هستند می تونند صندلی هایی با شناسه یکسان داشته باشند.
در قانون چهارم، از بیرون از یک aggregate نمی توانیم به اجزای داخلی آن اشاره کنیم.
در قانون پنجم، یک aggregate، معادل یک transaction boundary در نظر گرفته می شود. بنابراین aggregate را load می کنیم، تغییرات را اعمال می کنیم و ذخیره می کنیم.
قانون ششم اشاره به این دارد که aggregateها را فقط با شناسه، reference بزنیم و نمی توانیم یک object از آن نگهداری کنیم.
در قانون هفتم، امکان نگهداری id یک aggregate root در اجزای یک aggregate دیگر وجود دارد. برای مثال در order line می توانیم product id را نگهداری کنیم.
در قانون هشتم، حذف یک aggregate باعث حذف تمام اجزای آن می شود.
و در قانون نهم، وقتی که یک aggregate را commit می کنیم، تمام invariantها باید satisfy باشند. بنابراین هیچ وقت نمی گذاریم invariantها نقض شوند.
یکی از موضوعات مهمی که وجود دارد، مدل کردن صحیح invariantها می باشد. گاهی در طراحی ها، در شناسایی invariantها اشتباه صورت می گیرد و aggregateهای خیلی بزرگ طراحی می شوند. aggregateها باید حول محور invariantهای درست و کوچک طراحی شوند. اگر با load کردن یک aggregate تعداد زیادی object خوانده شود، با مشکلاتی روبرو خواهیم شد.
مدل aggregate base برای کنترل invariantها و کاهش پیچیدگی business طراحی می شود. در مورد کوئری زدن، در آینده با روش های مختلفی آشنا می شویم.