فلٹر میں فنکشنل ماڈیولرائزیشن: آزاد اور توسیع پذیر فعالیت کے لیے صاف فن تعمیر اور ڈومین سے چلنے والے ڈیزائن کو کیسے جوڑنا ہے۔

ایک چھوٹی ٹیم میں کام کرنے والے انجینئر کے طور پر، آپ کا موجودہ ڈھانچہ کامیاب ہوسکتا ہے۔ لیکن کیا ہوتا ہے جب آپ کے پاس 20 یا اس سے زیادہ لوگوں کی ایک بڑی ٹیم ہو جو سب ایک ہی کوڈ بیس میں حصہ ڈال رہے ہوں؟ اپنے کوڈبیس کو دبلی پتلی اور کمپیکٹ رکھنے کے لیے محتاط ڈیزائن سوچ اور ایک ہموار شراکت کے ورک فلو اور عمل کی ضرورت ہوتی ہے۔

آپ کو فولڈر کی ساخت، فن تعمیر، اور پیٹرن کا انتخاب بھی احتیاط سے کرنا چاہیے جو آپ اپنے پروجیکٹ میں استعمال کرتے ہیں۔

زیادہ تر فلٹر ایپس اسی طرح شروع ہوتی ہیں۔ ابتدائی طور پر آپ کے پاس ایک lib فولڈر، چند اسکرینیں، شاید ایک ماڈل ڈائرکٹری اور ایک سروس فائل ہے جو ہر چیز کو ہینڈل کرتی ہے۔ یہ کام کرتا ہے، ایپ بھیجتی ہے، اور ہر کوئی خوش ہے۔

یہ آپ کی ایپ کو بڑا بنائے گا۔ نئی خصوصیات شامل کی گئی ہیں۔ آپ کی ٹیم پھیل رہی ہے۔ جو کبھی قابل انتظام کوڈ بیس تھا وہ بھولبلییا بن گیا۔ ایک چیز بدلو کچھ اور ٹوٹ جاتا ہے۔ کوئی نہیں جانتا کہ کاروباری منطق اصل میں کہاں رہتی ہے۔ ماڈل فولڈر میں 300 فائلیں ہیں۔ سروس فائل 6,000 لائنوں پر مشتمل ہے۔ ایک نئے انجینئر کو یہ سمجھنے میں کئی ہفتے لگتے ہیں کہ کہاں اور کیا شامل کرنا ہے۔

یہ نظم و ضبط کا مسئلہ نہیں ہے۔ یہ ایک ساختی مسئلہ ہے۔ ایپ کو اس طرح سے تشکیل نہیں دیا گیا ہے کہ یہ کبھی بھی ٹوٹے بغیر ترقی کو جذب کر سکے۔

فنکشنل ماڈیولرائزیشن ایک تنظیمی حکمت عملی ہے جو اس تباہی کو روکتی ہے۔ یہ نہ تو کوئی نیا فریم ورک ہے اور نہ ہی صاف فن تعمیر یا ڈومین سے چلنے والے ڈیزائن کا متبادل۔ دونوں اصولوں کو لے کر اور انہیں فنکشن بہ فنکشن کی بنیاد پر لاگو کرنے سے، ایپلیکیشن کا ہر حصہ آزادانہ طور پر قابل جانچ اور بغیر مداخلت کے قابل توسیع ہے۔

اس آرٹیکل میں، ہم موجودہ مسائل کا احاطہ کریں گے جو لیئر فرسٹ پیٹرن کا استعمال کرتے وقت پیدا ہوسکتے ہیں، ڈومین سے چلنے والا ڈیزائن دراصل کیسے کام کرتا ہے، قیمتی اشیاء کے ساتھ کچھ کاروباری اصول، صاف فن تعمیر کا کیا مطلب ہے، اور یہ سب فنکشنل ماڈیولرائزیشن میں کیسے اکٹھا ہوتا ہے۔

انڈیکس

شرطیں

اس مضمون کو پڑھنے سے پہلے، آپ کو درج ذیل سے واقف ہونا چاہیے:

  • ڈارٹ پر فلٹر ایپلی کیشنز بنانا

  • آبجیکٹ اورینٹڈ پروگرامنگ کیا ہے: کلاسز، وراثت، انٹرفیس؟

  • سافٹ ویئر فن تعمیر میں پرتوں کا کیا مطلب ہے: ڈیٹا تک رسائی کے لیے UI کو کاروباری منطق سے الگ کرنا

  • ڈارٹ میں غیر مطابقت پذیر پروگرامنگ: فیوچرز اور اسینک/انتظار

صاف فن تعمیر یا ڈومین سے چلنے والے ڈیزائن کے ساتھ کسی پیشگی تجربے کی ضرورت نہیں ہے۔ یہ مضمون دونوں متعلقہ تصورات کو متعارف کراتا ہے۔

فنکشنل ماڈیولرائزیشن واقعی کیا ہے؟

فنکشنل ماڈیولریٹی ٹیکنالوجی کی تہوں کے بجائے فعالیت کے ارد گرد ایپلی کیشنز کو منظم کرنے کی مشق ہے۔

ایپلیکیشن کی تمام خصوصیات میں کام کرنے کے لیے درکار ہر چیز شامل ہے: ڈومین ماڈل، کاروباری اصول، ڈیٹا تک رسائی، اور UI۔ یہ خصوصیت پورے ایپلیکیشن اسٹیک میں عمودی ٹکڑا ہے۔ یہ کام کرنے کے لیے کسی دوسری خصوصیات پر منحصر نہیں ہے۔ یہ اس کے اندرونی افعال کو دوسرے افعال سے بے نقاب نہیں کرتا ہے۔ یہ اکیلا کھڑا ہے۔

یہ بنیادی طور پر اس سے مختلف ہے جس طرح سے زیادہ تر ایپس کو ابتدائی طور پر منظم کیا جاتا ہے، جہاں تمام ڈومین کوڈ ایک فولڈر میں ہوتا ہے، تمام ڈیٹا تک رسائی دوسرے فولڈر میں ہوتی ہے، اور تمام UI تیسرے فولڈر میں ہوتے ہیں، قطع نظر اس سے کہ کوڈ کا تعلق کس فنکشن سے ہے۔

فنکشنل ماڈیولریٹی ایک صاف فن تعمیر نہیں ہے۔ کلین آرکیٹیکچر اس بات کی وضاحت کرتا ہے کہ پرتیں کیا ہیں اور وہ اصول جو ان کے تعامل کے طریقہ کار پر حکمرانی کرتے ہیں۔ یہ ہمیں بتاتا ہے کہ ڈومین کی تہہ بنیادی ڈھانچے کی پرت پر منحصر نہیں ہو سکتی، کاروباری قواعد UI کے بارے میں نہیں جان سکتے، اور ڈیٹا متعین حدود سے اندر کی طرف جاتا ہے۔

فنکشنل ماڈیولرائزیشن ڈومین سے چلنے والا ڈیزائن بھی نہیں ہے۔ DDD پیچیدہ کاروباری ڈومینز کی ماڈلنگ کے لیے ایک ذخیرہ الفاظ فراہم کرتا ہے۔ کوڈ میں کاروباری پیچیدگی کو کیپچر کرنے کے لیے اداروں، ویلیو آبجیکٹ، ایگریگیٹس، ڈومین سروسز، اور باؤنڈڈ سیاق و سباق کو ٹولز کے طور پر متعارف کراتا ہے۔

