ডোমেন লেয়ার

ডোমেন লেয়ার হল একটি ঐচ্ছিক লেয়ার যা UI লেয়ার এবং ডেটা লেয়ারের মধ্যে থাকে।

এটি অন্তর্ভুক্ত করা হলে, ঐচ্ছিক ডোমেন লেয়ার UI লেয়ারে ডিপেন্ডেন্সি প্রদান করে এবং
    ডেটা লেয়ারের উপর নির্ভর করে।
ছবি ১. অ্যাপ আর্কিটেকচারে ডোমেন লেয়ারের ভূমিকা।

ডোমেন লেয়ার জটিল বিজনেস লজিক বা একাধিক ViewModel-এর মাধ্যমে আবার ব্যবহার করা হয় এমন সহজ বিজনেস লজিক এনক্যাপসুলেট করার জন্য দায়ী। এই লেয়ারটি ঐচ্ছিক কারণ সব অ্যাপের ক্ষেত্রে এই প্রয়োজনীয়তা থাকবে না। এটি শুধুমাত্র প্রয়োজন হলেই ব্যবহার করা উচিত-যেমন, জটিলতা সামলানো বা পুনরায় ব্যবহারযোগ্যতার সুবিধা নেওয়া।

ডোমেন লেয়ার নিম্নলিখিত সুবিধা প্রদান করে:

  • এটি কোড ডুপ্লিকেশন এড়ায়।
  • এটি ডোমেন লেয়ার ক্লাস ব্যবহার করে এমন ক্লাসে পঠনযোগ্যতা উন্নত করে।
  • এটি অ্যাপের টেস্ট করার ক্ষমতা উন্নত করে।
  • এটি আপনাকে দায়িত্ব ভাগ করে নেওয়ার অনুমতি দিয়ে বড় ক্লাস এড়াতে সাহায্য করে।

এই ক্লাসগুলি সহজ এবং হালকা রাখতে, প্রতিটি ব্যবহারের ক্ষেত্রে শুধুমাত্র একটি কার্যকারিতার উপর দায়িত্ব থাকা উচিত এবং সেগুলিতে পরিবর্তনযোগ্য ডেটা থাকা উচিত নয়। পরিবর্তনযোগ্য ডেটা আপনার UI বা ডেটা লেয়ারে ম্যানেজ করা উচিত।

এই গাইডে নামকরণ সংক্রান্ত নিয়মাবলী

এই গাইডে, ব্যবহারের উদাহরণগুলির নাম সেইসব সিঙ্গল অ্যাকশনের নাম অনুসারে রাখা হয়েছে যেগুলির জন্য সেগুলি দায়িত্বপ্রাপ্ত। কনভেনশনটি নিচে উল্লেখ করা হল:

বর্তমান কালের ক্রিয়াপদ + বিশেষ্য/কী (ঐচ্ছিক) + UseCase.

যেমন: FormatDateUseCase, LogOutUserUseCase, GetLatestNewsWithAuthorsUseCase, অথবা MakeLoginRequestUseCase।

ডিপেন্ডেন্সি

একটি সাধারণ অ্যাপ আর্কিটেকচারে, ব্যবহারের ক্ষেত্রে ক্লাসগুলি UI লেয়ার থেকে ViewModels এবং ডেটা লেয়ার থেকে রিপোজিটরির মধ্যে ফিট করে। এর অর্থ হল, ব্যবহারের ক্ষেত্রে ক্লাসগুলি সাধারণত রিপোজিটরি ক্লাসের উপর নির্ভর করে এবং তারা UI লেয়ারের সাথে একইভাবে যোগাযোগ করে যেভাবে রিপোজিটরিগুলি করে—কলব্যাক (Java-এর জন্য) বা কোরুটিন (Kotlin-এর জন্য) ব্যবহার করে। এই বিষয়ে আরও জানতে, ডেটা লেয়ার পৃষ্ঠা দেখুন।

যেমন, আপনার অ্যাপে, আপনার কাছে একটি ব্যবহারের কেস ক্লাস থাকতে পারে যা একটি সংবাদ সংগ্রহস্থল এবং একজন লেখকের সংগ্রহস্থল থেকে ডেটা নিয়ে আসে এবং তাদের একত্রিত করে:

class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository
) { /* ... */ }

