فصل ۱۳ — طراحی دامنهمحور در دنیای واقعی
تا اینجا ابزارهای طراحی دامنهمحور را برای تحلیل دامنههای کسبوکار، اشتراک دانش و گرفتن تصمیمهای طراحی راهبردی و تاکتیکی بررسی کردهایم. حال تصور کنید قرار است این دانش را در عمل به کار بگیرید. فرض کنید روی یک پروژهٔ Greenfield کار میکنید؛ همهٔ همکاران شما درک عمیقی از DDD دارند و از همان آغاز بهترین تلاش خود را برای طراحی مدلهای مؤثر انجام میدهند و با تعهد کامل از Ubiquitous Language استفاده میکنند. با پیشرفت پروژه، مرزهای Bounded Context صریح و مؤثرند و از مدلهای دامنهٔ کسبوکار محافظت میکنند. در نهایت، چون همهٔ تصمیمهای طراحی تاکتیکی با راهبرد کسبوکار همراستا هستند، Codebase همیشه در وضعیت عالی است: زبان فراگیر را «صحبت میکند» و الگوهایی را پیاده میکند که با پیچیدگی مدل سازگارند. حالا بیدار شوید.
احتمال تجربهٔ چنین شرایط آزمایشگاهی تقریباً به اندازهٔ برنده شدن در بختآزمایی است. ممکن است، اما بعید است. متأسفانه بسیاری تصور میکنند DDD فقط در پروژههای Greenfield و شرایط ایدهآلی قابل استفاده است که همهٔ اعضای تیم «کمربند سیاه DDD» دارند. نکتهٔ طنزآمیز این است که پروژههایی که بیشترین سود را از DDD میبرند اغلب پروژههای Brownfield هستند: پروژههایی که قبلاً ارزش تجاری خود را ثابت کردهاند و اکنون برای مقابله با بدهی فنی و آنتروپی طراحی انباشتهشده به بازنگری نیاز دارند. اتفاقاً بخش بزرگی از عمر حرفهای مهندسان نرمافزار نیز در همین Codebaseهای Brownfield، Legacy و Big Ball of Mud میگذرد.
برداشت نادرست رایج دیگری این است که DDD یک پیشنهاد «همه یا هیچ» است: یا باید همهٔ ابزارهای روش را به کار ببرید، یا کاری که انجام میدهید DDD نیست. این تصور غلط است. فهم همهٔ مفاهیم ممکن است طاقتفرسا به نظر برسد، چه رسد به پیادهسازی همهٔ آنها در عمل. خوشبختانه برای بهرهمند شدن از DDD لازم نیست همهٔ الگوها و شیوهها را استفاده کنید. این موضوع مخصوصاً در پروژههای Brownfield درست است، جایی که معرفی همهٔ الگوها در یک بازهٔ زمانی معقول عملاً ناممکن است.
در این فصل راهبردهایی برای استفاده از ابزارها و الگوهای DDD در دنیای واقعی خواهید آموخت، از جمله در پروژههای Brownfield و محیطهایی که با شرایط ایدهآل فاصله دارند.
تحلیل راهبردی
بر اساس ترتیب بررسی الگوها و شیوههای DDD در این کتاب، بهترین نقطهٔ شروع برای معرفی DDD در یک سازمان این است که زمان صرف درک راهبرد کسبوکار سازمان و وضعیت فعلی معماری سیستمهای آن کنید.
دامنهٔ کسبوکار را بشناسید
نخست دامنهٔ کسبوکار شرکت را شناسایی کنید:
- دامنه یا دامنههای کسبوکار سازمان چیست؟
- مشتریان آن چه کسانیاند؟
- سازمان چه سرویس یا ارزشی به مشتریان ارائه میکند؟
- سازمان با چه شرکتها یا محصولاتی رقابت میکند؟
پاسخ به این پرسشها نمایی از بالا به اهداف کلان شرکت میدهد. سپس روی دامنه Zoom کنید و اجزای سازندهٔ کسبوکار را پیدا کنید که سازمان برای رسیدن به اهداف کلان از آنها استفاده میکند: Subdomainها.
یک قاعدهٔ اکتشافی اولیهٔ مناسب، نمودار سازمانی شرکت است: دپارتمانها و واحدهای سازمانی دیگر. بررسی کنید این واحدها چگونه با یکدیگر همکاری میکنند تا شرکت بتواند در دامنهٔ کسبوکار خود رقابت کند.
همچنین به دنبال نشانههای انواع مشخص Subdomain باشید.
Core Subdomainها
برای شناسایی Core Subdomainها، به دنبال چیزهایی باشید که شرکت را از رقیبان متمایز میکنند:
- آیا شرکت «سس مخفی» دارد که رقیبان ندارند؟ برای مثال مالکیت فکری مانند Patent یا Algorithmهای طراحیشده در داخل سازمان؟
- به یاد داشته باشید مزیت رقابتی و در نتیجه Core Subdomain لزوماً فنی نیست. آیا شرکت مزیت رقابتی غیرفنی دارد؟ برای مثال توانایی جذب نیروی سطحبالا، تولید طراحی هنری منحصربهفرد و موارد مشابه؟
یک قاعدهٔ اکتشافی قدرتمند اما ناخوشایند برای یافتن Core Subdomainها، شناسایی بدترین مؤلفههای نرمافزاری از نظر طراحی است: همان Big Ball of Mudهایی که همهٔ مهندسان از آنها متنفرند، اما کسبوکار به دلیل ریسک تجاری حاضر نیست آنها را از صفر بازنویسی کند. نکته این است که چنین Legacy Systemی را نمیتوان با یک محصول آماده جایگزین کرد—اگر میشد، Generic Subdomain بود—و هر تغییر در آن با ریسک تجاری همراه است.
Generic Subdomainها
برای شناسایی Generic Subdomainها، به دنبال راهحلهای آماده، سرویسهای Subscription یا یکپارچهسازی نرمافزار متنباز بگردید. همانطور که در فصل ۱ آموختید، همان راهحلهای آماده باید برای شرکتهای رقیب نیز در دسترس باشند و استفادهٔ آنها از همان محصول نباید اثر تجاری مستقیمی بر شرکت شما داشته باشد.
Supporting Subdomainها
برای Supporting Subdomainها، به دنبال مؤلفههای نرمافزاری باقیمانده باشید که با راهحل آماده قابل جایگزینی نیستند اما مستقیماً مزیت رقابتی ایجاد نمیکنند. اگر کد این بخشها وضعیت نامناسبی داشته باشد، معمولاً واکنش احساسی مهندسان نرمافزار کمتر است، زیرا این بخشها با فرکانس کمی تغییر میکنند؛ بنابراین اثر طراحی نامناسب در آنها به شدت Core Subdomainها نیست.
لازم نیست همهٔ Core Subdomainها را شناسایی کنید. حتی برای یک شرکت متوسط چنین کاری عملی یا ممکن نیست. ساختار کلی را شناسایی کنید، اما توجه بیشتری به Subdomainهایی بدهید که بیشترین ارتباط را با سیستم نرمافزاری فعلی شما دارند.
طراحی فعلی را بررسی کنید
وقتی با Problem Domain آشنا شدید، میتوانید Solution و تصمیمهای طراحی آن را بررسی کنید. ابتدا از مؤلفههای سطح بالا شروع کنید. این مؤلفهها لزوماً Bounded Context به معنای DDD نیستند، بلکه Boundaryهایی هستند که دامنهٔ کسبوکار را به Subsystemها تجزیه کردهاند.
ویژگی مهمی که باید جستوجو کنید Lifecycleهای مستقل مؤلفهها است. حتی اگر Subsystemها در یک Source Control Repository مشترک (Mono-repo) نگهداری شوند یا همهٔ مؤلفهها در یک Monolithic Codebase باشند، بررسی کنید کدام بخشها میتوانند مستقل از بقیه تکامل یابند، Test شوند و Deploy شوند.
طراحی تاکتیکی را ارزیابی کنید
برای هر مؤلفهٔ سطح بالا، بررسی کنید چه Business Subdomainهایی در آن قرار گرفتهاند و چه تصمیمهای فنی گرفته شدهاند: برای پیادهسازی منطق کسبوکار و تعریف معماری مؤلفه از چه Patternهایی استفاده شده است؟
آیا Solution با پیچیدگی Problem تناسب دارد؟ آیا ناحیههایی وجود دارند که به Patternهای پیچیدهتر نیاز دارند؟ برعکس، آیا Subdomainهایی هستند که میتوان در آنها سادهتر عمل کرد یا از راهحل آماده استفاده کرد؟ این اطلاعات را برای گرفتن تصمیمهای راهبردی و تاکتیکی بهتر به کار ببرید.
طراحی راهبردی را ارزیابی کنید
با دانش حاصل از مؤلفههای سطح بالا، Context Map طراحی فعلی را ترسیم کنید؛ طوری که انگار این مؤلفهها Bounded Context هستند. رابطهٔ میان مؤلفهها را بر اساس الگوهای یکپارچهسازی Bounded Context شناسایی و ثبت کنید.
سپس Context Map حاصل را از دید DDD تحلیل کنید. آیا تصمیمهای راهبردی نامناسب وجود دارند؟ برای مثال:
- چند تیم روی یک مؤلفهٔ سطح بالا کار میکنند؛
- Core Subdomain بهصورت تکراری در چند جا پیادهسازی شده است؛
- Core Subdomain توسط شرکت برونسپاریشده پیاده شده است؛
- اصطکاک ناشی از Integrationهایی که مرتب شکست میخورند وجود دارد؛
- مدلهای نامناسب از سرویسهای خارجی و سیستمهای Legacy به بخشهای دیگر سرایت کردهاند.
این بینشها نقطهٔ شروع خوبی برای برنامهریزی راهبرد Modernization طراحی هستند. اما پیش از آن، با توجه به درک عمیقتر از Problem Space و Solution Space، به دنبال دانش دامنهٔ از دسترفته باشید.
همانطور که در فصل ۱۱ دیدیم، دانش دامنه میتواند به دلایل مختلف از بین برود. این مشکل بهخصوص در Core Subdomainها گسترده و جدی است، زیرا Business Logic هم پیچیده و هم برای کسبوکار حیاتی است. اگر با چنین وضعیتهایی روبهرو شدید، EventStorming برگزار کنید تا دانش را بازیابی کنید. همچنین از EventStorming بهعنوان پایهای برای پرورش Ubiquitous Language استفاده کنید.
راهبرد Modernization
تلاشهای «Big Rewrite»، که در آن مهندسان میخواهند سیستم را از صفر بازنویسی کنند و این بار همهچیز را «درست» طراحی و پیادهسازی کنند، بهندرت موفقاند. حتی کمتر پیش میآید مدیریت از چنین تغییر معماری عظیمی حمایت کند.
رویکرد امنتر برای بهبود طراحی سیستمهای موجود این است که بزرگ فکر کنید اما کوچک شروع کنید. همانطور که Eric Evans میگوید، همهٔ بخشهای یک سیستم بزرگ خوب طراحی نخواهند شد؛ این واقعیتی است که باید پذیرفت. بنابراین باید بهصورت راهبردی تصمیم بگیریم تلاش Modernization را کجا سرمایهگذاری کنیم.
پیشنیاز این تصمیم، داشتن مرزهایی است که Subdomainهای سیستم را از هم جدا کنند. این Boundaryها لازم نیست در ابتدا Physical باشند و هر Subdomain را به یک Bounded Context کامل تبدیل کنند. کافی است مطمئن شوید حداقل Boundaryهای منطقی—Namespace، Module و Package بر اساس Technology Stack—با مرزهای Subdomain همراستا هستند؛ شکل ۱۳-۱ این سازماندهی را نشان میدهد.
تنظیم Moduleهای سیستم شکل نسبتاً امنی از Refactoring است. Business Logic را تغییر نمیدهید؛ فقط Typeها را در ساختاری منظمتر جابهجا میکنید. با این حال مطمئن شوید ارجاعهایی که از نام کامل Type استفاده میکنند، مانند Dynamic Loading یا Reflection، شکسته نمیشوند.
همچنین Business Logicهای Subdomain را که در Codebaseهای دیگر، Stored Procedureهای پایگاه داده، Serverless Functionها و موارد مشابه پیاده شدهاند دنبال کنید. Boundaryهای جدید را در این Platformها نیز معرفی کنید. برای مثال، اگر بخشی از منطق در Stored Procedureهای Database قرار دارد، یا Procedureها را طوری نامگذاری کنید که Module آنها مشخص باشد، یا Schema اختصاصی ایجاد کنید و Procedureها را به آن منتقل کنید.
Modernization راهبردی
همانطور که در فصل ۱۰ بحث شد، تجزیهٔ زودهنگام سیستم به کوچکترین Bounded Contextهای ممکن میتواند خطرناک باشد. رابطهٔ Bounded Context و Microservice در فصل بعد با جزئیات بیشتر بررسی خواهد شد. فعلاً به دنبال ناحیههایی باشید که تبدیل Boundary منطقی به Boundary فیزیکی بیشترین ارزش را ایجاد میکند. فرایند استخراج Bounded Context از طریق فیزیکی کردن Boundary در شکل ۱۳-۲ نمایش داده شده است.
پرسشهایی که باید از خود بپرسید:
- آیا چند تیم روی یک Codebase کار میکنند؟ اگر بله، Lifecycleهای توسعه را با تعریف Bounded Context جدا برای هر تیم Decouple کنید.
- آیا مؤلفههای مختلف از مدلهای متعارض استفاده میکنند؟ اگر بله، مدلهای متعارض را به Bounded Contextهای جدا منتقل کنید.
وقتی حداقل Bounded Contextهای لازم ایجاد شدند، رابطه و الگوهای Integration میان آنها را بررسی کنید. ببینید تیمهایی که روی Contextهای مختلف کار میکنند چگونه ارتباط و همکاری دارند. بهویژه اگر از Integrationهای موردی یا شبیه Shared Kernel استفاده میشود، آیا تیمها اهداف مشترک و سطح همکاری کافی دارند؟
به مشکلاتی توجه کنید که الگوهای Context Integration میتوانند حل کنند:
Customer–Supplier
همانطور که در فصل ۱۱ دیدیم، رشد سازمانی میتواند Patternهای پیشین ارتباط و همکاری را نامعتبر کند. به دنبال مؤلفههایی باشید که در اصل برای Partnership چند تیم طراحی شده بودند، اما اکنون Partnership دیگر پایدار نیست. آنها را به نوع مناسب Customer–Supplier—Conformist، Anticorruption Layer یا Open-Host Service—بازآرایی کنید.
Anticorruption Layer
Anticorruption Layer برای حفاظت Bounded Context در برابر Legacy System مفید است، بهویژه وقتی Legacy System از مدلهای ناکارآمدی استفاده میکند که تمایل دارند به مؤلفههای Downstream سرایت کنند.
کاربرد رایج دیگر ACL، محافظت از Bounded Context در برابر تغییرهای پرتکرار Public Interface یک Upstream Service است.
Open-Host Service
اگر تغییر جزئیات پیادهسازی یک مؤلفه مرتب در سراسر سیستم موج ایجاد میکند و مصرفکنندگان را تحت تأثیر قرار میدهد، آن را به Open-Host Service تبدیل کنید: مدل پیادهسازی را از Public API جدا کنید.
Separate Ways
بهویژه در سازمانهای بزرگ، ممکن است میان تیمهای مهندسی به دلیل نیاز به همکاری و تکامل مشترک یک قابلیت اصطکاک ایجاد شود. اگر قابلیت مورد اختلاف Business-Critical نیست—یعنی Core Subdomain نیست—تیمها میتوانند Separate Ways را انتخاب کنند و هرکدام پیادهسازی خود را داشته باشند تا منبع اصطکاک حذف شود.
Modernization تاکتیکی
از دید تاکتیکی، پیش از هر چیز به دنبال دردناکترین عدمتطابقها میان ارزش تجاری و راهبرد پیادهسازی باشید؛ برای مثال Core Subdomainهایی که با Patternهایی پیاده شدهاند که با پیچیدگی مدل سازگار نیستند، مانند Transaction Script یا Active Record. این مؤلفهها مستقیماً بر موفقیت کسبوکار اثر میگذارند، بیشترین تغییر را لازم دارند و در عین حال به دلیل طراحی ضعیف سختترین بخش برای نگهداری و تکاملاند.
Ubiquitous Language را پرورش دهید
پیشنیاز Modernization موفق، دانش دامنه و یک مدل مؤثر از دامنهٔ کسبوکار است. همانطور که بارها در کتاب تأکید شده، Ubiquitous Language در DDD برای رسیدن به دانش و ساخت Solution Model مؤثر ضروری است.
میانبُر DDD برای جمعآوری دانش دامنه را فراموش نکنید: EventStorming. از EventStorming برای ساخت Ubiquitous Language همراه Domain Expertها و بررسی Legacy Codebase استفاده کنید، مخصوصاً وقتی Codebase آشفتگی مستندنشدهای است که هیچکس واقعاً آن را نمیفهمد. همهٔ افراد مرتبط با قابلیت را گرد هم بیاورید و دامنهٔ کسبوکار را بررسی کنید. EventStorming ابزاری عالی برای بازیابی دانش دامنه است.
پس از دستیابی به دانش دامنه و مدل یا مدلهای آن، تصمیم بگیرید کدام Patternهای پیادهسازی Business Logic برای قابلیت مورد نظر مناسباند. بهعنوان نقطهٔ شروع از Design Heuristicهای فصل ۱۰ استفاده کنید. تصمیم بعدی دربارهٔ Strategy مدرنسازی است: آیا کل مؤلفههای سیستم را تدریجی جایگزین کنیم (Strangler Pattern) یا راهحل موجود را بهتدریج Refactor کنیم؟
الگوی Strangler
Strangler Fig، که در شکل ۱۳-۳ نشان داده شده، خانوادهای از درختان گرمسیری با الگوی رشد خاص است: درخت Strangler روی درخت دیگری—Host—رشد میکند. زندگیاش را به صورت دانهای در شاخههای بالایی Host شروع میکند، سپس با رشد به سمت پایین حرکت میکند تا در خاک ریشه بگیرد. در نهایت، برگهای Strangler بر Host سایه میاندازند و باعث مرگ آن میشوند.
الگوی مهاجرت Strangler از همین پویایی الهام گرفته است. ایده این است که Bounded Context جدیدی—Strangler—بسازید، Requirementهای جدید را در آن پیاده کنید و بهتدریج قابلیت Legacy Context را به آن منتقل کنید. همزمان، به جز Hotfix و موارد اضطراری، تکامل و توسعهٔ Legacy Context متوقف میشود. در نهایت همهٔ قابلیت به Context جدید مهاجرت میکند و Host—Legacy Codebase—از بین میرود.
معمولاً Strangler با Façade Pattern استفاده میشود: لایهٔ انتزاعی نازکی که Public Interface را فراهم میکند و Requestها را بر اساس وضعیت مهاجرت یا به Legacy Context یا Context جدید میفرستد. وقتی Migration کامل شد، Façade نیز دیگر لازم نیست و حذف میشود؛ شکل ۱۳-۴ این جریان را نشان میدهد.
برخلاف اصل «هر Bounded Context یک Subsystem مستقل است و Database خود را دارد»، هنگام پیادهسازی Strangler میتوان این قاعده را موقتاً نرمتر کرد. Context مدرنشده و Legacy Context میتوانند برای جلوگیری از Integration بسیار پیچیده—که ممکن است به Distributed Transaction نیاز داشته باشد—از Database مشترک استفاده کنند، زیرا هر دو باید با دادهٔ یکسان کار کنند. شکل ۱۳-۵ این حالت را نشان میدهد.
شرط این استثنا این است که در نهایت، و بهتر است هرچه زودتر، Legacy Context بازنشسته شود و Database فقط توسط پیادهسازی جدید استفاده گردد.
Refactoring درجا
جایگزین مهاجرت مبتنی بر Strangler این است که Legacy Codebase را در همان محل مدرن کنیم؛ یعنی Refactoring.
بازآرایی تصمیمهای طراحی تاکتیکی
در فصل ۱۱ جنبههای مختلف مهاجرت تصمیمهای تاکتیکی را دیدید. با این حال، هنگام مدرن کردن Legacy Codebase دو نکته باید در نظر گرفته شود.
نخست، گامهای کوچک و افزایشی از Big Rewrite امنترند. بنابراین Transaction Script یا Active Record را مستقیماً به Event-Sourced Domain Model تبدیل نکنید. ابتدا گام میانی یعنی طراحی Aggregateهای حالتمحور را بردارید. برای یافتن مرزهای مؤثر Aggregate وقت بگذارید و مطمئن شوید Business Logic مرتبط داخل همان Boundary قرار دارد. حرکت از Aggregate حالتمحور به Event-Sourced بسیار امنتر از کشف Boundary تراکنشی اشتباه در Aggregate رویدادمنبع است.
دوم، بر اساس همان اصل گامهای کوچک، Refactor به Domain Model لازم نیست یک تغییر Atomic باشد. میتوان عناصر Domain Model را بهتدریج وارد کرد.
با جستوجوی Value Objectهای ممکن شروع کنید. اشیای Immutable حتی بدون Domain Model کامل نیز میتوانند پیچیدگی Solution را بهطور معناداری کاهش دهند.
همانطور که در فصل ۱۱ دیدیم، تبدیل Active Recordها به Aggregate لازم نیست یکشبه انجام شود. ابتدا Business Logic مرتبط را جمع کنید. سپس Boundaryهای تراکنشی را تحلیل کنید. آیا تصمیمهایی وجود دارند که Strong Consistency میخواهند اما روی دادهٔ Eventually Consistent اجرا میشوند؟ یا برعکس، آیا Solution جایی Strong Consistency را تحمیل میکند که Eventual Consistency کافی است؟ هنگام تحلیل Codebase فراموش نکنید این تصمیمها از ملاحظات کسبوکار هدایت میشوند، نه Technology. فقط پس از تحلیل کامل Requirementهای تراکنشی باید Boundaryهای Aggregate را طراحی کنید.
در نهایت، هر زمان در Refactoring سیستم Legacy لازم بود، با Anticorruption Layer از کد جدید در برابر مدل قدیمی محافظت کنید و با Open-Host Service و Published Language، مصرفکنندگان را از تغییرهای Legacy Codebase مصون نگه دارید.
طراحی دامنهمحور عملگرایانه
همانطور که در مقدمهٔ فصل گفته شد، استفاده از DDD یک کار «همه یا هیچ» نیست. لازم نیست از همهٔ ابزارهای DDD استفاده کنید. شاید Patternهای تاکتیکی به هر دلیلی برای شما مناسب نباشند. ممکن است Design Patternهای دیگری را ترجیح دهید چون در دامنهٔ مشخص شما بهتر کار میکنند یا صرفاً آنها را مؤثرتر میدانید. اشکالی ندارد.
تا زمانی که دامنهٔ کسبوکار و Strategy آن را تحلیل میکنید، به دنبال مدلهای مؤثر برای حل Problemهای مشخص میگردید و—مهمتر از همه—تصمیمهای طراحی را بر اساس نیازهای دامنهٔ کسبوکار میگیرید، در حال انجام DDD هستید.
ارزش دارد دوباره تأکید کنیم که DDD دربارهٔ Aggregate یا Value Object نیست. DDD دربارهٔ این است که اجازه دهید دامنهٔ کسبوکار، تصمیمهای طراحی نرمافزار را هدایت کند.
«فروش» DDD
نویسنده میگوید هنگام ارائهٔ این موضوع در کنفرانسهای فناوری تقریباً همیشه یک سؤال مطرح میشود: «همهٔ اینها عالی است، اما چطور DDD را به تیم و مدیریت بفروشم؟» این سؤال بسیار مهمی است.
فروش سخت است. با این حال، اگر دقیقتر نگاه کنیم، طراحی نرمافزار نیز نوعی فروش است: ایدههای خود را به تیم، مدیریت یا مشتری میفروشیم. روشی که طیف وسیعی از تصمیمهای طراحی را پوشش میدهد و حتی از محدودهٔ مهندسی فراتر میرود تا سایر ذینفعان را درگیر کند، میتواند بسیار سخت «فروخته» شود.
حمایت مدیریت برای ایجاد تغییر قابل توجه در سازمان ضروری است. اما اگر مدیران ارشد از قبل با DDD آشنا نباشند یا نخواهند برای یادگیری ارزش تجاری روش زمان بگذارند، این موضوع در اولویت ذهنی آنها قرار نمیگیرد؛ مخصوصاً چون DDD در نگاه اول تغییر بزرگی در فرایند مهندسی ایجاد میکند. با این حال، این بدان معنا نیست که نمیتوانید از DDD استفاده کنید.
DDD زیرپوستی (Undercover Domain-Driven Design)
DDD را بخشی از جعبهابزار حرفهای خود کنید، نه الزاماً یک راهبرد رسمی سازمانی. الگوها و شیوههای DDD تکنیکهای مهندسیاند و چون مهندسی نرمافزار کار شما است، از آنها استفاده کنید.
در ادامه میبینیم چگونه میتوان DDD را بدون سروصدای زیاد وارد کار روزمره کرد.
Ubiquitous Language
Ubiquitous Language سنگبنای DDD است. این شیوه برای کشف دانش دامنه، ارتباط و مدلسازی مؤثر Solution ضروری است.
خوشبختانه این شیوه آنقدر طبیعی است که تقریباً Common Sense محسوب میشود. به زبانی که ذینفعان هنگام صحبت دربارهٔ دامنهٔ کسبوکار استفاده میکنند با دقت گوش دهید. بهآرامی اصطلاحات را از Jargon فنی به سمت معنای تجاری هدایت کنید.
به دنبال اصطلاحهای ناسازگار باشید و توضیح بخواهید. اگر برای یک چیز چند نام وجود دارد، دلیلش را پیدا کنید. آیا Modelهای متفاوت در یک Solution به هم آمیختهاند؟ Contextها را پیدا و صریح کنید. اگر معنا یکسان است، منطقی عمل کنید و درخواست کنید از یک اصطلاح واحد استفاده شود.
تا جای ممکن با Domain Expertها ارتباط داشته باشید. این ارتباط لزوماً نیازمند جلسهٔ رسمی نیست؛ گفتوگوهای کوتاه کنار دستگاه آب یا وقت قهوه هم ابزار ارتباطی خوبی هستند. دربارهٔ دامنهٔ کسبوکار با متخصصان صحبت کنید، زبان آنها را به کار ببرید، نقاط ابهام را پیدا کنید و توضیح بخواهید. Domain Expertها معمولاً از همکاری با مهندسانی که صادقانه میخواهند Problem Domain را یاد بگیرند استقبال میکنند.
مهمتر از همه، Ubiquitous Language را در کد و همهٔ ارتباطهای مربوط به پروژه به کار ببرید. صبور باشید؛ تغییر اصطلاحاتی که مدتها در سازمان استفاده شدهاند زمان میبرد، اما در نهایت جا میافتند.
Bounded Contextها
هنگام بررسی گزینههای تجزیه، به اصول زیربنایی Bounded Context رجوع کنید:
- چرا بهتر است بهجای یک مدل واحد برای همهٔ Use Caseها، Modelهای Problem-Oriented بسازیم؟ چون Solutionهای «همهکاره» بهندرت در چیزی واقعاً مؤثرند.
- چرا Bounded Context نمیتواند مدلهای متعارض را در خود نگه دارد؟ چون Cognitive Load و Solution Complexity را افزایش میدهد.
- چرا کار چند تیم روی یک Codebase ایدهٔ بدی است؟ چون اصطکاک ایجاد میکند و همکاری تیمها را مختل میسازد.
- چرا Boundaryهای تراکنشی نباید از Aggregate عبور کنند و اطلاعات موجود در مؤلفههای فیزیکی دور از هم را strongly consistent فرض کنند؟ چون این اطلاعات تمایل دارد از هم Sync خارج شود و به فساد داده منجر گردد.
- چرا باید برای Aggregateهای کوچک تلاش کنیم؟ چون Scope تراکنشی بزرگ هم پیچیدگی Aggregate را افزایش میدهد و هم Performance را کاهش میدهد.
- چرا بهجای Event Sourcing نمیتوان فقط Eventها را در فایل Log نوشت؟ چون تضمین بلندمدتی برای سازگاری داده وجود ندارد.
Event-Sourced Domain Model
با وجود مزایای متعدد، Event Sourcing برای بسیاری از افراد بیش از حد Radical به نظر میرسد. مانند همهٔ مباحث کتاب، راهحل این است که اجازه دهید دامنهٔ کسبوکار تصمیم را هدایت کند.
با Domain Expertها صحبت کنید. Model حالتمحور و مبتنی بر Event را به آنها نشان دهید. تفاوتها و مزیتهای Event Sourcing را، بهویژه دربارهٔ بُعد زمان، توضیح دهید. در بسیاری از موارد، سطح Insightی که فراهم میکند برای آنها بسیار جذاب خواهد بود و خودشان به حامی Event Sourcing تبدیل میشوند.
و هنگام تعامل با Domain Expertها، فراموش نکنید روی Ubiquitous Language نیز کار کنید.
جمعبندی
در این فصل تکنیکهای مختلف استفاده از ابزارهای DDD در سناریوهای واقعی را آموختید: کار روی پروژههای Brownfield و Legacy Codebaseها، حتی بدون تیمی پر از متخصص DDD.
مانند پروژهٔ Greenfield، همیشه از تحلیل دامنهٔ کسبوکار شروع کنید. اهداف شرکت چیست و برای رسیدن به آنها چه Strategy دارد؟ از ساختار سازمانی و تصمیمهای طراحی نرمافزار موجود برای شناسایی Subdomainها و نوع آنها استفاده کنید. با این دانش، راهبرد Modernization را برنامهریزی کنید. Pain Pointها را پیدا کنید و جایی را هدف بگیرید که بیشترین Business Value ایجاد میکند. Legacy Code را یا با Refactoring یا با جایگزینی مؤلفههای مناسب مدرن کنید. در هر دو حالت، تدریجی عمل کنید؛ Big Rewriteها معمولاً ریسکی بیش از ارزش تجاری دارند.
در نهایت، حتی اگر DDD بهطور گسترده در سازمان پذیرفته نشده باشد میتوانید از ابزارهای آن استفاده کنید. ابزار درست را به کار بگیرید و هنگام صحبت با همکاران، همیشه منطق و اصول زیربنایی هر Pattern را توضیح دهید.
این فصل بحث مستقل DDD را به پایان میرساند. در بخش چهارم، تعامل DDD با روشها و Patternهای دیگر بررسی خواهد شد.
تمرینها
- فرض کنید میخواهید ابزارها و شیوههای DDD را به یک پروژهٔ Brownfield معرفی کنید. اولین گام شما چیست؟
- تمام Business Logic را به Event-Sourced Domain Model تبدیل کنید.
- دامنهٔ کسبوکار و Strategy سازمان را تحلیل کنید.
- مؤلفههای سیستم را با اطمینان از رعایت اصول Bounded Context بهبود دهید.
- استفاده از DDD در پروژهٔ Brownfield ناممکن است.
- Strangler Pattern در طول Migration از چه راههایی موقتاً با بعضی اصول بنیادی DDD در تضاد است؟
- چند Bounded Context از Database مشترک استفاده میکنند.
- اگر Context مدرنشده Core Subdomain باشد، پیادهسازی آن موقتاً در قدیم و جدید تکرار میشود.
- چند تیم روی یک Bounded Context کار میکنند.
- A و B.
- چرا معمولاً ایدهٔ خوبی نیست Business Logic مبتنی بر Active Record را مستقیماً به Event-Sourced Domain Model تبدیل کنیم؟
- State-Based Model بازآرایی Boundaryهای Aggregate را در فرایند یادگیری آسانتر میکند.
- معرفی تغییرهای بزرگ بهصورت تدریجی امنتر است.
- A و B.
- هیچکدام؛ تبدیل مستقیم Transaction Script نیز منطقی است.
- وقتی Aggregate Pattern را معرفی میکنید، تیم میپرسد چرا Aggregate نمیتواند همهٔ Entityهای ممکن را Reference کند تا بتوان کل دامنهٔ کسبوکار را از یک نقطه پیمایش کرد. چگونه پاسخ میدهید؟
بخش چهارم — رابطه با روشها و الگوهای دیگر
تا اینجا آموختید چگونه از DDD برای طراحی Solutionهای نرمافزاری متناسب با Strategy و نیازهای کسبوکار سازمان استفاده کنید. دیدیم چگونه ابزارها و شیوههای DDD به درک دامنهٔ کسبوکار، طراحی Boundaryهای مؤلفههای سیستم و پیادهسازی Business Logic کمک میکنند.
DDD بخش بزرگی از چرخهٔ توسعهٔ نرمافزار را پوشش میدهد، اما همهٔ مهندسی نرمافزار را پوشش نمیدهد. روشها و ابزارهای دیگر نیز نقش خود را دارند. در بخش چهارم رابطهٔ DDD با روشها و Patternهای دیگر بررسی میشود:
- محبوبیت DDD تا حد زیادی با رواج معماری مبتنی بر Microservice رشد کرد. فصل ۱۴ تعامل Microservice و DDD و شیوهٔ تکمیل یکدیگر را بررسی میکند.
- Event-Driven Architecture روشی پرکاربرد برای معماری سیستمهای توزیعشدهٔ مقیاسپذیر، performant و resilient است. فصل ۱۵ اصول EDA و نحوهٔ استفاده از DDD برای طراحی ارتباط Async مؤثر را توضیح میدهد.
- فصل ۱۶ کتاب را با مدلسازی در زمینهٔ Data Analytics به پایان میرساند: معماریهای Data Warehouse و Data Lake، کاستیهای آنها، Data Mesh و اشتراک اصول طراحی Data Mesh و DDD.