فنکشنل ماڈیولرائزیشن ان دونوں کو لیتی ہے اور ان کو یکجا کرتی ہے۔ یہ کلین آرکیٹیکچر کے لیئرنگ رولز اور ڈی ڈی ڈی کی ماڈلنگ الفاظ کا استعمال کرتا ہے اور انہیں فنکشنل سطح پر لاگو کرتا ہے۔ ہر فیچر کی اپنی ڈومین پرت، اس کی اپنی ایپلیکیشن لیئر، اس کی اپنی انفراسٹرکچر لیئر، اور اس کی اپنی پریزنٹیشن لیئر ہوتی ہے۔ کلین آرکیٹیکچر کے اصول اس بات پر حکمرانی کرتے ہیں کہ اس کی پرتیں کسی فنکشن کے اندر کیسے تعامل کرتی ہیں۔ DDD کا تصور اس بات پر حکمرانی کرتا ہے کہ ڈومین کے درجہ بندی کے اندر ڈومین ماڈل کو کس طرح منظم کیا جاتا ہے۔

نتیجے کے طور پر، کوڈبیس کا کوئی بھی حصہ غیر معینہ مدت تک بڑھ نہیں سکتا ہے بغیر اس کے محفوظ طریقے سے سمجھنے یا اس میں ترمیم کرنے کے لیے بہت بڑا ہو گیا ہے۔

پرت-پہلی تنظیم کے چیلنجز

یہ سمجھنے کے لیے کہ فنکشنل ماڈیولریٹی کیوں اہم ہے، ہمیں یہ دیکھنا ہوگا کہ پیمانے پر متبادل کیسا نظر آتا ہے۔

درجہ بندی کی پہلی تنظیم میں، فولڈر کا ڈھانچہ تکنیکی کردار کے لحاظ سے کوڈ کرتا ہے۔

lib/
  domain/
    employee.dart
    payment.dart
    product.dart
    notification.dart
    user.dart
    ... 47 more files

  application/
    get_employee_usecase.dart
    process_payment_usecase.dart
    get_products_usecase.dart
    send_notification_usecase.dart
    ... 83 more use cases

  infrastructure/
    employee_repository_impl.dart
    payment_repository_impl.dart
    product_datasource.dart
    notification_service_impl.dart
    ... 91 more implementations

  presentation/
    employee_page.dart
    payment_page.dart
    product_list_page.dart
    ... 200 more screens and widgets

یہ چھوٹے پیمانے پر اچھا کام کرتا ہے۔ درمیانے درجے پر، دراڑیں نمودار ہونے لگتی ہیں۔ بڑے پیمانے پر، یہ واقعی تکلیف دہ ہے۔

اگر ڈویلپرز کو ملازمین کی خصوصیات میں ترمیم کرنے کی ضرورت ہے، تو وہ فائلوں کو چار الگ الگ ٹاپ لیول فولڈرز میں ٹچ کرتے ہیں۔ ڈومین فولڈر میں ملازم ہستی کو تبدیل کرنے کے لیے، آپ کو اپنے استعمال کے کیس کے لیے ایپلیکیشن فولڈر، اپنے ریپوزٹری کے نفاذ کے لیے انفراسٹرکچر فولڈر، اور اپنے UI کے لیے پریزنٹیشن فولڈر میں جانا چاہیے۔ یہ فولڈر ایک دوسرے کے ساتھ نہیں ہیں۔ یہ دوسرے تمام فنکشنز سے الگ ہے جو اس فولڈر کو شیئر کرتے ہیں۔

کسی ایک فنکشن کو سمجھنے کے لیے ذہنی طور پر پورے کوڈبیس میں بکھرے ہوئے ٹکڑوں کو جمع کرنے کی ضرورت ہوتی ہے۔ ایک نئے انجینئر کو اپنے اسٹاف فنکشن میں شامل کرنے کا مطلب ہے کہ پوری تصویر دیکھنے سے پہلے انہیں چار مختلف پوزیشنیں دکھائیں۔

انفرادی طور پر ملازمین کے افعال کی جانچ کرنا مشکل ہے کیونکہ پرزے الگ تھلگ نہیں ہیں۔ وہ فولڈرز اور بعض اوقات انحصار کو دوسری خصوصیات کے ساتھ ان طریقوں سے بانٹتے ہیں جو واضح حدود کو کھینچنا مشکل بنا دیتے ہیں۔

فنکشن ماڈیولرائزیشن ایک فنکشن سے تعلق رکھنے والی ہر چیز کو ایک ساتھ رکھ کر اس مسئلے کو حل کرتی ہے۔

بلڈنگ بلاکس: کلین آرکیٹیکچر ڈی ڈی ڈی سے ملتا ہے۔

فولڈر کے ڈھانچے اور کوڈ کو دیکھنے سے پہلے، ہمیں یہ سمجھنے کی ضرورت ہے کہ کلین آرکیٹیکچر اور ڈی ڈی ڈی دونوں فنکشنل ماڈیولریٹی میں کیا کردار ادا کرتے ہیں اور ہمیں دونوں کی ضرورت کیوں ہے۔

کلین آرکیٹیکچر کیا تعاون کرتا ہے۔

کلین آرکیٹیکچر آپ کے کوڈ کو تہوں میں ترتیب دینے کے لیے انحصار کے سخت اصولوں کا استعمال کرتا ہے۔ یعنی اندرونی تہہ بیرونی تہہ پر منحصر نہیں ہے۔ ڈومینز سب سے اندرونی تہہ ہیں۔ ایپلیکیشن کی پرت ڈومین کو لپیٹ دیتی ہے۔ بنیادی ڈھانچے کی تہہ سب سے باہر کی تہہ ہے۔

فلٹر ایپلی کیشن میں، اس کا مطلب ہے کئی چیزیں:

کہ ڈومین پرت ہستی، قدر آبجیکٹ، اور اسٹوریج انٹرفیس پر مشتمل ہے۔ Flutter، HTTP لائبریریوں، مقامی ڈیٹا بیس، یا بیرونی پیکجوں پر کوئی انحصار نہیں ہے۔ یہ خالص ڈارٹ ہے، فنکشن کی کاروباری منطق اس سے بالکل الگ ہے کہ ایپ کیسے بنتی ہے یا کیسے لگائی جاتی ہے۔

کہ درخواست کی پرت استعمال کے معاملات شامل ہیں۔ مخصوص کاروباری کاموں کو انجام دینے کے لیے کیسز کوآرڈینیٹ ڈومین آبجیکٹ کا استعمال کریں۔ یہ صرف ڈومین پرت پر انحصار کرتا ہے۔ میں اداروں اور اسٹوریج کے بارے میں جانتا ہوں، لیکن میں HTTP، SQLite، یا Riverpod کے بارے میں نہیں جانتا ہوں۔

کہ بنیادی ڈھانچے کی پرت ڈومین میں بیان کردہ ریپوزٹری انٹرفیس کے ٹھوس نفاذ پر مشتمل ہے۔ میں HTTP کلائنٹس، مقامی ڈیٹا بیس، اور بیرونی خدمات کے بارے میں جانتا ہوں۔ یہ ڈومین لیئر انٹرفیس پر منحصر ہے، لیکن ڈومین لیئر کبھی بھی اس پر منحصر نہیں ہے۔

کہ پریزنٹیشن پرت UI (وجیٹس، اطلاعات، حیثیت) پر مشتمل ہے۔ یہ استعمال کیس کے ذریعے درخواست کی پرت پر منحصر ہے۔ یہ ریاست پر ردعمل ظاہر کرتا ہے اور اس کی کوئی کاروباری منطق نہیں ہے۔

انحصار کی یہ سمت اس بات کو یقینی بناتی ہے کہ بنیادی ڈھانچے کی تفصیلات سے کاروباری قواعد کو توڑا نہیں جاتا ہے۔ HTTP کلائنٹ کو Dio سے HTTP میں تبدیل کرنے کے لیے کسی ایک ڈومین ہستی کو چھونے کی ضرورت نہیں ہے۔ ریاستی انتظام کو Riverpod سے BLoC میں تبدیل کرنے کے لیے استعمال کے ایک کیس کو چھونے کی ضرورت نہیں ہے۔

ڈومین سے چلنے والا ڈیزائن کیا تعاون کرتا ہے۔

DDD ڈومین لیئرز کو مناسب طریقے سے ماڈل کرنے کے لیے ایک ذخیرہ الفاظ فراہم کرتا ہے۔

نہیں حقیقت ID کے ساتھ ایک ڈومین آبجیکٹ۔ اس کی ایک شناخت ہے جو اسے اسی قسم کے دیگر اداروں سے ممتاز کرتی ہے۔ ملازم ہستی کی شناخت اس کے ملازم کی شناخت سے ہوتی ہے۔ ایک ہی نام کے دو ملازمین اب بھی مختلف ادارے ہیں کیونکہ ان کی شناخت مختلف ہے۔ ایک ہستی کی ایک ایسی حالت ہو سکتی ہے جو وقت کے ساتھ ساتھ بدلتی ہے، اور آپ اس بارے میں قواعد لاگو کرتے ہیں کہ وہ ریاست کیسے بدل سکتی ہے۔

کوئی راستہ نہیں قدر آبجیکٹ ڈیٹا کے ایک ٹکڑے کو لپیٹتا ہے اور اس کی اندرونی درستگی کو بڑھاتا ہے۔ EmployeeId ایک ویلیو آبجیکٹ ہے، بالکل ای میل ایڈریس یا رقم کی طرح۔ قیمتی اشیاء ناقابل تغیر ہیں۔ اگر ڈیٹا غلط ہے تو، ڈومین میں غلط ڈیٹا کے داخل ہونے سے پہلے ویلیو آبجیکٹ تخلیق پر ایک استثناء پیدا کرتا ہے۔

کوئی راستہ نہیں ڈومین سروس کاروباری منطق کو سنبھالتا ہے جو قدرتی طور پر کسی ایک ہستی سے تعلق نہیں رکھتا ہے۔ اگر آپ کے قواعد متعدد اداروں پر محیط ہیں یا اداروں کے درمیان اس طرح سے ہم آہنگی کی ضرورت ہے جو کسی ایک ہستی میں فٹ نہ ہو، تو اس منطق کا تعلق ڈومین سروس میں ہے۔

کوئی راستہ نہیں ذخیرہ ڈومین پرت میں بیان کردہ ایک انٹرفیس جو بیان کرتا ہے کہ ڈومین اشیاء کو برقرار رکھنے اور بازیافت کرنے کا طریقہ۔ ڈومین جانتا ہے کہ کیا کرنے کی ضرورت ہے۔ بنیادی ڈھانچے کی پرت ٹھوس نفاذ فراہم کرتی ہے۔ آپ کا ڈومین کبھی نہیں جان سکے گا کہ آپ کے ذخیرے کے پیچھے کون سا ڈیٹا بیس یا API ہے۔

اداروں کی ماڈیولر خصوصیات، ویلیو آبجیکٹ، اور DTOs

ان تین اقسام کے درمیان فرق کو سمجھنا فنکشنل ماڈیولرائزیشن کے لیے بنیادی ہے۔ اگر یہ غلط ہو جاتا ہے، تو آپ کے کاروباری اصول غلط پرت پر پہنچ جائیں گے۔

قدر آبجیکٹ

ایک ویلیو آبجیکٹ ڈیٹا کے ایک ٹکڑے کو لپیٹتا ہے اور اس بات کو نافذ کرتا ہے کہ ڈیٹا ہمیشہ درست ہے۔ یہ اس وقت ہوتا ہے جب غلط ڈیٹا فراہم کیا جاتا ہے۔ اس کا مطلب ہے کہ ڈومین کے اندر غلط ڈیٹا موجود نہیں ہو سکتا۔

class EmployeeId {
  final String value;

  EmployeeId(this.value) {
    if (value.isEmpty) {
      throw DomainException('Employee ID cannot be empty');
    }
    if (value.length < 4) {
      throw DomainException('Employee ID must be at least 4 characters');
    }
  }

  @override
  bool operator ==(Object other) =>
      other is EmployeeId && other.value == value;

  @override
  int get hashCode => value.hashCode;

  @override
  String toString() => value;
}
class Money {
  final double amount;
  final String currency;

  Money({required this.amount, required this.currency}) {
    if (amount < 0) {
      throw DomainException('Amount cannot be negative');
    }
    if (currency.isEmpty) {
      throw DomainException('Currency cannot be empty');
    }
  }

  Money add(Money other) {
    if (currency != other.currency) {
      throw DomainException('Cannot add different currencies');
    }
    return Money(amount: amount + other.amount, currency: currency);
  }

  @override
  String toString() => '$currency ${amount.toStringAsFixed(2)}';
}

قیمتی اشیاء ناقابل تغیر ہیں۔ قدر آبجیکٹ میں ترمیم نہ کریں۔ اس کے بجائے، ایک نیا آئٹم بنائیں۔ EmployeeId اور Money مذکورہ بالا دونوں کی تعمیر کے دوران تصدیق کی جاتی ہے۔ کوئی غلط نہیں EmployeeId یہ ڈومین میں کہیں بھی موجود ہوسکتا ہے۔ کوئی منفی نہیں Money یہ موجود ہو سکتا ہے۔ گارڈز خود ہی قسم میں بنائے جاتے ہیں۔

ہستی

ایک ہستی ایک ڈومین آبجیکٹ ہے جس میں ایک ID ہے اور ریاست کو تبدیل کرنے کے طریقہ کار کے اصول ہیں۔

class Employee {
  final EmployeeId id;
  final String name;
  DateTime? clockInTime;
  DateTime? clockOutTime;

  Employee({
    required this.id,
    required this.name,
  });

  void clockIn(DateTime time) {
    if (clockInTime != null && clockOutTime == null) {
      throw DomainException('Cannot clock in: already clocked in');
    }
    clockInTime = time;
    clockOutTime = null;
  }

  void clockOut(DateTime time) {
    if (clockInTime == null) {
      throw DomainException('Cannot clock out: not clocked in');
    }
    if (time.isBefore(clockInTime!)) {
      throw DomainException('Clock out time cannot be before clock in time');
    }
    clockOutTime = time;
  }

  bool get isClockedIn => clockInTime != null && clockOutTime == null;

