세션설명 : Koin으로 DI를 하고 AAC, Rx를 조합한 MVVM 패턴에 대하여 이야기 하고자 합니다. 어째서 효율적인지 Testable한지를 함께 고민 해 보고 더 나은 구조를 향한 이야기를 나누어 보았으면 합니다.
목차 :
1. 발표 동기
2. AAC를 소개(나온 배경등)
3. MVVM을 소개(livedata,ViewModel과 엮으며)
4. Rx를 소개 (KickStarter를 참고하여)
5. Koin을 소개(DI를 간단하게 설명하며)
6. 위의 모든 것을 조합 한 예제 설명
7. 효율적인 부분 고민이 되는 지점등 같이 이야기해볼만 한 부분들을 화두로 던져 소통
8. 테스트코드 또한 위와같이 진행
9. 마무리.
Android 코드 아키텍쳐
MVC
MVP
MVVM
with Clean Architecture
with RFP(rxJava2)
with AAC, Koin
with Spek
MVI
VIPER
etc...
MVVM
with CleanArchitecture
with RFP (rxJava2)
with AAC, Koin
with Spek
with에 적힌 라이브러리, 프레임워크는 간단히 설명하기로 함.
why MVVM?
MVP로 충분히 잘 구현해도 서비스 운영을 하면서 아래의 문제들이 생김.
요구사항, 비즈니스 로직 늘어남 -> 코드량, 복잡성 높아짐 -> 유지보수성, 테스트 용이성 하락
what MVVM?
(MVVM에 대한 개인적인 견해이므로 다른 의견이 있을 수도 있음.)
Model, View, ViewModel로 구성되어 있는 패턴
ViewModel은 View의 추상화
View와 ViewModel은 n:m 의 관계
View는 ViewModel에 bindable함
ViewModel은 View의 추상화
ViewModel은 View의 상태와 행동이 추상화 된 것
ViewModel은 View의 input과 output이 명시되어 있는 인터페이스 (같은 input 엔 항상 같은 output 이라 테스트에 용이함)
output은 View의 상태와 Rooute로 나뉨
View와 ViewModel은 n:m 의 관계
하나의 View가 여러 ViewModel에 조합 가능
하나의 ViewModel이 여러 View에 적용 가능
재 사용성이 용이함.
View는 ViewModel에 bindable함
사용자 행동에 의해 입력 받았을 때 (View -> ViewModel)
사용자 행동에 따른 View의 상태를 변경시켜야 할 때 (ViewModel -> View)
모든 로직은 binding되는 시점에 결정됨.
장점
View에 대한 의존성이 제거되어 효과적으로 역할과 책임을 나눌 수 있음.
ViewModel 단독으로 테스트가 가능함으로 테스트 용이성 증가.
binding 되는 시점에 input 대비 output을 산출하는 로직이 정해지기 때문에 개발자가 상태 관리해야 하는 위험 줄여줌.
Databinding을 통해 보일러 플레이트 코드 줄일 수 있음.
How MVVM?
발표자가 생각한 android에서 MVVM
LiveData는 라이프싸이클 처리 등의 문제로 사용.
ViewModel 윗단 Model단부터는 Clean Architecher가 도입함.
Rx로 비동기 처리함.
Activity/Fragment/something은 View의 상태 변화를 제외한 Router역할+ViewModel과 View를 binding하는 역할 수행함.
beforeEachTest {
ArchTaskExecutor.getInstance().setDelegate(object:TaskExecutor() {
override fun executeOnDiskIO(runnable:Runnable) {
runnable.run()
}
override fun isMainThread():Boolean {
return true
}
override fun postToMainThread(runnable:Runnable) {
runnable.run()
}
})
}
afterEachTest {
ArchTaskExecutor.getInstance().setDelegate(null)
}
MainViewModelSpec
object MainViewModelSpec :KoinSpek({
beforeEachTest { ... }
afterEachTest { ... }
lateinit var userName:String
val viewModel:MainViewModel by inject { parametersOf(userName) }
val getUserData:GetUserData by inject()
Feature("MainViewModel spec") {
Scenario("유저가 화면에 들어오면 검색한 유저의 프로필,저장소 데이터가 정상적으로 보여야 한다") {
Given("검색하려는 유저의 이름은 omjoonkim이다"){
userName = "omjoonkim"
}
Then("화면에 검색한 유저의 데이터가 정상적으로 나타난다") {
assertEquals(
getUserData.get(userName).blockingGet(),
viewModel.output.refreshListData().value
)
}
}
Scenario("유저 프로필을 클릭하면 유저의 프로필 화면으로 이동되어야 한다") {
When("프로필을 클릭 했을 때") {
viewModel.input.clickUser(
viewModel.output.refreshListData().value?.first
?: throw IllegalStateException()
)
}
Then("해당 유저의 프로필 화면으로 이동 된다") {
assertEquals(
viewModel.output.refreshListData().value?.first?.name
?: throw IllegalStateException(),
viewModel.output.goProfileActivity().value
)
}
}
Scenario("홈버튼을 클릭하면 화면이 정상적으로 종료되어야 한다.") {
When("홈버튼을 클릭 했을 때") {
viewModel.input.clickHomeButton()
}
Then("화면이 정상적으로 종료 된다.") {
assertEquals(
Unit,
viewModel.output.finish().value
)
}
}
}
})
DI for Test
test_modules
val testModule = module {
single(override=true) {
TestSchedulerProvider() asSchedulersProvider
}
single(override=true) {
TestDummyGithubBrowserService() asGithubBrowserService
}
}
val test_module =listOf(myModule, testModule)
Spek을 이용한 테스트 코드 작성
Feature
Scenario
Given
When
Then
More... + TMI
Dagger2 vs Koin
Heavy vs light
Dependency Injection vs ServiceLocator
CompileTime vs RunTime
Spek과 Koin의 호환성
Spek + Koin을 사용하려면 추가적으로 작업해야 하는 코드들 있음.
(Spek 구동방식과 Koin을 사용하는 방식이 서로 충돌되기 때문)
개선의 여지 + 아쉬운 점
Databinding이 kotlin에 100% 호환되지 않음 (람다 함수를 xml에서 값으로 지정해줄 수 없음)
Router
Presentation module 분리
Q & A
Q : MVVM 적용시 피곤한 점은?
A : 기존 뱅크샐러드의 경우 MVP 베이스임. 그래서 MVP에 Koin 추가나 코드 수정해야 했음.
팀원 협의에 대한 부분. Rx나 MVVM 러닝커브가 높은 점.
Q : 클린 아키텍쳐면 presentation, domain, entity, data 4가지 레이어 정의를 보면 presentation이 ui에 해당되는데 왜 presentation에 data와 같은 레벨로 하고 ui는 remote로 한건지?
A : ui가 remote라기 보다는 ui와 remote는 서버에서 데이터를 받아오는가장 바깥 레이어로 생각하면 됨.
Q : Koin의 runtime error가 compile time error 대비 장점은?
A : Dagger는 적용하기 어려웠음. 선수지식과 많은 코드가 필요했음. Compile error시 어디서 에러가 나는지 찾아야 했음.
Koin을 하면서 코드 스타일로 의존성 주입을 하면서 에러 찾기가 편함.
복잡한 서비스의 경우는 Dagger, 심플한 경우 Koin이 괜찮아 보였음.
Q : TDD가 아닌 BDD를 한 이유는?
A : TDD와 BDD의 가장 큰 차이는 구현 대신 행동 즉 행위를 테스트하는 것임. Functional Programming 관점에서 행동 기반이 맞다고 생각했음.
Q : Model의 수에 따라 Mapper의 수도 지속적으로 증가하는데, Mapper가 특별한 경우가 아닐 땐 1:1로 매핑되고 Koin으로 바인딩 해주는 것들이 보일러 플레이트로 느껴짐. 이런 코드를 좀 더 줄일만한 아이디어는?
A : 진정한 클린 아키텍쳐라면 레이어 별로 테스트가 가능해야 하고, Mapper도 테스트되어야 함. 똑같은 input이 들어갈 경우 똑같은 entity를 내보내주는 부분이 확인 되어야 함. 그것 때문에 인터페이스를 만들고 개선의 여지가 있다고 봄. 모델에 단순히 컨버팅해주는 코드들이 많아 필요 없다거나 확장함수로 처리하는 경우도 있지만 제대로 하겠다면 매퍼를 만드는게 필요하다 봄.
Q : 개발 일정을 단축시킨다거나 안드로이드 인력을 줄였다거나 하는 경우는?
A : input과 output을 먼저 정의하고 ViewModel을 View를 짜고 그 다음에 binding 코드를 짜는 방식이 사고의 흐름에 맞게 개발한다고 봄. 그래서 빠르게 개발한다고 보지만, 선수지식과 경험이 필요한 부분이라 경우에 따라 다를 것으로 봄.
Q : input & output 등을 인터페이스로 구현하고 ViewState 모델링한 부분은 MVVM과 별개로 적용한 것인지?
A : 고민했던 부분. output 자체를 state로 나누는 것과 아예 router명을 명시해서 나누는 것도 고민했음. 개념적으로 같고 코드상의 차이로 보고, 모든 결과에 state라는 것과 그 외에는 router 코드로 판단하기로 함. MVVM과 별개로 봐도 될 것으로 봄.
완벽한 아키텍처, 완벽한 패턴 그리고 정답은 없다고 봄.
각각의 프레임워크, 플랫폼, 서비스 그리고 어떤 사람들이 어떤 컨벤션을 가지고 개발하는지에 따라 달라진다고 봄.
어떻게 할지에 고민보다는 왜 이렇게 하는지에 대한 이유만 명확하게 생각하고 논의하면 될 것으로 봄.
세션설명 : 변화의 시대 : 안드로이드 앱 어떻게 개발할 것인가? 안드로이드는 끊임없는 OS 버전 뿐만아니라 개발 언어, 구조, GUI등 많은 부분에서 다양항 변화가 시도되고 있습니다. 많은 방법론과 라이브러리가 제공되다보니 어떤 전략과 기준으로 개발해야하는지 혼돈스러울 때가 많습니다. 네이버 앱의 개편에 적용한 기술 사례와 방법론을 통해서 효율적인 앱 개발애 대해서 얘기하고자 합니다.
목차 :
1. 안드로이드 앱의 구조
2. 함수형과 객체형: 함수형과 객체형 개발 어떻게 적용할 것이가?
3. GUI 기반 구조: Activity 구조,Fragment 구조, View 기반의 구조의 장단점은 무엇인가? 어떤 구조를 택할 것인가?
4. 데이터 모델: 데이터 모델이 필요한가? MVC, MVP, MVVM 이 정말 필요한가?
5. 프로세스 와 쓰레드 어떤 모델로 가져갈 것인가? : Service, AsyncTask, Thread, JobManager, WorkManager, Coroutine ,Loader 너무 많은데
무엇이 정답인가? 멀티 프로세스 모델 어떻게 설계할 것인가?
6. 통계와 설정: 어떤 정보를 모으고 최적할 것인가?