הגדרת Clean Architecture לאפליקציות iOS – מפתחות
דמיינו אפליקציה עם 50 מסכים, שבה כל ViewController מושך את שכבת הרשת, מסד הנתונים והלוגיקה של הניווט. הוספת פיצ'ר חדש לוקחת שבוע, אבל כל שינוי שובר בדיקות ישנות. בסיס הקוד גדל, ומהירות הפיתוח יורדת. ראינו את זה ביותר מ-30 פרויקטים – מבנקאות ועד סטרימינג. הפתרון הוא גישת Clean Architecture, ארכיטקטורה שכבתית שמפרידה בקפדנות בין אחריות והופכת את הקוד לצפוי.
Clean Architecture, שהפכה פופולרית על ידי רוברט מרטין, מציעה שלוש שכבות קונצנטריות: Domain (לוגיקה עסקית ליבה), Data (מימושים של רפוזיטוריות ומקורות נתונים), ו-Presentation (ממשק משתמש ו-ViewModel). התלויות מצביעות פנימה: Domain לא יודעת דבר על Data ו-Presentation. זה מאפשר לבדוק לוגיקה עסקית מבלי להפעיל את הסימולטור ולשנות את ממשק המשתמש מבלי לגעת בליבה. יישום גישה זו מאיץ הוספת פיצ'רים חדשים פי 3 ומפחית את זמן בדיקות הרגרסיה ב-80% – נתונים מהניסיון שלנו. ההשקעה האופיינית להגדרה מלאה נעה בין 3,000 ל-15,000 דולר, תלוי במורכבות הפרויקט.
יישמנו דפוס זה ביותר מ-30 פרויקטים מסחריים: אפליקציות בנקאות, שירותי סטרימינג, מרקטפלייסים. לקוחות מקבלים ארכיטקטורה שמאיצה את הפיתוח ומפחיתה עלויות תחזוקה. להלן נפרק איך זה עובד בפועל.
איך ארכיטקטורה זו פותרת בעיות פיתוח iOS
בפרשנות הקלאסית של בוב מרטין, יש שלוש טבעות: Entities → Use Cases → Interface Adapters. ב-iOS זה מתורגם כך.
שכבת Domain – הליבה. כאן נמצאים מודלי Entity: מבני Swift טהורים ללא ייבוא Foundation, רק נתונים עסקיים. לצידם נמצאים פרוטוקולי UseCase והמימושים שלהם. לדוגמה, FetchUserProfileUseCase מקבל UserRepository באמצעות הזרקת תלויות ומחזיר AnyPublisher<UserProfile, DomainError>. אין URLSession, אין CoreData. שכבה זו מתקמפלת ונבדקת בבידוד.
protocol UserRepository {
func fetchProfile(id: String) -> AnyPublisher<UserProfile, DomainError>
}
final class FetchUserProfileUseCase {
private let repository: UserRepository
init(repository: UserRepository) {
self.repository = repository
}
func execute(id: String) -> AnyPublisher<UserProfile, DomainError> {
repository.fetchProfile(id: id)
}
}שכבת Data – מימושים של רפוזיטוריות. protocol UserRepository { func fetchProfile(id: String) -> AnyPublisher<UserProfile, DomainError> } final class FetchUserProfileUseCase { private let repository: UserRepository init(repository: UserRepository) { self.repository = repository } func execute(id: String) -> AnyPublisher<UserProfile, DomainError> { repository.fetchProfile(id: id) } } עובד עם UserRepositoryImpl או Alamofire, ממפה DTO למודל domain, ומטפל בשגיאות רשת. URLSession מממש את אותו פרוטוקול עבור קאש מקומי. בחירת מקור הנתונים נמצאת ב-CoreDataUserCache באמצעות אסטרטגיה או במיכל DI.
שכבת Presentation – כאן חיים ViewModel/Presenter. בשילוב עם SwiftUI, UserRepositoryImpl-ViewModel נוח: הוא קורא ל-UseCase, הופך את התוצאה למצב ObservableObject ומפרסם אותה. ViewController או @Published של SwiftUI מטפל ברינדור בלבד.
ניווט ו-DI
| שכבה | אחריות | תלויות |
|---|---|---|
| Domain | לוגיקה עסקית, Entity, UseCase | אין |
| Data | מימושים של רפוזיטוריות, מיפוי | Domain |
| Presentation | ViewModel, View, Coordinator | Domain |
בעיה אופיינית – ViewController יוצר את ה-ViewController הבא ודוחף אותו. פתרון – Coordinator. ViewModel מחזיק הפניה חלשה ל-View. ה-ProfileCoordinator הקונקרטי יודע על ProfileCoordinatorImpl והמסכים הבאים. ViewModel לא.
protocol ProfileCoordinator: AnyObject { func showEditProfile(user: UserProfile) func showOrders(userId: String) } אנו משתמשים בהזרקת בנאי בשרשרת: UINavigationController יוצר protocol ProfileCoordinator: AnyObject { func showEditProfile(user: UserProfile) func showOrders(userId: String) } , שיוצר רפוזיטוריות ו-UseCases ספציפיים, ומעביר אותם ל-ViewModel דרך SceneDelegate. אפשר לחבר Swinject או Needle, אבל עבור רוב הפרויקטים הרכבה ידנית ב-AppCoordinator מספיקה. Service Locator הוא אנטי-דפוס: הוא מסתיר תלויות ושובר בדיקות.
למה Clean Architecture עדיפה על MVC רגיל
ב-MVC, ViewController הופך לעיתים קרובות ל"אובייקט אלוהי" שאחראי על הכל. הארכיטקטורה השכבתית נותנת האצה פי 3 בהוספת פיצ'רים חדשים בזכות גבולות ברורים: שינויים בממשק המשתמש לא משפיעים על הלוגיקה העסקית. זמן הבנייה של בדיקות שכבת domain מופחת ב-80% – שניות במקום דקות. הנה דוגמת בדיקה:
final class FetchUserProfileUseCaseTests: XCTestCase {
func test_execute_returnsProfile() {
let mock = MockUserRepository(result: .success(.stub()))
let sut = FetchUserProfileUseCase(repository: mock)
var received: UserProfile?
_ = sut.execute(id: "123").sink(
receiveCompletion: { _ in },
receiveValue: { received = $0 }
)
XCTAssertEqual(received?.id, "123")
}
} איך ליישם Clean Architecture בפרויקט iOS: מדריך שלב-אחר-שלב
- בצעו ביקורת על הארכיטקטורה הנוכחית וזהו נקודות חוצות-שכבות.
- צרו חבילות Swift עבור כל שכבה: Domain, Data, Presentation.
- הגדירו פרוטוקולי רפוזיטוריות ב-Domain, ויישמו אותם ב-Data.
- הקימו CompositionRoot – מחלקה מרכזית לאתחול תלויות ידני.
- כתבו בדיקות יחידה ל-UseCases מרכזיים.
טעויות נפוצות ביישום Clean Architecture
- UseCases דקים מדי.
initשעושהCompositionRootהוא חסר תועלת. - מודלי Domain עם Codable. DTO ומיפוי חייבים להיות בשכבת Data.
- ViewModel יודע על רפוזיטוריה קונקרטית. רק פרוטוקול + הזרקת בנאי.
הימנעות מטעויות אלו מעניקה לכם ארכיטקטורה שקל לתחזק ולהרחיב. אנו מבטיחים איכות המבוססת על יותר מ-5 שנות ניסיון ויותר מ-200 פריסות מוצלחות. צרו קשר לייעוץ – נעריך את הפרויקט שלכם ונציע פתרון אופטימלי. הזמינו הגדרת Clean Architecture והאיצו את הפיתוח פי כמה.
הגדרת Clean Architecture מפתחות: לוחות זמנים ומחירים
הגדרה מאפס על פרויקט חדש: 3–5 ימים. רפקטורינג של פרויקט קיים עם העברת 10–15 מודולים: 2–4 שבועות. העלות מחושבת לאחר ניתוח קוד, בדרך כלל בין 3,000 ל-15,000 דולר. קבלו ייעוץ – נעריך את הפרויקט שלכם ונספק אחריות על הארכיטקטורה. צרו קשר כדי לדון בפרטים.
מה כלול בהגדרה
| שלב | פעולות | תוצאה |
|---|---|---|
| ביקורת | ניתוח הארכיטקטורה הנוכחית, זיהוי תלויות | תוכנית העברה |
| מודולים | יצירת מודולי Swift Package עבור Domain, Data, Presentation | מבנה פרויקט נקי |
| DI | יישום CompositionRoot או מיכל DI | ניהול תלויות |
| פיצ'רים | הגדרת 2–3 מודולי פיצ'ר כתבניות | דוגמה לצוות |
| בדיקות | כתיבת בדיקות יחידה בסיסיות לשכבת domain | כיסוי בדיקות |







