در این مقاله به بررسی مفهوم کلیدی Aggregate می پردازیم. اما قبل از پرداختن به این مفهوم لازم است تا با مفهوم دیگری به نام Invariant آشنا شویم .
Invariant چیست؟
Invariant (به فارسی : ثابت، نامتغیر) Business Rule یا قانونی از دامین است که همیشه باید رعایت شود. در صورتی که Invariant های یک شیء رعایت نشوند، آن شیء در وضعیت نامعتبر (Invalid) قرار می گیرد. برای درک بهتر به مثال ساده ی زیر توجه کنید :
هر سفارش (Order) از تعدادی جزء سفارش (OrderItem) تشکیل شده است و هر جزء سفارش یک قیمت (Price) مشخص دارد. قیمت واقعی یک سفارش از مجموع قیمت اجزاء آن محاسبه می شود. حال فرض کنید قیمت یک سفارش هرگز نباید بیشتر از N شود. این شرط یک Invariant بوده و در صورتی که صحیح نباشد، شیء سفارش در وضعیت نامعتبر قرار می گیرد.
تعریف Aggregate
هنگامی که مدل نرم افزار پیچیده و رابطه ی بین کلاس ها زیاد می شود، کنترل تغییرات بر روی اشیاء و اطمینان از صحت Invariant ها مشکل می شود زیرا اغلب اوقات کنترل Invariant ها بین مجموعه ای از اشیاء صورت می گیرد. برای حل مشکلات این چنینی مفهومی به نام Aggregate ایجاد شده است که Eric Evans در کتاب Domain Driven Design آن را اینگونه تعریف می کند:
Aggregate به مجموعه ای از اشیاء مرتبط گفته می شود که جهت کنترل تغییرات، به عنوان یک واحد در نظر گرفته می شوند.
هر Aggregate دارای یک ریشه (Aggregate Root) و یک مرز (Aggregate Boundary) است. مرز Aggregate مشخص می کند که چیزهایی در آن وجود دارند. Aggregate Root نیز یکی از Entity های داخل Aggregate می باشد و تنها شیء می باشد که اشیاء بیرونی می توانند به آن دسترسی داشته و یا به آن اشاره کنند.
- قوانین پیاده سازی Aggregate ها
- Aggregate Root دارای ID منحصر به فرد در سطح نرم افزار بوده اما موجودیت های دیگر در Aggregate، شناسه های محنصر به فرد در سطح همان Aggregate دارند.
- Aggregate Root مسئول چک کردن Invariant ها می باشد.
- اشیاء خارج از مرز Aggregate نمی توانند به شیء غیر از Aggregate Root دسترسی داشته و یا به آن اشاره کنند.
- (با توجه به قانون قبل) تنها Aggregate Root می تواند مستقیما توسط Query از دیتابیس بازیابی شود. به عبارت دیگر به ازای هر Aggregate Root یک Repository تعریف می شود و اشیاء دیگر مستقیما قابل بازیابی نخواهند بود و باید توسط Aggregate Root مربوطه بازیابی شوند. همچنین دسترسی مستقیم برای ذخیره و یا ویرایش یک موجودیت داخل یک Aggregate بی معنی است و Transaction های دیتابیسی در سطح یک Aggregate معنی دار می باشند.
- اشیاء داخل Aggregate (اعم از خود Aggregate Root) می توانند به Aggregate Root های دیگر اشاره کنند. این اشاره کردن توسط Identity انجام می شود.
پیاده سازی مثال Order
در مثال مطرح شده در اول مقاله، ۳ شیء Order، OrderItem و Price داخل یک Aggregate تعریف می شوند. Order به عنوان Aggregate Root تعریف شده و مسئولیت کنترل Invariant مطرح شده را دارد. پیاده سازی فوق صرفا جهت یادگیری مفاهیم بوده و با پیاده سازی یک نرم افزار واقعی و عملیاتی فاصله ی زیادی دارد. بسیاری از جزئیات در این مثال لحاظ نشده است، فرضا واضح است که هر OrderItem باید به یک محصول و یا کالا متصل باشد.
کلاس Price که در قالب یک Value Object تعریف شده است :
کلاس OrderItem نیز در قالب یک Value Object تعریف شده است :
کلاس Order نیز به عنوان یک Entity و همچنین Aggregate Root تعریف می شود.
همانطور که ملاحظه می کنید Order مسئولیت چک کردن Invariant ها (بیشتر نشدن قیمت سفارش از ۱۰۰۰) را بر عهده دارد و با استفاده از Encapsulation از صحت آنها اطمینان پیدا می کند.
جمع بندی
Entity ها و Value Object های مرتبط با توجه به Invariant هایشان در قالب Aggregate ها دسته بندی می شوند. یک Entity به عنوان Aggregate Root تعریف شده و مسئولیت دسترسی به اشیاء داخل Aggregate و همچنین کنترل Invariant ها را برعهده دارد. اشیاء بیرونی تنها می توانند به Aggregate Root دسترسی داشته باشند، و از طریق آن اشیا دیگر داخل Aggregate را تغییر دهند. این مکانیزم باعث می شود تا صحت Invariant ها همیشه رعایت شده و فرآیند های نرم افزار به درستی پیاده سازی شوند.