مصطفی جعفرزاده
مصطفی جعفرزاده
خواندن ۴ دقیقه·۲ ماه پیش

چگونه یک سیستم رزرو بلیت هواپیما با معماری Event-Driven و Saga پیاده‌سازی کنیم؟

مقدمه:

در دنیای نرمافزارهای بزرگ و توزیعشده، مدیریت تراکنشهای پیچیده و حفظ همزمانی دادهها به چالشهای مهمی تبدیل شده است. یکی از نمونههای کاربردی چنین سیستمی، سیستمهای رزرو بلیت هواپیما است. در این مقاله به بررسی پیادهسازی یک سیستم رزرو بلیت هواپیما با استفاده از معماری Clean Architecture، Saga Pattern، و Event-Driven Architecture میپردازیم. ترکیب این الگوها و فناوریها به ما امکان میدهد تا سیستمهای مقیاسپذیر، مقاوم در برابر خطا و کارآمد طراحی کنیم.

نکته مهم: در تهیه این مقاله از هوش مصنوعی استفاده شده است.

ساختار و مزایای ترکیب الگوهای استفاده شده

۱. Entities (موجودیتها)

در لایه دامنه، ما از موجودیتهای Flight و Booking برای مدلسازی عملیات رزرو استفاده میکنیم. موجودیت Flight مدیریت رزرو صندلیها و جلوگیری از رزرو بیش از حد را بر عهده دارد. همچنین، از مکانیزم Optimistic Locking برای مدیریت همزمانی استفاده شده است، که این کار با افزایش نسخه پرواز پس از هر رزرو صندلی انجام میشود.


// src/domain/entities/Flight.js
class Flight {
constructor(id, airlineId, departureTime, arrivalTime, price, availableSeats, version) {
this.id = id;
this.airlineId = airlineId;
this.departureTime = departureTime;
this.arrivalTime = arrivalTime;
this.price = price;
this.availableSeats = availableSeats;
this.version = version; // برای Optimistic Locking
}
reserveSeats(seatCount) {
if (this.availableSeats >= seatCount) {
this.availableSeats -= seatCount;
} else {
throw new Error('Not enough seats available');
}
}
incrementVersion() {
this.version += 1; // برای Optimistic Locking
}
}
module.exports = Flight;

۲. Use Cases (موارد استفاده)

در لایه کاربرد، عملیاتهای مهمی نظیر CreateBooking و ConfirmPayment پیادهسازی شدهاند. بهویژه در فرآیند ConfirmPayment از یک Timeout برای جلوگیری از طولانی شدن تراکنشها استفاده شده است.


class ConfirmPayment {
constructor(paymentRepository, bookingRepository) {
this.paymentRepository = paymentRepository;
this.bookingRepository = bookingRepository;
this.timeout = 5000; // Timeout of 5 seconds
}
async execute(paymentId) {
const payment = await this.paymentRepository.findById(paymentId);
if (!payment) {
throw new Error('Payment not found');
}
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Payment confirmation timed out')), this.timeout)
);
const paymentConfirmation = payment.confirm();
await Promise.race([timeoutPromise, paymentConfirmation]);
await this.paymentRepository.save(payment);
}
}

۳. Event-Driven Architecture (معماری رویدادمحور)

در این سیستم از معماری رویدادمحور برای حفظ همزمانی و مقیاسپذیری استفاده شده است. برای مثال، RabbitMQEventBus برای مدیریت رویدادها بهکار گرفته شده و مکانیزم Retry برای مدیریت خطاها و ارسال مجدد رویدادها به کار میرود. همچنین، در صورت شکست رویداد، رویداد به یک Recovery Queue منتقل میشود تا بعداً پردازش شود.

class RabbitMQEventBus {
async publish(event, data, retries = 3) {
try {
this.channel.sendToQueue(this.queue, Buffer.from(JSON.stringify({ event, data })));
} catch (error) {
if (retries > 0) {
console.warn(Failed to publish event. Retrying... Attempts left: ${retries});
await new Promise(res => setTimeout(res, 2000));
await this.publish(event, data, retries - 1);
} else {
const recoveryQueue = 'recovery_queue';
this.channel.sendToQueue(recoveryQueue, Buffer.from(JSON.stringify({ event, data })));
}
}
}
}

۴. Saga برای مدیریت تراکنشهای توزیعشده

در سیستمهای توزیعشده که ممکن است تراکنشها به صورت همزمان در چندین سرویس انجام شوند، استفاده از الگوی Saga ضروری است. در این پروژه، BookingSaga برای مدیریت تراکنشهای پیچیده نظیر رزرو صندلیها و پردازش پرداختها استفاده میشود. در صورت شکست یک تراکنش، سیستم بهطور خودکار عملیات جبرانی را انجام میدهد تا سیستم در وضعیت پایدار قرار گیرد.


EventBus.subscribe('BOOKING_CREATED', async (data) => {
const { bookingId, userId, amount } = data;
try {
const confirmPayment = new ConfirmPayment(paymentRepository, bookingRepository);
await confirmPayment.execute(bookingId);
} catch (error) {
console.error(Payment failed for booking ${bookingId}. Compensating...);
const booking = await bookingRepository.findById(bookingId);
booking.cancel();
await bookingRepository.save(booking);
}
});

۵. Eventual Consistency

با استفاده از معماری Event-Driven و پیادهسازی Eventual Consistency، سیستم میتواند تراکنشها را با وجود تأخیرهای احتمالی بین سرویسها به صورت نهایی پردازش کند. EventLogRepository لاگ رویدادها را ذخیره کرده و بهروزرسانی وضعیت تراکنشها را دنبال میکند.


class EventLogRepository {
constructor() {
this.events = [];
}
async logEvent(event, data) {
this.events.push({ event, data, status: 'PENDING' });
}
async markEventProcessed(eventId) {
const event = this.events.find(e => e.id === eventId);
if (event) {
event.status = 'PROCESSED';
}
}
}

نتیجهگیری

استفاده از ترکیب Clean Architecture، Saga Pattern، و Event-Driven Architecture مزایای بسیاری را برای پیادهسازی سیستمهای مقیاسپذیر و مقاوم در برابر خطا فراهم میکند. این ترکیب به ما امکان میدهد تا تراکنشهای پیچیده را به صورت توزیعشده و ایمن مدیریت کنیم. همچنین با استفاده از ابزارهایی مانند RabbitMQ و Kafka میتوانیم از مزایای مقیاسپذیری و پایداری بیشتر بهرهمند شویم.

این معماری با تمرکز بر همزمانی، انعطافپذیری و کنترل خطاها بهخوبی به نیازهای یک سیستم رزرو بلیت هواپیما پاسخ میدهد.

clean architectureevent driven
برنامه نویس علاقه مند به طراحی الگوریتم
شاید از این پست‌ها خوشتان بیاید