สื่อสารระหว่าง Microservice ด้วย RabbitMQ + Spring AMQP

Phayao Boonon
6 min readSep 16, 2018

--

การสื่อสารระหว่าง Service ของ Microservice นั้นมีได้หลายวิธี มีทั้ง Synchronous ที่ sender ต้องรอให้ receiver ดำเนินการเรียบร้อยและตอบมาถึงจะสิ้นสุดการสื่อสาร หรือ Asynchronous ที่ไม่ต้องรอให้ receiver ดำเนิการเรียบร้อยเพียงแต่ตอบกลับมาว่าได้รับ message แล้ว หลังจากนั้น receiver ค่อยดำเนินการต่อไปจนเสร็จสิ้น เป็นสถาปัตยกรรม Event-Driven

Message Queue ก็เป็นการสื่อสารแบบ Asynchronous แบบหนึ่งที่นิยมแพร่หลายเป็นอย่างมาก ซึ่งก็มีหลากหลายเจ้าที่เสนอระบบนี้ และ RabbitMQ ก็เป็นตัวหนึ่งที่มีคนใช้มากและง่าย ดังนั้นในบทความนี้จะนำเสนอการใช้งาน RabbitMQ ร่วมกับ Spring AMQP ในการสือสารของ Microservice ที่สร้างด้วย Spring Boot

RabbitMQ คืออะไร

เป็นซอฟต์แวร์ message broker ตัวหนึ่งที่ open source โดยเริ่มแรกสร้างด้วยโปรโตคอล AMQP (Advanced Message Queuing Protocol) แต่หลังๆ ได้ขยายไปรองรับ STOMP, MQTT และ โปรโตคอลอื่นๆ อีกด้วย

ซึ่ง message broker จะทำหน้าที่ รับ (accept) และ ส่งต่อ (forward) message เปรียบได้กับไปรษณีย์ เมื่อเราส่งจดหมายที่ต้องการไปที่ตู้ไปรษณีย์ ก็จะมีเจ้าหน้าที่ไปรษณีย์มาเก็บจดหมายของเราแล้วส่งไปยังปลายทางตามที่เราจ่าหน้าซองไว้ ซึ่ง RabbitMQ เป็นทั้งกล่องไปรษณีย์ ที่ทำการไปรษณีย์ และเจ้าหน้าที่ไปรษณีย์ เรียกได้ได้ว่าเป็นระบบไปรษณีย์เลยก็ว่าได้ แต่จะส่งข้อมูล (data — message) แทนที่จะเป็นจดหมายกระดาษ และใช้คำศัพย์ที่แตกต่างไป ดังนี้

Producing — เป็นการส่ง (sending) message และผู้ส่ง message เรียกว่า Producer

Queue — เป็นกล่องไปรษณีย์ซึ่งอยู่ภายใน RabbitMQ แม้ว่า message จะว่าผ่าน RabbitMQ แต่จะถูกเก็บไว้ใน Queue โดยที่ Queue จะผูกกับ host memory และ disk limit ซึ่งจำเป็นต้องมี buffer ขนาดใหญ่เพียงพอ ซึ่งหลายๆ producer สามารถส่ง message มาที่เพียงหนึ่ง Queue ได้และหลายๆ consumer สามารถรอรับข้อมูลจาก Queue ได้

Consuming — เป็นการรับ (receiving) message และผู้ที่รอรับ message เรียกว่า Consumer

แบ่งรูปแบบของการ Exchange ออกเป็น 3 รูปแบบ

  • Direct exchange —จะส่ง message ไปยัง Queue โดยตรงด้วย message routing key
  • Fanout exchange — จะส่ง message กระจายไปทุก Queue ที่เชื่อมโยงอยู่กัน exchange
  • Topic exchange — จะส่ง message ไปยัง Queue โดยขึ้นอยู่กับ matching ระหว่าง message routing key และ pattern ที่เชื่อมโยง Queue กับ Exchange
https://www.cloudamqp.com/blog/2015-05-18-part1-rabbitmq-for-beginners-what-is-rabbitmq.html

Spring AMQP

เป็น project ที่ประยุกต์แนวคิดหลักของ Spring เพื่อพัฒนาระบบส่ง message ด้วยโปรโตคอล AMQP โดยมี “template” เป็น high-level abstraction สำหรับส่งและรับ message รองรับ POJO (Pain old Java Object) ด้วย “listener container” โดยรองรับ dependency injection และ declarative configuration ซึ่งจะมี ​2 ส่วนคือ spring-amqp ที่เป็น AMQP-base abstraction และ spring-rabbit ที่เป็น RabbitMQ implementation โดยใช้ spring-boot-starter-amqp dependency สำหรับ Spring Boot

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

Use Case

ในบทความนี้จะสร้างการสื่อสารระหว่าง Microservice ดังนั้นจะสร้าง 2 Service โดยที่ User จะ POST Customer data มาที่ Customer Service จะบันทึกข้อมูลของ Customer ใน H2 database แล้วก็ส่งข้อมูล Customer มายัง Email Service เพื่อทำการส่ง email ให้ Customer

Customer Service จะเป็นฝั่ง Sender (Producer) และ Email Service จะเป็นฝั่ง Receiver (Consumer) และส่งข้อมูลผ่าน RabbitMQ โดยสร้าง Queue และ Exchange Topic customer.topic ด้วย routing key email.customer

ติดตั้ง RabbitMQ

เราสามารถติดตั้ง RabbitMQ ในเครื่องของเราด้วย ใน MacOS ด้วยคำสั่ง

$ brew update
$ brew install rabbitmq

และรัน RabbitMQ server ด้วยคำสั่ง

$ rabbitmq-server

หรือว่าจะรันจาก Docker ก็ได้ ด้วยคำสั่ง

$ docker run -d --rm \
-p 15672:15672 \
-p 5672:5672 \
-v rabbit-data:/data \
--hostname rabbit-service \
--name awesome-rabbit \
rabbitmq:3-management

และตรวจสอบ Docker log ของ RabbitMQ ด้วยคำสั่ง docker logs

$ docker logs awesome-rabbit
.....
Starting RabbitMQ 3.7.7 on Erlang 20.3.8.5
Copyright (C) 2007-2018 Pivotal Software, Inc.
Licensed under the MPL. See http://www.rabbitmq.com/
## ##
## ## RabbitMQ 3.7.7. Copyright (C) 2007-2018 Pivotal Software, Inc.
########## Licensed under the MPL. See http://www.rabbitmq.com/
###### ##
########## Logs: <stdout>
Starting broker...
2018-09-16 00:35:01.251 [info] <0.197.0>
node : rabbit@rabbit-service
home dir : /var/lib/rabbitmq
config file(s) : /etc/rabbitmq/rabbitmq.conf
cookie hash : 1wAGd4fKTt0iWB5FU2H4pQ==
log(s) : <stdout>
database dir : /var/lib/rabbitmq/mnesia/rabbit@rabbit-service
....

Sender Service (Producer)

สร้าง Customer Service ที่เป็น RESTful Service ด้วย Spring Initializr โดยเลือก dependency เป็น Web Lombok JPA H2 และ RabbitMQ เพื่อที่จะเป็น CRUD Service เก็บข้อมูลใน H2 local database

สร้าง Customer Data/Entity

Entity เป็นตัวแทนของข้อมูลของ Service ดังนั้นสร้าง Entity สำหรับ Customer โดยใช้ @Data annotation ที่จะสร้าง Getter/Setter ให้เราเอง และจะต้อง implement interface Serializable เพราะว่าเราจะส่งข้อมูลผ่าน RabbitMQ ต้องการ Object ที่สามารถ Serialize ได้จากฝั่ง Sender และ Deserialize ในฝั่ง Receiver

สร้าง Customer Controller

บทความนี้จะสร้าง Microservice อย่างง่ายดังนั้น สร้าง endpoint /customers เดียวโดย mapping กับ POST method รับ input เป็น Customer แล้วเรียก createService ของ CustomerService ใน Service layer ที่จะส่ง message ไปหา RabbitMQ

สร้าง Customer Service

ใน Service layer สร้าง Customer Service เพื่อ insert Customer data ใน H2 โดยเรียก save method ใน Customer Repository และส่งข้อมูล Customer เป็น message ไปยัง RabbitMQ ผ่านทาง sendEmail method ของ MessageSender

สร้าง Customer Repository

โดยเป็น interface ที่ extends หรือขยายจาก CrudRepository เพื่อดำเนินการเกี่ยวกับ Database ซึ่งในที่นี้เป็น H2 database

สร้าง Sender Config

ในฝั่งของ Sender สร้าง Message configuration ที่กำหนดชื่อของ Topic Exchange และ Sender instance ที่เป็นคลาส MessageSender โดยสร้างเป็น Bean ขึ้นมาเพื่อจะ Inject ในภายหลังของ Spring AMQP

สร้าง Message Sender

เป็นคลาสที่เป็นตัวส่ง message ไปหา RabbitMQ ด้วย RabbitTemplate ซึ่งจะส่งเป็น ชื่อ topic, key email.customer และ Customer data โดยอาศัยโปรโตคอล AMQP

