SlideShare une entreprise Scribd logo
1  sur  105
Télécharger pour lire hors ligne
Rainist | 김범준
예제에서는알려주지않는Model이야기
Rainist
!🙅
M
MVC
MVP
MVVM
MVI
Metc..
MODEL
MODEL
역할 분리
모델(model)이란 어떠한 동작을 수행하는 코드를 말한다. 표시 형식
에 의존하지 않는다. 다시 말해, 사용자에게 어떻게 보일지에 대해 신
경쓰지 않아도 된다. 모델은 순수하게 public 함수로만 이루어진다.
몇몇 함수들은 사용자의 질의(query)에 대해 상태 정보를 제공하고
나머지 함수들은 상태를 수정한다.
DefinitionofWikipedia(MVC)
모델(model)이란 어떠한 동작을 수행하는 코드를 말한다. 표시 형식
에 의존하지 않는다. 다시 말해, 사용자에게 어떻게 보일지에 대해 신
경쓰지 않아도 된다. 모델은 순수하게 public 함수로만 이루어진다.
몇몇 함수들은 사용자의 질의(query)에 대해 상태 정보를 제공하고
나머지 함수들은 상태를 수정한다.
DefinitionofWikipedia(MVC)
😐
간단하게..
데이터베이스, API를 통해 데이터를 불러오고 저장하는 역할.
서비스의 비지니스 로직(도메인)을 담당.
간단하게..
데이터베이스, API를 통해 데이터를 불러오고 저장하는 역할.
서비스의 비지니스 로직(도메인)을 담당.
간단하게..
데이터베이스, API를 통해 데이터를 불러오고 저장하는 역할.
서비스의 비지니스 로직(도메인)을 담당.
그래서 오늘 하고싶은 이야기는
Model의 역할(책임)에 따른 분리 과정
Model의 역할(책임)에 따른 분리 과정
Model
Model의 역할(책임)에 따른 분리 과정
Model
Domain Data Source
김신입의 좌충우돌 개발기(Review)
진행방식
코드 작성 -> Review -> 리뷰 반영.
이름 : 김신입
경력 : 신입
부연 설명 : 신입
드디어 원하던 회사에 입사하게된 김신입.
들어온지 얼마 안되어 피쳐를 맡게 된다.
서비스의 코드 구조는 MVP이며 김신입도 이에대한 경험이 있어 자신만만이다.
즐겁게 개발을 하는 신입씨. 그렇게 리뷰의 시간이 다가오는데….
Profile
ModelView Presenter
Early
View Presenter
Result
Data Source
Model
View Presenter
Result
Domain Data Source
Model
목차
#1. Repository Pattern을 사용하라!
#2. Business Logic을 분리하라!
#3. Exception handling
Repo : https://github.com/omjoonkim/GitHubBrowserApp
#1
#1. Repository Pattern을 사용하라!
#1
#1. Repository Pattern을 사용하라!
branch : model_v1
#1 Repository Pattern을 사용하라! - 예제
Github Api : Repo
#1#1 Repository Pattern을 사용하라! - 서버통신을 위한 Retrofit
object ApiClient {
val githubBrowserService: GithubService
init {
githubBrowserService = makeGithubBrowserService(…)
}
fun makeGithubBrowserService(): GithubService {…}
private fun makeGithubBrowserService(…): GithubService {…}
private fun makeOkHttpClient(…) :OkHttpClient{…}
private fun makeLoggingInterceptor(…): HttpLoggingInterceptor {…}
}
#1#1 Repository Pattern을 사용하라! - 서버통신을 위한 Retrofit
object ApiClient {
val githubBrowserService: GithubService
init {
githubBrowserService = makeGithubBrowserService(…)
}
fun makeGithubBrowserService(): GithubService {…}
private fun makeGithubBrowserService(…): GithubService {…}
private fun makeOkHttpClient(…) :OkHttpClient{…}
private fun makeLoggingInterceptor(…): HttpLoggingInterceptor {…}
}
#1#1 Repository Pattern을 사용하라! - MODEL
object RepoDetailModel {
fun getRepo(userName: String, id: String) :Repo =
ApiClient.githubBrowserService.getRepo(
userName,
id
)
}
#1#1 Repository Pattern을 사용하라! - MODEL
object RepoDetailModel {
fun getRepo(userName: String, id: String) :Repo =
ApiClient.githubBrowserService.getRepo(
userName,
id
)
}
#1#1 Repository Pattern을 사용하라! - PRESENTER
class RepoDetailPresenter(
view: RepoDetailView
) : BasePresenter<RepoDetailView>(view) {
fun onCreate(userName: String, repoName: String) {
val repo = RepoDetailModel.getRepo(userName, repoName)
view.setName(repo.name)
view.setDescription(repo.description ?: "")
view.setStarCount(repo.starCount)
}
}
#1#1 Repository Pattern을 사용하라! - PRESENTER
class RepoDetailPresenter(
view: RepoDetailView
) : BasePresenter<RepoDetailView>(view) {
fun onCreate(userName: String, repoName: String) {
val repo = RepoDetailModel.getRepo(userName, repoName)
view.setName(repo.name)
view.setDescription(repo.description ?: "")
view.setStarCount(repo.starCount)
}
}
#1 Repository Pattern을 사용하라! - Review
Review 👀🙏!
#1 Repository Pattern을 사용하라! - Review
Review 👀🙏!
Repository Pattern을 적용해보는게 어때요?
&🤷
#1 Repository Pattern을 사용하라! - Repository?
Repository?
👀
Data
#1 Repository Pattern을 사용하라! - Repository?
디자인 패턴중의 하나
데이터 불러오는 로직을 분리시켜 관리 하는것이 목적
추상화를 통해 테스트에 용이해진다.
하나의 Repository는 하나의 Domain을 담당한다.
Data
#1 Repository Pattern을 사용하라! - Repository?
디자인 패턴중의 하나
데이터 불러오는 로직을 분리시켜 관리 하는것이 목적
추상화를 통해 테스트에 용이해진다.
하나의 Repository는 하나의 Domain을 담당한다.
branch : model_v1_resolve
#1#1 Repository Pattern을 사용하라! - Repository
class RepoRepositoryImpl(
private val api : GithubService
) : RepoRepository {
override fun getRepo(userName: String, id: String): Repo =
api.getRepo(userName, id)
}
#1#1 Repository Pattern을 사용하라! - Repository
class RepoRepositoryImpl(
private val api : GithubService
) : RepoRepository {
override fun getRepo(userName: String, id: String): Repo =
api.getRepo(userName, id)
}
#1#1 Repository Pattern을 사용하라! - Repository
class RepoRepositoryImpl(
private val api : GithubService
) : RepoRepository {
override fun getRepo(userName: String, id: String): Repo =
api.getRepo(userName, id)
}
#1#1 Repository Pattern을 사용하라! - Repository
interface RepoRepository {
fun get(userName: String, id: String): Repo
}
#1#1 Repository Pattern을 사용하라! - PRESENTER
class RepoDetailPresenter(
view: RepoDetailView,
private val repoRepository: RepoRepository
) : BasePresenter<RepoDetailView>(view) {
fun onCreate(userName: String, repoName: String) {
val repo = repoRepository.getRepo(userName, repoName)
view.setName(repo.name)
view.setDescription(repo.description ?: "")
view.setStarCount(repo.starCount)
}
}
#1#1 Repository Pattern을 사용하라! - PRESENTER
class RepoDetailPresenter(
view: RepoDetailView,
private val repoRepository: RepoRepository
) : BasePresenter<RepoDetailView>(view) {
fun onCreate(userName: String, repoName: String) {
val repo = repoRepository.getRepo(userName, repoName)
view.setName(repo.name)
view.setDescription(repo.description ?: "")
view.setStarCount(repo.starCount)
}
}
#1 Repository Pattern을 사용하라! - Source
Source
데이터를 불러올 수 있는 모든 외부
Server(Http, socket), Local(db, cache)
현재 예제에서는 Http만 사용하기 때문에 나누지 않음.
#1 Repository Pattern을 사용하라! - 정리
Model -> Repository Pattern(추상화) 적용.
추상화를 통하여 테스트가 용이해지게 되었다.

(GitHubApiService, RepoRepository)
(🙆
데이터를 불러오고 처리하는 역할을 분리.
#2
#2. Business Logic을 분리하라!
#2
#2. Business Logic을 분리하라!
branch : model_v2
#2 Business Logic을 분리하라! - 지금까지의 줄거리
View Presenter
Data Source
Model
#2 Business Logic을 분리하라! - 기능 추가
Fork
#2 Business Logic을 분리하라! - 기능 추가
Fork
Business Logic 추가.
#2 Business Logic을 분리하라! - Business Logic
유저의 행동에 따라 서비스에서 보여주고자
하는 결과를 나타내기 위해 데이터를 가공하는 로직
#1#2 Business Logic을 분리하라! - ForkRepository
class ForkRepositoryImpl(
private val api: GithubService
) : ForkRepository {
override fun gets(userName: String, id: String): List<Fork> =
api.getForks(userName, id)
}
#1#2 Business Logic을 분리하라! - PRESENTER
class RepoDetailPresenter(
view: RepoDetailView,
private val repoRepository: RepoRepository,
private val forkRepository: ForkRepository
) : BasePresenter<RepoDetailView>(view) {
fun onCreate(userName: String, repoName: String) {
val repo = repoRepository.get(userName, repoName)
val forks = forkRepository.gets(userName, repoName)
view.setName(repo.name)
view.setDescription(repo.description ?: "")
view.setStarCount(repo.starCount)
view.refreshForks(forks)
}
}
#1#2 Business Logic을 분리하라! - PRESENTER
class RepoDetailPresenter(
view: RepoDetailView,
private val repoRepository: RepoRepository,
private val forkRepository: ForkRepository
) : BasePresenter<RepoDetailView>(view) {
fun onCreate(userName: String, repoName: String) {
val repo = repoRepository.get(userName, repoName)
val forks = forkRepository.gets(userName, repoName)
view.setName(repo.name)
view.setDescription(repo.description ?: "")
view.setStarCount(repo.starCount)
view.refreshForks(forks)
}
}
#1#2 Business Logic을 분리하라! - PRESENTER
class RepoDetailPresenter(
view: RepoDetailView,
private val repoRepository: RepoRepository,
private val forkRepository: ForkRepository
) : BasePresenter<RepoDetailView>(view) {
fun onCreate(userName: String, repoName: String) {
val repo = repoRepository.get(userName, repoName)
val forks = forkRepository.gets(userName, repoName)
view.setName(repo.name)
view.setDescription(repo.description ?: "")
view.setStarCount(repo.starCount)
view.refreshForks(forks)
}
}
Review 👀🙏!
#2 Business Logic을 분리하라! - Review!
Review 👀🙏!
#2 Business Logic을 분리하라! - Review!
Business Logic을 분리해보시겠어요?
&🤷
#1#2 Business Logic을 분리하라! - Business Logic
class RepoDetailPresenter(
view: RepoDetailView,
private val repoRepository: RepoRepository,
private val forkRepository: ForkRepository
) : BasePresenter<RepoDetailView>(view) {
fun onCreate(userName: String, repoName: String) {
val repo = repoRepository.get(userName, repoName)
val forks = forkRepository.gets(userName, repoName)
view.setName(repo.name)
view.setDescription(repo.description ?: "")
view.setStarCount(repo.starCount)
view.refreshForks(forks)
}
}
#1#2 Business Logic을 분리하라! - Business Logic
fun onCreate(userName: String, repoName: String) {
Single.zip(
repoRepository.get(userName, repoName),
forkRepository.gets(userName, repoName),
BiFunction { t1: RepoModel, t2: List<ForkModel> ->
t1 to t2
}
).subscribe({ (repo, forks) ->
view.setName(repo.name)
view.setDescription(repo.description ?: "")
view.setStarCount(repo.starCount)
view.refreshForks(forks)
}, ::printStackTrace)
}
#2 Business Logic을 분리하라! - Business Logic
Business Logic을 분리해야 하는 이유?
#2 Business Logic을 분리하라! - Business Logic
Business Logic을 Presentation Logic에서 분리해야 하는 이유?
복잡한 화면일수록 Presentation의 코드가 커지게 된다.
그럴수록 복잡성이 증대. 유지보수성 저하 됨.
Presentation의 역할을 분리해줄 필요성이 생김.
Presentation에서 Business Logic을 분리. Presentation에 집중하게한다.
물론 작은 서비스, 화면, Platform에 따라 분리하지 않아도 괜찮긴 함.
#2 Business Logic을 분리하라! - Business Logic
Business Logic을 Presentation Logic에서 어떻게 분리?
Service?
UseCase?
#2 Business Logic을 분리하라! - Service
Service Layer Pattern (n-tier, 3-layer에서 파생).
MSA, 작은 서비스에서 쓰일 때 유용함.
혹은 설계가 짜임새있게 되어 있어야 한다.
완벽하게 Presentation에서 Logic을 분리하기 어렵기 때문.
또한 유지보수에 있어 어려움이 있을 수 있다.
Business Logic의 집합소 개념. 재사용이 용이하다.
#2 Business Logic을 분리하라! - Service
Service
ServiceService
Service
Repository
Repository
Repository
#2 Business Logic을 분리하라! - UseCase
서로(UseCase)에게 독립적.
즉, Business Logic들 간의 의존성이 X
Service와 마찬가지로 설계가 중요.
유지보수, 변경등의 비용이 저렴하다.
하나의 유저행동에 대한 서비스(Application)의 Business Logic이 담겨있는 객체.
#2 Business Logic을 분리하라! - UseCase
UseCase
Repository
Repository
Repository
#2 Business Logic을 분리하라! - UseCase를 잘못 사용하는 경우.
UseCase를 Repository처럼 사용하는 경우.
UseCase를 Service처럼 사용하는 경우.
(강조) 보통의 경우에는 유저의 행동(Business Logic)과 1:1로 매칭된다. (강조)
#1#2 Business Logic을 분리하라! - UseCase Template
abstract class SingleUseCase<T, in Params>(
private val schedulersProvider: SchedulersProvider
) {
protected abstract fun buildUseCaseSingle(
params: Params
): Single<T>
fun get(params: Params) =
buildUseCaseSingle(params)
.subscribeOn(schedulersProvider.io())
.observeOn(schedulersProvider.ui())
}
#1#2 Business Logic을 분리하라! - UseCase Template
abstract class UseCase<T, in Params> {
protected abstract fun buildUseCase(params: Params): T
fun get(params: Params) = buildUseCase(params)
}
#1#2 Business Logic을 분리하라! - Business Logic
class GetRepoDetail(
private val repoRepository: RepoRepository,
private val forkRepository: ForkRepository,
schedulersProvider: SchedulersProvider
) : SingleUseCase<Pair<Repo, List<Fork>>, Pair<String, String>>(
schedulersProvider) {
override fun buildUseCaseSingle(params: Pair<String, String>)
: Single<Pair<Repo, List<Fork>>> =
Single.zip(
repoRepository.get(params.first, params.second),
forkRepository.gets(params.first, params.second),
BiFunction { t1: Repo, t2: List<Fork> ->
t1 to t2
}
)
}
#1#2 Business Logic을 분리하라! - Business Logic
class GetRepoDetail(
private val repoRepository: RepoRepository,
private val forkRepository: ForkRepository
) : UseCase<Pair<Repo, List<Fork>>, Pair<String, String>>() {
override fun buildUseCase(
params: Pair<String, String>
): Pair<Repo, List<Fork>> {
val repo = repoRepository.get(params.first, params.second)
val forks = forkRepository.gets(params.first, params.second)
return repo to forks
}
}
#1#2 Business Logic을 분리하라! - Business Logic
class GetRepoDetail(
private val repoRepository: RepoRepository,
private val forkRepository: ForkRepository
) : UseCase<Pair<Repo, List<Fork>>, Pair<String, String>>() {
override fun buildUseCase(
params: Pair<String, String>
): Pair<Repo, List<Fork>> {
val repo = repoRepository.get(params.first, params.second)
val forks = forkRepository.gets(params.first, params.second)
return repo to forks
}
}
#1#2 Business Logic을 분리하라! - Presentation
class RepoDetailPresenter(
view: RepoDetailView,
private val getRepoDetail: GetRepoDetail
) : BasePresenter<RepoDetailView>(view) {
fun onCreate(userName: String, repoName: String) {
val (repo, forks) = getRepoDetail.get(userName to repoName)
view.setName(repo.name)
view.setDescription(repo.description ?: "")
view.setStarCount(repo.starCount)
view.refreshForks(forks)
}
}
#1#2 Business Logic을 분리하라! - Presentation
class RepoDetailPresenter(
view: RepoDetailView,
private val getRepoDetail: GetRepoDetail
) : BasePresenter<RepoDetailView>(view) {
fun onCreate(userName: String, repoName: String) {
val (repo, forks) = getRepoDetail.get(userName to repoName)
view.setName(repo.name)
view.setDescription(repo.description ?: "")
view.setStarCount(repo.starCount)
view.refreshForks(forks)
}
}
#2 Business Logic을 분리하라! - 정리
Presentation에서 Business Logic을 분리.
(🙆
Domain시스템이 복잡해져도 유연하게 대처할 수 있게 되었다.
#3
#3. Exception handling
#3
#3. Exception handling
branch : model_v3
#3 Exception handling - 지금까지의 줄거리
View Presenter
Domain Data Source
Model
#3 Exception handling - 예제
HttpNetworkException
403 Forbidden Rate Limit
Toast
Api call
#1#3 Exception handling - Presenter
fun onCreate(userName: String, repoName: String) {
getRepoDetail.get(
userName to repoName
).subscribe({ (repo, forks) ->
…
}, ::handleException)
}
private fun handleException(throwable: Throwable) {
when (throwable) {
is HttpException -> {
if (throwable.code() == 403)
view.toastRateLimitError()
else view.toastNetworkError()
}
else -> view.toastUnexpectedError()
}
}
}
#1#3 Exception handling - Presenter
fun onCreate(userName: String, repoName: String) {
getRepoDetail.get(
userName to repoName
).subscribe({ (repo, forks) ->
…
}, ::handleException)
}
private fun handleException(throwable: Throwable) {
when (throwable) {
is HttpException -> {
if (throwable.code() == 403)
view.toastRateLimitError()
else view.toastNetworkError()
}
else -> view.toastUnexpectedError()
}
}
}
#3 Exception handling - Review
Review 👀🙏!
#3 Exception handling - Review
Review 👀🙏!
Business Logic이 분리되지 않았어요.
&🤷
#1#3 Exception handling - Business Logic
fun onCreate(userName: String, repoName: String) {
getRepoDetail.get(
userName to repoName
).subscribe({ (repo, forks) ->
…
}, ::handleException)
}
private fun handleException(throwable: Throwable) {
when (throwable) {
is HttpException -> {
if (throwable.code() == 403)
view.toastRateLimitError()
else view.toastNetworkError()
}
else -> view.toastUnexpectedError()
}
}
}
#3 Exception handling - Business Logic
Presenter의 Exception Handling이 Business Logic인 이유.
HttpException, 403 code 체킹은 Presentation로직이 아니다.
Presenter에선 Http, 403등에 대한 개념을 모르기 때문.
또한 유저의 행동에 따라 발생하는 예외 상황이기 때문에
Business Logic으로 볼 수 있다.
그러나 관점에 따라서는 다르게 처리 하는 방법도 있을 수 있다.
#3 Exception handling - Resolve
HttpNetworkException
403 Forbidden Rate Limit
Toast
Api call
Business Logic
Convert to using in domain
#3 Exception handling - Resolve
Presenter
Domain Data Source
Convert to using in domain
Business Logic
#1#3 Exception handling - Source
override fun getForks(
userName: String,
id: String
): Single<List<Fork>> = githubBrowserAppService
.getForks(userName, id)
.map { it.map { forkEntityMapper.mapFromRemote(it) } }
.composeDomain()
internal fun <T> Single<T>.composeDomain() =
compose(NetworkExceptionSingleTransformer())
Single.error(
if (it is HttpException)
NetworkException(it.message(), it.code())
else it
)
#1#3 Exception handling - UseCase
override fun buildUseCaseSingle(params: Pair<String, String>)
: Single<Pair<Repo, List<Fork>>> =
Single.zip(
repoRepository.getRepo(params.first, params.second),
forkRepository.getForks(params.first, params.second),
BiFunction { t1: Repo, t2: List<Fork> ->
t1 to t2
}
).onErrorResumeNext {
if (it is NetworkException && it.code == 403)
Single.error(RateLimitException())
else Single.error(it)
}
#1#3 Exception handling - Presenter
private fun handleException(throwable: Throwable) {
when(throwable){
is RateLimitException -> view.toastRateLimitError()
is NetworkException -> view.toastNetworkError()
else -> view.toastUnexpectedError()
}
}
#3 Exception handling - 정리
Exception Handling도 Business Logic으로 볼 수 있다.
도메인(UseCase)에서 사용할 수 있도록 관련된 곳 (Data)에서
Exception을 변환 해주어야 할 필요가 있다.
(🙆
Conclusion
View Presenter Model
Conclusion
View Presenter
Data Source
Model
]
View Presenter
Domain Data Source
Model
Conclusion
View
Clean Architecture
…… 오잉 !?

