Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga

Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga

Mình sẽ sử dụng lại ví dụ Booking Service Online trong phần trước đó

saga-introduction.jpeg

Ở đây mình sẽ tạo ra các isolated service, đồng thời thiết kế để chúng giao tiếp với nhau thông qua một Message Queue. Ở đây mình chọn RabbitMQ làm Message Queue.

 

1. Triển khai BookingService

// booking-service.ts
import express from 'express'
import amqp from 'amqplib'

const app = express()
const PORT = 3001

app.use(express.json())

let channel: amqp.Channel

const paymentQueue = 'payment_queue'

app.post('/booking', async (req, res) => {
  const { userId, eventId, numberOfSeats } = req.body

  // Pre-step 1: Validate booking request

  // Pre-step 2: Save booking request to application database
  const booking = {
    userId,
    eventId,
    numberOfSeats,
    bookingReservedSuccessfully: true,
  }

  /**
   * Step 1: Send Booking Request to PaymentService
   */
  if (booking.bookingReservedSuccessfully) {
    await sendMessageToQueue(paymentQueue, { booking })
  }

  res.json({ message: 'Booking request sent successfully' })
})

async function connectQueue(queue: string) {
  const connection = await amqp.connect('amqp://localhost')

  channel = await connection.createChannel()

  await channel.assertQueue(queue)
}

async function sendMessageToQueue(queue: string, message: unknown) {
  await channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)))
}

app.listen(PORT, async () => {
  console.log(`BookingService is running on http://localhost:${PORT}`)

  await connectQueue(paymentQueue)
})

 

BookingService xử lý các yêu cầu HTTP POST để tạo booking mới. BookingService cố gắng đặt trước một chỗ và nếu thành công, nó sẽ gửi một message đến PaymentService thông qua một queue trong RabbitMQ có tên là payment_queue.

 

2. Triển khai PaymentService

// booking-service.ts
import amqp from 'amqplib'

let channel: amqp.Channel

const paymentQueue = 'payment_queue'
const seatUpdatingQueue = 'seat_update_queue'
const compensationQueue = 'compensation_queue'

async function processPayment() {
  const connection = await amqp.connect('amqp://localhost')

  channel = await connection.createChannel()

  await channel.assertQueue(paymentQueue)
  await channel.assertQueue(seatUpdatingQueue)
  await channel.assertQueue(compensationQueue)

  channel.consume(paymentQueue, async (msg) => {
    const { booking } = JSON.parse(msg.content.toString())

    // Pre-step: Process payment
    const paymentProcessedSuccessfully = true

    /**
     * Step 2:
     * Send seats update request to SeatUpdatingService
     * Or Reverse the booking by sending a compensation request to CompensationService
     */
    if (paymentProcessedSuccessfully) {
      await sendMessageToQueue(seatUpdatingQueue, { booking })
    } else {
      await sendMessageToQueue(compensationQueue, {
        booking,
        event: 'PaymentServiceFailed',
      })
    }

    // Acknowledge the message
    channel.ack(msg as amqp.Message)
  })
}

async function sendMessageToQueue(queue: string, message: unknown) {
  await channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)))
}

processPayment()

PaymentService lắng nghe các message trên payment_queue. Nó chịu trách nhiệm xử lý payment, đồng thời nó cũng sẽ gửi một message đến seat_update_queue nếu thanh toán thành công hoặc compensation_queue nếu thanh toán thất bại.

 

3. Triển khai SeatUpdatingService

// seat-update-service.ts
import amqp from 'amqplib'

let channel: amqp.Channel

const seatUpdatingQueue = 'seat_update_queue'
const notificationQueue = 'notification_queue'
const compensationQueue = 'compensation_queue'