ตั้งค่า Application Config

เราตั้งค่าให้ RabbitMQ host เป็น localhost และ port 5672 ซึ่งเป็น default port ของ RabbitMQ

Receiver Service (Consumer)

สร้าง Email Service ด้วย Spring Initializr โดยเลือก dependency เป็น Lombok RabbitMQ และ Mail เพื่อที่จะเป็น Mail Service ที่จะรับ message และส่ง email หา Customer

สร้าง Customer Data

การที่จะรับข้อมูลจาก RabbitMQ ดังนั้นเราจะต้องมี data structure เดียวกันเพื่อการ Deserialize ข้อมูลกลับมาเป็น Object โดยการใช้ @Data annotation ที่จะสร้าง Getter/Setter ให้เรา และ implement interface Serialiable

สร้าง Message Config

ในฝั่งของ Receiver สร้าง Message configuration กำหนดชื่อของ Topic Exchange customer.topic ให้ตรงกับฝั่ง Sender เพื่อที่จะรับข้อมูลได้ถูกต้อง และ Receiver instance ที่เป็นคลาสMessageReceiver และ Subscribe Queue ด้วยการ binding ระหว่าง Topic Exchange และ Queue ด้วย routing key email.customer โดยสร้างเป็น Bean ขึ้นมาเพื่อ Inject ในภายหลัง

สร้าง Message Receiver

เป็นคลาสที่เป็นตัวรับ (consuming) message จาก RabbitMQ โดยอาศัยโปรโตคอล AMQP โดยที่ใช้ @RabbitListener annotation คอยตรวจสอบ autoDeletedQueue ว่ามี message เข้ามาหรือไม่ เมื่อได้รับ message แล้วจะส่ง email ไปหา Customer ด้วย sendMessageToCustomer ของ Email Service

สร้าง Email Service

เพื่อที่จะส่ง email ไปหา Customer ด้วย email ที่มากับ Customer data ด้วยการ EmailService interface โดยใช้ JavaMailSender

ตั้งค่า Application Config

กำหนดให้ server port แตกต่างจาก Customer Service เนื่องจากรันบนเครื่องเดียวกันและ กำหนดให้ส่งแบบ SMTP ของ Gmail ผ่าน port 587

ทดสอบ RabbitMQ

รันทั้ง Customer Service ในฝั่ง Sender และ Email Service ในฝั่ง Receiver สามารถตรวจสอบ connection ได้ด้วย Management Plugin localhost:15672 ใน Tab Queues จะมี Queue ที่สร้างขึ้น

ใน Tab Exchanges จะมี customer.topic เป็น topic สำหรับ exchange message

ใน Tab Connection จะมีการเชื่อมต่อจาก Receiver ที่ Listening message อยู่

ใช้ Postman ส่ง Customer data ด้วย POST method ไปที่ Customer Service ถ้าสำเร็จจะได้ status code 201 (Created)

เมื่อตรวจสอบหน้า Management ใน Tab Overview จะสังเกตเห็นมี message ส่งมาที่ RabbitMQ

และ Tab Connections จะมีการเชื่อมต่อจากฝั่ง Sender

สรุป

จากที่เราได้ทดลองสื่อสารระหว่าง Microservice ด้วย RabbitMQ กับ Spring AMQP นั้น ซึ่งเป็นการสื่อสารแบบ Asynchronous ที่ไม่ต้องรอให้ Receiver ทำงานเสร็จก่อนเพียงแต่ตอบกลับมาว่าได้รับ message แล้ว ในตัวอย่างของบทความนี้ แสดงให้เห็นว่าสามารถสื่อสารได้ด้วย Spring Boot ไม่อยาก โดยที่ User ส่งข้อมูลไปที่ Customer Service หลังจากที่ได้รับข้อมูลก็บันทึกข้อมูลใน Database และส่งไปที่ RabbitMQ โดยระบุ Queue และ Exchange Topic ด้วย Routing Key โดยที่ Email Service ได้ Subscribe ไว้กับ RabbitMQ ด้วยการ Listening ว่ามี message เข้ามาใน Queue แล้วหรือยัง ถ้ามีจะรับ (consuming) message ไปดำเนินการส่ง email หา Customer แต่ก็สามารถมีหลาย Sender/Receiver โดยแยก Exchange Topic หรือ Routing key เพื่อจะส่งไปที่ Receiver ที่แตกต่างกันได้

อ้างอิง

--

--

Phayao Boonon
Phayao Boonon

Written by Phayao Boonon

Software Engineer 👨🏻‍💻 Stay Hungry Stay Foolish

Responses (1)