[ios/Android] RIBs에 대해서 알아보자 (Design Pattern)
플라워로드 기술 블로그 : http://blog.flowerroad.ai
Notion Link : https://flyingcorp.notion.site/Design-Pattern-RIBs-e523ddcba2a2479b905b195179a49846
💡 이글은 2021년 2월 27일에 작성되었습니다. 이 페이지는 RIB에 대한 배경, 내용등 이론적인 부분이 정리되어 있고, 모든 내용은 RIBs 공식 페이지에서 발췌,참고,번역해서 본인의 생각을 조금 덧붙여서 작성되었습니다.
RIBs?
정의
R(router) I(interacter) B(builder)
공식 GitHub에서 정의 한 바에 따르면 Cross-Platform architecture Framework이라고 정의 하고 있다. 이 말은 ios이건 android이건 모두 적용할 수 있는 architecture라는건데 여기서 여타의 design pattern인 MVP, MVVM, VIPER등에 과는 조금 다르게 Framework이라는 말을 사용하고 있다.
이는 여타의 design pattern과는 다르게 RIB라는 Framework를 이용해서 이것이 정의하는 design pattern을 생산해낼 수 있다는 의미를 내포하고 있다.
요약
조금 풀어서 설명하자면, MVP의 경우 그냥 model이나 view, presenter를 내가 직접 파일을 만들어서 이해한 방향 대로 한땀한땀 구현하면 그만이나, 이때 발생할수 있는 boilerplate code의 양이 무지막지 하다는것 정도는 다들 알고 있을것이다. RIB는 개념적으로는 여타의 design pattern과 같으나 이런 boilerplate code를 자동으로 generate해주는 무언가를 제공해준다. Framework을 단순하게 해석하면 건물의 "뼈대","골조" 이런걸로 해석될 수 있는데 이런 전체 구조의 기본이 되는 "뼈대"를 generate해서 개발자의 부담을 덜어주기 때문에 Framework이라고 (본인들이) 지징하는것으로 보인다.
Why?
RIBs의 장점은?
사실 RIBs 또한 여타의 design pattern과 최종 목적은 같다. 예를 들어, unit test를 용이하게 해준다거나, component로 잘 분리시킬수 있도록 구조가 되어 있어서 유지보수에 쉽다거나, 새로운 인력이 유입되어도 코드에 적응하는데 시간이 짧은것 등 기존 design pattern의 장점은 대부분 갖추고 있다.
RIBs 공식 사이트에 있는 장점을 요약하자면,
- Cross-Platform
- iOS 와 Android 모두 적용가능하기 때문에 platform과 관계없이 review가 가능.
- Testability and Isolation
- 독립적인 component 형태이기 때문에 테스트 용의.
- 각각의 RIB들은 독립된 형태 및 responsibility 가짐.
- Productivity
- 생산성 향상을 위한 Tool(code 를 generate, memory leak detection, static analysis, runtime integration)의 존재.
- Open-Closed Principle
- 독립된 RIB는 다른 RIB에 영향을 주지 않고 개발이 가능하기 때문에 다수의 개발자들이 하나의 codebase를 공유/개발 하기에 용의.
- 이는 유지/보수 및 신규 개발자의 유입도 용의함.
- Business Logic에 의한 구조
- 기존 UI가 중점이던 코드 구조에서 벗어나 Business Logic에 의한 구조.
- Explicit Contracts
- class의 Dependencies가 명확하지 않은 경우 compile time에 검출.
대부분 여타의 design pattern에서 강조하는 내용과 차이가 없으나 tool의 제공에 의해서 여타의 design pattern보다는 쉽게(?) 개발/오류검출/디버깅/유지 보수가 가능하다고 한다.
그래서 이걸 왜 만든거지?
기본적으로 RIBs도 없던곳에서 반짝 하고 생겨난것은 아니다. 시초는 VIPER (View, interactor, Presenter, Entity, Router)에서 시작 되었고, 기존 형태는 무언가 불합리 하다는 판단 때문에 RIB가 생겨나게된 것이다.
여기서 불합리한 것이란, 결국 VIPER도 모든 행위(action)는 view에서 시작한다는 것이다. 이말은 view가 없으면 business logic 또한 존재 할 수 없는 형태이고, 이것이 불합리하다고 판단해서 view가 없어도 business logic가 동작할 수 있는 형태로 발전한 것이 RIB이다.
MV*/ VIPER과 다른점은?
일단 위에서 말한것 처럼 MVC, MVP, MVI, MVVM, VIPER 이들은 모두 architecture design pattern이고 RIBs는 Framework 라고 불린다(라기 보다는 그들이 부르고 있다).
RIB에서 하나의 RIB모듈은 View를 가지지 않아도 된다. View, Presenter가 business logic에 의에서 필요하다면 생성하고 그렇지 않다면 생성하지 않는 optional 의 형태를 가진다.
Business Logic이 View 계층구조 혹은 tree구조와 완전히 분리 되어 있기 때문에 Business Logic만의 tree 혹은 계층 구조를 가질 수 있는 형태가 가능하다.
Components
Interactor
그림에서와 같이 business logic을 포함하는 중추적인 역활을 한다. 모든 operation의 실행/운영 및 다른 RIB들과의 계층 관계에 대한 연결 결정(parent / child 관계)이 여기서 동작하고, 이 모든것들의 life cycle또한 해당 RIB에 속하게 된다.
Router
Router은 Interactor를 주시하고 이로부터 전달되는 내용에 따라 child의 attaching/detaching을 수행한다.
- interactor logic의 Unit Test에서 child interactor의 Mocking을 하지 않아도 되도록 해주는 역활
- parent와 child interactors 사이에서 abstraction layer의 역활. (non-direct coupling between RIBs)
- 대부분 간단하고 반복적인 boilerplate code들로 구성. (자동 생성 or factory pattern 필요)
Builder
RIB의 구성요소 및 해당 RIB의 child RIBs들의 Builder를 instance화(생성) 하는 역활을 한다.
RIB 를 instance화 할때 parent의 dependency를 주입 하는 형태가 되므로 RIB에서 DI mechanism의 중추적인 역활을 한다.
Presenter
Presenter는 state가 없는 class로서 business model에서 view model로 혹은 그 반대로의 데이터를 주고 받을때 각각의 model에 맞도록 데이터를 조작(?)한다.
초기에 설명한것과 같이 Presenter는 생략이 가능하고 생략했을 경우 데이터조작은 View(controller) 혹은 interactor이 대신할 수 있다.
View(Controller)
View는 다른 여타의 design pattern과 동일하게 UI를 build and/or update, user interaction등의 역활을 한다.
일반적으로 view의 경우 unit test를 필요로 하는 코드를 포함하지 않는다.
Component
Component는 RIB에서 Dependency를 관리하는 역활을 한다. Builder와 함께 RIB를 구성하는 다른 요소들의 instance화 를 하고, RIB를 구성하기 위해서 외부 dependency에 접근 할 수 있도록 하거나, 외부로부터의 Dependency 접근에 대한 관리도 한다.
일반적으로 parent RIB의 component는 child RIB builder에 자신의 dependency를 주입하고(inject), 이는 child가 parent RIB의 dependency에 접근할 수 있도록 해준다.
How it works?
RIB는 자신의 scope내의 state만 결정할 수 있다. 예를 들면, 위의 그림에서 Logged in RIB는 Request, Menu, On trip 의 범위내에서만 state를 결정할 수 있고, 그외의 결정은 해서도 안되고 할 필요도 없다. 여기서 On Trip RIB가 Logged in 의 state를 알아야 하는 일이 있다면, 일단 On Trip RIB는 Logged in의 child RIB이기 때문에 자연스럽게 Logged in state라는 것은 알수 있다. 이 외에 Logged in RIB가 user billing state를 가지고 있고 On trip RIB가 이를 알아야 하는 상황이 있다면 On Trip RIB가 생성될때 DI에 의해서 해당 state의 접근 권한을 가지면 된다.
RIB는 모든 state를 저장해둘 필요가 없다. 예를 들어, user의 상태가 변경되었다는 데이터를 Logged in RIB가 받았다고 하면(network를 통해서), 이때 attach or detach되는 RIB는 없고, 해당 데이터는 model내에 저장될것이다. 그리고, 이 변경된 user상태에 대한 read access는 이를 필요로하는 child RIB에게 DI형태로 전달 되게 된다.
각각의 RIB내애서 view state, business state등을 정의 해서 운영을 할수 있으나, 전체의 RIB들을 강제하거나 나 자신의 state가 아닌 다른 RIB의 state를 강제로 변경하는 것은 내용은 없다.
Interaction
Interactor가 business logic 에 대한 동작을 할때 다른 RIB들에게 event나 data등을 전달해야 하는경우가 있다. RIB Framework는 RIB간의 양방향 interaction을 가능하게 하는 방법을 제공한다.
Downward (parent RIB to child RIB)
parent RIB에서 child RIB로의 데이터/이벤트 등의 전송/전달은 DI로 부터 전달된 내용을 parameter로 받거나 Rx stream 을 subscribe하는 형태로 전달 할 수 있다.
Upward (child RIB to parent RIB)
child RIB에서 parent RIB로의 데이터/이벤트 전달은 여타의 design pattern에서 class module간의 통신과 비슷한 방식을 사용한다. child RIB가 builder로 부터 생성될때 연결된 전달 받아서 연결된 listener interface를 이용해서 parent RIB로 전달이 가능하다.
Tools
- Code Generation : 기본이 되는 IDE plugins이고 RIB의 기본적인 코드, test base code를 생성해준다.
- NPE Static analysis(android) : NullPointExceptions 분석툴
- Autodispose Static Analysis(android) : memory leak를 분석해주는 툴
Ref
- https://github.com/uber/RIBs
- https://github.com/uber/RIBs/wiki
- https://github.com/uber/RIBs/wiki/ios-specific-questions