DDD
خلاصه مطلب یادگیری Domain Driven Design
ایده: سایت پرستاری از سالمندان و کودکان
کتابها: Learning.Domain-Driven.Design و Domain-Driven Design Tackling Complexity in the Heart of Software و Wrox.Patterns.Principles.and.Practices.of.Domain-Driven.Design
نقشه راه دوره (Syllabus)
ما این مسیر را طی خواهیم کرد (این لیست پویاست و بر اساس پیشرفت ما تنظیم میشود):
- فاز تحلیل استراتژیک (Strategic Design): (مهمترین بخش که اکثر برنامهنویسان از آن فرار میکنند)
- تحلیل دامنه (Problem Space vs Solution Space).
- زبان مشترک (Ubiquitous Language).
- مرزبندیها (Bounded Contexts) و نقشه متناظر (Context Mapping).
- فاز طراحی تاکتیکال (Tactical Design) در .NET:
- مدلسازی غنی (Rich Domain Model) در مقابل Anemic Model.
- Aggregates, Entities, Value Objects (با استفاده از
recordهای C#). - Domain Events و Event Storming.
- معماری و پیادهسازی:
- معماری پیازی/هزگونال (Onion/Hexagonal) برای ایزوله کردن دامنه.
- تکنیکهای Persistence با EF Core (بدون اینکه دامنه را آلوده کنیم).
- CQRS (در حدی که نیاز است، نه بیش از حد).
تحلیل اولیه پروژه: سیستم مدیریت مراقبت (Care Nexus)
من نمونههای خارجی مثل AlayaCare و ClearCare (برای سالمندان) و Brightwheel (برای کودکان) را بررسی کردم. چالش اصلی: تفاوت ظاهری بین “مراقبت سالمند” (دارو، علائم حیاتی) و “مراقبت کودک” (آموزش، خواب، تغذیه) زیاد است، اما هسته (Core) یکی است.
امکانات کلیدی برای آموزش ما:
- Care Plan (برنامه مراقبت): قلب تپنده سیستم. این فقط یک لیست کار نیست؛ یک سند پویاست که قوانین دارد.
- Scheduling & Matching: تخصیص پرستار/مربی به مددجو بر اساس مهارت و زمان.
- Daily Logs/Journals: ثبت وقایع روزانه (اینجا جایی است که Event Sourcing میدرخشد).
بخش ۱: تحلیل استراتژیک و زبان مشترک (Ubiquitous Language)
بیا شروع کنیم. اکثر برنامهنویسان وقتی ایدهی “سیستم مراقبت” را میشنوند، سریع میگویند: “خب، یک جدول Users داریم، یک جدول Roles، یک جدول Patients…“. این اشتباه مرگبار اول است. در DDD (طبق کتاب Evans)، ما با دیتابیس شروع نمیکنیم؛ ما با زبان شروع میکنیم.
مفهوم ۱: زبان مشترک (Ubiquitous Language)
زبان مشترک، لیست لغات نیست. یک مدل زنده است. اگر تو (برنامهنویس) بگویی SavePatientData() و کارشناس مراقبت بگوید “پرونده پذیرش را تکمیل کردم”، یعنی شما دو زبان متفاوت دارید. این ترجمه ذهنی باعث باگهای منطقی میشود.
در پروژه ما، کلمات بار حقوقی و معنایی دارند:
- آیا به کسی که سرویس میگیرد میگوییم
User؟ یاClient؟ یاPatient؟ یاCareRecipient؟ - آیا پرستار کودک و پرستار سالمند هر دو
Employeeهستند؟ یاCaregiver؟
مفهوم ۲: فضای مسئله در برابر فضای راه حل (Problem vs. Solution Space)
طبق کتاب Khononov، قبل از کدنویسی باید بفهمیم کجای کار هستیم:
- Subdomainهای عمومی (Generic): چیزهایی که میتوانیم بخریم (مثل سیستم احراز هویت، سیستم چت). ما نباید وقت زیادی روی کدنویسی اینها بگذاریم.
- Core Domain (دامنه اصلی): جایی که پول در میآورد و مزیت رقابتی ماست. در ایده تو، “مدیریت هوشمند برنامه مراقبت” هسته اصلی است.
📝 تمرین عملی اول (Challenging You)
من میخواهم اولین قدم را برداری، اما نه با کد. فرض کن من مدیر یک مرکز خدمات مراقبتی هستم و روبروی تو نشستهام.
سناریو: “ما برای هر سالمند یک قرارداد میبندیم. توی قرارداد مشخص میشه که چه روزهایی نیاز به ویزیت داره. هر ویزیت ممکنه شامل چک کردن فشار خون، دادن قرص یا فقط همصحبت شدن باشه. اگر پرستار نره سر قرار، ما باید سریع بفهمیم. برای بچهها هم مشابهه ولی اونجا پدر و مادر چک میکنن که بچه ناهار چی خورده.”
وظیفه تو: بر اساس سناریوی بالا، و با توجه به اینکه میخواهیم DDD کار کنیم، لیست واژگان کلیدی (Ubiquitous Language) و Entities احتمالی را برای من بنویس. نکته انحرافی: حواست باشد که در دام User و Role نیفتی. من میخواهم مفاهیم بیزینسی را ببینم.
بسیار عالی. ممنونم که سریع پاسخ دادی.
لیستی که دادی برای شروع یک سیستم CRUD (Create, Read, Update, Delete) معمولی خوب است، اما برای DDD و یک سیستم پیچیده که قرار است هم سالمندان و هم کودکان را مدیریت کند، ناقص و خطرناک است.
بیاییم نقد کنیم (Critique) و بعد اصلاح کنیم.
۱. نقد واژگان (The Vocabulary Trap)
اشتباه اول: استفاده از کلمه “سالمند” (Elderly) تو گفتی میخواهی سیستمی بسازی که هم برای “سالمندان” باشد و هم “کودکان”. اگر در کد ما کلاسی به نام Elderly داشته باشیم، فردا که بخواهیم بخش کودکان را اضافه کنیم، باید یک کلاس Child بسازیم؟ و پسفردا که “بیماران خاص” اضافه شدند چه؟ این باعث میشود منطق برنامه دو تکه شود (Duplication).
- راه حل DDD: ما باید به دنبال Abstraction (انتزاع) در زبان مشترک باشیم.
- کسی که مراقبت را دریافت میکند، چه کودک باشد چه سالمند، یک Care Recipient (گیرنده مراقبت) است.
- کسی که پول میدهد (پدر کودک یا فرزند سالمند)، Guardian (سرپرست) یا Payer است.
اشتباه دوم: “آیتمهای چک” (Check items) این خیلی فنی است. پرستار نمیگوید “آیتم چک را پاس کردم”. پرستار میگوید: “علائم حیاتی را ثبت کردم” یا “وعده غذایی را دادم” یا “فعالیت را انجام دادم”.
- راه حل: کلماتی مثل Activity یا Task یا Measurement بار معنایی دقیقتری دارند.
۲. نقد ساختاری: تلهی “مدل یگانه” (The Single Model Trap)
تو Contract (قرارداد) و Visit (ویزیت/مراقبت) را کنار هم در یک لیست آوردی. اریک ایوانز (Eric Evans) در کتاب آبی میگوید: “تلاش برای ساختن یک مدل واحد برای کل سازمان، محکوم به شکست است.”
چرا؟
- Contract: مربوط به پول، تاریخ شروع و پایان حقوقی، و امضاهاست. (مربوط به تیم فروش/پذیرش).
- Visit: مربوط به اینکه “آیا قرص ساعت ۲ داده شد؟”، “آیا پوشک بچه عوض شد؟” است. (مربوط به تیم عملیات/پرستاری).
اگر اینها را در یک User یا یک دیتابیس درهم بریزیم، کلاسی خواهیم داشت با ۵۰ تا فیلد که نصفشان همیشه Null هستند.
اصلاح مسیر: معرفی Bounded Contexts (مهمترین درس امروز)
قبل از اینکه کد بزنیم یا حتی انتیتیها را دقیق کنیم، باید “مرزها” (Boundaries) را مشخص کنیم. در DDD، ما سیستم را به Bounded Context (کانتکستهای کراندار) تقسیم میکنیم.
بیاییم سیستم “Care Nexus” تو را برش بزنیم. من پیشنهاد میکنم به جای یک مدل بزرگ، فعلاً به دو کانتکست اصلی فکر کنیم:
۱. کانتکست پذیرش و قراردادها (Onboarding & Contracting Context)
- هدف: ثبت نام، مسائل حقوقی، تعیین تعرفه.
- زبان: اینجا کلماتی مثل
Contract،Pricing،Terms،Signatory(امضاکننده) معنی دارند. - دیدگاه: اینجا “سالمند” برای ما یک “مشتری” است.
۲. کانتکست مدیریت مراقبت (Care Management Context) - (Core Domain ما)
- هدف: برنامهریزی روزانه، انجام کارها، گزارشدهی سلامتی.
- زبان: اینجا
CarePlan،Visit،Caregiver،VitalSign،Incident(حادثه) معنی دارند. - دیدگاه: اینجا قرارداد مهم نیست؛ اینجا مهم این است که آیا بیمار حالش خوب است یا نه.
📝 تمرین عملی دوم: پالایش زبان مشترک (Refining Ubiquitous Language)
حالا میخواهم با توجه به نقد بالا، دوباره فکر کنی. ما میخواهیم روی Core Domain (یعنی همان کانتکست مدیریت مراقبت) تمرکز کنیم.
فرض کن من “مدیر فنی” (CTO) هستم و از تو میخواهم مفاهیم زیر را در کانتکست Care Management تعریف کنی (نه دیتابیسی، بلکه توصیفی). برای هر کدام ۱ خط توضیح بده که در سیستم ما چه معنایی دارند:
- Care Recipient (به جای سالمند/کودک)
- Care Plan (برنامه مراقبت - این با قرارداد فرق دارد!)
- Caregiver (مراقب/پرستار)
- Shift یا Visit (کدام واژه برای سیستم ما بهتر است؟ تفاوتشان چیست؟)
نکته راهنما: در کتاب Patterns, Principles, and Practices of DDD، تاکید میشود که Care Plan معمولاً یک موجودیت زنده است که در طول زمان تغییر میکند (مثلاً دکتر دوز دارو را عوض میکند)، در حالی که قرارداد ثابت است.
پاسخهایت را بفرست تا ببینم آیا توانستیم از تفکر CRUD فاصله بگیریم و به تفکر Domain نزدیک شویم یا نه.
بگذار دو نکته کوچک را اصلاح کنم و بعد برویم سراغ آن سوال عالی که پرسیدی: Shift یا Visit؟
۱. اصلاح جزئی تعاریف (Fine-tuning)
- Care Recipient: گفتی “توسط یک پرستار”. اینجا باید دقیق باشیم. در مهدکودک ممکن است “مربی” باشد، در نگهداری سالمند ممکن است “همدم” (Companion) باشد که اصلا کار پرستاری پزشکی نمیکند. پس کلمه Caregiver (مراقب) را جایگزین “پرستار” میکنیم تا دامنه ما محدود نشود.
- Care Plan: تعریف عالی بود. این “قانون اساسی” مراقبت از آن فرد است.
۲. تحلیل عمیق: Shift یا Visit؟ (یک تصمیم معماری)
این سوالی است که اگر الان اشتباه جواب بدهیم، ۶ ماه دیگر کل دیتابیس باید refactor شود.
بیاییم تحلیل کنیم:
- Shift (شیفت): تمرکز روی کارمند است. “علی از ساعت ۸ تا ۱۶ سر کار است”. این بیشتر مربوط به منابع انسانی (HR) و حقوق و دستمزد است.
- Visit (ویزیت/ملاقات): تمرکز روی مددجو (Recipient) است. “مریم خانم امروز ساعت ۱۰ نیاز دارد کسی بیاید فشار خونش را چک کند”.
سوال کلیدی: سیستم ما قرار است چه مشکلی را حل کند؟ آیا میخواهیم حقوق کارمندان را حساب کنیم؟ (Shift) یا میخواهیم مطمئن شویم سالمند دارویش را خورده؟ (Visit).
تصمیم معماری من: از آنجایی که Core Domain ما “مدیریت مراقبت” (Care Management) است، واحد اصلی کار ما Visit است.
- یک Visit یعنی: یک بازه زمانی مشخص، که یک Caregiver پیش یک Recipient میرود تا کارهای مشخص شده در Care Plan را انجام دهد.
نکته: ما بعداً میتوانیم بگوییم “مجموع Visitهای امروز علی، تشکیل Shift او را میدهند”. اما برعکسش ممکن نیست. پس
Visitدقیقتر و اتمیکتر است.
۳. ورود به Tactical Design: مفهوم Aggregate (تجمع)
حالا که واژگان را داریم، باید اولین آجر معماری نرمافزار را بگذاریم. در DDD مهمترین مفهوم فنی Aggregate است.
تعریف به زبان ساده: یک Aggregate (تجمع)، مجموعهای از آبجکتهاست که با هم معنا دارند و با هم ذخیره یا بازیابی میشوند. یک آبجکت رئیس دارد که به آن Aggregate Root میگوییم. هیچکس حق ندارد اجزای داخلی را مستقیم دستکاری کند، همه باید با “رئیس” صحبت کنند.
مثال غلط (روش سنتی): تو یک جدول CarePlan داری و یک جدول CarePlanItems. در کد سرویس مستقیماً CarePlanItems را Insert میکنی. این غلط است چون ممکن است قانونی را نقض کنی (مثلاً: نباید دو تا آیتم تکراری باشد).
مثال درست (روش DDD): CarePlan رئیس است. متدی دارد به نام AddActivity(). تو به رئیس میگویی فعالیت اضافه کن، او چک میکند، اگر درست بود اضافه میکند.
📝 تمرین عملی سوم: طراحی اولین Aggregate
میخواهیم CarePlan را به عنوان اولین Aggregate Root طراحی کنیم. به کد فکر نکن. به دادهها و رفتارهای آن فکر کن.
یک Care Plan (برنامه مراقبت) چه چیزهایی داخلش دارد؟ (اجزای داخلی). من چند مورد را میگویم، تو تکمیلش کن و بگو “چرا” اینها باید داخل Plan باشند؟
- RecipientId: (مشخص است که این برنامه مال کیست).
- DateRange: (از کی تا کی معتبر است).
- …؟ (مهمترین بخش: لیست کارها. اسمش را چه بگذاریم؟)
- …؟ (آیا نیاز داریم بدانیم چه کسی این پلن را درست کرده؟)
سوال تمرین: ساختار داخلی CarePlan را برایم لیست کن. به نظرت لیست “کارهایی که باید انجام شود” (مثلاً: قرص قند، پیاده روی، آموزش نقاشی) اسمش چیست؟ Task؟ Goal؟ Activity؟ (با توجه به اینکه هم برای کودک است هم سالمند).