RIBs Tutorial - 0
을 학습 & 정리해보겠습니다.
RIBs는 Uber에서 만든 cross-platform 아키텍처 프레임워크 입니다.
중첩된 state를 포함하는 규모가 큰 모바일앱을 위해 디자인 되었습니다.
RIBs를 사용하면 얻어지는 이점으로는...
cross-platform 협업 장려_
🐸 (솔직히 이건 너무 꿈같은 얘기 같습니다)
전역 상태(Global State) 최소화
- RIBs는 격리된 개별 RIBs의 깊은 계층 구조 내에 State를 캡슐화하는것을 장려해서 전역 상태를 최소화 할 수 있습니다.
🐸 (음..로직을 잘게 나눠 코드 간 의존성과 사이드이펙트를 최대한 줄일 수 있다는 말 같습니다.)
테스트용이성과 격리성
- UnitTest를 위해서는 Class가 격리된 환경에 있어야 하고, 개별 RIB 클래스는 각자 구문된 책임(라우팅, 비즈니스 로직, 뷰 로직, 다른 RIB 클래스 생성 등)을 갖고 있습니다.
🐸 ( RIBs를 구성하는 요소는 각자 맡은 역할이 명확히 구분되어있고, 의존성이 적어 Unit Test가 용이하다는 뜻 같습니다)
템플릿 제공
Open-Closed 원칙(개방-폐쇄 원칙)
- 기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있어야 합니다. 예를 들어, 부모 RIB을 거의 수정하지 않고 부모로부터 Dependency가 필요한 복잡한 자식 RIB을 추가하거나 구축할 수 있습니다.
🐸 ( RIB간에 똇다 붙였다가 자유롭나 봅니다)
비즈니스 로직을 중심으로한 구조화(Structured)
- 앱의 비즈니스 로직 구조는 UI 구조와 일치할 필요가 없습니다.
🐸 (RIBs는 비즈니스 로직을 중심으로 viewless RIB도 만든다 하니 그 얘기인 것 같습니다)
Explicit Contracts(명시적 계약)
- Requirements는 compile 타임에 안전하게 선언되어야 합니다. 클래스는 해당 클래스의 dependency와 ordering dependency가 충족되지 않으면 컴파일 되지 않아야 합니다.
Ordering dependency를 나타내기 위해 ReactiveX를 사용하고, class dependency를 관리하기 위해 dependency Injection(DI)를 사용합니다.
🐸 (DI와 RxSwift를 사용하고, RIB을 구성하는 Dependency가 충족하지 않으면 빌드할 수 없게 설계해 오류를 방지한다는 말 같습니다)
RIBs는 일반적으로 이 구성요소들로 이루어져있으며, 각 구성은 클래스로 구현됩니다.
비즈니스 로직을 포함한다.
Rx 구독을 수행한다.
state 변경을 결정한다.
어떤 데이터를 어디에 저장할지 결정한다.
어떤 다른 RIB을 Child로 attach 할지 결정한다.
Interactor에서 수행되는 모든 로직은 Interactor의 생명주기에 제한 되어야합니다.
따라서, RIBs는 비즈니스로직이 Interactor가 Active 상태일 때만 실행되도록 보장하는 툴을 만들었습니다.
(🐸 자동으로 된다는 소리)
Routing 로직에 대해 신경 쓸 필요 없이 Interactor의 테스트를 쉽게 하기 위해 (for mockup)
Parent RIB의 Interactor와 Child RIB의 Interactor에 Router라는 추상화 계층을 생성해서 Interactor간의 직접 통신을 어렵게 만든다.
-> RIB간의 직접 결합 대신 반응형(Reactive) 통신을 유도한다.
Interactor에서 Routing이라는 보일러플레이트 로직을 빼서 비즈니스 로직에 집중하게 한다.
RIB의 모든 Class와 RIB의 모든 Child에 대한 Builder들을 인스턴스화하는 역할을 수행한다.
클래스 생성 로직을 Builder에 분리함으로써 나머지 RIB 코드에서 DI(Dependency Injection)에 대해 신경쓰지 않게 한다.
Builder는 프로젝트에서 사용되는 DI 시스템에 대해 알게 만들어야하는 유일한 RIB 요소로써, 다른 Builder를 구현함으로써 다른 DI 메커니즘을 사용하는 프로젝트에서 RIB 코드의 나머지 부분(Ex. Interactor, Presenter)을 재사용하는 것이 가능해집니다.
business Model(Interactor) <-> View Model(View)로 변환하는 Stateless 클래스 입니다.
생략될 수 있으며, Presenter가 생략되면 Model변환은 VC, Interacot의 책임이 됩니다.
화면을 만들고 업데이트 하는 역할을 수행한다.
사용자의 Event를 정의한다.
애니메이션을 처리한다.
단위 테스트가 필요한 코드는 포함시키지 않는다.
RIB을 구축하는데 필요한 외부 Dependency에 대한 접근을 제공하며, RIB 자체에서 생성된 Depedency를 소유하고, 다른 RIB들로부터의 접근을 제어합니다.
(🐸 접근지정자를 통해 다른 RIB에서 사용하게 할건지)
RIB의 Depedency들을 관리하는데 사용한다.
Builder를 도와 RIB을 구성하는 다른 요소들을 인스턴스화 하는것을 돕는다.
부모 RIB의 Component는 자식 RIB의 Builder에 주입되어 자식 RIB이 부모 RIB의 Dependency에 접근할 수 있게 합니다.
App의 현재 State는 현재 RIB Tree에 붙어있는 RIB들로 관리되고 표현됩니다.
🐸 여기서 RIBs를 하면서 가장 헷갈릴것 같은 부분이 나온다. 지금까지 앱을 개발할 때 화면 단위로 기능을 쪼개서 생각했는데, RIBs는 앱의 현재 State에 따라 RIB을 나눠야 하니 화면 단위로 쪼개서 생각하던 버릇을 App의 현재 State로 쪼개서 생각하도록 의식해야겠다.
RIB Tree는 이런 형태입니다.
App의 현재 State는 현재 RIB Tree에 붙어있는 RIB들로 관리되고 표현됩니다.
RIB은 그 RIB의 범위내에서만 State 전환 결정할 수 있습니다.
예를들어 LoggedIn RIB은 Request, Menu, OnTrip RIB들간의 State 전환만 할 수 있고, OnTrip RIB에서의 동작에 대해서는 결정할 수 없습니다.
모든 State가 RIBs의 attach/detach로 저장되는것은 아닙니다.
예를들어 "사용자의 프로필 설정"이 변경되면 어떤 RIB도 attach/detach 되는게 아닙니다.
일반적으로 "프로필 설정"같은 값은 불변의 DataStream내부에 저장하며, 세부사항이 변경될 때 값들을 재발행 합니다.
LoggedIn RIB의 DataStream에 저장될 수 있습니다.
이 DataStream을 업데이트할 수 있는것은 Network Response만이 가능하고, 이 Stream에 대한 읽기 접근권을 제공하는 Interface를 DI 그래프 아래로 전달할 수 있습니다.
🐸 "사용자 프로필 설정"이 변경됐으니 State가 변경된거야! RIB을 새로 만들자! 라는 급발진을 막기위해 이런 Data 변경은 Rx의 Observable Stream으로 상위 RIB에 저장하고, 하위 RIB에서는 DI를 통해 접근권을 갖게하는 방식으로 풀 수 있다는것을 말하고 있습니다.
RIBs에는 RIB State에 대해 강제하는 single source of Truth가 없습니다.
(🐸 비즈니스로직이 중심이 되는 프레임워크이니만큼 해석하는 개발자 나름이기 때문에 RIB 상태에 대한 강제 사항은 없나봅니다.)
Interactor에서 비즈니스 로직을 수행한 후 다른 RIB으로 완료와 같은 Event를 알리거나, 데이터를 보내야 하는 경우가 있습니다.
RIB간에 데이터를 전달하는 방법이 정해져있는건 아니지만, 일반적인 패턴을 용이하게 하도록 구축되어 있습니다.
예를들어 이런 형태의 RIB Tree가 있을 때,
부모 RIB의 Interactor의 Interface가 자식 RIB의 Listner Interface를 conform 하여 부모 RIB의 Interactor에서 자식 RIB의 Listner Interface가 호출하는 listner.로직()을 구현하게 합니다.
이렇게하면 자식 RIB이 사라지더라도 부모 RIB이 listner Interface를 통해 이벤트에 반응할 수 있습니다.
Rx 스트림은 자식 RIB이 사라지면 같이 Dispose 될 수 있어 자식 -> 부모로의 Commuicate에서는 지양합니다.
추가로 이 방식의 이점은
LoggedOut RIB에서는 listner Interface 정의, Root RIB에서는 이를 채택, 구현합니다.