আপনি যদি মাইক্রোসার্ভিসেস আর্কিটেকচারে কাজ করেন, তাহলে নিশ্চয়ই একবার না একবার API কলের সময় ডুপ্লিকেট রিকোয়েস্ট হ্যান্ডলিং এর সমস্যায় পড়েছেন। নেটওয়ার্ক ডিসকানেকশন, টাইমআউট, অথবা ক্লায়েন্টের রিট্রাই মেকানিজমের কারণে একই অপারেশন একাধিকবার এক্সিকিউট হতে পারে, যা ডেটা করাপশন বা অপ্রত্যাশিত ফলাফল ডেকে আনতে পারে। এই টিউটোরিয়ালে আমরা Idempotency (অভিন্নতা) ধারণাটি বিস্তারিতভাবে আলোচনা করবো, যার মাধ্যমে আপনি নিশ্চিত করতে পারবেন যে একটি অপারেশন যতবারই কল করা হোক না কেন, তার ফলাফল একই থাকবে। আমরা Java এবং Spring Boot ব্যবহার করে একটি প্র্যাকটিক্যাল ইমপ্লিমেন্টেশন দেখবো, যেখানে আপনি idempotency key, ডাটাবেজ লকিং, এবং proper error handling এর মতো কৌশল শিখতে পারবেন।
ভূমিকা (Background)
মাইক্রোসার্ভিসেস এনভায়রনমেন্টে, বিভিন্ন সার্ভিসের মধ্যে HTTP কলের মাধ্যমে কমিউনিকেশন হয়। এই কমিউনিকেশন প্রায়ই অস্থির নেটওয়ার্কের উপর নির্ভরশীল, যার কারণে রিকোয়েস্ট ড্রপ হওয়া বা ডুপ্লিকেট হওয়া খুবই সাধারণ ব্যাপার। উদাহরণস্বরূপ, একটি পেমেন্ট সার্ভিসে ইউজার পেমেন্ট কনফার্ম করলে, ক্লায়েন্ট সাইডে নেটওয়ার্ক ইস্যুর কারণে একই পেমেন্ট রিকোয়েস্ট একাধিকবার পাঠানো হতে পারে। যদি সার্ভিসটি idempotent না হয়, তাহলে একই পেমেন্ট একাধিকবার প্রসেস হয়ে যাবে, যা ইউজারের অ্যাকাউন্ট থেকে একাধিকবার টাকা কেটে নিতে পারে।
Idempotency এর মূল লক্ষ্য হলো অপারেশনটিকে এমনভাবে ডিজাইন করা যাতে একই ইনপুটের জন্য প্রথম কলের পরে পরবর্তী সব কলের কোনো প্রভাব না থাকে। অর্থাৎ, প্রথম কলটি সফলভাবে সম্পন্ন হলে, পরবর্তী কোনো ডুপ্লিকেট কলকে হয় ইগনোর করা হবে, অথবা প্রথম কলের ফলাফলই রিটার্ন করা হবে। এটি HTTP methods এর জন্যও প্রযোজ্য, যেমন GET, PUT, DELETE সাধারণত idempotent হয়, কিন্তু POST সাধারণত idempotent নয়।
একটি প্র্যাকটিক্যাল idempotency ইমপ্লিমেন্টেশনে সাধারণত Idempotency Key ব্যবহার করা হয়। ক্লায়েন্ট প্রতিটি রিকোয়েস্টের সাথে একটি ইউনিক আইডি (যেমন UUID) পাঠায়, এবং সার্ভার এই আইডিটি স্টোর করে রাখে। একই আইডি সহ দ্বিতীয় রিকোয়েস্ট আসলে, সার্ভার বুঝতে পারে এটি একটি ডুপ্লিকেট এবং প্রথম রিকোয়েস্টের ফলাফলই রিটার্ন করে।
পূর্বশর্ত (Prerequisites)
এই টিউটোরিয়ালটি অনুসরণ করার জন্য আপনার নিম্নলিখিত বিষয়গুলির উপর বেসিক জ্ঞান থাকা প্রয়োজন:
- Java 17+ এবং Spring Boot 3.x এর সাথে পরিচিতি
- REST API ডেভেলপমেন্টের অভিজ্ঞতা (যেমন
@RestController,@PostMappingইত্যাদি) - Maven বা Gradle বিল্ড টুল ব্যবহার করার জ্ঞান
- ডাটাবেজ (যেমন PostgreSQL বা H2) এবং JPA/Spring Data এর হ্যান্ডলিং সম্পর্কে ধারণা
- Postman বা cURL এর মতো টুল দিয়ে API টেস্ট করার ক্ষমতা
আপনার প্রজেক্টে নিচের ডিপেন্ডেন্সিগুলো যোগ করতে হবে:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
ধাপে ধাপে বাস্তবায়ন (Step-by-Step Implementation)
এখন আমরা একটি প্র্যাকটিক্যাল উদাহরণের মাধ্যমে idempotency ইমপ্লিমেন্ট করবো। ধরা যাক, আমাদের একটি পেমেন্ট প্রসেসিং সার্ভিস আছে, যেখানে ইউজার পেমেন্ট কনফার্ম করে। আমরা চাই, একই পেমেন্ট রিকোয়েস্ট একাধিকবার প্রসেস না হোক।
1. Idempotency Key স্টোরেজ তৈরি করা
প্রথমে আমরা একটি ডাটাবেজ টেবিল তৈরি করবো, যেখানে প্রতিটি idempotency key এবং তার সংশ্লিষ্ট রেসপন্স স্টোর হবে।
@Entity
@Table(name = "idempotency_records")
public class IdempotencyRecord {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(nullable = false, unique = true)
private String idempotencyKey;
@Column(nullable = false)
private String responseBody;
@Column(nullable = false)
private LocalDateTime createdAt;
// getters, setters, constructors
}
এই IdempotencyRecord এন্টিটি ক্লাসটি idempotency key এবং তার সাথে সম্পর্কিত রেসপন্স স্টোর করে। idempotencyKey কলামটি unique হিসেবে ডিফাইন করা হয়েছে, যাতে একই key দিয়ে একাধিক রেকর্ড তৈরি না হয়।
2. Idempotency Service তৈরি
এখন আমরা একটি সার্ভিস ক্লাস তৈরি করবো, যা idempotency logic হ্যান্ডল করবে।
@Service
public class IdempotencyService {
@Autowired
private IdempotencyRecordRepository repository;
public Optional<IdempotencyRecord> getExistingRecord(String key) {
return repository.findByIdempotencyKey(key);
}
public IdempotencyRecord saveRecord(String key, String response) {
IdempotencyRecord record = new IdempotencyRecord();
record.setIdempotencyKey(key);
record.setResponseBody(response);
record.setCreatedAt(LocalDateTime.now());
return repository.save(record);
}
}
এই সার্ভিসটি প্রথমে চেক করে যে idempotency key টি আগে থেকে আছে কিনা। যদি থাকে, তাহলে আগের রেসপন্স রিটার্ন করে। না থাকলে নতুন রেকর্ড তৈরি করে।
3. Controller এ Idempotency লজিক যোগ করা
এখন আমাদের পেমেন্ট কন্ট্রোলারে idempotency চেক যোগ করবো।
@RestController
@RequestMapping("/api/payments")
public class PaymentController {
@Autowired
private IdempotencyService idempotencyService;
@PostMapping("/confirm")
public ResponseEntity<?> confirmPayment(@RequestHeader("Idempotency-Key") String key,
@RequestBody PaymentRequest request) {
// Check if this is a duplicate request
Optional<IdempotencyRecord> existing = idempotencyService.getExistingRecord(key);
if (existing.isPresent()) {
// Return the previous response
return ResponseEntity.status(HttpStatus.OK)
.body(existing.get().getResponseBody());
}
// Process the payment (simulate)
String response = processPayment(request);
// Save the idempotency record
idempotencyService.saveRecord(key, response);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
private String processPayment(PaymentRequest request) {
// Simulate payment processing
return "Payment successful for " + request.getAmount();
}
}
এখানে আমরা @RequestHeader("Idempotency-Key") ব্যবহার করে ক্লায়েন্ট থেকে ইউনিক key গ্রহণ করছি। প্রথমবার রিকোয়েস্ট আসলে, আমরা পেমেন্ট প্রসেস করে রেসপন্স স্টোর করি। দ্বিতীয়বার একই key আসলে, আমরা স্টোর করা রেসপন্স রিটার্ন করি।
সম্পূর্ণ উদাহরণ (Complete Example)
নিচে একটি সম্পূর্ণ Spring Boot অ্যাপ্লিকেশন দেওয়া হলো যা আপনি সরাসরি রান করতে পারেন:
// Main Application Class
@SpringBootApplication
public class IdempotencyApplication {
public static void main(String[] args) {
SpringApplication.run(IdempotencyApplication.class, args);
}
}
// Repository
@Repository
public interface IdempotencyRecordRepository extends JpaRepository<IdempotencyRecord, UUID> {
Optional<IdempotencyRecord> findByIdempotencyKey(String key);
}
// PaymentRequest DTO
public class PaymentRequest {
private double amount;
private String currency;
// getters and setters
}
// Application.properties
spring.h2.console.enabled=true
server.port=8080
এই সম্পূর্ণ উদাহরণটি একটি H2 ইন-মেমোরি ডাটাবেজ ব্যবহার করে। আপনি Postman দিয়ে টেস্ট করতে পারেন:
- প্রথম রিকোয়েস্ট:
POST /api/payments/confirmহেডারেIdempotency-Key: 123e4567-e89b-12d3-a456-426614174000দিয়ে একটি পেমেন্ট রিকোয়েস্ট পাঠান। রেসপন্স আসবে201 Created। - ডুপ্লিকেট রিকোয়েস্ট: একই হেডার এবং বডি দিয়ে আবার রিকোয়েস্ট পাঠান। এবার রেসপন্স আসবে
200 OKএবং প্রথমবারের মতো একই রেসপন্স দেখাবে।
সাধারণ সমস্যা ও সমাধান (Common Issues)
Idempotency ইমপ্লিমেন্ট করার সময় কিছু সাধারণ সমস্যা দেখা দিতে পারে:
- Idempotency Key এর মেয়াদ শেষ হওয়া: যদি আপনি key টি চিরকালের জন্য স্টোর করেন, তাহলে ডাটাবেজ বড় হতে পারে। একটি ভালো সমাধান হলো key টির একটি TTL (Time to Live) সেট করা। উদাহরণস্বরূপ, ২৪ ঘন্টা পরে রেকর্ডটি ডিলিট করে দিন।
- কনকারেন্ট রিকোয়েস্ট: একই সময়ে একই key দিয়ে দুটি রিকোয়েস্ট আসলে, ডাটাবেজে রেস কন্ডিশন হতে পারে। এটি এড়ানোর জন্য ডাটাবেজ লেভেলে
UNIQUEকনস্ট্রেইন্ট ব্যবহার করুন, অথবা পেসিমিস্টিক লকিং (Pessimistic Locking) ব্যবহার করুন। - ডিস্ট্রিবিউটেড সিস্টেমে কনসিস্টেন্সি: মাইক্রোসার্ভেসেসে idempotency key শেয়ার করার জন্য Redis বা ডিস্ট্রিবিউটেড ক্যাশ ব্যবহার করতে পারেন। তবে, কনসিস্টেন্সি নিশ্চিত করতে ট্রানজেকশনাল আউটবক্স প্যাটার্ন (Transactional Outbox Pattern) ব্যবহার করা ভালো।
সারসংক্ষেপ (Summary)
এই টিউটোরিয়ালে আমরা শিখেছি কিভাবে Idempotency ব্যবহার করে মাইক্রোসার্ভিসেসে ডুপ্লিকেট রিকোয়েস্টের সমস্যা সমাধান করা যায়। আমরা একটি সম্পূর্ণ Spring Boot অ্যাপ্লিকেশন তৈরি করেছি যেখানে:
- Idempotency Key হেডার ব্যবহার করা হয়েছে
- ডাটাবেজে key এবং রেসপন্স স্টোর করা হয়েছে
- ডুপ্লিকেট রিকোয়েস্ট ইগনোর করে প্রথম রেসপন্স রিটার্ন করা হয়েছে
Idempotency শুধু পেমেন্ট সিস্টেমের জন্যই নয়, বরং যেকোনো ক্রিটিকাল অপারেশন (যেমন অর্ডার প্লেসমেন্ট, ইউজার রেজিস্ট্রেশন) এর জন্য অপরিহার্য। এটি আপনার সিস্টেমকে রেলায়েবল এবং ফল্ট-টলারেন্ট করে তোলে। মনে রাখবেন, idempotency ইমপ্লিমেন্ট করার সময় কনকারেন্ট রিকোয়েস্ট এবং ডাটা কনসিস্টেন্সি এর দিকে বিশেষ নজর দিতে হবে।