고급 패턴 및 모범 사례
이 종합적인 가이드는 고급 의존성 주입 패턴, 아키텍처 모범 사례, 그리고 WeaveDI를 사용하여 프로덕션 준비가 완료된 애플리케이션을 구축하기 위한 전문 기법을 다룹니다.
목차
고급 Property Wrapper 패턴
조건부 주입
런타임 조건에 따라 의존성을 주입합니다:
swift
// 환경 기반 조건부 주입
class EnvironmentAwareService {
@Inject private var productionAPI: ProductionAPIService?
@Inject private var developmentAPI: DevelopmentAPIService?
private var apiService: APIServiceProtocol? {
#if DEBUG
return developmentAPI
#else
return productionAPI
#endif
}
// 대안: 런타임 조건 기반 주입
@Inject private var userService: UserService?
@Inject private var adminService: AdminService?
private func getService(for user: User) -> UserServiceProtocol? {
return user.isAdmin ? adminService : userService
}
}
제네릭 의존성 주입
타입 안전한 제네릭 의존성 패턴을 생성합니다:
swift
// 제네릭 레포지토리 패턴
protocol Repository<Entity> {
associatedtype Entity: Codable
func save(_ entity: Entity) async throws
func fetch(id: String) async throws -> Entity?
func fetchAll() async throws -> [Entity]
}
class CoreDataRepository<T: Codable>: Repository {
typealias Entity = T
@Inject private var coreDataStack: CoreDataStack?
@Inject private var logger: Logger?
func save(_ entity: T) async throws {
logger?.info("\(T.self) 타입의 엔티티를 저장합니다")
// CoreData 구현
}
func fetch(id: String) async throws -> T? {
logger?.info("id가 \(id)인 \(T.self)를 가져옵니다")
// CoreData 구현
return nil
}
func fetchAll() async throws -> [T] {
logger?.info("\(T.self) 타입의 모든 엔티티를 가져옵니다")
// CoreData 구현
return []
}
}
// 등록
await WeaveDI.Container.bootstrap { container in
container.register(Repository<User>.self) {
CoreDataRepository<User>()
}
container.register(Repository<Product>.self) {
CoreDataRepository<Product>()
}
}
// 사용
class UserManager {
@Inject private var userRepository: Repository<User>?
func createUser(_ user: User) async throws {
try await userRepository?.save(user)
}
}
DI를 사용한 데코레이터 패턴
데코레이터를 사용하여 횡단 관심사를 구현합니다:
swift
// 기본 서비스
protocol OrderService {
func processOrder(_ order: Order) async throws -> OrderResult
}
class BasicOrderService: OrderService {
@Inject private var paymentService: PaymentService?
@Inject private var inventoryService: InventoryService?
func processOrder(_ order: Order) async throws -> OrderResult {
// 기본 주문 처리 로직
return OrderResult(orderId: order.id, status: .processed)
}
}
// 로깅 데코레이터
class LoggingOrderService: OrderService {
@Inject private var logger: Logger?
private let decorated: OrderService
init(decorated: OrderService) {
self.decorated = decorated
}
func processOrder(_ order: Order) async throws -> OrderResult {
logger?.info("주문 처리 중: \(order.id)")
do {
let result = try await decorated.processOrder(order)
logger?.info("주문 처리 완료: \(order.id)")
return result
} catch {
logger?.error("주문 처리 실패: \(order.id), 오류: \(error)")
throw error
}
}
}
// 분석 데코레이터
class AnalyticsOrderService: OrderService {
@Inject private var analytics: AnalyticsService?
private let decorated: OrderService
init(decorated: OrderService) {
self.decorated = decorated
}
func processOrder(_ order: Order) async throws -> OrderResult {
let startTime = Date()
analytics?.track("order_processing_started", parameters: ["order_id": order.id])
do {
let result = try await decorated.processOrder(order)
let duration = Date().timeIntervalSince(startTime)
analytics?.track("order_processing_completed", parameters: [
"order_id": order.id,
"duration_ms": Int(duration * 1000),
"status": result.status.rawValue
])
return result
} catch {
analytics?.track("order_processing_failed", parameters: [
"order_id": order.id,
"error": error.localizedDescription
])
throw error
}
}
}
// 데코레이터 체인으로 등록
await WeaveDI.Container.bootstrap { container in
// 기본 서비스 등록
container.register("BasicOrderService", OrderService.self) {
BasicOrderService()
}
// 데코레이터된 서비스 등록
container.register(OrderService.self) {
let basicService = container.resolve("BasicOrderService", OrderService.self)!
let loggingService = LoggingOrderService(decorated: basicService)
return AnalyticsOrderService(decorated: loggingService)
}
}
복잡한 의존성 그래프
순환 의존성 해결
순환 의존성을 안전하게 처리합니다:
swift
// 지연 주입을 사용하여 순환 의존성 해결
class UserService {
@Inject private var orderService: OrderService? // 처음에는 nil
// 순환을 끊기 위한 지연 해결
private lazy var lazyOrderService: OrderService? = {
UnifiedDI.resolve(OrderService.self)
}()
func getUserOrders(userId: String) async throws -> [Order] {
return try await lazyOrderService?.getOrdersForUser(userId) ?? []
}
}
class OrderService {
@Inject private var userService: UserService?
private lazy var lazyUserService: UserService? = {
UnifiedDI.resolve(UserService.self)
}()
func getOrdersForUser(_ userId: String) async throws -> [Order] {
guard let user = try await lazyUserService?.getUser(id: userId) else {
return []
}
// 사용자의 주문 반환
return []
}
}
// 대안: 프로토콜을 사용하여 순환 해결
protocol UserServiceProtocol {
func getUser(id: String) async throws -> User?
}
protocol OrderServiceProtocol {
func getOrdersForUser(_ userId: String) async throws -> [Order]
}
class UserServiceImpl: UserServiceProtocol {
@Inject private var orderService: OrderServiceProtocol?
func getUser(id: String) async throws -> User? {
// 구현
return nil
}
}
class OrderServiceImpl: OrderServiceProtocol {
@Inject private var userService: UserServiceProtocol?
func getOrdersForUser(_ userId: String) async throws -> [Order] {
// 구현
return []
}
}
계층적 의존성
부모-자식 의존성 관계를 생성합니다:
swift
// 공유 의존성을 가진 부모 컨테이너
class ParentContainer {
static func configure() async {
await WeaveDI.Container.bootstrap { container in
// 공유/전역 의존성
container.register(DatabaseService.self) {
CoreDataService()
}
container.register(NetworkService.self) {
URLSessionNetworkService()
}
container.register(Logger.self) {
ConsoleLogger()
}
}
}
}
// 기능별 의존성을 위한 자식 컨테이너
class FeatureContainer {
static func configure() async {
// 부모 컨테이너가 이미 구성되었다고 가정
await WeaveDI.Container.bootstrap { container in
// 부모 의존성을 사용할 수 있는 기능별 의존성
container.register(UserRepository.self) {
UserRepositoryImpl() // 데이터베이스 및 네트워크 서비스를 자동 주입
}
container.register(UserService.self) {
UserServiceImpl() // 사용자 레포지토리 및 로거를 자동 주입
}
}
}
}
// 앱에서 사용
@main
struct MyApp: App {
init() {
Task {
// 순서대로 구성: 부모 먼저, 그 다음 자식
await ParentContainer.configure()
await FeatureContainer.configure()
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
성능 최적화 기법
지연 초기화 패턴
지연 초기화로 메모리 사용량을 최적화합니다:
swift
class PerformanceOptimizedService {
// 비용이 많이 드는 작업을 위한 지연 프로퍼티
@Inject private var _expensiveService: ExpensiveService?
private lazy var expensiveService: ExpensiveService? = {
print("💰 비용이 많이 드는 서비스가 생성되었습니다")
return _expensiveService
}()
// 임시 객체를 위한 팩토리
@Factory private var temporaryProcessor: TemporaryProcessor
// 캐시된 계산 프로퍼티
private var _cachedResult: String?
private var cachedResult: String {
if let cached = _cachedResult {
return cached
}
let result = performExpensiveComputation()
_cachedResult = result
return result
}
private func performExpensiveComputation() -> String {
// 비용이 많이 드는 작업
return "computed_result"
}
func performLightOperation() {
// 이것은 비용이 많이 드는 서비스 생성을 트리거하지 않습니다
print("가벼운 작업이 완료되었습니다")
}
func performHeavyOperation() async {
// 이것은 필요할 때만 비용이 많이 드는 서비스를 생성합니다
await expensiveService?.performHeavyWork()
}
func processTemporaryData(_ data: Data) {
// 각 호출마다 새로운 프로세서 인스턴스
let processor = temporaryProcessor
processor.process(data)
// processor는 사용 후 자동으로 해제됩니다
}
}
배치 의존성 해결
여러 의존성 해결을 최적화합니다:
swift
class BatchOptimizedService {
// 여러 의존성을 한 번에 배치로 해결
private let dependencies: (
userService: UserService?,
orderService: OrderService?,
paymentService: PaymentService?,
notificationService: NotificationService?
)
init() {
// 모든 의존성을 한 번에 배치 해결
dependencies = (
userService: UnifiedDI.resolve(UserService.self),
orderService: UnifiedDI.resolve(OrderService.self),
paymentService: UnifiedDI.resolve(PaymentService.self),
notificationService: UnifiedDI.resolve(NotificationService.self)
)
}
func processComplexWorkflow() async throws {
// 모든 의존성이 이미 해결됨 - 조회 비용 없음
guard let userService = dependencies.userService,
let orderService = dependencies.orderService,
let paymentService = dependencies.paymentService,
let notificationService = dependencies.notificationService else {
throw ServiceError.dependenciesNotAvailable
}
// 사전 해결된 의존성 사용
let user = try await userService.getCurrentUser()
let orders = try await orderService.getOrdersForUser(user.id)
for order in orders {
try await paymentService.processPayment(for: order)
await notificationService.sendOrderConfirmation(order: order, to: user)
}
}
}
메모리 풀 패턴
비용이 많이 드는 객체를 재사용합니다:
swift
// 생성 비용이 많이 드는 객체를 위한 객체 풀
class ImageProcessorPool {
private var availableProcessors: [ImageProcessor] = []
private var allProcessors: [ImageProcessor] = []
private let maxPoolSize = 5
private let lock = NSLock()
@Factory private var processorFactory: ImageProcessor
func borrowProcessor() -> ImageProcessor {
lock.lock()
defer { lock.unlock() }
if let processor = availableProcessors.popLast() {
return processor
}
// 풀이 최대 용량이 아니면 새 프로세서 생성
if allProcessors.count < maxPoolSize {
let processor = processorFactory // 새 인스턴스 생성
allProcessors.append(processor)
return processor
}
// 풀이 가득 참, 대기하고 재사용
return availableProcessors.first ?? processorFactory
}
func returnProcessor(_ processor: ImageProcessor) {
lock.lock()
defer { lock.unlock() }
processor.reset() // 상태 지우기
availableProcessors.append(processor)
}
}
class ImageService {
@Inject private var processorPool: ImageProcessorPool?
func processImages(_ images: [UIImage]) async -> [UIImage] {
var processedImages: [UIImage] = []
for image in images {
guard let pool = processorPool else { continue }
let processor = pool.borrowProcessor()
let processed = await processor.process(image)
processedImages.append(processed)
pool.returnProcessor(processor)
}
return processedImages
}
}
오류 처리 전략
우아한 성능 저하
누락된 의존성을 우아하게 처리합니다:
swift
class ResilientService {
@Inject private var primaryService: PrimaryService?
@Inject private var fallbackService: FallbackService?
@Inject private var logger: Logger?
func performOperation() async throws -> Result {
// 먼저 주 서비스 시도
if let primary = primaryService {
do {
return try await primary.performOperation()
} catch {
logger?.warning("주 서비스 실패, 대체 서비스 시도: \(error)")
}
}
// 보조 서비스로 대체
if let fallback = fallbackService {
do {
return try await fallback.performOperation()
} catch {
logger?.error("대체 서비스도 실패: \(error)")
throw ServiceError.allServicesFailed
}
}
// 우아한 성능 저하 - 캐시된 또는 기본 결과 반환
logger?.info("사용 가능한 서비스가 없음, 캐시된 결과 반환")
return getCachedResult() ?? getDefaultResult()
}
private func getCachedResult() -> Result? {
// 사용 가능한 경우 캐시된 데이터 반환
return nil
}
private func getDefaultResult() -> Result {
// 안전한 기본값 반환
return Result.empty
}
}
의존성 상태 검사
의존성 상태를 모니터링합니다:
swift
protocol HealthCheckable {
func healthCheck() async -> HealthStatus
}
enum HealthStatus {
case healthy
case degraded(reason: String)
case unhealthy(error: Error)
}
class HealthMonitorService {
@Inject private var userService: UserService?
@Inject private var databaseService: DatabaseService?
@Inject private var networkService: NetworkService?
@Inject private var logger: Logger?
func performHealthCheck() async -> [String: HealthStatus] {
var results: [String: HealthStatus] = [:]
// 모든 주입 가능한 의존성 검사
await withTaskGroup(of: (String, HealthStatus).self) { group in
if let service = userService as? HealthCheckable {
group.addTask { ("UserService", await service.healthCheck()) }
}
if let service = databaseService as? HealthCheckable {
group.addTask { ("DatabaseService", await service.healthCheck()) }
}
if let service = networkService as? HealthCheckable {
group.addTask { ("NetworkService", await service.healthCheck()) }
}
for await (serviceName, status) in group {
results[serviceName] = status
switch status {
case .healthy:
logger?.info("\(serviceName): 정상")
case .degraded(let reason):
logger?.warning("\(serviceName): 성능 저하 - \(reason)")
case .unhealthy(let error):
logger?.error("\(serviceName): 비정상 - \(error)")
}
}
}
return results
}
}
테스팅 패턴
테스트 더블 주입
고급 모킹 패턴:
swift
// 테스트 더블 프로토콜
protocol TestDouble {
var callHistory: [String] { get set }
func reset()
}
// 모든 상호작용을 기록하는 스파이 서비스
class SpyUserService: UserService, TestDouble {
var callHistory: [String] = []
var users: [String: User] = [:]
func reset() {
callHistory.removeAll()
users.removeAll()
}
func getUser(id: String) async throws -> User? {
callHistory.append("getUser(id: \(id))")
return users[id]
}
func createUser(_ user: User) async throws {
callHistory.append("createUser(\(user.name))")
users[user.id] = user
}
func updateUser(_ user: User) async throws {
callHistory.append("updateUser(\(user.name))")
users[user.id] = user
}
}
// 미리 정의된 응답을 가진 스텁 서비스
class StubPaymentService: PaymentService, TestDouble {
var callHistory: [String] = []
var shouldSucceed = true
var predefinedResults: [String: PaymentResult] = [:]
func reset() {
callHistory.removeAll()
shouldSucceed = true
predefinedResults.removeAll()
}
func processPayment(amount: Decimal, method: PaymentMethod) async throws -> PaymentResult {
callHistory.append("processPayment(amount: \(amount), method: \(method))")
let key = "\(amount)_\(method.rawValue)"
if let predefinedResult = predefinedResults[key] {
return predefinedResult
}
if shouldSucceed {
return PaymentResult(success: true, transactionId: "test_\(UUID().uuidString)")
} else {
throw PaymentError.processingFailed
}
}
}
// 테스트 더블을 사용하는 테스트 케이스
class PaymentProcessorTests: XCTestCase {
var spyUserService: SpyUserService!
var stubPaymentService: StubPaymentService!
var processor: PaymentProcessor!
override func setUp() async throws {
await super.setUp()
// 테스트 더블 생성
spyUserService = SpyUserService()
stubPaymentService = StubPaymentService()
// 테스트 더블 등록
await WeaveDI.Container.bootstrap { container in
container.register(UserService.self, instance: spyUserService)
container.register(PaymentService.self, instance: stubPaymentService)
}
processor = PaymentProcessor()
}
override func tearDown() async throws {
await super.tearDown()
// 테스트 더블 리셋
spyUserService.reset()
stubPaymentService.reset()
}
func testSuccessfulPayment() async throws {
// Given
let user = User(id: "test", name: "테스트 사용자", email: "test@example.com")
spyUserService.users[user.id] = user
stubPaymentService.shouldSucceed = true
// When
let result = try await processor.processUserPayment(
userId: user.id,
amount: 99.99,
method: .creditCard
)
// Then
XCTAssertTrue(result.success)
// 상호작용 검증
XCTAssertEqual(spyUserService.callHistory, ["getUser(id: test)"])
XCTAssertEqual(stubPaymentService.callHistory.count, 1)
XCTAssertTrue(stubPaymentService.callHistory[0].contains("processPayment"))
}
}
통합 테스트 컨테이너
통합 테스트를 위한 전문 컨테이너를 생성합니다:
swift
class IntegrationTestContainer {
static func configure() async {
await WeaveDI.Container.bootstrap { container in
// 통합 테스트를 위한 실제 구현 사용
container.register(NetworkService.self) {
URLSessionNetworkService(baseURL: "https://test-api.example.com")
}
// 테스트를 위한 인메모리 데이터베이스 사용
container.register(DatabaseService.self) {
InMemoryDatabaseService()
}
// 데이터를 전송하지 않는 테스트 분석 사용
container.register(AnalyticsService.self) {
TestAnalyticsService()
}
// 실제 비즈니스 로직 서비스 사용
container.register(UserService.self) {
UserServiceImpl()
}
}
}
}
class IntegrationTests: XCTestCase {
override func setUp() async throws {
await super.setUp()
await IntegrationTestContainer.configure()
}
func testCompleteUserFlow() async throws {
// 실제 서비스로 완전한 플로우 테스트
let userService = UnifiedDI.resolve(UserService.self)!
// 사용자 생성
let newUser = User(id: "integration_test", name: "통합 테스트", email: "test@integration.com")
try await userService.createUser(newUser)
// 사용자 생성 확인
let retrievedUser = try await userService.getUser(id: newUser.id)
XCTAssertEqual(retrievedUser?.name, newUser.name)
// 사용자 업데이트
var updatedUser = newUser
updatedUser.name = "업데이트된 이름"
try await userService.updateUser(updatedUser)
// 업데이트 확인
let finalUser = try await userService.getUser(id: newUser.id)
XCTAssertEqual(finalUser?.name, "업데이트된 이름")
}
}
멀티 모듈 아키텍처
기능 모듈 패턴
대형 애플리케이션을 기능 모듈로 조직화합니다:
swift
// 기본 기능 모듈 프로토콜
protocol FeatureModule {
static var name: String { get }
static func configure() async
static func dependencies() -> [String] // 모듈 의존성
}
// 사용자 기능 모듈
struct UserFeatureModule: FeatureModule {
static let name = "UserFeature"
static func dependencies() -> [String] {
return ["CoreFeature", "NetworkFeature"]
}
static func configure() async {
await WeaveDI.Container.bootstrap { container in
container.register(UserRepository.self) {
CoreDataUserRepository()
}
container.register(UserService.self) {
UserServiceImpl()
}
container.register(UserViewController.self) {
UserViewController()
}
}
}
}
// 주문 기능 모듈
struct OrderFeatureModule: FeatureModule {
static let name = "OrderFeature"
static func dependencies() -> [String] {
return ["UserFeature", "PaymentFeature"]
}
static func configure() async {
await WeaveDI.Container.bootstrap { container in
container.register(OrderRepository.self) {
APIOrderRepository()
}
container.register(OrderService.self) {
OrderServiceImpl()
}
}
}
}
// 의존성 순서 로딩을 위한 모듈 매니저
class ModuleManager {
private static var configuredModules: Set<String> = []
static func configureModules(_ modules: [FeatureModule.Type]) async {
let sortedModules = topologicalSort(modules)
for moduleType in sortedModules {
if !configuredModules.contains(moduleType.name) {
print("🔧 모듈 구성 중: \(moduleType.name)")
await moduleType.configure()
configuredModules.insert(moduleType.name)
print("✅ 모듈 구성 완료: \(moduleType.name)")
}
}
}
private static func topologicalSort(_ modules: [FeatureModule.Type]) -> [FeatureModule.Type] {
// 의존성에 기반한 위상 정렬 구현
var sorted: [FeatureModule.Type] = []
var visited: Set<String> = []
var visiting: Set<String> = []
func visit(_ moduleType: FeatureModule.Type) {
let moduleName = moduleType.name
if visiting.contains(moduleName) {
fatalError("\(moduleName)과 관련된 순환 의존성이 감지되었습니다")
}
if visited.contains(moduleName) {
return
}
visiting.insert(moduleName)
// 의존성 먼저 방문
for dependencyName in moduleType.dependencies() {
if let dependencyModule = modules.first(where: { $0.name == dependencyName }) {
visit(dependencyModule)
}
}
visiting.remove(moduleName)
visited.insert(moduleName)
sorted.append(moduleType)
}
for moduleType in modules {
visit(moduleType)
}
return sorted
}
}
// 앱에서 사용
@main
struct MyApp: App {
init() {
Task {
await ModuleManager.configureModules([
CoreFeatureModule.self,
NetworkFeatureModule.self,
UserFeatureModule.self,
PaymentFeatureModule.self,
OrderFeatureModule.self
])
print("🚀 모든 모듈이 성공적으로 구성되었습니다")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
프로덕션 배포
환경 구성
다양한 환경을 구성합니다:
swift
enum Environment {
case development
case staging
case production
static var current: Environment {
#if DEBUG
return .development
#elseif STAGING
return .staging
#else
return .production
#endif
}
}
class ProductionConfiguration {
static func configure() async {
switch Environment.current {
case .development:
await configureDevelopment()
case .staging:
await configureStaging()
case .production:
await configureProduction()
}
}
private static func configureDevelopment() async {
await WeaveDI.Container.bootstrap { container in
// 개발 서비스
container.register(APIService.self) {
MockAPIService()
}
container.register(Logger.self) {
ConsoleLogger(level: .debug)
}
container.register(AnalyticsService.self) {
ConsoleAnalyticsService()
}
}
// 개발 최적화 활성화
UnifiedDI.setLogLevel(.all)
}
private static func configureStaging() async {
await WeaveDI.Container.bootstrap { container in
// 스테이징 서비스
container.register(APIService.self) {
HTTPAPIService(baseURL: "https://staging-api.example.com")
}
container.register(Logger.self) {
FileLogger(level: .info, file: "staging.log")
}
container.register(AnalyticsService.self) {
TestAnalyticsService()
}
}
// 스테이징 최적화
UnifiedDI.setLogLevel(.warnings)
}
private static func configureProduction() async {
await WeaveDI.Container.bootstrap { container in
// 프로덕션 서비스
container.register(APIService.self) {
HTTPAPIService(baseURL: "https://api.example.com")
}
container.register(Logger.self) {
RemoteLogger(level: .error, endpoint: "https://logs.example.com")
}
container.register(AnalyticsService.self) {
FirebaseAnalyticsService()
}
}
// 프로덕션 최적화
UnifiedRegistry.shared.enableOptimization()
UnifiedDI.setLogLevel(.errors)
}
}
성능 모니터링
프로덕션에서 DI 성능을 모니터링합니다:
swift
class ProductionMonitoring {
@Inject private var logger: Logger?
static func setupMonitoring() {
// 느린 의존성 해결 모니터링
UnifiedDI.onSlowResolution { serviceName, duration in
if duration > 0.01 { // 10ms 임계값
print("⚠️ 느린 DI 해결: \(serviceName)가 \(duration * 1000)ms 소요")
}
}
// 메모리 사용량 모니터링
UnifiedDI.onMemoryPressure {
print("💾 DI 컨테이너 메모리 압박 상태")
}
// 오류 비율 모니터링
UnifiedDI.onResolutionError { serviceName, error in
print("❌ DI 해결 실패: \(serviceName) - \(error)")
}
}
}
이 종합적인 가이드는 프로덕션 애플리케이션에서 WeaveDI를 사용하기 위한 고급 패턴과 모범 사례를 제공합니다. 이러한 패턴은 정교한 의존성 주입 요구사항을 가진 확장 가능하고 유지보수 가능하며 성능이 뛰어난 애플리케이션을 구축하는 데 도움이 됩니다.