async function processSeatUpdate() {
  const connection = await amqp.connect('amqp://localhost')

  channel = await connection.createChannel()

  await channel.assertQueue(seatUpdatingQueue)

  channel.consume(seatUpdatingQueue, async (msg) => {
    const { booking } = JSON.parse(msg?.content.toString() || '{}')

    // Add your seat updating logic here
    const seatsUpdatedSuccessfully = true

    /**
     * Step 3:
     * Send seats update request to NotificationService
     * Or Reverse the booking by sending a compensation request to CompensationService
     */
    if (seatsUpdatedSuccessfully) {
      await sendMessageToQueue(notificationQueue, { booking, isSuccess: true })
    } else {
      await sendMessageToQueue(compensationQueue, {
        booking,
        event: 'SeatUpdatingServiceFailed',
      })
    }

    // Acknowledge the message
    channel.ack(msg as amqp.Message)
  })
}

async function sendMessageToQueue(queue: string, message: unknown) {
  await channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)))
}

processSeatUpdate()

SeatUpdatingService lắng nghe các message trên seat_update_queue. Nó chịu trách nhiệm xử lý cập nhật chỗ, đồng thời nó cũng sẽ gửi một message đến notification_queue nếu thực hiện thành công hoặc compensation_queue nếu thực hiện thất bại.

 

4. Triển khai CompensationService

// seat-update-service.ts
import amqp from 'amqplib'

let channel: amqp.Channel

const compensationQueue = 'compensation_queue'
const notificationQueue = 'notification_queue'

async function processCompensation() {
  const connection = await amqp.connect('amqp://localhost')

  channel = await connection.createChannel()

  await channel.assertQueue(compensationQueue)

  channel.consume(compensationQueue, async (msg) => {
    const { booking, event } = JSON.parse(msg?.content.toString() || '{}')

    console.log(`Compensating for user ${booking.userId} and event ${event}`)

    /**
     * Step 4: Do compensation
     */
    switch (event) {
      case 'PaymentServiceFailed':
        console.log(
          `Compensating PaymentServiceFailed for user ${booking.userId}`
        )
        // Add your compensation logic here
        break
      case 'SeatUpdatingServiceFailed':
        console.log(
          `Compensating SeatUpdatingServiceFailed for user ${booking.userId}`
        )
        // Add your compensation logic here
        break
    }

    // Send notification to user
    await sendMessageToQueue(notificationQueue, { booking, isSuccess: false })

    // Acknowledge the message
    channel.ack(msg as amqp.Message)
  })
}

async function sendMessageToQueue(queue: string, message: unknown) {
  await channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)))
}

processCompensation()

 

CompensationService lắng nghe các message trên compensation_queue. Khi CompensationService nhận được một message cho biết có sự cố khi xử lý booking, nó sẽ thực hiện một compensation transaction để hủy booking và đưa dữ liệu về trạng thái ban đầu, giúp hệ thống nhất quán về dữ liệu.

 

5. Triển khai NotificationService

// seat-update-service.ts
import amqp from 'amqplib'

let channel: amqp.Channel

const notificationQueue = 'notification_queue'

async function processSendNotification() {
  const connection = await amqp.connect('amqp://localhost')

  channel = await connection.createChannel()

  await channel.assertQueue(notificationQueue)

  channel.consume(notificationQueue, async (msg) => {
    const { booking, isSuccess } = JSON.parse(msg?.content.toString() || '{}')

    /**
     * Step 4: Send notification to user
     */
    if (isSuccess) {
      console.log(`Booking SUCCESS sent to user ${booking.userId}`)
    } else {
      console.log(`Booking FAIL send to user ${booking.userId}`)
    }

    // Acknowledge the message
    channel.ack(msg as amqp.Message)
  })
}

processSendNotification()

NotificationService lắng nghe các message trên notification_queue. Khi nó nhận được một message, nó sẽ gửi một thông báo (có thể là email, số điện thoại,...) tuỳ theo yêu cầu kỹ thuật.

 

Như vậy, bạn đã triển khai thành công các microservices với mô hình Choreography-Based Saga. Mỗi service sẽ thực hiện nhiệm vụ của mình và giao tiếp với các service khác thông qua event.

Comments

