Software Architecture

تسلط بر CQRS و Event Sourcing: نقشه راه سیستم‌های تجاری مقیاس‌پذیر

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

اگرچه این الگوها اغلب با هم مورد بحث قرار می‌گیرند، اما مشکلات متمایزی را حل می‌کنند. CQRS منطق به‌روزرسانی حالت (دستورات) را از منطق بازیابی حالت (کوئری‌ها) جدا می‌کند. Event Sourcing نحوه ذخیره‌سازی حالت را تغییر می‌دهد—به جای ذخیره حالت فعلی، ما دنباله‌ای از رویدادها را ذخیره می‌کنیم که هر تغییری را توصیف می‌کنند. این دو با هم ترکیبی قدرتمند برای ساخت سیستم‌های مقاوم، قابل حسابرسی و مقیاس‌پذیر تشکیل می‌دهند.

جداسازی خواندن و نوشتن با CQRS

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

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

در اینجا یک نمایش مفهومی از ساختار هندلر CQRS آورده شده است:

class TransferMoneyCommandHandler {
    constructor(eventStore, accountRepository) {
        this.eventStore = eventStore;
        this.accountRepository = accountRepository;
    }

    async execute(command) {
        // 1. بازیابی ریشه مجموعه (aggregate root)
        const account = await this.accountRepository.getById(command.SourceAccountId);

        // 2. اعمال منطق کسب‌وکار
        account.transfer(command.Amount, command.DestinationAccountId);

        // 3. ذخیره رویدادهای جدید (نه حالت)
        const events = account.getUncommittedEvents();
        await this.eventStore.append(events);
        
        // 4. به‌روزرسانی مدل خواندن به صورت ناهمگام
        this.publishEventsToReadModel(events);
    }
}

ساخت ردپای حسابرسی با Event Sourcing

Event Sourcing بخش «نوشتن» CQRS را یک مرحله فراتر می‌برد. به جای ذخیره حالت فعلی یک شیء، شما لیستی از رویدادهای رخ داده را ذخیره می‌کنید. حالت فعلی صرفاً یک تصویر (projection) از این رویدادها است.

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

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

بازسازی حالت و تصویربرداری (Projection)

یکی از مهم‌ترین تغییرات در این معماری، مدیریت خواندن‌ها است. از آنجا که مخزن نوشتن تنها حاوی رویدادها است، شما به روشی برای پاسخگویی کارآمد به کوئری‌ها نیاز دارید. این کار از طریق تصویربرداری (Projections) انجام می‌شود. تصویربرداری‌ها مدل‌های خواندی هستند که به رویدادها مشترک می‌شوند و مخازن داده نرمال‌زدایی شده را که برای کوئری‌زنی بهینه شده‌اند، به‌روزرسانی می‌کنند.

تصور کنید که نیاز به نمایش لیستی از تراکنش‌های اخیر مرتب شده بر اساس تاریخ دارید. به جای کوئری زدن روی لاگ رویداد خام (که پرهزینه است)، شما یک تصویربرداری دارید که به TransactionCreatedEvent گوش می‌دهد و داده‌های مرتبط را در یک پایگاه داده خواندن اختصاصی (مانند Elasticsearch یا یک مخزن NoSQL) می‌نویسد.

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

ملاحظات عملی و چالش‌ها

اگرچه قدرتمند هستند، CQRS و Event Sourcing پیچیدگی ایجاد می‌کنند. شما باید سازگاری نهایی (eventual consistency) را مدیریت کنید و اطمینان حاصل کنید که مدل خواندن پس از یک دستور نوشتن، در نهایت به‌روزرسانی می‌شود. همچنین به ابزارهای قوی برای مدیریت تکامل طرح رویداد نیاز دارید؛ همان‌طور که منطق کسب‌وکار شما تغییر می‌کند، طرح رویداد شما باید بدون شکستن قابلیت‌های پخش مجدد تاریخچه، سازگار شود.

علاوه بر این، عیب‌یابی می‌تواند سخت‌تر باشد زیرا حالت به صورت مستقیم در پایگاه داده قابل مشاهده نیست. با این حال، این مبادله برای سیستم‌هایی که مقیاس‌پذیری بالا، ردپاهای حسابرسی دقیق و منطق دامنه پیچیده را طلب می‌کنند، ارزشمند است.

نتیجه‌گیری

CQRS و Event Sourcing راه‌حل‌های جادویی نیستند، اما ابزارهای ضروری برای چالش‌های معماری خاص هستند. آن‌ها به توسعه‌دهندگان امکان می‌دهند سیستم‌هایی بسازند که نه تنها مقیاس‌پذیر و کارآمد، بلکه به دلیل تاریخچه غیرقابل تغییرشان، بینش عمیقی ارائه می‌دهند. برای توسعه‌دهندگان متوسط تا پیشرفته‌ای که به دنبال غلبه بر فرآیندهای کسب‌وکار پیچیده هستند، تسلط بر این الگوها گامی حیاتی به سمت بلوغ معماری است.

Share: