هر تیم توسعهای، صرفنظر از اینکه اعضایش چه میزان تجربه دارند یا با چه ابزارهایی کار میکنند، یک هدف مشترک دارد: ساختن سیستمی که «منسجم» باشد. انسجام یعنی بخشهای مختلف سیستم با هم هماهنگ باشند، تغییر یک بخش باعث فروپاشی بخش دیگر نشود، و توسعهدهندگان جدید بتوانند با خواندن کد، منطق آن را درک کنند.
اما «انسجام» یک مفهوم واحد نیست. مثل یک تندیس است که از زوایای مختلف، شکلهای متفاوتی به خود میگیرد. میتوان از یک زاویه ایستاد و گفت: «انسجام یعنی ساختار داده بهینه و بینقص.» میتوان از زاویهای دیگر ایستاد و گفت: «انسجام یعنی قواعد کسبوکار محکم و تغییرناپذیر.»
هر دو زاویه، بخشی از حقیقت را نشان میدهند. و هر دو میتوانند به سیستمهای موفقی ختم شوند. نکتهٔ ظریف اینجاست که ما گاهی ناخودآگاه جای پای خود را از یک زاویه به زاویهٔ دیگر تغییر میدهیم، بیآنکه متوجه تغییر منظری که رخ داده است بشویم. این جابهجایی ناآگاهانه میتواند ناهماهنگیهایی در سیستم ایجاد کند که درست برخلاف هدف اولیهٔ ما یعنی «انسجام» عمل میکنند.
هدف از این بحث، قضاوت هیچ زاویهٔ دیدی نیست. هدف، شفافسازی این است که ما از کدام زاویه به مسائل نگاه میکنیم، و آیا این زاویه با آنچه برای این پروژه در نظر گرفتهایم همخوانی دارد یا خیر.
تصور کنید میخواهیم یک محلهٔ مسکونی طراحی کنیم. دو معمار برجسته، دو طرح پیشنهاد میدهند:
معمار اول باور دارد که روح یک محله در شبکهٔ دسترسی آن است. او از طراحی خیابانها، مسیرهای پیادهروی و میدانها شروع میکند. ساختمانها را در زمینهای باقیمانده میان این شبکه میچیند. طرح او منظم، قابل پیشبینی و از نظر ترافیکی بهینه است. هر ساختمان دقیقاً در یک بلوک مشخص قرار دارد و هیچ بنبستی وجود ندارد.
معمار دوم باور دارد که روح یک محله در خود ساختمانها و فضاهای زندگی است. او از طراحی خانهها، حیاطها و فضاهای جمعی شروع میکند. مسیرها را طوری میسازد که این فضاها را به هم وصل کنند، حتی اگر مسیرش پیچدرپیچ و غیرمستقیم باشد. طرح او ممکن است از بالا نامنظم به نظر برسد، اما زندگی در آن گرم و انسانی است.
هر دو میتوانند محلههای بسیار موفقی بسازند. ساکنان هر دو محله میتوانند خوشحال باشند. اما مشکل از جایی شروع میشود که در میانهٔ ساخت محلهٔ دوم، یک مهندس ترافیک از راه برسد و با نگاه به نقشه بگوید: «چرا این مسیرها اینقدر پیچیده است؟ بیایید یک شاهراه مستقیم از وسط این خانهها عبور دهیم تا رفتوآمد سریعتر شود.» این تصمیم، هرچند از منظر شبکهٔ دسترسی «منطقی» و «بهینه» است، اما انسجام محلهٔ دوم را نابود میکند. آن شاهراه، حیاطها را میشکافد، آرامش را میگیرد و زندگی را از محله بیرون میبرد.
در طراحی نرمافزار نیز ما با دو «محور انسجام» روبرو هستیم. میتوانیم انسجام را حول ساختار داده بنا کنیم، یا حول قواعد رفتار. هر دو میتوانند سیستمهای موفقی بسازند. اما یک تیم باید بداند که محور اصلیاش کدام است، و مراقب باشد که در میانهٔ راه، یک «شاهراه» از جنس دیگر در آن نکشد.
وقتی انسجام را حول داده بنا میکنیم، زیبایی را در ساختار ذخیرهسازی میبینیم. جداول مثل بلوکهای منظم شهری چیده میشوند. هر داده یک خانهٔ مشخص دارد و در هیچ جای دیگری تکرار نمیشود. روابط بین موجودیتها با کلیدهای خارجی دقیقاً مدل میشود. افزونگی یک گناه است و نرمالسازی یک فضیلت.
در این جهان، «سادگی» یعنی جداول کمتر، کوئریهای مستقیمتر، و روابط شفافتر. وقتی میخواهیم گزارشی از سیستم بگیریم، کافی است چند جدول را با هم Join کنیم و پاسخ را در چند میلیثانیه دریافت کنیم. تغییر یک داده در یک جا، بهطور خودکار در همهٔ کوئریها منعکس میشود.
این رویکرد، قدرتی انکارناپذیر دارد. برای سیستمهایی که قلب آنها «داده» است — مثل سیستمهای گزارشگیری، انبارداری، یا حسابداری — این رویکرد بهترین انتخاب است. در این سیستمها، قواعد تجاری یا وجود ندارند، یا آنقدر سادهاند که میتوان آنها را در چند سرویس کوچک جای داد.
اما نوع دیگری از انسجام وجود دارد. وقتی انسجام را حول رفتار بنا میکنیم، زیبایی را در کپسولهسازی قواعد تجاری میبینیم. هر قاعدهٔ تجاری یک «خانه» دارد — یک Aggregate — که درون آن محافظت میشود. ساختار داده تابعی از این قواعد است، نه برعکس.
در این جهان، «سادگی» یعنی وقتی یک قاعدهٔ تجاری تغییر میکند، فقط یک فایل را عوض کنی. سادگی یعنی یک توسعهدهندهٔ جدید با خواندن یک Aggregate، کل منطق آن بخش از سیستم را بفهمد، بدون آنکه نیاز باشد در چندین سرویس و کنترلر دنبال تکههای پراکندهٔ آن منطق بگردد.
این رویکرد ممکن است از نظر یک ناظر دادهمحور «نامنظم» به نظر برسد. ممکن است دو جدول با ساختار مشابه ببینید که میتوانستند در یک جدول ادغام شوند. ممکن است دادهای را ببینید که در دو جا ذخیره شده است. اما اینها «افزونگی» نیستند — اینها «بهای آگاهانه»ای هستند که برای حفظ یکپارچگی رفتار پرداخته شده است. درست مثل مسیرهای پیچدرپیچ محلهٔ دوم که بهای حفظ حیاطها و فضاهای زندگیاند.
این رویکرد برای سیستمهایی قدرتمند است که قلب آنها «قواعد تجاری» است — سیستمهایی که در آنها، «چه کاری انجام میشود» مهمتر از «چه دادهای ذخیره میشود» است.
ذهن ما ماشینهای تشخیص الگوی شگفتانگیزی است. هر بار که یک مسئله را به روش خاصی حل میکنیم و نتیجه میگیریم، یک مسیر عصبی در مغز ما تقویت میشود. هر بار که یک الگوی طراحی را به کار میبریم و پروژه با موفقیت تحویل داده میشود، آن الگو برای ما «طبیعیتر» و «بدیهیتر» میشود.
این یک نقطهقوت است — چون به ما امکان میدهد بدون تحلیل طولانی، سریع تصمیم بگیریم. اما میتواند یک نقطهٔ کور نیز باشد. مسیر عصبیای که بارها تقویت شده، آنقدر سریع فعال میشود که ما حتی متوجه نمیشویم «انتخابی» صورت گرفته است. راهحل آشنا، قبل از آنکه فرصت کنیم از زاویهای دیگر به مسئله نگاه کنیم، خودش را به عنوان «تنها راه منطقی» تحمیل میکند.
برای کسی که سالها با لنز «انسجام داده» کار کرده و پروژههای موفقی با آن تحویل داده، دیدن یک جدول اضافی یا یک رابطهٔ غیرنرمال مثل شنیدن یک نت ناهماهنگ در یک سمفونی است. ذهن او بیدرنگ فریاد میزند: «این را باید نرمالسازی کنی!» و این فریاد، در بسیاری از مواقع، کاملاً درست است. اما نه همیشه.
تصور کنید یک موسیقیدان کلاسیک که تمام عمرش را صرف نواختن سمفونیهای باخ کرده، ناگهان به یک کنسرت جز دعوت شود. اولین نتهای بداههنوازی ممکن است برای گوش او «اشتباه» به نظر برسند. اما این «اشتباه» نیست — این یک «زبان موسیقیایی متفاوت» است. اگر او اصرار کند که نوازندهٔ جز باید دقیقاً مطابق پارتیتور بنوازد، نه تنها به موسیقی جز کمک نکرده، که روح آن را نابود کرده است.
در تیمهای ما نیز چنین است. اگر تیم در سطح کلان، «زبان موسیقیایی» خود را «انسجام رفتار» انتخاب کرده باشد، اما یکی از اعضا ناخودآگاه با گوش «انسجام داده» به کد گوش دهد، پیشنهادهایی خواهد داد که در چارچوب خودش کاملاً درستاند — اما با زبان کلی پروژه ناهماهنگ.
بیایید از فضای انتزاعی فاصله بگیریم و یک مسئلهٔ طراحی را بررسی کنیم — نه یک مسئلهٔ واقعی از پروژه، که یک مدل سادهشده که میتواند در هر سیستمی رخ دهد.
تصور کنید در یک سیستم، دو نوع «سند» داریم: سند داخلی و سند مشتری. این دو، ساختار بسیار مشابهی دارند. هر دو یک عنوان دارند، یک متن، یک تاریخ ایجاد. اما یک تفاوت حیاتی وجود دارد: سند مشتری باید حتماً به یک مشتری معتبر در سیستم متصل باشد. اگر مشتری حذف شود، سند مشتری نمیتواند بدون او وجود داشته باشد. اما سند داخلی چنین الزامی ندارد — میتواند مستقل باشد.
حال، دو ذهنیت متفاوت به این مسئله نگاه میکنند:
ذهنیت اول به ساختار نگاه میکند. میگوید: «اینها که یک چیز هستند. یک جدول Documents میسازم با یک فیلد Type که مشخص کند داخلی است یا مشتری. یک فیلد CustomerId هم میگذارم که برای اسناد داخلی NULL باشد. ساده، آشنا، بدون تکرار.»
این راهحل چه بهایی دارد؟ قاعدهٔ «سند مشتری باید مشتری معتبر داشته باشد» دیگر در هیچ ساختاری تضمین نمیشود. به یک if در یک سرویس تبدیل میشود. هر توسعهدهندهای که یک سند مشتری جدید میسازد، باید «یادش باشد» که این if را چک کند. تست این قاعده، نیازمند Setup کامل Application و دیتابیس است. و یک روز، یک همکار خسته، این چک را فراموش خواهد کرد — نه از سر بیدقتی، که از سر انسانی بودن.
ذهنیت دوم به رفتار نگاه میکند. میگوید: «اینها دو چیز متفاوتند. شبیه به نظر میرسند، اما قاعدهٔ تجاری آنها را از هم جدا میکند. دو Aggregate میسازم: InternalDocument و CustomerDocument. دومی در سازندهٔ خودش چک میکند که CustomerId معتبر باشد و اگر حذف شود، خودش را هم حذف میکند.»
این راهحل چه بهایی دارد؟ دو جدول با ساختار مشابه. ممکن است از نظر یک ناظر دادهمحور، «افزونگی» به نظر برسد. اما آن قاعدهٔ حیاتی، برای همیشه در یک مکان کپسوله شده است. هیچ توسعهدهندهای، هرچقدر هم خسته، نمیتواند ناآگاهانه آن را نقض کند. کد، خودش از خودش محافظت میکند.
یکی از لغزندهترین کلمات در طراحی نرمافزار «سادگی» است. ما اغلب میگوییم: «این راهحل سادهتر است.» اما سادگی یک مفهوم نسبی است. بستگی دارد از کدام زاویه نگاه کنید.
از زاویهٔ انسجام داده، راهحل اول سادهتر است: یک جدول، یک سرویس، یک کوئری.
از زاویهٔ انسجام رفتار، راهحل دوم سادهتر است: یک قانون، یک مکان، یک تست.
هر دو سادهاند. اما «سادگی»ای که انتخاب میکنیم، مسیر آیندهٔ سیستم را شکل میدهد. اگر امروز به خاطر سادگی ساختار، منطق تجاری را در لایههای بالاتر پخش کنیم، فردا که سیستم پیچیدهتر شد، آن سادگی اولیه به «بدهی فنی» تبدیل میشود. در مقابل، اگر امروز به خاطر سادگی رفتار، یک جدول اضافی بسازیم، شاید در کوتاهمدت هزینهٔ بیشتری بپردازیم، اما در بلندمدت سیستم مانع از خطاهای پنهان میشود.
وقتی یک تصمیم طراحی میگیریم، فقط هزینهٔ امروز را نمیپردازیم. بهرهٔ این هزینه را در ماهها و سالهای آینده نیز پرداخت خواهیم کرد.
بیایید تصور کنیم که راهحل اول (جدول یکپارچه با Type) را انتخاب کردهایم. امروز همه چیز خوب است. یک if نوشتهایم و کار میکند. اما فردا یک توسعهدهندهٔ جدید به تیم ملحق میشود. او نمیداند که «سند مشتری باید مشتری معتبر داشته باشد». این قانون در کد نوشته نشده — در یک سرویس Application پنهان است که او شاید هرگز آن را نخواند. پسفردا یک API جدید برای ایجاد سند مشتری مینویسیم. توسعهدهنده فراموش میکند که این چک را دوباره پیادهسازی کند. و یک روز، دادهای فاسد وارد سیستم میشود.
حال تصور کنید راهحل دوم (دو Aggregate) را انتخاب کردهایم. توسعهدهندهٔ جدید، کلاس CustomerDocument را باز میکند. در همان سازنده میبیند که CustomerId اجباری است و باید معتبر باشد. او نیازی به «یادآوری» ندارد — کد، خودش را توضیح میدهد. API جدید را هم که مینویسد، مجبور است از CustomerDocument استفاده کند، و CustomerDocument خودش از خودش محافظت میکند.
این است تفاوت بین یک «قرارداد شفاهی» و یک «الزام ساختاری». اولی بر حافظه و دقت انسان متکی است. دومی بر کد.
هیچکس در این تیم عمداً تصمیم «بد» نمیگیرد. ما همگی بر اساس بهترین قضاوت خود عمل میکنیم. اما قضاوت ما توسط لنزی که به چشم داریم شکل میگیرد — لنزی که ممکن است آنقدر شفاف و طبیعی باشد که حتی حضورش را حس نکنیم.
پیشنهاد این بحث، کنار گذاشتن یک لنز به نفع دیگری نیست. پیشنهاد، ایجاد یک مکث کوتاه قبل از نهایی کردن هر تصمیم طراحی است. یک دم، یک درنگ، یک تأمل:
۱. راهحلی که بهطور غریزی به ذهنم میرسد، ریشه در کدام نگاه به انسجام دارد؟
۲. اگر از زاویهٔ دیگر (انسجام داده یا انسجام رفتار) به این مسئله نگاه کنم، چه راهحلی میبینم؟
۳. هرکدام چه بهایی دارند، و کدام بها با مسیر بلندمدت این پروژه همخوانتر است؟
این سه پرسش، ما را به «پاسخ درست» نمیرسانند — چون در طراحی نرمافزار به ندرت «پاسخ درست» وجود دارد. اما تضمین میکنند که انتخاب ما آگاهانه باشد. و آگاهی، ارزشمندترین چیزی است که یک تیم فنی میتواند داشته باشد.
هیچکس را نمیتوان به خاطر عشق به ابزارش سرزنش کرد. نوازندهٔ کلاسیک با باخ احساس آرامش میکند، نوازندهٔ جز با بداهه. هر دو موسیقیدانهای ماهری هستند. اما یک ارکستر موفق، ارکستری است که بداند امشب قرار است باخ بنوازد یا جز. و همهٔ اعضا، صرفنظر از علاقهٔ شخصیشان، با همان زبان بنوازند.
ما نیز در این تیم، ترکیبی از ذهنهای دادهمحور و رفتارمحور هستیم. هر دو ارزشمندند. هر دو میتوانند شاهکار خلق کنند. اما در این پروژهٔ خاص، ما یک «زبان موسیقیایی» انتخاب کردهایم. هرچه بیشتر از این انتخاب آگاه باشیم، کمتر در دام ناهماهنگیهای ناخواسته میافتیم.
بیایید این مکث را به یک عادت تیمی تبدیل کنیم. نه برای قضاوت، که برای آگاهی.