Database Engineering

مدل‌سازی داده برای رویداد-محوری: مدیریت پرتوها و تکامل طرح در سیستم‌های توزیع‌شده

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

چالش اصلی: رویدادهای غیرقابل تغییر در برابر پرتوهای قابل تغییر

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

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

مدیریت تکامل طرح در رویدادها

یکی از مشکلات پایدار در رویداد-محوری، انحراف طرح است. اگر برنامه شما برای شامل کردن فیلدهای جدید در یک رویداد تکامل یابد، چگونه سازگاری با خدمات قدیمی‌تری که هنوز به‌روز نشده‌اند را تضمین می‌کنید؟

قانون طلایی این است: هرگز نام یا نوع یک خاصیت رویداد موجود را تغییر ندهید. همیشه خاصیت‌های جدید را اضافه کنید. اگر مجبور به تغییر یک خاصیت هستید، یک نوع رویداد جدید ایجاد کنید. برای تسهیل این کار، طرح رویداد شما باید شامل یک شماره نسخه باشد.

یک رویداد ساده ایجاد سفارش را در نظر بگیرید. در حالت اولیه، ممکن است به این شکل باشد:


{
  "eventId": "123e4567-e89b-12d3-a456-426614174000",
  "aggregateId": "order-001",
  "timestamp": "2023-10-27T10:00:00Z",
  "type": "OrderCreated",
  "version": 1,
  "customerId": "cust-123",
  "totalAmount": 150.00,
  "currency": "USD"
}

بعداً، اگر نیاز به ردیابی آدرس ارسال در زمان ایجاد سفارش داشته باشید، نباید رویداد موجود را تغییر دهید. در عوض، ممکن است یک نوع رویداد جدید معرفی کنید یا یک فیلل خالی‌پذیر اضافه کنید در حالی که از ساختار قدیمی در منطق تجزیه خود پشتیبانی می‌کنید. استفاده از کتابخانه‌ای مانند Newtonsoft.Json یا System.Text.Json با ویژگی‌های ignoreDataMember می‌تواند به مدیریت این تغییرات به صورت شایسته کمک کند.

طراحی پرتوهای مقاوم

پرتوها مکانیسمی هستند که از طریق آن رویدادهای غیرقابل تغییر را به مدل‌های خوانشی قابل پرس‌وجو تبدیل می‌کنیم. آن‌ها باید آی‌دِم‌پُتان (Idempotent) و جداساز از منطق دامنه‌ای که رویدادها را تولید می‌کند، باشند.

یک دستگیرنده پرتو به خوبی طراحی شده باید:

  1. به انواع خاصی از رویدادها مشترک شود.
  2. داده‌های رویداد را به فرمتی مناسب برای پایگاه داده خوانشی (اغلب یک انبار رابطه‌ای یا مستند) تبدیل کند.
  3. رویدادهای مفقود یا خارج از ترتیب را به صورت شایسته مدیریت کند.

در اینجا یک مثال مفهومی از یک دستگیرنده پرتو در C# آورده شده است:


public class OrderTotalProjection : IHandleEvents<OrderCreated>, IHandleEvents<OrderCancelled>
{
    private readonly IDocumentSession _session;

    public OrderTotalProjection(IDocumentSession session)
    {
        _session = session;
    }

    public void Handle(OrderCreated @event)
    {
        var projection = new OrderProjection
        {
            OrderId = @event.AggregateId,
            Status = "Open",
            Total = @event.TotalAmount
        };
        
        // منطق Upsert برای اطمینان از آی‌دِم‌پُتانسی
        _session.Store(projection); 
    }

    public void Handle(OrderCancelled @event)
    {
        var projection = await _session.LoadAsync<OrderProjection>(@event.AggregateId);
        if (projection != null)
        {
            projection.Status = "Cancelled";
            projection.Total = 0;
            _session.Store(projection);
        }
    }
}

مهاجرت پرتوها در محیط تولید

وقتی طرح مدل خوانشی شما تغییر می‌کند—برای مثال، اضافه کردن یک فیلد shippingAddress به OrderProjection—نمی‌توانید صرفاً یک مهاجرت پایگاه داده اجرا کنید. شما همچنین باید داده‌های پرتو را از جریان رویدادها بازسازی کنید. این کار معمولاً با انجام مراحل زیر انجام می‌شود:

  1. استقرار کد پرتوی جدید در کنار کد قدیمی.
  2. اجرای یک کارگر پس‌زمینه که تمام رویدادها را برای تجمیع‌های تحت تأثیر دوباره پخش می‌کند.
  3. به‌روزرسانی پایگاه داده خوانشی با طرح جدید.
  4. تغییر API برای پرس‌وجو از نمای جدید پس از تکمیل پرکردن مجدد.

نتیجه‌گیری

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

Share: