안녕하세요 한끼족보 Android Lead 개발자 박동민입니다.
오늘은 제가 한끼족보 프로젝트를 기획하며 도입하기로 결정한 기술들과, 그 기저에 깔려있는 근거들을 소개합니다.
저는 코드 한줄에도 의도를 담고자 노력하고 있습니다.
이유없는 코드라면 그 이유를 찾으려 노력하고, 잘못된 의도였다면 코드를 수정하는 과정을 거치며 프로젝트의 모든 곳에 근거를 넣으려 합니다. 이러한 관점을 이어나가 코드 뿐만 아니라 구조를 만들고, 기술을 도입하는 모든 과정에도 정당한 이유를 찾고자 노력했고, 그 노력들을 공유하려 합니다.
개발자라면 최신기술과 트랜드를 따라가야 하는 것은 당연합니다. 프론트 개발자라면 더욱이.
물론 기존 기술을 능숙하게 사용할 줄 알고 최신기술을 공부하는 관점이라면 좋다고 생각하지만, 프로젝트를 반드시 최신기술로만 해야 한다라고 생각한다면 그건 절대아니다 라고 하고싶습니다.
옛날 기술이 좋은 프로젝트도 있고, 최신기술은 아직 지원하지 않는 기능을 요구하는 프로젝트도 있습니다.
그렇기 때문에 단순히 "최신기술이니까", "요즘 이게 트랜디하니까" 라는 이유로 기술을 선택하지는 않았으면 좋겠습니다.
XML vs Compose
Android를 Native로 개발할 때는 크게 2가지 방법이 있습니다.
기존의 방법이던 XML과 선언현 UI인 Compose 중 고민하다 최종적으로 Compose를 채택했습니다.
1. 개발 효율성 with Appjam
선언형 UI라는 장점은 포기할 수 가 없었습니다.
선언형 UI로 인해 눈에 띄게 줄어드는 코드의 수와 직관적인 로직은 저희 팀의 개발속도를 향상시켜주고, 효율적인 개발이 가능하게 해줄 것이라 생각했습니다.
이 선택은 저희 프로젝트가 SOPT의 장기해커톤인 Appjam에서 개발되었다는 것이 큰 영향을 끼쳤습니다.
Appjam은 SOPT의 5주 장기해커톤으로, 해당 기수의 마지막에 진행됩니다.
5주의 기간이지만, 실질적인 개발 기간은 2주에서 많아야 3주밖에 되지 않습니다.
특히나 클라이언트의 경우 UI가 나와야 개발이 가능하므로 시간적인 리미트가 존재합니다.
그렇기 때문에 작업 속도를 빠르게 할 수 있는 선언형 UI인 Compose가 적합하다 판단하였습니다.
2. 숙련도
위에 말한 것 처럼 시간적 한계가 없더라도 Compose를 사용했을 것입니다.
저는 Android 공부를 Compose로 먼저 시작했습니다.
그렇다보니 XML보다 Compose가 더 익숙하고, 더 좋은 코드를 작성할 수 있다고 생각합니다.
물론 제가 팀원이었다면 XML인 프로젝트여도 기쁜마음으로 참여할 수 있었을 것입니다. 하지만, 저는 리드개발자로 팀원을 이끌고 도움을 줘야하는 의무가 있었습니다. 그렇기 때문에 제가 더 잘 알고, 쉽게 설명할 수 있는 방법을 선택하는게 저 자신 뿐만 아니라 팀원을 위해서도 좋은 방법이라 판단하였습니다.
3. 손쉬운 컴포넌트화
과거 XML로 컴포넌트를 만들고자 했을 때 include를 사용하거나, customView를 써왔었습니다.
물론 그렇게 구현해도 정상적으로 잘 돌아가지만, 귀찮다고 느껴질 정도의 shape파일과 style파일을 많이 만들어야했었습니다.
또한 include와 customView를 사용할 때면 복잡한 부분이 많다고 느껴졌었습니다.
하지만 compose를 사용하게 된다면 더욱 편하게 컴포넌트를 만들 수 있고, 재활용 하기가 편해집니다.
그렇기 때문에 지속가능한 개발을 위해 컴포넌트화가 쉬운 Compose를 선택하게 되었습니다.
MVC vs MVVM vs MVI
안드로이드 개발 방법을 정했으니 이제는 어떤 아키텍쳐를 도입할 지 고민이었습니다.
첫 번째로 제외시킨 아키텍쳐는 MVC였습니다.
절대 나쁜 아키텍쳐는 아니었지만, 저희 프로덕트의 성질상 제외하기로 결정하였습니다.
제일 큰 영향을 끼친 부분은 Home View였습니다.
home 화면에서는 많은 state를 관리해야했고, 다양한 event들이 있습니다.
또한 서버통신 또한 빈번하게 발생해야 했으며, 화면 전환(navigation)도 많습니다.
물론 home 뿐만 아니라 다른 뷰들 또한 꽤나 복잡한 기능들이 많았습니다.
그렇기 때문에 MVC로 하게 된다면 너무 많은 책임이 Controller에 부과될 것 이라는 생각이 있었고, 이는 유지보수가 힘들어지는 유연하지 못한 개발이 될것이라는 판단하에 MVC를 제외하였습니다.
MVVM과 MVI 중 고민하다 저는 결국 MVI를 사용하기로 하였습니다.
1. 많은 State와 획일화된 관리법
앞서 말했듯 한끼족보에서는 State가 많습니다.
그렇기 때문에 MVVM보다는 MVI가 더 State를 관리하기에 용이하다는 생각이 있었습니다.
그 이유로 MVI는 단방향의 의사 과정을 거칩니다.
이러한 방향으로 순환하기 때문에 상태의 업데이트가 획일화 되고, 모든 state의 흐름이 단방향으로 고정되어 추후 추적하기에 편해집니다.
현재 코드를 작성하기에도, 추후 유지보수 하며 관리하기에도 데이터가 단방향으로 흐르는 것이 좋다고 판단하였습니다.
2. Compose와 MVI의 궁합
Compose를 써보신 분들은 아시겠지만 MVI와 궁합이 상당히 좋습니다.
물론 MVVM을 쓴다고 해서 별로라는 것은 아닙니다.
하지만 Compose의 경우 Recomposition이 발생하고, 그 기준으로 state를 사용하는게 너무나 편합니다.
또한 SnackBar라거나, Navigate의 경우 SideEffect를 발행하여 StateHoisting해둔 Main화면에서 구독하는 구조 또한 너무나 이상적입니다.
그렇기 때문에 Compose를 사용한다면 MVI를 사용하는 것이 오히려 "편하다"라는 생각이 들 정도로 궁합이 좋다고 생각합니다.
Google Recommanded Architecture vs Clean Architecture
둘다 많이 들어봤고, 많은 앱들이 둘중 하나를 선택해서 사용하고 있습니다.
그 중 저희는 Clean Architecture를 사용했습니다.
구글 권장 아키텍쳐의 경우 정말 직관적입니다. 사람이 쉽게 생각하는 대로 정방향으로 데이터의 흐름이 이동되고 진행됩니다.
하지만 클린아키텍쳐의 경우 UI는 Domain으로, Data도 Domain으로 흐르며 의존성 역전이 발생합니다.
이로 인해 이해에 어려움을 겪을 수 있고 러닝커브도 높기로 유명합니다.
이런 단점이 있지만, 저희가 Clean Architecture를 선택한 이유는 아래와 같습니다.
1. 레퍼런스가 많다
아직 저희는 학생 개발자이고, 모든것을 안다고 말할 수 없는 상태입니다. 오히려 대부분을 몰라서 배워간다 라고 해야 맞습니다.
그렇기 때문에 공식문서 뿐만 아니라 많은 레퍼런스가 있는 것이 저희에게 큰 버팀목이 되어줄 것이라 생각했습니다.
레퍼런스가 많기 때문에 팀원들이 공부하고 적용하기에도 도움이 될 것이라 생각했습니다.
팀이 만들어지고 제가 팀원들에게 클린아키텍쳐를 가르쳐 주었지만 한번에 이해하는것은 불가능 하다는 것을 너무나 잘 알고있었습니다.
그래서 레퍼런스도 제공해주고, 아티클도 작성해줬지만, 제 설명 뿐만 아니라 다른사람의 설명을 보는 것도 큰 도움이 될 것이라 생각하였습니다.
2. 정형화된 구조
깃허브에서 구글 권장 아키텍쳐를 적용한 프로젝트를 보다보면 프로젝트마다 약간씩 다른 것을 볼 수 있습니다.
그 이유로 저는 Domain의 Optional한 성질때문이라 생각합니다.
어떤 프로젝트에서는 Domain을 사용하고, 어떤 프로젝트에서는 사용하지 않으니 구조가 달라지게 됩니다.
이런 상황에 위에 언급한 레퍼런스가 상대적으로 적다는 부분까지 합쳐진다면 새로 학습하는 입장에서는 어디를 따라해야하는지, 어떻게 학습해야하는지 막막해집니다.
반면 Clean Architecture의 경우 레퍼런스가 많고, 엄격한 규칙을 적용시키고 있기 때문에 대부분의 프로젝트가 정형화된 구조를 가지고 있습니다. 그렇기 때문에 타 프로젝트를 참고하기도 좋고, 공부하기도 좋다고 판단하였습니다.
Single Module vs Multi Module
이 부분에 대해서는 약간의 후회가 있는 부분입니다.
우선 저희가 선택한 방식은 Multi Module입니다.
1. 확장성
한끼족보는 지도, 대학교, 식당 이라는 큰 세가지 도메인이 있습니다.
이 세가지 도메인의 경우 앞으로 뻗어나갈 수 있는 방향이 무궁무진하다고 생각합니다. 지금은 단순히 대학별 가성비 식당들을 모아서 지도로 소개만 해주고 있지만, 추후 대학에서도 가지가 뻗어나올 수 있고 지도와 식당에서도 새로운 기능이라는 가지가 뻗어나올 수 있는 주제입니다. 뿐만 아니라 TL과 팀원 모두가 프로덕트에 대한 애정이 있고, 앞으로의 발전 방향성 및 가능성을 보았을 때에도 충분히 지금의 구조에서 더 커질 수 있다고 생각했습니다. 추후 확장성이 무궁무진 하기 때문에 멀티모듈을 적용하여 확장하기 좋도록 하였습니다.
2. 코드의 제약성
이게 장점이라고 생각한다는 것에 의문점을 가질 수 있습니다. 하지만, 제약조건이야 말로 초심자에게 필요하다고 생각합니다.
제가 처음 클린아키텍처를 적용했을 때 presentation에서 data에 접근하는 경우가 빈번하였습니다. repositoryImpl이나 dataStore는 네이밍부터 차이가 크기때문에 그런 실수를 하기 쉽지 않지만 Dto, Entity, Model의 경우 머릿속에서 정리가 되어있지 않다면 실수로 접근하기가 너무나 쉽습니다. 또한 Domain에 서드파티의 종속성을 넣는 등 클린아키텍처에 위배되는 코드를 작성할 때가 많았습니다.
실제로 저 뿐만 아니라 저와 같이 공부를 했던 친구들에게서도 심심치 않게 볼 수 있는 코드였습니다.
만약, 애초에 접근이 불가능하다면? 실수를 하고싶어도 할 수가 없습니다.
그렇기 때문에 공부하며 코드를 작성하는 저희팀에게 큰 장점이 될 수 있을 것이라 생각했습니다.
+ Project Convention Plugin
멀티모듈을 도입한다면 필수로 도입해야하는 개념입니다. 물론 하지 않아도 문제는 없지만, 추후 버전이 변경된다면 너무나 많은 공수가 들어가기 때문에 멀티모듈을 적용한다면 꼭 적용하기를 바랍니다.
쉽게 설명한다면 Gradle을 Custom하여 원하는 곳에 넣어서 사용하는 방법입니다. 그렇게 된다면 중복 코드를 없앨 수 있고, 추후 수정사항이 생기더라도 반영하기가 쉽습니다.
이렇게 설명만 들으면 정말 좋은 기능이고, 정상적으로 적용한 것 같은데 왜 후회한다고 말했을까요?
위 그림이 저희 한끼족보의 dependency 그래프입니다.
딱 보기만 해도 너무너무 복잡합니다. core에 해당하는 부분도, feature도, data도, domain도. 전부 멀티모듈로 나누어뒀기 때문에 프로젝트 그래프가 너무나 복잡하고, 이해하기 어렵게 되었습니다.
또한 제가 100퍼센트 이해를 하고 우리 프로젝트의 최선의 구조를 작성한 것이 아닌, 현재 상태에서의 최선의 구조를 찾아가며 작성하다보니 다소 난잡하고 불필요한 dependency가 연결되어있습니다.
처음에는 Single Module로 구현하고, 우리 프로젝트의 구조와 멀티모듈에 대해서도 완전히 이해한 후 리펙토링 과정을 통해 하나씩 분리했다면 어땠을까 라는 생각을 하였습니다. 그랬다면 조금 더 구조적으로 좋은 코드를 작성할 수 있었지 않을까 라는 아쉬움이 있습니다. 물론 지금도 맘에 들긴 하지만요 ㅎ.ㅎ
Kotlin 2.0
구조를 어떻게 구축할지 고민을 끝내고 프로젝트를 만들려고 보니 정말 당연하게 생각하던 Kotlin버전이라는 관문이 남았었습니다.
사실 간단하게 생각하면 1.9.x를 사용하면 되는 부분입니다. Kotlin 2.0은 출시된지 얼마되지 않았고(24/05/21), 1.9.x 버전을 사용한다면 고민할 것도 딱히 없었을 것입니다. 하지만 저희는 당당히 Kotlin 2.0을 도입하기로 했습니다.
1. Jetpack Compose Compiler가 Kotlin의 관리를 받게 되는 첫 버전
이전까지는 Kotlin 버전이 바뀌고 나서야 Compose Compiler의 버전 변경이 일어나게 되어 그 간격동안 최신버전을 사용하지 못하는 간극이 있었습니다. 하지만 이번 버전부터 Compose의 버전관리는 Kotlin과 같이 이뤄지게 됩니다. 그렇게 된다면 개발자 생산성 향상으로 이어질 것입니다. 따로 관리하지 않더라도 자동으로 둘 다 된되어 신경을 따로 쓰지 않아도 된다는 것 또한 장점입니다.
2. Strong Skipping Mode
Strong Skipping Mode가 사용이 가능해집니다.
Compose로 개발을 하다보면 가장 신경을 써야하는 부분이 Recomposition입니다. 이 부분에 스트레스를 많이 받고 코드 품질이 저하되기 쉽습니다. 하지만 Strong Skipping Mode가 적용이 된다면 어느정도는 자동으로 Recomposition이 되는 것을 막아줍니다.
물론 꼭 필요한 부분은 정상적으로 잘 돌아갑니다. 다만 mutable하여 리컴포지션의 대상이 되는 것을 Stable, Immutable하게 바꿔주어 연산의 대상에서 제외해줍니다.
결론적으로 성능적인 향상 또한 이끌어낼 수 있습니다.
3. 생각보다 크지 않은 변화
사실 Kotlin의 버전이 바뀐다고 해서 드라마틱한 변화가 생기지는 않습니다. 물론! 변화가 그렇다고 적지는 않습니다 ㅋㅋ
프로젝트를 구성하는 부분에서만 조금 고생을 한다면 단순히 코드를 작성한다면 바뀌는 부분은 거의 없다고 느껴질 정도입니다.
고생하는 부분은 제가 하면 되고 팀원들이 사용하기에는 변화가 없다고 느껴지기 때문에 나만 고생하면 팀에 도입하는 것에는 문제가 없다고 생각했습니다.
말한대로 고생을 좀 오래하긴 했지만 그래도 droidknight, nowinandroid, sopt-makers 등 우수한 자료와 제 스승님의 너무나 좋은 아티클과 함께 했기 때문에 도입하는데 문제가 없었습니다.
물론 제가 사용한 기술이 정답이다! 따라해라! 이게 아니라 제 생각과 근거들을 공유하며 근거있는 코드의 중요성을 이야기하고자 했습니다.
제가 장점이라고 한 부분이 누군가에게는 단점이 될 수도 있고, 제가 단점이라 생각한 부분이 누군가에겐 장점이 될 수도 있습니다. 각자 팀별로 자신의 팀에 맞는 구조와 코드를 선택한다면 그것이 가장 좋은 개발이라 생각합니다.
저희 팀의 코드가 궁금하시다면 hankki-android Github에서 구경 많이 해주시길 바랍니다 :)
'Android' 카테고리의 다른 글
MVI 아키텍처 패턴이란? (2) | 2024.09.17 |
---|---|
저는 Developer가 아닙니다 (1) | 2024.08.24 |
힐트, 클린아키텍처 그리고 로그인 리이슈... (1) | 2024.08.07 |
UI도 두들겨 보고 건너라 - modifier 알아보기 (1) | 2024.08.07 |