๋ชจ๋ฐ”์ผ์—์„œ์˜ Dependency Injection - Part2.

chihacker
  • #kotlin
  • #dagger
  • #hilt
  • #android
  • #ios
  • #swift
  • #swinject

๋“ค์–ด๊ฐ€๋ฉฐ

์ง€๋‚œ ํฌ์ŠคํŠธ์—์„œ DI ๊ฐ€ ๋ฌด์—‡์ธ์ง€์— ๋Œ€ํ•ด์„œ ๊ฐ„๋žตํ•˜๊ฒŒ ์„ค๋ช…ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ Part2 ํฌ์ŠคํŠธ์—์„œ๋Š” ๋ชจ๋ฐ”์ผํŒ€์—์„œ๋Š” DI ๋ฅผ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ ๊ฐ„๋žตํ•˜๊ฒŒ ์†Œ๊ฐœ๋“œ๋ฆฌ๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ํ™œ์šฉ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•˜๊ธฐ ์•ž์„œ ๋จผ์ € ๊ฐ ํ”Œ๋žซํผ ๋ณ„๋กœ ํ™œ์šฉํ•˜๊ณ  ์žˆ๋Š” DI Framework ์„ ๊ฐ„๋žตํžˆ ์„ค๋ช…๋“œ๋ฆฌ๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋ฐ”์ผํŒ€์—์„œ ํ™œ์šฉํ•˜๋Š” DI Framework

Hilt in Android

์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ๋Š” Hilt ๋ผ๋Š” DI framework์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Hilt ๋Š” Android์—์„œ ์ข…์† ํ•ญ๋ชฉ์„ ์‚ฝ์ž…ํ•˜๊ธฐ ์œ„ํ•œ Jetpack์˜ ๊ถŒ์žฅ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. Dagger ๊ธฐ๋ฐ˜์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋ฉฐ Dagger์˜ ๋‹ค์–‘ํ•˜๊ณ  ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ๋“ค์„ Android ํ™˜๊ฒฝ์— ๋งž์ถฐ ์†์‰ฝ๊ฒŒ ํ™œ์šฉ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด์ง„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž…๋‹ˆ๋‹ค.

Hilt ๋Š” ๋ณต์žกํ•œ ์„ค์ •์ด๋‚˜ ์ฝ”๋“œ ๊ตฌํ˜„ ์—†์ด Annotation๊ธฐ๋ฐ˜์œผ๋กœ Android ์˜ ๋ผ์ดํ”„์‚ฌ์ดํด์— ๋งž๋Š” ์ข…์†์„ฑ ์ฃผ์ž…์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด

@HiltAndroidApp  
class ExampleApplication : Application() { 
    //... 
}

@AndroidEntryPoint  
class ExampleActivity : AppCompatActivity() { 
    //... 
}

์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์ด HiltAndroidApp, AndroidEntryPoint Annotation ์„ ์ง€์ •ํ•จ์œผ๋กœ์„œ ์‰ฝ๊ฒŒ Android ์ปดํฌ๋„ŒํŠธ์˜ ๋ผ์ดํ”„์‚ฌ์ดํด์— ๋งž์ถฐ ์ข…์†์„ฑ์„ ์ฃผ์ž…์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

์ด์™ธ์—๋„ HiltViewModel, ViewModelScoped ๋“ฑ Android ์— ๋งž๋Š” ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ๋“ค์„ ์ œ๊ณตํ•ด์ค๋‹ˆ๋‹ค. ๋ฟ๋งŒ์•„๋‹ˆ๋ผ ์ปดํŒŒ์ผ ์‹œ Code Generation์„ ํ†ตํ•ด DI ๋ฅผ ์ง€์›ํ•ด์ฃผ๋Š” ํ˜•ํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋Ÿฐํƒ€์ž„์˜ค๋ฅ˜๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ์‚ฌ์ „์— DI ์ด์Šˆ๋ฅผ ํ•ด๊ฒฐ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Swinject in iOS

iOS์—์„œ๋Š” Swinject์ด๋ผ๋Š” DI framework์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Swinject์€ a lightweight dependency injectionย framework ๋กœ์„œ iOS ๊ฐœ๋ฐœ์—์„œ ์‰ฝ๊ฒŒ DI ๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.

Swinject์€ Container ๋ฅผ ํ†ตํ•ด ์ฃผ์ž…ํ•  ๊ฐ์ฒด๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด

let container = Container()
container.register(Animal.self) { _ in Cat(name: "Mimi") }
container.register(Person.self) { r in
    PetOwner(pet: r.resolve(Animal.self)!)
}

์œ„์™€ ๊ฐ™์ด Container ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์ฃผ์ž…ํ•  ๊ฐ์ฒด๋ฅผ ์„ค์ •ํ•˜๊ณ 

์ฃผ์ž…๋Œ€์ƒ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌํ˜„์ฒด๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

let person = container.resolve(Person.self)!
person.play() // prints "I'm playing with Mimi."

Android์™€ ๋‹ฌ๋ฆฌ iOS์—๋Š” ๊ถŒ์žฅํ•˜๋Š” DI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ €ํฌ๋Š” ๋น„๊ต์  ๊ฐ€๋ณ๊ณ  ์‚ฌ์šฉ์ด ์‰ฌ์šด Swinject์„ ์ด์šฉํ•˜์—ฌ DI๋ฅผ ์ ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

DI ํ™œ์šฉ

Hilt๋‚˜ Swinject์˜ ์‚ฌ์šฉ๋ฐฉ๋ฒ•์€ ๋”ฐ๋กœ ์„ค๋ช…ํ•˜์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ณต์‹๋ฌธ์„œ์—๋„ ์‚ฌ์šฉ๋ฐฉ๋ฒ•์€ ์ž˜ ๋‚˜์™€ ์žˆ์œผ๋ฉฐ ๊ตฌ๊ธ€๋ง์„ ํ†ตํ•ด์„œ๋„ ์‚ฌ์šฉ๋ฒ•์€ ์‰ฝ๊ฒŒ ์ตํž ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” ์ €ํฌํŒ€์—์„œ ์–ด๋–ป๊ฒŒ DI๋ฅผ ํ™œ์šฉํ•˜๋Š”์ง€์— ๋Œ€ํ•ด์„œ ์†Œ๊ฐœ๋“œ๋ฆฌ๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋ฐ”์ผํŒ€์—์„œ๋Š” Android, iOS ๋‘ ํ”Œ๋žซํผ ๋ชจ๋‘ UI, Domain, Data ๋ ˆ์ด์–ด๋กœ ๊ตฌ์„ฑ๋œ ์•„ํ‚คํ…์ฒ˜๋กœ ์„ค๊ณ„ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. Domain Layer์—์„œ๋Š” ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ UseCase๋กœ ๊ด€๋ฆฌํ•˜๋ฉฐ Repository Interface ๋ฅผ ์ •์˜ํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. UI ๋ ˆ์ด์–ด์—์„œ๋Š” Domain๋ ˆ์ด์–ด์—์„œ์˜ UseCase๋ฅผ ํ™œ์šฉํ•˜์—ฌ View์™€ ๊ด€๋ จ๋œ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, Data๋ ˆ์ด์–ด์—์„œ๋Š” API I/O, DB I/O, Preference(UserDefault) ๋“ฑ ๋ฐ์ดํ„ฐ ์†ก์ˆ˜์‹  ๊ด€๋ จ๋œ ์—ญํ• ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ๋‹ด๋‹น์„ Domain๋ ˆ์ด์–ด์— ๋ช…ํ™•ํ•˜๊ฒŒ ์œ„์ž„ํ•จ์œผ๋กœ์„œ UI์™€ Data์ชฝ ๋กœ์ง๊ฐ„์˜ coupling์„ ์ค„์ด๊ณ  ์„œ๋กœ ์˜ํ–ฅ์„ ์ค„์—ฌ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์šฉ์ดํ•˜๊ณ  ์žฌ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ๋กœ ์„ค๊ณ„ํ•˜๊ณ ์ž ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Domain๋ ˆ์ด์–ด๊ฐ€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‹ค๋ฅธ ๋ ˆ์ด์–ด๊ฐ„์˜ Loose Coupling์„ ์œ„ํ•ด interface๋งŒ ์ œ๊ณตํ•˜๊ณ  ์‹ค์ œ ๊ตฌํ˜„์€ ๊ฐ ๋‹ด๋‹น ๋ ˆ์ด์–ด๊ฐ€ ๋‹ด๋‹นํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋•Œ Domain ๋ ˆ์ด์–ด์—์„œ๋Š” IoC๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Domain๋ ˆ์ด์–ด์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ UserRepository๋ผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

