MapStruct একটি কোড জেনারেশন ভিত্তিক মডেল ম্যাপিং টুল যা ডোমেইন অবজেক্ট এবং ডেটা ট্রান্সফার অবজেক্ট (DTO) এর মধ্যে ম্যাপিং করতে ব্যবহৃত হয়। এটি compile-time কোড জেনারেশন ব্যবহার করে, যার ফলে রানটাইম পারফরম্যান্সে কোনো প্রভাব পড়ে না। যদিও MapStruct এর মাধ্যমে ম্যাপিং দ্রুত করা হয়, তবে কিছু কৌশল অনুসরণ করলে আপনি আপনার ম্যাপিং অপারেশন আরও দ্রুত এবং কার্যকরী করতে পারেন। এই টিউটোরিয়ালে, আমরা MapStruct এর object mapping পারফরম্যান্স অপটিমাইজেশনের কিছু কৌশল আলোচনা করব।
১. Compile-time Code Generation এর সুবিধা
MapStruct এর একটি বড় সুবিধা হলো এটি compile-time এ কোড জেনারেট করে, যা রানটাইমে অতিরিক্ত কাজ বা রিফ্লেকশন ব্যবহার করার প্রয়োজনীয়তা কমিয়ে দেয়। এতে MapStruct এর পারফরম্যান্স দ্রুত হয়, কারণ ম্যাপিং প্রক্রিয়া reflection বা dynamic proxy এর পরিবর্তে generated code এর মাধ্যমে পরিচালিত হয়।
২. Object Mapping Optimization কৌশল
২.১ Immutable Objects ব্যবহার করুন
যতটুকু সম্ভব immutable objects ব্যবহার করা উচিত, কারণ এগুলি thread-safe এবং re-entrant হয়, যা ম্যাপিংয়ের ক্ষেত্রে আরও দক্ষতা প্রদান করে। যখন আপনি MapStruct দিয়ে immutable objects ম্যাপ করবেন, তখন এটি আরও দ্রুত কাজ করতে পারে এবং কোনো অবাঞ্ছিত পারফরম্যান্স ওভারহেড এড়ানো যায়।
উদাহরণ:
public final class Person {
private final String name;
private final int age;
// constructor, getters
}
২.২ Avoid Repeated Mapping Operations (পুনরাবৃত্তি ম্যাপিং এড়ানো)
প্রতিবার ম্যাপিং করার চেয়ে, যদি ডেটা পরিবর্তিত না হয়, তবে আপনি পূর্ববর্তী ম্যাপিং ফলাফলগুলো cache বা reuse করতে পারেন। এটি পারফরম্যান্স উন্নত করতে সাহায্য করে, বিশেষত যখন অনেকবার একই ডেটা ম্যাপিং করতে হয়।
উদাহরণ:
@Mapper
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
@Mapping(source = "personDto.name", target = "name")
@Mapping(source = "personDto.age", target = "age")
Person toPerson(PersonDTO personDto);
// Caching results for reuse
default PersonDTO fromPersonToDTO(Person person) {
return toPersonDTO(person);
}
}
২.৩ Mapping Collections and Arrays in Batches
যখন আপনি List, Set, বা Arrays এর মতো কালেকশন ম্যাপিং করেন, তখন batch processing ব্যবহারের মাধ্যমে পারফরম্যান্স উন্নত করা যেতে পারে। আপনি যদি একাধিক অবজেক্ট একসাথে ম্যাপ করেন, তবে একাধিক ম্যাপিং কলের পরিবর্তে একবারে সব ডেটা ম্যাপ করতে পারেন।
উদাহরণ:
@Mapper
public interface PersonMapper {
List<PersonDTO> toPersonDTOList(List<Person> persons);
Set<PersonDTO> toPersonDTOSet(Set<Person> persons);
// Handling Arrays
PersonDTO[] toPersonDTOArray(Person[] persons);
}
এখানে, একাধিক অবজেক্ট ম্যাপ করার জন্য আমরা List, Set, এবং Arrays এর জন্য আলাদা ম্যাপিং মেথড তৈরি করেছি।
২.৪ Use of @Mapping (জেনেরিক ম্যাপিং কাস্টমাইজেশন)
আপনি যখন @Mapping অ্যানোটেশন ব্যবহার করেন, তখন কিছু ক্ষেত্রের জন্য ম্যাপিং কাস্টমাইজেশন করতে পারেন। যদি দুটি অবজেক্টের ফিল্ডের নাম বা টাইপের মধ্যে কোনো অমিল থাকে, তবে MapStruct এ @Mapping ব্যবহার করে আপনি কাস্টম ম্যাপিং লজিক তৈরি করতে পারেন।
উদাহরণ:
@Mapper
public interface PersonMapper {
@Mapping(source = "personDto.firstName", target = "name")
@Mapping(source = "personDto.yearsOld", target = "age")
Person toPerson(PersonDTO personDto);
}
এখানে, firstName কে name এবং yearsOld কে age তে ম্যাপ করার জন্য @Mapping ব্যবহার করা হয়েছে।
২.৫ Parallel Processing (একাধিক থ্রেড ব্যবহার)
যদি ম্যাপিং প্রক্রিয়া বড় বা জটিল হয়, তবে আপনি parallel processing বা multithreading ব্যবহার করতে পারেন। MapStruct সরাসরি থ্রেডিং সাপোর্ট না দিলেও, আপনি ম্যাপিং ফাংশনগুলোকে parallel streams বা ExecutorService এর মাধ্যমে একসাথে চালাতে পারেন।
উদাহরণ:
List<PersonDTO> personDTOList = persons.parallelStream()
.map(person -> personMapper.toPersonDTO(person))
.collect(Collectors.toList());
এখানে, parallelStream ব্যবহার করা হয়েছে যাতে অনেক person একসাথে ম্যাপ করা যায়।
২.৬ Avoid Unnecessary Field Mapping
ডেটা ম্যাপিংয়ের সময় কিছু ক্ষেত্র কখনও পরিবর্তিত হয় না বা প্রয়োজনে ব্যবহৃত হয় না। সেই ক্ষেত্রে, MapStruct এর @Mapping অ্যানোটেশন ব্যবহার করে এই ফিল্ডগুলির ম্যাপিং এড়ানো যেতে পারে, ফলে বিল্ডের সময় ওভারহেড কমবে।
উদাহরণ:
@Mapper
public interface PersonMapper {
@Mapping(target = "address", ignore = true) // Ignore unnecessary field
PersonDTO toPersonDTO(Person person);
}
এখানে, address ফিল্ডের ম্যাপিং এড়িয়ে গেছে।
২.৭ Leverage Builder Pattern for Complex Object Mapping
যখন একটি অবজেক্টের অনেক ফিল্ড থাকে এবং এগুলি ম্যাপিং করতে কিছু বিশেষ লজিক প্রযোজ্য হয়, তখন Builder Pattern ব্যবহার করে একটি immutable object তৈরি করা যেতে পারে। এতে ক্লাসের ইনস্ট্যান্স তৈরি করার সময় সমস্যা হতে পারে না এবং ম্যাপিং সহজ হবে।
উদাহরণ:
public class Person {
private String name;
private int age;
private String address;
// Builder Pattern Implementation
public static class Builder {
private String name;
private int age;
private String address;
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
public Builder setAddress(String address) {
this.address = address;
return this;
}
public Person build() {
return new Person(this);
}
}
private Person(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.address = builder.address;
}
}
৩. MapStruct Performance Best Practices
- Compile-time Code Generation: MapStruct এর প্রধান শক্তি হচ্ছে compile-time code generation। এতে রানটাইমের পারফরম্যান্স ভালো থাকে এবং reflection বা proxy এর চেয়ে এটি দ্রুত।
- Use of @Mapping Annotation: ব্যবহারকারী যদি @Mapping অ্যানোটেশন ব্যবহার করেন, তবে এটি পারফরম্যান্সে উন্নতি ঘটায় কারণ MapStruct বুঝতে পারে কোন ফিল্ড ম্যাপিং করা হচ্ছে।
- Avoid Reflection: যেহেতু MapStruct reflection ব্যবহার করে না, তাই এটি অন্য কোনো ম্যাপিং লাইব্রেরির তুলনায় অনেক দ্রুত কাজ করে।
- Parallel Streams for Large Data: বড় ডেটা সেটের জন্য parallel streams ব্যবহার করা যায়, যা ম্যাপিংয়ের সময়কে দ্রুত করে দেয়।
সারাংশ
MapStruct এর মাধ্যমে object mapping খুব দ্রুত এবং পারফরম্যান্স ফ্রেন্ডলি হয়, তবে কিছু কৌশল প্রয়োগের মাধ্যমে আপনি এটি আরও উন্নত করতে পারেন। Immutable objects, batch processing, custom mapping, parallel processing, এবং avoid unnecessary field mapping এর মতো কৌশলগুলো MapStruct এর পারফরম্যান্স অপটিমাইজ করতে সহায়ক। উপরোক্ত কৌশলগুলো অনুসরণ করলে আপনি আপনার ম্যাপিং প্রক্রিয়াকে আরও কার্যকর এবং দ্রুত করতে পারবেন, বিশেষ করে যখন আপনি বড় ডেটা সেট বা জটিল অবজেক্ট ম্যাপিং করছেন।
Read more