public Result Publish(IDateTimeProvider clock)
{
if (Status != CourseStatus.Draft)
{
return CourseErrors.AlreadyPublished;
}
if (_lessons.Count == 0)
{
return CourseErrors.CannotPublishWithoutLessons;
}
Status = CourseStatus.Published;
PublishedOn = clock.UtcNow;
return Result.Success();
}
در این طراحی: Handler دیگر مسئول دانستن قوانین نیست.
فقط متد Publish را فراخوانی میکند.
خود Entity تصمیم میگیرد آیا عملیات مجاز است یا خیر.
3️⃣ محافظت از Aggregate
برخی قوانین تنها به یک Entity محدود نیستند و چندین Entity درون یک Aggregate را درگیر میکنند.
در چنین شرایطی Aggregate Root مسئول حفظ Invariantها است.
مثال: Course منتشرشده باید حداقل یک Lesson داشته باشد.
پس از انتشار Course، حذف Lessonها مجاز نیست.
پیادهسازی صحیح:
public sealed class Course
{
private readonly List<Lesson> _lessons = [];
public IReadOnlyCollection<Lesson> Lessons =>
_lessons.AsReadOnly();
public Result RemoveLesson(LessonId id)
{
if (Status == CourseStatus.Published)
{
return CourseErrors
.CannotModifyPublishedLessons;
}
var lesson =
_lessons.FirstOrDefault(l => l.Id == id);
if (lesson is null)
{
return CourseErrors.LessonNotFound;
}
_lessons.Remove(lesson);
return Result.Success();
}
}
در این مدل:
هیچ مصرفکنندهای نمیتواند مستقیماً Collection را تغییر دهد.
تمام تغییرات باید از طریق Aggregate Root انجام شوند.
در نتیجه Invariantها همیشه حفظ خواهند شد.
🎯 اگر قانون بین چند Aggregate باشد چه؟
اینجا دیگر مسئولیت یک Aggregate تمام میشود.
اگر قانونی بین دو Aggregate مستقل وجود داشته باشد، معمولاً باید از Domain Event استفاده شود.
نه اینکه یک Aggregate مستقیماً وارد Aggregate دیگر شود.
این دقیقاً یکی از دلایل اصلی وجود Domain Eventها در DDD است.
🚀 نتیجه واقعی این رویکرد چیست؟
شاید همان سیستم را بتوان بهصورت Procedural نیز پیادهسازی کرد.
اما چیزی که به دست میآورید «اعتماد» است.
در یک سیستم Procedural:
تمام مصرفکنندگان مسئول حفظ قوانین هستند.
اما در یک Always Valid Model:
مسئولیت حفظ قوانین فقط بر عهده Domain Model است.
این تفاوت در طول زمان تأثیر بزرگی ایجاد میکند:
✅ ءValidationها از هم فاصله نمیگیرند.
✅ قوانین تکرار نمیشوند.
✅ ءEndpointهای جدید نمیتوانند بهاشتباه قوانین را دور بزنند.
✅ تستها سادهتر میشوند.
✅ ءDomain Model به مرکز واقعی قوانین کسبوکار تبدیل میشود.
📌 جمعبندی
ءInvariant قانونی است که باید در تمام طول عمر یک Object برقرار باشد.
بهترین محل برای اعمال این قوانین خود Domain Model است.
ءInvariantهای مربوط به ساخت Object در Factory و Constructor خصوصی قرار میگیرند.
ءInvariantهای مربوط به تغییر وضعیت در متدهای Domain پیادهسازی میشوند.
ءInvariantهای سطح Aggregate توسط Aggregate Root محافظت میشوند.
در مقابل، ممکن است مقداری از سادگی ظاهری کدهای Procedural را از دست بدهید.
اما در عوض مدلی خواهید داشت که همیشه معتبر است و میتوانید به آن اعتماد کنید.
و این دقیقاً یکی از مهمترین اهداف Domain-Driven Design است.