Skip to content

فصل ۹ — مالکیت داده و تراکنش‌های توزیع‌شده

خلاصهٔ تحلیلی — این فصل به‌صورت تحلیلی بازنویسی شده و ترجمهٔ لفظ‌به‌لفظ نیست.

مسئله و زمینه

پس از جداسازی data domainها، دو پرسش باقی می‌ماند: «چه سرویسی مجاز به تغییر کدام داده است؟» و «اگر یک درخواست کسب‌وکاری از چند سرویس عبور کرد، صحت آن چگونه حفظ می‌شود؟». پاسخ سادهٔ مالکیت این است که سرویسی که می‌نویسد، مالک است؛ اما با چند نویسنده، این قاعده بلافاصله به مسئلهٔ معماری تبدیل می‌شود.

مالکیت فقط نام‌گذاری نیست. owner مرز change، contract، access و پاسخ‌گویی را تعیین می‌کند. خواندن دادهٔ سرویس دیگر مالکیت ایجاد نمی‌کند؛ نوشتن مستقیم، ایجاد می‌کند و bounded context را گسترش می‌دهد.

مفاهیم و الگوهای محوری

سناریوهای مالکیت

مالکیت یگانه: فقط یک سرویس جدول را می‌نویسد. ساده‌ترین و مطلوب‌ترین حالت است: آن سرویس و داده‌اش یک bounded context می‌سازند و سرویس‌های خواننده نباید مستقیم به schema دست بزنند.

مالکیت عمومی: بیشتر یا همهٔ سرویس‌ها باید در یک جدول بنویسند؛ audit نمونهٔ شناخته‌شده است. shared table مشکل‌های schema، connection، scale و fault tolerance را بازمی‌گرداند. راه مناسب، یک سرویس تخصصی مالک و ارسال درخواست نوشتن به آن است: پیام پایدار و asynchronous برای fire-and-forget، یا REST/gRPC/request-reply وقتی پاسخ لازم است.

مالکیت مشترک: چند سرویس محدود در یک domain جدول را می‌نویسند. چهار پاسخ ممکن است:

  • table split: هر بخش داده به جدول مالک خودش منتقل می‌شود. ownership شفاف‌تر می‌شود، اما ایجاد/حذف entityهای مرتبط به synchronization و غالباً consistency نهایی نیاز پیدا می‌کند.
  • data domain مشترک: جدول در schema مشترک می‌ماند. performance و integrity محلی را حفظ می‌کند، ولی scope تغییر، test و deployment را وسیع و governance نوشتن را لازم می‌سازد.
  • delegate: یکی owner می‌شود و دیگری از راه service call می‌نویسد. domain priority معمولاً از واگذاری صرفاً بر اساس throughput بهتر است؛ cache می‌تواند فشار خواندن را کم کند.
  • ادغام سرویس‌ها: dependency و مشکل transaction را حذف می‌کند، اما سرویس coarse-grainedتر، scale مشترک، blast radius بالاتر و test/deployment بزرگ‌تر می‌سازد.

از ACID محلی تا BASE توزیع‌شده

ACID یعنی atomicity، consistency، isolation و durability برای یک واحد کار در یک database scope. وقتی هر سرویس دیتای خودش را commit می‌کند، request سراسری دیگر ACID نیست: بخشی ممکن است ثبت شود، بخش دیگر شکست بخورد، دادهٔ نیمه‌کاره برای دیگران قابل رؤیت باشد و دوام هر commit، موفقیت کل فرایند را تضمین نکند.

در مقابل، تراکنش توزیع‌شده با BASE زندگی می‌کند: availability پایه، soft state در میانهٔ کار و eventual consistency. این مدل «خرابی را نادیده نمی‌گیرد»؛ می‌پذیرد که حالت موقت ناسازگار وجود دارد و باید زمان، retry، compensation و مشاهده‌پذیری آن طراحی شود.

الگوهای eventual consistency

  • همگام‌سازی پس‌زمینه: job یا service دوره‌ای منابع را align می‌کند. پاسخ کاربر سریع است و برای سیستم‌های مستقل ناهمگون مناسب است، اما تاخیر طولانی، دسترسی گسترده به جدول‌های دیگر، شکستن ownership و تکرار business logic دارد.
  • درخواست orchestrated: orchestrator participantها، state و خطا را در حین request هماهنگ می‌کند. consistency زودتر دیده می‌شود، ولی latency، dependency همگام و compensation پیچیده بالا می‌رود. شکست در compensation ممکن است intervention انسانی بخواهد.
  • رویدادمحور: owner رخداد را publish و participantها مستقلاً مصرف می‌کنند. پاسخ‌گویی، coupling و زمان همگرایی خوب است؛ اما consumer باید durable باشد و failure پردازش با retry و DLQ و فرآیند repair مدیریت شود.

موازنه‌ها

جداسازی جدول، ownership شفاف می‌دهد اما باید میان availability و consistency تصمیم بگیرید: sync confirmation سازگاری سریع‌تر و latency بیشتر می‌آورد؛ پیام async پاسخ سریع‌تر و احتمال data lag. Data domain مشترک این مشکلات عملیاتی را کم می‌کند اما دوباره استقلال تغییر را می‌گیرد. هیچ تکنیک مشترک‌نویسی رایگان نیست.

همچنین «اتمیک بودن» باید از زبان business بیاید. اگر ثبت مشتری بدون رمز یا پرداخت بی‌معناست، ACID محلی یا طراحی جبرانی قوی لازم است. اگر پاک‌سازی دادهٔ ثانویه می‌تواند شبانه انجام شود، background sync ممکن است از orchestration سنگین بهتر باشد.

راهنمای عملی

  1. ابتدا جدول‌های single-owner را مشخص کنید؛ سپس موارد common و joint را جداگانه حل کنید.
  2. برای هر write workflow، invariant کسب‌وکار، owner، protocol، idempotency، retry، timeout و مسیر DLQ را ثبت کنید.
  3. قبل از table split، payload رویداد، ترتیب عملیات و رفتار سرویس مقصد هنگام unavailable بودن را طراحی کنید.
  4. برای delegate، reason انتخاب owner را صریح کنید: domain responsibility یا نیاز عملیاتی؛ dependency جدید را با cache یا async messaging کاهش دهید.
  5. حالت‌های شکست، از جمله شکست compensation، را در ADR و runbook بگنجانید؛ «eventually» بدون مالک repair، تضمین نیست.

نکات کلیدی فصل

  • مالکیت از write responsibility می‌آید، نه از تعداد readها.
  • جدول مشترک نشانهٔ نیاز به تصمیم است، نه مجوز پیش‌فرض برای shared database.
  • ACID میان سرویس‌ها از بین می‌رود؛ BASE و eventual consistency نیازمند طراحی آگاهانه‌اند.
  • انتخاب الگوی consistency باید به تحمل business برای تاخیر و ناسازگاری موقت متصل باشد.

یادداشت‌های مترجم

  • owner، bounded context، ACID، BASE، orchestrator و DLQ به دلیل کاربرد تثبیت‌شده، لاتین نگاه داشته شده‌اند.
  • «تراکنش جبرانی» برای compensating transaction به‌کار رفته است؛ این عملیات الزاماً rollback فنیِ کامل نیست.