티스토리 뷰

공부

RIBs란?

Zedd0202 2020. 1. 10. 10:23
반응형

 

 

안녕하세요 :) Zedd입니다.

오늘은 RIBs에 대해서 공부해보려고 해요.

그동안 컨퍼런스/세미나에서..많이 들었었는데, 특히 민소네님에게!

본격적인 공부는 처음이네요. 오늘은 본격적인 튜토리얼로 들어가기전에 그냥 RIBs가 어떤건지 공부해보려고 해요.

글 읽으시다가 틀린부분이 발견된다면 댓글로 알려주시면 감사하겠습니다.

 

RIBs

 

RIBs는 Uber에서 만든 cross-platform 모바일 아키텍쳐 프레임워크입니다!

 

 

uber/RIBs

Uber's cross-platform mobile architecture framework. - uber/RIBs

github.com

 

cross-platform이라는 말에 맞게..

 

 

 

android와 ios 둘 다 지원합니다. 

 

그럼 RIBs가 어떤건지 자세히 알아봅시다.

RIBs의 "RIB"은 Router, Interactor 그리고 Builder의 약자입니다. 

RIBs는 많은 엔지니어(개발자) + nested states(중첩된 상태)의 모바일 앱을 위해 설계되었다고 해요.

("많은" 엔지니어......)

 

RIBs로 다음과 같은 일들을 할 수 있는데요.

 

- Encourage Cross-Platform Collaboration: 앱의 복잡한 부분은 iOS, Android에서 비슷비슷하잖아요? RIBs는 iOS, Android에 대해 유사한 개발 패턴을 제공하기 때문에, 공동으로 디자인된 단일 아키텍쳐를 iOS, Android간에 공유할 수 있어요. 그러면 iOS <-> Android 팀이 비즈니스 로직 코드를 교차 검토 할 수 있는 장점이 생기겠죠!?

 

- Minimize Global States and Decisions: Global state변경은 예측할 수 없는 동작을 유발하며, 이걸 변경했다고 해서 이 변경이 미칠 영향들을 엔지니어가 전부 알 수 없습니다! RIBs는 잘 격리된(well-isolated) 개별 RIBs의 deep hierarchy내에서 상태를 캡슐화하여, Global state 문제를 방지하도록 권장합니다. 

 

Testability and Isolation: Class는 Unit Test가 쉬워야합니다. 개별 RIB클래스에는 별도의 "책임"이 있는데요. 이 책임에는 라우팅, 비즈니스 로직, 뷰 로직, 다른 RIB 클래스 생성 등이 있어요.
부모 RIB 논리는 자식 RIB 논리와 분리되어(decoupled) 있습니다. 따라서 RIB 클래스를 쉽게 테스트하고 독립적으로 추론(?)할 수 있습니다. 

 

- Tooling for Developer Productivity: non-trivial(사소한?) 아키텍쳐 패턴을 채택한다고 해서 강력한 툴링(tooling) 없이는 소규모 어플리케이션을 넘어서 확장되지 않습니다. RIBs에는 코드 생성, 정적 분석 및 runtime integrations에 대한 IDE 툴링이 함께 제공되며, 이 툴은 크고 작은 팀의 개발자 생산성을 향상시킵니다!

 

- Open-Closed Principle: 개발자는 가능하면 기존 코드를 수정하지 않고 새로운 기능을 추가 할 수 있어야 합니다. RIBs를 사용하면 몇 군데서 볼 수 있죠. 예를들어 부모 RIB을 거의 변경하지 않고 부모의 종속성이 필요한 복잡한 자식 RIB을 attach하거나 build할 수 있습니다.

 

- Structured around Business Logic: 앱의 비즈니스 로직 구조는 UI의 구조를 엄격하게(또는 절대적으로) 반영할 필요는 없습니다. 예를들어 애니메이션 및 view 성능을 용이하게하기 위해서 View 계층 구조는 RIB 계층 구조보다 더 얕을 수(shallower) 있습니다. 