ব্যবহারের ক্ষেত্রে যেহেতু পুনর্ব্যবহারযোগ্য লজিক থাকে, তাই সেগুলি অন্যান্য ব্যবহারের ক্ষেত্রেও ব্যবহার করা যেতে পারে। ডোমেন লেয়ারে একাধিক লেভেলের ব্যবহারের কেস থাকা স্বাভাবিক। যেমন, নিচের উদাহরণে উল্লেখ করা ব্যবহারের ক্ষেত্রে FormatDateUseCase ব্যবহারের ক্ষেত্র ব্যবহার করা যেতে পারে, যদি UI লেয়ারের একাধিক ক্লাস স্ক্রিনে সঠিক মেসেজ দেখানোর জন্য টাইম জোনের উপর নির্ভর করে:

class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository,
  private val formatDateUseCase: FormatDateUseCase
) { /* ... */ }
GetLatestNewsWithAuthorsUseCase ডেটা লেয়ার থেকে রিপোজিটরি ক্লাসের উপর নির্ভর করে, তবে এটি FormatDataUseCase-এর উপরও নির্ভর করে, যা ডোমেন লেয়ারে থাকা আরেকটি ইউজ কেস ক্লাস।
ছবি ২. অন্যান্য ব্যবহারের ক্ষেত্রের উপর নির্ভর করে এমন ব্যবহারের ক্ষেত্রের জন্য উদাহরণ ডিপেন্ডেন্সি গ্রাফ।

Kotlin-এ কল ব্যবহারের কেস

Kotlin-এ, আপনি operator মডিফায়ারের সাথে invoke() ফাংশনটি সংজ্ঞায়িত করে ফাংশন হিসেবে কল করার জন্য কেস ক্লাস ইনস্ট্যান্স ব্যবহার করতে পারেন। নিচের উদাহরণটি দেখুন:

class FormatDateUseCase(userRepository: UserRepository) {

    private val formatter = SimpleDateFormat(
        userRepository.getPreferredDateFormat(),
        userRepository.getPreferredLocale()
    )

    operator fun invoke(date: Date): String {
        return formatter.format(date)
    }
}

এই উদাহরণে, FormatDateUseCase-এ invoke() পদ্ধতি আপনাকে ক্লাসের ইনস্ট্যান্সকে ফাংশন হিসেবে কল করার অনুমতি দেয়। invoke() পদ্ধতিটি কোনও নির্দিষ্ট স্বাক্ষরের মধ্যে সীমাবদ্ধ নয়—এটি যেকোনও সংখ্যক প্যারামিটার নিতে পারে এবং যেকোনও ধরনের রিটার্ন দিতে পারে। এছাড়াও, আপনি নিজের ক্লাসে বিভিন্ন সিগনেচার সহ invoke() ওভারলোড করতে পারবেন। আপনি উপরের উদাহরণ থেকে ব্যবহারের কেসটি এইভাবে কল করবেন:

class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {
    init {
        val today = Calendar.getInstance()
        val todaysDate = formatDateUseCase(today)
        /* ... */
    }
}

invoke() অপারেটর সম্পর্কে আরও জানতে, Kotlin ডকুমেন্ট দেখুন।

জীবনচক্র

ব্যবহারের ক্ষেত্রে নিজস্ব লাইফসাইকেল থাকে না। পরিবর্তে, সেগুলি যে ক্লাস ব্যবহার করে তার মধ্যে সীমাবদ্ধ থাকে। এর অর্থ হল, আপনি UI লেয়ারের ক্লাস, পরিষেবা বা Application ক্লাস থেকে ব্যবহারের উদাহরণ কল করতে পারবেন। কারণ ব্যবহারের ক্ষেত্রে পরিবর্তনযোগ্য ডেটা থাকা উচিত নয়, তাই আপনি যখনই এটিকে ডিপেন্ডেন্সি হিসেবে পাস করবেন তখনই আপনাকে ব্যবহারের ক্ষেত্রে ক্লাসের একটি নতুন ইনস্ট্যান্স তৈরি করতে হবে।

থ্রেডিং

ডোমেন লেয়ার থেকে ব্যবহারের ক্ষেত্রে অবশ্যই main-safe হতে হবে; অন্য কথায়, সেগুলি অবশ্যই মূল থ্রেড থেকে কল করার জন্য নিরাপদ হতে হবে। ব্যবহারের ক্ষেত্রে ক্লাস যদি দীর্ঘ সময় ধরে চলা ব্লকিং অপারেশন পারফর্ম করে, তাহলে সেই লজিক উপযুক্ত থ্রেডে সরানোর দায়িত্ব তার। তবে, তা করার আগে, সেই ব্লকিং অপারেশনগুলি হায়ারার্কির অন্যান্য স্তরে আরও ভালভাবে রাখা হবে কিনা তা চেক করুন। সাধারণত, পুনর্ব্যবহারযোগ্যতা বা ক্যাশিংকে উৎসাহিত করার জন্য ডেটা লেয়ারে জটিল গণনা করা হয়। যেমন, কোনও বড় তালিকার উপর রিসোর্স-ইনটেনসিভ অপারেশন ডোমেন লেয়ারের পরিবর্তে ডেটা লেয়ারে রাখা ভালো, যদি ফলাফলটি অ্যাপের একাধিক স্ক্রিনে পুনরায় ব্যবহার করার জন্য ক্যাশে করতে হয়।

নিম্নলিখিত উদাহরণে এমন একটি ব্যবহারের উদাহরণ দেখানো হয়েছে যা ব্যাকগ্রাউন্ড থ্রেডে কাজ করে:

class MyUseCase(
    private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {

    suspend operator fun invoke(...) = withContext(defaultDispatcher) {
        // Long-running blocking operations happen on a background thread.
    }
}

সাধারণ টাস্ক

এই বিভাগে সাধারণ ডোমেন লেয়ার টাস্ক কীভাবে করতে হয় তা বর্ণনা করা হয়েছে।

সহজ বিজনেস লজিক যা বারবার ব্যবহার করা যায়

আপনাকে UI লেয়ারে উপস্থিত রিপিটেবল বিজনেস লজিককে একটি ইউজ কেস ক্লাসে এনক্যাপসুলেট করতে হবে। এর ফলে, যেখানে যেখানে লজিক ব্যবহার করা হয়েছে, সেখানে সেখানে কোনও পরিবর্তন প্রয়োগ করা সহজ হয়। এছাড়াও, এটি আপনাকে পৃথকভাবে লজিক পরীক্ষা করার অনুমতি দেয়।

আগে বর্ণনা করা FormatDateUseCase উদাহরণটি বিবেচনা করুন। ভবিষ্যতে তারিখের ফর্ম্যাট সংক্রান্ত আপনার ব্যবসার প্রয়োজনীয়তা পরিবর্তিত হলে, আপনাকে শুধু একটি কেন্দ্রীভূত জায়গায় কোড পরিবর্তন করতে হবে।

রিপোজিটরি একত্রিত করুন

কোনও নিউজ অ্যাপে, আপনার কাছে NewsRepository এবং AuthorsRepository ক্লাস থাকতে পারে যা যথাক্রমে খবর এবং লেখকের ডেটা অপারেশন ম্যানেজ করে। Article ক্লাস যা NewsRepository এক্সপোজ করে তাতে শুধু লেখকের নাম থাকে, কিন্তু আপনি স্ক্রিনে লেখক সম্পর্কে আরও তথ্য দেখাতে চান। লেখকের তথ্য AuthorsRepository থেকে পাওয়া যেতে পারে।

GetLatestNewsWithAuthorsUseCase ডেটা লেয়ার থেকে দুটি আলাদা রিপোজিটরি
    ক্লাসের উপর নির্ভর করে: NewsRepository এবং AuthorsRepository।
ছবি ৩. একাধিক রিপোজিটরি থেকে ডেটা একত্রিত করে এমন ব্যবহারের ক্ষেত্রে নির্ভরতা গ্রাফ।

যেহেতু লজিকে একাধিক রিপোজিটরি থাকে এবং এটি জটিল হতে পারে, তাই আপনি ViewModel থেকে লজিক অ্যাবস্ট্র্যাক্ট করতে এবং এটিকে আরও সহজে পাঠযোগ্য করতে একটি GetLatestNewsWithAuthorsUseCase ক্লাস তৈরি করেন। এটি লজিককে আলাদাভাবে পরীক্ষা করা এবং অ্যাপের বিভিন্ন অংশে পুনরায় ব্যবহার করা সহজ করে তোলে।

/**
 * This use case fetches the latest news and the associated author.
 */
class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository,
  private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
    suspend operator fun invoke(): List<ArticleWithAuthor> =
        withContext(defaultDispatcher) {
            val news = newsRepository.fetchLatestNews()
            val result: MutableList<ArticleWithAuthor> = mutableListOf()
            // This is not parallelized, the use case is linearly slow.
            for (article in news) {
                // The repository exposes suspend functions
                val author = authorsRepository.getAuthor(article.authorId)
                result.add(ArticleWithAuthor(article, author))
            }
            result
        }
}

লজিকটি news তালিকার সব আইটেম ম্যাপ করে; তাই ডেটা লেয়ার মেন-সেফ হলেও, এই কাজটি মূল থ্রেডকে ব্লক করা উচিত নয় কারণ আপনি জানেন না যে এটি কতগুলি আইটেম প্রসেস করবে। সেই কারণে, ব্যবহারের ক্ষেত্রে ডিফল্ট ডিসপ্যাচার ব্যবহার করে ব্যাকগ্রাউন্ড থ্রেডে কাজ সরানো হয়।

অন্যান্য গ্রাহক

UI লেয়ার ছাড়াও, ডোমেন লেয়ার অন্যান্য ক্লাস যেমন পরিষেবা এবং Application ক্লাস দ্বারা আবার ব্যবহার করা যেতে পারে। এছাড়াও, টিভি বা Wear-এর মতো অন্যান্য প্ল্যাটফর্ম যদি মোবাইল অ্যাপের সাথে কোডবেস শেয়ার করে, তাহলে তাদের UI লেয়ারও ডোমেন লেয়ারের উপরে উল্লিখিত সমস্ত সুবিধা পেতে ইউজ কেস আবার ব্যবহার করতে পারে।

ডেটা লেয়ার অ্যাক্সেস সংক্রান্ত বিধিনিষেধ

ডোমেন লেয়ার প্রয়োগ করার সময় আরেকটি বিষয় বিবেচনা করতে হবে, তা হল UI লেয়ার থেকে ডেটা লেয়ারে সরাসরি অ্যাক্সেস করার অনুমতি দেওয়া উচিত নাকি ডোমেন লেয়ারের মাধ্যমে সবকিছু করতে বাধ্য করা উচিত।

UI লেয়ার সরাসরি ডেটা লেয়ার অ্যাক্সেস করতে পারে না, এটি ডোমেন লেয়ারের মাধ্যমে করতে হবে
ছবি ৪. ডিপেন্ডেন্সি গ্রাফ দেখাচ্ছে যে UI লেয়ারকে ডেটা লেয়ার অ্যাক্সেস করার অনুমতি দেওয়া হয়নি।

এই বিধিনিষেধ আরোপ করার একটি সুবিধা হল, এটি আপনার UI-কে ডোমেন লেয়ার লজিক বাইপাস করা থেকে আটকায়, যেমন, আপনি যদি ডেটা লেয়ারে প্রতিটি অ্যাক্সেসের অনুরোধে অ্যানালিটিক্স লগিং পারফর্ম করেন।

তবে, সম্ভাব্য উল্লেখযোগ্য অসুবিধা হল, এটি আপনাকে ডেটা লেয়ারে সাধারণ ফাংশন কল হলেও ব্যবহারের উদাহরণ যোগ করতে বাধ্য করে, যা সামান্য সুবিধার জন্য জটিলতা বাড়াতে পারে।

প্রয়োজন হলেই শুধু ব্যবহারের উদাহরণ যোগ করা একটি ভালো পদ্ধতি। আপনি যদি দেখেন যে আপনার UI লেয়ার প্রায় একচেটিয়াভাবে ব্যবহারের কেসের মাধ্যমে ডেটা অ্যাক্সেস করছে, তাহলে শুধুমাত্র এইভাবে ডেটা অ্যাক্সেস করা যুক্তিযুক্ত হতে পারে।

শেষ পর্যন্ত, ডেটা লেয়ারে অ্যাক্সেস সীমাবদ্ধ করার সিদ্ধান্ত আপনার ব্যক্তিগত কোডবেসের উপর নির্ভর করে এবং আপনি কঠোর নিয়ম বা আরও নমনীয় পদ্ধতি পছন্দ করেন কিনা।

পরীক্ষা করা

ডোমেন লেয়ার পরীক্ষা করার সময় পরীক্ষা করার সাধারণ নির্দেশিকা প্রযোজ্য হয়। অন্যান্য UI পরীক্ষার জন্য, ডেভেলপাররা সাধারণত ফেক রিপোজিটরি ব্যবহার করেন এবং ডোমেন লেয়ার পরীক্ষা করার সময়ও ফেক রিপোজিটরি ব্যবহার করা ভালো অভ্যাস।

নমুনা

নিম্নলিখিত Google স্যাম্পেল ডোমেন লেয়ারের ব্যবহার দেখায়। এই নির্দেশিকা বাস্তবে প্রয়োগ করতে, সেগুলি এক্সপ্লোর করুন: