فصل ۴ — یکپارچهسازی زمینههای محدود
الگوی زمینهٔ محدود نهتنها از سازگاری زبان فراگیر محافظت میکند، بلکه مدلسازی را نیز ممکن میسازد. بدون مشخصکردن هدف یک مدل ــ یعنی مرز آن ــ نمیتوان مدلی ساخت. مرز، مسئولیت زبانها را از هم جدا میکند. زبان یک زمینهٔ محدود میتواند دامنهٔ کسبوکار را برای حل مسئلهای خاص مدل کند، در حالی که زمینهٔ محدود دیگر همان موجودیتهای کسبوکار را با مدلی متفاوت و برای حل مسئلهای دیگر نمایش میدهد.
افزون بر این، مدلهای زمینههای محدود متفاوت را میتوان مستقل از یکدیگر تکامل داد و پیادهسازی کرد. بااینحال، خودِ زمینههای محدود مستقل نیستند. همانطور که یک سیستم را نمیتوان از اجزایی ساخت که هیچ تعاملی با یکدیگر ندارند، پیادهسازیهای زمینههای محدود نیز باید برای رسیدن به اهداف کلان سیستم با یکدیگر تعامل کنند. بنابراین همیشه نقاط تماس میان زمینههای محدود وجود دارد. این نقاط تماس قراردادها (Contracts) نامیده میشوند.
نیاز به قرارداد از تفاوت مدلها و زبانهای زمینههای محدود ناشی میشود. چون هر قرارداد بیش از یک طرف را تحت تأثیر قرار میدهد، باید تعریف و هماهنگ شود. همچنین، طبق تعریف، دو زمینهٔ محدود از دو زبان فراگیر متفاوت استفاده میکنند. پس برای یکپارچهسازی از کدام زبان استفاده خواهد شد؟ این دغدغهها باید در طراحی راهحل ارزیابی و حل شوند.
در این فصل الگوهای DDD برای تعریف روابط و یکپارچهسازی میان زمینههای محدود را میآموزید. این الگوها بر ماهیت همکاری تیمهایی استوارند که روی زمینههای محدود کار میکنند. الگوها را در سه گروه بررسی میکنیم: همکاری (Cooperation)، مشتری–تأمینکننده (Customer–Supplier) و راههای جداگانه (Separate Ways).
همکاری
الگوهای همکاری برای زمینههای محدودی مناسباند که تیمهای پیادهکنندهٔ آنها ارتباط تثبیتشده و مؤثری با یکدیگر دارند.
سادهترین حالت زمانی است که یک تیم هر دو زمینهٔ محدود را پیادهسازی میکند. این وضعیت دربارهٔ تیمهایی با اهداف وابسته نیز صدق میکند؛ یعنی موفقیت یک تیم به موفقیت دیگری وابسته است و برعکس. معیار اصلی در اینجا کیفیت ارتباط و همکاری تیمهاست.
دو الگوی DDD مناسب برای تیمهای همکار عبارتاند از Partnership و Shared Kernel.
مشارکت (Partnership)
در مدل مشارکت، یکپارچهسازی زمینههای محدود بهصورت موردی و با هماهنگی مستقیم انجام میشود. یک تیم میتواند تغییر API را به تیم دیگر اطلاع دهد و تیم دوم نیز بدون تنش یا تعارض همکاری کرده و خود را با تغییر وفق دهد؛ شکل ۴-۱ این مدل را نشان میدهد.
شکل ۴-۱ — مدل مشارکت.
هماهنگی یکپارچهسازی در اینجا دوطرفه است. هیچ تیمی زبانی را که برای تعریف قراردادها استفاده میشود به دیگری تحمیل نمیکند. تیمها میتوانند تفاوتها را حل کنند و مناسبترین راهحل را با هم انتخاب کنند. هر دو طرف نیز برای رفع مشکلات یکپارچهسازی همکاری میکنند و هیچکدام علاقهای به مسدود کردن دیگری ندارد.
برای موفقیت چنین رابطهای، شیوههای همکاری تثبیتشده، تعهد زیاد و همگامسازی مکرر میان تیمها لازم است. از منظر فنی نیز یکپارچهسازی پیوستهٔ تغییرات هر دو تیم ضروری است تا چرخهٔ بازخورد یکپارچهسازی کوتاهتر شود.
این الگو ممکن است برای تیمهای جغرافیاییِ پراکنده مناسب نباشد، زیرا همگامسازی و ارتباط را دشوار میکند.
هستهٔ مشترک (Shared Kernel)
با وجود اینکه زمینههای محدود مرز مدل هستند، ممکن است همان مدل یک زیردامنه یا بخشی از آن در چند زمینهٔ محدود پیادهسازی شود. باید تأکید کرد که مدل مشترک مطابق نیازهای همهٔ زمینههای محدود طراحی میشود و باید در تمام زمینههایی که از آن استفاده میکنند سازگار باقی بماند.
برای نمونه، سامانهای سازمانی را در نظر بگیرید که از مدلی سفارشی برای مدیریت مجوزهای کاربران استفاده میکند. هر کاربر میتواند مجوزی را مستقیماً دریافت کند یا آن را از واحد سازمانی خود به ارث ببرد. هر زمینهٔ محدود نیز میتواند مدل مجوزدهی را تغییر دهد و تغییر ایجادشده باید بر تمام زمینههای محدودی که از همان مدل استفاده میکنند اثر بگذارد؛ شکل ۴-۲.
شکل ۴-۲ — هستهٔ مشترک.
دامنهٔ مشترک
مدل همپوشان، چرخههای عمر زمینههای محدود شرکتکننده را به هم وابسته میکند. تغییر در مدل مشترک بلافاصله بر تمام زمینهها اثر میگذارد. بنابراین برای کاهش اثرهای آبشاری تغییرات، مدل همپوشان باید محدود باشد و فقط آن بخشی از مدل را در معرض اشتراک بگذارد که واقعاً باید در هر دو زمینه پیادهسازی شود. در حالت ایدئال، هستهٔ مشترک فقط شامل قراردادهای یکپارچهسازی و ساختارهای دادهای است که قرار است از مرز زمینههای محدود عبور کنند.
پیادهسازی
هستهٔ مشترک باید طوری پیادهسازی شود که هر تغییر در کد منبع آن فوراً در تمام زمینههای محدود استفادهکننده منعکس شود.
اگر سازمان از رویکرد مخزن یکپارچه (Mono-repository) استفاده میکند، میتوان همان فایلهای منبع را از چند زمینهٔ محدود ارجاع داد. اگر مخزن مشترک ممکن نباشد، هستهٔ مشترک را میتوان به پروژهای مستقل استخراج کرد و بهصورت کتابخانهٔ پیوندی در زمینههای محدود ارجاع داد. در هر دو حالت، هر تغییر در هستهٔ مشترک باید آزمونهای یکپارچهسازی تمام زمینههای تحت تأثیر را اجرا کند.
یکپارچهسازی پیوستهٔ تغییرات ضروری است، زیرا هستهٔ مشترک متعلق به چند زمینهٔ محدود است. اگر تغییرات آن به تمام زمینهها منتشر نشود، مدل ناسازگار میشود: بعضی زمینهها ممکن است به نسخهٔ قدیمی هسته تکیه کنند و این موضوع به فساد داده یا خطاهای زمان اجرا منجر شود.
چه زمانی از هستهٔ مشترک استفاده کنیم؟
معیار کلی کاربرد این الگو، مقایسهٔ هزینهٔ تکرار با هزینهٔ هماهنگی است. چون هستهٔ مشترک وابستگی قدرتمندی میان زمینههای مشارکتکننده ایجاد میکند، فقط زمانی باید به کار رود که هزینهٔ تکرار از هزینهٔ هماهنگی بیشتر باشد؛ یعنی زمانی که یکپارچهسازی جداگانهٔ تغییرات اعمالشده به مدل مشترک توسط هر دو زمینه از هماهنگکردن تغییرات در یک پایگاه کد مشترک پرهزینهتر باشد.
تفاوت هزینهٔ یکپارچهسازی و تکرار به نوسان مدل وابسته است. هرچه مدل بیشتر تغییر کند، هزینهٔ یکپارچهسازی بیشتر میشود. ازاینرو هستهٔ مشترک بهطور طبیعی برای زیردامنههایی مطرح میشود که بیشترین تغییر را دارند، یعنی زیردامنههای هستهای.
از یک منظر، هستهٔ مشترک با اصل مالکیت زمینهٔ محدود در فصل قبل تعارض دارد. اگر زمینههای شرکتکننده توسط یک تیم واحد پیادهسازی نشوند، مدل همپوشان عملاً توسط چند تیم توسعه داده میشود.
به همین دلیل استفاده از هستهٔ مشترک باید توجیه شود. این یک استثنای عملگرایانه است و باید با دقت بررسی شود. یکی از موارد رایج، زمانی است که مسائل ارتباطی یا همکاری اجازهٔ Partnership را نمیدهد؛ مثلاً بهدلیل فاصلهٔ جغرافیایی یا سیاستهای سازمانی. پیادهسازی قابلیتهای نزدیک بدون هماهنگی مناسب به مشکلات یکپارچهسازی، مدلهای ناهمگام و اختلافنظر دربارهٔ «مدل بهتر» منجر میشود. کوچک نگهداشتن هستهٔ مشترک، دامنهٔ اثرهای آبشاری را محدود میکند و اجرای آزمونهای یکپارچهسازی برای هر تغییر نیز مشکلات را زود آشکار میسازد.
کاربرد رایج دیگر ــ هرچند موقت ــ نوسازی تدریجی یک سامانهٔ قدیمی است. در این حالت، کد مشترک میتواند یک راهحل میانی عملگرایانه برای تجزیهٔ تدریجی سیستم به زمینههای محدود باشد.
هستهٔ مشترک همچنین برای یکپارچهسازی زمینههایی که توسط یک تیم واحد مالکیت و پیادهسازی میشوند مناسب است. در چنین حالتی یکپارچهسازی موردی Partnership ممکن است در طول زمان مرزها را محو کند. هستهٔ مشترک میتواند قراردادهای یکپارچهسازی را صریح نگه دارد.
مشتری–تأمینکننده
گروه دوم الگوهای همکاری، الگوهای مشتری–تأمینکننده است. همانطور که شکل ۴-۳ نشان میدهد، یکی از زمینههای محدود ــ تأمینکننده ــ سرویسی به مشتریان خود ارائه میدهد. ارائهدهندهٔ سرویس بالادست (Upstream) و مشتری یا مصرفکننده پاییندست (Downstream) است.
شکل ۴-۳ — رابطهٔ مشتری–تأمینکننده.
برخلاف حالت همکاری، هر دو تیم بالادست و پاییندست میتوانند مستقل از دیگری موفق شوند. در نتیجه معمولاً عدم توازن قدرت وجود دارد: یا تیم بالادست یا تیم پاییندست میتواند قرارداد یکپارچهسازی را تحمیل کند.
سه الگو برای چنین اختلاف قدرتی عبارتاند از Conformist، Anticorruption Layer و Open-Host Service.
همرنگشونده (Conformist)
در برخی موارد قدرت در اختیار تیم بالادست است و انگیزهٔ واقعی برای پشتیبانی از نیازهای مشتریان ندارد. در عوض، قرارداد یکپارچهسازی را مطابق مدل خودش عرضه میکند: «همین است که هست». چنین عدم توازنی ممکن است بهدلیل یکپارچهشدن با ارائهدهندهای بیرون از سازمان یا سیاستهای درونی سازمان باشد.
اگر تیم پاییندست بتواند مدل تیم بالادست را بپذیرد، رابطهٔ دو زمینهٔ محدود Conformist نامیده میشود. پاییندست خود را با مدل زمینهٔ بالادست وفق میدهد؛ شکل ۴-۴.
شکل ۴-۴ — رابطهٔ Conformist.
تصمیم تیم پاییندست برای واگذاری بخشی از استقلال خود میتواند دلایل مختلف داشته باشد. شاید قرارداد بالادست یک استاندارد صنعتی و مدلی تثبیتشده باشد، یا صرفاً برای نیازهای پاییندست بهاندازهٔ کافی خوب باشد.
لایهٔ ضدفساد (Anticorruption Layer یا ACL)
در این رابطه نیز توازن قدرت به سمت سرویس بالادست متمایل است، اما این بار زمینهٔ پاییندست حاضر نیست با مدل بالادست همرنگ شود. در عوض، از طریق یک لایهٔ ضدفساد، مدل بالادست را به مدلی متناسب با نیازهای خودش ترجمه میکند؛ شکل ۴-۵.
شکل ۴-۵ — یکپارچهسازی از طریق لایهٔ ضدفساد.
این الگو برای سناریوهایی مناسب است که پذیرفتن مدل تأمینکننده مطلوب نیست یا ارزش هزینه را ندارد، از جمله:
- وقتی زمینهٔ پاییندست شامل زیردامنهٔ هستهای است: مدل زیردامنهٔ هستهای به توجه بیشتری نیاز دارد و تبعیت از مدل تأمینکننده ممکن است مدلسازی دامنهٔ مسئله را مختل کند.
- وقتی مدل بالادست برای مصرفکننده ناکارآمد یا نامناسب است: اگر زمینهای خود را با یک آشفتگی وفق دهد، خطر دارد خودش نیز آشفته شود؛ چیزی که هنگام یکپارچهسازی با سامانههای قدیمی رایج است.
- وقتی قرارداد تأمینکننده زیاد تغییر میکند: مصرفکننده میخواهد مدل خود را از تغییرات پرتکرار محافظت کند. در ACL، تغییرات مدل تأمینکننده فقط سازوکار ترجمه را تحت تأثیر قرار میدهد.
از منظر مدلسازی، ترجمهٔ مدل تأمینکننده، مصرفکنندهٔ پاییندست را از مفاهیم بیگانه و نامرتبط با زمینهٔ خودش جدا میکند و در نتیجه زبان فراگیر و مدل آن را سادهتر میسازد. در فصل ۹ راههای مختلف پیادهسازی ACL را بررسی خواهیم کرد.
سرویس میزبان باز (Open-Host Service)
این الگو زمانی کاربرد دارد که قدرت بیشتر در اختیار مصرفکنندگان است. تأمینکننده علاقهمند است از مصرفکنندگان خود محافظت کند و بهترین سرویس ممکن را ارائه دهد.
برای محافظت از مصرفکنندگان در برابر تغییرات مدل پیادهسازی، تأمینکنندهٔ بالادست مدل داخلی خود را از رابط عمومی جدا میکند. این جداسازی اجازه میدهد مدل پیادهسازی و مدل عمومی با سرعتهای متفاوت تکامل پیدا کنند؛ شکل ۴-۶.
شکل ۴-۶ — یکپارچهسازی از طریق Open-Host Service.
رابط عمومی تأمینکننده قرار نیست از زبان فراگیر داخلی آن تبعیت کند. هدف رابط عمومی، ارائهٔ پروتکلی مناسب برای مصرفکنندگان و بیانشده با زبانی مناسب یکپارچهسازی است. این پروتکل عمومی زبان منتشرشده (Published Language) نامیده میشود.
از یک منظر Open-Host Service وارونهٔ ACL است: بهجای مصرفکننده، این تأمینکننده است که ترجمهٔ مدل داخلی خود را پیادهسازی میکند.
جداکردن مدل پیادهسازی و مدل یکپارچهسازی، آزادی تکامل داخلی را بدون اثرگذاری بر پاییندست فراهم میکند؛ البته به شرطی که مدل داخلی جدید همچنان به زبان منتشرشدهای که مصرفکنندگان استفاده میکنند قابل ترجمه باشد.
همچنین این جداسازی اجازه میدهد زمینهٔ بالادست چند نسخه از زبان منتشرشده را همزمان ارائه کند تا مصرفکنندگان بتوانند تدریجی به نسخهٔ جدید مهاجرت کنند؛ شکل ۴-۷.
شکل ۴-۷ — Open-Host Service با چند نسخه از زبان منتشرشده.
راههای جداگانه (Separate Ways)
آخرین گزینهٔ همکاری این است که اصلاً همکاری نکنیم. این الگو در وضعیتهایی رخ میدهد که تیمها نمیخواهند یا نمیتوانند همکاری کنند.
مشکلات ارتباطی
یک دلیل رایج برای کنارگذاشتن همکاری، دشواری ارتباط ناشی از اندازهٔ سازمان یا سیاستهای داخلی است. وقتی تیمها بهسختی بر سر تصمیمها توافق میکنند، ممکن است جدا شدن و تکرار قابلیت در چند زمینهٔ محدود از نظر اقتصادی بهصرفهتر باشد.
زیردامنههای عمومی
ماهیت زیردامنهٔ تکرارشونده نیز میتواند دلیل انتخاب Separate Ways باشد. اگر زیردامنه عمومی است و راهحل عمومی بهآسانی یکپارچه میشود، ممکن است ادغام محلی آن در هر زمینهٔ محدود ارزانتر باشد. برای مثال، چارچوب Logging: منطقی نیست یک زمینه آن را بهصورت سرویس برای بقیه ارائه کند، چون پیچیدگی اضافهٔ یکپارچهسازی از مزیت حذف تکرار بیشتر میشود.
تفاوت مدلها
گاهی مدلهای زمینههای محدود آنقدر متفاوتاند که رابطهٔ Conformist ممکن نیست و ساخت ACL نیز از تکرار قابلیت گرانتر است. در چنین حالتی نیز Separate Ways بهصرفهتر است.
نقشهٔ زمینه (Context Map)
پس از تحلیل الگوهای یکپارچهسازی میان زمینههای محدود سیستم، میتوان آنها را روی یک نقشهٔ زمینه نمایش داد؛ شکل ۴-۸.
شکل ۴-۸ — نقشهٔ زمینه.
نقشهٔ زمینه نمایش بصری زمینههای محدود سیستم و یکپارچهسازیهای میان آنهاست و در چند سطح دید راهبردی ارزشمندی ارائه میکند:
- طراحی سطح بالا: نمایی کلی از اجزای سیستم و مدلهایی که پیادهسازی میکنند.
- الگوهای ارتباطی: نشان میدهد کدام تیمها همکاری نزدیک دارند و کدامها الگوهای «کمصمیمانهتر» مانند ACL یا Separate Ways را ترجیح میدهند.
- مسائل سازمانی: میتواند مشکل سازمانی را آشکار کند؛ مثلاً اگر همهٔ مصرفکنندگان پاییندست یک تیم خاص ناچار به ACL باشند یا همهٔ Separate Waysها پیرامون یک تیم متمرکز شده باشند.
نگهداری
در حالت ایدئال، نقشهٔ زمینه باید از آغاز پروژه ایجاد و با اضافهشدن زمینههای جدید یا تغییر یکپارچهسازیها بهروز شود. چون اطلاعات آن از چند تیم میآید، نگهداری بهتر است مسئولیتی مشترک باشد: هر تیم مسئول بهروزرسانی یکپارچهسازیهای خودش با دیگر زمینههاست. میتوان نقشه را بهصورت کد نیز با ابزاری مانند Context Mapper نگهداری کرد.
محدودیتها
ترسیم نقشهٔ زمینه میتواند دشوار باشد. وقتی یک زمینهٔ محدود چند زیردامنه را شامل میشود، چند الگوی یکپارچهسازی ممکن است همزمان وجود داشته باشد. شکل ۴-۹ نمونهای با Partnership و ACL میان دو زمینه را نشان میدهد. حتی اگر هر زمینه فقط یک زیردامنه داشته باشد، ماژولهای مختلف همان زیردامنه ممکن است راهبردهای یکپارچهسازی متفاوتی بخواهند.
شکل ۴-۹ — نقشهٔ زمینهٔ پیچیده.
جمعبندی
زمینههای محدود مستقل نیستند و باید با یکدیگر تعامل کنند. الگوهای اصلی یکپارچهسازی عبارتاند از:
- Partnership: یکپارچهسازی بهشکل موردی و با همکاری مستقیم.
- Shared Kernel: دو یا چند زمینه یک مدل همپوشان محدود را به اشتراک میگذارند که متعلق به همهٔ آنهاست.
- Conformist: مصرفکننده با مدل ارائهدهندهٔ سرویس همرنگ میشود.
- Anticorruption Layer: مصرفکننده مدل ارائهدهنده را به مدلی متناسب با نیاز خود ترجمه میکند.
- Open-Host Service: ارائهدهنده زبان منتشرشدهای را عرضه میکند که برای نیازهای مصرفکنندگان بهینه شده است.
- Separate Ways: هزینهٔ تکرار یک قابلیت از هزینهٔ همکاری و یکپارچهسازی کمتر است.
یکپارچهسازی زمینهها را میتوان روی Context Map ترسیم کرد تا تصویری از طراحی سطح بالا، الگوهای ارتباطی و مسائل سازمانی ارائه شود.
اکنون که ابزارها و تکنیکهای DDD برای تحلیل و مدلسازی دامنههای کسبوکار را آموختید، از راهبرد به تاکتیک میرویم. در بخش دوم روشهای پیادهسازی منطق دامنه، سازماندهی معماری سطح بالا و هماهنگکردن ارتباط میان اجزای سیستم بررسی خواهند شد.
تمرینها
- کدام الگوی یکپارچهسازی هرگز نباید برای یک زیردامنهٔ هستهای استفاده شود؟
- Shared Kernel
- Open-Host Service
- Anticorruption Layer
- Separate Ways
- کدام زیردامنهٔ پاییندست احتمال بیشتری دارد ACL پیادهسازی کند؟
- هستهای
- پشتیبان
- عمومی
- B و C
- کدام زیردامنهٔ بالادست احتمال بیشتری دارد Open-Host Service پیادهسازی کند؟
- هستهای
- پشتیبان
- عمومی
- A و B
- کدام الگوی یکپارچهسازی، از یک منظر، مرزهای مالکیت زمینهٔ محدود را نقض میکند؟
- Partnership
- Shared Kernel
- Separate Ways
- هیچ الگوی یکپارچهسازی هرگز نباید مرز مالکیت را بشکند.
بخش دوم — طراحی تاکتیکی
در بخش اول دربارهٔ «چه چیزی» و «چرا»ی نرمافزار صحبت کردیم: آموختید دامنههای کسبوکار را تحلیل کنید، زیردامنهها و ارزش راهبردی آنها را تشخیص دهید و دانش دامنه را به طراحی زمینههای محدود، یعنی اجزای نرمافزاری که مدلهای مختلف دامنه را پیادهسازی میکنند، تبدیل کنید.
در این بخش از راهبرد به تاکتیک میرویم؛ یعنی «چگونه»ی طراحی نرمافزار:
- در فصلهای ۵ تا ۷ الگوهای پیادهسازی منطق کسبوکار را میآموزید که به کد اجازه میدهند زبان فراگیر زمینهٔ محدود را بیان کند. فصل ۵ دو الگوی مناسب برای منطق نسبتاً ساده، Transaction Script و Active Record، را معرفی میکند. فصل ۶ به مسائل دشوارتر و Domain Model میپردازد و فصل ۷ با مدلکردن بُعد زمان Domain Model را گسترش میدهد.
- در فصل ۸ روشهای سازماندهی معماری زمینهٔ محدود را با Layered Architecture، Ports & Adapters و CQRS بررسی خواهیم کرد و کاربرد هرکدام را میآموزید.
- فصل ۹ دغدغههای فنی و راهبردهای پیادهسازی برای هماهنگکردن تعامل اجزای سیستم را بررسی میکند: پشتیبانی از الگوهای یکپارچهسازی، انتشار قابل اتکای پیام و تعریف گردشکارهای پیچیدهٔ میانجزئی.
شکلها و جدولهای منبع
برای حفظ نمایش بصری دقیق عناصر گرافیکی، بازنمایی صفحههای دارای شکل یا جدول منبع در ادامه آمده است. متن آموزشی فصل در بخشهای بالا به فارسی ترجمه شده است.
بازنمایی صفحهٔ 76 منبع برای حفظ دقیق شکل/جدول اصلی. Figure 4-1. The partnership model
بازنمایی صفحهٔ 77 منبع برای حفظ دقیق شکل/جدول اصلی. Figure 4-2. Shared kernel
بازنمایی صفحهٔ 79 منبع برای حفظ دقیق شکل/جدول اصلی. Figure 4-3. Customer–supplier relationship | Figure 4-4. Conformist relationship
بازنمایی صفحهٔ 80 منبع برای حفظ دقیق شکل/جدول اصلی. Figure 4-5. | Figure 4-5. Integration through an anticorruption layer
بازنمایی صفحهٔ 81 منبع برای حفظ دقیق شکل/جدول اصلی. Figure 4-6. Integration through an open-host service
بازنمایی صفحهٔ 82 منبع برای حفظ دقیق شکل/جدول اصلی. Figure 4-7. Open-host service exposing multiple versions of the published language
بازنمایی صفحهٔ 83 منبع برای حفظ دقیق شکل/جدول اصلی. Figure 4-8. Context map
بازنمایی صفحهٔ 84 منبع برای حفظ دقیق شکل/جدول اصلی. Figure 4-9. Complicated context map