Isolating the Domain
@CSharpGeeksبخشی از نرمافزار که بهطور مشخص مسائل مربوط به domain را حل میکند، معمولاً فقط بخش کوچکی از کل سیستم نرمافزاری را تشکیل میدهد، هرچند اهمیت آن بهمراتب بیشتر از اندازهٔ آن است. برای بهکارگیری بهترین تفکر خود، لازم است بتوانیم به عناصر model نگاه کنیم و آنها را بهصورت یک system ببینیم. نباید مجبور شویم آنها را از میان ترکیب بسیار بزرگتری از اشیاء بیرون بکشیم، شبیه تلاش برای شناسایی صورتهای فلکی در آسمان شب. ما نیاز داریم domain objects را از سایر کارکردهای سیستم جدا (decouple) کنیم، تا از اشتباه گرفتن مفاهیم domain با مفاهیمی که صرفاً به فناوری نرمافزار مربوطاند جلوگیری کنیم، یا اصلاً domain را در میان انبوه سیستم از نظر گم نکنیم.
تکنیکهای پیشرفتهای برای این جداسازی پدید آمدهاند. این مسیر قبلاً بارها طی شده است، اما آنقدر برای بهکارگیری موفق اصول domain-modeling حیاتی است که باید بهاختصار، از دیدگاه domain-driven، دوباره مرور شود...
برای اینکه یک shipping application بتواند از عمل سادهٔ کاربر یعنی انتخاب مقصد یک cargo از میان فهرستی از شهرها پشتیبانی کند، باید کدی در برنامه وجود داشته باشد که:
(1) یک widget روی صفحه رسم کند،
(2) پایگاه داده را برای همهٔ شهرهای ممکن query کند،
(3) ورودی کاربر را تفسیر کرده و آن را validate کند،
(4) شهر انتخابشده را به cargo associate کند، و
(5) تغییر را در پایگاه داده commit کند.
تمام این کدها بخشی از یک برنامه هستند، اما فقط بخش کوچکی از آنها به کسبوکار shipping مربوط میشود.
برنامههای نرمافزاری شامل طراحی و کد برای انجام انواع مختلفی از کارها هستند. آنها ورودی کاربر را میپذیرند، business logic را اجرا میکنند، به پایگاههای داده دسترسی دارند، از طریق شبکهها ارتباط برقرار میکنند، اطلاعات را به کاربران نمایش میدهند، و غیره. بنابراین کدی که در هر عملکرد برنامه دخیل است میتواند قابلتوجه باشد.
در یک برنامهٔ object-oriented، کد مربوط به UI، پایگاه داده و سایر کدهای پشتیبان اغلب مستقیماً داخل business objects نوشته میشود. همچنین منطق کسبوکار اضافی در رفتار widgetهای UI و اسکریپتهای پایگاه داده embed میشود. این اتفاق معمولاً به این دلیل میافتد که در کوتاهمدت سادهترین راه برای کار راه انداختن سیستم است.
وقتی کد مرتبط با domain در میان چنین حجم بزرگی از کدهای دیگر پخش (diffused) میشود، دیدن و استدلال کردن دربارهٔ آن بسیار دشوار میگردد. تغییرات سطحی در UI ممکن است در واقع منطق کسبوکار را تغییر دهد. برای تغییر یک business rule ممکن است نیاز به ردگیری دقیق کد UI، کد پایگاه داده یا سایر عناصر برنامه باشد. پیادهسازی اشیای منسجم و model-driven عملاً غیرممکن میشود. Automated testing سخت و نامناسب میشود. با وجود تمام این فناوریها و منطقهایی که در هر فعالیت درگیر هستند، یک برنامه باید بسیار ساده نگه داشته شود، وگرنه فهم آن غیرممکن خواهد شد. 🧠⚙️
ایجاد برنامههایی که بتوانند وظایف بسیار پیچیده را مدیریت کنند، مستلزم separation of concerns است؛ بهطوری که امکان تمرکز بر بخشهای مختلف طراحی بهصورت جداگانه فراهم شود. در عین حال، با وجود این جداسازی، تعاملات پیچیدهٔ درون سیستم باید حفظ شوند.
راههای بسیار مختلفی برای تقسیم یک سیستم نرمافزاری وجود دارد، اما از طریق تجربه و عرف، صنعت به سمت LAYERED ARCHITECTURES همگرا شده است، و بهطور خاص به چند لایهٔ نسبتاً استاندارد. استعارهٔ لایهبندی آنقدر بهطور گسترده استفاده شده است که برای بیشتر توسعهدهندگان شهودی به نظر میرسد. بحثهای خوب زیادی دربارهٔ لایهبندی در منابع موجود است.اصل اساسی این است که هر عنصر از یک لایه فقط به عناصر دیگر در همان لایه یا به عناصر لایههای «زیرین» خود وابسته است. ارتباط به سمت بالا باید از طریق نوعی سازوکار غیرمستقیم انجام شود، که کمی بعد دربارهٔ آن صحبت خواهم کرد.ارزش لایهها در این است که هرکدام در یک جنبهٔ خاص از برنامهٔ کامپیوتری تخصص پیدا میکنند. این تخصص اجازه میدهد طراحیهای منسجمتری برای هر جنبه شکل بگیرد و تفسیر این طراحیها بسیار آسانتر شود. البته انتخاب لایههایی که مهمترین جنبههای منسجم طراحی را ایزوله کنند، حیاتی است. باز هم تجربه و عرف به نوعی همگرایی منجر شده است. اگرچه تنوعهای زیادی وجود دارد، بیشتر معماریهای موفق از نوعی نسخه از این چهار لایهٔ مفهومی استفاده میکنند. 🧠⚙️
User Interface (یا Presentation Layer)
مسئول نمایش اطلاعات به کاربر و تفسیر دستورات کاربر است. عامل خارجی گاهی ممکن است یک سیستم کامپیوتری دیگر باشد، نه یک کاربر انسانی.
Application Layer
وظایفی را تعریف میکند که نرمافزار قرار است انجام دهد و اشیای بیانی دامنه را هدایت میکند تا مسائل را حل کنند. وظایفی که این لایه مسئول آنهاست، برای کسبوکار معنادار هستند یا برای تعامل با لایههای کاربردی سیستمهای دیگر ضروریاند.
این لایه نازک نگه داشته میشود. شامل business rule یا دانش کسبوکار نیست، بلکه فقط وظایف را هماهنگ میکند و کار را به همکاریهای اشیای دامنه در لایهٔ پایینتر واگذار میکند. این لایه وضعیت (state)ای که بازتابدهندهٔ وضعیت کسبوکار باشد ندارد، اما میتواند وضعیتی داشته باشد که پیشرفت یک وظیفه را برای کاربر یا برنامه منعکس کند.
Domain Layer (یا Model Layer)
مسئول نمایش مفاهیم کسبوکار، اطلاعات مربوط به وضعیت کسبوکار، و business ruleهاست. وضعیتی که بازتابدهندهٔ وضعیت کسبوکار است در اینجا کنترل و استفاده میشود، هرچند جزئیات فنی ذخیرهسازی آن به لایهٔ زیرساخت واگذار میشود. این لایه قلب نرمافزارهای کسبوکاری است. ❤️
Infrastructure Layer
قابلیتهای فنی عمومی را فراهم میکند که از لایههای بالاتر پشتیبانی میکنند: ارسال پیام برای لایهٔ کاربردی، persistence برای دامنه، رسم widgetها برای UI، و غیره. لایهٔ زیرساخت همچنین ممکن است از الگوی تعامل بین این چهار لایه از طریق یک چارچوب معماری پشتیبانی کند. ⚙️
برخی پروژهها تمایز دقیقی بین لایهٔ رابط کاربر و لایهٔ کاربردی قائل نمیشوند. برخی دیگر چندین لایهٔ زیرساخت دارند. اما این جداسازی حیاتیِ لایهٔ دامنه است که MODEL-DRIVEN DESIGN را ممکن میسازد.
بنابراین:
یک برنامهٔ پیچیده را به لایهها تقسیم کنید. در هر لایه، طرحی توسعه دهید که منسجم باشد و فقط به لایههای زیرین وابسته باشد. از الگوهای معماری استاندارد پیروی کنید تا اتصال سست (loose coupling) با لایههای بالاتر فراهم شود. تمام کدی را که به domain model مربوط است در یک لایه متمرکز کنید و آن را از کد رابط کاربر، کاربردی و زیرساخت جدا سازید. اشیای دامنه، که از مسئولیت نمایش خود، ذخیرهسازی خود، مدیریت وظایف برنامه و غیره آزاد هستند، میتوانند بر بیان domain model متمرکز شوند. این کار اجازه میدهد مدل به اندازهای غنی و شفاف تکامل یابد که دانش اساسی کسبوکار را ثبت کند و آن را به کار بگیرد. 🧠
جدا کردن لایهٔ دامنه از لایههای زیرساخت و رابط کاربر، امکان طراحی بسیار تمیزتری برای هر لایه فراهم میکند. لایههای ایزوله هزینهٔ نگهداری بسیار کمتری دارند، زیرا معمولاً با نرخهای متفاوتی تکامل مییابند و به نیازهای متفاوتی پاسخ میدهند. این جداسازی همچنین در استقرار در یک سیستم توزیعشده کمک میکند، زیرا اجازه میدهد لایههای مختلف بهصورت انعطافپذیر روی سرورها یا کلاینتهای متفاوت قرار گیرند تا سربار ارتباطی به حداقل برسد و کارایی بهبود یابد. 🚀
ارتباط بین لایهها
تا اینجا بحث بر جداسازی لایهها و شیوهای که این تقسیمبندی طراحی هر بخش از برنامه را بهبود میدهد متمرکز بوده است، بهویژه لایهٔ دامنه. اما البته لایهها باید به هم متصل شوند. انجام این کار بدون از دست دادن مزیت جداسازی، انگیزهٔ پشت تعدادی از الگوها است.
لایهها قرار است loosely coupled باشند، با وابستگیهای طراحی فقط در یک جهت. لایههای بالاتر میتوانند بهصورت مستقیم عناصر لایههای پایینتر را استفاده یا دستکاری کنند، با فراخوانی public interfaceهای آنها، نگهداشتن reference به آنها (حداقل بهطور موقت)، و بهطور کلی با استفاده از روشهای متعارف تعامل. اما وقتی یک شیء در سطح پایینتر نیاز دارد به سمت بالا ارتباط برقرار کند (فراتر از پاسخ دادن به یک پرسوجوی مستقیم)، به یک سازوکار دیگر نیاز داریم که از الگوهای معماری برای ارتباط دادن لایهها استفاده میکند، مانند callbackها یا الگوی OBSERVER. 🔄📡
پدربزرگ الگوها برای متصل کردن UI به لایههای application و domain، الگوی MODEL - VIEW - CONTROLLER (MVC) است.سبکهای دیگری هم برای اتصال UI و application وجود دارد.
لایهٔ infrastructure معمولاً آغازگر عمل در لایهٔ domain نیست. چون «پایینتر» از لایهٔ domain قرار دارد، نباید هیچ دانش خاصی از دامنهای که در حال سرویسدهی به آن است داشته باشد. در واقع، چنین قابلیتهای فنیای اغلب بهصورت SERVICES ارائه میشوند. برای مثال، اگر یک application نیاز داشته باشد یک e-mail ارسال کند، میتوان یک interface برای ارسال پیام در لایهٔ infrastructure قرار داد و عناصر لایهٔ application درخواست ارسال پیام را بدهند.این decoupling مقدار بیشتری انعطافپذیری ایجاد میکند. interface ارسال پیام ممکن است به یک ارسالکنندهٔ e-mail، یک ارسالکنندهٔ فکس، یا هر چیز دیگری که در دسترس است متصل شود. اما فایدهٔ اصلی، سادهسازی لایهٔ application است و اینکه آن را بهطور محدود روی وظیفهٔ اصلیاش متمرکز نگه میدارد: دانستن اینکه چه زمانی باید پیام ارسال شود، بدون آنکه درگیر چگونگی انجام آن باشد.
لایهٔ دامنه جایی است که مدل در آن زندگی میکند
معماری لایهای (LAYERED ARCHITECTURE) امروزه در بیشتر سیستمها، با طرحهای لایهبندی مختلف، مورد استفاده قرار میگیرد. بسیاری از سبکهای توسعه نیز میتوانند از لایهبندی بهرهمند شوند. با این حال، domain-driven design فقط به وجود یک لایهٔ خاص نیاز دارد.
مدل دامنه مجموعهای از مفاهیم است. «لایهٔ دامنه» تجلی (manifestation) آن مدل و تمام عناصر طراحیِ مرتبطِ مستقیم با آن است. طراحی و پیادهسازی منطق کسبوکار، لایهٔ دامنه را تشکیل میدهد. در یک MODEL - DRIVEN DESIGN، سازههای نرمافزاریِ لایهٔ دامنه، مفاهیم مدل را بازتاب (mirror) میدهند.
دستیابی به این تطابق زمانی که منطق دامنه با سایر دغدغههای برنامه ترکیب شده باشد، عملی نیست. ایزولهکردن پیادهسازی دامنه، پیشنیاز domain-driven design است. 🔒🧠
اگر یک تیم کمتجربه با یک پروژهٔ ساده تصمیم بگیرد از MODEL - DRIVEN DESIGN همراه با LAYERED ARCHITECTURE استفاده کند، با یک منحنی یادگیری دشوار روبهرو خواهد شد. اعضای تیم باید فناوریهای جدید و پیچیدهای را یاد بگیرند و در فرایند یادگیری object modeling دستوپا بزنند (که حتی با کمک این کتاب هم کار چالشبرانگیزی است!). سربار مدیریت زیرساخت و لایهها باعث میشود حتی کارهای بسیار ساده زمان بیشتری ببرند.
بنابراین، وقتی شرایط چنین اقتضا میکند:
تمام منطق کسبوکار را داخل رابط کاربری قرار بدهید. برنامه را به توابع کوچک تقسیم کنید و آنها را بهصورت رابطهای کاربری جداگانه پیادهسازی کنید، بهطوری که قوانین کسبوکار درون آنها جاسازی شده باشد. از یک پایگاه دادهٔ رابطهای بهعنوان مخزن مشترک دادهها استفاده کنید. از خودکارترین ابزارهای ساخت UI و ابزارهای برنامهنویسی بصری موجود استفاده کنید. 🧩🖥️🗄️