Büyük iOS projeleri zamanla monolit canavarlara dönüşür: derleme süreleri dakikalar alır, bir dosyadaki değişiklik beklenmedik yerleri kırar, ve yeni geliştiriciler projeye adapte olamaz. Microservices mimarisi backend dünyasını dönüştürdü — aynı prensipler mobil dünyada da devrim yaratabilir. Bu rehberde, iOS uygulamanızı bağımsız, test edilebilir ve ölçeklenebilir modüllere ayırmayı öğreneceksiniz.
Not: Mobile microservices, backend microservices ile birebir aynı değildir. Burada "micro" kelimesi, bağımsız feature modülleri anlamında kullanılmaktadır.
İçindekiler
- 1. Monolit vs Modüler Mimari
- 2. Module Boundaries Tasarımı
- 3. Inter-Module Communication
- 4. Dependency Injection Container
- 5. Feature Module Anatomisi
- 6. Build Performance
- Sonuç ve Öneriler
1. Monolit vs Modüler Mimari
Karşılaştırma
Kriter | Monolit | Modüler | Micro-Feature |
|---|---|---|---|
Derleme süresi | Uzun (tümü) | Orta (değişen modül) | Kısa (tek modül) |
Ekip ölçeği | 1-5 geliştirici | 5-15 geliştirici | 15+ geliştirici |
Code ownership | Herkes her yer | Modül bazlı | Feature team |
Test izolasyonu | Zor | Kolay | Mükemmel |
Bağımlılık yönetimi | İmplicit | Explicit | Strict boundaries |
Reusability | Düşük | Orta | Yüksek |
Onboarding | Tüm projeyi anla | Modülü anla | Feature'ı anla |
Merge conflict | Sık | Nadir | Çok nadir |
Release bağımsızlığı | Yok | Kısıtlı | Feature flag ile |
2. Module Boundaries Tasarımı
Doğru sınırları belirlemek, modüler mimarinin en kritik adımıdır.
Katman Yapısı
swift
1// MARK: - Module Hierarchy2 3// TIER 1: Core Modules (sifir bagimlilik)4// - CoreFoundation: Extensions, utilities, protocols5// - CoreNetworking: HTTP client, request/response models6// - CoreStorage: Local storage abstractions7// - CoreUI: Design system, shared components8 9// TIER 2: Domain Modules (Core'a bagimli)10// - AuthDomain: Authentication models, protocols11// - PaymentDomain: Payment models, protocols12// - UserDomain: User models, protocols13 14// TIER 3: Feature Modules (Domain + Core'a bagimli)15// - AuthFeature: Login, register, forgot password UI16// - HomeFeature: Home screen, feed17// - ProfileFeature: User profile, settings18// - PaymentFeature: Checkout, payment history19 20// TIER 4: App Module (hepsini birlestirir)21// - MyApp: Composition root, dependency injection, navigationBağımlılık Kuralı
swift
1// Package.swift - Module bagimliliklari2// swift-tools-version: 5.93import PackageDescription4 5let package = Package(6 name: "AppModules",7 platforms: [.iOS(.v17)],8 products: [9 // Core10 .library(name: "CoreFoundation", targets: ["CoreFoundation"]),11 .library(name: "CoreNetworking", targets: ["CoreNetworking"]),12 .library(name: "CoreUI", targets: ["CoreUI"]),13 // Domain14 .library(name: "AuthDomain", targets: ["AuthDomain"]),15 .library(name: "UserDomain", targets: ["UserDomain"]),16 // Feature17 .library(name: "AuthFeature", targets: ["AuthFeature"]),18 .library(name: "HomeFeature", targets: ["HomeFeature"]),19 .library(name: "ProfileFeature", targets: ["ProfileFeature"]),20 ],21 targets: [22 // TIER 1: Core - sifir internal bagimlilik23 .target(name: "CoreFoundation"),24 .target(name: "CoreNetworking", dependencies: ["CoreFoundation"]),25 .target(name: "CoreUI", dependencies: ["CoreFoundation"]),26 27 // TIER 2: Domain - sadece Core'a bagimli28 .target(name: "AuthDomain", dependencies: ["CoreFoundation"]),29 .target(name: "UserDomain", dependencies: ["CoreFoundation"]),30 31 // TIER 3: Feature - Domain + Core'a bagimli32 .target(name: "AuthFeature", dependencies: [33 "AuthDomain", "CoreNetworking", "CoreUI"34 ]),35 .target(name: "HomeFeature", dependencies: [36 "UserDomain", "CoreNetworking", "CoreUI"37 ]),38 .target(name: "ProfileFeature", dependencies: [39 "UserDomain", "CoreNetworking", "CoreUI"40 ]),41 42 // Tests43 .testTarget(name: "AuthFeatureTests", dependencies: ["AuthFeature"]),44 .testTarget(name: "HomeFeatureTests", dependencies: ["HomeFeature"]),45 ]46)3. Inter-Module Communication
Feature modüller birbirini doğrudan import edemez. İletişim, protocol-based routing ile sağlanır.
swift
1// MARK: - Module Router Protocol (CoreFoundation'da)2protocol ModuleRouter {3 func route(to destination: RouteDestination) -> AnyView4}5 6enum RouteDestination: Hashable {7 case profile(userId: String)8 case settings9 case payment(productId: String)10 case auth(mode: AuthMode)11}12 13enum AuthMode: Hashable {14 case login15 case register16}17 18// MARK: - Feature Module Route Handler19// Her feature kendi route'larini handle eder20protocol FeatureRouteHandler {21 var handledRoutes: Set<String> { get }22 func handle(_ destination: RouteDestination) -> AnyView?23}24 25// ProfileFeature icinde:26struct ProfileRouteHandler: FeatureRouteHandler {27 var handledRoutes: Set<String> { ["profile", "settings"] }28 29 func handle(_ destination: RouteDestination) -> AnyView? {30 switch destination {31 case .profile(let userId):32 return AnyView(ProfileView(userId: userId))33 case .settings:34 return AnyView(SettingsView())35 default:36 return nil37 }38 }39}40 41// MARK: - Central Router (App Module'de)42final class AppRouter: ModuleRouter {43 private var handlers: [FeatureRouteHandler] = []44 45 func register(_ handler: FeatureRouteHandler) {46 handlers.append(handler)47 }48 49 func route(to destination: RouteDestination) -> AnyView {50 for handler in handlers {51 if let view = handler.handle(destination) {52 return view53 }54 }55 return AnyView(NotFoundView())56 }57}Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
4. Dependency Injection Container
swift
1// MARK: - DI Container2final class DIContainer {3 static let shared = DIContainer()4 5 private var factories: [String: () -> Any] = [:]6 private var singletons: [String: Any] = [:]7 private let lock = NSRecursiveLock()8 9 func register<T>(10 _ type: T.Type,11 scope: Scope = .transient,12 factory: @escaping () -> T13 ) {14 let key = String(describing: type)15 lock.lock()16 defer { lock.unlock() }17 18 switch scope {19 case .transient:20 factories[key] = factory21 case .singleton:22 factories[key] = { [weak self] in23 guard let self else { return factory() }24 if let existing = self.singletons[key] as? T {25 return existing26 }27 let instance = factory()28 self.singletons[key] = instance29 return instance30 }31 }32 }33 34 func resolve<T>(_ type: T.Type) -> T {35 let key = String(describing: type)36 lock.lock()37 defer { lock.unlock() }38 39 guard let factory = factories[key] else {40 fatalError("\(key) kayitli degil! register() ile kaydedin.")41 }42 guard let instance = factory() as? T else {43 fatalError("\(key) icin factory yanlis tip dondu")44 }45 return instance46 }47 48 enum Scope {49 case transient // Her resolve'da yeni instance50 case singleton // Tek instance51 }52}53 54// MARK: - Module Registration55// Her feature module kendi dependency'lerini kaydeder56protocol ModuleRegistrar {57 static func registerDependencies(in container: DIContainer)58}59 60// AuthFeature/AuthModule.swift61struct AuthModuleRegistrar: ModuleRegistrar {62 static func registerDependencies(in container: DIContainer) {63 container.register(AuthServiceProtocol.self, scope: .singleton) {64 AuthService(65 networkClient: container.resolve(NetworkClientProtocol.self),66 tokenStore: container.resolve(TokenStoreProtocol.self)67 )68 }69 container.register(LoginViewModel.self) {70 LoginViewModel(authService: container.resolve(AuthServiceProtocol.self))71 }72 }73}5. Feature Module Anatomisi
swift
1// MARK: - Ideal Feature Module Yapisi2//3// Sources/ProfileFeature/4// Public/5// ProfileFeatureAPI.swift (Public interface)6// ProfileRouteHandler.swift (Route handling)7// Internal/8// Views/9// ProfileView.swift10// EditProfileView.swift11// ViewModels/12// ProfileViewModel.swift13// Models/14// ProfileState.swift15// Services/16// ProfileService.swift17//18// Tests/ProfileFeatureTests/19// ProfileViewModelTests.swift20// ProfileServiceTests.swift21// Mocks/22// MockProfileService.swift23 24// Public API - sadece bu dosya public25public struct ProfileFeatureAPI {26 public static func makeProfileView(userId: String) -> some View {27 let vm = DIContainer.shared.resolve(ProfileViewModel.self)28 return ProfileView(viewModel: vm, userId: userId)29 }30 31 public static func routeHandler() -> FeatureRouteHandler {32 ProfileRouteHandler()33 }34}6. Build Performance
Modüler mimarinin en büyük avantajlarından biri derleme süresidir.
Build Süresi Karşılaştırması
Senaryo | Monolit | Modüler (SPM) | Kazanım |
|---|---|---|---|
Clean build | 180 sn | 160 sn | %11 |
Incremental (1 dosya) | 45 sn | 8 sn | %82 |
Incremental (1 modül) | 45 sn | 15 sn | %67 |
Test (1 modül) | 120 sn | 12 sn | %90 |
CI full build | 300 sn | 200 sn | %33 |
Build Optimizasyonları
swift
1// Xcode build settings per module2// SWIFT_COMPILATION_MODE = wholemodule (Release)3// BUILD_LIBRARY_FOR_DISTRIBUTION = YES (binary framework)4// EAGER_LINKING = YES (Xcode 15+)5 6// Pre-compiled binary cache7// CI'da her commit'te modulleri pre-build edin8// swift build --product CoreNetworking9// Artifact'i cache'leyin (GitHub Actions cache, Tuist warmup vb.)ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:
Sonuç ve Öneriler
Mobile microservices mimarisi, büyük ekiplerin büyük uygulamalar geliştirmesi için vazgeçilmezdir. Doğru module boundary'leri çizin, bağımlılıkları strict tutun, inter-module communication'ı protocol-based yapın ve DI container ile composition root oluşturun. Derleme süreleri düşecek, test coverage artacak ve yeni geliştiriciler çok daha hızlı adapte olacak.