또는 단일 기능 RIB이 UI의 다른 위치에 나타나는 세가지..View의 모양을 제어할 수 있습니다. 뭔소리야 세가지 View가 왜나와 그냥 예를 든건가..? 진짜 three views라구 나옴..

 

- Explicit Contracts: Requirements(요구사항)은 compile-time safe contracts(컴파일 타임 안전 계약.....???)으로 선언해야합니다.

클래스 종속성과 순서(ordering) 종속성이 충족되지 않으면 클래스는 컴파일되지 않아야 합니다. 우리는 ReactiveX를 사용하여 순서 의존성을 나타내며, 클래스 의존성을 나타내기 위해 type safe dependency injection (DI) 시스템과 data invariants의 생성을 장려..하기 위해 많은 DI scopes를 나타냅니다. 

 

 

 

 

자..뭐 많은 것들을 할 수 있는 것 같아 보이네요. 아직 완전히 이해가 가진 않지만...

사실 VIPER를 사용해보신 분이라면, RIBs가 조금 익숙하실거에요.

RIB은 일반적으로 다음과 같이 구성되며 모든 요소는 자체 클래스로 구현됩니다. 

 

 

출처 https://github.com/uber/RIBs/wiki

 

 

엥 분명히 Router, Interactor 그리고 Builder의 약자랬음서 뭐가 좀 많네? 라고 생각하실 수 있는데!

잘 보시면 Presenter와 View는 optional입니다. Component는 저도 지금은 잘 모르겠네요!?

 

각 요소들을 볼게요.

라우터부터 오른쪽으로 보고싶은데..문서에는 순서가 좀 다르네요. 문서 순서를 따르겠습니다!

먼저 Interactor. 

 

Interactor

Interactor는 비즈니스 로직을 포함한다고 해요.

 

Interactor에서

 

- Rx subscriptions을 수행

- 상태 변경 결정을 내림

- 데이터를 저장할 위치 결정

- 다른 RIB을 자식으로 붙힐 위치 결정

 

을 한다고 해요. 

Interactor가 수행하는 모든 작업은 반드시 lifecycle에  국한되어야 합니다. Interactor가 활성화 된 경우에만 비즈니스 로직이 실행되도록 툴을 구축했다고 해요. 이렇게하면 Interactor가 비활성화되는 시나리오는 방지되지만 subscriptions이 계속 발생하여 비즈니스 로직 또는 UI상태에 원치 않는 업데이트가 발생합니다. (-> 그니까 Interactor가 수행하는 모든 작업은 반드시 lifecycle에  국한시켜라!! 라는 뜻이겠죠?)

 

 

 

Router

Router는 Interactor를 듣고..?(listens) 출력을 하위 RIBs에 연결(attaching) 및 분리(detaching)로 변환합니다. Router는 다음과 같은 3가지의 간단한 이유로 존재합니다. 

1. Router는 Humble Objects(겸손한 객체...) 의 역할을 하여, 하위 Interactor를 mock하거나 그 존재에 대해 신경 쓸 필요없이 복잡한 Interactor 로직을 쉽게 테스트 할 수 있습니다. 

(Humble Objects가 대문자로 쓰였길래..뭔가 있나하고 찾아봤는데 Humble Objects pattern이란게 있는 것 같아요. 

Humble Object 는 Humble Object는 테스트 가능한 객체를 감싸는 wrapper로,  테스트하기 어려운 객체의 로직을 비용면에서 효율적인 방식으로 가져오는 방법입니다.

 

출처 : http://xunitpatterns.com/Humble%20Object.html

 

Router가 Humble Objects의 역할을 한댔으니 테스트 가능한 객체를 감싸는 wrapper정도로 생각하면 되는건가..!? 

 

2. Router는 상위 interactor와 하위 interactor간에 추가 추상화 계층을 만듭니다. 이로 인해 interactor간의 동기 통신이 조금 더 어려워지고, RIBs간의 직접 연결 대신 reactive communication 채택이 권장됩니다. 

 

3. Router에는 interactor가 구현할 수 있는 단순하고 반복적인 라우팅 로직이 포함되어 있습니다. 이 boilerplate code를 제외하면 interactor를 작게 유지하고 RIB이 제공하는 핵심 비즈니스 로직에 더 집중 할 수 있습니다. 

 

 

 

Builder

Builder의 책임은 RIB의 각 구성요소 클래스/children을 위한 Builder를 인스턴스화 하는 것입니다.

 

Builder에서 클래스 작성 로직을 분리하면 iOS에서 mockability에 대한 지원이 추가되고, 나머지 RIB코드는 DI구현의 세부사항에 영향을 미치지 않습니다. Builder는 프로젝트에서 사용된 DI 시스템을 인식해야하는 RIB의 유일한 부분입니다. 다른 Builder를 구현하면 다른 DI 메커니즘을 사용하여 프로젝트에서 나머지 RIB코드를 재사용 할 수 있습니다. 

 

 

 

Presenter(Optional)

Presenters는 비즈니스 모델을 ViewModel 또는 그 반대로 변환하는 stateless class입니다. viewModel변환 테스트를 용이하게 하는데 사용할 수 있습니다. 그러나 종종 이건 너무 trivial해서 전용 Presenter class를 만들지 않습니다. 

Presenter가 생략되면 ViewModel변환은 View(Controller) 또는 Interactor의 책임이 됩니다.

 

 

 

View(Controller)(Optional)

 View는 UI를 빌드하고 업데이트합니다. 여기에는

- UI 구성요소 인스턴스화 및 레이아웃

- 사용자 상호작용 처리

- UI 구성 요소에 데이터 채우기 및 애니메이션

 

이 포함됩니다. View는 가능한 말이없는(dumb)것으로 설계되었습니다. 단지 정보를 표시 할 뿐입니다. 일반적으로 Unit Test가 필요한 코드는 포함되어 있지 않습니다.

 

 

 

Component

Component는 RIB종속성을 관리하는데 사용됩니다. 

RIB을 구성하는 다른 Unit을 인스턴스화하여 Builder를 지원합니다. Component는

- RIB을 구축하는데 필요한 외부 종속성에 대한 액세스를 제공

- RIB자체에서 생성된 종속성을 소유 

- 다른 RIB에서 위에 대한 액세스 제어

를 한다고 합니다. 부모 RIB의 Component는 일반적으로 자식 RIB의 Builder에 주입되어 자식에서 부모 RIB의 종속성에 대한 액세스 권한을 부여합니다. 


State Management

앱의 state는 largely manage되고 현재 RIB트리에 연결된 RIBs로 표시됩니다.

예를들어 단순한 ride sharing app에서 사용자가 다른 state를 진행함에 따라

앱은 다름 RIB을 attattaches / detaches합니다.

 

 

Example of state transitions in which lines denote RIB hierarchy.

 

위 파일은 GIF에요! 

보시면 사용자가 어떤 state를 가지느냐에 따라 RIB이 저렇게 활성화 되는게 RIB이 attach되는거라고 보면 될 것 같고,..회색처리 된게 detaches라고 보면 되는거겠죠..!?!

 

RIB은 해당 범위 내에서만 state decisions을 내립니다.

예를들어 LoggedIn RIB은 Request 및 OnTrip과 같은 상태 간 전환에 대해서만 상태를 결정합니다. OnTrip화면에서 작동하는 방법에 대해서는 결정하지 않습니다.

 

RIBs를 추가하거나 제거하여 모든 상태를 저장할 수 있는것은 아닙니다.

예를들어 사용자의 프로필 설정이 변경되면 RIB이 연결되거나 분리되지 않습니다. 

일반적으로 이 상태는 세부사항이 변경될 때 값을 다시 방출하는 불변 모델 스트림(streams of immutable models)에 저장됩니다.

사용자 이름은 LoggedIn 범위 내에 있는 ProfileDataStream에 저장될 수 있습니다. 

오직 네트워크 응답만이 스트림에 대한 쓰기 권한을 갖습니다. 

DI 그래프 아래로 이러한 스트림에 대한 읽기 액세스를 제공하는 인터페이스를 전달합니다. 

 

RIB에는 RIB state를 위해 source of truth를 강요하진 않습니다! 이건 React같은것들과 대조적이죠.

각 RIB의 context내에서 단방향 데이터 흐름을 촉진하는 패턴을 채택하거나 효율적인 플랫폼 애니메이션 프레임워크를 활용하기 위해 비즈니스 state 및 view state를 일시적으로 분산시킬 수 있습니다.

 

 

 

Communication Between RIBs

Interactor가 비즈니스 로직을 결정 할 때, 다른 RIB에 completion과 같은 이벤트를 알리고 데이터를 보내야 할 수도 있습니다. RIBs프레임워크에는 RIB간에 데이터를 전달하는 방법이 포함되어있진 않지만 그럼에도 불구하고 일반적인 패턴을 용이하게 하기 위해 만들어졌습니다. 

 

일반적으로 커뮤니케이션이 child RIB으로 하향 전달되는 경우, 이 정보를 Rx 스트림으로 전달합니다. 또는 데이터가 child RIB의 build()메소드에 대한 parameter로 포함될 수 있으며, 이 경우 이 parameter는 child의 수명(lifetime)동안 변하지 않습니다. 

 

Rx를 통한 하향 통신(downwards communication)의 예. 선은 RIB계층을 나타냅니다. 

 

커뮤니케이션이 parent RIB의 Interactor로 진행하는 경우(상향통신이라는 말이겠죠..?),parent가 child보다 오래 사용 할 수 있으므로 listener 인터페이스를 통해 커뮤니케이션이 수행됩니다. 

parent RIB 또는 해당 DI 그래프의 일부 객체는 listener 인터페이스를 구현하고 child RIB이 이를 호출할 수 있도록 DI그래프에 배치합니다. parent가 child의 Rx스트림을 직접 구독하는 대신 이 패턴을 사용하여 데이터를 위쪽으로 전달하면 몇가지 이점이 있습니다. 

memory leak을 방지하고 어떤 child가 attach되어있는지에 대한 지식없이 parent를 작성/테스트 및 유지관리 할 수 있습니다. 또한 child RIB을 attach/detach하는데 필요한 의례(?)의 양(amount of ceremony)을 줄입니다. 

child RIB을 이런 방식으로 연결할 때, Rx 스트림 또는 listener를 등록 취소/재등록 할 필요가 없습니다.

 

listener 인터페이스와의 상향통신(upwards communication)의 예. 

 

 

오 RIBs 리드미 밑에 RIBs와 MV*/VIPER와의 차이점에 대한 글도 있네요.

 


MVC, MVP, MVI, MVVM 및 VIPER는 아키텍쳐 패턴.

RIBs는 프레임워크.

 

MV*/VIPER를 기반으로 하는 프레임워크와 RIBs의 차이점은 

 

view tree가 아닌 비즈니스 로직이 앱을 주도합니다. MV*/VIPER와 달리, 하나의 RIB은 view를 안가져도 됩니다.

이는 app hierarchy가 view tree가 아닌 비즈니스 로직에 의해 주도됨을 의미합니다.

 

독립적인 비즈니스 로직 및 view tree. RIBs는 비즈니스 로직 범위가 view hierarchy에서 구성되는 방식을 분리합니다. 이를 통해 앱은 비즈니스 로직 노드를 격리하는 심층 비즈니스 로직 트리..?(deep business logic tree)를 가질 수 있으며, shallow view hierarchy 구조를 유지하면서 레이아웃, 애니메이션 및 transition을 쉽게 할 수 있습니다. 


 

흠 아직 립스 코드를 안봐서..아직 문서가 잘 이해가 가는건 아니지만

나중을 위해 한번 정리하고 싶었어요!

 

그럼 저는 튜토리얼을 하러 가겠습니다..

https://github.com/uber/RIBs/wiki/iOS-Tutorial-1

 

uber/RIBs

Uber's cross-platform mobile architecture framework. - uber/RIBs

github.com

 

반응형