ضمیمه A — بهکارگیری DDD: یک مطالعهٔ موردی
در این ضمیمه نویسنده توضیح میدهد سفر شخصی او در طراحی دامنهمحور چگونه آغاز شد: داستان یک شرکت Start-up که برای این مثال آن را Marketnovus مینامد. Marketnovus از روز تأسیس از روش DDD استفاده میکرد. طی سالها تقریباً هر اشتباه ممکنی در DDD را مرتکب شد، اما در عین حال فرصت پیدا کرد از آن اشتباهها درس بگیرد و اصلاحشان کند. نویسنده از این داستان و اشتباههای واقعی برای نشان دادن نقش Patternها و Practiceهای DDD در موفقیت یک پروژهٔ نرمافزاری استفاده میکند.
مطالعهٔ موردی دو بخش دارد. در بخش نخست، داستان پنج Bounded Context در Marketnovus، تصمیمهای طراحی و نتیجههایشان بررسی میشود. در بخش دوم، تحلیل میشود که این تجربهها چگونه مفاهیم آموختهشده در کتاب را منعکس میکنند.
نویسنده تأکید میکند Marketnovus دیگر وجود ندارد؛ بنابراین این ضمیمه تبلیغ شرکت نیست و چون شرکت منحل شده، او میتواند صادقانه دربارهٔ تجربهها صحبت کند.
پنج Bounded Context
پیش از ورود به طراحی Bounded Contextها، مطابق رفتار درست یک DDD Practitioner باید Business Domain شرکت را تعریف کنیم.
Business Domain
تصور کنید محصول یا سرویسی تولید میکنید. Marketnovus به شما اجازه میداد تقریباً همهٔ کارهای Marketing را Outsource کنید. متخصصان شرکت Strategy بازاریابی محصول را طراحی میکردند. Copywriter و Graphic Designer حجم زیادی Creative Material—مانند Banner و Landing Page—تولید میکردند که برای اجرای Campaignهای تبلیغاتی استفاده میشد. Leadهای تولیدشده از Campaignها توسط Sales Agentها مدیریت میشدند؛ Agentها تماس میگرفتند و محصول را میفروختند. شکل A-1 فرایند Marketing را نمایش میدهد.
مهمتر از همه، این Marketing Process فرصتهای بسیاری برای Optimization داشت و Analysis Department دقیقاً مسئول همین کار بود. آنها همهٔ Data را تحلیل میکردند تا Marketnovus و مشتریان بیشترین بازده سرمایهگذاری را بگیرند: شناسایی موفقترین Campaignها، یافتن مؤثرترین Creativeها و اطمینان از اینکه Sales Agentها روی امیدبخشترین Leadها کار میکنند.
Marketnovus یک شرکت Self-Funded بود و باید سریع حرکت میکرد. بلافاصله بعد از تأسیس، نسخهٔ نخست Software System باید یکسوم ابتدایی Value Chain را پیاده میکرد:
- سیستمی برای مدیریت Contractها و Integration با Publisherهای خارجی؛
- Catalog برای Designerها جهت مدیریت Creative Material؛
- راهحل Campaign Management برای اجرای Advertising Campaign.
پیچیدگی Business Domain برای نویسنده سنگین بود و باید راهی پیدا میکرد که آن را قابل فهم کند. خوشبختانه کمی پیشتر کتاب مشهور Eric Evans، Domain-Driven Design: Tackling Complexity in the Heart of Software، را خوانده بود؛ کتابی که دقیقاً وعدهٔ مقابله با چنین پیچیدگیای را میداد.
اگر پیشگفتار این کتاب را خوانده باشید، میدانید کتاب Evans پاسخهایی برای مسئلهای بود که نویسنده مدتها دنبال میکرد: چگونه Business Logic را طراحی و پیاده کنیم. با این حال، درک کتاب در نخستین مطالعه آسان نبود. با وجود این، نویسنده احساس میکرد فقط با خواندن فصلهای Tactical Design درک محکمی از DDD پیدا کرده است.
Bounded Context شمارهٔ ۱: Marketing
سبک معماری نخستین Solution را میشد با عبارت «Aggregate همهجا» خلاصه کرد. Agency، Campaign، Placement، Funnel، Publisher: هر اسم موجود در Requirementها به Aggregate تبدیل شد.
همهٔ این «Aggregate»ها در یک Bounded Context بزرگ قرار گرفتند؛ همان Monolith بزرگ و ترسناکی که امروزه همه دربارهٔ خطرش هشدار میدهند.
البته اینها Aggregate واقعی نبودند. Transactional Boundary مشخصی فراهم نمیکردند و Behavior بسیار کمی داشتند. تقریباً تمام Business Logic در یک Service Layer عظیم پیاده شده بود.
وقتی قصد دارید Domain Model پیاده کنید اما نتیجه به Active Record شباهت دارد، این وضعیت را اغلب Anemic Domain Model Antipattern مینامند. با نگاه امروز، طراحی Marketnovus نمونهٔ کتابی «چگونه Domain Model را پیاده نکنیم» بود. اما از دید Business، داستان کاملاً متفاوت بود.
از نظر کسبوکار، پروژه موفقیت بزرگی محسوب شد. با وجود Architecture ناقص، Team توانست Working Software را در Time-to-Market بسیار تهاجمی تحویل دهد. چگونه؟
نوعی جادو
تیم به شکلی موفق شد Ubiquitous Language قدرتمندی بسازد. هیچکدام تجربهٔ قبلی در Online Marketing نداشتند، اما میتوانستند با Domain Expertها گفتوگوی واقعی داشته باشند؛ مهندسان متخصصان را میفهمیدند و متخصصان نیز مهندسان را. حتی Domain Expertها از اینکه تیم Engineering واقعاً مایل است از دانش و تجربهٔ آنها یاد بگیرد استقبال میکردند.
ارتباط روان با Domain Expertها به Team اجازه داد Business Domain را خیلی سریع درک کند و Business Logic آن را پیاده سازد. بله، یک Monolith بزرگ بود، اما برای دو Developer در یک Garage «بهاندازهٔ کافی خوب» بود. Team توانست Working Software را در زمان بسیار کوتاه تحویل دهد.
درک اولیهٔ ما از DDD
در این مرحله درک Team از DDD تقریباً همان چیزی بود که شکل A-2 نشان میدهد: Ubiquitous Language + «Aggregate همهجا». اما هنوز بسیاری از عناصر مهم Strategic Design در مدل ذهنی Team غایب بودند.
Bounded Context شمارهٔ ۲: CRM
اندکی بعد از Deploy شدن Campaign Management، Leadها شروع به ورود کردند و تیم فوراً به یک Customer Relationship Management System قدرتمند برای مدیریت Lead و Lifecycle آنها نیاز پیدا کرد.
CRM باید همهٔ Leadهای ورودی را Aggregate میکرد، آنها را بر اساس Parameterهای مختلف Group میکرد و در چند Sales Desk در نقاط مختلف جهان توزیع میکرد. همچنین باید با سیستمهای داخلی Clientها Integrate میشد: هم برای اطلاع دادن تغییرهای Lifecycle Lead و هم برای غنی کردن Lead با اطلاعات تکمیلی.
و البته CRM باید هر فرصت ممکن برای Optimization را فراهم میکرد. Team باید مطمئن میشد Agentها روی امیدوارکنندهترین Leadها کار میکنند؛ Leadها بر اساس Qualification و Past Performance به Agent مناسب Assign شوند و سیستم محاسبهٔ Commission بسیار منعطف باشد.
هیچ محصول آمادهای Requirementها را پوشش نمیداد؛ بنابراین Company تصمیم گرفت CRM خودش را بسازد.
Aggregateهای بیشتر!
رویکرد اولیه ادامهٔ تمرکز روی Tactical Patternها بود. دوباره هر اسم Aggregate اعلام شد و همه در همان Monolith جا گرفتند. اما این بار از همان ابتدا چیزی درست به نظر نمیرسید.
Team مرتب Prefixهای ناخوشایند به نام Aggregateها اضافه میکرد: CRMLead و MarketingLead، یا MarketingCampaign و CRMCampaign. جالب اینکه در مکالمه با Domain Expertها هیچکس از این Prefixها استفاده نمیکرد؛ Context گفتوگو معنا را روشن میکرد.
نویسنده به یاد آورد DDD مفهومی به نام Bounded Context دارد که تا آن زمان نادیده گرفته شده بود. پس از بازخوانی فصلهای مربوط از کتاب Evans، روشن شد Bounded Context دقیقاً همان مشکلی را حل میکند که Team تجربه میکرد: محافظت از سازگاری Ubiquitous Language.
همزمان Vaughn Vernon مقالهٔ Effective Aggregate Design را منتشر کرده بود. مقاله همهٔ اشتباههای Team را صریح کرد: آنها Aggregate را Data Structure میدیدند، در حالی که نقش مهمتر Aggregate حفاظت از Consistency دادهٔ سیستم است.
Team یک گام عقب رفت و CRM را بر اساس این آموختهها دوباره طراحی کرد.
طراحی راهحل: بار دوم
Monolith به دو Bounded Context مجزا—Marketing و CRM—تقسیم شد. Team تا Microservice پیش نرفت؛ فقط حداقل کار لازم برای حفاظت از Ubiquitous Language را انجام داد.
اما در CRM قرار نبود اشتباه Marketing تکرار شود. دیگر خبری از Anemic Domain Model نبود؛ این بار یک Domain Model واقعی با Aggregateهای «طبق کتاب» ساخته میشد. Team متعهد شد:
- هر Transaction فقط یک Instance از Aggregate را تغییر دهد؛
- بهجای ORM، خود Aggregate Scope تراکنش را تعیین کند؛
- Service Layer رژیم سختی بگیرد و همهٔ Business Logic به Aggregateهای متناظر منتقل شود.
Team مشتاق بود کار را «درست» انجام دهد، اما خیلی زود روشن شد مدلسازی Domain Model درست سخت است.
نسبت به Marketing همهچیز زمان بیشتری میبرد. Transactional Boundary تقریباً هیچوقت در تلاش اول درست نبود. Team چند Model را طراحی و Test میکرد تا بعداً بفهمد Modelی که حتی به آن فکر نکرده بود درستتر است. قیمت انجام کار «طبق کتاب» بسیار بالا شد: زمان زیاد.
بهزودی روشن شد Deadlineها دستنیافتنیاند. Management برای کمک بخشی از Featureها را به Team DBA سپرد تا Business Logic را در Stored Procedure پیاده کند.
این تصمیم در سالهای بعد آسیب زیادی ایجاد کرد. مشکل این نبود که SQL زبان مناسبی برای Business Logic نیست؛ مسئله بسیار ظریفتر و بنیادیتر بود.
برج بابل ۲٫۰
این وضعیت یک Bounded Context ضمنی ایجاد کرد که Boundary آن یکی از پیچیدهترین Business Entityها—Lead—را از وسط برید.
دو Team روی یک Business Component مشترک و Featureهای بهشدت مرتبط کار میکردند، اما حداقل Interaction را داشتند. Ubiquitous Language؟ عملاً هیچ. هر Team Vocabulary خودش را برای توصیف Business Domain و Ruleها ساخت.
Modelها ناسازگار شدند، Shared Understanding از بین رفت، Knowledge تکرار شد و Ruleهای مشابه دو بار پیاده شدند. طبیعی بود که با تغییر Business Logic، دو Implementation فوراً از Sync خارج شوند.
پروژه نهتنها به Deadline نرسید، بلکه Bugهای زیادی داشت. مشکلهای جدی Production که سالها از دید پنهان مانده بودند، باارزشترین Asset شرکت—Data—را خراب کردند.
در نهایت تنها راه خروج از این آشفتگی، بازنویسی کامل Lead Aggregate با Boundary درست بود. چند سال بعد این کار انجام شد. آسان نبود، اما وضعیت آنقدر بد شده بود که راه دیگری وجود نداشت.
درک گستردهتر از DDD
اگرچه پروژه از نظر Business شکست بدی بود، درک Team از DDD تکامل یافت: Ubiquitous Language بساز، Integrity آن را با Bounded Context محافظت کن و بهجای پیادهسازی Anemic Domain Model در همهجا، Domain Model واقعی بساز. شکل A-3 این مدل ذهنی را نشان میدهد.
با این حال، بخش مهمی هنوز غایب بود: Subdomainها، Typeهای آنها و اثرشان بر System Design.
Team میخواست بهترین کار ممکن را انجام دهد، اما در عمل زمان و تلاش زیادی برای Domain Modelهای Supporting Subdomain هدر داد. جملهٔ Eric Evans که «همهٔ بخشهای یک سیستم بزرگ خوب طراحی نخواهند شد» بهصورت دردناک آموخته شد و قرار بود تجربه در پروژهٔ بعدی به کار رود.
Bounded Context شمارهٔ ۳: Event Crunchers
پس از راهاندازی CRM، Team به یک Subdomain ضمنی مشکوک شد که منطق آن در Marketing و CRM پخش شده بود. هر بار فرایند مدیریت Incoming Customer Event تغییر میکرد، لازم بود هم Marketing و هم CRM تغییر کنند.
چون از نظر مفهومی این Process کاملاً متعلق به هیچکدام نبود، Logic به Bounded Context جداگانهای با نام Event Crunchers منتقل شد؛ شکل A-4.
شرکت از «روش جابهجایی Data» درآمدی کسب نمیکرد و هیچ Off-the-Shelf Solution مناسبی هم وجود نداشت. بنابراین Event Crunchers شبیه Supporting Subdomain تشخیص داده شد و همانطور طراحی شد.
این بار چیز Fancyای انتخاب نشد: Layered Architecture و Transaction Scriptهای ساده. راهحل عالی کار کرد—اما فقط برای مدتی.
با تکامل Business، Featureهای بیشتری به Event Crunchers اضافه شد. ابتدا BI Team چند Flag میخواست: Flag برای Contact جدید، Flag برای First-Time Eventهای مختلف، Flagهای بیشتر برای Business Invariantها و غیره.
بهتدریج Flagهای ساده به Business Logic واقعی با Ruleها و Invariantهای پیچیده تبدیل شدند. چیزی که بهعنوان Transaction Script ساده شروع شده بود، به Core Business Subdomain تمامعیار تبدیل شد.
اما طراحی با Domain تکامل نیافت. Complex Business Logic در Transaction Script به Big Ball of Mud بزرگی تبدیل شد. هر Modification گرانتر شد و Quality افت کرد. Team مجبور شد طراحی Event Crunchers را دوباره بررسی کند؛ این بازطراحی یک سال بعد انجام شد.
تا آن زمان Business Logic آنقدر پیچیده شده بود که فقط Event Sourcing میتوانست آن را بهخوبی مدیریت کند. Logic به Event-Sourced Domain Model Refactor شد و Bounded Contextهای دیگر Subscriber Eventهای آن شدند.
Bounded Context شمارهٔ ۴: Bonuses
یک روز Sales Desk Managerها درخواست کردند Procedure ساده اما خستهکنندهای که دستی انجام میدادند خودکار شود: محاسبهٔ Commission برای Sales Agentها.
نیاز اولیه بسیار ساده بود: ماهی یک بار درصدی از Sales هر Agent را محاسبه و Report را برای Manager ارسال کن. مانند قبل، Team بررسی کرد آیا این Core Subdomain است؟ خیر. هیچ چیز جدیدی اختراع نمیشد، شرکت مستقیماً از این Process پول درنمیآورد و اگر Implementation آماده وجود داشت حتماً آن را میخرید. پس نه Core بود و نه Generic؛ Supporting Subdomain دیگری بود.
Solution نیز بر همان اساس طراحی شد: Active Record Objectها که یک Service Layer «هوشمند» آنها را Orchestrate میکرد؛ شکل A-5.
اما به محض خودکار شدن Process، تقریباً همهٔ Company دربارهٔ آن خلاق شدند. Analystها میخواستند Process را شدیداً Optimize کنند: Percentageهای مختلف، پیوند Percentage به Amount و Price فروش، Commission اضافه برای رسیدن به Goalهای مختلف و موارد متعدد دیگر.
حدس بزنید طراحی اولیه چه زمانی شکست؟
Codebase دوباره به Big Ball of Mud غیرقابل مدیریت نزدیک شد. افزودن Feature گرانتر و Bugها بیشتر شدند؛ و وقتی با Money کار میکنید، حتی کوچکترین Bug پیامد بزرگ دارد.
طراحی: بار دوم
مثل Event Crunchers، در نقطهای Team دیگر نتوانست وضعیت را تحمل کند. Code قدیمی دور ریخته شد و Solution از پایه بازنویسی شد؛ این بار به صورت Event-Sourced Domain Model.
و مانند Event Crunchers، Business Domain در ابتدا Supporting تشخیص داده شده بود اما با تکامل System بهتدریج Core شد؛ شرکت راههایی پیدا کرد که از این Processها درآمد ایجاد کند. با این حال تفاوت مهمی میان دو Context وجود داشت.
Ubiquitous Language
برای Bonuses، Team Ubiquitous Language داشت. حتی وقتی Implementation اولیه Active Record بود، داشتن Ubiquitous Language ممکن بود.
با افزایش Complexity دامنه، زبان Domain Expertها نیز پیچیدهتر شد. در نقطهای دیگر نمیشد آن را با Active Record مدل کرد. همین مشاهده به Team اجازه داد نیاز به تغییر Design را بسیار زودتر از Event Crunchers تشخیص دهد. Ubiquitous Language باعث شد Team تلاش نکند «میخ چهارگوش را در سوراخ گرد» جا دهد و زمان و انرژی زیادی ذخیره شد.
درک کلاسیک از DDD
در این مرحله، درک Team از DDD بالاخره به مدل کلاسیک رسید: Ubiquitous Language، Bounded Context و Typeهای مختلف Subdomain که هرکدام مطابق نیاز خود طراحی میشوند؛ شکل A-6.
اما پروژهٔ بعدی مسیر غیرمنتظرهای داشت.
Bounded Context شمارهٔ ۵: Marketing Hub
Management دنبال Vertical سودآور جدیدی میگشت. تصمیم گرفته شد از توان تولید Lead در مقیاس بالا استفاده شود و Leadها به Clientهای کوچکتری فروخته شوند که شرکت پیشتر با آنها کار نمیکرد. پروژه Marketing Hub نام گرفت.
چون Management این Business Domain را فرصت سود جدید تعریف کرده بود، بهوضوح Core Business Domain تلقی شد. از نظر طراحی نیز Team «توپخانهٔ سنگین» را وارد کرد: Event-Sourced Domain Model و CQRS. همزمان Buzzword تازهٔ Microservices محبوب شده بود و Team تصمیم گرفت آن را نیز امتحان کند.
Solution شبیه شکل A-7 بود: Serviceهای کوچک، Database مستقل برای هر Service و ارتباط Synchronous و Asynchronous. روی کاغذ، Design کامل به نظر میرسید. در عمل، نه.
Micro چه چیزی؟
Team سادهلوحانه Microservice را با این فرض پیاده کرد که هرچه Service کوچکتر باشد بهتر است. بنابراین Service Boundary دور Aggregate کشیده شد. به زبان DDD، هر Aggregate یک Bounded Context مستقل شد.
در ابتدا این Design جذاب بود. هر Service بر اساس نیاز خودش Implementation میشد. فقط یکی Event Sourcing داشت و بقیه State-Based Aggregate بودند. علاوه بر آن، همه میتوانستند مستقل Maintain و Evolve شوند.
اما با رشد System، Serviceها بیشتر و بیشتر Chatty شدند. در نهایت تقریباً هر Service برای تکمیل بعضی Operationها به Data همهٔ Serviceهای دیگر نیاز داشت. نتیجه؟ چیزی که قرار بود Decoupled System باشد به Distributed Monolith تبدیل شد: کابوسی مطلق برای Maintenance.
مسئلهٔ واقعی
مشکل بنیادیتری نیز وجود داشت. برای Marketing Hub از پیچیدهترین Patternهای Modeling Business Domain—Domain Model و Event-Sourced Domain Model—استفاده شده بود. Team با دقت Serviceها را طراحی کرده بود، اما تمام این تلاش بیفایده بود.
با وجود اینکه Business، Marketing Hub را Core Subdomain میدانست، Software Aspect آن هیچ Technical Complexity خاصی نداشت. پشت معماری بسیار پیچیده، Business Logic بسیار سادهای قرار داشت که حتی با Active Record معمولی هم قابل پیادهسازی بود.
مزیت تجاری واقعی از Algorithm هوشمند نمیآمد؛ Business میخواست از Relationshipهای موجود با Companyهای دیگر کسب درآمد کند. Technical Complexity چند برابر Business Complexity شد. برای چنین اختلافی از اصطلاح Accidental Complexity استفاده میکنیم. Design اولیه بهوضوح Overengineered بود.
بحث
این پنج Bounded Context—Marketing، CRM، Event Crunchers، Bonuses و Marketing Hub—تجربههایی بودند که بیشترین درس را به Team دادند. Business Domain Marketnovus Contextهای بیشتری داشت، اما نویسنده این پنج مورد را برای تحلیل انتخاب کرده است.
اکنون پرسش این است که استفاده یا استفادهٔ نادرست از عناصر اصلی DDD چگونه نتیجهها را تغییر داد.
Ubiquitous Language
در تجربهٔ نویسنده، Ubiquitous Language «Core Subdomain» خود DDD است. توانایی صحبت با Domain Expertها با همان Language برای Team حیاتی بود و از Test یا Document روش بسیار مؤثرتری برای Share کردن Knowledge محسوب میشد.
وجود Ubiquitous Language همچنین Predictor مهمی برای موفقیت پروژه بود:
- در Marketing، Architecture از کامل بودن فاصله داشت، اما Ubiquitous Language قوی ضعفهای معماری را جبران کرد و اجازه داد هدفهای پروژه محقق شوند.
- در CRM، Team شکست خورد: ناخواسته دو Language برای یک Business Domain وجود داشت. Design ظاهراً درست بود، اما مشکل Communication به آشفتگی بزرگی منجر شد.
- Event Crunchers بهعنوان Supporting Subdomain ساده شروع شد و Team روی Ubiquitous Language سرمایهگذاری نکرد. وقتی Complexity رشد کرد، این تصمیم بهشدت پشیمانکننده شد؛ شروع با Language مشترک میتوانست زمان زیادی ذخیره کند.
- در Bonuses، Business Logic چند مرتبه پیچیدهتر شد، اما Ubiquitous Language به Team اجازه داد نیاز به تغییر Implementation Strategy را بسیار زودتر تشخیص دهد.
نتیجه: Ubiquitous Language اختیاری نیست، فرقی ندارد با Core، Supporting یا Generic Subdomain کار میکنید.
Team اهمیت سرمایهگذاری زودهنگام در Ubiquitous Language را بهصورت سخت آموخت. «اصلاح» Languageای که مدتها در Company استفاده شده نیازمند تلاش و صبر بسیار است؛ همانطور که در CRM رخ داد. Implementation را بالاخره میتوان Fix کرد، اما Language خیلی سختتر تغییر میکند. سالها بعد هنوز بعضی افراد اصطلاحهای متعارض تعریفشده در Implementation اولیه را به کار میبردند.
Subdomainها
همانطور که در فصل ۱ دیدید سه نوع Subdomain داریم: Core، Supporting و Generic. شناسایی Type در طراحی Solution مهم است.
اما تشخیص Type همیشه آسان نیست. Granularity تحلیل باید با Software System مورد ساخت هماهنگ باشد. برای مثال Marketing Hub قرار بود منبع درآمد جدید Company باشد، اما Software Functionality آن در واقع Supporting بود؛ مزیت رقابتی واقعی، Relationshipها و Contractهای موجود با Companyهای دیگر بود. همان Business Initiative در سطح کلان Core به نظر میرسید، اما Software Subdomain آن Supporting بود.
همچنین کافی نیست Type را یک بار شناسایی کنیم. فصل ۱۱ نشان داد Subdomainها میتوانند Evolve شوند. Marketnovus تقریباً همهٔ ترکیبهای تغییر Type را تجربه کرد:
- Event Crunchers و Bonuses هر دو Supporting شروع شدند، اما وقتی Company راههای Monetize کردن Processها را پیدا کرد، Core شدند.
- در Marketing، Creative Catalog داخلی Supporting بود. چند سال بعد Open Source Projectای با Featureهای بیشتر عرضه شد. با جایگزینی Implementation داخلی، Supporting به Generic تبدیل شد.
- در CRM، Algorithm شناسایی امیدبخشترین Lead ابتدا Core بود و طی زمان Refinement شد؛ اما در نهایت با Machine Learning Model روی Managed Cloud Service جایگزین شد. از نظر فنی Core به Generic تبدیل شد.
- Marketing Hub به عنوان Core آغاز شد، اما در نهایت Supporting تشخیص داده شد، چون Competitive Edge در Dimension دیگری قرار داشت.
Type Subdomain روی طیف گستردهای از Design Decisionها اثر دارد. اشتباه در تشخیص Type میتواند بسیار پرهزینه باشد، همانطور که Event Crunchers و Marketing Hub نشان دادند.
نگاشت Design Decision به Subdomain
نویسنده در Marketnovus ترفندی برای Foolproof کردن شناسایی Subdomain پیدا کرد: رابطهٔ Subdomain و Tactical Design Decision را برعکس کنید.
ابتدا Business Logic Implementation Pattern را انتخاب کنید؛ بدون Speculation یا Gold Plating، فقط Patternی که Requirement فعلی را پوشش میدهد. سپس Pattern انتخابشده را به Type مناسب Subdomain نگاشت کنید. در پایان Type حاصل را با Business Vision مقایسه و Verify کنید.
معکوس کردن رابطهٔ Subdomain و Tactical Design یک Dialogue اضافه میان Engineering و Business ایجاد میکند. گاهی Businesspeople همانقدر به ما نیاز دارند که ما به آنها.
اگر Business چیزی را Core میداند اما Engineer میتواند Implementation آن را در یک روز Hack کند، احتمالاً یا باید Subdomainهای ریزتری پیدا شود، یا باید دربارهٔ Viability آن Business سؤالهای جدی مطرح شود.
از طرف دیگر، اگر Business یک Subdomain را Supporting میداند اما فقط با Modeling Techniqueهای پیشرفته مانند Domain Model یا Event-Sourced Domain Model قابل پیادهسازی است، وضعیت جالب میشود:
- ممکن است Businesspeople بیش از حد در Requirementها خلاق شده و Accidental Business Complexity ساخته باشند؛ در این حالت Requirementها احتمالاً باید ساده شوند.
- ممکن است Business هنوز متوجه نشده باشد از این Subdomain برای ساخت Competitive Advantage استفاده میکند. در Bonuses همین اتفاق افتاد. کشف این mismatch میتواند به Business کمک کند Source جدید Profit را سریعتر تشخیص دهد.
درد را نادیده نگیرید
مهمتر از همه، Pain هنگام پیادهسازی Business Logic را نادیده نگیرید. Pain سیگنالی حیاتی برای تکامل Model Business Domain یا Tactical Design Decision است.
اگر مشکل از Tactical Design باشد، یعنی Subdomain تکامل یافته و زمان آن رسیده Type و Implementation Strategy دوباره بررسی شوند. اگر Type تغییر کرده، با Domain Expertها صحبت کنید تا Business Context جدید را بفهمید. اگر Implementation باید برای واقعیت جدید Business بازطراحی شود، از تغییر نترسید. وقتی تصمیم دربارهٔ Modeling Business Logic آگاهانه گرفته شده و همهٔ Optionها شناخته شدهاند، واکنش به تغییر و Refactor به Pattern پیچیدهتر آسانتر است.
Boundaryهای Bounded Context
Marketnovus راهبردهای مختلفی برای تعیین Bounded Context Boundary امتحان کرد:
- Linguistic Boundary: Monolith اولیه به Marketing و CRM تقسیم شد تا Ubiquitous Languageهایشان محافظت شوند.
- Subdomain-Based Boundary: بسیاری Subdomainها مانند Event Crunchers و Bonuses در Bounded Context مستقل پیاده شدند.
- Entity-Based Boundary: در Marketing Hub موفقیت محدودی داشت، هرچند در بعضی پروژههای دیگر مؤثر بود.
- Suicidal Boundary: در Implementation اولیهٔ CRM، یک Aggregate میان دو Bounded Context شکسته شد. نویسنده با طنز میگوید: «این را در خانه امتحان نکنید.»
کدام Strategy توصیه میشود؟ هیچکدام برای همهٔ Caseها مناسب نیست. در تجربهٔ Marketnovus، Extract کردن Service از Service بزرگتر امنتر از شروع با Serviceهای بیش از حد کوچک بود. بنابراین Team ترجیح میداد با Boundaryهای عریضتر آغاز کند و بعد از کسب Domain Knowledge بیشتر آنها را تجزیه کند.
Boundary اولیه چقدر عریض باشد؟ پاسخ به Business Domain برمیگردد: هرچه دربارهٔ Domain کمتر بدانید، Boundary اولیه باید عریضتر باشد.
این Heuristic مفید بود. Marketing و CRM در ابتدا هرکدام چند Subdomain را شامل میشدند. با گذشت زمان Boundaryهای عریض بهتدریج به Microservice تجزیه شدند. همانطور که فصل ۱۴ توضیح میدهد، در طول Evolution در محدودهٔ Safe Boundary ماندند و فقط پس از کسب دانش کافی Refactor کردند.
جمعبندی
در داستان Bounded Contextهای Marketnovus دیدید درک Team از DDD چگونه طی زمان تکامل یافت؛ برای مرور به شکل A-6 رجوع کنید:
- همیشه با ساخت Ubiquitous Language همراه Domain Expertها شروع میکردند تا Business Domain را تا جای ممکن یاد بگیرند.
- وقتی Modelهای متعارض وجود داشت، Solution در امتداد Boundaryهای زبانی Ubiquitous Language به Bounded Context تجزیه میشد.
- Boundary و Type Subdomainها در هر Bounded Context شناسایی میشد.
- برای هر Subdomain، Implementation Strategy با Tactical Design Heuristic انتخاب میشد.
- Type اولیهٔ Subdomain با Type حاصل از Tactical Design مقایسه میشد. اگر mismatch بود، با Business گفتگو میشد. گاهی این Dialogue باعث تغییر Requirement میشد، چون Engineering Perspective تازهای به Product Owner میداد.
- با افزایش Domain Knowledge، در صورت نیاز Bounded Contextها به Contextهای باریکتر تجزیه میشدند.
اگر این دیدگاه نهایی را با نقطهٔ شروع مقایسه کنیم، تفاوت اصلی این است که Team از «Aggregate everywhere» به «Ubiquitous Language everywhere» رسید.
در پایان، نویسنده داستان سرنوشت Marketnovus را نیز تعریف میکند. Company خیلی سریع Profitable شد و در نهایت توسط بزرگترین Client خود Acquire شد. او نمیتواند موفقیت شرکت را فقط به DDD نسبت دهد، اما در تمام آن سالها Company دائماً در «Start-up Mode» بود.
چیزی که در اسرائیل «Start-up Mode» نامیده میشود، در بقیهٔ جهان «Chaos» است: Business Requirement و Priority دائماً تغییر میکند، Timeframeها تهاجمیاند و R&D Team کوچک است. DDD به Team کمک کرد همهٔ این Complexityها را مدیریت کند و Working Software را پیوسته تحویل دهد. وقتی نویسنده به گذشته نگاه میکند، به این نتیجه میرسد شرطبندی روی DDD کاملاً ارزشمند بوده است.