김신입의 상태가 ………!
Evolution
김사원 (으)로
진화했다!
Evolution
김사원 (으)로
진화했다!
Evolution
Would you Join us?
#End
Thank you!✨✨✨
https://crosp.net/blog/software-architecture/clean-architecture-part-1-databse-vs-domain/
https://crosp.net/blog/software-architecture/clean-architecture-part-2-the-clean-architecture/
참고 링크
https://github.com/android10/Android-CleanArchitecture/issues/141
https://groups.google.com/forum/#!msg/clean-code-discussion/mvP_NR2MUPc/wrkHHxzkDQAJ
https://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/
https://www.youtube.com/watch?v=Nltqi7ODZTM
(🙆
(🙆

Contenu connexe

Tendances

애플리케이션 아키텍처와 객체지향
애플리케이션 아키텍처와 객체지향 애플리케이션 아키텍처와 객체지향
애플리케이션 아키텍처와 객체지향
Young-Ho Cho
 
좌충우돌 ORM 개발기 | Devon 2012
좌충우돌 ORM 개발기 | Devon 2012좌충우돌 ORM 개발기 | Devon 2012
좌충우돌 ORM 개발기 | Devon 2012
Daum DNA
 
Fabric.js — Building a Canvas Library
Fabric.js — Building a Canvas LibraryFabric.js — Building a Canvas Library
Fabric.js — Building a Canvas Library
Juriy Zaytsev
 
[JWAP-2] DI & Spring
[JWAP-2] DI & Spring[JWAP-2] DI & Spring
[JWAP-2] DI & Spring
Young-Ho Cho
 

Tendances (20)

애플리케이션 아키텍처와 객체지향
애플리케이션 아키텍처와 객체지향 애플리케이션 아키텍처와 객체지향
애플리케이션 아키텍처와 객체지향
 
Android data binding
Android data bindingAndroid data binding
Android data binding
 
[JWPA-1]의존성 주입(Dependency injection)
[JWPA-1]의존성 주입(Dependency injection)[JWPA-1]의존성 주입(Dependency injection)
[JWPA-1]의존성 주입(Dependency injection)
 
Postgresql các vấn đề thực tế
Postgresql các vấn đề thực tếPostgresql các vấn đề thực tế
Postgresql các vấn đề thực tế
 
[부스트캠프 웹・모바일 7기 Tech Talk]이지훈_뉴비의 시점에서 바라본 Kotlin_suspend
[부스트캠프 웹・모바일 7기 Tech Talk]이지훈_뉴비의 시점에서 바라본 Kotlin_suspend[부스트캠프 웹・모바일 7기 Tech Talk]이지훈_뉴비의 시점에서 바라본 Kotlin_suspend
[부스트캠프 웹・모바일 7기 Tech Talk]이지훈_뉴비의 시점에서 바라본 Kotlin_suspend
 
자바 웹 개발 시작하기 (10주차 : ㅌㅗㅇ ㅎㅏ ㄹㅏ)

자바 웹 개발 시작하기 (10주차 : ㅌㅗㅇ ㅎㅏ ㄹㅏ)
자바 웹 개발 시작하기 (10주차 : ㅌㅗㅇ ㅎㅏ ㄹㅏ)

자바 웹 개발 시작하기 (10주차 : ㅌㅗㅇ ㅎㅏ ㄹㅏ)

 
Action Bar in Android
Action Bar in AndroidAction Bar in Android
Action Bar in Android
 
Android Modularization
Android ModularizationAndroid Modularization
Android Modularization
 
좌충우돌 ORM 개발기 | Devon 2012
좌충우돌 ORM 개발기 | Devon 2012좌충우돌 ORM 개발기 | Devon 2012
좌충우돌 ORM 개발기 | Devon 2012
 
Introduction to Spring's Dependency Injection
Introduction to Spring's Dependency InjectionIntroduction to Spring's Dependency Injection
Introduction to Spring's Dependency Injection
 
Fabric.js — Building a Canvas Library
Fabric.js — Building a Canvas LibraryFabric.js — Building a Canvas Library
Fabric.js — Building a Canvas Library
 
Spring Data JPA
Spring Data JPASpring Data JPA
Spring Data JPA
 
Jetpack Navigation Component
Jetpack Navigation ComponentJetpack Navigation Component
Jetpack Navigation Component
 
Android internals By Rajesh Khetan
Android internals By Rajesh KhetanAndroid internals By Rajesh Khetan
Android internals By Rajesh Khetan
 
[JWAP-2] DI & Spring
[JWAP-2] DI & Spring[JWAP-2] DI & Spring
[JWAP-2] DI & Spring
 
Ngrx
NgrxNgrx
Ngrx
 
Android - Application Framework
Android - Application FrameworkAndroid - Application Framework
Android - Application Framework
 
An introduction to Vue.js
An introduction to Vue.jsAn introduction to Vue.js
An introduction to Vue.js
 
Spring - Part 1 - IoC, Di and Beans
Spring - Part 1 - IoC, Di and Beans Spring - Part 1 - IoC, Di and Beans
Spring - Part 1 - IoC, Di and Beans
 
Design Pattern - MVC, MVP and MVVM
Design Pattern - MVC, MVP and MVVMDesign Pattern - MVC, MVP and MVVM
Design Pattern - MVC, MVP and MVVM
 

Similaire à [TECHCON 2019: MOBILE - Android]2.예제에서는 알려주지 않는 Model 이야기

Jdk(java) 7 - 5. invoke-dynamic
Jdk(java) 7 - 5. invoke-dynamicJdk(java) 7 - 5. invoke-dynamic
Jdk(java) 7 - 5. invoke-dynamic
knight1128
 
Ksug 세미나 (윤성준) (20121208)
Ksug 세미나 (윤성준) (20121208)Ksug 세미나 (윤성준) (20121208)
Ksug 세미나 (윤성준) (20121208)
Sungjoon Yoon
 

Similaire à [TECHCON 2019: MOBILE - Android]2.예제에서는 알려주지 않는 Model 이야기 (20)

반복적인 작업이 싫은 안드로이드 개발자에게
반복적인 작업이 싫은 안드로이드 개발자에게반복적인 작업이 싫은 안드로이드 개발자에게
반복적인 작업이 싫은 안드로이드 개발자에게
 
(IT실무교육/국비지원교육/자바/스프링교육추천)#15.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
(IT실무교육/국비지원교육/자바/스프링교육추천)#15.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)(IT실무교육/국비지원교육/자바/스프링교육추천)#15.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
(IT실무교육/국비지원교육/자바/스프링교육추천)#15.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
 
Jdk(java) 7 - 5. invoke-dynamic
Jdk(java) 7 - 5. invoke-dynamicJdk(java) 7 - 5. invoke-dynamic
Jdk(java) 7 - 5. invoke-dynamic
 
Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)
 
5.Spring IoC&DI(DI와 관련된 어노테이션)
5.Spring IoC&DI(DI와 관련된 어노테이션)5.Spring IoC&DI(DI와 관련된 어노테이션)
5.Spring IoC&DI(DI와 관련된 어노테이션)
 
Booting Spring Data REST
Booting Spring Data RESTBooting Spring Data REST
Booting Spring Data REST
 
[국비지원교육/재직자/실업자환급교육/IT실무학원추천/스프링교육추천]#5.스프링프레임워크 & 마이바티스 (Spring Framework, M...
[국비지원교육/재직자/실업자환급교육/IT실무학원추천/스프링교육추천]#5.스프링프레임워크 & 마이바티스 (Spring Framework, M...[국비지원교육/재직자/실업자환급교육/IT실무학원추천/스프링교육추천]#5.스프링프레임워크 & 마이바티스 (Spring Framework, M...
[국비지원교육/재직자/실업자환급교육/IT실무학원추천/스프링교육추천]#5.스프링프레임워크 & 마이바티스 (Spring Framework, M...
 
React 튜토리얼 1차시
React 튜토리얼 1차시React 튜토리얼 1차시
React 튜토리얼 1차시
 
HOONS닷넷 오픈소스 프로젝트 Part1.
HOONS닷넷 오픈소스 프로젝트 Part1.HOONS닷넷 오픈소스 프로젝트 Part1.
HOONS닷넷 오픈소스 프로젝트 Part1.
 
Spring MVC
Spring MVCSpring MVC
Spring MVC
 
(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...
(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...
(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...
 
React 튜토리얼 2차시
React 튜토리얼 2차시React 튜토리얼 2차시
React 튜토리얼 2차시
 
Express framework tutorial
Express framework tutorialExpress framework tutorial
Express framework tutorial
 
Eclipse RCP 1/2
Eclipse RCP 1/2Eclipse RCP 1/2
Eclipse RCP 1/2
 
Ksug 세미나 (윤성준) (20121208)
Ksug 세미나 (윤성준) (20121208)Ksug 세미나 (윤성준) (20121208)
Ksug 세미나 (윤성준) (20121208)
 
[Codelab 2017] ReactJS 기초
[Codelab 2017] ReactJS 기초[Codelab 2017] ReactJS 기초
[Codelab 2017] ReactJS 기초
 
[114]angularvs react 김훈민손찬욱
[114]angularvs react 김훈민손찬욱[114]angularvs react 김훈민손찬욱
[114]angularvs react 김훈민손찬욱
 
Domain Specific Languages With Groovy
Domain Specific Languages With GroovyDomain Specific Languages With Groovy
Domain Specific Languages With Groovy
 
20220716_만들면서 느껴보는 POP
20220716_만들면서 느껴보는 POP20220716_만들면서 느껴보는 POP
20220716_만들면서 느껴보는 POP
 
React Hooks 마법. 그리고 깔끔한 사용기
React Hooks 마법. 그리고 깔끔한 사용기React Hooks 마법. 그리고 깔끔한 사용기
React Hooks 마법. 그리고 깔끔한 사용기
 

Plus de NAVER Engineering

Plus de NAVER Engineering (20)

React vac pattern
React vac patternReact vac pattern
React vac pattern
 
디자인 시스템에 직방 ZUIX
디자인 시스템에 직방 ZUIX디자인 시스템에 직방 ZUIX
디자인 시스템에 직방 ZUIX
 
진화하는 디자인 시스템(걸음마 편)
진화하는 디자인 시스템(걸음마 편)진화하는 디자인 시스템(걸음마 편)
진화하는 디자인 시스템(걸음마 편)
 
서비스 운영을 위한 디자인시스템 프로젝트
서비스 운영을 위한 디자인시스템 프로젝트서비스 운영을 위한 디자인시스템 프로젝트
서비스 운영을 위한 디자인시스템 프로젝트
 
BPL(Banksalad Product Language) 무야호
BPL(Banksalad Product Language) 무야호BPL(Banksalad Product Language) 무야호
BPL(Banksalad Product Language) 무야호
 
이번 생에 디자인 시스템은 처음이라
이번 생에 디자인 시스템은 처음이라이번 생에 디자인 시스템은 처음이라
이번 생에 디자인 시스템은 처음이라
 
날고 있는 여러 비행기 넘나 들며 정비하기
날고 있는 여러 비행기 넘나 들며 정비하기날고 있는 여러 비행기 넘나 들며 정비하기
날고 있는 여러 비행기 넘나 들며 정비하기
 
쏘카프레임 구축 배경과 과정
 쏘카프레임 구축 배경과 과정 쏘카프레임 구축 배경과 과정
쏘카프레임 구축 배경과 과정
 
플랫폼 디자이너 없이 디자인 시스템을 구축하는 프로덕트 디자이너의 우당탕탕 고통 연대기
플랫폼 디자이너 없이 디자인 시스템을 구축하는 프로덕트 디자이너의 우당탕탕 고통 연대기플랫폼 디자이너 없이 디자인 시스템을 구축하는 프로덕트 디자이너의 우당탕탕 고통 연대기
플랫폼 디자이너 없이 디자인 시스템을 구축하는 프로덕트 디자이너의 우당탕탕 고통 연대기
 
200820 NAVER TECH CONCERT 15_Code Review is Horse(코드리뷰는 말이야)(feat.Latte)
200820 NAVER TECH CONCERT 15_Code Review is Horse(코드리뷰는 말이야)(feat.Latte)200820 NAVER TECH CONCERT 15_Code Review is Horse(코드리뷰는 말이야)(feat.Latte)
200820 NAVER TECH CONCERT 15_Code Review is Horse(코드리뷰는 말이야)(feat.Latte)
 
200819 NAVER TECH CONCERT 03_화려한 코루틴이 내 앱을 감싸네! 코루틴으로 작성해보는 깔끔한 비동기 코드
200819 NAVER TECH CONCERT 03_화려한 코루틴이 내 앱을 감싸네! 코루틴으로 작성해보는 깔끔한 비동기 코드200819 NAVER TECH CONCERT 03_화려한 코루틴이 내 앱을 감싸네! 코루틴으로 작성해보는 깔끔한 비동기 코드
200819 NAVER TECH CONCERT 03_화려한 코루틴이 내 앱을 감싸네! 코루틴으로 작성해보는 깔끔한 비동기 코드
 
200819 NAVER TECH CONCERT 10_맥북에서도 아이맥프로에서 빌드하는 것처럼 빌드 속도 빠르게 하기
200819 NAVER TECH CONCERT 10_맥북에서도 아이맥프로에서 빌드하는 것처럼 빌드 속도 빠르게 하기200819 NAVER TECH CONCERT 10_맥북에서도 아이맥프로에서 빌드하는 것처럼 빌드 속도 빠르게 하기
200819 NAVER TECH CONCERT 10_맥북에서도 아이맥프로에서 빌드하는 것처럼 빌드 속도 빠르게 하기
 
200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활
200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활
200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활
 
200819 NAVER TECH CONCERT 05_모르면 손해보는 Android 디버깅/분석 꿀팁 대방출
200819 NAVER TECH CONCERT 05_모르면 손해보는 Android 디버깅/분석 꿀팁 대방출200819 NAVER TECH CONCERT 05_모르면 손해보는 Android 디버깅/분석 꿀팁 대방출
200819 NAVER TECH CONCERT 05_모르면 손해보는 Android 디버깅/분석 꿀팁 대방출
 
200819 NAVER TECH CONCERT 09_Case.xcodeproj - 좋은 동료로 거듭나기 위한 노하우
200819 NAVER TECH CONCERT 09_Case.xcodeproj - 좋은 동료로 거듭나기 위한 노하우200819 NAVER TECH CONCERT 09_Case.xcodeproj - 좋은 동료로 거듭나기 위한 노하우
200819 NAVER TECH CONCERT 09_Case.xcodeproj - 좋은 동료로 거듭나기 위한 노하우
 
200820 NAVER TECH CONCERT 14_야 너두 할 수 있어. 비전공자, COBOL 개발자를 거쳐 네이버에서 FE 개발하게 된...
200820 NAVER TECH CONCERT 14_야 너두 할 수 있어. 비전공자, COBOL 개발자를 거쳐 네이버에서 FE 개발하게 된...200820 NAVER TECH CONCERT 14_야 너두 할 수 있어. 비전공자, COBOL 개발자를 거쳐 네이버에서 FE 개발하게 된...
200820 NAVER TECH CONCERT 14_야 너두 할 수 있어. 비전공자, COBOL 개발자를 거쳐 네이버에서 FE 개발하게 된...
 
200820 NAVER TECH CONCERT 13_네이버에서 오픈 소스 개발을 통해 성장하는 방법
200820 NAVER TECH CONCERT 13_네이버에서 오픈 소스 개발을 통해 성장하는 방법200820 NAVER TECH CONCERT 13_네이버에서 오픈 소스 개발을 통해 성장하는 방법
200820 NAVER TECH CONCERT 13_네이버에서 오픈 소스 개발을 통해 성장하는 방법
 
200820 NAVER TECH CONCERT 12_상반기 네이버 인턴을 돌아보며
200820 NAVER TECH CONCERT 12_상반기 네이버 인턴을 돌아보며200820 NAVER TECH CONCERT 12_상반기 네이버 인턴을 돌아보며
200820 NAVER TECH CONCERT 12_상반기 네이버 인턴을 돌아보며
 
200820 NAVER TECH CONCERT 11_빠르게 성장하는 슈퍼루키로 거듭나기
200820 NAVER TECH CONCERT 11_빠르게 성장하는 슈퍼루키로 거듭나기200820 NAVER TECH CONCERT 11_빠르게 성장하는 슈퍼루키로 거듭나기
200820 NAVER TECH CONCERT 11_빠르게 성장하는 슈퍼루키로 거듭나기
 
200819 NAVER TECH CONCERT 07_신입 iOS 개발자 개발업무 적응기
200819 NAVER TECH CONCERT 07_신입 iOS 개발자 개발업무 적응기200819 NAVER TECH CONCERT 07_신입 iOS 개발자 개발업무 적응기
200819 NAVER TECH CONCERT 07_신입 iOS 개발자 개발업무 적응기
 

[TECHCON 2019: MOBILE - Android]2.예제에서는 알려주지 않는 Model 이야기

  • 1.
  • 5. M
  • 6. MVC
  • 7. MVP
  • 9. MVI
  • 11. MODEL
  • 13. 모델(model)이란 어떠한 동작을 수행하는 코드를 말한다. 표시 형식 에 의존하지 않는다. 다시 말해, 사용자에게 어떻게 보일지에 대해 신 경쓰지 않아도 된다. 모델은 순수하게 public 함수로만 이루어진다. 몇몇 함수들은 사용자의 질의(query)에 대해 상태 정보를 제공하고 나머지 함수들은 상태를 수정한다. DefinitionofWikipedia(MVC)
  • 14. 모델(model)이란 어떠한 동작을 수행하는 코드를 말한다. 표시 형식 에 의존하지 않는다. 다시 말해, 사용자에게 어떻게 보일지에 대해 신 경쓰지 않아도 된다. 모델은 순수하게 public 함수로만 이루어진다. 몇몇 함수들은 사용자의 질의(query)에 대해 상태 정보를 제공하고 나머지 함수들은 상태를 수정한다. DefinitionofWikipedia(MVC) 😐
  • 15. 간단하게.. 데이터베이스, API를 통해 데이터를 불러오고 저장하는 역할. 서비스의 비지니스 로직(도메인)을 담당.
  • 16. 간단하게.. 데이터베이스, API를 통해 데이터를 불러오고 저장하는 역할. 서비스의 비지니스 로직(도메인)을 담당.
  • 17. 간단하게.. 데이터베이스, API를 통해 데이터를 불러오고 저장하는 역할. 서비스의 비지니스 로직(도메인)을 담당.
  • 18. 그래서 오늘 하고싶은 이야기는 Model의 역할(책임)에 따른 분리 과정
  • 19. Model의 역할(책임)에 따른 분리 과정 Model
  • 20. Model의 역할(책임)에 따른 분리 과정 Model Domain Data Source
  • 22. 이름 : 김신입 경력 : 신입 부연 설명 : 신입 드디어 원하던 회사에 입사하게된 김신입. 들어온지 얼마 안되어 피쳐를 맡게 된다. 서비스의 코드 구조는 MVP이며 김신입도 이에대한 경험이 있어 자신만만이다. 즐겁게 개발을 하는 신입씨. 그렇게 리뷰의 시간이 다가오는데…. Profile
  • 26. 목차 #1. Repository Pattern을 사용하라! #2. Business Logic을 분리하라! #3. Exception handling
  • 29. #1 #1. Repository Pattern을 사용하라! branch : model_v1
  • 30. #1 Repository Pattern을 사용하라! - 예제 Github Api : Repo
  • 31. #1#1 Repository Pattern을 사용하라! - 서버통신을 위한 Retrofit object ApiClient { val githubBrowserService: GithubService init { githubBrowserService = makeGithubBrowserService(…) } fun makeGithubBrowserService(): GithubService {…} private fun makeGithubBrowserService(…): GithubService {…} private fun makeOkHttpClient(…) :OkHttpClient{…} private fun makeLoggingInterceptor(…): HttpLoggingInterceptor {…} }
  • 32. #1#1 Repository Pattern을 사용하라! - 서버통신을 위한 Retrofit object ApiClient { val githubBrowserService: GithubService init { githubBrowserService = makeGithubBrowserService(…) } fun makeGithubBrowserService(): GithubService {…} private fun makeGithubBrowserService(…): GithubService {…} private fun makeOkHttpClient(…) :OkHttpClient{…} private fun makeLoggingInterceptor(…): HttpLoggingInterceptor {…} }
  • 33. #1#1 Repository Pattern을 사용하라! - MODEL object RepoDetailModel { fun getRepo(userName: String, id: String) :Repo = ApiClient.githubBrowserService.getRepo( userName, id ) }
  • 34. #1#1 Repository Pattern을 사용하라! - MODEL object RepoDetailModel { fun getRepo(userName: String, id: String) :Repo = ApiClient.githubBrowserService.getRepo( userName, id ) }
  • 35. #1#1 Repository Pattern을 사용하라! - PRESENTER class RepoDetailPresenter( view: RepoDetailView ) : BasePresenter<RepoDetailView>(view) { fun onCreate(userName: String, repoName: String) { val repo = RepoDetailModel.getRepo(userName, repoName) view.setName(repo.name) view.setDescription(repo.description ?: "") view.setStarCount(repo.starCount) } }
  • 36. #1#1 Repository Pattern을 사용하라! - PRESENTER class RepoDetailPresenter( view: RepoDetailView ) : BasePresenter<RepoDetailView>(view) { fun onCreate(userName: String, repoName: String) { val repo = RepoDetailModel.getRepo(userName, repoName) view.setName(repo.name) view.setDescription(repo.description ?: "") view.setStarCount(repo.starCount) } }
  • 37. #1 Repository Pattern을 사용하라! - Review Review 👀🙏!
  • 38. #1 Repository Pattern을 사용하라! - Review Review 👀🙏! Repository Pattern을 적용해보는게 어때요? &🤷
  • 39. #1 Repository Pattern을 사용하라! - Repository? Repository? 👀
  • 40. Data #1 Repository Pattern을 사용하라! - Repository? 디자인 패턴중의 하나 데이터 불러오는 로직을 분리시켜 관리 하는것이 목적 추상화를 통해 테스트에 용이해진다. 하나의 Repository는 하나의 Domain을 담당한다.
  • 41. Data #1 Repository Pattern을 사용하라! - Repository? 디자인 패턴중의 하나 데이터 불러오는 로직을 분리시켜 관리 하는것이 목적 추상화를 통해 테스트에 용이해진다. 하나의 Repository는 하나의 Domain을 담당한다. branch : model_v1_resolve
  • 42. #1#1 Repository Pattern을 사용하라! - Repository class RepoRepositoryImpl( private val api : GithubService ) : RepoRepository { override fun getRepo(userName: String, id: String): Repo = api.getRepo(userName, id) }
  • 43. #1#1 Repository Pattern을 사용하라! - Repository class RepoRepositoryImpl( private val api : GithubService ) : RepoRepository { override fun getRepo(userName: String, id: String): Repo = api.getRepo(userName, id) }
  • 44. #1#1 Repository Pattern을 사용하라! - Repository class RepoRepositoryImpl( private val api : GithubService ) : RepoRepository { override fun getRepo(userName: String, id: String): Repo = api.getRepo(userName, id) }
  • 45. #1#1 Repository Pattern을 사용하라! - Repository interface RepoRepository { fun get(userName: String, id: String): Repo }
  • 46. #1#1 Repository Pattern을 사용하라! - PRESENTER class RepoDetailPresenter( view: RepoDetailView, private val repoRepository: RepoRepository ) : BasePresenter<RepoDetailView>(view) { fun onCreate(userName: String, repoName: String) { val repo = repoRepository.getRepo(userName, repoName) view.setName(repo.name) view.setDescription(repo.description ?: "") view.setStarCount(repo.starCount) } }
  • 47. #1#1 Repository Pattern을 사용하라! - PRESENTER class RepoDetailPresenter( view: RepoDetailView, private val repoRepository: RepoRepository ) : BasePresenter<RepoDetailView>(view) { fun onCreate(userName: String, repoName: String) { val repo = repoRepository.getRepo(userName, repoName) view.setName(repo.name) view.setDescription(repo.description ?: "") view.setStarCount(repo.starCount) } }
  • 48. #1 Repository Pattern을 사용하라! - Source Source 데이터를 불러올 수 있는 모든 외부 Server(Http, socket), Local(db, cache) 현재 예제에서는 Http만 사용하기 때문에 나누지 않음.
  • 49. #1 Repository Pattern을 사용하라! - 정리 Model -> Repository Pattern(추상화) 적용. 추상화를 통하여 테스트가 용이해지게 되었다.
 (GitHubApiService, RepoRepository) (🙆 데이터를 불러오고 처리하는 역할을 분리.
  • 50. #2 #2. Business Logic을 분리하라!
  • 51. #2 #2. Business Logic을 분리하라! branch : model_v2
  • 52. #2 Business Logic을 분리하라! - 지금까지의 줄거리 View Presenter Data Source Model
  • 53. #2 Business Logic을 분리하라! - 기능 추가 Fork
  • 54. #2 Business Logic을 분리하라! - 기능 추가 Fork Business Logic 추가.
  • 55. #2 Business Logic을 분리하라! - Business Logic 유저의 행동에 따라 서비스에서 보여주고자 하는 결과를 나타내기 위해 데이터를 가공하는 로직
  • 56. #1#2 Business Logic을 분리하라! - ForkRepository class ForkRepositoryImpl( private val api: GithubService ) : ForkRepository { override fun gets(userName: String, id: String): List<Fork> = api.getForks(userName, id) }
  • 57. #1#2 Business Logic을 분리하라! - PRESENTER class RepoDetailPresenter( view: RepoDetailView, private val repoRepository: RepoRepository, private val forkRepository: ForkRepository ) : BasePresenter<RepoDetailView>(view) { fun onCreate(userName: String, repoName: String) { val repo = repoRepository.get(userName, repoName) val forks = forkRepository.gets(userName, repoName) view.setName(repo.name) view.setDescription(repo.description ?: "") view.setStarCount(repo.starCount) view.refreshForks(forks) } }
  • 58. #1#2 Business Logic을 분리하라! - PRESENTER class RepoDetailPresenter( view: RepoDetailView, private val repoRepository: RepoRepository, private val forkRepository: ForkRepository ) : BasePresenter<RepoDetailView>(view) { fun onCreate(userName: String, repoName: String) { val repo = repoRepository.get(userName, repoName) val forks = forkRepository.gets(userName, repoName) view.setName(repo.name) view.setDescription(repo.description ?: "") view.setStarCount(repo.starCount) view.refreshForks(forks) } }
  • 59. #1#2 Business Logic을 분리하라! - PRESENTER class RepoDetailPresenter( view: RepoDetailView, private val repoRepository: RepoRepository, private val forkRepository: ForkRepository ) : BasePresenter<RepoDetailView>(view) { fun onCreate(userName: String, repoName: String) { val repo = repoRepository.get(userName, repoName) val forks = forkRepository.gets(userName, repoName) view.setName(repo.name) view.setDescription(repo.description ?: "") view.setStarCount(repo.starCount) view.refreshForks(forks) } }
  • 60. Review 👀🙏! #2 Business Logic을 분리하라! - Review!
  • 61. Review 👀🙏! #2 Business Logic을 분리하라! - Review! Business Logic을 분리해보시겠어요? &🤷
  • 62. #1#2 Business Logic을 분리하라! - Business Logic class RepoDetailPresenter( view: RepoDetailView, private val repoRepository: RepoRepository, private val forkRepository: ForkRepository ) : BasePresenter<RepoDetailView>(view) { fun onCreate(userName: String, repoName: String) { val repo = repoRepository.get(userName, repoName) val forks = forkRepository.gets(userName, repoName) view.setName(repo.name) view.setDescription(repo.description ?: "") view.setStarCount(repo.starCount) view.refreshForks(forks) } }
  • 63. #1#2 Business Logic을 분리하라! - Business Logic fun onCreate(userName: String, repoName: String) { Single.zip( repoRepository.get(userName, repoName), forkRepository.gets(userName, repoName), BiFunction { t1: RepoModel, t2: List<ForkModel> -> t1 to t2 } ).subscribe({ (repo, forks) -> view.setName(repo.name) view.setDescription(repo.description ?: "") view.setStarCount(repo.starCount) view.refreshForks(forks) }, ::printStackTrace) }
  • 64. #2 Business Logic을 분리하라! - Business Logic Business Logic을 분리해야 하는 이유?
  • 65. #2 Business Logic을 분리하라! - Business Logic Business Logic을 Presentation Logic에서 분리해야 하는 이유? 복잡한 화면일수록 Presentation의 코드가 커지게 된다. 그럴수록 복잡성이 증대. 유지보수성 저하 됨. Presentation의 역할을 분리해줄 필요성이 생김. Presentation에서 Business Logic을 분리. Presentation에 집중하게한다. 물론 작은 서비스, 화면, Platform에 따라 분리하지 않아도 괜찮긴 함.
  • 66. #2 Business Logic을 분리하라! - Business Logic Business Logic을 Presentation Logic에서 어떻게 분리? Service? UseCase?
  • 67. #2 Business Logic을 분리하라! - Service Service Layer Pattern (n-tier, 3-layer에서 파생). MSA, 작은 서비스에서 쓰일 때 유용함. 혹은 설계가 짜임새있게 되어 있어야 한다. 완벽하게 Presentation에서 Logic을 분리하기 어렵기 때문. 또한 유지보수에 있어 어려움이 있을 수 있다. Business Logic의 집합소 개념. 재사용이 용이하다.
  • 68. #2 Business Logic을 분리하라! - Service Service ServiceService Service Repository Repository Repository
  • 69. #2 Business Logic을 분리하라! - UseCase 서로(UseCase)에게 독립적. 즉, Business Logic들 간의 의존성이 X Service와 마찬가지로 설계가 중요. 유지보수, 변경등의 비용이 저렴하다. 하나의 유저행동에 대한 서비스(Application)의 Business Logic이 담겨있는 객체.
  • 70. #2 Business Logic을 분리하라! - UseCase UseCase Repository Repository Repository
  • 71. #2 Business Logic을 분리하라! - UseCase를 잘못 사용하는 경우. UseCase를 Repository처럼 사용하는 경우. UseCase를 Service처럼 사용하는 경우. (강조) 보통의 경우에는 유저의 행동(Business Logic)과 1:1로 매칭된다. (강조)
  • 72. #1#2 Business Logic을 분리하라! - UseCase Template abstract class SingleUseCase<T, in Params>( private val schedulersProvider: SchedulersProvider ) { protected abstract fun buildUseCaseSingle( params: Params ): Single<T> fun get(params: Params) = buildUseCaseSingle(params) .subscribeOn(schedulersProvider.io()) .observeOn(schedulersProvider.ui()) }
  • 73. #1#2 Business Logic을 분리하라! - UseCase Template abstract class UseCase<T, in Params> { protected abstract fun buildUseCase(params: Params): T fun get(params: Params) = buildUseCase(params) }
  • 74. #1#2 Business Logic을 분리하라! - Business Logic class GetRepoDetail( private val repoRepository: RepoRepository, private val forkRepository: ForkRepository, schedulersProvider: SchedulersProvider ) : SingleUseCase<Pair<Repo, List<Fork>>, Pair<String, String>>( schedulersProvider) { override fun buildUseCaseSingle(params: Pair<String, String>) : Single<Pair<Repo, List<Fork>>> = Single.zip( repoRepository.get(params.first, params.second), forkRepository.gets(params.first, params.second), BiFunction { t1: Repo, t2: List<Fork> -> t1 to t2 } ) }
  • 75. #1#2 Business Logic을 분리하라! - Business Logic class GetRepoDetail( private val repoRepository: RepoRepository, private val forkRepository: ForkRepository ) : UseCase<Pair<Repo, List<Fork>>, Pair<String, String>>() { override fun buildUseCase( params: Pair<String, String> ): Pair<Repo, List<Fork>> { val repo = repoRepository.get(params.first, params.second) val forks = forkRepository.gets(params.first, params.second) return repo to forks } }
  • 76. #1#2 Business Logic을 분리하라! - Business Logic class GetRepoDetail( private val repoRepository: RepoRepository, private val forkRepository: ForkRepository ) : UseCase<Pair<Repo, List<Fork>>, Pair<String, String>>() { override fun buildUseCase( params: Pair<String, String> ): Pair<Repo, List<Fork>> { val repo = repoRepository.get(params.first, params.second) val forks = forkRepository.gets(params.first, params.second) return repo to forks } }
  • 77. #1#2 Business Logic을 분리하라! - Presentation class RepoDetailPresenter( view: RepoDetailView, private val getRepoDetail: GetRepoDetail ) : BasePresenter<RepoDetailView>(view) { fun onCreate(userName: String, repoName: String) { val (repo, forks) = getRepoDetail.get(userName to repoName) view.setName(repo.name) view.setDescription(repo.description ?: "") view.setStarCount(repo.starCount) view.refreshForks(forks) } }
  • 78. #1#2 Business Logic을 분리하라! - Presentation class RepoDetailPresenter( view: RepoDetailView, private val getRepoDetail: GetRepoDetail ) : BasePresenter<RepoDetailView>(view) { fun onCreate(userName: String, repoName: String) { val (repo, forks) = getRepoDetail.get(userName to repoName) view.setName(repo.name) view.setDescription(repo.description ?: "") view.setStarCount(repo.starCount) view.refreshForks(forks) } }
  • 79. #2 Business Logic을 분리하라! - 정리 Presentation에서 Business Logic을 분리. (🙆 Domain시스템이 복잡해져도 유연하게 대처할 수 있게 되었다.
  • 82. #3 Exception handling - 지금까지의 줄거리 View Presenter Domain Data Source Model
  • 83. #3 Exception handling - 예제 HttpNetworkException 403 Forbidden Rate Limit Toast Api call
  • 84. #1#3 Exception handling - Presenter fun onCreate(userName: String, repoName: String) { getRepoDetail.get( userName to repoName ).subscribe({ (repo, forks) -> … }, ::handleException) } private fun handleException(throwable: Throwable) { when (throwable) { is HttpException -> { if (throwable.code() == 403) view.toastRateLimitError() else view.toastNetworkError() } else -> view.toastUnexpectedError() } } }
  • 85. #1#3 Exception handling - Presenter fun onCreate(userName: String, repoName: String) { getRepoDetail.get( userName to repoName ).subscribe({ (repo, forks) -> … }, ::handleException) } private fun handleException(throwable: Throwable) { when (throwable) { is HttpException -> { if (throwable.code() == 403) view.toastRateLimitError() else view.toastNetworkError() } else -> view.toastUnexpectedError() } } }
  • 86. #3 Exception handling - Review Review 👀🙏!
  • 87. #3 Exception handling - Review Review 👀🙏! Business Logic이 분리되지 않았어요. &🤷
  • 88. #1#3 Exception handling - Business Logic fun onCreate(userName: String, repoName: String) { getRepoDetail.get( userName to repoName ).subscribe({ (repo, forks) -> … }, ::handleException) } private fun handleException(throwable: Throwable) { when (throwable) { is HttpException -> { if (throwable.code() == 403) view.toastRateLimitError() else view.toastNetworkError() } else -> view.toastUnexpectedError() } } }
  • 89. #3 Exception handling - Business Logic Presenter의 Exception Handling이 Business Logic인 이유. HttpException, 403 code 체킹은 Presentation로직이 아니다. Presenter에선 Http, 403등에 대한 개념을 모르기 때문. 또한 유저의 행동에 따라 발생하는 예외 상황이기 때문에 Business Logic으로 볼 수 있다. 그러나 관점에 따라서는 다르게 처리 하는 방법도 있을 수 있다.
  • 90. #3 Exception handling - Resolve HttpNetworkException 403 Forbidden Rate Limit Toast Api call Business Logic Convert to using in domain
  • 91. #3 Exception handling - Resolve Presenter Domain Data Source Convert to using in domain Business Logic
  • 92. #1#3 Exception handling - Source override fun getForks( userName: String, id: String ): Single<List<Fork>> = githubBrowserAppService .getForks(userName, id) .map { it.map { forkEntityMapper.mapFromRemote(it) } } .composeDomain() internal fun <T> Single<T>.composeDomain() = compose(NetworkExceptionSingleTransformer()) Single.error( if (it is HttpException) NetworkException(it.message(), it.code()) else it )
  • 93. #1#3 Exception handling - UseCase override fun buildUseCaseSingle(params: Pair<String, String>) : Single<Pair<Repo, List<Fork>>> = Single.zip( repoRepository.getRepo(params.first, params.second), forkRepository.getForks(params.first, params.second), BiFunction { t1: Repo, t2: List<Fork> -> t1 to t2 } ).onErrorResumeNext { if (it is NetworkException && it.code == 403) Single.error(RateLimitException()) else Single.error(it) }
  • 94. #1#3 Exception handling - Presenter private fun handleException(throwable: Throwable) { when(throwable){ is RateLimitException -> view.toastRateLimitError() is NetworkException -> view.toastNetworkError() else -> view.toastUnexpectedError() } }
  • 95. #3 Exception handling - 정리 Exception Handling도 Business Logic으로 볼 수 있다. 도메인(UseCase)에서 사용할 수 있도록 관련된 곳 (Data)에서 Exception을 변환 해주어야 할 필요가 있다. (🙆
  • 100. …… 오잉 !?
 김신입의 상태가 ………! Evolution
  • 104. (🙆
  • 105. (🙆