Event-driven architecture: মাইক্রোসার্ভিসের জগতে ইভেন্ট-চালিত সমাধান
কল্পনা করুন, আপনার কাছে একটি ই-কমার্স অ্যাপ্লিকেশন আছে। একজন ইউজার অর্ডার করলেন, কিন্তু পেমেন্ট, ইনভেন্টরি আপডেট, এবং নোটিফিকেশন পাঠানোর মতো কাজগুলো একসাথে করতে গিয়ে পুরো সিস্টেম ধীর হয়ে গেল। কখনও কখনও একটি সার্ভিস ডাউন থাকলে পুরো অর্ডার প্রক্রিয়াই ব্যর্থ হচ্ছে। এই সমস্যার সমাধান নিয়ে আসে Event-driven architecture (EDA)। এই টিউটোরিয়ালে আমরা শিখব কীভাবে EDA ব্যবহার করে মাইক্রোসার্ভিসগুলোকে আলাদা, স্বাধীন, এবং অ্যাসিনক্রোনাস করা যায়, যাতে সিস্টেম আরও স্থিতিস্থাপক ও স্কেলেবল হয়। আমরা জাভা ও স্প্রিং বুটের ব্যবহারিক উদাহরণ দিয়ে ধাপে ধাপে একটি ইভেন্ট-চালিত মাইক্রোসার্ভিস তৈরি করব।
ভূমিকা: ইভেন্ট-চালিত আর্কিটেকচার কী এবং কেন?
ইভেন্ট-চালিত আর্কিটেকচার হলো একটি সফটওয়্যার ডিজাইন প্যাটার্ন যেখানে সার্ভিসগুলো একে অপরের সাথে সরাসরি কল করার পরিবর্তে "ইভেন্ট" নামক বার্তার মাধ্যমে যোগাযোগ করে। ধরা যাক, একটি সার্ভিস (প্রযোজক বা পাবলিশার) কোনো কিছু ঘটলে (যেমন: অর্ডার প্লেসড) তা একটি ইভেন্ট আকারে জানায়। অন্য সার্ভিসগুলো (ভোক্তা বা কনজিউমার) সেই ইভেন্টের জন্য অপেক্ষা করে এবং প্রয়োজনীয় কাজ সম্পাদন করে। এটি ঐতিহ্যবাহী রিকোয়েস্ট-রেসপন্স মডেলের থেকে সম্পূর্ণ আলাদা, যেখানে সার্ভিসগুলো সিঙ্ক্রোনাসভাবে একে অপরের উপর নির্ভরশীল।
এই আর্কিটেকচারের মূল সুবিধা হলো ডিকপলিং (Decoupling)। প্রতিটি মাইক্রোসার্ভিস স্বাধীনভাবে কাজ করতে পারে, নিজের গতিতে ডেটা প্রক্রিয়া করতে পারে এবং কোনো একটি সার্ভিস ডাউন হলেও পুরো সিস্টেম ভেঙে পড়ে না। উদাহরণস্বরূপ, অর্ডার সার্ভিস শুধু ইভেন্ট পাবলিশ করে দেয়; পেমেন্ট, ইনভেন্টরি বা নোটিফিকেশন সার্ভিস পরে সেই ইভেন্ট প্রক্রিয়া করে। ফলে স্কেলেবিলিটি, ফল্ট টলারেন্স এবং এক্সটেন্সিবিলিটি ব্যাপকভাবে বেড়ে যায়। EDA সাধারণত মেসেজ ব্রোকার (যেমন: Apache Kafka, RabbitMQ) বা ইভেন্ট বাস (Spring Cloud Bus) ব্যবহার করে বাস্তবায়িত হয়।
পূর্বশর্ত: কী কী জানা প্রয়োজন?
এই টিউটোরিয়ালের ধাপগুলো অনুসরণ করতে আপনার নিম্নলিখিত বিষয়গুলোর সাথে পরিচিত থাকা জরুরি:
- জাভা প্রোগ্রামিং ভাষা (Java 8+) এবং মৌলিক অবজেক্ট ওরিয়েন্টেড কনসেপ্ট।
- Spring Boot ফ্রেমওয়ার্কের বেসিক ধারণা, যেমন
@RestController,@Service, এবংApplicationContext। - Apache Maven বা Gradle বিল্ড টুলের সাথে কাজ করার অভিজ্ঞতা।
- মেসেজ ব্রোকার (কমপক্ষে তাত্ত্বিক ধারণা) যেমন RabbitMQ বা Apache Kafka। এই টিউটোরিয়ালে আমরা ধরে নেব আপনি RabbitMQ ব্যবহার করছেন।
- একটি ডেভেলপমেন্ট এনভায়রনমেন্ট (IDE: IntelliJ IDEA বা Eclipse) এবং RabbitMQ লোকালি ইনস্টল করা।
আপনি যদি মেসেজ ব্রোকার ব্যবহারে নতুন হন, তবে চিন্তার কিছু নেই। আমরা কোডের মাধ্যমে উদাহরণ দিয়ে ব্যাখ্যা করব।
ধাপে ধাপে বাস্তবায়ন: একটি স্প্রিং বুট ইভেন্ট-ড্রিভেন মাইক্রোসার্ভিস
ধরা যাক, আমরা একটি ছোট ই-কমার্স সিস্টেম বানাবো যেখানে একটি অর্ডার সার্ভিস এবং একটি পেমেন্ট সার্ভিস থাকবে। যখনই একটি নতুন অর্ডার তৈরি হবে, অর্ডার সার্ভিস একটি ইভেন্ট পাবলিশ করবে এবং পেমেন্ট সার্ভিস সেই ইভেন্ট শুনে পেমেন্ট প্রক্রিয়া করবে।
ধাপ ১: প্রকল্প সেটআপ ও নির্ভরতা (Dependencies)
প্রথমে আপনার pom.xml ফাইলে স্প্রিং বুট এবং RabbitMQ-এর জন্য প্রয়োজনীয় নির্ভরতা যোগ করুন:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
Spring Boot AMQP স্টার্টার RabbitMQ-এর সাথে কাজ করার জন্য প্রয়োজনীয় ক্লাস এবং ডিফল্ট কনফিগারেশন সরবরাহ করে। এটি RabbitTemplate এবং MessageListenerContainer-এর মতো মূল উপাদানগুলো স্বয়ংক্রিয়ভাবে কনফিগার করে।
ধাপ ২: ইভেন্ট ক্লাস তৈরি
একটি সিরিয়ালাইজেবল জাভা ক্লাস তৈরি করুন যা ইভেন্টের ডেটা ধারণ করবে:
import java.io.Serializable;
public class OrderPlacedEvent implements Serializable {
private String orderId;
private String userId;
private double amount;
public OrderPlacedEvent() {}
public OrderPlacedEvent(String orderId, String userId, double amount) {
this.orderId = orderId;
this.userId = userId;
this.amount = amount;
}
// Getters and setters...
}
OrderPlacedEvent ক্লাসটি একটি সাধারণ POJO (Plain Old Java Object)। এটি Serializable ইন্টারফেস ইমপ্লিমেন্ট করে কারণ RabbitMQ-তে বার্তা পাঠানোর সময় জাভা অবজেক্টগুলিকে বাইট অ্যারেতে রূপান্তর করতে হয়। গেটার ও সেটার মেথডগুলো জ্যাকসন বা অন্যান্য লাইব্রেরি দ্বারা অবজেক্ট সিরিয়ালাইজেশনের জন্য প্রয়োজনীয়।
ধাপ ৩: ইভেন্ট পাবলিশার (অর্ডার সার্ভিস)
অর্ডার সার্ভিসে ইভেন্ট পাবলিশ করার জন্য নিচের মতো একটি ক্লাস তৈরি করুন:
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderEventPublisher {
@Autowired
private RabbitTemplate rabbitTemplate;
public void publishOrderPlacedEvent(OrderPlacedEvent event) {
rabbitTemplate.convertAndSend("order.exchange", "order.placed", event);
System.out.println("Event published: " + event.getOrderId());
}
}
RabbitTemplate স্প্রিং AMQP-এর কেন্দ্রীয় ক্লাস, যা মেসেজ ব্রোকারে বার্তা পাঠানোর সহজ API সরবরাহ করে। convertAndSend মেথডটি নির্দিষ্ট এক্সচেঞ্জে (order.exchange) এবং রাউটিং কী (order.placed) সহ অবজেক্টটি পাঠায়। স্প্রিং স্বয়ংক্রিয়ভাবে অবজেক্টটিকে বার্তায় রূপান্তর করে। এটি প্রযোজক সার্ভিস থেকে ভোক্তা সার্ভিসে সম্পূর্ণ ডিকপলড যোগাযোগ নিশ্চিত করে।
ধাপ ৪: ইভেন্ট কনজিউমার (পেমেন্ট সার্ভিস)
পেমেন্ট সার্ভিসে একটি @RabbitListener অ্যানোটেটেড মেথড তৈরি করুন যা ইভেন্ট প্রক্রিয়া করবে:
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
public class PaymentEventListener {
@RabbitListener(queues = "order.payment.queue")
public void handleOrderPlacedEvent(OrderPlacedEvent event) {
System.out.println("Processing payment for order: " + event.getOrderId());
// actual payment logic here...
}
}
@RabbitListener অ্যানোটেশনটি একটি মেথডকে র্যাবিটএমকিউ কিউতে আসা বার্তার লিসেনার হিসেবে চিহ্নিত করে। যখনই "order.payment.queue" কিউতে একটি বার্তা আসে, স্প্রিং স্বয়ংক্রিয়ভাবে এই মেথডটিকে কল করে এবং বার্তাটিকে OrderPlacedEvent অবজেক্টে ডিসিরিয়ালাইজ করে। এই মেথডের ভিতরে আপনি পেমেন্ট প্রক্রিয়াকরণ, ডেটাবেস আপডেট বা অন্য যেকোনো বিজনেস লজিক রাখতে পারেন। এটি ভোক্তা সার্ভিসকে প্রযোজকের থেকে সম্পূর্ণ স্বাধীন করে তোলে।
ধাপ ৫: কনফিগারেশন ফাইল
application.properties ফাইলে RabbitMQ সার্ভার ও এক্সচেঞ্জ/কিউ কনফিগার করুন:
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
order.exchange.name=order.exchange
order.queue.name=order.payment.queue
order.routing.key=order.placed
এছাড়াও, একটি RabbitMQConfig ক্লাস তৈরি করে এক্সচেঞ্জ, কিউ এবং বাইন্ডিং ডিক্লেয়ার করতে পারেন।
সম্পূর্ণ উদাহরণ: অর্ডার ও পেমেন্ট মাইক্রোসার্ভিস
নিচে একটি সম্পূর্ণ স্প্রিং বুট অ্যাপ্লিকেশন দেওয়া হলো যা একটি REST API-র মাধ্যমে অর্ডার গ্রহণ করে এবং স্বয়ংক্রিয়ভাবে পেমেন্ট ইভেন্ট ট্রিগার করে:
// OrderController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
private OrderEventPublisher publisher;
@PostMapping("/orders")
public String placeOrder(@RequestBody OrderPlacedEvent orderEvent) {
publisher.publishOrderPlacedEvent(orderEvent);
return "Order placed: " + orderEvent.getOrderId();
}
}
OrderController একটি REST এন্ডপয়েন্ট এক্সপোজ করে যা POST রিকোয়েস্টের মাধ্যমে নতুন অর্ডার গ্রহণ করে। এটি OrderEventPublisher ব্যবহার করে ইভেন্ট পাবলিশ করে এবং অবিলম্বে ক্লায়েন্টকে একটি সাফল্যের বার্তা ফেরত দেয়। অর্ডার সার্ভিস পেমেন্ট প্রক্রিয়ার জন্য অপেক্ষা না করায় এটি অ্যাসিনক্রোনাস এবং দ্রুততর। পেমেন্ট প্রক্রিয়াকরণ পটভূমিতে (ব্যাকগ্রাউন্ড) সম্পন্ন হয়।
সাধারণ সমস্যা ও সমাধান
- ইভেন্টের অর্ডার লঙ্ঘন (Event ordering): কখনও কখনও ইভেন্টগুলি সঠিক ক্রমে প্রক্রিয়া না হওয়ার কারণে ডেটা অসঙ্গতি দেখা দেয়। সমাধান হিসেবে আপনি ইভেন্টে একটি টাইমস্ট্যাম্প বা সিকোয়েন্স নম্বর যুক্ত করতে পারেন, অথবা Kafka-র মতো পার্টিশন-ভিত্তিক সিস্টেম ব্যবহার করতে পারেন যা একই পার্টিশনের মধ্যে অর্ডার গ্যারান্টি দেয়।
- ইভেন্ট ডুপ্লিকেশন (Duplicate events): নেটওয়ার্ক সমস্যার কারণে একই ইভেন্ট একাধিকবার আসতে পারে। সমাধান হলো ইডেম্পোটেন্ট কনজিউমার তৈরি করা - অর্থাৎ, কনজিউমার লজিকটি এমনভাবে ডিজাইন করা উচিত যাতে একই ইভেন্ট বারবার প্রক্রিয়া করলেও একই ফলাফল পাওয়া যায়। একটি ইউনিক ইভেন্ট আইডি ব্যবহার করে ডাটাবেসে ডি-ডুপ্লিকেশন করা যায়।
- ইভেন্ট হারিয়ে যাওয়া (Message loss): কোনো কারণে যদি কনজিউমার ক্র্যাশ করে, তাহলে ইভেন্ট হারিয়ে যেতে পারে। সমাধান হলো ম্যানুয়াল অ্যাকনলেজমেন্ট এবং ডেড লেটার কিউ (DLQ) ব্যবহার করা। RabbitMQ-তে আপনি ম্যানুয়ালি
basicAckকল করে নিশ্চিত করতে পারেন যে বার্তাটি সফলভাবে প্রক্রিয়া হয়েছে; অন্যথায় বার্তাটি DLQ-তে চলে যায়।
সারসংক্ষেপ
ইভেন্ট-চালিত আর্কিটেকচার মাইক্রোসার্ভিসের জটিলতা কমিয়ে একটি শক্তিশালী, স্কেলেবল এবং রক্ষণাবেক্ষণযোগ্য সিস্টেম গড়ে তুলতে সাহায্য করে। এই টিউটোরিয়ালে আমরা শিখেছি কীভাবে স্প্রিং বুট ও RabbitMQ ব্যবহার করে অর্ডার ও পেমেন্ট সার্ভিসের মধ্যে ইভেন্ট পাবলিশ ও কনজিউম করা যায়। ইভেন্টগুলি সার্ভিসগুলোর মধ্যে ডিকপলিং নিশ্চিত করে, ফলে নতুন ফিচার যোগ করা, স্কেল করা বা কোনো সার্ভিস রিপ্লেস করা অনেক সহজ হয়। এখন আপনি আপনার নিজস্ব প্রজেক্টে EDA ব্যবহার করে দেখতে পারেন, এবং ধীরে ধীরে আরও জটিল প্যাটার্ন যেমন সাগা (Saga) বা ইভেন্ট সোর্সিং শিখতে পারেন।