VIPER (View-Interactor-Presenter-Entity-Router) — Mutual Mobile কোম্পানি দ্বারা iOS অ্যাপ্লিকেশনের জন্য তৈরি একটি মডুলার আর্কিটেকচার। VIPER অ্যাপ্লিকেশনটিকে পাঁচটি স্তরে বিভক্ত করে: View প্রদর্শনের জন্য, Interactor ব্যবসায়িক যুক্তির জন্য, Presenter ডেটা প্রস্তুতির জন্য, Entity ডেটা মডেলের জন্য, Router মডিউলগুলির মধ্যে নেভিগেশনের জন্য দায়ী। VIPER মোবাইল আর্কিটেকচারগুলির মধ্যে একক দায়িত্ব নীতির সবচেয়ে বিস্তারিত বাস্তবায়ন। objc.io-তে নিবন্ধ এ আরও পড়ুন।
মূল বিষয়
VIPER (View-Interactor-Presenter-Entity-Router) — 2013–2014 সালে Mutual Mobile-এ বড় iOS প্রকল্পগুলির জন্য তৈরি একটি আর্কিটেকচারাল প্যাটার্ন। অ্যাপ্লিকেশনের প্রতিটি স্ক্রিন কঠোরভাবে সংজ্ঞায়িত দায়িত্ব সহ পাঁচটি উপাদানের একটি পৃথক মডিউল। VIPER মোবাইল ডেভেলপমেন্টে একক দায়িত্ব নীতির (Single Responsibility Principle) সবচেয়ে কঠোর বাস্তবায়ন: কোনো উপাদান তা করে না যা অন্য উপাদান করতে পারে।
View একটি প্যাসিভ উপাদান যা শুধুমাত্র Presenter-এর পাঠানো ডেটা প্রদর্শনের জন্য দায়ী। View-এ কোনো ব্যবসায়িক যুক্তি নেই, নেভিগেশন পরিচালনা করে না, নেটওয়ার্ক অনুরোধ করে না। iOS-এ — ViewProtocol সহ UIViewController। Interactor ব্যবসায়িক যুক্তি স্তর যা Entity এবং পরিষেবাগুলির (নেটওয়ার্ক, DB, GPS) সাথে কাজ করে। Interactor UIKit ইম্পোর্ট করে না। Presenter View এবং Interactor-এর মধ্যে মধ্যস্থতাকারী: Interactor থেকে ডেটা গ্রহণ করে, প্রদর্শনের জন্য ফর্ম্যাট করে, View-এ পাঠায়। Presenter-ও UIKit ইম্পোর্ট করে না। Entity — ডেটা মডেল (struct, class)। Router — নেভিগেশন পরিচালনা করে: মডিউল তৈরি করে, স্ক্রিন খোলে, মডিউলগুলির মধ্যে ডেটা পাঠায়।
| উপাদান | দায়িত্ব | নির্ভরতা |
|---|---|---|
| View | প্রদর্শন, অ্যানিমেশন, জেসচার | UIKit (শুধুমাত্র View) |
| Interactor | ব্যবসায়িক যুক্তি, নেটওয়ার্ক, DB | Entity, পরিষেবা |
| Presenter | ডেটা ফর্ম্যাটিং, View কমান্ড | ViewProtocol, Interactor |
| Entity | ডেটা মডেল | কোনোটিই নয় |
| Router | নেভিগেশন, মডিউল তৈরি | UIViewController (পরিবর্তনের জন্য) |
উপাদানগুলির মধ্যে সম্পর্ক প্রোটোকল দ্বারা বর্ণিত হয়। ViewProtocol প্রদর্শন পদ্ধতি নির্ধারণ করে, InteractorProtocol ব্যবসায়িক যুক্তি পদ্ধতি, PresenterProtocol ইভেন্ট পরিচালনা পদ্ধতি, RouterProtocol নেভিগেশন পদ্ধতি নির্ধারণ করে। প্রতিটি উপাদান শুধুমাত্র প্রোটোকলের মাধ্যমে অন্যটির সাথে যোগাযোগ করে, যা বাস্তবায়ন সহজেই প্রতিস্থাপন এবং বিচ্ছিন্ন পরীক্ষণের অনুমতি দেয়। গড়ে, একটি স্ক্রিনের জন্য VIPER মডিউলে 5টি প্রোটোকল + 5টি ক্লাস + 1টি Builder/Assembler = প্রতি স্ক্রিনে 11টি ফাইল থাকে।
VIPER মডিউল নির্মাণ Builder (বা Assembler)-এ সম্পন্ন হয়, যা পাঁচটি উপাদান তৈরি করে এবং প্রোটোকলের মাধ্যমে সংযুক্ত করে। Builder হল একমাত্র স্থান যেখানে উপাদানগুলি একে অপরের কংক্রিট টাইপ জানে। সমাবেশের পর, View প্রদর্শনের জন্য বাইরে ফেরত দেওয়া হয়, বাকি চেইন পরিষ্কার এবং বিচ্ছিন্নভাবে পরীক্ষণযোগ্য।
// প্রোটোকল — View
protocol UserViewProtocol: AnyObject {
func display(name: String)
func display(email: String)
func showLoading()
func hideLoading()
}
// প্রোটোকল — Interactor
protocol UserInteractorProtocol {
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void)
}
// প্রোটোকল — Router
protocol UserRouterProtocol {
func navigateToProfile(userId: Int)
}
// Interactor — ব্যবসায়িক যুক্তি
final class UserInteractor: UserInteractorProtocol {
private let service: UserService
init(service: UserService) { self.service = service }
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
service.fetchUser(id: id, completion: completion)
}
}
// Presenter — ডেটা প্রস্তুতি
final class UserPresenter {
private weak var view: UserViewProtocol?
private let interactor: UserInteractorProtocol
private let router: UserRouterProtocol
init(interactor: UserInteractorProtocol, router: UserRouterProtocol) {
self.interactor = interactor
self.router = router
}
func setView(_ view: UserViewProtocol) {
self.view = view
}
func viewDidLoad() {
view?.showLoading()
interactor.fetchUser(id: 42) { [weak self] result in
guard let self else { return }
self.view?.hideLoading()
switch result {
case .success(let user):
self.view?.display(name: user.name)
self.view?.display(email: user.email)
case .failure(let error):
// ত্রুটি পরিচালনা
}
}
}
}
// Router — নেভিগেশন
final class UserRouter: UserRouterProtocol {
private weak var viewController: UIViewController?
func setViewController(_ vc: UIViewController) {
viewController = vc
}
func navigateToProfile(userId: Int) {
let profileModule = ProfileModuleBuilder.build(userId: userId)
viewController?.navigationController?.pushViewController(profileModule, animated: true)
}
}
// Builder — মডিউল সমাবেশ
enum UserModuleBuilder {
static func build(userId: Int) -> UIViewController {
let service = UserService()
let interactor = UserInteractor(service: service)
let router = UserRouter()
let presenter = UserPresenter(interactor: interactor, router: router)
let viewController = UserViewController(presenter: presenter)
presenter.setView(viewController)
router.setViewController(viewController)
return viewController
}
}
Builder/Assembler VIPER-এর একটি মূল উপাদান, যা ম্যানুয়ালি ডিপেন্ডেন্সি ইনজেকশন বাস্তবায়ন করে। কনস্ট্রাক্টরের মাধ্যমে ডিপেন্ডেন্সি ইনজেকশন (constructor injection) নিশ্চিত করে যে একটি উপাদান তার নির্ভরতা ছাড়া তৈরি করা যাবে না। আধুনিক VIPER-এ, Builder Swinject (DI কন্টেইনার) ব্যবহার করতে পারে, কিন্তু ম্যানুয়াল সমাবেশ পরীক্ষণের জন্য আরও স্বচ্ছ থাকে। IT Sectr-এ, আমরা জটিল যুক্তিযুক্ত মডিউলগুলির জন্য ম্যানুয়াল সমাবেশ সহ VIPER প্রয়োগ করি — এটি নতুন ডেভেলপারদের জন্য কোড পড়া সহজ করে।
Mapper (Formatter) VIPER-এর একটি ঐচ্ছিক ষষ্ঠ উপাদান। Mapper Entity (DB/সার্ভার মডেল) কে ViewModel (প্রদর্শন মডেল) এ রূপান্তর করে। Entity-তে id, first_name, last_name, email ফিল্ড সহ UserDTO থাকে। ViewModel — name (first_name + last_name) এবং email সহ UserDisplayItem। Mapper Presenter-এ নির্বাহিত হয়। যদি ডেটা ম্যাপিং জটিল হয় (একাধিক Entity → একটি ViewModel), তাহলে Mapper পরীক্ষণের জন্য একটি পৃথক ক্লাসে নিষ্কাশিত হয়।
VIPER মডিউলগুলি বিচ্ছিন্ন এবং একে অপরের সম্পর্কে জানে না। মডিউলগুলির মধ্যে যোগাযোগ Router-এর মাধ্যমে ঘটে। যখন ব্যবহারকারী ব্যবহারকারীর স্ক্রিনে "প্রোফাইল" বাটনে ক্লিক করে, Presenter router.navigateToProfile(userId: 42) কল করে। Router ProfileModuleBuilder.build(userId: 42) এর মাধ্যমে একটি নতুন মডিউল তৈরি করে এবং navigationController.push এর মাধ্যমে খোলে। ডেটা প্রবাহ: মডিউল A → Router A → মডিউল B Builder → মডিউল B তৈরি এবং খোলা হয়।
ডেটা ফেরত পাঠানো (উদাহরণস্বরূপ, নির্বাচন স্ক্রিনে একটি শহর বেছে নেওয়া → প্রোফাইল সম্পাদনা স্ক্রিনে ফিরে আসা) VIPER-এ ডেলিগেট বা ক্লোজারের মাধ্যমে বাস্তবায়িত হয়। মডিউল B didSelectCity(_ city: City) পদ্ধতি সহ ModuleBDelegate প্রোটোকল নির্ধারণ করে। মডিউল A এই প্রোটোকল বাস্তবায়ন করে। Router A ডেলিগেটটি মডিউল B Builder-এ পাঠায়। যখন একটি শহর নির্বাচিত হয়, মডিউল B delegate?.didSelectCity(city) কল করে। এটি মানক iOS অনুশীলন, যেকোনো UIKit ডেভেলপারের কাছে পরিচিত।
| পরিস্থিতি | পদ্ধতি | উদাহরণ |
|---|---|---|
| সামনের নেভিগেশন | Router → Builder | navigateToProfile(userId:) |
| ডেটা ফেরত পাঠানো | Delegate | didSelectCity(_:) |
| সিস্টেম বিজ্ঞপ্তি | NotificationCenter | UserDidLogout |
| Interactor থেকে ইভেন্ট | Presenter → View | WebSocket বার্তা |
NotificationCenter সিস্টেম ইভেন্টের (লগআউট, প্ল্যান পরিবর্তন, পুশ বিজ্ঞপ্তি) জন্য ব্যবহৃত হয় যা একসাথে একাধিক মডিউলকে প্রভাবিত করে। Router বা AppDelegate Notification-এ সাবস্ক্রাইব করে, প্রয়োজনীয় মডিউল তৈরি করে বা অবস্থা আপডেট করে। VIPER NotificationCenter নিষিদ্ধ করে না — গুরুত্বপূর্ণ হল এটি শুধুমাত্র 1-to-many ইভেন্টের জন্য ব্যবহার করা, যখন 1-to-1 যোগাযোগের জন্য ডেলিগেট বা ক্লোজার ব্যবহার করা।
VIPER বনাম MVVM — VIPER-এর প্রতি স্ক্রিনে 2–3 গুণ বেশি কোড প্রয়োজন কিন্তু সম্পূর্ণ উপাদান বিচ্ছিন্নতা প্রদান করে। MVVM ViewModel + SwiftUI সহ সরল এবং দ্রুত কিন্তু 5+ ডেভেলপারের দলের জন্য কম স্কেলেবল। VIPER কঠোরভাবে নির্ধারণ করে কে কীসের জন্য দায়ী: Interactor — শুধুমাত্র ব্যবসায়িক যুক্তি, Presenter — ফর্ম্যাটিং, Router — নেভিগেশন। MVVM-এ, ViewModel প্রায়শই বেড়ে ওঠে, নেভিগেশন এবং ব্যবসায়িক যুক্তি নিজের হাতে নেয়।
VIPER বনাম Clean Architecture — VIPER হল iOS UIKit-এর জন্য অভিযোজিত Clean Architecture-এর একটি নির্দিষ্ট ক্ষেত্র। Interactor = Use Case, Entity = Domain Model, Presenter = Presentation, Router = Controller রবার্ট মার্টিনের ভাষায়। Clean Architecture Interactor এবং ডেটার মধ্যে একটি Gateway/Repository স্তর যোগ করে, যা সাধারণত VIPER-এ আলাদা করা হয় না। আধুনিক SwiftUI প্রকল্পগুলির জন্য, বেশিরভাগ দল Clean Architecture (The Composable Architecture) বা MVVM বেছে নেয়, VIPER-কে UIKit লিগ্যাসির জন্য রেখে দেয়।
কখন VIPER বেছে নেবেন — 5+ ডেভেলপারের দল, 50+ স্ক্রিনের UIKit প্রকল্প, 80% এর উপরে পরীক্ষণের প্রয়োজনীয়তা, শুধুমাত্র iOS (VIPER পুনর্লিখন ছাড়া Android-এ পোর্টেবল নয়)। VIPER একটি পূর্বানুমানযোগ্য কাঠামো প্রদান করে: একজন নতুন ডেভেলপার 15 মিনিটে একটি মডিউল বুঝতে পারে। তবে, বেশি ফাইলের কারণে উন্নয়নের গতি MVVM-এর তুলনায় 20–30% কম। IT Sectr-এ, আমরা 3+ জনের দল সহ এন্টারপ্রাইজ UIKit প্রকল্পগুলির জন্য VIPER ব্যবহার করি এবং নতুন SwiftUI প্রকল্পগুলির জন্য Clean Architecture পছন্দ করি।
VIPER পরীক্ষণের জন্য ডিজাইন করা হয়েছে — প্রতিটি উপাদান প্রোটোকলের মাধ্যমে বিচ্ছিন্নভাবে পরীক্ষিত হয়। Interactor মক পরিষেবার সাথে পরীক্ষিত হয়: এটি যাচাই করে যে fetchUser সঠিক ID দিয়ে কল করা হয়েছে এবং ফলাফল Presenter-এ পাঠানো হয়েছে। Presenter মক View এবং Interactor অবজেক্টের সাথে পরীক্ষিত হয়। Router মক নেভিগেশনের সাথে পরীক্ষিত হয়: এটি যাচাই করে যে navigateToProfile সঠিক userId দিয়ে কল করা হয়েছে এবং সঠিক মডিউল তৈরি করা হয়েছে। View UI টেস্ট (XCUITest) দিয়ে পরীক্ষিত হয়।
import XCTest
final class UserPresenterTests: XCTestCase {
func testViewDidLoad_callsFetchUserAndUpdatesView() {
// Given
let view = MockUserView()
let interactor = MockUserInteractor()
let router = MockUserRouter()
let presenter = UserPresenter(interactor: interactor, router: router)
presenter.setView(view)
let expectedUser = User(id: 42, name: "John", email: "john@test.com")
interactor.result = .success(expectedUser)
// When
presenter.viewDidLoad()
// Then
XCTAssertEqual(interactor.capturedUserId, 42)
XCTAssertEqual(view.displayedName, "John")
XCTAssertTrue(view.didShowLoading)
}
}
মক অবজেক্ট VIPER-এর জন্য ম্যানুয়ালি তৈরি করা হয় (সংরক্ষিত ক্যাপচার বৈশিষ্ট্য সহ ক্লাস) বা Cuckoo / Mockingbird-এর মতো লাইব্রেরির মাধ্যমে। ম্যানুয়াল মক ক্লাস সরল এবং স্পষ্ট, বিশেষ করে নতুন ডেভেলপারদের প্রশিক্ষণের জন্য। প্রতিটি মক ক্যাপচার করা মান (capturedUserId, displayedName) এবং কল ফ্ল্যাগ (didShowLoading) সংরক্ষণ করে। পরীক্ষণের শেষে, শুধু পদ্ধতিটি কল করা হয়েছিল তা নয়, কোন প্যারামিটার দিয়ে কল করা হয়েছিল তাও যাচাই করা হয় — এটি ডেটা প্রবাহের সঠিকতায় আস্থা দেয়।
কোড কভারেজ IT Sectr VIPER প্রকল্পগুলিতে Interactor-এর জন্য 85–95%, Presenter-এর জন্য 90–95%, Router-এর জন্য 70–80%, View-এর জন্য 30–50% (UI টেস্টের মাধ্যমে) পৌঁছায়। View স্ন্যাপশট টেস্ট (SnapshotTesting, 1.5K স্টার) দিয়ে পরীক্ষিত হয় — এটি XCUITest-এর চেয়ে দ্রুত এবং আরও বেশি ক্ষেত্র কভার করে। একটি VIPER প্রকল্পের মোট কভারেজ সাধারণত 70–80%, যা MVVM প্রকল্পের (50–65%) চেয়ে বেশি, কিন্তু টেস্ট লিখতে বেশি সময় লাগে (উন্নয়ন সময়ের 30–40% বনাম MVVM-এ 20–25%)।
প্রায়শই জিজ্ঞাসিত প্রশ্ন
সর্বনিম্ন 11টি ফাইল: 5টি প্রোটোকল (ViewProtocol, InteractorProtocol, PresenterProtocol, RouterProtocol, Entity), 5টি বাস্তবায়ন (ViewController, Interactor, Presenter, Router, Entity) এবং Builder/Assembler। Mapper (Formatter) সহ — 12–13। 50 স্ক্রিনের প্রকল্পের জন্য, শুধুমাত্র VIPER মডিউলের 550–650টি ফাইল হয়। MVVM-এর প্রতি স্ক্রিনে 3টি ফাইল প্রয়োজন (ViewModel, View, Model) — 50 স্ক্রিনের জন্য 150টি ফাইল।
হ্যাঁ, তাত্ত্বিকভাবে VIPER Android-এ পোর্টেবল, কিন্তু বাস্তবে এটি ব্যবহার করা হয় না — Google Jetpack-এর সাথে MVVM সুপারিশ করে। VIPER iOS UIKit-এর জন্য তৈরি হয়েছিল, যেখানে ViewController তার জীবনচক্রের কারণে পরীক্ষণ করা কঠিন। Android-এ, Jetpack ViewModel VIPER বিচ্ছিন্নতা ছাড়া পরীক্ষণের সমস্যা সমাধান করে। Android-এ VIPER-এর সমতুল্য হল মডিউল/ফিচার বিভাজন সহ Clean Architecture।
VIPER হল Clean Architecture-এর iOS-নির্দিষ্ট বাস্তবায়ন। Interactor Use Case-এর সাথে মিলে যায়, Entity — Domain Model, Presenter — Presentation স্তর। Clean Architecture Interactor এবং ডেটার মধ্যে Repository/Gateway যোগ করে, যা VIPER-এ সাধারণত Interactor-এর ভিতরে বাস্তবায়িত হয়। Clean Architecture Router নির্ধারণ করে না — নেভিগেশন বাস্তবায়নের উপর ছেড়ে দেওয়া হয়।
না — SwiftUI MVVM + Combine-এর জন্য ডিজাইন করা হয়েছে। SwiftUI-তে VIPER অপ্রয়োজনীয়: ঘোষণামূলক UI-এর সাথে প্রতি স্ক্রিনে পাঁচটি উপাদান সুবিধা ছাড়া ওভারহেড। SwiftUI-এর জন্য, MVVM বা TCA (The Composable Architecture) বেছে নিন। VIPER UIKit লিগ্যাসি এবং সেই প্রকল্পগুলির জন্য প্রাসঙ্গিক থাকে যেখানে iOS 12 এবং নীচে ন্যূনতম সংস্করণ।
Router-এর মাধ্যমে। মডিউল A router.navigateToProfile(userId: id) কল করে। Router A Builder-এর মাধ্যমে মডিউল B তৈরি করে, userId পাঠায়। ফেরত যোগাযোগ — ডেলিগেটের মাধ্যমে: মডিউল B ModuleBDelegate প্রোটোকল নির্ধারণ করে, মডিউল A এটি বাস্তবায়ন করে এবং Router-এর মাধ্যমে পাঠায়। সিস্টেম ইভেন্ট (লগআউট) — NotificationCenter-এর মাধ্যমে।
সারাংশ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন