Software Engineering

تسلط بر Event Sourcing و CQRS برای میکروسرویس‌های مقیاس‌پذیر

در منظره مدرن سیستم‌های توزیع‌شده، مقیاس‌پذیری و یکپارچگی داده‌ها حیاتی هستند. معماری‌های سنتی CRUD (ایجاد، خواندن، به‌روزرسانی، حذف) اغلب در برآوردن نیازهای برنامه‌های با تراکم بالا که نسبت فرمان به خواندن می‌تواند تا ۱۰۰۰:۱ باشد، با مشکل مواجه می‌شوند. اینجاست که تفکیک مسئولیت‌های فرمان و پرس‌وجو (CQRS) و رویداد-محوری (Event Sourcing) برای ارائه یک جایگزین قدرتمند و با عملکرد بالا همگرا می‌شوند.

درک مفاهیم پایه

CQRS یک الگوی معماری است که عملیات خواندن و نوشتن را به مدل‌های متفاوتی جدا می‌کند. به جای اینکه یک مدل دامنه واحد هر دو را مدیریت کند، شما یک سمت فرمان دارید که مسئول پردازش فرمان‌ها و نوشتن داده‌ها است و یک سمت پرس‌وجو که برای خواندن و نمایش داده‌ها بهینه شده است. این جداسازی به تیم‌ها اجازه می‌دهد تا ظرفیت‌های خواندن و نوشتن را به صورت مستقل مقیاس‌پذیر کنند و هر سمت را برای بار کاری خاص خود بهینه سازند.

رویداد-محوری (Event Sourcing) این موضوع را گامی فراتر می‌برد و وضعیت یک موجود را نه به عنوان مقادیر فعلی آن، بلکه به عنوان دنباله‌ای از رویدادها ذخیره می‌کند. هر تغییری در وضعیت برنامه به عنوان یک رویداد ثبت می‌شود. برای بازسازی وضعیت فعلی، کافی است این رویدادها را مجدداً پخش کنید. این رویکرد یک ردیابی حسابرسی کامل فراهم می‌کند و ویژگی‌های قدرتمندی مانند اشکال‌زدایی سفر در زمان و پرس‌وجوهای زمانی را امکان‌پذیر می‌سازد.

پیاده‌سازی الگو: یک مثال عملی

بیایید به یک پیاده‌سازی ساده‌شده از یک سرویس سفارش نگاه کنیم. در یک سیستم سنتی، به‌روزرسانی یک سفارش ممکن است به این شکل باشد:

// رویکرد سنتی UPDATE
await db.orders.update(
  { id: orderId }, 
  { $set: { status: 'SHIPPED' } }
);

در یک معماری مبتنی بر رویداد-محوری، ما سفارش را مستقیماً به‌روزرسانی نمی‌کنیم. در عوض، یک رویداد جدید را به انبار رویدادها اضافه می‌کنیم.

// رویکرد رویداد-محوری: افزودن یک رویداد
const event = {
  aggregateId: orderId,
  type: 'OrderShippedEvent',
  timestamp: new Date(),
  payload: {
    trackingNumber: 'TRK-12345',
    shippedBy: 'FedEx'
  }
};

await eventStore.append(event);

وضعیت فعلی سفارش با پخش مجدد تمام رویدادهای مربوط به آن شناسه گروه (aggregate ID) استخراج می‌شود. هنگامی که یک درخواست خواندن وارد می‌شود، یک فرآیند جداگانه (اغلب یک دستکاری‌کننده رویداد یا Event Handler) این داده‌ها را به یک مدل خواندن غیرعادی (denormalized view model) مانند یک سند NoSQL یا یک جدول SQL تخصصی که برای بازیابی سریع بهینه شده است، نمایش می‌دهد.

// نمایش رویدادها در یک مدل خواندن
class OrderReadModel {
  constructor(eventStore, repository) {
    this.eventStore = eventStore;
    this.repository = repository;
  }

  async handle(event) {
    if (event.type === 'OrderShippedEvent') {
      await this.repository.update({
        _id: event.aggregateId,
        $set: {
          status: 'SHIPPED',
          trackingNumber: event.payload.trackingNumber
        }
      });
    }
  }
}

مزایا و چالش‌ها

پیاده‌سازی CQRS و رویداد-محوری مزایای قابل توجهی ارائه می‌دهد:

  • مقیاس‌پذیری: شما می‌توانید کپی‌های خواندن خود را به صورت مستقل از قطعات نوشتن (write shards) مقیاس‌پذیر کنید.
  • قابلیت حسابرسی: هر عملی به عنوان یک رویداد غیرقابل تغییر ثبت می‌شود که رعایت مقررات و اشکال‌زدایی را آسان‌تر می‌کند.
  • انعطاف‌پذیری: شما می‌توانید مدل‌های خواندن جدید را در صورت نیاز ایجاد کنید بدون اینکه بر سمت نوشتن تأثیر بگذارد.

با این حال، این پیچیدگی هزینه‌ای دارد. شما باید ناهمگنی نهایی (eventual consistency) را مدیریت کنید، نسخه‌بندی رویدادها را برای جلوگیری از تغییرات مخرب مدیریت کنید و با سربار عملیاتی نگهداری انبارهای رویداد روبرو شوید. این راه‌حل جادویی نیست؛ زمانی از آن استفاده کنید که عدم تقارن خواندن/نوشتن یا نیازهای حسابرسی شما پیچیدگی معماری را توجیه کند.

نتیجه‌گیری

رویداد-محوری و CQRS ابزارهای قدرتمندی برای ساخت میکروسرویس‌های مقاوم و مقیاس‌پذیر هستند. با جداسازی فرمان‌ها از پرس‌وجوها و بهره‌گیری از غیرقابل تغییر بودن رویدادها، توسعه‌دهندگان می‌توانند سیستم‌هایی ایجاد کنند که تحت بار کاری سنگین عملکردی استثنایی دارند. اگرچه منحنی یادگیری شیب‌دار است، اما منافع بلندمدت از نظر قابلیت نگهداری، مقیاس‌پذیری و یکپارچگی داده‌ها، آن را به سرمایه‌گذاری ارزشمندی برای برنامه‌های سازمانی پیچیده تبدیل می‌کند.

Share: