ส่งข้อมูลระหว่างแอพพลิเคชั่นผ่าน RabbitMQ

❓ โจทย์วันนี้จะเป็นการศึกษาหาวิธีส่งข้อมูลระหว่าง Ruby Application โดยลองนึกภาพว่าแอพตัวที่ 1ถูกสร้างมาเพื่อบันทึกข้อมูลต่างๆ เก็บไว้ และต้องการส่งข้อมูลที่บันทึกไว้ไปยังแอพตัวที่ 2 เพื่อจุดประสงค์ต่างๆ เช่น การประมวลผล (Process) หรือบันทึกเหตุการณ์ (Log)

💡 สำหรับวิธีแก้ไขโจทย์นี้เราจะใช้ RabbitMQ เป็นตัวกลางในการสื่อสารระหว่างแอพพลิเคชัน ซึ่งมีภาพการเชื่อมต่อดังแสดงในภาพด้านล่าง

architecture design แผนภาพการเชื่อมต่อระหว่าง Application กับ RabbitMQ

🌱 รูปแบบการพัฒนาเป็นดังนี้

  1. แอพตัวที่ 1 (Rails Application)
    • โมเดล Post สำหรับเก็บข้อมูล โดยมีการดักจับเหตุการณ์ after_create เป็น trigger ในการส่งข้อมูล
    • สร้างคลาส Publisher เพื่อทำหน้าที่ในการส่งข้อมูลไปยัง RabbitMQ โดยใช้ไลบราลี่ bunny และกำหนด channel เป็น kz.message
    • เรียกใช้ Active Job ในการส่งข้อมูลผ่าน background process เพื่อให้โปรแกรมทำงานได้ราบรื่นยิ่งขึ้น
  2. แอพตัวที่ 2 (Ruby Application)
    • สร้างเป็น Commandline Application ที่รับคำสั่ง exit เพื่อปิดโปรแกรมเพียงอย่างเดียว
    • สร้างคลาส Subscriber เพื่อรอรับข้อมูลจาก RabbitMQ โดยใช้ไลบราลี bunny เช่นเดียวกับแอพตัวที่ 1 และกำหนด channel เป็น kz.message
    • เมื่อรับข้อมูลเข้าจาก RabbitMQ ก็จะทำการบันเหตุการณ์ลงในไฟล์ชื่อ my-post.log

Rails Application บันทึกโพสผ่าน Rails Application และส่งข้อมูลไปยัง RabbitMQ

Log Ruby Application รับข้อมูลจาก RabbitMQ และบันทึกเหตุการณ์ลงไฟล์

โค้ดตัวอย่างของ Publisher

require 'bunny'

class Publisher
  class << self
    def publish(chnnl, message = {})
      puts "Publish message to #{chnnl} with #{message.to_json}"
      stream = channel.queue("kw.#{chnnl}")
      stream.publish(message.to_json)
    end

    def channel
      @channel ||= connection.create_channel
    end

    def connection
      @connection ||= Bunny.new.tap(&:start)
    end
  end
end

โค้ดตัวอย่างของ Subscriber

require 'bunny'
require 'json'
require 'logger'

@logger = Logger.new('my-post.log')

class Subscriber
  class << self
    def subscribe(queue, &block)
      q = channel.queue(queue)
      q.subscribe(manual_ack: false) do |delivery_info, properties, payload|
        puts "Received #{payload}, message properties are #{properties.inspect}"
        data = JSON.parse(payload)
        block.call(data) if block
      end
    end

    def channel
      @channel ||= connection.create_channel
    end

    def connection
      @connection ||= Bunny.new.tap(&:start)
    end
  end
end

# Start subscribe with channel name
Subscriber.subscribe("kz.message") do |data|
  @logger.info("ขณะนี้มีการสร้างโพสชื่อ #{data['title']} โดย #{data['author']} เมื่อเวลา #{data['created_at']}")
end

puts("Type exit to quit")
loop do
  cmd = gets.chomp
  exit(0) if (cmd == 'exit')
end

Referecnes