건우의 개발 일기

코드 리팩토링 - 도담도담 V2 작업 본문

프로젝트

코드 리팩토링 - 도담도담 V2 작업

거누팍 2020. 2. 1. 18:07

 

방학을 시작하고 도담도담 V2 작업을 진행하였습니다. 이번 포스트에서는 어떻게 V2 작업을 진행하였는지 포스팅하겠습니다.

 

🙄 어떻게 바꿔야 하나

 

저는 도담도담 코드의 리팩토링 필요성을 느껴 어떻게 리팩토링을 할까 늘 고민하였습니다.

 

처음 계획

처음 계획은 도담도담 코드가 발전된 순서대로 1단계부터 4단계까지 리팩토링을 하는 것이었습니다.

팀원들과 함께 이때까지 공부하며 배웠던 기술들을 차례대로 익힐 수 있게 하기 위함이었습니다.

 

문제점

하지만 해당 작업은 수정이 너무 잦고 많기 때문에 방학 도중 메세지로 팀원들과 리팩토링을 하기엔 시간적인 문제가 많았습니다

3월 시작하며 바로 배포 하여야 하는데 언제 어디서 오류를 뿜어낼지 모르고 변화와 확장에 민감한 V1 코드를 끌고 가긴엔 문제가 있다고 생각했습니다.

 

수정된 계획

그래서 코드를 단계별로 리팩토링을 하는것이 아닌 새로운 프로젝트로 설계하여 1단계 상태인 처음 코드를 4단계로 바로 만드는게 더 효율적일 것이라고 판단했습니다

처음부터 다시 짜면서 프로젝트의 히스토리를 공부할수 있었고 완전한 우리의 코드가 되었기 때문에 이슈와 기능 추가에 더 쉽게 대응할수 있었습니다

단계별로 리팩토링을 하며 천천히 얻는 지식이 보다 더 가치 있을것이고 급진적인 변화는 분명히 문제가 생길수 있습니다. 하지만 많은 코드리뷰와 스터디를 통해 이를 해결해낼수 있을것이라고 생각합니다

 

처음 계획했던 단계별 리팩토링 계획서

 

 

🤷‍♀️ 무엇을 바꿔야 하나

 

MVVM, Data Binding

View - ViewModel의 분리를 더 신경썼고 Data Binding의 활용도 확대하였습니다.

 

Java → Kotlin

예전부터 느껴 왔지만 Kotlin이 생산성, 가독성등 거의 모든 면에서 자바보다 우수하다고 생각합니다,

배움의 폭을 늘리고자 Kotlin을 사용하였는데 완전히 푹 빠져 더 이상 자바 코드가 보기 싫을 정도였습니다 😆

NullPointExeption 회피, Kotlin Extension, 보일러 코드 감소 등 다양한 장점들이 있었습니다

 

내부 DB 활용

서버 API 요청횟수를 줄이고 사용자에게 더 빠른 속도로 로딩이 될 수 있도록 하기 위해 내부 DB를 더 활용해야 했습니다.

내부 DB 사용 빈도를 늘리고 API 요청을 최소화하는 방향으로 진행하였습니다.

또한 기존에는 SQLITE를 사용하여 내부 DB를 구성하였다면 리팩토링을 하며 모두 Room으로 바꾸었습니다.

Room은 SQLITE 보다 더 간단하고 쿼리문을 쉽게 작성할 수 있으며 RxJava를 통한 비동기 처리를 적극 활용할 수 있었습니다.

 

시리즈 | Room - rjsdnqkr1.log

👍 Room이란? 안드로이드 앱에서 SQLite 데이터베이스를 쉽고 편리하게 사용할 수 있도록 하는 라이브러리이다. Room은 Entity, Dao, RoomDatabase 3가지 요소로 구성된다. blog-android-roomarchitecture-1.png ✌ Depe

velog.io

위 블로그는 제가 Room을 공부하며 작성하였던 블로그 글입니다.

 

Repository 패턴을 활용한 관심사 분리

기존 로직에서는 ViewModel이 데이터를 들고오는 로직과 처리, 정렬, 비즈니스 로직을 모두 부담해야하는 문제가 있었습니다.

저는 이 문제를 Repository 패턴을 활용하여 data passing 로직과 비즈니스 로직을 분담하게 하였습니다.

Repository에서 Server와 내부 DB에 요청을 하여 데이터를 전달하고 하고 ViewModel이 이를 활용하여 비즈니스 로직을 실행하도록 하였습니다.

이렇게 하여 ViewModel의 부담이 줄였습니다.

 

RxJava2 활용

V1에서는 RxJava를 사용하긴 하였지만 Success 혹은 Error를 쉽게 하기 위한 용도로 사용되었습니다.

이 때문에 스트림의 Return Value는 아무런 데이터 변화 없이 Success or Error만 판단하여 ViewModel에 집중되었습니다.

 

예를 들어 맴버를 불러오는 코드에서...

  1. 내부 DB로 멤버를 요청
  2. subscribe
  3. 내부 DB에 멤버가 없다면 (onError) 서버에 멤버를 요청
  4. subscribe
  5. 서버에서 멤버를 받아왔다면 (onSuccess) 내부 DB로 Insert
  6. subscribe

이처럼 ViewModel에서만 최종적인 subscribe가 가능하기 때문에 모든 호출을 subscribe가 있는 ViewModel에서 처리해야 했습니다.

 

하지만 Repository에 로직들을 연결 해주고 ViewModel에 최종적인 값만 보내준다면 코드가 굉장히 간략해집니다.

  1. 내부 DB로 멤버를 요청
  2. 내부 DB에 멤버가 없다면 서버에 멤버를 요청
  3. 서버에서 멤버를 받아왔다면 내부 DB로 Insert
  4. subscribe
fun getAllMember(): Single<List<MemberEntity>> =
        cache.getAllMember().onErrorResumeNext { getAllMemberRemote() }
        
private fun getAllMemberRemote(): Single<List<MemberEntity>> =
        remote.getAllMember().map(MemberData::getMembers)
            .map { memberList -> memberList.map { member -> memberMapper.mapToEntity(member) } }
            .flatMap { memberEntityList -> cache.insertMembers(memberEntityList).toSingleDefault(memberEntityList) }
            
private fun insertAllMemberRemote(): Completable =
        remote.getAllMember()
            .flatMap {
                val memberList = it.getMembers()
                val studentList = it.students
                val teacherList = it.teachers

                insertMembersList(memberList, studentList, teacherList).toSingleDefault(it)
            }.ignoreElement()            

두번째 과정의 코드입니다. map과 flapMap을 활용하여 RxJava를 서로 연결해주고 최종적인 값만 반환될 수 있도록 해줍니다.

 

Clean Architecture를 통한 계층화

프로젝트의 규모가 점점 커지니 유지보수에도 큰 어려움이 따랐습니다.

서버에서 API 요청 형식만 바꾸었는데도 Repository, 심지어는 ViewModel에서 까지 코드를 바꾸어야 했습니다.

그래서 저는 Clean Architecture를 통해 각각의 계층으로 나누어 문제 상황에 따른 유지보수 쉽게 진행할 수 있도록 하였습니다.

Clean Architecture에는 Presentation, Domain, Data 3개의 계층이 존재합니다.

 

1. Presentation Layer

  - 사용자와 직접 연관되는 Layer

  - MVVM 디자인 패턴 사용

 

2. Domain Layer

  - 비즈니스 로직을 구성하는 Layer

  - Android에 전혀 의존하지 않는 순수 Kotlin 코드로 구성

  - Presentation, Data Layer 중 그 무엇도 의존하지 않음

 

3. Data Layer

  - 데이터 관련 로직 구현하는 Layer

  - Domain을 의존하여 Domain의 Repository를 실제로 구현

  - Data 패싱 로직이 필요하므로 Android와는 의존관계

 

이렇게 사용하였을 때 만약 서버에서 API 요청 형식을 바꾸었다면 Data Layer의 코드만 바꾸면 됩니다.

또, 비즈니스 로직이 바뀌었다면 Domain Layer의 변화만 있으면 됩니다.

 

Dagger2를 사용한 의존성 주입

CleanArchitecture의 도입으로 의존관계가 굉장히 복잡해졌습니다.

Dagger를 활용하여 이러한 의존관계를 간단하게 정리할 수 있었습니다.

Dagger는 DI(Dependency Injection)를 쉽게 할 수 있도록 도와주는 구글에서 만든 라이브러리입니다.

 

 

저는 이러한 리팩토링 과정에서 우아한 형제들 기술 블로그가 정말 도움되었습니다.

 

우아한형제들 기술 블로그

이 블로그는 배달의민족, 배민라이더스, 배민상회 등 Food Tech를 선도하는 우아한형제들 기술조직의 성장 일기를 다루는 블로그입니다./

woowabros.github.io

 

😁 결과

 

새로 추가된 기능

리팩토링 하는 김에 스리슬쩍 끼워넣은 새로운 기능들 입니다.

 

간편 그 자체 👏

 

통계를 기반으로 한 자신의 기본위치를 바로 신청할 수 있게 해주는 기능입니다.

또한 기본 위치를 재설정 할 수도 있습니다.

 

 

분실물 페이지 맞음

 

원래 분실물은 좀 딱딱한 리스트 형식이였는데 사진 여러개 업로드하고 SNS 처럼 조회할 수 있도록 변경하였습니다.

사진 슬라이드도 가능하며 다운로드 할 수도 있습니다. 

 

계층의 변화

 

 

Before & After

 

왼쪽이 V1 이고 오른쪽이 V2 입니다

 

업데이트

 

 

1월 28일, 플레이스토어에 업데이트하였습니다 😎

 

✨ 앞으로 할 일

 

도담도담 T (선생님용 앱) 마무리 작업을 진행해야합니다. 도담도담 T 같은 경우엔 이미 리팩토링은 작업이 끝났고 오류와 테스트가 남아있습니다.

긴 글 읽어주셔서 감사합니다 ☺️