ویرگول
ورودثبت نام
عادل برکم
عادل برکمعلاقه‌مند به موضوعات مختلف که اینجا نوشته هام رو درباره اون ها به اشتراک میگذارم.
عادل برکم
عادل برکم
خواندن ۴ دقیقه·۷ ماه پیش

«تله موجودیت» در نرم‌افزار چیست؟

مقدمه

در دنیای توسعه نرم‌افزار، از اشتباهات رایجی که می‌تواند معماری سیستم را به مرور زمان شکننده و غیرقابل نگهداری کند و بعدا بدهی فنی آن هم از نوع شدید را بوجود آورد، تله موجودیت (Entity Trap) است. زمانی در این تله می افتیم که برای طراحی سیستم، به‌جای مدل‌کردن رفتارها (Behavior) و منطق کسب‌وکار (Business Logic)، صرفاً روی ساختار داده‌ها (Data Structure) و موجودیت‌های پایگاه داده تمرکز شود. به بیان دیگر وقتی داده‌ها جای منطق کسب‌وکار را می‌گیرند در تله‌ای به نام موجودیت افتاده‌ایم.

نتیجه تله موجودیت، یک سیستم یا محصول یا پروژه کوتاه مدت است! در کوتاه‌مدت کار می‌کند اما آنچنان شکننده که با رشد پروژه، نیازمندی جدید مشتری، تغییر قوانین کسب‌وکار یا حتی ضرورت بهینه سازی و استفاده از تکنولوژی جدید، پروژه به کابوس تبدیل می‌شود. در ادامه به چرایی و راهکارهای مقابله با این تله میپردازیم.

موجودیت (Entity) چیست؟

قبل از اینکه تله موجودیت را معرفی کنم، برای مرور بهتر موجودیت را تعریف کنیم. همانطور که میدانید در معماری نرم‌افزار، موجودیت (Entity) به یک شیء یا مدلی گفته می‌شود که:

  • هویت (Identity) دارد (مثلاً یک `User` با `ID` منحصربه‌فرد).
  • حالت (State) را نگه می‌دارد (مثلاً نام کاربر، موجودی حساب بانکی).
  • معمولاً مستقیماً به جدول پایگاه داده نگاشت می‌شود.

تله موجودیت (Entity Trap) چه زمانی اتفاق می‌افتد؟

تصور کنید تیم توسعه، تمام منطق کسب‌وکار را مستقیماً داخل موجودیت‌ها یا لایه داده می‌نویسد. مثلا کلاسی که مربوط به مشتری یا حساب بانکی است، متدی برای انتقال حساب داشته باشد.

class BankAccount { private: int id; std::string owner; double balance; public: // انتقال وجه مستقیماً در موجودیت! void transfer(BankAccount& targetAccount, double amount) { if (this->balance < amount) { throw std::runtime_error(&quotInsufficient funds&quot); } this->balance -= amount; targetAccount.balance += amount; } // سایر متدها };

این نحوه توسعه چه مشکلی را ایجاد می‌کند؟

  • عدم تست‌پذیری: چون منطق کسب‌وکار مستقیماً به موجودیت وابسته است، تست واحد (Unit Test) سخت می‌شود.
  • انعطاف‌ناپذیری: اگر قوانین برداشت تغییر کند (مثلاً کارمزد برداشت اضافه شود)، مجبورید موجودیت را تغییر دهید.
  • عدم تفکیک مسئولیت: موجودیت هم داده را نگه می‌دارد و هم منطق را مدیریت می‌کند (نقض اصل Single Responsibility) از اصول SOLID.
  • پیچیدگی مداوم:با اضافه شدن قوانین جدید (مثل کارمزد) کلاس بزرگ می‌شود.

تله موجودیت، به عبارت ساده «موجودیت همه کاره» است

موجودیت فراتر از فانکشن یا کلاس یا ماژول است. سرویس یا پروژه هم میتواند در لایه های مختلف از همین جهت بررسی و مورد نقد و کنکاش قرار گیرد. به زبان ساده هرگاه هر موجودیتی در بستر یک موجودیت بزرگتر تبدیل به God Objects یا یک موجودیت همه کاره با مسئولیت‌های زیادی تبدیل شود در دام موجودیت افتاده ایم. مسئولیت پذیری زیاد و نامتوازن توسط یک موجودیت، توسعه، تست، نگهداری، مقیاس‌پذیری و تحمل‌پذیری خطای آنها را بسیار بزرگ و دشوار می‌کند.

فرار از تله!

قبل از ادامه توضیحات و در ادامه مثال ایجاد تله موجودیت به مثال اصلاحی آن توجه کنید.

class BankAccount { private: int id; std::string owner; double balance; public: // فقط متدهای دسترسی ساده double getBalance() const { return balance; } void setBalance(double newBalance) { balance = newBalance; } // ... }; class MoneyTransferService { public: static void transfer(BankAccount& source, BankAccount& target, double amount) { if (source.getBalance() < amount) { throw std::runtime_error(&quotInsufficient funds&quot); } source.setBalance(source.getBalance() - amount); target.setBalance(target.getBalance() + amount); // امکان اضافه کردن قوانین جدید: // - کارمزد انتقال // - اعتبارسنجی AML // - لاگ تراکنش } };

برای رهایی از تله راهکارهای زیر توصیه میشود:

۱. تفکیک رفتار (Behavior) از حالت (State)

به‌جای قراردادن تمام منطق داخل موجودیت، آن را به سرویس‌های حوزه (Domain Services) منتقل کنید. یا تعریف کلاس و ماژول های دیگر. مشابه مثال ذکر شده. البته قطعا مثال گفته شده یک مثال ابتدایی و ناقص است و تنها برای توضیح کلی مطلب گفته شده است.

۲. استفاده از الگوی Domain-Driven Design (DDD)

در DDD، موجودیت‌ها فقط داده را نگه می‌دارند و رفتارها در Aggregate Roots یا Domain Services تعریف می‌شوند. میتوان گفت موجودیت‌ها را باید Anemic (فاقد منطق) نگه دارید (مگر برای رفتارهای ساده مثل Validation).

۳. معماری لایه‌بندی شده

  • لایه نمایش (Presentation): کنترلرهای REST یا واسط کاربر.
  • لایه کسب‌وکار (Business Logic): سرویس‌های حوزه.
  • لایه داده (Data Access): ریپوزیتوری‌ها و موجودیت‌ها.

جمع بندی

بدهی فنی مثل بدهی مالی (وام)‌ است. همچنان که برای جلو زدن از تورم یا راه اندازی کسب و کار و... راه گریزی نیست. بدهی فنی هم برای شروع پروژه و توسعه محصول نیاز است. اما آنچه مهم است پرداخت به موقع آن است که مستلزم شناخت آن است. یعنی این مهم است که اگر راهی سریع به جای راه اصولی انتخاب میشود با شناخت و پذیرش عواقب آن و برنامه عملیاتی برای جبران به موقع آن باشد. تله موجودیت از یک معماری اشتباه می آید که خیلی از مواقع بدلیل تصمیمات سریع رخ میدهد. اگر سیستم شما پر از موجودیت‌های چاق و سنگین است که همه کار می‌کنند و سنگین شده اند، احتمالاً در تله موجودیت افتاده‌اید! زمان بازبینی معماری است. جدا از مباحث فنی مانند تست پذیری و مقیاس پذیری، تله موجودیت در سطح سرویس یا پروژه علاوه بر مشکلات گفته شده در زمینه مدیریت تیم ها و نفرات نیز به شدت از چابکی و انعطاف می کاهد.

نرم افزارمعماری نرم افزارطراحی سیستمبدهی فنی
۰
۰
عادل برکم
عادل برکم
علاقه‌مند به موضوعات مختلف که اینجا نوشته هام رو درباره اون ها به اشتراک میگذارم.
شاید از این پست‌ها خوشتان بیاید