ডোমেন স্তর

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

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

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

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

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

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

এই নির্দেশিকায় নামকরণের নিয়মাবলী

এই নির্দেশিকায়, ব্যবহারের ক্ষেত্রের নামকরণ করা হয়েছে একক ক্রিয়াকলাপের জন্য যা তারা দায়ী। কনভেনশনটি নিম্নরূপ:

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

উদাহরণস্বরূপ: FormatDateUseCase , LogOutUserUseCase , GetLatestNewsWithAuthorsUseCase , বা MakeLoginRequestUseCase

নির্ভরতা

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

উদাহরণস্বরূপ, আপনার অ্যাপে, আপনার কাছে একটি ব্যবহারের কেস ক্লাস থাকতে পারে যা একটি সংবাদ সংগ্রহস্থল এবং একটি লেখক সংগ্রহস্থল থেকে ডেটা আনে এবং সেগুলিকে একত্রিত করে:

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 এর উপরও নির্ভর করে, আরেকটি ইউজ কেস ক্লাস যা ডোমেন লেয়ারেও রয়েছে।
চিত্র 2. একটি ব্যবহারের ক্ষেত্রে নির্ভরতা গ্রাফের উদাহরণ যা অন্যান্য ব্যবহারের ক্ষেত্রে নির্ভর করে।

কোটলিনে কল ব্যবহারের ক্ষেত্রে

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)
    }
}

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

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

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

জীবনচক্র

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

থ্রেডিং

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

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

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 ক্লাস থাকতে পারে যা যথাক্রমে সংবাদ এবং লেখক ডেটা অপারেশন পরিচালনা করে। NewsRepository যে Article শ্রেণীটি প্রকাশ করে তাতে শুধুমাত্র লেখকের নাম থাকে তবে আপনি স্ক্রিনে লেখক সম্পর্কে আরও তথ্য প্রদর্শন করতে চান। লেখকের তথ্য AuthorsRepository থেকে প্রাপ্ত করা যেতে পারে।

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

কারণ যুক্তিতে একাধিক সংগ্রহস্থল জড়িত এবং জটিল হয়ে উঠতে পারে, আপনি একটি 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 ক্লাস দ্বারা পুনরায় ব্যবহার করা যেতে পারে। উপরন্তু, যদি অন্যান্য প্ল্যাটফর্ম যেমন TV বা Wear মোবাইল অ্যাপের সাথে কোডবেস শেয়ার করে, তাহলে তাদের UI লেয়ার ডোমেন লেয়ারের উপরোল্লিখিত সমস্ত সুবিধাগুলি পাওয়ার জন্য কেসগুলিকে পুনরায় ব্যবহার করতে পারে।

ডেটা স্তর অ্যাক্সেস সীমাবদ্ধতা

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

UI স্তর সরাসরি ডেটা স্তর অ্যাক্সেস করতে পারে না, এটি অবশ্যই ডোমেন স্তরের মধ্য দিয়ে যেতে হবে
চিত্র 4. নির্ভরতা গ্রাফ দেখায় যে UI স্তরটি ডেটা স্তরে অ্যাক্সেস অস্বীকার করা হচ্ছে।

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

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

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

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

পরীক্ষামূলক

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

নমুনা

নিম্নলিখিত Google নমুনাগুলি ডোমেন স্তরের ব্যবহার প্রদর্শন করে৷ অনুশীলনে এই নির্দেশিকা দেখতে তাদের অন্বেষণ করুন:

{% শব্দার্থে %} {% endverbatim %} {% শব্দার্থে %} {% endverbatim %}