  Duration? get hoursWorked {
    if (clockInTime == null || clockOutTime == null) return null;
    return clockOutTime!.difference(clockInTime!);
  }
}

اداروں کی ریاستی منتقلی ہوتی ہے۔ clockIn اور clockOut صرف ایک سیٹر نہیں۔ وہ کاروباری قوانین کو نافذ کرتے ہیں۔ ایک ملازم کلاک آؤٹ کیے بغیر دو بار گھڑی میں نہیں جا سکتا، بالکل اسی طرح جیسے وہ کلاک ان کرنے سے پہلے گھڑی نہیں نکال سکتا۔ یہ اصول اس ادارے پر لاگو ہوتے ہیں جس سے وہ تعلق رکھتا ہے، نہ کہ استعمال کے کیس، ویجیٹ یا ذخیرہ پر۔

ڈی ٹی او

ڈیٹا ٹرانسفر آبجیکٹ بیرونی ذرائع سے خام ڈیٹا پاس کرتے ہیں، جیسے API کے جوابات، ڈیٹا بیس کی قطاریں، اور JSON پے لوڈز۔ DTO کے پاس کوئی کاروباری اصول یا کارروائیاں نہیں ہیں۔ یہ مکمل طور پر ڈیٹا کو حدود میں منتقل کرنے کے لیے موجود ہے۔

class EmployeeDTO {
  final String id;
  final String name;
  final String? clockInTime;
  final String? clockOutTime;

  const EmployeeDTO({
    required this.id,
    required this.name,
    this.clockInTime,
    this.clockOutTime,
  });

  factory EmployeeDTO.fromJson(Map json) {
    return EmployeeDTO(
      id: json['id'] as String,
      name: json['name'] as String,
      clockInTime: json['clock_in_time'] as String?,
      clockOutTime: json['clock_out_time'] as String?,
    );
  }

  Employee toDomain() {
    final employee = Employee(
      id: EmployeeId(id),
      name: name,
    );

    if (clockInTime != null) {
      employee.clockIn(DateTime.parse(clockInTime!));
    }
    if (clockOutTime != null) {
      employee.clockOut(DateTime.parse(clockOutTime!));
    }

    return employee;
  }
}

ڈی ٹی اوز بنیادی ڈھانچے کی سطح پر ہیں۔ ڈومین ادارے JSON کے بارے میں جانتے ہیں، لیکن ڈومین اداروں کو JSON کے بارے میں کچھ نہیں معلوم۔ کہ toDomain() طریقے خام ڈیٹا کو مناسب ڈھانچہ والے ڈومین اشیاء میں تبدیل کرتے ہیں اور راستے میں کاروباری اصولوں پر عمل درآمد کرتے ہیں۔

کاروباری قواعد اور رہائش

ماڈیولر فنکشنز کے لیے تمام کاروباری اصول ڈومین لیئر میں رہتے ہیں۔ یہ ترجیح نہیں ہے۔ جو چیز پورے نقطہ نظر کو کام کرتی ہے وہ آرکیٹیکچرل معاہدہ ہے۔

قدر آبجیکٹ جوہری، ضروری قوانین کو نافذ کرتا ہے۔ آپ کے ای میل ایڈریس میں @ کی علامت شامل ہونی چاہیے۔ رقم منفی نہیں ہو سکتی۔ ملازم کی شناخت خالی نہیں ہو سکتی۔ یہ اصول خود ڈیٹا کے بارے میں ہیں اور ویلیو آبجیکٹ سے تعلق رکھتے ہیں۔

ہستی ریاستی قوانین کو نافذ کریں۔ ملازمین دو بار کام کرنے کی اطلاع نہیں دے سکتے۔ اگر آپ کو پہلے ہی رقم کی واپسی مل چکی ہے، تو ہم آپ کی ادائیگی پر کارروائی نہیں کر سکتے۔ اگر انحصار مکمل نہیں ہوتا ہے تو کام مکمل نہیں کیا جاسکتا۔ ان قوانین میں ہستی کی ریاستیں شامل ہوتی ہیں جو وقت کے ساتھ ساتھ تبدیل ہوتی ہیں۔ ان کا تعلق اداروں سے ہے۔

ڈومین سروس ایسے قوانین کو نافذ کریں جو متعدد اداروں پر محیط ہوں یا ہم آہنگی کی ضرورت ہو۔ ملازمین اپنی چھٹی کی درخواستوں کو خود منظور نہیں کر سکتے۔ ادائیگیوں کے لیے ادائیگی کرنے والے اور دھوکہ دہی کا پتہ لگانے والے دونوں چیکس سے تصدیق کی ضرورت ہوتی ہے۔ یہ قواعد متعدد اداروں پر محیط ہیں اور قدرتی طور پر کسی ایک ہستی سے تعلق نہیں رکھتے۔ ان کا تعلق ڈومین سروسز سے ہے۔

اصول سادہ اور مطلق ہیں۔ اگر یہ کاروباری اصول ہے، تو یہ ڈومین پرت میں ہے۔ درخواست کی پرت ڈومین کو کال کرتی ہے۔ بنیادی ڈھانچے کی پرت ڈومین کے انٹرفیس کو نافذ کرتی ہے۔ پریزنٹیشن پرت نتائج پر ردعمل ظاہر کرتی ہے۔ ان میں سے کوئی بھی کاروباری قواعد کی وضاحت یا ترمیم نہیں کرتا ہے۔

یہ حد وہی ہے جو نظام کو قابل اعتماد بناتی ہے۔ آپ براہ راست ڈیٹا بیس پر جا کر کاروباری قواعد کو نظرانداز نہیں کر سکتے۔ آپ ویجیٹ پر براہ راست ریپوزٹری طریقہ کو کال کرکے اسے نظرانداز نہیں کرسکتے ہیں۔ یہ ہمیشہ لاگو ہوتا ہے کیونکہ ڈومین کی حالت کو تبدیل کرنے کا واحد طریقہ خود ڈومین آبجیکٹ کے ذریعے ہے۔

مستثنیٰ ہینڈلنگ اور محفوظ ریاستی تبلیغ

اگر ڈومین پرت، ڈومین پر کاروباری اصول کی خلاف ورزی کی جاتی ہے۔ DomainException. یہ رعایت ایپلیکیشن لیئر سے ہوتی ہوئی پریزنٹیشن لیئر تک جاتی ہے جہاں اسے UI حالت میں تبدیل کر دیا جاتا ہے۔

ایک اہم بصیرت یہ ہے کہ مستثنیات آپ کی ایپ کو نہیں توڑتے ہیں۔ اسے ایک متعین حد پر پکڑا جاتا ہے اور ایک محفوظ حالت میں تبدیل کر دیا جاتا ہے جسے UI رینڈر کر سکتا ہے۔

مکمل بہاؤ مندرجہ ذیل ہے:

// domain layer — throws when rules are violated
class Employee {
  void clockIn(DateTime time) {
    if (clockInTime != null && clockOutTime == null) {
      throw DomainException('Cannot clock in: already clocked in');
    }
    clockInTime = time;
  }
}
// application layer — catches domain exceptions and returns Result
class ClockInUseCase {
  final EmployeeRepository _repository;

  ClockInUseCase(this._repository);

  Future> execute(
    String employeeId,
    DateTime time,
  ) async {
    try {
      final employee = await _repository.findById(EmployeeId(employeeId));

      if (employee == null) {
        return Result.failure(AppException.notFound('Employee not found'));
      }

      employee.clockIn(time);
      await _repository.save(employee);

      return Result.success(employee);
    } on DomainException catch (e) {
      return Result.failure(AppException.businessRule(e.message));
    } catch (e) {
      return Result.failure(AppException.unknown(e.toString()));
    }
  }
}
// presentation layer — converts result into UI state
@riverpod
class EmployeeNotifier extends _$EmployeeNotifier {
  @override
  AsyncValue build() => const AsyncData(null);

  Future clockIn(String employeeId) async {
    state = const AsyncLoading();

    final result = await ref
        .read(clockInUseCaseProvider)
        .execute(employeeId, DateTime.now());

    result.fold(
      onSuccess: (employee) => state = AsyncData(employee),
      onFailure: (error) => state = AsyncError(error, StackTrace.current),
    );
  }
}
// widget — renders state, owns nothing
class EmployeeClockInWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(employeeNotifierProvider);

    return state.when(
      data: (employee) => employee != null
          ? ClockInSuccess(employee: employee)
          : const ClockInForm(),
      loading: () => const LoadingIndicator(),
      error: (error, _) => ErrorMessage(message: error.toString()),
    );
  }
}

بہاؤ لکیری اور پیش قیاسی ہے۔

  1. یوزر ان پٹ ٹرگرز کیسز استعمال کرتے ہیں۔

  2. ہمارے استعمال کے معاملے میں، ہم اسے ڈومین کہتے ہیں۔

  3. ڈومینز قوانین کو نافذ کرتے ہیں۔

  4. اگر کسی اصول کی خلاف ورزی ہوتی ہے تو، ایک DomainException پھینک دیا جاتا ہے۔

  5. استعمال کا معاملہ اسے پکڑتا ہے اور ناکامی کا نتیجہ لوٹاتا ہے۔

  6. نوٹیفائر نتیجہ کو AsyncError حالت میں تبدیل کرتا ہے۔

  7. ویجیٹ ایک غلطی پیش کرتا ہے۔

کسی بھی صورت میں کسی رعایت سے بچا نہیں جائے گا۔ اور ویجٹ کسی کاروباری منطق کو نہیں چھوتے۔

فولڈر کی ساخت

ان تصورات کو لاگو کرنے کے بعد، فولڈر کی ساخت قدرتی طور پر اس طرح نظر آتی ہے:

lib/
  shared/
    core/
      errors/
        domain_exception.dart
        app_exception.dart
      result/
        result.dart
      di/
        injection.dart
      utils/
        date_utils.dart

  features/
    employee/
      domain/
        entities/
          employee.dart
        value_objects/
          employee_id.dart
        repositories/
          employee_repository.dart
        services/
          attendance_domain_service.dart
        exceptions/
          employee_exceptions.dart

      application/
        use_cases/
          clock_in_usecase.dart
          clock_out_usecase.dart
          get_employee_usecase.dart

      infrastructure/
        datasources/
          employee_remote_datasource.dart
          employee_local_datasource.dart
        repositories/
          employee_repository_impl.dart
        dtos/
          employee_dto.dart

      presentation/
        notifier/
          employee_notifier.dart
          employee_notifier.g.dart
        state/
          employee_state.dart
        pages/
          employee_dashboard_page.dart
          clock_in_page.dart
        widgets/
          employee_card.dart
          clock_in_button.dart

    payment/
      domain/
        entities/
          payment.dart
        value_objects/
          money.dart
          payment_reference.dart
        repositories/
          payment_repository.dart
        services/
          payment_verification_service.dart

      application/
        use_cases/
          initiate_payment_usecase.dart
          verify_payment_usecase.dart
          refund_payment_usecase.dart

      infrastructure/
        datasources/
          payment_remote_datasource.dart
        repositories/
          payment_repository_impl.dart
        dtos/
          payment_dto.dart

      presentation/
        notifier/
          payment_notifier.dart
          payment_notifier.g.dart
        pages/
          payment_page.dart
          payment_confirmation_page.dart
        widgets/
          payment_summary_card.dart

  main.dart

کہ shared/core فولڈر میں صرف وہی ہوتا ہے جو اصل میں تمام فنکشنز میں شیئر کیا جاتا ہے، جیسے پہلے سے طے شدہ غلطی کی اقسام، نتائج کی قسمیں، انحصار انجیکشن کنفیگریشنز، اور بغیر کسی کاروباری منطق کے افادیت۔ باقی سب کچھ افعال میں آتا ہے۔

ہر خصوصیت ایک مکمل عمودی ٹکڑا ہے۔ فیچر کے لیے درکار تمام پرتیں اس فیچر کے فولڈر کے اندر موجود ہوتی ہیں۔ عملے کی خصوصیات پر کام کرنے والے ڈویلپرز کو کبھی بھی بیرونی سفر نہیں کرنا پڑتا ہے۔ features/employee خصوصیات کو سمجھنے یا تبدیل کرنے کے لیے ڈائریکٹری۔

حقیقی زندگی کی مثال 1: ملازم کے افعال

ملازم کی خصوصیت آپ کی افرادی قوت کے انتظام کی درخواست میں ملازمین کی حاضری کے پورے لائف سائیکل کا انتظام کرتی ہے۔ کام پر آنا، کام چھوڑنا، شفٹ کا انتظام، حاضری کا ریکارڈ۔

ڈومین

// value objects
class EmployeeId {
  final String value;

  EmployeeId(this.value) {
    if (value.isEmpty) throw DomainException('Employee ID cannot be empty');
  }
}

class ShiftDuration {
  final Duration duration;

  ShiftDuration(this.duration) {
    if (duration.isNegative) {
      throw DomainException('Shift duration cannot be negative');
    }
    if (duration.inHours > 16) {
      throw DomainException('Shift duration cannot exceed 16 hours');
    }
  }
}
// entity
class Employee {
  final EmployeeId id;
  final String name;
  final String teamId;
  DateTime? clockInTime;
  DateTime? clockOutTime;

  Employee({
    required this.id,
    required this.name,
    required this.teamId,
  });

  void clockIn(DateTime time) {
    if (isClockedIn) {
      throw DomainException('Employee is already clocked in');
    }
    clockInTime = time;
    clockOutTime = null;
  }

  void clockOut(DateTime time) {
    if (!isClockedIn) {
      throw DomainException('Employee is not clocked in');
    }
    if (time.isBefore(clockInTime!)) {
      throw DomainException('Clock out time cannot be before clock in time');
    }

    final shift = ShiftDuration(time.difference(clockInTime!));
    clockOutTime = time;
  }

  bool get isClockedIn => clockInTime != null && clockOutTime == null;

  Duration? get currentShiftDuration {
    if (!isClockedIn) return null;
    return DateTime.now().difference(clockInTime!);
  }
}
// repository interface — in the domain layer
abstract class EmployeeRepository {
  Future findById(EmployeeId id);
  Future> findByTeam(String teamId);
  Future save(Employee employee);
}

درخواست کی پرت

class ClockInUseCase {
  final EmployeeRepository _repository;

  ClockInUseCase(this._repository);

  Future> execute(String employeeId) async {
    try {
      final id = EmployeeId(employeeId);
      final employee = await _repository.findById(id);

      if (employee == null) {
        return Result.failure(
          AppException.notFound('Employee $employeeId not found'),
        );
      }

      employee.clockIn(DateTime.now());
      await _repository.save(employee);

      return Result.success(employee);
    } on DomainException catch (e) {
      return Result.failure(AppException.businessRule(e.message));
    } catch (e) {
      return Result.failure(AppException.unknown(e.toString()));
    }
  }
}

class ClockOutUseCase {
  final EmployeeRepository _repository;

  ClockOutUseCase(this._repository);

  Future> execute(String employeeId) async {
    try {
      final id = EmployeeId(employeeId);
      final employee = await _repository.findById(id);

      if (employee == null) {
        return Result.failure(
          AppException.notFound('Employee $employeeId not found'),
        );
      }

      employee.clockOut(DateTime.now());
      await _repository.save(employee);

      return Result.success(employee);
    } on DomainException catch (e) {
      return Result.failure(AppException.businessRule(e.message));
    } catch (e) {
      return Result.failure(AppException.unknown(e.toString()));
    }
  }
}

بنیادی ڈھانچے کی پرت

class EmployeeDTO {
  final String id;
  final String name;
  final String teamId;
  final String? clockInTime;
  final String? clockOutTime;

  const EmployeeDTO({
    required this.id,
    required this.name,
    required this.teamId,
    this.clockInTime,
    this.clockOutTime,
  });

  factory EmployeeDTO.fromJson(Map json) {
    return EmployeeDTO(
      id: json['id'] as String,
      name: json['name'] as String,
      teamId: json['team_id'] as String,
      clockInTime: json['clock_in_time'] as String?,
      clockOutTime: json['clock_out_time'] as String?,
    );
  }

  Employee toDomain() {
    final employee = Employee(
      id: EmployeeId(id),
      name: name,
      teamId: teamId,
    );

    if (clockInTime != null) {
      employee.clockIn(DateTime.parse(clockInTime!));
    }
    if (clockOutTime != null) {
      employee.clockOut(DateTime.parse(clockOutTime!));
    }

    return employee;
  }

  Map toJson() {
    return {
      'id': id,
      'name': name,
      'team_id': teamId,
      'clock_in_time': clockInTime,
      'clock_out_time': clockOutTime,
    };
  }
}

class EmployeeRepositoryImpl implements EmployeeRepository {
  final EmployeeRemoteDataSource _remote;

  EmployeeRepositoryImpl(this._remote);

  @override
  Future findById(EmployeeId id) async {
    final dto = await _remote.fetchEmployee(id.value);
    return dto?.toDomain();
  }

  @override
  Future> findByTeam(String teamId) async {
    final dtos = await _remote.fetchTeamEmployees(teamId);
    return dtos.map((dto) => dto.toDomain()).toList();
  }

  @override
  Future save(Employee employee) async {
    final dto = EmployeeDTO(
      id: employee.id.value,
      name: employee.name,
      teamId: employee.teamId,
      clockInTime: employee.clockInTime?.toIso8601String(),
      clockOutTime: employee.clockOutTime?.toIso8601String(),
    );
    await _remote.updateEmployee(dto);
  }
}

پریزنٹیشن پرت

@riverpod
class EmployeeNotifier extends _$EmployeeNotifier {
  @override
  AsyncValue build() => const AsyncData(null);

  Future clockIn(String employeeId) async {
    state = const AsyncLoading();

    final result = await ref
        .read(clockInUseCaseProvider)
        .execute(employeeId);

    result.fold(
      onSuccess: (employee) => state = AsyncData(employee),
      onFailure: (error) => state = AsyncError(error, StackTrace.current),
    );
  }

  Future clockOut(String employeeId) async {
    state = const AsyncLoading();

    final result = await ref
        .read(clockOutUseCaseProvider)
        .execute(employeeId);

    result.fold(
      onSuccess: (employee) => state = AsyncData(employee),
      onFailure: (error) => state = AsyncError(error, StackTrace.current),
    );
  }
}
class EmployeeDashboardPage extends ConsumerWidget {
  final String employeeId;

  const EmployeeDashboardPage({required this.employeeId, super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(employeeNotifierProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Employee Dashboard')),
      body: state.when(
        data: (employee) => employee != null
            ? EmployeeDashboardContent(
                employee: employee,
                onClockIn: () => ref
                    .read(employeeNotifierProvider.notifier)
                    .clockIn(employeeId),
                onClockOut: () => ref
                    .read(employeeNotifierProvider.notifier)
                    .clockOut(employeeId),
              )
            : const EmptyDashboard(),
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (error, _) => ErrorView(message: error.toString()),
      ),
    );
  }
}

ویجیٹ کلاکنز، EmployeeId ویلیو آبجیکٹ، یا DomainExceptions کے بارے میں کچھ نہیں جانتا ہے۔ ریاست کو پیش کریں اور کام کو اطلاع دہندہ کو سونپیں۔ باقی تمام کارروائیاں نیچے کی تہوں میں ہوتی ہیں۔

حقیقی زندگی کی مثال 2: ادائیگی کی تقریب

ادائیگی کا فنکشن ادائیگی کے پورے لائف سائیکل کو ہینڈل کرتا ہے، بشمول شروعات، پروسیسنگ، تصدیق، اور ناکامی سے نمٹنے کے۔ یہ پیچیدہ کاروباری قواعد کے ساتھ ایک ڈومین ہے جو واضح طور پر ماڈل بنائے جانے سے بہت فائدہ اٹھا سکتا ہے۔

ڈومین

enum PaymentStatus {
  pending,
  processing,
  completed,
  failed,
  refunded,
}

class PaymentReference {
  final String value;

  PaymentReference(this.value) {
    if (value.isEmpty) {
      throw DomainException('Payment reference cannot be empty');
    }
    if (!RegExp(r'^[A-Z0-9]{8,16}$').hasMatch(value)) {
      throw DomainException('Invalid payment reference format');
    }
  }
}

class Payment {
  final PaymentReference reference;
  final Money amount;
  final String payerId;
  final String recipientId;
  PaymentStatus status;
  String? failureReason;
  DateTime? processedAt;

  Payment({
    required this.reference,
    required this.amount,
    required this.payerId,
    required this.recipientId,
    this.status = PaymentStatus.pending,
  });

  void startProcessing() {
    if (status != PaymentStatus.pending) {
      throw DomainException(
        'Cannot process payment: current status is ${status.name}',
      );
    }
    status = PaymentStatus.processing;
  }

  void complete() {
    if (status != PaymentStatus.processing) {
      throw DomainException(
        'Cannot complete payment: current status is ${status.name}',
      );
    }
    status = PaymentStatus.completed;
    processedAt = DateTime.now();
  }

  void fail(String reason) {
    if (status != PaymentStatus.processing) {
      throw DomainException(
        'Cannot fail payment: current status is ${status.name}',
      );
    }
    status = PaymentStatus.failed;
    failureReason = reason;
  }

  void refund() {
    if (status != PaymentStatus.completed) {
      throw DomainException(
        'Cannot refund payment: only completed payments can be refunded',
      );
    }
    status = PaymentStatus.refunded;
  }

  bool get canBeRefunded => status == PaymentStatus.completed;
  bool get isTerminal =>
      status == PaymentStatus.completed ||
      status == PaymentStatus.failed ||
      status == PaymentStatus.refunded;
}

ادائیگی کا ادارہ تمام درست ریاستی تبدیلیوں کو نافذ کرتا ہے۔ جن ادائیگیوں پر پہلے سے کارروائی ہو رہی ہے وہ دوبارہ شروع نہیں کی جا سکتیں۔ نامکمل ادائیگیاں واپس نہیں کی جا سکتیں۔ اور ناکام ادائیگیاں مکمل نہیں کی جا سکتیں۔

یہ قواعد ہستی پر انکوڈ کیے گئے ہیں اور ریاست کی ہر تبدیلی پر لاگو ہوتے ہیں۔

درخواست کی پرت

class InitiatePaymentUseCase {
  final PaymentRepository _repository;

  InitiatePaymentUseCase(this._repository);

  Future> execute({
    required String reference,
    required double amount,
    required String currency,
    required String payerId,
    required String recipientId,
  }) async {
    try {
      final payment = Payment(
        reference: PaymentReference(reference),
        amount: Money(amount: amount, currency: currency),
        payerId: payerId,
        recipientId: recipientId,
      );

      payment.startProcessing();
      await _repository.save(payment);

      return Result.success(payment);
    } on DomainException catch (e) {
      return Result.failure(AppException.businessRule(e.message));
    } catch (e) {
      return Result.failure(AppException.unknown(e.toString()));
    }
  }
}

class VerifyPaymentUseCase {
  final PaymentRepository _repository;
  final PaymentVerificationService _verificationService;

  VerifyPaymentUseCase(this._repository, this._verificationService);

  Future> execute(String reference) async {
    try {
      final ref = PaymentReference(reference);
      final payment = await _repository.findByReference(ref);

      if (payment == null) {
        return Result.failure(
          AppException.notFound('Payment $reference not found'),
        );
      }

      final isVerified = await _verificationService.verify(payment);

      if (isVerified) {
        payment.complete();
      } else {
        payment.fail('Verification failed');
      }

      await _repository.save(payment);
      return Result.success(payment);
    } on DomainException catch (e) {
      return Result.failure(AppException.businessRule(e.message));
    } catch (e) {
      return Result.failure(AppException.unknown(e.toString()));
    }
  }
}

پریزنٹیشن پرت

@riverpod
class PaymentNotifier extends _$PaymentNotifier {
  @override
  AsyncValue build() => const AsyncData(null);

  Future initiatePayment({
    required String reference,
    required double amount,
    required String currency,
    required String payerId,
    required String recipientId,
  }) async {
    state = const AsyncLoading();

    final result = await ref.read(initiatePaymentUseCaseProvider).execute(
          reference: reference,
          amount: amount,
          currency: currency,
          payerId: payerId,
          recipientId: recipientId,
        );

    result.fold(
      onSuccess: (payment) => state = AsyncData(payment),
      onFailure: (error) => state = AsyncError(error, StackTrace.current),
    );
  }

  Future verifyPayment(String reference) async {
    state = const AsyncLoading();

    final result = await ref
        .read(verifyPaymentUseCaseProvider)
        .execute(reference);

    result.fold(
      onSuccess: (payment) => state = AsyncData(payment),
      onFailure: (error) => state = AsyncError(error, StackTrace.current),
    );
  }
}
class PaymentPage extends ConsumerWidget {
  const PaymentPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(paymentNotifierProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Payment')),
      body: state.when(
        data: (payment) {
          if (payment == null) return const PaymentForm();
          return PaymentStatusView(payment: payment);
        },
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (error, _) => PaymentErrorView(message: error.toString()),
      ),
    );
  }
}

انٹر فنکشنل مواصلات

آزاد افعال کو لامحالہ مواصلات کی ضرورت ہوتی ہے۔ ملازم کے افعال کو یہ چیک کرنے کی ضرورت پڑ سکتی ہے کہ آیا صارف کی توثیق ہوئی ہے۔ ادائیگیوں کے فنکشن کو پے رول کی ادائیگیوں کے بارے میں عملے کے فنکشن کو مطلع کرنے کی ضرورت پڑ سکتی ہے۔

انٹر فنکشن کمیونیکیشن کے اصول ہر فنکشن کی خود کو برقرار رکھتے ہیں۔

1. خصوصیات ایک دوسرے کی اندرونی تہوں سے براہ راست نہیں کھینچتی ہیں۔

ملازم کے افعال اس سے نہیں آنے چاہئیں: features/payment/domain/entities/payment.dart. فنکشنز کے درمیان براہ راست درآمدات سخت جوڑے پیدا کرتی ہیں، جو ماڈیولریٹی کے مقصد کو شکست دیتی ہے۔

2. مشترکہ ڈومین کا تصور مندرجہ ذیل ہے: shared/core.

اگر آپ کو ملازم اور ادائیگی کے افعال دونوں کے لیے درج ذیل تصورات کی ضرورت ہے: UserId یا Money قدر آبجیکٹ، تصور زندہ ہے shared/core/domain دونوں افعال وہاں سے لیے گئے ہیں۔

3. فنکشنز کے درمیان مواصلت متعین انٹرفیس کے ذریعے ہوتی ہے۔

اگر آپ کے ادائیگی کے فنکشن کو ملازمین کے بارے میں پے رول پر کارروائی کرنے کے بارے میں جاننے کی ضرورت ہے، تو اس کا مطلب ہے۔ EmployeeService متعین انٹرفیس shared/core. عملے کی تقریب عمل درآمد فراہم کرتی ہے۔ مجھے نہیں معلوم کہ ادائیگی کی کیا فعالیت فراہم کی گئی تھی۔

4. ایونٹ اور ڈومین کی اطلاعات مشترکہ ایونٹ بس کا استعمال کرتی ہیں۔

ادائیگی مکمل ہونے کے بعد ادائیگی کی خصوصیت شائع کی جائے گی۔ PaymentCompleted ڈومین کے واقعات۔ کوئی بھی فنکشن جس کو ادائیگی کی تکمیل پر ردعمل ظاہر کرنے کی ضرورت ہوتی ہے وہ اس ایونٹ کو سبسکرائب کرتا ہے۔ ادائیگی کی خصوصیت یہ نہیں جانتی ہے کہ کون سن رہا ہے۔ سننے کی خصوصیت براہ راست ادائیگی کی خصوصیت سے نہیں آتی ہے۔

نمونے جو ماڈیولرٹی کو بڑھاتے ہیں۔

کچھ ڈیزائن پیٹرن ماڈیولر فنکشنل ڈھانچے کے اندر خاص طور پر اچھی طرح سے کام کرتے ہیں۔

پروٹوٹائپ پیٹرن یہ ملازم فنکشن سے ملازم ٹیمپلیٹس بنانے کے لیے مفید ہے۔ آپ کی تنظیم میں مختلف کرداروں کے لیے معیاری ملازم پروفائلز ہو سکتے ہیں۔ ٹیمپلیٹ ملازم کی کلوننگ اسی ترتیب کے ساتھ ایک نئی مثال بناتی ہے، اس بات کو یقینی بناتی ہے کہ کاروباری قواعد کو نظرانداز نہیں کیا جاتا ہے اور کلون کی ریاستی منتقلی کے دوران ان پر عمل کیا جاسکتا ہے۔

class Employee {
  Employee clone() {
    return Employee(
      id: EmployeeId('${id.value}_copy_${DateTime.now().millisecondsSinceEpoch}'),
      name: name,
      teamId: teamId,
    );
  }
}

سنگلٹن پیٹرن یہ دراصل منفرد ڈومین آبجیکٹ پر لاگو ہوتا ہے۔ فی الحال توثیق شدہ صارف سنگلٹن ہے۔ موجودہ سیشن سنگلٹن ہے۔ یہ ہیں۔ shared/core کیونکہ یہ ایک کراس فنکشنل مسئلہ ہے۔

ذخیرہ پیٹرن یہ تمام افعال کے بنیادی ڈھانچے کے تجرید کی ریڑھ کی ہڈی ہے۔ ڈومین پرت انٹرفیس کی وضاحت کرتی ہے۔ بنیادی ڈھانچے کی پرت عمل درآمد فراہم کرتی ہے۔ ایپلی کیشن پرت انٹرفیس کو یہ جانے بغیر استعمال کرتی ہے کہ ان کے پیچھے کیا عمل درآمد ہے۔

مبصر پیٹرن، ڈومین ایونٹس فنکشنز کے درمیان سخت جوڑے کے بغیر مواصلت کی اجازت دیتے ہیں۔ ہر فنکشن واقعات کو شائع کرتا ہے جب ڈومین کی اہم حالتیں تبدیل ہوتی ہیں۔ دوسرے فنکشنز مشترکہ ایونٹ بس کے ذریعے ان ایونٹس کو سبسکرائب کرتے ہیں۔

بڑی ٹیموں تک پیمانہ

جیسے جیسے آپ کی ٹیم بڑھتی ہے فیچر ماڈیولرائزیشن زیادہ قیمتی ہو جاتی ہے۔ ہمارے پاس 10 انجینئرز بیک وقت مختلف خصوصیات پر کام کر رہے ہیں، اور ہر خصوصیت کی خود مختار نوعیت انضمام کے مستقل تنازعات کو روکتی ہے۔ ملازمین کی خصوصیات پر کام کرنے والے انجینئر اور ادائیگیوں کی خصوصیات پر کام کرنے والے انجینئر شاذ و نادر ہی ایک جیسی فائلوں کو چھوتے ہیں۔

اگر آپ کے پاس 30 انجینئرز ہیں، تو یہ خصوصیت ایک علیحدہ ڈارٹ پیکج ہو سکتی ہے۔ عملے کے افعال میں شامل ہیں: packages/employee. ادائیگی کی تقریب ہے packages/payment. وہ اشتراک پر بھروسہ کرتے ہیں۔ packages/core پیکج ہر پیکج میں منفرد مواد ہوتا ہے۔ pubspec.yamlآپ اپنی جانچ خود کر سکتے ہیں اور اپنے ورژنز کو آزادانہ طور پر منظم کر سکتے ہیں۔ یہ ماڈیولرائزیشن کا اگلا مرحلہ ہے اور فطری طور پر فنکشن فرسٹ آرکیٹیکچر کی پیروی کرتا ہے۔

50 انجینئرز کے ساتھ، آپ کی ٹیم پوری خصوصیت کی مالک بن سکتی ہے۔ ملازمین کی ایک ٹیم کی ملکیت۔ packages/employee. یہ ادائیگیوں کی ٹیم کی ملکیت ہے۔ packages/payment. انٹرفیس کے ذریعے بیان کردہ افعال کے درمیان معاہدہ packages/coreٹیموں کے درمیان API کی حد بن جاتی ہے۔ ٹیمیں آزادانہ طور پر فیچر پیکج کے نئے ورژن جاری کر سکتی ہیں، اور دوسری ٹیمیں اس ورژن کے تیار ہونے پر اسے اپ ڈیٹ کر سکتی ہیں۔

آج ہم جس فولڈر کا ڈھانچہ شروع کر رہے ہیں وہی ہے جو ہمارے 50 انجینئرز ایک پیکیج پر مبنی مونو ریپوزٹری میں توسیع کر رہے ہیں۔ تصور نہیں بدلتا۔ سرحدیں واضح ہو گئی ہیں۔

نتیجہ

فیچر ماڈیولرائزیشن فولڈر کی ساخت کی چال نہیں ہے۔ یہ سافٹ ویئر کی ساخت کے بارے میں ایک حکمت عملی کا فیصلہ ہے تاکہ یہ معیار پر سمجھوتہ کیے بغیر ترقی کر سکے۔

یہ کلین آرکیٹیکچر کے لیئرنگ رولز اور ڈی ڈی ڈی کے ڈومین ماڈلنگ الفاظ کو لیتا ہے اور انہیں فنکشنل لیول پر لاگو کرتا ہے۔ ہر فنکشن اپنے ڈومین، کاروباری قواعد، ڈیٹا تک رسائی، اور UI کا مالک ہے۔ کوئی رساو یا پھیلاؤ نہیں۔ آپ پوری ایپلیکیشن کو سمجھے بغیر ہر فیچر کو سمجھ سکتے ہیں، اس میں ترمیم کر سکتے ہیں اور جانچ سکتے ہیں۔

ملازم کا فنکشن کسی ادارے پر حاضری کے قواعد کا اطلاق کرتا ہے۔ ادائیگی کے افعال کسی ہستی پر ٹرانزیکشن اسٹیٹ ٹرانزیشن کو نافذ کرتے ہیں۔ غلط ڈیٹا کو ڈومین میں داخل ہونے سے روکنے کے لیے دونوں خصوصیات ویلیو آبجیکٹ کا استعمال کرتی ہیں۔ ڈومین سے ایپلیکیشن لیئر کے ذریعے پریزنٹیشن لیئر تک غلطیوں کو محفوظ طریقے سے پھیلانے کے لیے دونوں خصوصیات ایک ہی نتیجہ کا نمونہ استعمال کرتی ہیں۔

جیسے ہی عملے کی صلاحیتوں کے لیے نئے تقاضے آتے ہیں، اسٹاف فولڈر کھولیں اور تبدیلیاں کریں۔ ادائیگی کی فعالیت وہی رہے گی۔ ادائیگی کی خصوصیت میں ادائیگی کا نیا طریقہ شامل ہونے کے بعد، ادائیگیوں کا فولڈر کھولیں اور تبدیلیاں کریں۔ عملے کے کام وہی رہیں گے۔

اسٹینڈ لون کا اصل مطلب یہی ہے۔ یہ صرف نام کے ساتھ ایک فولڈر نہیں ہے۔ یہ ایک ایپلیکیشن کا مکمل طور پر عمودی ٹکڑا ہے جو اپنی ضرورت کی ہر چیز کا مالک ہے اور ایسی کوئی چیز شیئر نہیں کرتا جس کا اشتراک نہیں کیا جانا چاہیے۔

یہ فنکشنل ماڈیولرائزیشن ہے۔ اس طرح فلٹر ایپس کوڈ بیس کو توڑے بغیر ایک انجینئر سے پچاس تک سکیل کرتی ہیں۔

کوڈنگ کا مزہ لیں !!

Scroll to Top