Bài viết nổi bật

Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
PHP là ngôn ngữ được sử dụng rộng rãi nhất trên thế giới trong lập trình web. Nó cũng bị ghét nhất. Nhưng tại sao nhiều developer lại ghét nó đến vậy? Hôm nay chúng ta hãy cùng tìm hiểu lý do xem chúng có thuyết phục không nhé ^_^
Lúc trước mình hay sử dụng cách này trên laptop phụ của mình, giờ mua license luôn rồi. Hôm nay mình xin chia sẻ cho bạn nào cần nhé.
JWT Tokens là một cách thức lưu trữ thông tin xác thực hiệu quả, nhưng làm cách nào để chúng ta có thể giúp chúng an toàn hơn? Có 2 cách thường dùng để lưu trữ JWT Tokens là LocalStorage và Cookies. Bây giờ chúng ta sẽ bắt đầu "mổ xẻ" các ưu - nhược điểm của mỗi loại nhé.
Có khá nhiều bạn đã yêu cầu mình một bài viết về Repository Design Pattern. Vậy mục đích của nó là gì? Nó có thực sự cần thiết cho ứng dụng của bạn hay không? Những điểm mạnh, điểm yếu của nó là gì? Chúng ta cùng đi sâu tìm hiểu qua bài viết này nhé.

Mục lục

Related posts

Mô hình Saga đưa ra một giải pháp có cấu trúc để giải quyết thách thức này. Nó cung cấp một phương pháp có hệ thống để quản lý transaction qua nhiều microservices. Điều này giải quyết những phức tạp của các transaction phân tán và hoàn toàn tương thích với các nguyên tắc của kiến trúc microservices, được đặc trưng bởi sự kết nối lỏng lẻo và khả năng triển khai độc lập của các service.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Phân trang - một thành phần không thể thiếu trong các ứng dụng có lượng dữ liệu lớn. Tuy nhiên, bạn hiểu được bao nhiêu về nó?
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Dạo này mình làm việc với mấy bạn trên github, thấy hay xài mấy từ viết tắt mà mình không hiểu lắm. Thôi thì tổng hợp lại một list các từ viết tắt hay dùng trong github luôn cho ai cần :D
Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
Cách bỏ qua câu lệnh --set-upstream quen thuộc cho các con lười
Mình sẽ giới thiệu 2 cách để xóa một property trong Javascript Object. Một cách sử dụng mutable - toán tử delete, một cách còn lại là immutable - tính năng Object Restructuring.
Đây là một khái niệm rất quan trọng trong Functional Programming. Ở đây mình sẽ cho ví dụ dựa trên Javascript, cụ thể là TypeScript, do đó mình hi vọng các bạn đã có một số kiến thức nhất định về JS trước. Điều này sẽ giúp bạn nắm bắt nội dung bài viết dễ dàng hơn.

Tin mới nhất

Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Mô hình Saga đưa ra một giải pháp có cấu trúc để giải quyết thách thức này. Nó cung cấp một phương pháp có hệ thống để quản lý transaction qua nhiều microservices. Điều này giải quyết những phức tạp của các transaction phân tán và hoàn toàn tương thích với các nguyên tắc của kiến trúc microservices, được đặc trưng bởi sự kết nối lỏng lẻo và khả năng triển khai độc lập của các service.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Trong phần này, chúng ta sẽ tìm hiểu một số khái niệm cơ bản nhất về AWS là gì và một số lợi ích khi sử dụng AWS.
Trở thành một software developer hiệu suất cao không phải là điều dễ dàng. Điều này đòi hỏi bạn phải có kỹ năng và kiến thức về lập trình, cũng như cách tiếp cận và giải quyết các vấn đề phức tạp. Tuy nhiên, nếu bạn có chút kiên nhẫn và sự nỗ lực, bạn hoàn toàn có thể trở thành một developer tài năng và thành công.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Thông tin được định nghĩa dưới dạng dữ liệu, kiến thức về thông tin, và trí tuệ về tri thức.
Phân trang - một thành phần không thể thiếu trong các ứng dụng có lượng dữ liệu lớn. Tuy nhiên, bạn hiểu được bao nhiêu về nó?
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Dạo này mình làm việc với mấy bạn trên github, thấy hay xài mấy từ viết tắt mà mình không hiểu lắm. Thôi thì tổng hợp lại một list các từ viết tắt hay dùng trong github luôn cho ai cần :D
Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.