class LoginUseCase(private val userRepository) {
	fun execute(): User {
		userRepository.signIn()
		userRepository.getProfile()
	}
}

interface UserRepository {
	fun signIn()
	fun getProfile()
}

Data๋ ˆ์ด์–ด์—์„œ๋Š” Domain๋ ˆ์ด์–ด์˜ ์˜์กด์„ฑ์„ ๊ฐ–๊ณ  UserRepository๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

class UserRepositoryImpl: UserRepository {
	fun signIn() {
		//do something
	}
	fun getProfile() {
		//do something
	}
}

์ด๋•Œ Domain๋ ˆ์ด์–ด์—์„œ ์˜์กด์„ฑ ์—ญ์ „์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. Domain๋ ˆ์ด์–ด๋Š” Data๋ ˆ์ด์–ด์˜ ๊ตฌํ˜„์ฒด๋ฅผ ๋ชจ๋ฅธ ์ƒํƒœ๋กœ ๊ฐ์ฒด๋ฅผ ์ฃผ์ž…๋ฐ›์•„์•ผ Domain๋ ˆ์ด์–ด์˜ decoupling์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ DI๊ฐ€ ํ•„์š”ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ๋กœ ๋“  Android ์ฝ”๋“œ์—์„œ๋Š” Hilt๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์‰ฝ๊ฒŒ DI๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

class LoginUseCase(@Inject private val userRepository) {
	fun execute(): User {
		userRepository.getProfile()
	}
}

DI Framework๋ฅผ ํ™œ์šฉํ•˜๋ฉด Dependency Tree ๋ฐ Scope์„ ์‰ฝ๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Hilt์˜ ๊ฒฝ์šฐ ์ด๋ฏธ Android์—์„œ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” scope๋“ค์„ ์ œ๊ณตํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์‰ฝ๊ฒŒ Scope๋ฅผ ์„ค์ • ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์‹ค์ƒ ํ•„์ˆ˜ ์ ์ธ scope๋“ค์ธ Activity, Fragment, ViewModel๋“ฑ์„ ๋ฏธ๋ฆฌ ์ œ๊ณตํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์‰ฝ๊ฒŒ ์˜์กด์„ฑ ์ฃผ์ž…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. iOS์˜ ๊ฒฝ์šฐ Swinject์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š” ObjectScope ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์—ฌ ์‰ฝ๊ฒŒ ์›ํ•˜๋Š” Scope์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ €ํฌ๋Š” ViewModel, ViewController ๋“ฑ ์•„ํ‚คํ…์ฒ˜ ๋ฐ ํ”Œ๋žซํผ ์ƒ Scope ๊ด€๋ฆฌํ•ด์•ผํ•˜๋Š” ๊ฐ์ฒด๋“ค์— ObjectScope์„ ํ™œ์šฉํ•˜์—ฌ Scope์„ ์ง€์ •, ์˜์กด์„ฑ ์ฃผ์ž…์„ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์น˜๋ฉฐ

์—ฌ๊ธฐ๊นŒ์ง€ ๋ชจ๋ฐ”์ผํŒ€์—์„œ DI๋ฅผ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ ๊ธฐ๋ณธ์ ์ธ ๋ถ€๋ถ„์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์†Œ๊ฐœ๋“œ๋ ธ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ part3์—์„œ๋Š” ๊ธฐ๋ณธ์ ์ธ ์˜์กด์„ฑ ์ฃผ์ž…์„ ๋„˜์–ด ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๊ตฌํ˜„์„ ์œ„ํ•œ ์˜์กด์„ฑ ์ฃผ์ž…์„ ์–ด๋–ค ์‹์œผ๋กœ DI Framework์„ ํ™œ์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ๋Š”์ง€, Custom Scope , Property Wrapper, InjectionMap๋“ฑ ๋‹ค์–‘ํ•˜๊ณ  ๊ธฐ๋Šฅ๋“ค์„ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ ์ข€ ๋” ์ƒ์„ธํžˆ ์„ค๋ช…๋“œ๋ฆฌ๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

Art Changes Life

๋…ธ๋จธ์Šค์™€ ํ•จ๊ป˜ ์—”ํ„ฐํ…Œํฌ ์‚ฐ์—…์„ ํ˜์‹ ํ•ด๋‚˜๊ฐˆ ๋ฉค๋ฒ„๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ฑ„์šฉ ์ค‘์ธ ๊ณต๊ณ  ๋ณด๊ธฐ